Files
xiaomihome/miot/examples/diag_device/main.go
T
4566704 cdc459b5ff fix(xiaomihome): 模块路径改为 git.zeroonesoft.cn/golib/xiaomihome
- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步
- 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
2026-10-05 08:53:23 +08:00

223 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Command diag_device 诊断指定米家设备的云端属性(纯 HTTP,不初始化 MQTT)。
//
// 用途:对比 SDK 读到属性值 vs 米家 App 显示,验证 siid/piid 是否正确、
// 控制是否生效、设备连接类型等。可在生产环境旁路运行(不与生产 MQTT 冲突)。
//
// 用法:token/did 直接改下面常量,然后 go run . 即可。
package main
import (
"fmt"
"os"
"sort"
"strconv"
"strings"
"git.zeroonesoft.cn/golib/xiaomihome/miot"
)
// ── 配置:按需修改 ──
const (
accessToken = "V3_1GfW88WpawoKACRqwdYBb86In_I5e8r8stZdRPXI1PR4zZgIEm4cbjNwn0wYa6-ZQtIliJJerf-iI0W9Lx8t6fm7Ln16w0RVAmYO3e9XubJ0dK7D3eM_zBlCcVmQCla6Sx0BKQv_YXI9E2zXXopKaRjIUWyx0lb0YcE7oEopLuvfA0sEFoTohVKt543KnHJH" // 米家 OAuth access_token
targetDID = "2188673403" // 目标设备 did
homeID = "619001787902" // 家庭 ID(留空=全部家庭;先跑一遍看家庭列表再填)
dataDir = "/home/ubuntu/server/zoSmartUser/data" // spec 缓存目录(生产 Linux 路径)
setTest = "4,1,false|4,2,26" // 写属性测试:"siid,piid,value",多组用 | 分隔;留空跳过
// ──────────────────────
)
func main() {
if accessToken == "" {
fmt.Fprintln(os.Stderr, "请先在 main.go 顶部 const 里填写 accessToken")
os.Exit(1)
}
// 纯 HTTP 客户端(不连 MQTT,不占 mqtt client id)
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.Println("HTTP 客户端 OK(未连接 MQTT)")
// 1. 按 did 精确拉设备信息(不依赖家庭列表)
devMap, err := httpClient.GetDevicesWithDIDs([]string{targetDID})
if err != nil {
fmt.Fprintf(os.Stderr, "获取设备信息失败: %v\n", err)
os.Exit(1)
}
info, ok := devMap[targetDID]
if !ok {
fmt.Fprintf(os.Stderr, "设备 %s 未返回(共 %d 台)\n", targetDID, len(devMap))
os.Exit(1)
}
infoMap := info
fmt.Printf("\n=== 设备信息 %s ===\n", targetDID)
printDeviceInfo(infoMap)
urn, _ := infoMap["urn"].(string)
model, _ := infoMap["model"].(string)
fmt.Printf("URN: %s\nMODEL: %s\n", urn, model)
// 2. 解析 SPEC
parser := miot.NewMIoTSpecParser("en", miot.NewMIoTStorage(dataDir), dataDir+"/specs")
spec, err := parser.Parse(urn, model)
if err != nil {
fmt.Fprintf(os.Stderr, "SPEC 解析失败: %v\n", err)
os.Exit(1)
}
fmt.Printf("\n=== SPEC 服务/属性(siid 布局) ===\n")
for _, svc := range spec.Services {
if svc == nil {
continue
}
fmt.Printf("svc[%d] %s\n", svc.IID, short(svc.Type))
names := make([]string, 0, len(svc.Properties))
for _, p := range svc.Properties {
if p == nil {
continue
}
names = append(names, fmt.Sprintf("%s(%d)", short(p.Type), p.PIID))
}
sort.Strings(names)
fmt.Printf(" %s\n", strings.Join(names, " "))
}
// 3. 批量读取全部可读属性
fmt.Printf("\n=== 读取全部可读属性 ===\n")
var params []miot.PropParam
keys := make([]string, 0)
for _, svc := range spec.Services {
if svc == nil {
continue
}
for _, p := range svc.Properties {
if p == nil {
continue
}
params = append(params, miot.PropParam{DID: targetDID, Siid: svc.IID, Piid: p.PIID})
keys = append(keys, fmt.Sprintf("%d.%d %s", svc.IID, p.PIID, short(p.Type)))
}
}
results, err := httpClient.GetProps(params)
if err != nil {
fmt.Fprintf(os.Stderr, "读取属性失败: %v\n", err)
} else {
for i, r := range results {
name := ""
if i < len(keys) {
name = keys[i]
}
fmt.Printf(" %-24s code=%d value=%v\n", name, r.Code, r.Value)
}
}
// 4. 三合一设备对比读取(空调/地暖双温度)
fmt.Printf("\n=== 温度属性对比(三合一设备) ===\n")
read := func(siid, piid int, label string) {
rs, err := httpClient.GetProps([]miot.PropParam{{DID: targetDID, Siid: siid, Piid: piid}})
if err != nil {
fmt.Printf(" %s: 错误 %v\n", label, err)
return
}
if len(rs) > 0 {
fmt.Printf(" %-28s code=%d value=%v\n", label, rs[0].Code, rs[0].Value)
}
}
read(2, 1, "siid=2/piid=1 on(空调)")
read(2, 2, "siid=2/piid=2 mode")
read(2, 4, "siid=2/piid=4 target-temperature")
read(4, 2, "siid=4/piid=2 target-temperature")
read(5, 2, "siid=5/piid=2 temperature(环境)")
// 5. 写属性测试(可选,多组用 | 分隔,如 "2,4,26|4,2,26")
if setTest != "" {
for _, spec := range strings.Split(setTest, "|") {
doSetAndVerify(httpClient, strings.TrimSpace(spec))
}
}
}
// doSetAndVerify 执行一次写属性并回读验证。
func doSetAndVerify(httpClient *miot.MIoTHttpClient, spec string) {
parts := strings.Split(spec, ",")
if len(parts) < 3 {
fmt.Fprintf(os.Stderr, "setTest 格式错误: %s (应为 siid,piid,value)\n", spec)
return
}
siid, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
piid, _ := strconv.Atoi(strings.TrimSpace(parts[1]))
var val interface{}
if b, err := strconv.ParseBool(parts[2]); err == nil {
val = b
} else if f, err := strconv.ParseFloat(strings.TrimSpace(parts[2]), 64); err == nil {
val = f
} else {
val = strings.TrimSpace(parts[2])
}
fmt.Printf("\n=== 写属性 siid=%d piid=%d value=%v ===\n", siid, piid, val)
rs, err := httpClient.SetProp([]miot.PropParam{{DID: targetDID, Siid: siid, Piid: piid, Value: val}})
if err != nil {
fmt.Printf("写属性失败: %v\n", err)
return
}
for _, r := range rs {
fmt.Printf(" code=%d (0/1=成功,非0=失败)\n", r.Code)
}
// 写后立即回读验证(区分"写错服务" vs "云端假成功")
fmt.Println("--- 写后回读 ---")
verify := func(vsiid, vpiid int, label string) {
vrs, err := httpClient.GetProps([]miot.PropParam{{DID: targetDID, Siid: vsiid, Piid: vpiid}})
if err != nil {
fmt.Printf(" %s: 错误 %v\n", label, err)
return
}
if len(vrs) > 0 {
fmt.Printf(" %-30s code=%d value=%v\n", label, vrs[0].Code, vrs[0].Value)
}
}
verify(siid, piid, "回读 siid=目标")
verify(2, 1, "siid=2/piid=1 on(地暖)")
verify(2, 2, "siid=2/piid=2 mode(地暖)")
verify(2, 4, "siid=2/piid=4 target-temperature(地暖)")
verify(4, 1, "siid=4/piid=1 on(空调)")
verify(4, 2, "siid=4/piid=2 target-temperature(空调)")
}
func printDeviceInfo(m map[string]interface{}) {
fields := []struct{ k, label string }{
{"name", "名称"}, {"model", "型号"}, {"connect_type", "connect_type"},
{"pid", "pid"}, {"online", "online"}, {"isOnline", "isOnline"},
{"rssi", "rssi"}, {"local_ip", "local_ip"}, {"parent_id", "parent_id"},
{"urn", "urn"},
}
for _, f := range fields {
if v, ok := m[f.k]; ok {
fmt.Printf(" %-12s = %v\n", f.label, v)
}
}
}
func short(urn string) string {
parts := strings.Split(urn, ":")
if len(parts) >= 4 {
return parts[3]
}
return urn
}
// toStrSlice 将 interface{}([]interface{})转为 []string。
func toStrSlice(v interface{}) []string {
arr, ok := v.([]interface{})
if !ok {
return nil
}
out := make([]string, 0, len(arr))
for _, item := range arr {
if s, ok := item.(string); ok {
out = append(out, s)
}
}
return out
}