// 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" "git.huangwc.com/pig/pig-farm-controller/internal/websocket" "github.com/gin-gonic/gin" ) // ListResponse 设备列表响应结构体 type ListResponse struct { Devices []DeviceListItem `json:"devices"` } // DeviceListItem 设备列表项结构体 type DeviceListItem struct { model.Device Active bool `json:"active"` } // 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 websocketManager *websocket.Manager heartbeatService *service.HeartbeatService deviceStatusPool *service.DeviceStatusPool logger *logs.Logger } // NewController 创建设备控制控制器实例 func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketManager *websocket.Manager, heartbeatService *service.HeartbeatService, deviceStatusPool *service.DeviceStatusPool) *Controller { return &Controller{ deviceControlRepo: deviceControlRepo, deviceRepo: deviceRepo, websocketManager: websocketManager, heartbeatService: heartbeatService, deviceStatusPool: deviceStatusPool, 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 } // 构建设备列表项,包含设备状态信息 deviceList := make([]DeviceListItem, len(devices)) for i, device := range devices { // 从设备状态池获取设备状态,默认为非激活状态 active := false if status, exists := c.deviceStatusPool.GetStatus(strconv.FormatUint(uint64(device.ID), 10)); exists { active = status.Active } deviceList[i] = DeviceListItem{ Device: device, Active: active, } } controller.SendSuccessResponse(ctx, "获取设备列表成功", ListResponse{Devices: deviceList}) } // 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 } // 刷新设备状态 c.heartbeatService.TriggerManualHeartbeatAsync() 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) } if err := c.deviceRepo.Update(device); err != nil { c.logger.Error("更新设备失败: " + err.Error()) controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "更新设备失败") return } // 刷新设备状态 c.heartbeatService.TriggerManualHeartbeatAsync() 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 } // 刷新设备状态 c.heartbeatService.TriggerManualHeartbeatAsync() 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"` // 添加消息字段 } // DeviceStatusResponse 设备状态响应结构体 type DeviceStatusResponse struct { DeviceID string `json:"device_id"` Active bool `json:"active"` } // 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.websocketManager.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, } // 刷新设备状态 c.heartbeatService.TriggerManualHeartbeatAsync() 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) } // GetDeviceStatus 获取设备当前状态 func (c *Controller) GetDeviceStatus(ctx *gin.Context) { deviceID := ctx.Query("device_id") if deviceID == "" { controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "设备ID不能为空") return } // TODO 需要刷新设备状态吗? 刷新的话这个接口可能会很慢 // 从设备状态池中获取设备状态 status, exists := c.deviceStatusPool.GetStatus(deviceID) if !exists { controller.SendErrorResponse(ctx, controller.NotFoundCode, "设备状态不存在") return } response := DeviceStatusResponse{ DeviceID: deviceID, Active: status.Active, } controller.SendSuccessResponse(ctx, "获取设备状态成功", response) }