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

306 lines
9.2 KiB
Markdown
Raw Permalink 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.
# 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):
```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 各设备接口 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 中执行。*