feat: AC 三合一设备支持 + 新增 Plug/Gateway 驱动 + Cover 结构化重构

**AC 驱动增强**
- 支持 thermostat + air-conditioner + air-fresh 多服务三合一设备,属性跨服务合并解析
- 新增 specResolver.findValListInService/findInService/hasService/serviceShortName 方法
- 移除 KindThermostat,heater/electric-blanket/thermostat 重新归类为 KindAirConditioner

**Cover 窗帘重构**
- CoverProps 结构化:Position/Status/Fault 分离
- SetPosition 优先写 target-position,回退 current-position
- OnPropsChanged/FetchProps 支持多属性同步刷新

**新增设备类型**
- Plug 驱动:插座/插头,支持开合、在线状态、属性变更订阅
- Gateway 驱动:网关设备类型识别
- miot/miot_errors.go:新增 MIoT 云端错误码定义

**MIoT 客户端健壮性**
- SetProp 空结果视为失败;设备被移除或离线时自动刷新设备列表
- GetDevices 的 has_more 改用 parseBool 严格解析
- MIPS SubscribeMany 批量订阅减少往返
- matchURN 改为按段精确匹配,避免 tofan 误匹配 fan

**Bridge**
- 移除 Thermostat case,Cover 改用结构化 CoverProps
- 新增设备创建后自动订阅属性变更

**其他**
- 日志整体 Info → Debug 降噪
- .gitignore 忽略 /.codebuddy/memory
- 更新 devices_test 与 08_adv_filter_classify 示例
This commit is contained in:
2026-08-23 19:42:55 +08:00
parent 04333750ab
commit 86c21ce91c
25 changed files with 1129 additions and 155 deletions
+90
View File
@@ -0,0 +1,90 @@
// Command diag_control 直接控制米家设备(纯 HTTP,不初始化 MQTT)。
//
// 控制序列:开空调 → 设温度 → 设模式 → 回读验证。
// 与生产 SDK 同一云端链路(miotspec/prop/set),不占 MQTT client id。
//
// 用法:token/did 改下面常量,在生产环境 go run .(Linux 下 dataDir 填生产路径)。
package main
import (
"fmt"
"os"
"xiaomihome/miot"
)
// ── 配置:按需修改 ──
const (
accessToken = "V3_1GfW88WpawoKACRqwdYBb86In_I5e8r8stZdRPXI1PR4zZgIEm4cbjNwn0wYa6-ZQtIliJJerf-iI0W9Lx8t6fm7Ln16w0RVAmYO3e9XubJ0dK7D3eM_zBlCcVmQCla6Sx0BKQv_YXI9E2zXXopKaRjIUWyx0lb0YcE7oEopLuvfA0sEFoTohVKt543KnHJH" // 米家 OAuth access_token
targetDID = "2188673403" // 目标设备 did
dataDir = "/home/ubuntu/server/zoSmartUser/data" // spec 缓存目录(生产 Linux)
// ──────────────────────
)
func main() {
if accessToken == "" {
fmt.Fprintln(os.Stderr, "请先填写 accessToken")
os.Exit(1)
}
httpClient, err := miot.NewMIoTHttpClient("cn", miot.OAUTH2_CLIENT_ID, accessToken)
if err != nil {
fmt.Fprintf(os.Stderr, "创建 HTTP 客户端失败: %v\n", err)
os.Exit(1)
}
fmt.Printf("HTTP 客户端 OK(未连接 MQTT),设备 %s\n", targetDID)
// 读取当前状态(控制前)
readAll("控制前状态", httpClient)
// ── 控制序列 ──
set("开空调 siid=4/piid=1=true", 4, 1, true, httpClient)
set("设温度 siid=4/piid=2=26", 4, 2, float64(26), httpClient)
set("设模式 siid=2/piid=2=1(Cool)", 2, 2, float64(1), httpClient)
set("关空调 siid=4/piid=1=false", 4, 1, false, httpClient)
// 回读验证(控制后)
readAll("控制后状态", httpClient)
}
// set 写属性并回读验证。
func set(label string, siid, piid int, value interface{}, c *miot.MIoTHttpClient) {
fmt.Printf("\n=== %s ===\n", label)
rs, err := c.SetProp([]miot.PropParam{{DID: targetDID, Siid: siid, Piid: piid, Value: value}})
if err != nil {
fmt.Printf(" 写失败: %v\n", err)
return
}
for _, r := range rs {
fmt.Printf(" 写返回 code=%d\n", r.Code)
}
// 回读
gr, err := c.GetProps([]miot.PropParam{{DID: targetDID, Siid: siid, Piid: piid}})
if err != nil {
fmt.Printf(" 回读失败: %v\n", err)
return
}
if len(gr) > 0 {
fmt.Printf(" 回读 siid=%d/piid=%d = %v\n", siid, piid, gr[0].Value)
}
}
// readAll 读取关键属性。
func readAll(label string, c *miot.MIoTHttpClient) {
fmt.Printf("\n--- %s ---\n", label)
read := func(siid, piid int, name string) {
rs, err := c.GetProps([]miot.PropParam{{DID: targetDID, Siid: siid, Piid: piid}})
if err != nil {
fmt.Printf(" %-28s 错误: %v\n", name, err)
return
}
if len(rs) > 0 {
fmt.Printf(" %-28s = %v\n", name, rs[0].Value)
}
}
read(4, 1, "空调开关 siid=4/piid=1")
read(4, 2, "空调温度 siid=4/piid=2")
read(2, 2, "模式 siid=2/piid=2")
read(2, 4, "地暖温度 siid=2/piid=4")
read(5, 2, "环境温度 siid=5/piid=2")
}