Files
xiaomihome/xiaomi
4566704 04333750ab feat: State→Props 命名统一 + 新增 Plug/Gateway 设备 + 分类系统重构
**命名统一(State → Props)**
- 所有设备类型:XxxState → XxxProps,GetState() → GetProps(),FetchState() → FetchProps()
- 回调接口:OnStateChanged → OnPropsChanged
- Bridge 层类型别名、示例代码、文档同步更名

**新增设备类型**
- KindPlug + Plug 驱动:智能插座/插头(TurnOn/TurnOff/IsOn/OnPropsChanged)
- KindGateway + Gateway 驱动:网关设备类型识别
- KindOccupancySensor 从 KindSensor 独立,添加 OccupancyProps

**设备分类系统改进**
- classifyByModel 改为按 key 长度降序匹配(防止 sensor 先于 sensor-occupy)
- 新增下划线变体识别(sensor_occupy、sensor_temp 等)
- outlet 重新归类为 Plug(非 Switch)
- 新增 Or() 组合过滤器

**连接桥增强**
- 设备创建成功后自动订阅属性变更
- 新增 nil 防护检查
- 支持 Plug/Speaker/Gateway/Occupancy 类型的 FetchProps 刷新
- 日志级别 Info → Debug

**AC 驱动增强**
- 新增 HasPower()、GetElectricPower()、GetPowerConsumption() 功率接口
- ACProps 新增 ElectricPower、PowerConsumption 字段
- valMapper:Desc 为空时 fallback 用 Name,key 统一小写

**MIoT 核心**
- 设备列表刷新改用 reflect.DeepEqual 全字段变更检测
- 移除 BLE/代理设备默认在线 hack
- RefreshDeviceAllProps 增加离线检查
- 新增 miot_errors.go:MIoT 错误码定义
- 全局日志级别 Info→Debug/Warn(降低噪音)

**示例与文档**
- 所有示例代码同步 API 变更
- 传感器示例改用 Or 组合过滤器
- ARCH_PLAN 等迁移文档同步更新
2026-08-05 17:54:08 +08:00
..

xiaomi — Xiaomi 智能家居 Go SDK 高级封装层

xiaomihome/xiaomi 是 xiaomihome/miot 的强类型上层封装,将底层 map[string]interface{} API 转换为主流 Go 风格的 struct + interface,并提供设备控制抽象,隐藏 siid/piid 细节。

快速开始

import (
    "xiaomihome/miot"
    "xiaomihome/xiaomi"
    "xiaomihome/xiaomi/devices"
)

// 1. 创建底层 miot 客户端
miotClient := miot.NewMIoTClient("phone", entryData, "", "cn", miot.CtrlModeAuto)
miotClient.Init()

// 2. 创建 HTTP 客户端
httpClient, _ := miot.NewMIoTHttpClient(cloudServer, clientID, accessToken)

// 3. 包装为 xiaomi.Client
client := xiaomi.NewClient(miotClient, httpClient)

API 参考

Client 主入口 (client.go)

client := xiaomi.NewClient(inner *miot.MIoTClient, httpClient *miot.MIoTHttpClient)

client.Inner()      // 返回底层 MIoTClient
client.HTTPClient() // 返回底层 MIoTHttpClient

用户信息 (user.go)

user, err := client.GetUserInfo()
// → *UserInfo{UID, NickName, AvatarURL}

家庭/房间 (homes.go)

homes, err := client.GetHomeList()              // → []*HomeInfo
home, err := client.GetHome(homeID)             // → *HomeInfo (含房间列表)
rooms, err := client.GetRooms(homeID)           // → []*RoomInfo
dids, err := client.GetRoomDevices(homeID, roomID) // → []string

HomeInfo 结构:

字段 类型 说明
ID string 家庭 ID
Name string 家庭名称
UID string 用户 ID
GroupID string MIPS 分组 ID
Rooms []RoomInfo 房间列表
DidList []string 所有设备 DID
ShareState bool 是否为共享家庭

设备列表 (devices.go)

