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:
@@ -0,0 +1,171 @@
|
||||
// Package specs provides SPEC property resolution — mapping semantic property
|
||||
// names (like "on", "mode", "target-temperature") to siid/piid pairs.
|
||||
//
|
||||
// This enables device control abstractions to work across different Xiaomi
|
||||
// device models that may assign different siid/piid numbering to the same
|
||||
// functional property.
|
||||
package specs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"xiaomihome/miot"
|
||||
)
|
||||
|
||||
// PropertyResolver resolves semantic property names to siid/piid pairs.
|
||||
// It indexes a device's SPEC instance by property type (Type field),
|
||||
// description (translated), and data format for flexible lookup.
|
||||
type PropertyResolver struct {
|
||||
spec *miot.MIoTSpecInstance
|
||||
byType map[string]*miot.MIoTSpecProperty // type → first matching property
|
||||
byDesc map[string]*miot.MIoTSpecProperty // description → property
|
||||
byFormat map[string][]*miot.MIoTSpecProperty // format → properties
|
||||
actionMap map[int]map[string]int // siid → (actionType → aiid)
|
||||
}
|
||||
|
||||
// NewPropertyResolver creates a PropertyResolver from a SPEC instance.
|
||||
// Returns nil if spec is nil.
|
||||
func NewPropertyResolver(spec *miot.MIoTSpecInstance) *PropertyResolver {
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := &PropertyResolver{
|
||||
spec: spec,
|
||||
byType: make(map[string]*miot.MIoTSpecProperty),
|
||||
byDesc: make(map[string]*miot.MIoTSpecProperty),
|
||||
byFormat: make(map[string][]*miot.MIoTSpecProperty),
|
||||
actionMap: make(map[int]map[string]int),
|
||||
}
|
||||
|
||||
// Index all properties across all services
|
||||
for _, svc := range spec.Services {
|
||||
for i := range svc.Properties {
|
||||
prop := svc.Properties[i]
|
||||
|
||||
// Index by type (first match wins for primary lookup)
|
||||
name := propShortName(prop.Type)
|
||||
if name != "" {
|
||||
if _, exists := r.byType[name]; !exists {
|
||||
r.byType[name] = prop
|
||||
}
|
||||
}
|
||||
|
||||
// Index by translated description
|
||||
if prop.DescriptionTrans != "" {
|
||||
if _, exists := r.byDesc[prop.DescriptionTrans]; !exists {
|
||||
r.byDesc[prop.DescriptionTrans] = prop
|
||||
}
|
||||
}
|
||||
// Fallback: index by raw description
|
||||
if prop.Description != "" {
|
||||
if _, exists := r.byDesc[prop.Description]; !exists {
|
||||
r.byDesc[prop.Description] = prop
|
||||
}
|
||||
}
|
||||
|
||||
// Index by format
|
||||
if prop.Format != "" {
|
||||
r.byFormat[prop.Format] = append(r.byFormat[prop.Format], prop)
|
||||
}
|
||||
}
|
||||
|
||||
// Index actions
|
||||
for _, act := range svc.Actions {
|
||||
if r.actionMap[act.SIID] == nil {
|
||||
r.actionMap[act.SIID] = make(map[string]int)
|
||||
}
|
||||
if act.Type != "" {
|
||||
r.actionMap[act.SIID][act.Type] = act.AIID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// FindByType looks up a property by its SPEC type string (e.g., "on", "mode",
|
||||
// "target-temperature", "fan-level", "brightness").
|
||||
// Returns (siid, piid, found).
|
||||
func (r *PropertyResolver) FindByType(propType string) (siid, piid int, found bool) {
|
||||
if prop, ok := r.byType[propType]; ok {
|
||||
return prop.SIID, prop.PIID, true
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
// FindByFormat looks up the first property with the given format string within
|
||||
// a specific service (siid). Returns (piid, found).
|
||||
// format examples: "bool", "uint8", "uint16", "float", "string"
|
||||
func (r *PropertyResolver) FindByFormat(format string, siid int) (piid int, found bool) {
|
||||
props, ok := r.byFormat[format]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
for _, p := range props {
|
||||
if p.SIID == siid {
|
||||
return p.PIID, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// FindByDescription looks up a property by its description text (translated or raw).
|
||||
// Returns (siid, piid, found).
|
||||
func (r *PropertyResolver) FindByDescription(desc string) (siid, piid int, found bool) {
|
||||
if prop, ok := r.byDesc[desc]; ok {
|
||||
return prop.SIID, prop.PIID, true
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
// FindAction looks up an action by its type within a specific service.
|
||||
// Returns (aiid, found).
|
||||
func (r *PropertyResolver) FindAction(siid int, actionType string) (aiid int, found bool) {
|
||||
if actions, ok := r.actionMap[siid]; ok {
|
||||
if aiid, ok := actions[actionType]; ok {
|
||||
return aiid, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// GetAllProperties returns all properties for a given siid.
|
||||
func (r *PropertyResolver) GetAllProperties(siid int) []*miot.MIoTSpecProperty {
|
||||
var result []*miot.MIoTSpecProperty
|
||||
for _, svc := range r.spec.Services {
|
||||
if svc.IID == siid {
|
||||
return svc.Properties
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetProperty returns a specific property by siid/piid.
|
||||
func (r *PropertyResolver) GetProperty(siid, piid int) *miot.MIoTSpecProperty {
|
||||
for _, svc := range r.spec.Services {
|
||||
if svc.IID != siid {
|
||||
continue
|
||||
}
|
||||
for _, p := range svc.Properties {
|
||||
if p.PIID == piid {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Spec returns the underlying SPEC instance.
|
||||
func (r *PropertyResolver) Spec() *miot.MIoTSpecInstance {
|
||||
return r.spec
|
||||
}
|
||||
|
||||
// propShortName extracts the short name from a SPEC property URN.
|
||||
func propShortName(urn string) string {
|
||||
parts := strings.Split(urn, ":")
|
||||
if len(parts) >= 4 && parts[2] == "property" {
|
||||
return parts[3]
|
||||
}
|
||||
return urn
|
||||
}
|
||||
Reference in New Issue
Block a user