**命名统一(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 等迁移文档同步更新
285 lines
8.4 KiB
Go
285 lines
8.4 KiB
Go
// Package bridge 提供多用户小米设备管理门面。
|
|
//
|
|
// 外部只需 import 这一个包,无需直接依赖 xiaomi/devices/miot。
|
|
//
|
|
// 用法:
|
|
//
|
|
// pool := bridge.NewPool()
|
|
// pool.Add(ctx, id, uuid, auth, dataDir)
|
|
// b, _ := pool.Get(id)
|
|
// ac, _ := b.AC("did")
|
|
// ac.TurnOn(ctx)
|
|
// state := ac.GetProps()
|
|
package bridge
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"xiaomihome/logger"
|
|
"xiaomihome/xiaomi"
|
|
"xiaomihome/xiaomi/devices"
|
|
)
|
|
|
|
// ── 类型别名:业务层不需要 import devices ──
|
|
|
|
type (
|
|
ACProps = devices.ACProps
|
|
LightProps = devices.LightProps
|
|
FanProps = devices.FanProps
|
|
HumidifierProps = devices.HumidifierProps
|
|
HeaterProps = devices.HeaterProps
|
|
VacuumProps = devices.VacuumProps
|
|
SwitchProps = devices.SwitchProps
|
|
SpeakerProps = devices.SpeakerProps
|
|
PlugProps = devices.PlugProps
|
|
)
|
|
|
|
// OccupancyProps 人体传感器状态(有人/无人)。
|
|
type OccupancyProps struct {
|
|
Occupied bool `json:"occupied"`
|
|
}
|
|
|
|
// ── Pool ──
|
|
|
|
// Pool 全局路由:id → Bridge。
|
|
type Pool struct {
|
|
mu sync.RWMutex
|
|
items map[int64]*Bridge
|
|
}
|
|
|
|
// NewPool 创建 Pool。
|
|
func NewPool() *Pool {
|
|
return &Pool{items: make(map[int64]*Bridge)}
|
|
}
|
|
|
|
// Get 按 id 查找。
|
|
func (p *Pool) Get(id int64) (*Bridge, bool) {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
b, ok := p.items[id]
|
|
return b, ok
|
|
}
|
|
|
|
// Add 新建并注册一个 Bridge。
|
|
func (p *Pool) Add(ctx context.Context, id int64, uuid string, auth xiaomi.AuthInfo, dataDir string, homeIDs []int64) (*Bridge, error) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if _, exists := p.items[id]; exists {
|
|
return nil, fmt.Errorf("bridge: %d already exists", id)
|
|
}
|
|
b, err := newBridge(ctx, id, uuid, auth, dataDir, homeIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.items[id] = b
|
|
return b, nil
|
|
}
|
|
|
|
// Remove 移除并关闭。
|
|
func (p *Pool) Remove(id int64) error {
|
|
p.mu.Lock()
|
|
b, ok := p.items[id]
|
|
if ok {
|
|
delete(p.items, id)
|
|
}
|
|
p.mu.Unlock()
|
|
if !ok {
|
|
return fmt.Errorf("bridge: %d not found", id)
|
|
}
|
|
return b.Close()
|
|
}
|
|
|
|
// Close 关闭所有。
|
|
func (p *Pool) Close() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
for id, b := range p.items {
|
|
b.Close()
|
|
delete(p.items, id)
|
|
}
|
|
}
|
|
|
|
// ── Bridge ──
|
|
|
|
// Bridge 封装单个用户的 Client + 类型化设备控制器 + 状态缓存。
|
|
type Bridge struct {
|
|
id int64
|
|
client *xiaomi.Client
|
|
lgr logger.Logger
|
|
mu sync.RWMutex
|
|
dv map[string]interface{} // DID → devices.Xxx interface
|
|
|
|
onPropsChanged func(id int64, did, deviceType, changed string, props interface{})
|
|
onOnlineChanged func(id int64, did string, online bool)
|
|
}
|
|
|
|
func newBridge(ctx context.Context, id int64, uuid string, auth xiaomi.AuthInfo, dataDir string, homeIDs []int64) (*Bridge, error) {
|
|
idStr := fmt.Sprintf("%d", id)
|
|
client, err := xiaomi.NewClient(ctx, xiaomi.Config{
|
|
AuthInfo: auth,
|
|
DataDir: dataDir,
|
|
UUID: uuid,
|
|
UID: idStr,
|
|
HomeIDs: homeIDs,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bridge: %w", err)
|
|
}
|
|
|
|
b := &Bridge{
|
|
id: id,
|
|
client: client,
|
|
lgr: logger.Default(),
|
|
dv: make(map[string]interface{}),
|
|
}
|
|
|
|
devList, err := client.GetDevices(ctx)
|
|
if err != nil {
|
|
client.Close()
|
|
return nil, fmt.Errorf("bridge: GetDevices: %w", err)
|
|
}
|
|
|
|
for _, info := range devList {
|
|
d, createErr := devices.Create(client, info)
|
|
if createErr != nil {
|
|
continue
|
|
}
|
|
b.dv[info.DID] = d
|
|
}
|
|
|
|
b.lgr.Infof("[bridge] %d: %d devices", id, len(b.dv))
|
|
return b, nil
|
|
}
|
|
|
|
// SetLogger 设置日志记录器。
|
|
func (b *Bridge) SetLogger(l logger.Logger) { b.lgr = l }
|
|
|
|
func (b *Bridge) Close() error { return b.client.Close() }
|
|
|
|
// Devices 返回设备列表(DID → 类型名)。
|
|
func (b *Bridge) Devices() map[string]string {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
out := make(map[string]string, len(b.dv))
|
|
for did, d := range b.dv {
|
|
out[did] = deviceTypeName(d)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func deviceTypeName(d interface{}) string {
|
|
switch d.(type) {
|
|
case devices.AirConditioner: return devices.TypeAirConditioner
|
|
case devices.Light: return devices.TypeLight
|
|
case devices.Switch: return devices.TypeSwitch
|
|
case devices.Fan: return devices.TypeFan
|
|
case devices.Cover: return devices.TypeCover
|
|
case devices.Humidifier: return devices.TypeHumidifier
|
|
case devices.Vacuum: return devices.TypeVacuum
|
|
case devices.WaterHeater: return devices.TypeWaterHeater
|
|
case devices.Thermostat: return devices.TypeThermostat
|
|
case devices.TempHumiditySensor: return devices.TypeTempHumidSensor
|
|
case devices.OccupancySensor: return devices.TypeOccupancySensor
|
|
case devices.Speaker: return devices.TypeSpeaker
|
|
case devices.Gateway: return devices.TypeGateway
|
|
case devices.Plug: return devices.TypePlug
|
|
default: return "unknown"
|
|
}
|
|
}
|
|
|
|
// ── 类型化设备访问器 ──
|
|
|
|
func (b *Bridge) AC(did string) (devices.AirConditioner, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
ac, ok := d.(devices.AirConditioner)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not an AirConditioner", did) }
|
|
return ac, nil
|
|
}
|
|
func (b *Bridge) Light(did string) (devices.Light, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
lt, ok := d.(devices.Light)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Light", did) }
|
|
return lt, nil
|
|
}
|
|
func (b *Bridge) Switch(did string) (devices.Switch, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
sw, ok := d.(devices.Switch)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Switch", did) }
|
|
return sw, nil
|
|
}
|
|
func (b *Bridge) Fan(did string) (devices.Fan, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
f, ok := d.(devices.Fan)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Fan", did) }
|
|
return f, nil
|
|
}
|
|
func (b *Bridge) Cover(did string) (devices.Cover, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
cv, ok := d.(devices.Cover)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Cover", did) }
|
|
return cv, nil
|
|
}
|
|
func (b *Bridge) Humidifier(did string) (devices.Humidifier, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
h, ok := d.(devices.Humidifier)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Humidifier", did) }
|
|
return h, nil
|
|
}
|
|
func (b *Bridge) Vacuum(did string) (devices.Vacuum, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
v, ok := d.(devices.Vacuum)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Vacuum", did) }
|
|
return v, nil
|
|
}
|
|
func (b *Bridge) WaterHeater(did string) (devices.WaterHeater, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
wh, ok := d.(devices.WaterHeater)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a WaterHeater", did) }
|
|
return wh, nil
|
|
}
|
|
func (b *Bridge) Thermostat(did string) (devices.Thermostat, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
t, ok := d.(devices.Thermostat)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Thermostat", did) }
|
|
return t, nil
|
|
}
|
|
func (b *Bridge) TempHumidity(did string) (devices.TempHumiditySensor, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
s, ok := d.(devices.TempHumiditySensor)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a TempHumiditySensor", did) }
|
|
return s, nil
|
|
}
|
|
func (b *Bridge) Occupancy(did string) (devices.OccupancySensor, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
s, ok := d.(devices.OccupancySensor)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not an OccupancySensor", did) }
|
|
return s, nil
|
|
}
|
|
func (b *Bridge) Plug(did string) (devices.Plug, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
pg, ok := d.(devices.Plug)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Plug", did) }
|
|
return pg, nil
|
|
}
|
|
func (b *Bridge) Speaker(did string) (devices.Speaker, error) {
|
|
d, ok := b.dv[did]
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
|
|
sp, ok := d.(devices.Speaker)
|
|
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Speaker", did) }
|
|
return sp, nil
|
|
}
|