devices, err := client.GetDevices(opts...) // → []*DeviceInfo
device, err := client.GetDevice(did)       // → *DeviceInfo
err := client.RefreshDevices()             // 强制刷新设备列表

过滤器(组合使用):

// 只获取在线设备
client.GetDevices(xiaomi.FilterByOnline())

// 指定家庭 + 在线
client.GetDevices(
    xiaomi.FilterByHome("home001"),
    xiaomi.FilterByOnline(),
)

// 指定房间
client.GetDevices(xiaomi.FilterByRoom("room001"))

// 指定型号
client.GetDevices(xiaomi.FilterByModel("xiaomi.aircondition.c1"))

属性读写 (properties.go)

prop, err := client.GetProp(did, siid, piid)  // → *PropertyValue
err := client.SetProp(did, siid, piid, value)

props, err := client.GetProps([]PropKey{...})       // → []*PropertyValue
results, err := client.SetProps([]PropKeyValue{...}) // → []*PropResult

Action 调用 (actions.go)

result, err := client.Action(did, siid, aiid, params) // → *ActionResult

订阅通知 (subscribe.go)

// 属性变化订阅
subID, err := client.SubProp(did, siid, piid, func(did string, prop *PropertyValue) {
    fmt.Printf("属性变化: %v\n", prop.Value)
})

// 取消属性订阅
client.UnsubProp(did, subID)

// 事件订阅
subID, err := client.SubEvent(did, siid, eiid, func(did string, event *EventOccurred) {
    fmt.Printf("事件: %v\n", event.Arguments)
})
client.UnsubEvent(did, subID)

// 设备在线状态订阅
client.SubDeviceState(did, func(did string, state xiaomi.DeviceState) {
    if state == xiaomi.StateOnline {
        fmt.Println("设备上线")
    }
})
client.UnsubDeviceState(did)

设备控制抽象 (devices/)

基础能力 (BaseDevice)

所有设备控制对象共享的基础方法:

d.DID()      // 设备 ID
d.Name()     // 设备名称
d.Model()    // 设备型号
d.Online()   // 是否在线
d.Info()     // 完整设备信息 *DeviceInfo

d.GetProp(siid, piid)          // 读取属性
d.SetProp(siid, piid, value)   // 写入属性
d.Action(siid, aiid, params)   // 调用 Action
d.SubProp(siid, piid, handler) // 订阅属性变化
d.SubDeviceState(handler)      // 订阅在线状态

开关 (Switch)

sw, _ := devices.NewSwitch(client, info)

sw.TurnOn()
sw.TurnOff()
sw.Toggle()
on, _ := sw.IsOn()

// 订阅状态变化
sw.OnPropsChanged(func(on bool) { ... })

灯光 (Light)

light, _ := devices.NewLight(client, info)

light.TurnOn() / TurnOff() / Toggle()

light.SetBrightness(80)      // 1-100
b, _ := light.GetBrightness()

light.SetColorTemp(4000)     // 2700-6500K
k, _ := light.GetColorTemp()

light.SetColor(255, 0, 0)    // RGB 0-255
r, g, b, _ := light.GetColor()

light.OnPropsChanged(func(state LightState) { ... })

空调 (AirConditioner)

ac, _ := devices.NewAirConditioner(client, info)

// 基础
ac.TurnOn() / TurnOff()
on, _ := ac.IsOn()

// 模式
ac.SetMode(devices.ModeCool)       // Cool/Heat/Fan/Dry/Auto
mode, _ := ac.GetMode()

// 温度
ac.SetTargetTemp(26.0)
target, _ := ac.GetTargetTemp()
current, _ := ac.GetCurrentTemp()

// 风速
ac.SetFanSpeed(devices.SpeedAuto)  // Low/Medium/High/Auto
speed, _ := ac.GetFanSpeed()

// 摆风
ac.SetSwing(devices.SwingBoth)     // Off/Vertical/Horizontal/Both
swing, _ := ac.GetSwing()

