2025-09-12 16:08:39 +08:00
|
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"errors"
|
2025-09-29 17:06:21 +08:00
|
|
|
|
"strings"
|
2025-09-12 16:08:39 +08:00
|
|
|
|
|
|
|
|
|
|
"gorm.io/datatypes"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// --- Properties 结构体定义 ---
|
|
|
|
|
|
|
2025-09-29 23:46:28 +08:00
|
|
|
|
// Bus485Properties 定义了总线设备的特有属性
|
|
|
|
|
|
type Bus485Properties struct {
|
|
|
|
|
|
BusNumber uint8 `json:"bus_number"` // 485 总线号
|
|
|
|
|
|
BusAddress uint8 `json:"bus_address"` // 485 总线地址
|
2025-09-29 16:58:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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"`
|
2025-09-12 16:08:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-29 17:06:21 +08:00
|
|
|
|
// SelfCheck 对 AreaController 的关键字段进行业务逻辑验证。
|
|
|
|
|
|
func (ac *AreaController) SelfCheck() error {
|
|
|
|
|
|
if strings.TrimSpace(ac.NetworkID) == "" {
|
|
|
|
|
|
return errors.New("区域主控的 NetworkID 不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-29 16:58:32 +08:00
|
|
|
|
// TableName 自定义 GORM 使用的数据库表名
|
|
|
|
|
|
func (AreaController) TableName() string {
|
|
|
|
|
|
return "area_controllers"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Device 代表系统中的所有普通设备
|
2025-09-12 16:08:39 +08:00
|
|
|
|
type Device struct {
|
|
|
|
|
|
// gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
|
|
|
|
|
|
gorm.Model
|
|
|
|
|
|
|
2025-09-29 16:58:32 +08:00
|
|
|
|
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器"
|
2025-09-20 17:11:04 +08:00
|
|
|
|
Name string `gorm:"not null" json:"name"`
|
2025-09-12 16:08:39 +08:00
|
|
|
|
|
2025-09-29 16:58:32 +08:00
|
|
|
|
// DeviceTemplateID 是设备模板的外键
|
|
|
|
|
|
DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"`
|
2025-09-12 16:08:39 +08:00
|
|
|
|
|
2025-09-29 16:58:32 +08:00
|
|
|
|
// DeviceTemplate 是设备的模板,包含了设备的通用信息
|
|
|
|
|
|
DeviceTemplate DeviceTemplate `json:"device_template"`
|
2025-09-12 16:08:39 +08:00
|
|
|
|
|
2025-09-29 16:58:32 +08:00
|
|
|
|
// AreaControllerID 是区域主控的外键
|
|
|
|
|
|
AreaControllerID uint `gorm:"not null;index" json:"area_controller_id"`
|
2025-09-12 16:08:39 +08:00
|
|
|
|
|
2025-09-29 16:58:32 +08:00
|
|
|
|
// AreaController 是设备所属的区域主控
|
|
|
|
|
|
AreaController AreaController `json:"area_controller"`
|
2025-09-26 22:50:08 +08:00
|
|
|
|
|
2025-09-29 17:06:21 +08:00
|
|
|
|
// Location 描述了设备的物理安装位置,例如 "1号猪舍东侧",方便运维。建立索引以优化按位置查询。
|
|
|
|
|
|
Location string `gorm:"index" json:"location"`
|
|
|
|
|
|
|
2025-09-12 16:08:39 +08:00
|
|
|
|
// Properties 用于存储特定类型设备的独有属性,采用JSON格式。
|
2025-09-29 23:46:28 +08:00
|
|
|
|
// 建议在应用层为不同子类型的设备定义专用的属性结构体,以保证数据一致性。
|
2025-09-12 16:08:39 +08:00
|
|
|
|
Properties datatypes.JSON `json:"properties"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-29 17:06:21 +08:00
|
|
|
|
// SelfCheck 对 Device 的关键字段和属性进行业务逻辑验证。
|
|
|
|
|
|
func (d *Device) SelfCheck() error {
|
|
|
|
|
|
if d.AreaControllerID == 0 {
|
|
|
|
|
|
return errors.New("设备必须关联一个区域主控 (AreaControllerID不能为0)")
|
|
|
|
|
|
}
|
|
|
|
|
|
if d.DeviceTemplateID == 0 {
|
|
|
|
|
|
return errors.New("设备必须关联一个设备模板 (DeviceTemplateID不能为0)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证 Properties 是否包含必要的总线地址信息
|
|
|
|
|
|
if d.Properties == nil {
|
|
|
|
|
|
return errors.New("设备属性 (Properties) 不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-29 23:46:28 +08:00
|
|
|
|
var props Bus485Properties
|
2025-09-29 17:06:21 +08:00
|
|
|
|
if err := json.Unmarshal(d.Properties, &props); err != nil {
|
|
|
|
|
|
return errors.New("无法解析设备属性 (Properties)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-29 23:46:28 +08:00
|
|
|
|
if props.BusAddress == 0 {
|
2025-09-29 17:06:21 +08:00
|
|
|
|
return errors.New("设备属性 (Properties) 中缺少总线地址 (bus_address)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 16:08:39 +08:00
|
|
|
|
// 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)
|
|
|
|
|
|
}
|