168 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package models
 | ||
| 
 | ||
| import (
 | ||
| 	"encoding/json"
 | ||
| 	"errors"
 | ||
| 	"fmt"
 | ||
| 
 | ||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/command_generater"
 | ||
| 	"gorm.io/datatypes"
 | ||
| 	"gorm.io/gorm"
 | ||
| )
 | ||
| 
 | ||
| // DeviceCategory 定义了设备模板的宽泛类别
 | ||
| type DeviceCategory string
 | ||
| 
 | ||
| const (
 | ||
| 	// CategoryActuator 代表一个执行器,可以被控制(例如:风机、阀门)
 | ||
| 	CategoryActuator DeviceCategory = "执行器"
 | ||
| 	// CategorySensor 代表一个传感器,用于报告测量值(例如:温度计)
 | ||
| 	CategorySensor DeviceCategory = "传感器"
 | ||
| )
 | ||
| 
 | ||
| // ValueDescriptor 描述了传感器可以报告的单个数值。
 | ||
| // 它提供了必要的元数据,以便应用程序能够正确解释从设备读取的原始数据。
 | ||
| type ValueDescriptor struct {
 | ||
| 	Type       SensorType `json:"type"`
 | ||
| 	Multiplier float64    `json:"multiplier"` // 乘数,用于原始数据转换
 | ||
| 	Offset     float64    `json:"offset"`     // 偏移量,用于原始数据转换
 | ||
| }
 | ||
| 
 | ||
| // --- 指令结构体 (Command Structs) ---
 | ||
| 
 | ||
| // SwitchCommands 定义了开关类指令所需的Modbus参数
 | ||
| type SwitchCommands struct {
 | ||
| 	// ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。
 | ||
| 	ModbusStartAddress uint16 `json:"modbus_start_address"`
 | ||
| 	// ModbusQuantity 记录Modbus寄存器的数量,对于开关通常为1。
 | ||
| 	ModbusQuantity uint16 `json:"modbus_quantity"`
 | ||
| }
 | ||
| 
 | ||
| // SelfCheck 校验开关指令参数的有效性
 | ||
| func (sc *SwitchCommands) SelfCheck() error {
 | ||
| 	// 对于开关,数量通常为1
 | ||
| 	if sc.ModbusQuantity != 1 {
 | ||
| 		return errors.New("'switch' 指令集 ModbusQuantity 必须为1")
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // SensorCommands 定义了传感器读取指令所需的Modbus参数
 | ||
| type SensorCommands struct {
 | ||
| 	// ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。
 | ||
| 	ModbusFunctionCode command_generater.ModbusFunctionCode `json:"modbus_function_code"`
 | ||
| 	// ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。
 | ||
| 	ModbusStartAddress uint16 `json:"modbus_start_address"`
 | ||
| 	// ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。
 | ||
| 	ModbusQuantity uint16 `json:"modbus_quantity"`
 | ||
| }
 | ||
| 
 | ||
| // SelfCheck 校验读取指令参数的有效性
 | ||
| func (sc *SensorCommands) SelfCheck() error {
 | ||
| 	// 校验ModbusFunctionCode是否为读取类型
 | ||
| 	switch sc.ModbusFunctionCode {
 | ||
| 	case command_generater.ReadCoils, command_generater.ReadDiscreteInputs, command_generater.ReadHoldingRegisters, command_generater.ReadInputRegisters:
 | ||
| 		// 支持的读取功能码
 | ||
| 	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
 | ||
| }
 | ||
| 
 | ||
| // 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 存储了生成Modbus指令所需的参数,而不是原始指令字符串。
 | ||
| 	// 使用 JSON 格式,具有良好的可扩展性。
 | ||
| 	// 例如,对于执行器 (开关): {"modbus_start_address": 0, "modbus_quantity": 1}
 | ||
| 	// 例如,对于传感器: {"modbus_function_code": 3, "modbus_start_address": 0, "modbus_quantity": 1}
 | ||
| 	Commands datatypes.JSON `json:"commands"`
 | ||
| 
 | ||
| 	// Values 描述了传感器模板所能提供的数据点。
 | ||
| 	// 当 Category 是 "sensor" 时,此字段尤为重要。
 | ||
| 	// 它是一个 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 {
 | ||
| 			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 {
 | ||
| 			return errors.New("传感器模板的 Commands 无法被解析为 'sensor' 指令集: " + err.Error())
 | ||
| 		}
 | ||
| 		if err := cmd.SelfCheck(); err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 
 | ||
| 		if dt.Values == nil {
 | ||
| 			return errors.New("传感器类型的设备模板缺少 Values 定义")
 | ||
| 		}
 | ||
| 		// 这里应该解析为 ValueDescriptor 的切片,因为传感器可能提供多个数据点
 | ||
| 		var values []ValueDescriptor
 | ||
| 		if err := dt.ParseValues(&values); err != nil {
 | ||
| 			return errors.New("无法解析传感器模板的 Values 属性: " + err.Error())
 | ||
| 		}
 | ||
| 		if len(values) == 0 {
 | ||
| 			return errors.New("传感器类型的设备模板 Values 属性不能为空")
 | ||
| 		}
 | ||
| 	default:
 | ||
| 		return errors.New("未知的设备模板类别")
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 |