// 湿度(部分支持)
ac.SetTargetHumidity(50)
ac.GetTargetHumidity()
ac.GetCurrentHumidity()

// 电辅热 / 节能(部分支持)
ac.SetAuxHeat(true)
ac.SetEco(true)

ac.OnPropsChanged(func(state ACState) { ... })

风扇 (Fan)

fan, _ := devices.NewFan(client, info)
fan.TurnOn() / TurnOff()
fan.SetFanLevel(2)         // 1-4 档位
fan.SetOscillation(true)   // 摇头
fan.SetNaturalWind(true)   // 自然风

窗帘 (Cover)

cover, _ := devices.NewCover(client, info)
cover.Open() / Close() / Stop()
cover.SetPosition(50)      // 0=全关, 100=全开
pos, _ := cover.GetPosition()

加湿器 (Humidifier)

h, _ := devices.NewHumidifier(client, info)
h.TurnOn() / TurnOff()
h.SetTargetHumidity(60)
h.SetFanLevel(2)

扫地机 (Vacuum)

v, _ := devices.NewVacuum(client, info)
v.Start() / Stop()
v.ReturnToDock()
v.SetFanLevel(3)           // 吸力档位
status, _ := v.GetStatus() // → VacuumStatus{On, Mode, FanLevel, Battery}

热水器 (WaterHeater)

wh, _ := devices.NewWaterHeater(client, info)
wh.TurnOn() / TurnOff()
wh.SetTargetTemp(45.0)
wh.GetCurrentTemp()

温控器 (Thermostat)

t, _ := devices.NewThermostat(client, info)
t.TurnOn() / TurnOff()
t.SetTargetTemp(22.0)
t.SetHeating(true)

工厂函数 (factory.go)

自动根据设备 URN 创建对应的控制接口:

for _, info := range deviceList {
    ctrl, err := devices.Create(client, info)
    if err != nil || ctrl == nil {
        continue
    }
    switch d := ctrl.(type) {
    case devices.Switch:
        d.TurnOn()
    case devices.Light:
        d.SetBrightness(80)
    case devices.AirConditioner:
        d.SetTargetTemp(26.0)
    case devices.Fan:
        d.SetFanLevel(2)
    case devices.Cover:
        d.Open()
    case devices.Humidifier:
        d.SetTargetHumidity(60)
    case devices.Vacuum:
        d.Start()
    case devices.WaterHeater:
        d.SetTargetTemp(45.0)
    case devices.Thermostat:
        d.SetTargetTemp(22.0)
    }
}

SPEC 解析器 (specs/)

用于直接从设备 SPEC 中按功能语义查找 siid/piid:

resolver := specs.NewPropertyResolver(spec)

siid, piid, ok := resolver.FindByType("on")                     // 按属性类型
piid, ok := resolver.FindByFormat("bool", siid)                 // 按数据格式
siid, piid, ok := resolver.FindByDescription("开关")             // 按描述
aiid, ok := resolver.FindAction(siid, "toggle")                 // 按 Action 类型
props := resolver.GetAllProperties(siid)                        // 获取某服务所有属性
prop := resolver.GetProperty(siid, piid)                        // 获取单个属性

错误处理

所有错误都实现了 Go 标准 error 接口,同时可通过 errors.Is 判断:

if errors.Is(err, xiaomi.ErrDeviceOffline) { ... }
if errors.Is(err, xiaomi.ErrNotSupported) { ... }

// ClientError 携带操作上下文
var ce *xiaomi.ClientError
if errors.As(err, &ce) {
    fmt.Println("操作:", ce.Op, "设备:", ce.DID)
}

关于 siid/piid

如果你需要直接操作底层属性,xiaomi 封装层仍支持传统的 siid/piid 方式:

// 读取空调开关 (常见 siid=2, piid=1)
prop, _ := client.GetProp("did", 2, 1)

// 写入目标温度
client.SetProp("did", 2, 3, 26.0)

但强烈推荐使用设备控制抽象,它会自动从 SPEC 解析正确的 siid/piid。

完整示例

参见 examples/main.go。