issue_25 #26
@@ -6,41 +6,6 @@ import (
|
|||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"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 定义了总线设备的特有属性
|
// BusProperties 定义了总线设备的特有属性
|
||||||
type BusProperties struct {
|
type BusProperties struct {
|
||||||
BusID int `json:"bus_id"` // 485 总线号
|
BusID int `json:"bus_id"` // 485 总线号
|
||||||
BusAddress int `json:"bus_address"` // 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 {
|
type Device struct {
|
||||||
// gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
|
// gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
|
||||||
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" 或 "做料车间主控"
|
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器"
|
||||||
Name string `gorm:"not null" json:"name"`
|
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 描述了设备的物理安装位置,例如 "1号猪舍东侧",方便运维。建立索引以优化按位置查询。
|
||||||
Location string `gorm:"index" json:"location"`
|
Location string `gorm:"index" json:"location"`
|
||||||
|
|
||||||
// Command 存储了与设备交互所需的具体指令。
|
// DeviceTemplateID 是设备模板的外键
|
||||||
// 例如,对于传感器,这里存储 Modbus 采集指令;对于开关和区域主控,这里可以为空。
|
DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"`
|
||||||
Command string `gorm:"type:varchar(255)" json:"command"`
|
|
||||||
|
// 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格式。
|
// Properties 用于存储特定类型设备的独有属性,采用JSON格式。
|
||||||
// 建议在应用层为不同子类型的设备定义专用的属性结构体(如 LoraProperties, BusProperties),以保证数据一致性。
|
// 建议在应用层为不同子类型的设备定义专用的属性结构体(如 LoraProperties, BusProperties),以保证数据一致性。
|
||||||
@@ -115,55 +105,3 @@ func (d *Device) ParseProperties(v interface{}) error {
|
|||||||
}
|
}
|
||||||
return json.Unmarshal(d.Properties, v)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
75
internal/infra/models/device_template.go
Normal file
75
internal/infra/models/device_template.go
Normal file
@@ -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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user