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

171 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package models
import (
"encoding/json"
"errors"
"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"
)
// 设备属性名大全
var (
// 普通开关式设备
BusNumber = "bus_number" // 总线号
BusAddress = "bus_address" // 总线地址
RelayChannel = "relay_channel" // 继电器通道号
// 区域主控
LoRaAddress = "lora_address" // 区域主控 LoRa 地址, 如果使用LoRa网关也可能是LoRa网关记录的设备ID
)
// --- Properties 结构体定义 ---
// LoraProperties 定义了区域主控的特有属性
type LoraProperties struct {
LoraAddress string `json:"lora_address"` // LoRa 地址
}
// BusProperties 定义了总线设备的特有属性
type BusProperties struct {
BusID int `json:"bus_id"` // 485 总线号
BusAddress int `json:"bus_address"` // 485 总线地址
}
// Device 代表系统中的所有设备
type Device struct {
// gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
gorm.Model
// 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"`
// Properties 用于存储特定类型设备的独有属性采用JSON格式。
// 建议在应用层为不同子类型的设备定义专用的属性结构体(如 LoraProperties, BusProperties以保证数据一致性。
Properties datatypes.JSON `json:"properties"`
}
// TableName 自定义 GORM 使用的数据库表名
func (Device) TableName() string {
return "devices"
}
// ParseProperties 解析 JSON 属性到一个具体的结构体中。
// 调用方需要传入一个指向目标结构体实例的指针。
// 示例:
//
// var props LoraProperties
// if err := device.ParseProperties(&props); err != nil { ... }
func (d *Device) ParseProperties(v interface{}) error {
if d.Properties == nil {
return errors.New("设备属性为空,无法解析")
}
return json.Unmarshal(d.Properties, v)
}
// SelfCheck 进行参数自检, 返回检测结果
// 方法会根据自身类型进行参数检查, 参数不全时返回false
// TODO 没写单测
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
}
}