Files
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

9.2 KiB
Raw Permalink Blame History

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
  • OnPropsChanged
  • NewClient / NewDevice 等工厂方法

#2 PropertyValue.Type 字段在回调场景下不可用

问题:PropertyValue 定义了 Type string 字段标注"SPEC format"。但 SubProp 回调中 miot 传入的 params 只有 {did, siid, piid, value},没有 type 信息。该字段只在主动 GetProp 时才有可能从 SPEC 查到。

当前代码(types.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:

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 中加方法:

func (d *DeviceInfo) DeviceKind() devices.Kind

client.go 中加过滤器:

func FilterByKind(kind devices.Kind) DeviceFilter

#4 缺少 Device 基础接口

问题:BaseDevice 目前是 struct 而非 interface。设备控制抽象需要一个通用 Device 接口让所有设备类型内嵌,工厂函数才能返回统一类型。

当前代码(devices/base.go):

type BaseDevice struct {
    client *xiaomi.Client
    info   *xiaomi.DeviceInfo
}

修复方案:在 base.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:

type AirConditioner interface {
    Device  // ← 嵌入
    TurnOn() error
    // ...
}
type Light interface {
    Device
    TurnOn() error
    // ...
}

新文件 factory.go(通用工厂):

// NewDevice 根据 DeviceInfo 自动创建对应的设备控制接口
// 返回 Device,调用方用 type switch 分流
func NewDevice(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Device, error)

使用模式:

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 结构体加字段:

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 保持无状态:

type Client struct {
    inner *miot.MIoTClient
    mu    sync.RWMutex
}

#7 枚举值与 SPEC 原始值的映射关系未定义

问题:ACMode 值是 "cool"、"heat" 等字符串,但 SPEC 中 mode 是数字 0-4。SpecResolver 需要做双向值映射,文档未提。

修复方案:新增 specs/mapper.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 各设备接口 OnPropsChanged 返回值不统一

问题:OnPropsChanged 返回 (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 OnPropsChanged 统一命名返回值 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 中执行。