为什么:并入宿主项目后须与宿主日志体系一致——宿主统一用 pkg/logger 初始化 logrus 全局实例并直接 logrus.Xxx 包级调用;包一层 Logger 接口会让 logrus 的 caller 定位(报告调用文件:行号)失准。 改动: - 删除 logger 包(Logger 接口 / Default / SetDefault,默认本就是 logrus.StandardLogger) - 11 个结构体移除 lgr logger.Logger 字段、SetLogger 方法及构造中的 lgr 初始化 - 192 处 lgr.Xxxf 调用改为 logrus.Xxxf,日志消息文本保持不变 - xiaomi.Client 移除 Logger() 访问器,air_conditioner 回读日志去掉判空调用直连 logrus - miot_i18n.go 两处标准库 log.Printf 改为 logrus.Errorf,统一日志出口 - go.mod:logrus 从 indirect 提升为直接依赖 - goimports/goformat 全树规范化:此前部分文件未 gofmt(单行 if、对齐),本次顺带 格式化,纯空白/换行变化,无逻辑改动 验证:go build / go vet / go test ./... 全部通过(bridge、miot、xiaomi、devices、specs)
172 lines
4.8 KiB
Go
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"
|
|
|
|
"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
|
|
}
|