Files
pig-farm-controller/internal/infra/models/device_template.go

197 lines
7.7 KiB
Go
Raw Normal View History

2025-09-29 16:58:32 +08:00
package models
import (
"encoding/json"
"errors"
2025-09-29 23:46:28 +08:00
"fmt"
2025-09-29 16:58:32 +08:00
"gorm.io/datatypes"
)
// ModbusFunctionCode 定义Modbus功能码的枚举类型
type ModbusFunctionCode byte
// 定义常用的Modbus功能码常量及其应用场景
const (
// ReadCoils 读取线圈状态 (0x01)
// 场景: 用于读取数字量输出DO或内部标志位的当前状态这些状态通常是开关量。
ReadCoils ModbusFunctionCode = 0x01
// ReadDiscreteInputs 读取离散输入状态 (0x02)
// 场景: 用于读取数字量输入DI的当前状态这些状态通常是外部传感器的开关量信号。
ReadDiscreteInputs ModbusFunctionCode = 0x02
// ReadHoldingRegisters 读取保持寄存器 (0x03)
// 场景: 用于读取设备内部可读写的参数或数据,例如温度设定值、电机速度等模拟量或配置数据。
ReadHoldingRegisters ModbusFunctionCode = 0x03
// ReadInputRegisters 读取输入寄存器 (0x04)
// 场景: 用于读取设备的模拟量输入AI数据这些数据通常是只读的例如当前温度、压力、电压等实时测量值。
ReadInputRegisters ModbusFunctionCode = 0x04
// WriteSingleCoil 写入单个线圈 (0x05)
// 场景: 用于控制单个数字量输出DO例如打开或关闭一个继电器、指示灯等。
WriteSingleCoil ModbusFunctionCode = 0x05
// WriteSingleRegister 写入单个保持寄存器 (0x06)
// 场景: 用于修改设备内部的单个可写参数,例如设置一个温度控制器的目标温度、调整一个阀门的开度等。
WriteSingleRegister ModbusFunctionCode = 0x06
// WriteMultipleCoils 写入多个线圈 (0x0F)
// 场景: 用于批量控制多个数字量输出DO例如同时打开或关闭一组继电器。
WriteMultipleCoils ModbusFunctionCode = 0x0F
// WriteMultipleRegisters 写入多个保持寄存器 (0x10)
// 场景: 用于批量修改设备内部的多个可写参数,例如一次性更新多个配置参数或模拟量输出值。
WriteMultipleRegisters ModbusFunctionCode = 0x10
)
2025-09-29 23:46:28 +08:00
// DeviceCategory 定义了设备模板的宽泛类别
2025-09-29 16:58:32 +08:00
type DeviceCategory string
const (
// CategoryActuator 代表一个执行器,可以被控制(例如:风机、阀门)
2025-10-01 20:39:59 +08:00
CategoryActuator DeviceCategory = "执行器"
2025-09-29 16:58:32 +08:00
// CategorySensor 代表一个传感器,用于报告测量值(例如:温度计)
2025-10-01 20:39:59 +08:00
CategorySensor DeviceCategory = "传感器"
2025-09-29 16:58:32 +08:00
)
// ValueDescriptor 描述了传感器可以报告的单个数值。
// 它提供了必要的元数据,以便应用程序能够正确解释从设备读取的原始数据。
type ValueDescriptor struct {
2025-09-29 22:23:35 +08:00
Type SensorType `json:"type"`
2025-11-10 21:42:46 +08:00
Multiplier float32 `json:"multiplier"` // 乘数,用于原始数据转换
Offset float32 `json:"offset"` // 偏移量,用于原始数据转换
}
// --- 指令结构体 (Command Structs) ---
2025-09-29 23:46:28 +08:00
// SwitchCommands 定义了开关类指令所需的Modbus参数
type SwitchCommands struct {
2025-10-17 10:44:20 +08:00
// ModbusStartAddress 记录Modbus寄存器的起始地址用于生成指令。(一般是第三到四字节)
2025-09-29 23:46:28 +08:00
ModbusStartAddress uint16 `json:"modbus_start_address"`
2025-10-17 10:44:20 +08:00
// ModbusQuantity 记录Modbus寄存器的数量对于开关通常为1。(一般是五到六字节)
2025-09-29 23:46:28 +08:00
ModbusQuantity uint16 `json:"modbus_quantity"`
}
2025-09-29 23:46:28 +08:00
// SelfCheck 校验开关指令参数的有效性
func (sc *SwitchCommands) SelfCheck() error {
2025-09-29 23:46:28 +08:00
// 对于开关数量通常为1
if sc.ModbusQuantity != 1 {
return errors.New("'switch' 指令集 ModbusQuantity 必须为1")
}
return nil
}
2025-09-29 23:46:28 +08:00
// SensorCommands 定义了传感器读取指令所需的Modbus参数
type SensorCommands struct {
2025-10-17 10:44:20 +08:00
// ModbusFunctionCode 记录Modbus功能码例如 ReadHoldingRegisters。(一般是第二字节)
ModbusFunctionCode ModbusFunctionCode `json:"modbus_function_code"`
2025-10-17 10:44:20 +08:00
// ModbusStartAddress 记录Modbus寄存器的起始地址用于生成指令。(一般是第三到四字节)
2025-09-29 23:46:28 +08:00
ModbusStartAddress uint16 `json:"modbus_start_address"`
2025-10-17 10:44:20 +08:00
// ModbusQuantity 记录Modbus寄存器的数量用于生成指令。(一般是五到六字节)
2025-09-29 23:46:28 +08:00
ModbusQuantity uint16 `json:"modbus_quantity"`
2025-09-29 16:58:32 +08:00
}
2025-09-29 23:46:28 +08:00
// SelfCheck 校验读取指令参数的有效性
func (sc *SensorCommands) SelfCheck() error {
2025-09-29 23:46:28 +08:00
// 校验ModbusFunctionCode是否为读取类型
switch sc.ModbusFunctionCode {
case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters:
2025-09-29 23:46:28 +08:00
// 支持的读取功能码
default:
return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode)
}
// 校验ModbusQuantity的合理性例如不能为0且在常见Modbus读取数量限制内
if sc.ModbusQuantity == 0 || sc.ModbusQuantity > 125 {
return fmt.Errorf("'sensor' 指令集 ModbusQuantity 无效: %d, 必须在1-125之间", sc.ModbusQuantity)
}
return nil
2025-09-29 16:58:32 +08:00
}
// DeviceTemplate 代表一种物理设备的类型。
type DeviceTemplate struct {
2025-11-10 22:23:31 +08:00
Model
2025-09-29 16:58:32 +08:00
// Name 是此模板的唯一名称, 例如 "FanModel-XYZ-2000" 或 "TempSensor-T1"
Name string `gorm:"not null;unique" json:"name"`
// Manufacturer 是设备的制造商。
Manufacturer string `json:"manufacturer"`
// Description 提供了关于此设备类型的更多详细信息。
Description string `json:"description"`
2025-09-29 19:17:42 +08:00
// Category 将模板分类为传感器、执行器
2025-09-29 16:58:32 +08:00
Category DeviceCategory `gorm:"not null;index" json:"category"`
2025-09-29 23:46:28 +08:00
// Commands 存储了生成Modbus指令所需的参数而不是原始指令字符串。
2025-09-29 16:58:32 +08:00
// 使用 JSON 格式,具有良好的可扩展性。
2025-09-29 23:46:28 +08:00
// 例如,对于执行器 (开关): {"modbus_start_address": 0, "modbus_quantity": 1}
// 例如,对于传感器: {"modbus_function_code": 3, "modbus_start_address": 0, "modbus_quantity": 1}
2025-09-29 16:58:32 +08:00
Commands datatypes.JSON `json:"commands"`
// Values 描述了传感器模板所能提供的数据点。
2025-09-29 23:46:28 +08:00
// 当 Category 是 "sensor" 时,此字段尤为重要。
2025-09-29 16:58:32 +08:00
// 它是一个 ValueDescriptor 对象的 JSON 数组。
Values datatypes.JSON `json:"values"`
}
// TableName 自定义 GORM 使用的数据库表名
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 {
2025-09-29 23:46:28 +08:00
return errors.New("执行器模板的 Commands 无法被解析为 'switch' 指令集: " + err.Error())
}
if err := cmd.SelfCheck(); err != nil {
return err
}
case CategorySensor:
var cmd SensorCommands
if err := dt.ParseCommands(&cmd); err != nil {
2025-09-29 23:46:28 +08:00
return errors.New("传感器模板的 Commands 无法被解析为 'sensor' 指令集: " + err.Error())
}
if err := cmd.SelfCheck(); err != nil {
return err
}
if dt.Values == nil {
return errors.New("传感器类型的设备模板缺少 Values 定义")
}
2025-09-29 23:46:28 +08:00
// 这里应该解析为 ValueDescriptor 的切片,因为传感器可能提供多个数据点
var values []ValueDescriptor
if err := dt.ParseValues(&values); err != nil {
2025-09-29 23:46:28 +08:00
return errors.New("无法解析传感器模板的 Values 属性: " + err.Error())
}
if len(values) == 0 {
return errors.New("传感器类型的设备模板 Values 属性不能为空")
}
default:
return errors.New("未知的设备模板类别")
}
return nil
}