From facbbfe6a1ddb816e474a66ccb7b7107c43e41b2 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 29 Sep 2025 16:58:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AE=BE=E5=A4=87=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/models/device.go | 146 +++++++---------------- internal/infra/models/device_template.go | 75 ++++++++++++ 2 files changed, 117 insertions(+), 104 deletions(-) create mode 100644 internal/infra/models/device_template.go diff --git a/internal/infra/models/device.go b/internal/infra/models/device.go index c3cc889..86279cb 100644 --- a/internal/infra/models/device.go +++ b/internal/infra/models/device.go @@ -6,41 +6,6 @@ import ( "gorm.io/datatypes" "gorm.io/gorm" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils" -) - -// 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" - // SubTypeSensorWeight 电子秤 - SubTypeSensorWeight DeviceSubType = "weight" - - // SubTypeValveFeed 下料阀门 - SubTypeValveFeed DeviceSubType = "feed_valve" - // SubTypeFan 风机 - SubTypeFan DeviceSubType = "fan" - // SubTypeWaterCurtain 水帘 - SubTypeWaterCurtain DeviceSubType = "water_curtain" ) // 设备属性名大全 @@ -64,34 +29,59 @@ type LoraProperties struct { // BusProperties 定义了总线设备的特有属性 type BusProperties struct { - BusID int `json:"bus_id"` // 485 总线号 - BusAddress int `json:"bus_address"` // 485 总线地址 + BusID int `json:"bus_id"` // 485 总线号 + BusAddress int `json:"bus_address"` // 485 总线地址 + RelayChannel int `json:"relay_channel"` // 继电器通道号 } -// Device 代表系统中的所有设备 +// AreaController 是一个LoRa转总线(如485)的通信网关 +type AreaController struct { + gorm.Model + + // Name 是主控的业务名称,例如 "1号猪舍主控" + Name string `gorm:"not null;unique" json:"name"` + + // NetworkID 是主控在通信网络中的唯一标识,例如 LoRaWAN 的 DevEUI。 + // 这是 transport 层用来寻址的关键。 + NetworkID string `gorm:"not null;unique;index" json:"network_id"` + + // Location 描述了主控的物理安装位置。 + Location string `gorm:"index" json:"location"` + + // Status 表示主控的在线状态等,可以后续扩展。 + Status string `gorm:"default:'unknown'" json:"status"` + + // Properties 用于存储其他与主控相关的属性,例如硬件版本、固件版本等。 + Properties datatypes.JSON `json:"properties"` +} + +// TableName 自定义 GORM 使用的数据库表名 +func (AreaController) TableName() string { + return "area_controllers" +} + +// Device 代表系统中的所有普通设备 type Device struct { // gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt) gorm.Model - // Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" 或 "做料车间主控" + // Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" Name string `gorm:"not null" json:"name"` - // 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"` - // Command 存储了与设备交互所需的具体指令。 - // 例如,对于传感器,这里存储 Modbus 采集指令;对于开关和区域主控,这里可以为空。 - Command string `gorm:"type:varchar(255)" json:"command"` + // DeviceTemplateID 是设备模板的外键 + DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"` + + // DeviceTemplate 是设备的模板,包含了设备的通用信息 + DeviceTemplate DeviceTemplate `json:"device_template"` + + // AreaControllerID 是区域主控的外键 + AreaControllerID uint `gorm:"not null;index" json:"area_controller_id"` + + // AreaController 是设备所属的区域主控 + AreaController AreaController `json:"area_controller"` // Properties 用于存储特定类型设备的独有属性,采用JSON格式。 // 建议在应用层为不同子类型的设备定义专用的属性结构体(如 LoraProperties, BusProperties),以保证数据一致性。 @@ -115,55 +105,3 @@ func (d *Device) ParseProperties(v interface{}) error { } return json.Unmarshal(d.Properties, v) } - -// SelfCheck 进行参数自检, 返回检测结果 -// 方法会根据自身类型进行参数检查, 参数不全时返回false -func (d *Device) SelfCheck() bool { - // 使用清晰的 switch 结构,确保所有情况都被覆盖 - switch d.Type { - case DeviceTypeAreaController: - props := make(map[string]interface{}) - if err := d.ParseProperties(&props); err != nil { - return false - } - _, ok := props[LoRaAddress].(string) - return ok - - case DeviceTypeDevice: - // 所有普通设备都必须有父级 - if d.ParentID == nil || *d.ParentID == 0 { - return false - } - - props := make(map[string]interface{}) - if err := d.ParseProperties(&props); err != nil { - return false - } - - // hasPureNumeric 检查一个key是否存在于map中,并且其值是纯数字(整数或可解析为整数的字符串) - hasPureNumeric := func(key string) bool { - val, ok := props[key] - if !ok { - return false // Key不存在 - } - return utils.IsPureNumeric(val) - } - - // 根据子类型进行具体校验 - switch d.SubType { - // 所有传感器类型都必须有 Command 和总线信息,且总线信息为纯数字 - case SubTypeSensorTemp, SubTypeSensorHumidity, SubTypeSensorWeight, SubTypeSensorAmmonia: - return d.Command != "" && hasPureNumeric(BusNumber) && hasPureNumeric(BusAddress) - // 所有开关类型都必须有继电器和总线信息,且都为纯数字 - case SubTypeFan, SubTypeWaterCurtain, SubTypeValveFeed: - return hasPureNumeric(BusNumber) && hasPureNumeric(BusAddress) && hasPureNumeric(RelayChannel) - // 如果是未知的子类型,或者没有子类型,则认为自检失败 - default: - return false - } - - // 如果设备类型不是已知的任何一种,则自检失败 - default: - return false - } -} diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go new file mode 100644 index 0000000..d3a3549 --- /dev/null +++ b/internal/infra/models/device_template.go @@ -0,0 +1,75 @@ +package models + +import ( + "gorm.io/datatypes" + "gorm.io/gorm" +) + +// DeviceCategory 定义了设备模板的宽泛类别 +type DeviceCategory string + +const ( + // CategoryActuator 代表一个执行器,可以被控制(例如:风机、阀门) + CategoryActuator DeviceCategory = "actuator" + // CategorySensor 代表一个传感器,用于报告测量值(例如:温度计) + CategorySensor DeviceCategory = "sensor" + // CategoryCompound 代表一个复合设备,既是执行器也是传感器 + CategoryCompound DeviceCategory = "compound" +) + +// ValueDescriptor 描述了传感器可以报告的单个数值。 +// 它提供了必要的元数据,以便应用程序能够正确解释从设备读取的原始数据。 +type ValueDescriptor struct { + Name string `json:"name"` // 值的业务名称, 例如 "temperature", "humidity" + Unit string `json:"unit"` // 测量单位, 例如 "°C", "%RH", "ppm" + DataType string `json:"data_type"` // 期望的数据类型, 例如 "float", "int", "boolean" + Multiplier float64 `json:"multiplier"` // 乘以原始值的系数 (例如 0.1) + Offset float64 `json:"offset"` // 乘法之后再增加的偏移量 (最终值 = 原始值 * multiplier + offset) +} + +// DeviceCommands 定义了设备模板支持的指令集合。 +// 使用指针类型来表示指令的可选性,如果一个模板不支持某个指令,则该字段为 nil。 +// json tag 中的 "omitempty" 确保了在序列化回 JSON 时,nil 字段会被省略,保持数据库数据的整洁。 +type DeviceCommands struct { + On *string `json:"on,omitempty"` // 开指令 + Off *string `json:"off,omitempty"` // 关指令 + Read *string `json:"read,omitempty"` // 读取传感器数值的指令 + + // 为了未来的扩展性,可以预留一些通用指令 + SetSpeed *string `json:"set_speed,omitempty"` // 设置速度/档位 + SetValue *string `json:"set_value,omitempty"` // 设置某个具体值(例如设定温度) +} + +// DeviceTemplate 代表一种物理设备的类型。 +// 它作为一个蓝图,定义了设备的通用属性、操作指令和数据解释规则。 +type DeviceTemplate struct { + gorm.Model + + // Name 是此模板的唯一名称, 例如 "FanModel-XYZ-2000" 或 "TempSensor-T1" + Name string `gorm:"not null;unique" json:"name"` + + // Manufacturer 是设备的制造商。 + Manufacturer string `json:"manufacturer"` + + // Description 提供了关于此设备类型的更多详细信息。 + Description string `json:"description"` + + // Category 将模板分类为传感器、执行器或复合设备。 + Category DeviceCategory `gorm:"not null;index" json:"category"` + + // Commands 存储了一个从“动作名称”到“原始指令”的映射。 + // 使用 JSON 格式,具有良好的可扩展性。 + // 例如,对于风机: {"ON": "01050000FF008C3A", "OFF": "010500000000CDCA"} + // 例如,对于传感器: {"READ": "010300000001840A"} + Commands datatypes.JSON `json:"commands"` + + // Values 描述了传感器模板所能提供的数据点。 + // 当 Category 是 "sensor" 或 "compound" 时,此字段尤为重要。 + // 它是一个 ValueDescriptor 对象的 JSON 数组。 + Values datatypes.JSON `json:"values"` +} + +// TableName 自定义 GORM 使用的数据库表名 +func (DeviceTemplate) TableName() string { + return "device_templates" +}