chore: 提交高层 xiaomi 封装库及示例
- 新增 xiaomi/examples/ 下 8 个示例程序(用户/家庭查询、设备列表、开关/灯光/空调控制、属性订阅、高级过滤分类、SPEC 解析) - miot_client_sub.go: 重构 SubProp/SubEvent,使用 buildPropTopic/buildEventTopic 支持通配符订阅(siid/piid=0 → +),并修复锁顺序问题(将 RequestRefreshProp 移到 Lock 外) - spec_parser.go: 新增 downloadSpecFile 方法,本地 SPEC 文件缺失时自动从 miot-spec.org 下载 - ARCH_PLAN.md: 架构设计从 Proposed 更新为 Accepted(v1.0→v1.1),补充设备分类/工厂/SPEC 映射等模块设计 - xiaomi/: 新增 miot 上层强类型封装模块,包含 Client 主入口、用户/家庭/设备 API、属性读写、动作调用、订阅通知,以及 devices/ 设备控制抽象(Switch/Light/AirConditioner/Fan/Cover/Humidifier/Vacuum/WaterHeater/Thermostat)和 specs/ SPEC 查询辅助 - 更多 xiaomi 示例(风扇/窗帘/传感器控制)
This commit is contained in:
+320
-135
@@ -1,6 +1,6 @@
|
||||
# go-xiaomihome 上层封装架构设计规划
|
||||
|
||||
> 状态:Proposed | 版本:v1.0 | 日期:2026-06-28
|
||||
> 状态:Accepted | 版本:v1.1 | 日期:2026-06-28
|
||||
|
||||
---
|
||||
|
||||
@@ -48,6 +48,8 @@ go-xiaomihome/
|
||||
│ │
|
||||
│ ├── devices/ # [新] 设备控制抽象
|
||||
│ │ ├── base.go # Device 基础接口 + BaseDevice 实现
|
||||
│ │ ├── kind.go # Kind 枚举 + Classify 设备分类
|
||||
│ │ ├── factory.go # NewDevice 通用工厂
|
||||
│ │ ├── air_conditioner.go # 空调控制接口
|
||||
│ │ ├── light.go # 灯光控制接口
|
||||
│ │ ├── switch.go # 开关控制接口
|
||||
@@ -59,7 +61,8 @@ go-xiaomihome/
|
||||
│ │ └── thermostat.go # 温控器/电热毯控制接口
|
||||
│ │
|
||||
│ └── specs/ # [新] SPEC 辅助(设备能力查询)
|
||||
│ └── resolver.go # 按功能语义解析 SPEC,自动找 siid/piid
|
||||
│ ├── resolver.go # 按功能语义解析 SPEC,自动找 siid/piid
|
||||
│ └── mapper.go # SPEC 值映射(原始值 ↔ 枚举字符串)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -89,6 +92,10 @@ type HomeInfo struct {
|
||||
Rooms []RoomInfo `json:"rooms"`
|
||||
DidList []string `json:"did_list"`
|
||||
ShareState bool `json:"share_state"`
|
||||
CityID string `json:"city_id"` // 城市ID
|
||||
Longitude float64 `json:"longitude"` // 经度
|
||||
Latitude float64 `json:"latitude"` // 纬度
|
||||
Address string `json:"address"` // 地址
|
||||
}
|
||||
|
||||
// RoomInfo 房间信息
|
||||
@@ -125,6 +132,9 @@ type DeviceInfo struct {
|
||||
ConnectType int `json:"connect_type"` // 0=WiFi, 1=BLE, 2=ZigBee, ...
|
||||
}
|
||||
|
||||
// DeviceKind 从 URN/Model 快速判断设备大类(不依赖 SPEC 加载)
|
||||
func (d *DeviceInfo) DeviceKind() devices.Kind
|
||||
|
||||
// ========== 属性/事件 ==========
|
||||
|
||||
// PropertyValue 属性值(强类型)
|
||||
@@ -133,8 +143,8 @@ type PropertyValue struct {
|
||||
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"
|
||||
Value interface{} `json:"value"` // 原始值,上层自行类型断言
|
||||
Type string `json:"type"` // 仅 GetProp 场景可用(从 SPEC 查询);SubProp 回调中为空
|
||||
}
|
||||
|
||||
// PropertiesChanged 属性变化事件
|
||||
@@ -176,13 +186,12 @@ type ActionResult struct {
|
||||
```go
|
||||
package xiaomi
|
||||
|
||||
import "context"
|
||||
|
||||
// Client 高级封装客户端(包装 miot.MIoTClient)
|
||||
type Client struct {
|
||||
inner *miot.MIoTClient // 内部 miot 实例(不对外暴露)
|
||||
|
||||
currentUser UserInfo
|
||||
homes map[string]*HomeInfo
|
||||
mu sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewClient 创建客户端
|
||||
@@ -191,30 +200,30 @@ func NewClient(inner *miot.MIoTClient) *Client
|
||||
// ========== 用户/家庭/房间 ==========
|
||||
|
||||
// GetUserInfo 获取用户信息(强类型返回)
|
||||
func (c *Client) GetUserInfo() (*UserInfo, error)
|
||||
func (c *Client) GetUserInfo(ctx context.Context) (*UserInfo, error)
|
||||
|
||||
// GetHomeList 获取所有家庭列表
|
||||
func (c *Client) GetHomeList() ([]*HomeInfo, error)
|
||||
func (c *Client) GetHomeList(ctx context.Context) ([]*HomeInfo, error)
|
||||
|
||||
// GetHome 获取指定家庭详情(含房间)
|
||||
func (c *Client) GetHome(homeID string) (*HomeInfo, error)
|
||||
func (c *Client) GetHome(ctx context.Context, homeID string) (*HomeInfo, error)
|
||||
|
||||
// GetRooms 获取指定家庭的所有房间
|
||||
func (c *Client) GetRooms(homeID string) ([]*RoomInfo, error)
|
||||
func (c *Client) GetRooms(ctx context.Context, homeID string) ([]*RoomInfo, error)
|
||||
|
||||
// GetRoomDevices 获取指定房间的设备 DID 列表
|
||||
func (c *Client) GetRoomDevices(homeID, roomID string) ([]string, error)
|
||||
func (c *Client) GetRoomDevices(ctx context.Context, homeID, roomID string) ([]string, error)
|
||||
|
||||
// ========== 设备列表 ==========
|
||||
|
||||
// GetDevices 获取所有设备信息(强类型返回)
|
||||
func (c *Client) GetDevices(opts ...DeviceFilter) ([]*DeviceInfo, error)
|
||||
func (c *Client) GetDevices(ctx context.Context, opts ...DeviceFilter) ([]*DeviceInfo, error)
|
||||
|
||||
// GetDevice 获取单个设备信息
|
||||
func (c *Client) GetDevice(did string) (*DeviceInfo, error)
|
||||
func (c *Client) GetDevice(ctx context.Context, did string) (*DeviceInfo, error)
|
||||
|
||||
// RefreshDevices 刷新设备列表
|
||||
func (c *Client) RefreshDevices() error
|
||||
func (c *Client) RefreshDevices(ctx context.Context) error
|
||||
|
||||
// DeviceFilter 设备过滤器函数类型
|
||||
type DeviceFilter func(*DeviceInfo) bool
|
||||
@@ -227,6 +236,8 @@ func FilterByRoom(roomID string) DeviceFilter
|
||||
func FilterByOnline() DeviceFilter
|
||||
// FilterByModel 按型号过滤
|
||||
func FilterByModel(model string) DeviceFilter
|
||||
// FilterByKind 按设备大类过滤
|
||||
func FilterByKind(kind devices.Kind) DeviceFilter
|
||||
```
|
||||
|
||||
### 4.3 属性读写封装
|
||||
@@ -234,17 +245,19 @@ func FilterByModel(model string) DeviceFilter
|
||||
```go
|
||||
package xiaomi
|
||||
|
||||
import "context"
|
||||
|
||||
// GetProp 读取单个属性(与 miot.GetProp 对齐命名)
|
||||
func (c *Client) GetProp(did string, siid, piid int) (*PropertyValue, error)
|
||||
func (c *Client) GetProp(ctx context.Context, did string, siid, piid int) (*PropertyValue, error)
|
||||
|
||||
// SetProp 写入单个属性(与 miot.SetProp 对齐命名)
|
||||
func (c *Client) SetProp(did string, siid, piid int, value interface{}) error
|
||||
func (c *Client) SetProp(ctx context.Context, did string, siid, piid int, value interface{}) error
|
||||
|
||||
// GetProps 批量读取属性(与 miot.GetProps 对齐命名)
|
||||
func (c *Client) GetProps(params []PropKey) ([]*PropertyValue, error)
|
||||
func (c *Client) GetProps(ctx context.Context, params []PropKey) ([]*PropertyValue, error)
|
||||
|
||||
// SetProps 批量写入属性(与 miot.SetProps 对齐命名)
|
||||
func (c *Client) SetProps(params []PropKeyValue) ([]*PropResult, error)
|
||||
func (c *Client) SetProps(ctx context.Context, params []PropKeyValue) ([]*PropResult, error)
|
||||
|
||||
// PropKey 属性键对
|
||||
type PropKey struct {
|
||||
@@ -268,7 +281,7 @@ type PropResult struct {
|
||||
// ========== Action 封装 ==========
|
||||
|
||||
// Action 调用设备 Action(与 miot.Action 对齐命名)
|
||||
func (c *Client) Action(did string, siid, aiid int, params []interface{}) (*ActionResult, error)
|
||||
func (c *Client) Action(ctx context.Context, did string, siid, aiid int, params []interface{}) (*ActionResult, error)
|
||||
```
|
||||
|
||||
### 4.4 订阅通知接口
|
||||
@@ -396,12 +409,24 @@ xiaomi/devices/
|
||||
- 通过 `specs/resolver.go` 从 SPEC 中自动解析出对应的 siid/piid
|
||||
- 用户不关心 `siid=2, piid=3` 是什么,只关心 `ac.SetTargetTemp(26)` / `ac.SetMode(ModeCool)`
|
||||
|
||||
### 5.2 BaseDevice
|
||||
### 5.2 Device 基础接口 + BaseDevice 实现
|
||||
|
||||
```go
|
||||
package devices
|
||||
|
||||
// BaseDevice 所有设备控制类的公共基础
|
||||
import "context"
|
||||
|
||||
// Device 所有设备控制接口的公共基础
|
||||
// AirConditioner / Light / Switch / ... 都内嵌此接口
|
||||
type Device interface {
|
||||
DID() string
|
||||
Name() string
|
||||
Model() string
|
||||
Online() bool
|
||||
Info() *xiaomi.DeviceInfo
|
||||
}
|
||||
|
||||
// BaseDevice 所有设备控制类的公共实现(嵌入到具体类型中)
|
||||
type BaseDevice struct {
|
||||
client *xiaomi.Client
|
||||
info *xiaomi.DeviceInfo
|
||||
@@ -419,11 +444,11 @@ func (d *BaseDevice) Online() bool
|
||||
func (d *BaseDevice) Info() *xiaomi.DeviceInfo
|
||||
|
||||
// GetProp 快捷属性读取(委托给 xiaomi.Client.GetProp)
|
||||
func (d *BaseDevice) GetProp(siid, piid int) (*xiaomi.PropertyValue, error)
|
||||
func (d *BaseDevice) GetProp(ctx context.Context, siid, piid int) (*xiaomi.PropertyValue, error)
|
||||
// SetProp 快捷属性写入
|
||||
func (d *BaseDevice) SetProp(siid, piid int, value interface{}) error
|
||||
func (d *BaseDevice) SetProp(ctx context.Context, siid, piid int, value interface{}) error
|
||||
// Action 快捷 Action 调用
|
||||
func (d *BaseDevice) Action(siid, aiid int, params []interface{}) (*xiaomi.ActionResult, error)
|
||||
func (d *BaseDevice) Action(ctx context.Context, siid, aiid int, params []interface{}) (*xiaomi.ActionResult, error)
|
||||
|
||||
// SubProp 订阅属性变化(委托给 xiaomi.Client.SubProp)
|
||||
func (d *BaseDevice) SubProp(siid, piid int, handler xiaomi.PropHandler) (string, error)
|
||||
@@ -436,45 +461,49 @@ func (d *BaseDevice) SubDeviceState(handler xiaomi.DeviceStateHandler) error
|
||||
### 5.3 空调控制接口
|
||||
|
||||
```go
|
||||
import "context"
|
||||
|
||||
// AirConditioner 空调统一控制接口
|
||||
type AirConditioner interface {
|
||||
Device // 嵌入公共接口
|
||||
|
||||
// 基础能力
|
||||
TurnOn() error
|
||||
TurnOff() error
|
||||
IsOn() (bool, error)
|
||||
TurnOn(ctx context.Context) error
|
||||
TurnOff(ctx context.Context) error
|
||||
IsOn(ctx context.Context) (bool, error)
|
||||
|
||||
// 模式控制
|
||||
SetMode(mode ACMode) error
|
||||
GetMode() (ACMode, error)
|
||||
SetMode(ctx context.Context, mode ACMode) error
|
||||
GetMode(ctx context.Context) (ACMode, error)
|
||||
|
||||
// 温度控制
|
||||
SetTargetTemp(celsius float64) error
|
||||
GetTargetTemp() (float64, error)
|
||||
GetCurrentTemp() (float64, error) // 室内温度
|
||||
SetTargetTemp(ctx context.Context, celsius float64) error
|
||||
GetTargetTemp(ctx context.Context) (float64, error)
|
||||
GetCurrentTemp(ctx context.Context) (float64, error) // 室内温度
|
||||
|
||||
// 风速控制
|
||||
SetFanSpeed(speed FanSpeed) error
|
||||
GetFanSpeed() (FanSpeed, error)
|
||||
SetFanSpeed(ctx context.Context, speed FanSpeed) error
|
||||
GetFanSpeed(ctx context.Context) (FanSpeed, error)
|
||||
|
||||
// 摆风控制
|
||||
SetSwing(mode SwingMode) error
|
||||
GetSwing() (SwingMode, error)
|
||||
SetSwing(ctx context.Context, mode SwingMode) error
|
||||
GetSwing(ctx context.Context) (SwingMode, error)
|
||||
|
||||
// 湿度(部分空调支持)
|
||||
SetTargetHumidity(pct int) error
|
||||
GetTargetHumidity() (int, error)
|
||||
GetCurrentHumidity() (int, error)
|
||||
SetTargetHumidity(ctx context.Context, pct int) error
|
||||
GetTargetHumidity(ctx context.Context) (int, error)
|
||||
GetCurrentHumidity(ctx context.Context) (int, error)
|
||||
|
||||
// 电辅热
|
||||
SetAuxHeat(on bool) error
|
||||
GetAuxHeat() (bool, error)
|
||||
SetAuxHeat(ctx context.Context, on bool) error
|
||||
GetAuxHeat(ctx context.Context) (bool, error)
|
||||
|
||||
// 节能
|
||||
SetEco(on bool) error
|
||||
GetEco() (bool, error)
|
||||
SetEco(ctx context.Context, on bool) error
|
||||
GetEco(ctx context.Context) (bool, error)
|
||||
|
||||
// 订阅
|
||||
OnStateChanged(handler func(state ACState)) (string, error)
|
||||
// 订阅(不需要 ctx,订阅是本地注册操作)
|
||||
OnStateChanged(handler func(state ACState)) (subID string, err error)
|
||||
}
|
||||
|
||||
// ACMode 空调模式
|
||||
@@ -530,31 +559,35 @@ type ACState struct {
|
||||
### 5.4 灯光控制接口
|
||||
|
||||
```go
|
||||
import "context"
|
||||
|
||||
// Light 灯光统一控制接口
|
||||
type Light interface {
|
||||
TurnOn() error
|
||||
TurnOff() error
|
||||
Toggle() error
|
||||
Device
|
||||
|
||||
// 亮度
|
||||
SetBrightness(pct int) error // 1-100, -1 表示不支持
|
||||
GetBrightness() (int, error)
|
||||
TurnOn(ctx context.Context) error
|
||||
TurnOff(ctx context.Context) error
|
||||
Toggle(ctx context.Context) error
|
||||
|
||||
// 色温
|
||||
SetColorTemp(kelvin int) error // 2700-6500, -1 表示不支持
|
||||
GetColorTemp() (int, error)
|
||||
// 亮度(若返回 -1 表示不支持)
|
||||
SetBrightness(ctx context.Context, pct int) error // 1-100
|
||||
GetBrightness(ctx context.Context) (int, error)
|
||||
|
||||
// 颜色
|
||||
SetColor(r, g, b int) error // 0-255, -1 表示不支持
|
||||
GetColor() (r, g, b int, error)
|
||||
// 色温(若返回 -1 表示不支持)
|
||||
SetColorTemp(ctx context.Context, kelvin int) error // 2700-6500
|
||||
GetColorTemp(ctx context.Context) (int, error)
|
||||
|
||||
OnStateChanged(handler func(state LightState)) (string, error)
|
||||
// 颜色(若 r/g/b 均返回 -1 表示不支持)
|
||||
SetColor(ctx context.Context, r, g, b int) error // 0-255
|
||||
GetColor(ctx context.Context) (r, g, b int, error)
|
||||
|
||||
OnStateChanged(handler func(state LightState)) (subID string, err error)
|
||||
}
|
||||
|
||||
type LightState struct {
|
||||
Power bool `json:"power"`
|
||||
Brightness int `json:"brightness"`
|
||||
ColorTemp int `json:"color_temp"`
|
||||
Power bool `json:"power"`
|
||||
Brightness int `json:"brightness"`
|
||||
ColorTemp int `json:"color_temp"`
|
||||
ColorRGB [3]int `json:"color_rgb"`
|
||||
}
|
||||
```
|
||||
@@ -562,71 +595,179 @@ type LightState struct {
|
||||
### 5.5 开关控制接口
|
||||
|
||||
```go
|
||||
import "context"
|
||||
|
||||
// Switch 开关统一控制接口
|
||||
type Switch interface {
|
||||
TurnOn() error
|
||||
TurnOff() error
|
||||
Toggle() error
|
||||
IsOn() (bool, error)
|
||||
Device
|
||||
|
||||
OnStateChanged(handler func(on bool)) (string, error)
|
||||
TurnOn(ctx context.Context) error
|
||||
TurnOff(ctx context.Context) error
|
||||
Toggle(ctx context.Context) error
|
||||
IsOn(ctx context.Context) (bool, error)
|
||||
|
||||
OnStateChanged(handler func(on bool)) (subID string, err error)
|
||||
}
|
||||
```
|
||||
|
||||
### 5.6 其他设备接口(概要)
|
||||
|
||||
```go
|
||||
import "context"
|
||||
|
||||
// Fan 风扇接口
|
||||
type Fan interface {
|
||||
TurnOn() error
|
||||
TurnOff() error
|
||||
SetSpeed(speed FanLevel) error // 0=off, 1-4 档位
|
||||
SetOscillation(on bool) error
|
||||
SetNaturalWind(on bool) error
|
||||
Device
|
||||
TurnOn(ctx context.Context) error
|
||||
TurnOff(ctx context.Context) error
|
||||
SetSpeed(ctx context.Context, speed FanLevel) error // 0=off, 1-4 档位
|
||||
SetOscillation(ctx context.Context, on bool) error
|
||||
SetNaturalWind(ctx context.Context, on bool) error
|
||||
}
|
||||
|
||||
// Cover 窗帘/晾衣架
|
||||
type Cover interface {
|
||||
Open() error
|
||||
Close() error
|
||||
Stop() error
|
||||
SetPosition(pct int) error // 0=关, 100=全开
|
||||
GetPosition() (int, error)
|
||||
Device
|
||||
Open(ctx context.Context) error
|
||||
Close(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
SetPosition(ctx context.Context, pct int) error // 0=关, 100=全开
|
||||
GetPosition(ctx context.Context) (int, error)
|
||||
}
|
||||
|
||||
// Humidifier 加湿器/除湿机
|
||||
type Humidifier interface {
|
||||
TurnOn() error
|
||||
TurnOff() error
|
||||
SetTargetHumidity(pct int) error
|
||||
SetFanSpeed(speed FanLevel) error
|
||||
Device
|
||||
TurnOn(ctx context.Context) error
|
||||
TurnOff(ctx context.Context) error
|
||||
SetTargetHumidity(ctx context.Context, pct int) error
|
||||
SetFanSpeed(ctx context.Context, speed FanLevel) error
|
||||
}
|
||||
|
||||
// Vacuum 扫地机
|
||||
type Vacuum interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
ReturnToDock() error
|
||||
SetFanSpeed(speed FanLevel) error
|
||||
GetStatus() (VacuumStatus, error)
|
||||
Device
|
||||
Start(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
ReturnToDock(ctx context.Context) error
|
||||
SetFanSpeed(ctx context.Context, speed FanLevel) error
|
||||
GetStatus(ctx context.Context) (VacuumStatus, error)
|
||||
}
|
||||
|
||||
// WaterHeater 热水器
|
||||
type WaterHeater interface {
|
||||
TurnOn() error
|
||||
TurnOff() error
|
||||
SetTargetTemp(celsius float64) error
|
||||
GetCurrentTemp() (float64, error)
|
||||
Device
|
||||
TurnOn(ctx context.Context) error
|
||||
TurnOff(ctx context.Context) error
|
||||
SetTargetTemp(ctx context.Context, celsius float64) error
|
||||
GetCurrentTemp(ctx context.Context) (float64, error)
|
||||
}
|
||||
|
||||
// Thermostat 温控器
|
||||
type Thermostat interface {
|
||||
SetTargetTemp(celsius float64) error
|
||||
GetCurrentTemp() (float64, error)
|
||||
SetHeating(on bool) error
|
||||
Device
|
||||
SetTargetTemp(ctx context.Context, celsius float64) error
|
||||
GetCurrentTemp(ctx context.Context) (float64, error)
|
||||
SetHeating(ctx context.Context, on bool) error
|
||||
}
|
||||
```
|
||||
|
||||
### 5.7 设备分类与工厂
|
||||
|
||||
```go
|
||||
package devices
|
||||
|
||||
// Kind 设备大类(用于分类和过滤)
|
||||
type Kind string
|
||||
const (
|
||||
KindAirConditioner Kind = "air_conditioner"
|
||||
KindLight Kind = "light"
|
||||
KindSwitch Kind = "switch"
|
||||
KindFan Kind = "fan"
|
||||
KindCover Kind = "cover"
|
||||
KindHumidifier Kind = "humidifier"
|
||||
KindVacuum Kind = "vacuum"
|
||||
KindWaterHeater Kind = "water_heater"
|
||||
KindThermostat Kind = "thermostat"
|
||||
KindUnknown Kind = "unknown"
|
||||
)
|
||||
|
||||
// Classify 从 DeviceInfo 快速判断设备种类
|
||||
// 基于 URN/Model 前缀做字符串匹配,不依赖 SPEC 加载
|
||||
func Classify(info *xiaomi.DeviceInfo) Kind
|
||||
|
||||
// NewDevice 通用工厂:根据 DeviceInfo 自动创建对应的设备控制实例
|
||||
// 返回的是具体接口(AirConditioner / Light / Switch / ...),调用方用 type switch 使用
|
||||
func NewDevice(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Device, error)
|
||||
```
|
||||
|
||||
**工厂使用模式**:
|
||||
|
||||
```go
|
||||
dev, err := devices.NewDevice(client, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch d := dev.(type) {
|
||||
case devices.AirConditioner:
|
||||
d.TurnOn(ctx)
|
||||
d.SetTargetTemp(ctx, 26)
|
||||
case devices.Light:
|
||||
d.SetBrightness(ctx, 80)
|
||||
case devices.Switch:
|
||||
d.Toggle(ctx)
|
||||
default:
|
||||
fmt.Printf("设备 %s 暂无专用控制接口\n", dev.Name())
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### 值映射
|
||||
|
||||
SPEC 中的枚举属性(如空调模式)在 SPEC 中以整数值存储,但封装层用字符串枚举(如 `"cool"`)。SpecResolver 需要提供双向映射:
|
||||
|
||||
```go
|
||||
package specs
|
||||
|
||||
// ValueMapping Spec 原始值 → 枚举字符串 的映射
|
||||
type ValueMapping struct {
|
||||
SpecValue interface{} // SPEC 中的原始值(通常是 int 或 string)
|
||||
EnumValue string // 对应的语义枚举值(用于 ACMode / FanSpeed 等)
|
||||
}
|
||||
|
||||
// PropertyMapper 属性值映射器
|
||||
// 从 SPEC 的 value-list 中提取原始值到枚举值的对应关系
|
||||
type PropertyMapper struct {
|
||||
rawToEnum map[interface{}]string // 原始值 → 枚举字符串
|
||||
enumToRaw map[string]interface{} // 枚举字符串 → 原始值
|
||||
}
|
||||
|
||||
// NewPropertyMapper 从 SPEC property 的 value-list 构造映射器
|
||||
// 需要外部提供 "原始值 → 枚举名" 的对照表(如 {0:"auto", 1:"cool", 2:"dry", 3:"fan", 4:"heat"})
|
||||
func NewPropertyMapper(vlist []miot.MIoTSpecValueItem, valueMap map[interface{}]string) *PropertyMapper
|
||||
|
||||
// ToEnum 将原始值转为枚举字符串
|
||||
func (m *PropertyMapper) ToEnum(raw interface{}) (string, bool)
|
||||
|
||||
// ToRaw 将枚举字符串转为原始值
|
||||
func (m *PropertyMapper) ToRaw(enum string) (interface{}, bool)
|
||||
```
|
||||
|
||||
**示例**:空调模式映射
|
||||
|
||||
```go
|
||||
acModeMap := map[interface{}]string{
|
||||
0: "auto",
|
||||
1: "cool",
|
||||
2: "dry",
|
||||
3: "fan",
|
||||
4: "heat",
|
||||
}
|
||||
mapper := specs.NewPropertyMapper(modeProp.ValueList, acModeMap)
|
||||
rawValue, _ := mapper.ToRaw("cool") // → 1
|
||||
enumValue, _ := mapper.ToEnum(4) // → "heat"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、specs/resolver.go — SPEC 语义解析器
|
||||
@@ -776,6 +917,39 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
|
||||
---
|
||||
|
||||
### ADR-006:所有 IO 方法携带 context.Context
|
||||
|
||||
**状态**:Proposed
|
||||
|
||||
**背景**:封装层的 GetUserInfo / GetDevices / SetProp / Action 等涉及网络 IO,Go 惯例需要超时和取消控制。
|
||||
|
||||
**决定**:所有涉及 IO 的方法首参加 `ctx context.Context`。订阅方法(SubProp / SubEvent / SubDeviceState)因为是本地注册操作不涉及直接 IO,不携带 ctx。
|
||||
|
||||
**后果**:
|
||||
- ✅ 调用方可控制超时和取消
|
||||
- ✅ 符合 Go 标准库惯例
|
||||
- ❌ 方法签名变长(但这是 Go 生态的标准代价)
|
||||
|
||||
---
|
||||
|
||||
### ADR-007:Device 基础接口 + Kind 分类体系
|
||||
|
||||
**状态**:Proposed
|
||||
|
||||
**背景**:用户拿到 DeviceInfo 需要判断设备类型才能创建对应的控制接口。需要一个快速分类机制(不依赖 SPEC 加载)和一个统一的工厂入口。
|
||||
|
||||
**决定**:
|
||||
1. 定义 `Device` 基础接口,所有设备接口内嵌它
|
||||
2. 定义 `Kind` 枚举,`Classify(DeviceInfo)` 基于 URN/Model 字符串匹配快速分类
|
||||
3. 提供 `NewDevice(client, info)` 通用工厂,返回 `Device`,调用方 type switch
|
||||
|
||||
**后果**:
|
||||
- ✅ 用户无需了解 URN 命名规则
|
||||
- ✅ 一个工厂函数处理所有设备类型
|
||||
- ❌ Classify 基于字符串匹配,极端情况下可能误判(非常见型号),需要可扩展的匹配表
|
||||
|
||||
---
|
||||
|
||||
## 八、实施计划
|
||||
|
||||
### Phase 1 — 基础封装(优先级 P0)
|
||||
@@ -783,10 +957,11 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| 项目骨架搭建 | `xiaomi/go.mod`, module 初始化 | 小 |
|
||||
| types.go 结构体定义 | `xiaomi/types.go` | 中 |
|
||||
| Client 基础框架 | `xiaomi/client.go` | 中 |
|
||||
| types.go 结构体定义(UserInfo/HomeInfo/RoomInfo/DeviceInfo 等) | `xiaomi/types.go` | 中 |
|
||||
| DeviceKind 分类 + Classify 函数 | `xiaomi/types.go` (DeviceKind 方法) + `xiaomi/devices/kind.go` | 小 |
|
||||
| Client 基础框架 + context 集成 | `xiaomi/client.go` | 中 |
|
||||
| 用户/家庭/房间信息封装 | `xiaomi/user.go`, `homes.go` | 中 |
|
||||
| 设备列表封装 | `xiaomi/devices.go` | 中 |
|
||||
| 设备列表封装(含 FilterByKind) | `xiaomi/devices.go` | 中 |
|
||||
| 属性读写封装 | `xiaomi/properties.go` | 中 |
|
||||
| Action 封装 | `xiaomi/actions.go` | 小 |
|
||||
| 错误定义 | `xiaomi/errors.go` | 小 |
|
||||
@@ -795,30 +970,31 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| 订阅通知接口 | `xiaomi/subscribe.go` | 中 |
|
||||
| 订阅通知接口(SubProp/SubEvent/SubDeviceState) | `xiaomi/subscribe.go` | 中 |
|
||||
| 底层回调 → 强类型事件转换 | `xiaomi/subscribe.go` | 中 |
|
||||
| 设备状态订阅 | `xiaomi/subscribe.go` | 小 |
|
||||
| subID 映射管理 | `xiaomi/subscribe.go` | 小 |
|
||||
|
||||
### Phase 3 — 设备控制抽象(优先级 P1)
|
||||
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| SPEC 语义解析器 | `xiaomi/specs/resolver.go` | 大 |
|
||||
| BaseDevice 基础实现 | `xiaomi/devices/base.go` | 中 |
|
||||
| SPEC 语义解析器(PropertyResolver) | `xiaomi/specs/resolver.go` | 大 |
|
||||
| SPEC 值映射(PropertyMapper) | `xiaomi/specs/mapper.go` | 中 |
|
||||
| Device 接口 + 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` | 中 |
|
||||
| Humidifier/Vacuum/WaterHeater/Thermostat | 各文件 | 各小 |
|
||||
| 通用工厂 NewDevice | `xiaomi/devices/factory.go` | 中 |
|
||||
|
||||
### Phase 4 — 测试和文档(优先级 P2)
|
||||
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| 单元测试 | `*_test.go` | 大 |
|
||||
| 集成示例 | `xiaomi/examples/` | 中 |
|
||||
| 集成示例(更新为 ctx + Device 接口 + factory) | `xiaomi/examples/` | 中 |
|
||||
| API 文档 | `xiaomi/README.md` | 中 |
|
||||
|
||||
---
|
||||
@@ -829,12 +1005,17 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
"xiaomihome/xiaomi"
|
||||
"xiaomihome/xiaomi/devices"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 1. 创建底层 miot 客户端(照常)
|
||||
// ... miot.NewMIoTClient(...) ...
|
||||
|
||||
@@ -842,56 +1023,60 @@ func main() {
|
||||
client := xiaomi.NewClient(miotClient)
|
||||
|
||||
// 3. 获取用户信息(强类型!)
|
||||
user, _ := client.GetUserInfo()
|
||||
user, _ := client.GetUserInfo(ctx)
|
||||
fmt.Printf("用户: %s (UID: %s)\n", user.NickName, user.UID)
|
||||
|
||||
// 4. 获取家庭和房间
|
||||
homes, _ := client.GetHomeList()
|
||||
homes, _ := client.GetHomeList(ctx)
|
||||
for _, h := range homes {
|
||||
fmt.Printf("家庭: %s\n", h.Name)
|
||||
fmt.Printf("家庭: %s (%s)\n", h.Name, h.Address)
|
||||
fmt.Printf(" 位置: %.4f, %.4f\n", h.Latitude, h.Longitude)
|
||||
for _, r := range h.Rooms {
|
||||
fmt.Printf(" 房间: %s (%d 设备)\n", r.Name, len(r.DidList))
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取设备列表(可过滤)
|
||||
devices_, _ := client.GetDevices(
|
||||
devices_, _ := client.GetDevices(ctx,
|
||||
xiaomi.FilterByHome(homes[0].ID),
|
||||
xiaomi.FilterByOnline(),
|
||||
)
|
||||
for _, d := range devices_ {
|
||||
fmt.Printf("设备: %s (%s) 在线:%v\n", d.Name, d.Model, d.Online)
|
||||
fmt.Printf("设备: %s (%s) 在线:%v 种类:%s\n",
|
||||
d.Name, d.Model, d.Online, d.DeviceKind())
|
||||
}
|
||||
|
||||
// 6. 创建空调控制对象(自动解析 SPEC)
|
||||
acInfo := devices_[0] // 假设是个空调
|
||||
ac, err := devices.NewAirConditioner(client, acInfo)
|
||||
if err != nil {
|
||||
// 该设备不是空调或不支持
|
||||
return
|
||||
// 6. 通用工厂:一条命令创建对应控制接口
|
||||
for _, info := range devices_ {
|
||||
dev, err := devices.NewDevice(client, info)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch d := dev.(type) {
|
||||
case devices.AirConditioner:
|
||||
d.TurnOn(ctx)
|
||||
d.SetMode(ctx, devices.ModeCool)
|
||||
d.SetTargetTemp(ctx, 26)
|
||||
d.SetFanSpeed(ctx, devices.SpeedAuto)
|
||||
// 订阅状态变化
|
||||
d.OnStateChanged(func(state devices.ACState) {
|
||||
fmt.Printf("空调 %s: 模式=%s 温度=%.1f°C\n",
|
||||
dev.Name(), state.Mode, state.TargetTemp)
|
||||
})
|
||||
case devices.Light:
|
||||
d.TurnOn(ctx)
|
||||
d.SetBrightness(ctx, 80)
|
||||
d.SetColorTemp(ctx, 4000)
|
||||
case devices.Switch:
|
||||
d.Toggle(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 7. 直接订阅原始属性变化(不用设备抽象)
|
||||
client.SubProp("did_xxx", 0, 0, func(did string, prop *xiaomi.PropertyValue) {
|
||||
fmt.Printf("属性变化: %s siid=%d piid=%d value=%v\n",
|
||||
did, prop.SIID, prop.PIID, prop.Value)
|
||||
})
|
||||
|
||||
// 9. 灯光控制
|
||||
light, _ := devices.NewLight(client, lightInfo)
|
||||
light.TurnOn()
|
||||
light.SetBrightness(80)
|
||||
light.SetColorTemp(4000)
|
||||
|
||||
// 10. 开关控制
|
||||
sw, _ := devices.NewSwitch(client, switchInfo)
|
||||
sw.Toggle()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
# ARCH_PLAN.md 审查报告
|
||||
|
||||
> 审查时间:2026-06-28 | 目标版本:v1.0 → v1.1
|
||||
|
||||
---
|
||||
|
||||
## 审查结果总览
|
||||
|
||||
共发现 **11 个问题**,分级如下:
|
||||
|
||||
| 等级 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| P0 阻塞 | 4 | 必须修复才能进入实施 |
|
||||
| P1 重要 | 3 | 影响完整性和一致性 |
|
||||
| P2 优化 | 4 | 锦上添花 |
|
||||
|
||||
---
|
||||
|
||||
## P0 — 阻塞项
|
||||
|
||||
### #1 缺少 `context.Context` 支持
|
||||
|
||||
**问题**:所有涉及 IO 的方法(GetUserInfo、GetHomeList、GetDevices、SetProp、Action 等)都没有 `context.Context` 参数。Go 惯例要求 IO 操作可超时和取消。
|
||||
|
||||
**影响范围**:
|
||||
- `client.go`:GetUserInfo、GetHomeList、GetHome、GetRooms、GetRoomDevices、GetDevices、GetDevice、RefreshDevices
|
||||
- `properties.go`:GetProp、SetProp、GetProps、SetProps
|
||||
- `actions.go`:Action
|
||||
- `devices/base.go`:GetProp、SetProp、Action
|
||||
- `devices/air_conditioner.go`:TurnOn、TurnOff、IsOn、SetMode、GetMode、SetTargetTemp、GetTargetTemp、GetCurrentTemp 等全部 IO 方法
|
||||
- `devices/light.go`、`switch.go`、`fan.go`、`cover.go` 等:同上
|
||||
|
||||
**不修改的方法**(纯本地操作):
|
||||
- SubProp / UnsubProp / SubEvent / UnsubEvent / SubDeviceState / UnsubDeviceState
|
||||
- OnStateChanged
|
||||
- NewClient / NewDevice 等工厂方法
|
||||
|
||||
---
|
||||
|
||||
### #2 `PropertyValue.Type` 字段在回调场景下不可用
|
||||
|
||||
**问题**:`PropertyValue` 定义了 `Type string` 字段标注"SPEC format"。但 SubProp 回调中 miot 传入的 params 只有 `{did, siid, piid, value}`,没有 type 信息。该字段只在主动 GetProp 时才有可能从 SPEC 查到。
|
||||
|
||||
**当前代码**(types.go):
|
||||
```go
|
||||
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"
|
||||
}
|
||||
```
|
||||
|
||||
**修复方案**:改为注释说明只在 GetProp 场景可用。
|
||||
|
||||
---
|
||||
|
||||
### #3 缺少通用的设备类型判断/分类机制
|
||||
|
||||
**问题**:用户拿到 `[]*DeviceInfo` 后无法区分哪台是空调、哪台是灯。只能盲目试 `devices.NewAirConditioner(...)` 看返回 error。
|
||||
|
||||
**修复方案**:在 `xiaomi/devices/` 新增分类体系:
|
||||
|
||||
**新文件 `kind.go`**:
|
||||
```go
|
||||
package devices
|
||||
|
||||
// Kind 设备大类
|
||||
type Kind string
|
||||
const (
|
||||
KindAirConditioner Kind = "air_conditioner"
|
||||
KindLight Kind = "light"
|
||||
KindSwitch Kind = "switch"
|
||||
KindFan Kind = "fan"
|
||||
KindCover Kind = "cover"
|
||||
KindHumidifier Kind = "humidifier"
|
||||
KindVacuum Kind = "vacuum"
|
||||
KindWaterHeater Kind = "water_heater"
|
||||
KindThermostat Kind = "thermostat"
|
||||
KindUnknown Kind = "unknown"
|
||||
)
|
||||
|
||||
// Classify 从 DeviceInfo 快速判断设备种类(基于 URN/Model 前缀匹配,不依赖 SPEC 加载)
|
||||
func Classify(info *xiaomi.DeviceInfo) Kind
|
||||
```
|
||||
|
||||
**types.go 中加方法**:
|
||||
```go
|
||||
func (d *DeviceInfo) DeviceKind() devices.Kind
|
||||
```
|
||||
|
||||
**client.go 中加过滤器**:
|
||||
```go
|
||||
func FilterByKind(kind devices.Kind) DeviceFilter
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### #4 缺少 `Device` 基础接口
|
||||
|
||||
**问题**:BaseDevice 目前是 struct 而非 interface。设备控制抽象需要一个通用 `Device` 接口让所有设备类型内嵌,工厂函数才能返回统一类型。
|
||||
|
||||
**当前代码**(devices/base.go):
|
||||
```go
|
||||
type BaseDevice struct {
|
||||
client *xiaomi.Client
|
||||
info *xiaomi.DeviceInfo
|
||||
}
|
||||
```
|
||||
|
||||
**修复方案**:在 base.go 中拆分接口和实现:
|
||||
|
||||
```go
|
||||
// Device 所有设备控制接口的公共基础
|
||||
type Device interface {
|
||||
DID() string
|
||||
Name() string
|
||||
Model() string
|
||||
Online() bool
|
||||
Info() *xiaomi.DeviceInfo
|
||||
}
|
||||
|
||||
// BaseDevice 公共实现(嵌入到具体类型中)
|
||||
type BaseDevice struct {
|
||||
client *xiaomi.Client
|
||||
info *xiaomi.DeviceInfo
|
||||
}
|
||||
// 实现 Device 接口的所有方法...
|
||||
```
|
||||
|
||||
各设备接口改为内嵌 Device:
|
||||
```go
|
||||
type AirConditioner interface {
|
||||
Device // ← 嵌入
|
||||
TurnOn() error
|
||||
// ...
|
||||
}
|
||||
type Light interface {
|
||||
Device
|
||||
TurnOn() error
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**新文件 `factory.go`**(通用工厂):
|
||||
```go
|
||||
// NewDevice 根据 DeviceInfo 自动创建对应的设备控制接口
|
||||
// 返回 Device,调用方用 type switch 分流
|
||||
func NewDevice(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Device, error)
|
||||
```
|
||||
|
||||
使用模式:
|
||||
```go
|
||||
dev, err := devices.NewDevice(client, info)
|
||||
switch d := dev.(type) {
|
||||
case devices.AirConditioner:
|
||||
d.TurnOn(ctx)
|
||||
case devices.Light:
|
||||
d.SetBrightness(ctx, 80)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P1 — 重要项
|
||||
|
||||
### #5 HomeInfo 缺少地理位置字段
|
||||
|
||||
**问题**:Python 版 `get_homeinfos_async` 返回的 home 包含 `city_id`、`longitude`、`latitude`、`address`,对位置相关自动化有用。
|
||||
|
||||
**修复方案**:HomeInfo 结构体加字段:
|
||||
```go
|
||||
type HomeInfo struct {
|
||||
// ... 现有字段 ...
|
||||
CityID string `json:"city_id"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### #6 Client 的 homes 缓存语义不清
|
||||
|
||||
**问题**:Client struct 定义了 `homes map[string]*HomeInfo` 暗示缓存,但 GetHomeList 每次都调 HTTP API。缓存何时加载、何时失效均未说明。
|
||||
|
||||
**修复方案**:去掉 `homes` 缓存字段,Client 保持无状态:
|
||||
```go
|
||||
type Client struct {
|
||||
inner *miot.MIoTClient
|
||||
mu sync.RWMutex
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### #7 枚举值与 SPEC 原始值的映射关系未定义
|
||||
|
||||
**问题**:`ACMode` 值是 `"cool"`、`"heat"` 等字符串,但 SPEC 中 mode 是数字 0-4。SpecResolver 需要做双向值映射,文档未提。
|
||||
|
||||
**修复方案**:新增 `specs/mapper.go`:
|
||||
|
||||
```go
|
||||
package specs
|
||||
|
||||
// PropertyMapper SPEC 原始值 ↔ 枚举字符串 双向映射
|
||||
type PropertyMapper struct {
|
||||
rawToEnum map[interface{}]string
|
||||
enumToRaw map[string]interface{}
|
||||
}
|
||||
|
||||
// NewPropertyMapper 从 SPEC value-list + 外部映射表 构造
|
||||
func NewPropertyMapper(vlist []miot.MIoTSpecValueItem, valueMap map[interface{}]string) *PropertyMapper
|
||||
|
||||
func (m *PropertyMapper) ToEnum(raw interface{}) (string, bool)
|
||||
func (m *PropertyMapper) ToRaw(enum string) (interface{}, bool)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P2 — 优化项
|
||||
|
||||
### #8 DeviceInfo 无直接可读的设备类型
|
||||
|
||||
**解决**:已通过 P0 #3 的 `DeviceKind()` 方法和 `Classify()` 函数解决。
|
||||
|
||||
---
|
||||
|
||||
### #9 各设备接口 OnStateChanged 返回值不统一
|
||||
|
||||
**问题**:OnStateChanged 返回 `(string, error)`,但 SubProp/SubEvent 返回 `(string, error)` — 这里它们是统一的。但缺少对 subID 语义的说明。
|
||||
|
||||
**修复方案**:注释统一说明 subID 是订阅标识符,可用于取消。
|
||||
|
||||
---
|
||||
|
||||
### #10 例中 ACMode 枚举值和 SPEC 原始值需对应
|
||||
|
||||
**解决**:已通过 P1 #7 的 PropertyMapper 解决。
|
||||
|
||||
---
|
||||
|
||||
### #11 使用示例未体现上下文控制和工厂模式
|
||||
|
||||
**修复方案**:使用示例改为展示 `context.WithTimeout`、`devices.NewDevice` 通用工厂、`type switch` 分流模式。
|
||||
|
||||
---
|
||||
|
||||
## 修复后需新增的 ADR
|
||||
|
||||
### ADR-006:所有 IO 方法携带 context.Context
|
||||
- 决定:IO 方法首参加 ctx;订阅/工厂等本地方法不加
|
||||
- 代价:方法签名变长
|
||||
|
||||
### ADR-007:Device 基础接口 + Kind 分类体系
|
||||
- 决定:Device 接口为所有设备接口的公共基;Classify 基于 URN 字符串匹配快速分类;NewDevice 为通用工厂
|
||||
- 代价:Classify 依赖字符串匹配,非常见型号可能误判,匹配表需可扩展
|
||||
|
||||
---
|
||||
|
||||
## 修复实施清单
|
||||
|
||||
| 序号 | 等级 | 修改文件 | 内容 |
|
||||
|------|------|----------|------|
|
||||
| 1 | P0 | client.go, properties.go, actions.go, devices/*.go | 所有 IO 方法加 ctx context.Context 首参 |
|
||||
| 2 | P0 | types.go | PropertyValue.Type 注释改为"仅 GetProp 场景可用" |
|
||||
| 3 | P0 | devices/kind.go(新) | Kind 枚举 + Classify 函数 |
|
||||
| 3 | P0 | types.go | DeviceInfo.DeviceKind() 方法 |
|
||||
| 3 | P0 | client.go | 新增 FilterByKind |
|
||||
| 4 | P0 | devices/base.go | 拆分 Device 接口 + BaseDevice 实现 |
|
||||
| 4 | P0 | devices/factory.go(新) | NewDevice 通用工厂 |
|
||||
| 4 | P0 | devices/*.go | 各接口内嵌 Device |
|
||||
| 5 | P1 | types.go | HomeInfo 加 CityID/Longitude/Latitude/Address |
|
||||
| 6 | P1 | client.go | Client 去掉 homes 缓存字段 |
|
||||
| 7 | P1 | specs/mapper.go(新) | PropertyMapper 双向值映射 |
|
||||
| 9 | P2 | devices/*.go | OnStateChanged 统一命名返回值 subID |
|
||||
| 11 | P2 | ARCH_PLAN.md 使用示例 | 更新为 ctx + factory + type switch 模式 |
|
||||
|
||||
---
|
||||
|
||||
## 修复后的包结构变化
|
||||
|
||||
```
|
||||
xiaomi/
|
||||
├── devices/
|
||||
│ ├── base.go # 修改:拆分 Device 接口 + BaseDevice 实现
|
||||
│ ├── kind.go # 新增:Kind 枚举 + Classify 分类
|
||||
│ ├── factory.go # 新增:NewDevice 通用工厂
|
||||
│ └── ... # 修改:各接口内嵌 Device,IO 方法加 ctx
|
||||
├── specs/
|
||||
│ ├── resolver.go # 不变
|
||||
│ └── mapper.go # 新增:PropertyMapper 值映射
|
||||
├── client.go # 修改:加 ctx、去 homes 缓存、加 FilterByKind
|
||||
├── types.go # 修改:HomeInfo 加字段、PropertyValue.Type 注释、DeviceKind 方法
|
||||
├── properties.go # 修改:加 ctx
|
||||
├── actions.go # 修改:加 ctx
|
||||
└── ... # 其他 IO 方法加 ctx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*此报告为独立审查文档,详细修复已在 ARCH_PLAN.md 中执行。*
|
||||
Reference in New Issue
Block a user