go.mod 模块名为 xiaomihome,将所有内部 import 从 "miot" 更新为 "xiaomihome/miot",涉及所有测试文件和示例文件。新增 README.md 项目 介绍文档(架构概览、模块说明、移植进度等)。新增 ARCH_PLAN.md 架构 设计文档。
29 KiB
go-xiaomihome 上层封装架构设计规划
状态:Proposed | 版本:v1.0 | 日期:2026-06-28
一、问题分析
当前 miot 包的三个核心痛点:
| 痛点 | 现状 | 影响 |
|---|---|---|
| 大量 map[string]interface{} | 22+ 个 API 返回 map,10+ 个结构体字段使用 map,回调全部用 map | 无类型安全、无 IDE 补全、运行时才发现错误 |
| 高层信息分散在裸 map | GetUserInfo/GetHomeInfos/GetDevices 全部返回 map | 用户/家庭/房间/设备信息需要用 ["key"] 取值 |
| 设备控制无抽象 | 直接走 SetProp(siid, piid, value) | 不知道空调当前模式/温度具体对应哪个 siid/piid |
二、设计目标
- 强类型封装:所有 API 返回值从
map[string]interface{}转为明确的结构体 - 设备控制抽象:空调、灯光、开关等提供统一控制接口,隐藏 siid/piid
- 订阅回调强类型:PropsChanged / EventOccurred / DeviceStateChanged 回调携带强类型数据
- miot 底层隔离:上层使用者不直接接触 miot 包的任何类型(可选零依赖)
- 渐进迁移:miot 包自行不变,封装层纯粹是一层薄壳
三、包结构设计
go-xiaomihome/
├── miot/ # 现有底层包(不动)
│ └── ... # 与 Python miot/ 严格对齐
│
├── xiaomi/ # [新] 高级封装层
│ ├── go.mod # module xiaomihome/xiaomi
│ │
│ ├── types.go # 所有强类型结构体定义
│ ├── client.go # Client 主入口(包装 miot.MIoTClient)
│ ├── devices.go # 设备列表 + 设备信息
│ ├── homes.go # 家庭/房间信息
│ ├── user.go # 用户信息
│ ├── properties.go # 属性读写封装(强类型)
│ ├── actions.go # Action 调用封装
│ ├── subscribe.go # 订阅通知 API(强类型回调)
│ ├── errors.go # 封装层错误定义
│ │
│ ├── devices/ # [新] 设备控制抽象
│ │ ├── base.go # Device 基础接口 + BaseDevice 实现
│ │ ├── air_conditioner.go # 空调控制接口
│ │ ├── light.go # 灯光控制接口
│ │ ├── switch.go # 开关控制接口
│ │ ├── fan.go # 风扇控制接口
│ │ ├── cover.go # 窗帘/晾衣架控制接口
│ │ ├── humidifier.go # 加湿器控制接口
│ │ ├── vacuum.go # 扫地机控制接口
│ │ ├── water_heater.go # 热水器控制接口
│ │ └── thermostat.go # 温控器/电热毯控制接口
│ │
│ └── specs/ # [新] SPEC 辅助(设备能力查询)
│ └── resolver.go # 按功能语义解析 SPEC,自动找 siid/piid
四、核心类型设计
4.1 types.go — 强类型结构体
package xiaomi
// ========== 基础信息 ==========
// UserInfo 用户信息
type UserInfo struct {
UID string `json:"uid"`
NickName string `json:"miliao_nick"`
AvatarURL string `json:"avatar_url"`
}
// HomeInfo 家庭信息
type HomeInfo struct {
ID string `json:"id"`
Name string `json:"name"`
UID string `json:"uid"`
GroupID string `json:"group_id"`
Rooms []RoomInfo `json:"rooms"`
DidList []string `json:"did_list"`
ShareState bool `json:"share_state"`
}
// RoomInfo 房间信息
type RoomInfo struct {
ID string `json:"id"`
Name string `json:"name"`
HomeID string `json:"home_id"`
DidList []string `json:"did_list"` // 房间内设备 DID
}
// DeviceInfo 设备信息(替代 map[string]interface{})
type DeviceInfo struct {
DID string `json:"did"`
Name string `json:"name"`
Model string `json:"model"`
Manufacturer string `json:"manufacturer"`
FWVersion string `json:"fw_version"`
Icon string `json:"icon"`
URN string `json:"urn"` // 设备 SPEC 类型
Token string `json:"token"`
IP string `json:"local_ip"`
RSSI int `json:"rssi"`
SSID string `json:"ssid"`
BSSID string `json:"bssid"`
Online bool `json:"is_online"`
HomeID string `json:"home_id"`
HomeName string `json:"home_name"`
RoomID string `json:"room_id"`
RoomName string `json:"room_name"`
OwnerID string `json:"owner_id"`
ParentID string `json:"parent_id"`
ParentModel string `json:"parent_model"`
GroupID string `json:"group_id"`
ConnectType int `json:"connect_type"` // 0=WiFi, 1=BLE, 2=ZigBee, ...
}
// ========== 属性/事件 ==========
// PropertyValue 属性值(强类型)
type PropertyValue struct {
DID string `json:"did"`
SIID int `json:"siid"`
PIID int `json:"piid"`
Code int `json:"code"`
Value interface{} `json:"value"` // 对上层暴露具体类型,不作转换
Type string `json:"type"` // SPEC format: "bool"/"uint8"/"float"/"string"
}
// PropertiesChanged 属性变化事件
type PropertiesChanged struct {
DID string `json:"did"`
Properties []PropertyValue `json:"properties"`
}
// EventOccurred 事件发生通知
type EventOccurred struct {
DID string `json:"did"`
SIID int `json:"siid"`
EIID int `json:"eiid"`
Arguments map[string]interface{} `json:"arguments"`
Timestamp int64 `json:"timestamp"`
}
// DeviceState 设备在线状态
type DeviceState string
const (
StateOnline DeviceState = "online"
StateOffline DeviceState = "offline"
)
// ========== Action ==========
// ActionResult Action 执行结果
type ActionResult struct {
DID string `json:"did"`
SIID int `json:"siid"`
AIID int `json:"aiid"`
Code int `json:"code"`
Out []map[string]interface{} `json:"out"`
}
4.2 Client — 主入口
package xiaomi
// Client 高级封装客户端(包装 miot.MIoTClient)
type Client struct {
inner *miot.MIoTClient // 内部 miot 实例(不对外暴露)
currentUser UserInfo
homes map[string]*HomeInfo
mu sync.RWMutex
}
// NewClient 创建客户端
func NewClient(inner *miot.MIoTClient) *Client
// ========== 用户/家庭/房间 ==========
// GetUserInfo 获取用户信息(强类型返回)
func (c *Client) GetUserInfo() (*UserInfo, error)
// GetHomeList 获取所有家庭列表
func (c *Client) GetHomeList() ([]*HomeInfo, error)
// GetHome 获取指定家庭详情(含房间)
func (c *Client) GetHome(homeID string) (*HomeInfo, error)
// GetRooms 获取指定家庭的所有房间
func (c *Client) GetRooms(homeID string) ([]*RoomInfo, error)
// GetRoomDevices 获取指定房间的设备 DID 列表
func (c *Client) GetRoomDevices(homeID, roomID string) ([]string, error)
// ========== 设备列表 ==========
// GetDevices 获取所有设备信息(强类型返回)
func (c *Client) GetDevices(opts ...DeviceFilter) ([]*DeviceInfo, error)
// GetDevice 获取单个设备信息
func (c *Client) GetDevice(did string) (*DeviceInfo, error)
// RefreshDevices 刷新设备列表
func (c *Client) RefreshDevices() error
// DeviceFilter 设备过滤器函数类型
type DeviceFilter func(*DeviceInfo) bool
// FilterByHome 按家庭过滤
func FilterByHome(homeID string) DeviceFilter
// FilterByRoom 按房间过滤
func FilterByRoom(roomID string) DeviceFilter
// FilterByOnline 只返回在线设备
func FilterByOnline() DeviceFilter
// FilterByModel 按型号过滤
func FilterByModel(model string) DeviceFilter
4.3 属性读写封装
package xiaomi
// GetProp 读取单个属性(与 miot.GetProp 对齐命名)
func (c *Client) GetProp(did string, siid, piid int) (*PropertyValue, error)
// SetProp 写入单个属性(与 miot.SetProp 对齐命名)
func (c *Client) SetProp(did string, siid, piid int, value interface{}) error
// GetProps 批量读取属性(与 miot.GetProps 对齐命名)
func (c *Client) GetProps(params []PropKey) ([]*PropertyValue, error)
// SetProps 批量写入属性(与 miot.SetProps 对齐命名)
func (c *Client) SetProps(params []PropKeyValue) ([]*PropResult, error)
// PropKey 属性键对
type PropKey struct {
DID string
SIID int
PIID int
}
// PropKeyValue 属性键值对
type PropKeyValue struct {
PropKey
Value interface{}
}
// PropResult 写入结果
type PropResult struct {
PropKey
Code int
}
// ========== Action 封装 ==========
// Action 调用设备 Action(与 miot.Action 对齐命名)
func (c *Client) Action(did string, siid, aiid int, params []interface{}) (*ActionResult, error)
4.4 订阅通知接口
底层机制回顾:miot 包通过 subTree(topic 匹配树)管理订阅。设备消息从 MQTT/LAN 推送到达后,走 OnPropMsg / OnEventMsg → subTree.Match(topic) → 回调原始 func(map[string]interface{}, interface{}) handler。封装层不直接碰 MQTT,而是复用 miot 的 SubProp/SubEvent/SubDeviceState,做 handler 签名转换。
数据流:
MQTT/LAN 推送
→ miot.OnPropMsg(params) / miot.OnEventMsg(params)
→ subTree.Match(did/p/siid/piid)
→ miot handler: func(map[string]interface{}, interface{})
│
▼ 封装层转换(本文范围)
→ xiaomi handler: func(did string, prop *PropertyValue)
package xiaomi
// ========== 强类型回调定义 ==========
// PropHandler 属性变化回调(强类型,替代 miot 的 map handler)
type PropHandler func(did string, prop *PropertyValue)
// EventHandler 事件回调(强类型)
type EventHandler func(did string, event *EventOccurred)
// DeviceStateHandler 设备在线状态变化回调
type DeviceStateHandler func(did string, state DeviceState)
// ========== 订阅方法(与 miot.SubProp/SubEvent/SubDeviceState 对齐命名)==========
// SubProp 订阅设备属性变化通知
// 内部调用 miot.MIoTClient.SubProp,在 adapter handler 中完成 map→PropertyValue 转换
// siid/piid 为 0 表示订阅该设备所有属性
// 返回 subscription ID(与 miot.SubProp 一致)
func (c *Client) SubProp(did string, siid, piid int, handler PropHandler) (string, error)
// UnsubProp 取消属性订阅(与 miot.UnsubProp 对齐命名)
func (c *Client) UnsubProp(did string, subID string) error
// SubEvent 订阅设备事件通知
// 内部调用 miot.MIoTClient.SubEvent,完成 map→EventOccurred 转换
func (c *Client) SubEvent(did string, siid, eiid int, handler EventHandler) (string, error)
// UnsubEvent 取消事件订阅
func (c *Client) UnsubEvent(did string, subID string) error
// SubDeviceState 订阅设备在线状态变化
// 内部调用 miot.MIoTClient.SubDeviceState
func (c *Client) SubDeviceState(did string, handler DeviceStateHandler) error
// UnsubDeviceState 取消设备状态订阅
func (c *Client) UnsubDeviceState(did string) error
实现要点:
// SubProp 实现示意:包装 miot handler,转换 map → PropertyValue
func (c *Client) SubProp(did string, siid, piid int, handler PropHandler) (string, error) {
// 1. 包装 handler:map → PropertyValue
wrappedHandler := func(params map[string]interface{}, ctx interface{}) {
prop := &PropertyValue{
DID: params["did"].(string),
SIID: toInt(params["siid"]),
PIID: toInt(params["piid"]),
Value: params["value"],
}
handler(did, prop)
}
// 2. 委托给 miot 底层(触发 subTree 注册 + MQTT/LAN 底层订阅)
subID := c.inner.SubProp(did, wrappedHandler, siid, piid)
// 3. 记录 subID→did 映射(用于 UnsubProp 时清理)
c.propSubs[subID] = did
return subID, nil
}
subID 映射管理(miot.SubProp 返回 string subID,miot.UnsubProp 需要 subID):
type Client struct {
// ...
propSubs map[string]string // subID → did(用于 UnsubProp 清理)
eventSubs map[string]string // subID → did
}
与 miot 底层签名的对应关系:
| 封装层 API | 内部调用 miot 方法 | miot handler 签名 | 转换后 handler 签名 |
|---|---|---|---|
| SubProp | SubProp(did, wrapped, siid, piid) | func(map[string]interface{}, interface{}) |
func(string, *PropertyValue) |
| SubEvent | SubEvent(did, wrapped, siid, eiid) | func(map[string]interface{}, interface{}) |
func(string, *EventOccurred) |
| SubDeviceState | SubDeviceState(did, wrapped) | func(string, MIoTDeviceState, interface{}) |
func(string, DeviceState) |
五、设备控制抽象设计
5.1 架构思路
xiaomi/devices/
├── base.go # Device 基础接口
├── air_conditioner.go # AirConditioner 接口
├── light.go # Light 接口
├── switch.go # Switch 接口
├── fan.go # Fan 接口
├── cover.go # Cover 接口
├── humidifier.go # Humidifier 接口
├── vacuum.go # Vacuum 接口
├── water_heater.go # WaterHeater 接口
└── thermostat.go # Thermostat 接口
设计原则:
- 每种设备类型定义一个接口(Interface)和具体实现(Struct)
- 具体实现内部持有
*xiaomi.Client引用,将高层语义操作翻译为siid/piid调用 - 通过
specs/resolver.go从 SPEC 中自动解析出对应的 siid/piid - 用户不关心
siid=2, piid=3是什么,只关心ac.SetTargetTemp(26)/ac.SetMode(ModeCool)
5.2 BaseDevice
package devices
// BaseDevice 所有设备控制类的公共基础
type BaseDevice struct {
client *xiaomi.Client
info *xiaomi.DeviceInfo
}
// DID 返回设备 DID
func (d *BaseDevice) DID() string
// Name 返回设备名称
func (d *BaseDevice) Name() string
// Model 返回设备型号
func (d *BaseDevice) Model() string
// Online 返回设备在线状态
func (d *BaseDevice) Online() bool
// Info 返回完整设备信息
func (d *BaseDevice) Info() *xiaomi.DeviceInfo
// GetProp 快捷属性读取(委托给 xiaomi.Client.GetProp)
func (d *BaseDevice) GetProp(siid, piid int) (*xiaomi.PropertyValue, error)
// SetProp 快捷属性写入
func (d *BaseDevice) SetProp(siid, piid int, value interface{}) error
// Action 快捷 Action 调用
func (d *BaseDevice) Action(siid, aiid int, params []interface{}) (*xiaomi.ActionResult, error)
// SubProp 订阅属性变化(委托给 xiaomi.Client.SubProp)
func (d *BaseDevice) SubProp(siid, piid int, handler xiaomi.PropHandler) (string, error)
// SubEvent 订阅事件
func (d *BaseDevice) SubEvent(siid, eiid int, handler xiaomi.EventHandler) (string, error)
// SubDeviceState 订阅在线状态
func (d *BaseDevice) SubDeviceState(handler xiaomi.DeviceStateHandler) error
5.3 空调控制接口
// AirConditioner 空调统一控制接口
type AirConditioner interface {
// 基础能力
TurnOn() error
TurnOff() error
IsOn() (bool, error)
// 模式控制
SetMode(mode ACMode) error
GetMode() (ACMode, error)
// 温度控制
SetTargetTemp(celsius float64) error
GetTargetTemp() (float64, error)
GetCurrentTemp() (float64, error) // 室内温度
// 风速控制
SetFanSpeed(speed FanSpeed) error
GetFanSpeed() (FanSpeed, error)
// 摆风控制
SetSwing(mode SwingMode) error
GetSwing() (SwingMode, error)
// 湿度(部分空调支持)
SetTargetHumidity(pct int) error
GetTargetHumidity() (int, error)
GetCurrentHumidity() (int, error)
// 电辅热
SetAuxHeat(on bool) error
GetAuxHeat() (bool, error)
// 节能
SetEco(on bool) error
GetEco() (bool, error)
// 订阅
OnStateChanged(handler func(state ACState)) (string, error)
}
// ACMode 空调模式
type ACMode string
const (
ModeCool ACMode = "cool"
ModeHeat ACMode = "heat"
ModeFan ACMode = "fan"
ModeDry ACMode = "dry"
ModeAuto ACMode = "auto"
)
// FanSpeed 风速
type FanSpeed string
const (
SpeedLow FanSpeed = "low"
SpeedMedium FanSpeed = "medium"
SpeedHigh FanSpeed = "high"
SpeedAuto FanSpeed = "auto"
)
// SwingMode 摆风
type SwingMode string
const (
SwingOff SwingMode = "off"
SwingVertical SwingMode = "vertical"
SwingHorizontal SwingMode = "horizontal"
SwingBoth SwingMode = "both"
)
// ACState 空调完整状态
type ACState struct {
Power bool `json:"power"`
Mode ACMode `json:"mode"`
TargetTemp float64 `json:"target_temp"`
CurrentTemp float64 `json:"current_temp"`
TargetHumidity int `json:"target_humidity"`
CurrentHumidity int `json:"current_humidity"`
FanSpeed FanSpeed `json:"fan_speed"`
Swing SwingMode `json:"swing"`
AuxHeat bool `json:"aux_heat"`
Eco bool `json:"eco"`
}
实现原理:acDevice 结构体内部维护一个 specMap,通过 specs/resolver.go 从 SPEC 中找出:
power→siid=2, piid=1mode→siid=2, piid=2target_temp→siid=2, piid=3fan_speed→siid=2, piid=4- ...
5.4 灯光控制接口
// Light 灯光统一控制接口
type Light interface {
TurnOn() error
TurnOff() error
Toggle() error
// 亮度
SetBrightness(pct int) error // 1-100, -1 表示不支持
GetBrightness() (int, error)
// 色温
SetColorTemp(kelvin int) error // 2700-6500, -1 表示不支持
GetColorTemp() (int, error)
// 颜色
SetColor(r, g, b int) error // 0-255, -1 表示不支持
GetColor() (r, g, b int, error)
OnStateChanged(handler func(state LightState)) (string, error)
}
type LightState struct {
Power bool `json:"power"`
Brightness int `json:"brightness"`
ColorTemp int `json:"color_temp"`
ColorRGB [3]int `json:"color_rgb"`
}
5.5 开关控制接口
// Switch 开关统一控制接口
type Switch interface {
TurnOn() error
TurnOff() error
Toggle() error
IsOn() (bool, error)
OnStateChanged(handler func(on bool)) (string, error)
}
5.6 其他设备接口(概要)
// Fan 风扇接口
type Fan interface {
TurnOn() error
TurnOff() error
SetSpeed(speed FanLevel) error // 0=off, 1-4 档位
SetOscillation(on bool) error
SetNaturalWind(on bool) error
}
// Cover 窗帘/晾衣架
type Cover interface {
Open() error
Close() error
Stop() error
SetPosition(pct int) error // 0=关, 100=全开
GetPosition() (int, error)
}
// Humidifier 加湿器/除湿机
type Humidifier interface {
TurnOn() error
TurnOff() error
SetTargetHumidity(pct int) error
SetFanSpeed(speed FanLevel) error
}
// Vacuum 扫地机
type Vacuum interface {
Start() error
Stop() error
ReturnToDock() error
SetFanSpeed(speed FanLevel) error
GetStatus() (VacuumStatus, error)
}
// WaterHeater 热水器
type WaterHeater interface {
TurnOn() error
TurnOff() error
SetTargetTemp(celsius float64) error
GetCurrentTemp() (float64, error)
}
// Thermostat 温控器
type Thermostat interface {
SetTargetTemp(celsius float64) error
GetCurrentTemp() (float64, error)
SetHeating(on bool) error
}
六、specs/resolver.go — SPEC 语义解析器
为了让设备控制类能自动找到 power / mode / target_temp 等语义属性对应的 siid/piid,需要一个规约解析器:
package specs
// PropertyResolver 属性语义解析器
// 输入:设备的 SPEC Instance
// 输出:语义 → (siid, piid) 映射
type PropertyResolver struct {
spec *miot.MIoTSpecInstance
// cache
byType map[string][]*miot.MIoTSpecProperty // "power" → [{siid:2, piid:1}]
byDesc map[string]*miot.MIoTSpecProperty // "开关" → {siid:2, piid:1}
byFormat map[string][]*miot.MIoTSpecProperty // "bool" → [...]
}
// NewPropertyResolver 创建解析器
func NewPropertyResolver(spec *miot.MIoTSpecInstance) *PropertyResolver
// FindByType 按 SPEC property type 查找
// type 如: "on", "mode", "target-temperature", "fan-level", "brightness"
func (r *PropertyResolver) FindByType(propType string) (siid, piid int, found bool)
// FindByFormat 按数据格式查找
// format 如: "bool", "uint8", "float", "string"
// 返回第一个匹配的
func (r *PropertyResolver) FindByFormat(format string, siid int) (piid int, found bool)
// FindAction 查找 Action
func (r *PropertyResolver) FindAction(siid int, actionType string) (aiid int, found bool)
解析策略
每种设备控制接口的构造流程:
// acDevice 构造函数
func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirConditioner, error) {
// 1. 获取设备 SPEC
device := client.GetMIoTDevice(info.DID) // 内部可能获取
spec := device.SpecInstance()
// 2. 创建解析器
resolver := specs.NewPropertyResolver(spec)
// 3. 解析语义映射
powerSIID, powerPIID, _ := resolver.FindByType("on")
modeSIID, modePIID, _ := resolver.FindByType("mode")
tempSIID, tempPIID, _ := resolver.FindByType("target-temperature")
fanSIID, fanPIID, _ := resolver.FindByType("fan-level")
// 4. 如果某个语义映射找不到,该功能标记为不支持
return &acDevice{
BaseDevice: NewBaseDevice(client, info),
propMap: propMap{
power: {powerSIID, powerPIID},
mode: {modeSIID, modePIID},
temp: {tempSIID, tempPIID},
fan: {fanSIID, fanPIID},
},
}, nil
}
七、关键设计决策
ADR-001:封装层不修改 miot 包
状态:Proposed
背景:miot 包需要与 Python 严格对齐,API 签名和行为不能轻易改变。
决定:上层封装 xiaomi/ 作为独立 Go module(module xiaomihome/xiaomi),通过 import miot 使用,不做任何修改。封装层专注于转换和简化。
后果:
- ✅ miot 对齐性不受影响
- ✅ 封装层可独立测试
- ❌ 封装层需要处理 miot 类型转换(额外开销但可控)
ADR-002:设备控制接口按功能语义而非 SPEC 编号
状态:Proposed
背景:不同型号空调的 power 属性可能是 siid=2,piid=1 或 siid=3,piid=1,必须抽象。
决定:接口方法使用功能语义(SetTargetTemp、SetMode),由 specs.PropertyResolver 在构造时从 SPEC 自动解析映射关系。解析失败的功能标记为不支持。
后果:
- ✅ 用户无需关心 siid/piid
- ✅ 跨型号兼容
- ❌ 需要在 SPEC 解析阶段做额外工作
- ❌ 部分非常规设备可能解析不完全,需要降级方案
ADR-003:Interface 而非 Struct 嵌入
状态:Proposed
背景:有两种设计方式:纯 interface 或 struct 嵌入 + 方法重写。
决定:每种设备类型定义 interface + 私有 struct 实现。工厂函数返回 interface。这样用户代码只依赖接口,便于 mock 测试。
后果:
- ✅ 可测试性强(mock 接口即可)
- ✅ 未来可替换实现
- ❌ 增加了一点 boilerplate(接口 + 实现双定义)
ADR-004:回调使用强类型事件对象
状态:Proposed
背景:底层回调全是 func(map[string]interface{}, interface{})。
决定:封装层在内部转换底层 map 回调为强类型事件对象(*PropertyValue、*EventOccurred),对上层暴露强类型回调签名。
后果:
- ✅ 类型安全
- ✅ IDE 补全
- ❌ 封包/解包开销(每个回调一次类型断言,影响极小)
ADR-005:Filter 模式用于设备过滤
状态:Proposed
背景:用户需要按家庭/房间/在线状态筛选设备。
决定:使用函数式 DeviceFilter 模式(func(*DeviceInfo) bool),提供预设 Filter 组合。
后果:
- ✅ 灵活可组合
- ✅ 无额外依赖
- ❌ 性能对比预建索引略差(但对家庭设备量级无影响)
八、实施计划
Phase 1 — 基础封装(优先级 P0)
| 任务 | 文件 | 工作量 |
|---|---|---|
| 项目骨架搭建 | xiaomi/go.mod, module 初始化 |
小 |
| types.go 结构体定义 | xiaomi/types.go |
中 |
| Client 基础框架 | xiaomi/client.go |
中 |
| 用户/家庭/房间信息封装 | xiaomi/user.go, homes.go |
中 |
| 设备列表封装 | xiaomi/devices.go |
中 |
| 属性读写封装 | xiaomi/properties.go |
中 |
| Action 封装 | xiaomi/actions.go |
小 |
| 错误定义 | xiaomi/errors.go |
小 |
Phase 2 — 订阅与通知(优先级 P0)
| 任务 | 文件 | 工作量 |
|---|---|---|
| 订阅通知接口 | xiaomi/subscribe.go |
中 |
| 底层回调 → 强类型事件转换 | xiaomi/subscribe.go |
中 |
| 设备状态订阅 | xiaomi/subscribe.go |
小 |
Phase 3 — 设备控制抽象(优先级 P1)
| 任务 | 文件 | 工作量 |
|---|---|---|
| SPEC 语义解析器 | xiaomi/specs/resolver.go |
大 |
| BaseDevice 基础实现 | xiaomi/devices/base.go |
中 |
| Switch 接口 + 实现 | xiaomi/devices/switch.go |
小 |
| Light 接口 + 实现 | xiaomi/devices/light.go |
中 |
| AirConditioner 接口 + 实现 | xiaomi/devices/air_conditioner.go |
大 |
| Fan 接口 + 实现 | xiaomi/devices/fan.go |
中 |
| Cover 接口 + 实现 | xiaomi/devices/cover.go |
中 |
| Humidifier/Vacuum/WaterHeater | 各文件 | 各小 |
| 工厂函数(NewXXX 从 DeviceInfo 创建) | xiaomi/devices/factory.go |
中 |
Phase 4 — 测试和文档(优先级 P2)
| 任务 | 文件 | 工作量 |
|---|---|---|
| 单元测试 | *_test.go |
大 |
| 集成示例 | xiaomi/examples/ |
中 |
| API 文档 | xiaomi/README.md |
中 |
九、使用示例(目标形态)
package main
import (
"fmt"
"xiaomihome/xiaomi"
"xiaomihome/xiaomi/devices"
)
func main() {
// 1. 创建底层 miot 客户端(照常)
// ... miot.NewMIoTClient(...) ...
// 2. 用上层封装包装
client := xiaomi.NewClient(miotClient)
// 3. 获取用户信息(强类型!)
user, _ := client.GetUserInfo()
fmt.Printf("用户: %s (UID: %s)\n", user.NickName, user.UID)
// 4. 获取家庭和房间
homes, _ := client.GetHomeList()
for _, h := range homes {
fmt.Printf("家庭: %s\n", h.Name)
for _, r := range h.Rooms {
fmt.Printf(" 房间: %s (%d 设备)\n", r.Name, len(r.DidList))
}
}
// 5. 获取设备列表(可过滤)
devices_, _ := client.GetDevices(
xiaomi.FilterByHome(homes[0].ID),
xiaomi.FilterByOnline(),
)
for _, d := range devices_ {
fmt.Printf("设备: %s (%s) 在线:%v\n", d.Name, d.Model, d.Online)
}
// 6. 创建空调控制对象(自动解析 SPEC)
acInfo := devices_[0] // 假设是个空调
ac, err := devices.NewAirConditioner(client, acInfo)
if err != nil {
// 该设备不是空调或不支持
return
}
// 7. 像操作普通对象一样控制(隐藏了 siid/piid!)
ac.TurnOn()
ac.SetMode(devices.ModeCool)
ac.SetTargetTemp(26)
ac.SetFanSpeed(devices.SpeedAuto)
// 8. 订阅变化
ac.OnStateChanged(func(state devices.ACState) {
fmt.Printf("空调状态变化: 模式=%s 温度=%.1f°C\n",
state.Mode, state.TargetTemp)
})
// 9. 灯光控制
light, _ := devices.NewLight(client, lightInfo)
light.TurnOn()
light.SetBrightness(80)
light.SetColorTemp(4000)
// 10. 开关控制
sw, _ := devices.NewSwitch(client, switchInfo)
sw.Toggle()
}
十、待讨论问题
-
工厂模式:
devices.NewAirConditioner(client, info)还是client.NewAirConditioner(info)?前者职责分离更清晰,后者使用稍方便。 -
SPEC 解析失败降级:当某个功能语义找不到时,是返回 error(构造失败)还是返回一个部分功能的对象(不支持的方法返回
ErrNotSupported)?建议后者——空调不能调摆风总比整个空调不能用好。 -
go-xiaomihome 主模块 go.mod:目前
go-xiaomihome/go.mod是module xiaomihome,miot 是子包xiaomihome/miot。新封装包是否作为xiaomihome/xiaomi子包,还是独立 repo?建议子包,降低引用复杂度。 -
是否需要 Cloud 直连简化:
xiaomi.Client的构造函数是否应支持直接传配置,内部自建 miot 客户端(完整封装),这样用户连 miot 都不需要 import。建议 Phase 4 再做。
本文档为架构规划,尚未执行。