package tools import ( "context" "encoding/json" "fmt" "strings" "github.com/yourname/cyrene-ai/tool-engine/internal/model" ) // IoTControlTool IoT 设备控制工具 type IoTControlTool struct { iotClient IoTClientInterface } // NewIoTControlTool 创建 IoT 控制工具 func NewIoTControlTool(iotClient IoTClientInterface) *IoTControlTool { return &IoTControlTool{iotClient: iotClient} } // Definition 返回工具定义 func (t *IoTControlTool) Definition() model.ToolDefinition { return model.ToolDefinition{ Name: "iot_control", Description: "【仅当开拓者明确要求控制设备时才使用此工具】控制家中智能设备。可以开关灯光、空调、窗帘、门锁等设备,也可以调节温度、亮度、位置、模式、颜色等属性。" + "\n⚠️ 重要约束:" + "\n - 不要在开拓者只是询问设备状态时调用此工具(查询设备请用 iot_query)" + "\n - 不要自行决定执行操作,必须等开拓者明确说出「打开」「关闭」「调到」「设置」等控制指令" + "\n - 不要因为之前对话中提到过某个设备就主动控制它" + "\n支持的操作:toggle(切换开关状态)、turn_on(打开设备)、turn_off(关闭设备)、" + "set_temperature(设置空调温度,需要 value 参数,单位°C)、" + "set_brightness(设置灯光亮度,需要 value 参数,0-100)、" + "set_position(设置窗帘位置,需要 value 参数,0-100,0=关闭 100=全开)、" + "set_mode(设置空调模式,需要 value 参数,可选值: cool/heat/auto)、" + "set_color(设置灯光颜色,需要 value 参数,可选值: warm_white/cool_white/colorful)", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "device_id": map[string]interface{}{ "type": "string", "description": "要控制的设备ID。可选值: light-livingroom, light-bedroom, ac-livingroom, ac-bedroom, curtain-livingroom, lock-door", }, "action": map[string]interface{}{ "type": "string", "enum": []string{"toggle", "turn_on", "turn_off", "set_temperature", "set_brightness", "set_position", "set_mode", "set_color"}, "description": "要执行的操作。toggle:切换开关状态;turn_on:打开设备;turn_off:关闭设备;set_temperature:设置空调温度(需配合value参数);set_brightness:设置灯光亮度(需配合value参数);set_position:设置窗帘位置(需配合value参数);set_mode:设置空调模式(需配合value参数);set_color:设置灯光颜色(需配合value参数)", }, "value": map[string]interface{}{ "type": "number", "description": "操作的值。set_temperature 时表示目标温度(°C),set_brightness 时表示亮度百分比(0-100),set_position 时表示窗帘开合程度(0-100)。action 为 set_temperature/set_brightness/set_position 时必须提供。set_mode 时为字符串(cool/heat/auto),set_color 时为字符串(warm_white/cool_white/colorful)", }, }, "required": []string{"device_id", "action"}, }, } } // normalizeAction 标准化 action 参数,支持中文别名、power 参数等 func normalizeAction(arguments map[string]interface{}) string { action, _ := arguments["action"].(string) // 如果 action 为空,检查 power/status 参数 if action == "" { // power 参数: "off"/"关"/"关闭" → turn_off, "on"/"开"/"打开" → turn_on if pv, ok := arguments["power"]; ok { switch v := pv.(type) { case string: switch strings.ToLower(strings.TrimSpace(v)) { case "off", "false", "关", "关闭": return "turn_off" case "on", "true", "开", "打开", "开启": return "turn_on" } case bool: if !v { return "turn_off" } return "turn_on" } } // status 参数同理 if sv, ok := arguments["status"]; ok { switch v := sv.(type) { case string: switch strings.ToLower(strings.TrimSpace(v)) { case "off", "false", "关", "关闭": return "turn_off" case "on", "true", "开", "打开", "开启": return "turn_on" } case bool: if !v { return "turn_off" } return "turn_on" } } // 默认 toggle return "toggle" } // 标准化中文 action 名 switch strings.ToLower(strings.TrimSpace(action)) { case "打开", "开启", "开": return "turn_on" case "关闭", "关": return "turn_off" case "切换": return "toggle" case "设置温度", "调温度", "set_temp": return "set_temperature" case "设置亮度", "调亮度", "set_light": return "set_brightness" case "设置位置", "调位置": return "set_position" case "设置模式", "调模式", "切换模式": return "set_mode" case "设置颜色", "调颜色", "换颜色": return "set_color" } return action } // Execute 执行设备控制 func (t *IoTControlTool) Execute(ctx context.Context, arguments map[string]interface{}) (*model.ToolResult, error) { if t.iotClient == nil { return &model.ToolResult{ Output: "", Error: "IoT 客户端未初始化", }, nil } // 参数别名:entity_id → device_id deviceID, _ := arguments["device_id"].(string) if deviceID == "" { deviceID, _ = arguments["entity_id"].(string) } action := normalizeAction(arguments) if deviceID == "" { return &model.ToolResult{ Output: "", Error: "缺少设备ID(请使用 device_id 参数)", }, nil } // 先获取设备名用于友好的返回消息(失败不影响后续流程) deviceName := deviceID if dev, err := t.iotClient.GetDevice(deviceID); err == nil { deviceName = dev.Name } // 处理属性设置类操作 switch action { case "set_temperature": return t.handleSetTemperature(deviceID, arguments) case "set_brightness": return t.handleSetBrightness(deviceID, arguments) case "set_position": return t.handleSetPosition(deviceID, arguments) case "set_mode": return t.handleSetMode(deviceID, arguments) case "set_color": return t.handleSetColor(deviceID, arguments) case "turn_off": // 声明式关闭:使用 SetDeviceProperty status/off 而非 toggle // 即使设备已经关闭,SetProperty 也会幂等处理 if err := t.iotClient.SetDeviceProperty(deviceID, "status", "off"); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("关闭设备失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已关闭设备: %s", deviceName), Error: "", }, nil case "turn_on": // 声明式打开:使用 SetDeviceProperty status/on 而非 toggle if err := t.iotClient.SetDeviceProperty(deviceID, "status", "on"); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("打开设备失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已打开设备: %s", deviceName), Error: "", }, nil default: // "toggle" if err := t.iotClient.ToggleDevice(deviceID); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("操作设备失败: %v", err), }, nil } // 获取切换后的状态 updatedDevice, err := t.iotClient.GetDevice(deviceID) if err != nil { return &model.ToolResult{ Output: fmt.Sprintf("已成功切换设备 %s 的状态。", deviceName), Error: "", }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已成功操作设备: %s\n当前状态: %s", updatedDevice.Name, formatDeviceLine(*updatedDevice)), Error: "", }, nil } } // extractValue 从 arguments 中提取 value 参数(支持 value/Value 及数字/字符串类型) func extractValue(arguments map[string]interface{}) interface{} { if v, ok := arguments["value"]; ok { return v } return nil } // handleSetTemperature 处理设置温度 func (t *IoTControlTool) handleSetTemperature(deviceID string, arguments map[string]interface{}) (*model.ToolResult, error) { val := extractValue(arguments) if val == nil { return &model.ToolResult{ Output: "", Error: "缺少 value 参数,请指定目标温度(如 24)", }, nil } // 先获取当前设备信息 currentDevice, err := t.iotClient.GetDevice(deviceID) if err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("获取设备状态失败: %v", err), }, nil } temperature, ok := toFloat64(val) if !ok { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("温度值无效: %v", val), }, nil } if err := t.iotClient.SetDeviceProperty(deviceID, "temperature", temperature); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("设置温度失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已将 %s 温度从 %.1f°C 调整为 %.1f°C", currentDevice.Name, currentDevice.Temperature, temperature), Error: "", }, nil } // handleSetBrightness 处理设置亮度 func (t *IoTControlTool) handleSetBrightness(deviceID string, arguments map[string]interface{}) (*model.ToolResult, error) { val := extractValue(arguments) if val == nil { return &model.ToolResult{ Output: "", Error: "缺少 value 参数,请指定亮度值(0-100)", }, nil } // 先获取当前设备信息 currentDevice, err := t.iotClient.GetDevice(deviceID) if err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("获取设备状态失败: %v", err), }, nil } brightness, ok := toFloat64(val) if !ok { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("亮度值无效: %v", val), }, nil } if err := t.iotClient.SetDeviceProperty(deviceID, "brightness", brightness); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("设置亮度失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已将 %s 亮度调整为 %d%%", currentDevice.Name, int(brightness)), Error: "", }, nil } // handleSetPosition 处理设置窗帘位置 func (t *IoTControlTool) handleSetPosition(deviceID string, arguments map[string]interface{}) (*model.ToolResult, error) { val := extractValue(arguments) if val == nil { return &model.ToolResult{ Output: "", Error: "缺少 value 参数,请指定位置值(0=关闭, 100=全开)", }, nil } currentDevice, err := t.iotClient.GetDevice(deviceID) if err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("获取设备状态失败: %v", err), }, nil } position, ok := toFloat64(val) if !ok { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("位置值无效: %v", val), }, nil } if err := t.iotClient.SetDeviceProperty(deviceID, "position", position); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("设置窗帘位置失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已将 %s 窗帘调整为 %d%%", currentDevice.Name, int(position)), Error: "", }, nil } // handleSetMode 处理设置空调模式 func (t *IoTControlTool) handleSetMode(deviceID string, arguments map[string]interface{}) (*model.ToolResult, error) { val := extractValue(arguments) if val == nil { return &model.ToolResult{ Output: "", Error: "缺少 value 参数,请指定模式(cool/heat/auto)", }, nil } mode, ok := val.(string) if !ok { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("模式值无效: %v", val), }, nil } currentDevice, err := t.iotClient.GetDevice(deviceID) if err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("获取设备状态失败: %v", err), }, nil } if err := t.iotClient.SetDeviceProperty(deviceID, "mode", mode); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("设置模式失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已将 %s 模式切换为 %s", currentDevice.Name, mode), Error: "", }, nil } // handleSetColor 处理设置灯光颜色 func (t *IoTControlTool) handleSetColor(deviceID string, arguments map[string]interface{}) (*model.ToolResult, error) { val := extractValue(arguments) if val == nil { return &model.ToolResult{ Output: "", Error: "缺少 value 参数,请指定颜色(warm_white/cool_white/colorful)", }, nil } color, ok := val.(string) if !ok { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("颜色值无效: %v", val), }, nil } currentDevice, err := t.iotClient.GetDevice(deviceID) if err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("获取设备状态失败: %v", err), }, nil } if err := t.iotClient.SetDeviceProperty(deviceID, "color", color); err != nil { return &model.ToolResult{ Output: "", Error: fmt.Sprintf("设置颜色失败: %v", err), }, nil } return &model.ToolResult{ Output: fmt.Sprintf("已将 %s 灯光颜色切换为 %s", currentDevice.Name, color), Error: "", }, nil } // toFloat64 将 interface{} 转换为 float64 func toFloat64(v interface{}) (float64, bool) { switch val := v.(type) { case float64: return val, true case float32: return float64(val), true case int: return float64(val), true case int64: return float64(val), true case json.Number: f, err := val.Float64() return f, err == nil default: return 0, false } }