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:
2026-06-29 08:51:21 +08:00
parent 35efed7d15
commit a3d94c4b9f
93 changed files with 7582 additions and 157 deletions
+132
View File
@@ -0,0 +1,132 @@
package xiaomi
import "context"
// GetHomeList 获取所有家庭列表
// 合并自有家庭 (home_list) 和共享家庭 (share_home_list)
func (c *Client) GetHomeList(ctx context.Context) ([]*HomeInfo, error) {
data, err := c.http.GetHomeInfos()
if err != nil {
return nil, wrapErr("GetHomeList", "", err)
}
var homes []*HomeInfo
// 解析自有家庭
if homeMap, ok := data["home_list"].(map[string]interface{}); ok {
for _, v := range homeMap {
if hm, ok := v.(map[string]interface{}); ok {
homes = append(homes, parseHomeInfo(hm, false))
}
}
}
// 解析共享家庭
if shareMap, ok := data["share_home_list"].(map[string]interface{}); ok {
for _, v := range shareMap {
if hm, ok := v.(map[string]interface{}); ok {
homes = append(homes, parseHomeInfo(hm, true))
}
}
}
return homes, nil
}
// GetHome 获取指定家庭详情(含房间)
func (c *Client) GetHome(ctx context.Context, homeID string) (*HomeInfo, error) {
homes, err := c.GetHomeList(ctx)
if err != nil {
return nil, err
}
for _, h := range homes {
if h.ID == homeID {
return h, nil
}
}
return nil, ErrHomeNotFound
}
// GetRooms 获取指定家庭的所有房间
func (c *Client) GetRooms(ctx context.Context, homeID string) ([]*RoomInfo, error) {
home, err := c.GetHome(ctx, homeID)
if err != nil {
return nil, err
}
rooms := make([]*RoomInfo, len(home.Rooms))
for i := range home.Rooms {
rooms[i] = &home.Rooms[i]
}
return rooms, nil
}
// GetRoomDevices 获取指定房间的设备 DID 列表。
// 优先使用 homeroom API 返回的 dids,为空时从设备列表按 room_id 过滤。
func (c *Client) GetRoomDevices(ctx context.Context, homeID, roomID string) ([]string, error) {
rooms, err := c.GetRooms(ctx, homeID)
if err != nil {
return nil, err
}
for _, r := range rooms {
if r.ID == roomID {
if len(r.DidList) > 0 {
return r.DidList, nil
}
break
}
}
// 退化:从设备列表按 room_id 过滤
devices, err := c.GetDevices(ctx, FilterByRoom(roomID))
if err != nil {
return nil, err
}
dids := make([]string, 0, len(devices))
for _, d := range devices {
dids = append(dids, d.DID)
}
return dids, nil
}
// parseHomeInfo 从 miot 返回的 map 解析 HomeInfo(含房间列表)
func parseHomeInfo(m map[string]interface{}, shared bool) *HomeInfo {
h := &HomeInfo{
ID: strVal(m, "home_id"),
Name: strVal(m, "home_name"),
UID: strVal(m, "uid"),
GroupID: strVal(m, "group_id"),
CityID: strVal(m, "city_id"),
Address: strVal(m, "address"),
ShareState: shared,
}
// 地理位置
if v, ok := m["longitude"]; ok {
h.Longitude = toFloat64(v)
}
if v, ok := m["latitude"]; ok {
h.Latitude = toFloat64(v)
}
// 解析设备列表
h.DidList = strSliceVal(m, "dids")
// 解析房间(room_info 是 map[string]interface{},key=room_id)
if roomMap, ok := m["room_info"].(map[string]interface{}); ok {
for _, v := range roomMap {
if rm, ok := v.(map[string]interface{}); ok {
h.Rooms = append(h.Rooms, RoomInfo{
ID: strVal(rm, "room_id"),
Name: strVal(rm, "room_name"),
HomeID: h.ID,
DidList: strSliceVal(rm, "dids"),
})
}
}
}
return h
}