Files
4566704 cdc459b5ff fix(xiaomihome): 模块路径改为 git.zeroonesoft.cn/golib/xiaomihome
- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步
- 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
2026-10-05 08:53:23 +08:00

172 lines
4.8 KiB
Go

// 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"
"git.zeroonesoft.cn/golib/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
}