package remotedevice import ( "fmt" "sort" "strings" "time" agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io" ) // devicectlDevice 把设备网关暴露为 IOManager 的一个 Device: // Tools() 提供 devicedetect / device_ctl_status / device_ctl_cmdrun / device_ctl_cmdresult, // Execute() 检查授权并路由到 WS 在线设备。 type devicectlDevice struct { reg *Registry persist func() // 授权变更后持久化 } func (d *devicectlDevice) Name() string { return "devicectl" } func (d *devicectlDevice) Type() agentIO.DeviceType { return agentIO.DeviceIO } func (d *devicectlDevice) OutputCapabilities() agentIO.OutputCapability { return agentIO.CapStructured } func (d *devicectlDevice) Description() string { return "远程设备控制网关:查看已接入/已授权的设备并发送控制指令(经用户授权的设备)" } func (d *devicectlDevice) ChannelDef() agentIO.ChannelDef { return agentIO.ChannelDef{} } func (d *devicectlDevice) Start() error { return nil } func (d *devicectlDevice) Stop() error { return nil } func (d *devicectlDevice) Tools() []agentIO.ToolDef { return []agentIO.ToolDef{ { Name: "devicedetect", Description: "扫描并列出已接入设备网关的设备(含在线/离线状态与授权状态)。" + "用途:查看当前有哪些设备连接了 HomeAgent、是否在线、是否已授权。" + "参数 kind 可选,只返回该种类的设备。返回值 device_id 用于后续 device_ctl_* 工具。", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "kind": map[string]interface{}{"type": "string", "description": "按设备种类过滤(light/camera/computer/phone/...),可省略"}, }, }, }, { Name: "device_ctl_status", Description: "查询一台已在线设备的实时状态。" + "仅返回设备上报的状态信息(如电量/温度/运行状态)。" + "需要 device_id(来自 devicedetect)。设备必须已授权且在线。", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "device_id": map[string]interface{}{"type": "string", "description": "目标设备 ID"}, }, "required": []interface{}{"device_id"}, }, }, { Name: "device_ctl_cmdrun", Description: "向一台已授权且在线设备发送命令执行请求(如开机/重启/播放/自定义命令)。" + "⚡ 高危:设备必须已授权,且该操作会改变设备行为。" + "返回 accepted=true 表示已下发并等待设备执行,之后可用 device_ctl_cmdresult 查询结果。" + "若设备未授权或离线,返回错误信息。", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "device_id": map[string]interface{}{"type": "string", "description": "目标设备 ID"}, "command": map[string]interface{}{"type": "string", "description": "要执行的命令(设备自定义语义,如 power_on、play:xxx 或 shell 命令)"}, }, "required": []interface{}{"device_id", "command"}, }, }, { Name: "device_ctl_cmdresult", Description: "查询之前 device_ctl_cmdrun 下发命令的执行结果(按 req_id 或 device_id 最近一次)。" + "若非阻塞或已超时,用此工具取回设备执行输出。", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "req_id": map[string]interface{}{"type": "string", "description": "命令请求 ID(device_ctl_cmdrun 返回)"}, "device_id": map[string]interface{}{"type": "string", "description": "设备 ID(查询最近一次结果)"}, }, }, }, { Name: "deviceinfo", Description: "探查一台设备接入网关时声明的详细信息与支持能力。" + "返回设备的 OS/架构/CPU/内存/能力 caps 等(设备接入时上报,非实时)。" + "需要 device_id(来自 devicedetect)。设备必须已授权。", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "device_id": map[string]interface{}{"type": "string", "description": "目标设备 ID"}, }, "required": []interface{}{"device_id"}, }, }, } } func (d *devicectlDevice) Execute(tool string, args map[string]interface{}) (interface{}, error) { switch tool { case "devicedetect": return d.detect(args) case "device_ctl_status": return d.status(args) case "device_ctl_cmdrun": return d.cmdrun(args) case "device_ctl_cmdresult": return d.cmdresult(args) case "deviceinfo": return d.info(args) default: return nil, fmt.Errorf("unknown device tool %s", tool) } } // publicDevices 把设备列表转为简洁 JSON(无内部字段)。 func publicDevices(in []DeviceMeta) []map[string]interface{} { out := make([]map[string]interface{}, 0, len(in)) for _, m := range in { entry := map[string]interface{}{ "device_id": m.DeviceID, "name": m.Name, "kind": m.Kind, "caps": m.Caps, "authorized": m.Authorized, "online": m.Online, "last_seen": m.LastSeen, } if len(m.Info) > 0 { entry["info"] = m.Info } out = append(out, entry) } return out } // detect 实现 devicedetect。 func (d *devicectlDevice) detect(args map[string]interface{}) (interface{}, error) { kind, _ := args["kind"].(string) devs := d.reg.List() if kind != "" { var filtered []DeviceMeta for _, m := range devs { if strings.EqualFold(m.Kind, kind) { filtered = append(filtered, m) } } devs = filtered } sort.Slice(devs, func(i, j int) bool { return devs[i].DeviceID < devs[j].DeviceID }) if len(devs) == 0 { return map[string]interface{}{"devices": []interface{}{}, "message": "暂无设备接入"}, nil } return map[string]interface{}{"devices": publicDevices(devs)}, nil } // status 实现 device_ctl_status(只读,查询设备最后一次上报状态)。 func (d *devicectlDevice) status(args map[string]interface{}) (interface{}, error) { id, _ := args["device_id"].(string) if id == "" { return nil, fmt.Errorf("device_id required") } m, ok := d.reg.Get(id) if !ok { return nil, fmt.Errorf("device %s 不存在", id) } if !m.Authorized { return nil, fmt.Errorf("device %s 未授权,无法查询状态(需先在设备管理页或经 bind 授权)", id) } if !m.Online { return publicDevices([]DeviceMeta{m}), nil // 带 offline=true } return publicDevices([]DeviceMeta{m}), nil } // cmdrun 实现 device_ctl_cmdrun:检查授权+在线,push 命令,异步等待结果。 func (d *devicectlDevice) cmdrun(args map[string]interface{}) (interface{}, error) { id, _ := args["device_id"].(string) cmd, _ := args["command"].(string) if id == "" || cmd == "" { return nil, fmt.Errorf("device_id and command required") } m, ok := d.reg.Get(id) if !ok { return nil, fmt.Errorf("device %s 不存在", id) } if !m.Authorized { return nil, fmt.Errorf("device %s 未授权,无法执行命令(请先在设备管理页授权)", id) } if !m.Online { return nil, fmt.Errorf("device %s 不在线,无法执行命令", id) } reqID := newReqID() if err := d.reg.PushCmd(id, reqID, cmd); err != nil { return nil, fmt.Errorf("下发命令失败: %w", err) } // 阻塞等待设备结果(带超时);结果同时由 registry 留档。 res, err := d.reg.AwaitResult(reqID, 30*time.Second) if err != nil { d.reg.SaveResult(reqID, map[string]interface{}{"accepted": true, "error": err.Error(), "pending": true}) return map[string]interface{}{"accepted": true, "req_id": reqID, "pending": true, "note": "命令已下发但设备未在超时内回执,可用 device_ctl_cmdresult 再查"}, nil } out := res out["req_id"] = reqID d.reg.SaveResult(reqID, out) return out, nil } // cmdresult 实现 device_ctl_cmdresult。 func (d *devicectlDevice) cmdresult(args map[string]interface{}) (interface{}, error) { reqID, _ := args["req_id"].(string) deviceID, _ := args["device_id"].(string) if reqID != "" { res, ok := d.reg.GetResult(reqID) if !ok { return nil, fmt.Errorf("no result for req %s(设备可能尚未回执)", reqID) } return res, nil } if deviceID != "" { // 返回该设备最近一次结果(简化:遍历 results 找 device_id 匹配的最近一条) // 说明:当前只按 req_id 查询;device_id 查询留给后续迭代。 m, ok := d.reg.Get(deviceID) if !ok { return nil, fmt.Errorf("device %s 不存在", deviceID) } if !m.Authorized { return nil, fmt.Errorf("device %s 未授权", deviceID) } return map[string]interface{}{"device_id": deviceID, "note": "请用 device_ctl_cmdrun 返回的 req_id 查询命令结果"}, nil } return nil, fmt.Errorf("req_id 或 device_id 至少提供一个") } // info 实现 deviceinfo:返回设备详情 + 支持能力。 func (d *devicectlDevice) info(args map[string]interface{}) (interface{}, error) { id, _ := args["device_id"].(string) if id == "" { return nil, fmt.Errorf("device_id required") } m, ok := d.reg.Get(id) if !ok { return nil, fmt.Errorf("device %s 不存在", id) } if !m.Authorized { return nil, fmt.Errorf("device %s 未授权,无法探查信息(请先在设备管理页授权)", id) } out := map[string]interface{}{ "device_id": m.DeviceID, "name": m.Name, "kind": m.Kind, "caps": m.Caps, "authorized": m.Authorized, "online": m.Online, "last_seen": m.LastSeen, } if len(m.Info) > 0 { out["info"] = m.Info } return out, nil }