Files
pig-farm-controller/internal/controller/device/device.go
2025-09-08 20:28:26 +08:00

439 lines
13 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 device 提供设备控制相关功能的控制器
// 实现设备控制、查询等操作
package device
import (
"encoding/json"
"strconv"
"git.huangwc.com/pig/pig-farm-controller/internal/api/middleware"
"git.huangwc.com/pig/pig-farm-controller/internal/controller"
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/model"
"git.huangwc.com/pig/pig-farm-controller/internal/service"
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
"github.com/gin-gonic/gin"
)
// ListResponse 设备列表响应结构体
type ListResponse struct {
Devices []model.Device `json:"devices"`
}
// DeviceRequest 设备创建/更新请求结构体
type DeviceRequest struct {
Name string `json:"name" binding:"required"` // 设备名称,必填
Type model.DeviceType `json:"type" binding:"required"` // 设备类型,必填
ParentID *uint `json:"parent_id,omitempty"` // 父设备ID可选
Address *string `json:"address,omitempty"` // 设备地址,可选
// 485总线设备的额外字段
BusNumber *int `json:"bus_number,omitempty"` // 485总线号
DeviceAddress *string `json:"device_address,omitempty"` // 485设备地址
}
// BindAndValidate 绑定并验证请求数据
func (req *DeviceRequest) BindAndValidate(data []byte) error {
// 创建一个map来解析原始JSON
raw := make(map[string]interface{})
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
// 解析已知字段
if name, ok := raw["name"].(string); ok {
req.Name = name
}
if typ, ok := raw["type"].(string); ok {
req.Type = model.DeviceType(typ)
}
// 特殊处理parent_id字段
if parentIDVal, exists := raw["parent_id"]; exists && parentIDVal != nil {
switch v := parentIDVal.(type) {
case float64:
// JSON数字默认是float64类型
if v >= 0 {
parentID := uint(v)
req.ParentID = &parentID
}
case string:
// 如果是字符串尝试转换为uint
if v != "" && v != "null" {
if parentID, err := strconv.ParseUint(v, 10, 32); err == nil {
parentIDUint := uint(parentID)
req.ParentID = &parentIDUint
}
}
}
}
// 特殊处理address字段
if addressVal, exists := raw["address"]; exists && addressVal != nil {
switch v := addressVal.(type) {
case string:
// 如果是字符串,直接赋值
if v != "" {
req.Address = &v
}
}
}
// 特殊处理bus_number字段
if busNumberVal, exists := raw["bus_number"]; exists && busNumberVal != nil {
switch v := busNumberVal.(type) {
case float64:
// JSON数字默认是float64类型
busNumber := int(v)
req.BusNumber = &busNumber
case string:
// 如果是字符串尝试转换为int
if v != "" && v != "null" {
if busNumber, err := strconv.Atoi(v); err == nil {
req.BusNumber = &busNumber
}
}
}
}
// 特殊处理device_address字段
if deviceAddressVal, exists := raw["device_address"]; exists && deviceAddressVal != nil {
switch v := deviceAddressVal.(type) {
case string:
// 如果是字符串,直接赋值
if v != "" {
req.DeviceAddress = &v
}
}
}
return nil
}
// Controller 设备控制控制器
type Controller struct {
deviceControlRepo repository.DeviceControlRepo
deviceRepo repository.DeviceRepo
websocketService *service.WebSocketService
logger *logs.Logger
}
// NewController 创建设备控制控制器实例
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *Controller {
return &Controller{
deviceControlRepo: deviceControlRepo,
deviceRepo: deviceRepo,
websocketService: websocketService,
logger: logs.NewLogger(),
}
}
// List 获取设备列表
func (c *Controller) List(ctx *gin.Context) {
devices, err := c.deviceRepo.ListAll()
if err != nil {
c.logger.Error("获取设备列表失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "获取设备列表失败")
return
}
controller.SendSuccessResponse(ctx, "获取设备列表成功", ListResponse{Devices: devices})
}
// Create 创建设备
func (c *Controller) Create(ctx *gin.Context) {
var req DeviceRequest
// 直接使用绑定和验证方法处理JSON数据
rawData, err := ctx.GetRawData()
if err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "无法读取请求数据: "+err.Error())
return
}
if err := req.BindAndValidate(rawData); err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error())
return
}
device := &model.Device{
Name: req.Name,
Type: req.Type,
ParentID: req.ParentID,
Address: req.Address,
}
// 如果是485总线设备且提供了总线号和设备地址则合并为一个地址
if (req.Type == model.DeviceTypeFan || req.Type == model.DeviceTypeWaterCurtain) &&
req.BusNumber != nil && req.DeviceAddress != nil {
device.Set485Address(*req.BusNumber, *req.DeviceAddress)
}
if err := c.deviceRepo.Create(device); err != nil {
c.logger.Error("创建设备失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "创建设备失败")
return
}
controller.SendSuccessResponse(ctx, "创建设备成功", device)
}
// Update 更新设备
func (c *Controller) Update(ctx *gin.Context) {
var req struct {
ID uint `json:"id" binding:"required"`
DeviceRequest
}
// 直接使用绑定和验证方法处理JSON数据
rawData, err := ctx.GetRawData()
if err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "无法读取请求数据: "+err.Error())
return
}
// 先解析ID
var raw map[string]interface{}
if json.Unmarshal(rawData, &raw) == nil {
if idVal, ok := raw["id"]; ok {
switch id := idVal.(type) {
case float64:
req.ID = uint(id)
case string:
if idUint, err := strconv.ParseUint(id, 10, 32); err == nil {
req.ID = uint(idUint)
}
}
}
}
// 再解析DeviceRequest部分
if err := req.DeviceRequest.BindAndValidate(rawData); err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error())
return
}
device, err := c.deviceRepo.FindByID(req.ID)
if err != nil {
c.logger.Error("查找设备失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.NotFoundCode, "设备不存在")
return
}
device.Name = req.Name
device.Type = req.Type
device.ParentID = req.ParentID
device.Address = req.Address
// 如果是485总线设备且提供了总线号和设备地址则合并为一个地址
if (req.Type == model.DeviceTypeFan || req.Type == model.DeviceTypeWaterCurtain) &&
req.BusNumber != nil && req.DeviceAddress != nil {
device.Set485Address(*req.BusNumber, *req.DeviceAddress)
}
// TODO: 设备状态应该由系统自动获取,而不是由用户指定
// 这里保持设备原有状态,后续需要实现自动状态检测
// 设备状态现在只在内存中维护,不持久化到数据库
if err := c.deviceRepo.Update(device); err != nil {
c.logger.Error("更新设备失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "更新设备失败")
return
}
controller.SendSuccessResponse(ctx, "更新设备成功", device)
}
// Delete 删除设备
func (c *Controller) Delete(ctx *gin.Context) {
var req struct {
ID uint `json:"id" binding:"required"`
}
if err := ctx.ShouldBindJSON(&req); err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error())
return
}
// 先检查设备是否存在
_, err := c.deviceRepo.FindByID(req.ID)
if err != nil {
c.logger.Error("查找设备失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.NotFoundCode, "设备不存在")
return
}
if err := c.deviceRepo.Delete(req.ID); err != nil {
c.logger.Error("删除设备失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "删除设备失败")
return
}
controller.SendSuccessResponse(ctx, "删除设备成功", nil)
}
// SwitchRequest 设备控制请求结构体
type SwitchRequest struct {
ParentID *uint `json:"parent_id"` // 区域主控ID
DeviceType string `json:"device_type" binding:"required,oneof=fan water_curtain"`
DeviceID string `json:"device_id" binding:"required"`
Action string `json:"action" binding:"required,oneof=on off"`
}
// SwitchResponseData 设备控制响应数据结构体
type SwitchResponseData struct {
DeviceType string `json:"device_type"`
DeviceID string `json:"device_id"`
Action string `json:"action"`
Status string `json:"status"` // 添加状态字段
Message string `json:"message"` // 添加消息字段
}
// RelayControlData 发送给中继设备的控制数据结构体
type RelayControlData struct {
DeviceType string `json:"device_type"`
DeviceID string `json:"device_id"`
Action string `json:"action"`
}
// RelayControlResponseData 中继设备控制响应数据结构体
type RelayControlResponseData struct {
Status string `json:"status"`
Message string `json:"message"`
}
// Switch 设备控制接口
func (c *Controller) Switch(ctx *gin.Context) {
// 从上下文中获取用户信息
userValue, exists := ctx.Get("user")
if !exists {
controller.SendErrorResponse(ctx, controller.UnauthorizedCode, "无法获取用户信息")
return
}
user, ok := userValue.(*middleware.AuthUser)
if !ok {
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "用户信息格式错误")
return
}
var req SwitchRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误")
return
}
// 通过WebSocket向中继设备发送控制指令
controlData := RelayControlData{
DeviceType: req.DeviceType,
DeviceID: req.DeviceID,
Action: req.Action,
}
// 发送指令并等待响应
response, err := c.websocketService.SendCommandAndWait("relay-001", "control_device", controlData, 0)
if err != nil {
c.logger.Error("通过WebSocket发送设备控制指令失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "设备控制失败: "+err.Error())
return
}
// 解析响应数据
status := "解析失败"
message := "消息解析失败"
var responseData RelayControlResponseData
if err := response.ParseData(&responseData); err == nil {
status = responseData.Status
message = responseData.Message
}
// 创建设备控制记录
if err := c.createDeviceControlRecord(
user.ID,
req.DeviceID,
req.DeviceType,
req.Action,
status,
message,
); err != nil {
c.logger.Error("创建设备控制记录失败: " + err.Error())
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "记录控制历史失败")
return
}
data := SwitchResponseData{
DeviceType: req.DeviceType,
DeviceID: req.DeviceID,
Action: req.Action,
Status: status,
Message: message,
}
controller.SendSuccessResponse(ctx, "设备控制成功", data)
}
// createDeviceControlRecord 创建设备控制记录
func (c *Controller) createDeviceControlRecord(userID uint, deviceID, deviceType, action, status, result string) error {
// 获取设备信息
device, err := c.deviceRepo.FindByIDString(deviceID)
if err != nil {
return err
}
// 构建位置信息
var location string
switch device.Type {
case model.DeviceTypeRelay:
// 如果设备本身就是中继设备
location = device.Name
case model.DeviceTypePigPenController, model.DeviceTypeFeedMillController:
// 如果设备本身就是区域主控设备
if device.ParentID != nil {
// 获取中继设备
relayDevice, err := c.deviceRepo.FindByID(*device.ParentID)
if err != nil {
location = device.Name
} else {
location = relayDevice.Name + " -> " + device.Name
}
} else {
location = device.Name
}
default:
// 如果是普通设备(风机、水帘等)
if device.ParentID != nil {
// 获取区域主控设备
parentDevice, err := c.deviceRepo.FindByID(*device.ParentID)
if err != nil {
location = "未知区域"
} else {
// 检查区域主控设备是否有上级设备(中继设备)
if parentDevice.ParentID != nil {
// 获取中继设备
relayDevice, err := c.deviceRepo.FindByID(*parentDevice.ParentID)
if err != nil {
location = parentDevice.Name
} else {
location = relayDevice.Name + " -> " + parentDevice.Name
}
} else {
location = parentDevice.Name
}
}
} else {
location = "未知区域"
}
}
control := &model.DeviceControl{
UserID: userID,
Location: location,
DeviceType: model.DeviceType(deviceType),
DeviceID: deviceID,
Action: action,
Status: status,
Result: result,
}
return c.deviceControlRepo.Create(control)
}