diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index d3a3549..b91405e 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -1,11 +1,14 @@ package models import ( + "encoding/json" + "errors" + "gorm.io/datatypes" "gorm.io/gorm" ) -// DeviceCategory 定义了设备模板的宽泛类别 +// DeviceCategory 定义了设备模板的宽泛类别 (移除了 Compound) type DeviceCategory string const ( @@ -13,35 +16,51 @@ const ( 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) + Name string `json:"name"` + Unit string `json:"unit"` + DataType string `json:"data_type"` + Multiplier float64 `json:"multiplier"` + Offset float64 `json:"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"` // 读取传感器数值的指令 +// --- 指令结构体 (Command Structs) --- - // 为了未来的扩展性,可以预留一些通用指令 - SetSpeed *string `json:"set_speed,omitempty"` // 设置速度/档位 - SetValue *string `json:"set_value,omitempty"` // 设置某个具体值(例如设定温度) +// SwitchCommands 定义了开关类指令 +type SwitchCommands struct { + On string `json:"on"` + Off string `json:"off"` +} + +// SelfCheck 校验开关指令的有效性 +func (sc *SwitchCommands) SelfCheck() error { + if sc.On == "" { + return errors.New("'switch' 指令集缺少 'on' 指令") + } + if sc.Off == "" { + return errors.New("'switch' 指令集缺少 'off' 指令") + } + return nil +} + +// SensorCommands 定义了传感器读取指令 +type SensorCommands struct { + Read string `json:"read"` +} + +// SelfCheck 校验读取指令的有效性 +func (sc *SensorCommands) SelfCheck() error { + if sc.Read == "" { + return errors.New("'sensor' 指令集缺少 'read' 指令") + } + return nil } // DeviceTemplate 代表一种物理设备的类型。 -// 它作为一个蓝图,定义了设备的通用属性、操作指令和数据解释规则。 type DeviceTemplate struct { gorm.Model @@ -73,3 +92,63 @@ type DeviceTemplate struct { func (DeviceTemplate) TableName() string { return "device_templates" } + +// ParseCommands ... +func (dt *DeviceTemplate) ParseCommands(v interface{}) error { + if dt.Commands == nil { + return errors.New("设备模板的 Commands 属性为空,无法解析") + } + return json.Unmarshal(dt.Commands, v) +} + +// ParseValues ... +func (dt *DeviceTemplate) ParseValues(v interface{}) error { + if dt.Values == nil { + return errors.New("设备模板的 Values 属性为空,无法解析") + } + return json.Unmarshal(dt.Values, v) +} + +// SelfCheck 对 DeviceTemplate 进行彻底的、基于角色的校验 +func (dt *DeviceTemplate) SelfCheck() error { + if dt.Commands == nil { + return errors.New("所有设备模板都必须有 Commands 定义") + } + + switch dt.Category { + case CategoryActuator: + var cmd SwitchCommands + if err := dt.ParseCommands(&cmd); err != nil { + return errors.New("执行器模板的 Commands 无法被解析为 'switch' 指令集") + } + if err := cmd.SelfCheck(); err != nil { + return err + } + + case CategorySensor: + var cmd SensorCommands + if err := dt.ParseCommands(&cmd); err != nil { + return errors.New("传感器模板的 Commands 无法被解析为 'sensor' 指令集") + } + if err := cmd.SelfCheck(); err != nil { + return err + } + + if dt.Values == nil { + return errors.New("传感器类型的设备模板缺少 Values 定义") + } + var values []*ValueDescriptor + if err := dt.ParseValues(&values); err != nil { + return errors.New("无法解析传感器模板的 Values 属性") + } + // 黄金准则: 一个传感器模板只能定义一种数值的解析方式 + if len(values) != 1 { + return errors.New("传感器模板的 Values 定义必须且只能包含一个描述符") + } + + default: + return errors.New("未知的设备模板类别") + } + + return nil +}