2025-09-12 16:08:39 +08:00
|
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
|
|
|
|
"gorm.io/datatypes"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// DeviceType 定义了设备的高级类别
|
|
|
|
|
|
type DeviceType string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// DeviceTypeAreaController 区域主控,负责管理一个片区的设备
|
|
|
|
|
|
DeviceTypeAreaController DeviceType = "area_controller"
|
|
|
|
|
|
// DeviceTypeDevice 普通设备,如传感器、阀门等
|
|
|
|
|
|
DeviceTypeDevice DeviceType = "device"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// DeviceSubType 定义了普通设备的具体子类别
|
|
|
|
|
|
type DeviceSubType string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// SubTypeNone 未指定或不适用的子类型
|
|
|
|
|
|
SubTypeNone DeviceSubType = ""
|
|
|
|
|
|
// SubTypeSensorTemp 温度传感器
|
|
|
|
|
|
SubTypeSensorTemp DeviceSubType = "temperature"
|
|
|
|
|
|
// SubTypeSensorHumidity 湿度传感器
|
|
|
|
|
|
SubTypeSensorHumidity DeviceSubType = "humidity"
|
|
|
|
|
|
// SubTypeSensorAmmonia 氨气传感器
|
|
|
|
|
|
SubTypeSensorAmmonia DeviceSubType = "ammonia"
|
|
|
|
|
|
|
|
|
|
|
|
// SubTypeValveFeed 下料阀门
|
|
|
|
|
|
SubTypeValveFeed DeviceSubType = "feed_valve"
|
|
|
|
|
|
// SubTypeFan 风机
|
|
|
|
|
|
SubTypeFan DeviceSubType = "fan"
|
|
|
|
|
|
// SubTypeWaterCurtain 水帘
|
|
|
|
|
|
SubTypeWaterCurtain DeviceSubType = "water_curtain"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-16 17:12:27 +08:00
|
|
|
|
// 设备属性名大全
|
|
|
|
|
|
var (
|
|
|
|
|
|
|
|
|
|
|
|
// 普通开关式设备
|
|
|
|
|
|
BusNumber = "bus_number" // 总线号
|
|
|
|
|
|
BusAddress = "bus_address" // 总线地址
|
|
|
|
|
|
RelayChannel = "relay_channel" // 继电器通道号
|
|
|
|
|
|
|
|
|
|
|
|
// 区域主控
|
|
|
|
|
|
LoRaAddress = "lora_address" // 区域主控 LoRa 地址, 如果使用LoRa网关也可能是LoRa网关记录的设备ID
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-12 16:08:39 +08:00
|
|
|
|
// --- Properties 结构体定义 ---
|
|
|
|
|
|
|
|
|
|
|
|
// LoraProperties 定义了区域主控的特有属性
|
|
|
|
|
|
type LoraProperties struct {
|
|
|
|
|
|
LoraAddress string `json:"lora_address"` // LoRa 地址
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// BusProperties 定义了总线设备的特有属性
|
|
|
|
|
|
type BusProperties struct {
|
|
|
|
|
|
BusID int `json:"bus_id"` // 485 总线号
|
|
|
|
|
|
BusAddress int `json:"bus_address"` // 485 总线地址
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Device 代表系统中的所有设备
|
|
|
|
|
|
type Device struct {
|
|
|
|
|
|
// gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
|
|
|
|
|
|
gorm.Model
|
|
|
|
|
|
|
|
|
|
|
|
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" 或 "做料车间主控"
|
2025-09-20 17:11:04 +08:00
|
|
|
|
Name string `gorm:"not null" json:"name"`
|
2025-09-12 16:08:39 +08:00
|
|
|
|
|
|
|
|
|
|
// Type 是设备的高级类别,用于区分区域主控和普通设备。建立索引以优化按类型查询。
|
|
|
|
|
|
Type DeviceType `gorm:"not null;index" json:"type"`
|
|
|
|
|
|
|
|
|
|
|
|
// SubType 是设备的子类别,用于描述普通设备的具体功能,例如 "temperature", "fan" 等。建立索引以优化按子类型查询。
|
|
|
|
|
|
SubType DeviceSubType `gorm:"index" json:"sub_type"`
|
|
|
|
|
|
|
|
|
|
|
|
// ParentID 指向其父级设备的ID。对于顶层设备(如区域主控),此值为 NULL。
|
|
|
|
|
|
// 使用指针类型 *uint 来允许 NULL 值,从而清晰地表示“无父级”,避免了使用 0 作为魔术数字的歧义。建立索引以优化层级查询。
|
|
|
|
|
|
ParentID *uint `gorm:"index" json:"parent_id"`
|
|
|
|
|
|
|
|
|
|
|
|
// Location 描述了设备的物理安装位置,例如 "1号猪舍东侧",方便运维。建立索引以优化按位置查询。
|
|
|
|
|
|
Location string `gorm:"index" json:"location"`
|
|
|
|
|
|
|
|
|
|
|
|
// Properties 用于存储特定类型设备的独有属性,采用JSON格式。
|
|
|
|
|
|
// 建议在应用层为不同子类型的设备定义专用的属性结构体(如 LoraProperties, BusProperties),以保证数据一致性。
|
|
|
|
|
|
Properties datatypes.JSON `json:"properties"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TableName 自定义 GORM 使用的数据库表名
|
|
|
|
|
|
func (Device) TableName() string {
|
|
|
|
|
|
return "devices"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ParseProperties 解析 JSON 属性到一个具体的结构体中。
|
|
|
|
|
|
// 调用方需要传入一个指向目标结构体实例的指针。
|
|
|
|
|
|
// 示例:
|
|
|
|
|
|
//
|
|
|
|
|
|
// var props LoraProperties
|
|
|
|
|
|
// if err := device.ParseProperties(&props); err != nil { ... }
|
|
|
|
|
|
func (d *Device) ParseProperties(v interface{}) error {
|
|
|
|
|
|
if d.Properties == nil {
|
|
|
|
|
|
return errors.New("设备属性为空,无法解析")
|
|
|
|
|
|
}
|
|
|
|
|
|
return json.Unmarshal(d.Properties, v)
|
|
|
|
|
|
}
|
2025-09-16 17:12:27 +08:00
|
|
|
|
|
|
|
|
|
|
// SelfCheck 进行参数自检, 返回检测结果
|
|
|
|
|
|
// 方法会根据自身类型进行参数检查, 参数不全时返回false
|
|
|
|
|
|
// TODO 没写单测
|
|
|
|
|
|
func (d *Device) SelfCheck() bool {
|
|
|
|
|
|
|
|
|
|
|
|
properties := make(map[string]interface{})
|
|
|
|
|
|
if err := d.ParseProperties(&properties); err != nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
has := func(key string) bool {
|
|
|
|
|
|
_, ok := properties[key]
|
|
|
|
|
|
return ok
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch d.SubType {
|
|
|
|
|
|
case SubTypeFan:
|
|
|
|
|
|
if !has(BusNumber) || !has(BusAddress) || !has(RelayChannel) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 不应该有类型未知的设备
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|