// 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,可选 } // 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 } } } } 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 } // TODO: 设备状态应该由系统自动获取,而不是由用户指定 // 这里设置默认状态为active,后续需要实现自动状态检测 device := &model.Device{ Name: req.Name, Type: req.Type, ParentID: req.ParentID, Status: "active", // 默认设置为active状态 } 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 // TODO: 设备状态应该由系统自动获取,而不是由用户指定 // 这里保持设备原有状态,后续需要实现自动状态检测 // device.Status = req.Status 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) }