Files
xiaomihome/xiaomi/client.go
T
4566704 a3d94c4b9f 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 示例(风扇/窗帘/传感器控制)
2026-06-29 08:51:21 +08:00

120 lines
3.1 KiB
Go
Raw 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.
package xiaomi
import (
"context"
"fmt"
"sync"
"xiaomihome/miot"
)
// Client 高级封装客户端(包装 miot.MIoTClient + miot.MIoTHttpClient)
type Client struct {
cfg Config
inner *miot.MIoTClient
http *miot.MIoTHttpClient
mipsCloud *miot.MipsCloudClient
specParser *miot.MIoTSpecParser
deviceMu sync.RWMutex
devices map[string]*miot.MIoTDevice
propSubs map[string]string
eventSubs map[string]string
mu sync.RWMutex
}
// NewClient 创建并初始化客户端(零 miot import,一个 Config 搞定)。
func NewClient(ctx context.Context, cfg Config) (*Client, error) {
entryData := map[string]interface{}{
"access_token": cfg.AccessToken,
}
miotClient := miot.NewMIoTClient("xiaomi_sdk", entryData, cfg.UID, "cn", miot.CtrlModeAuto)
miotClient.SetStorage(miot.NewMIoTStorage("./data"))
miotClient.SetSpecParser(miot.NewMIoTSpecParser("en", miot.NewMIoTStorage("./data"), "./data/specs"))
httpClient, err := miot.NewMIoTHttpClient("cn", miot.OAUTH2_CLIENT_ID, cfg.AccessToken)
if err != nil {
return nil, fmt.Errorf("xiaomi: http: %w", err)
}
miotClient.SetHTTPClient(httpClient)
// MIPS client ID 使用 config 中的 UUID,空则自动生成
mipsUUID := cfg.UUID
if mipsUUID == "" {
mipsUUID = generateUUID()
}
mipsCloud := miot.NewMipsCloudClient(
"ssl://cn-ha.mqtt.io.mi.com:8883",
"ha."+mipsUUID,
miot.OAUTH2_CLIENT_ID,
cfg.AccessToken,
)
miotClient.SetMipsCloud(mipsCloud)
if err := miotClient.Init(); err != nil {
return nil, fmt.Errorf("xiaomi: init: %w", err)
}
// 初始刷新设备列表(对齐 full_client 示例)
_ = miotClient.RefreshDevices()
// 连接 MIPS(对齐 full_client 示例 line 83)
if err := mipsCloud.Connect(); err != nil {
return nil, fmt.Errorf("xiaomi: mips connect: %w", err)
}
return &Client{
cfg: cfg,
inner: miotClient,
http: httpClient,
mipsCloud: mipsCloud,
specParser: miot.NewMIoTSpecParser("en", miot.NewMIoTStorage("./data"), "./data/specs"),
devices: make(map[string]*miot.MIoTDevice),
propSubs: make(map[string]string),
eventSubs: make(map[string]string),
}, nil
}
// Close 关闭客户端。
func (c *Client) Close() error {
if c.mipsCloud != nil {
c.mipsCloud.Disconnect()
}
if c.inner != nil {
return c.inner.Deinit()
}
return nil
}
func (c *Client) getOrCreateDevice(did string, info map[string]interface{}) *miot.MIoTDevice {
c.deviceMu.Lock()
defer c.deviceMu.Unlock()
if dev, ok := c.devices[did]; ok {
return dev
}
// 从 device info 获取 URN 和 model 用于 SPEC 加载
urn, _ := info["urn"].(string)
model, _ := info["model"].(string)
var spec *miot.MIoTSpecInstance
if c.specParser != nil && urn != "" {
s, err := c.specParser.Parse(urn, model)
if err == nil {
spec = s
} else {
fmt.Printf("[xiaomi] spec parse failed for %s (%s): %v\n", did, urn, err)
}
}
dev := miot.NewMIoTDevice(c.inner, info, spec)
c.devices[did] = dev
return dev
}
func (c *Client) getDevice(did string) *miot.MIoTDevice {
c.deviceMu.RLock()
defer c.deviceMu.RUnlock()
return c.devices[did]
}