diff --git a/bridge/account.go b/bridge/account.go index 1245c6f..4da2caf 100644 --- a/bridge/account.go +++ b/bridge/account.go @@ -6,6 +6,8 @@ import ( "xiaomihome/xiaomi" "xiaomihome/xiaomi/devices" + + "github.com/sirupsen/logrus" ) // ── 账号 / Token / 家庭 ── @@ -66,7 +68,7 @@ func (b *Bridge) RefreshDevices(ctx context.Context, homeIDs ...int64) error { alive[info.DID] = struct{}{} } if removed := pruneControllers(b.dv, alive); len(removed) > 0 { - b.lgr.Infof("[bridge] %d: %d device(s) removed from Mi Home, controllers dropped: %v", + logrus.Infof("[bridge] %d: %d device(s) removed from Mi Home, controllers dropped: %v", b.id, len(removed), removed) } diff --git a/bridge/bridge.go b/bridge/bridge.go index 438a30b..dcf70de 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -17,23 +17,24 @@ import ( "fmt" "sync" - "xiaomihome/logger" "xiaomihome/xiaomi" "xiaomihome/xiaomi/devices" + + "github.com/sirupsen/logrus" ) // ── 类型别名:业务层不需要 import devices ── type ( - ACProps = devices.ACProps - LightProps = devices.LightProps - FanProps = devices.FanProps - HumidifierProps = devices.HumidifierProps - HeaterProps = devices.HeaterProps + ACProps = devices.ACProps + LightProps = devices.LightProps + FanProps = devices.FanProps + HumidifierProps = devices.HumidifierProps + HeaterProps = devices.HeaterProps VacuumProps = devices.VacuumProps - SwitchProps = devices.SwitchProps - SpeakerProps = devices.SpeakerProps - PlugProps = devices.PlugProps + SwitchProps = devices.SwitchProps + SpeakerProps = devices.SpeakerProps + PlugProps = devices.PlugProps ) // OccupancyProps 人体传感器状态(有人/无人)。 @@ -112,7 +113,6 @@ func (p *Pool) Close() { type Bridge struct { id int64 client *xiaomi.Client - lgr logger.Logger mu sync.RWMutex dv map[string]interface{} // DID → devices.Xxx interface @@ -137,7 +137,6 @@ func newBridge(ctx context.Context, id int64, uuid string, auth xiaomi.AuthInfo, b := &Bridge{ id: id, client: client, - lgr: logger.Default(), dv: make(map[string]interface{}), } @@ -151,22 +150,19 @@ func newBridge(ctx context.Context, id int64, uuid string, auth xiaomi.AuthInfo, d, createErr := devices.Create(client, info) if createErr != nil || d == nil { if createErr != nil { - b.lgr.Warnf("[bridge] %d: create device %s (%s) failed: %v", id, info.DID, info.Model, createErr) + logrus.Warnf("[bridge] %d: create device %s (%s) failed: %v", id, info.DID, info.Model, createErr) } else { - b.lgr.Debugf("[bridge] %d: device %s (%s) unsupported, skipped", id, info.DID, info.Model) + logrus.Debugf("[bridge] %d: device %s (%s) unsupported, skipped", id, info.DID, info.Model) } continue } b.dv[info.DID] = d } - b.lgr.Infof("[bridge] %d: %d devices", id, len(b.dv)) + logrus.Infof("[bridge] %d: %d devices", id, len(b.dv)) return b, nil } -// SetLogger 设置日志记录器。 -func (b *Bridge) SetLogger(l logger.Logger) { b.lgr = l } - func (b *Bridge) Close() error { return b.client.Close() } // Devices 返回设备列表(DID → 类型名)。 @@ -195,20 +191,34 @@ func pruneControllers(dv map[string]interface{}, alive map[string]struct{}) []st func deviceTypeName(d interface{}) string { switch d.(type) { - case devices.AirConditioner: return devices.TypeAirConditioner - case devices.Light: return devices.TypeLight - case devices.Switch: return devices.TypeSwitch - case devices.Fan: return devices.TypeFan - case devices.Cover: return devices.TypeCover - case devices.Humidifier: return devices.TypeHumidifier - case devices.Vacuum: return devices.TypeVacuum - case devices.WaterHeater: return devices.TypeWaterHeater - case devices.TempHumiditySensor: return devices.TypeTempHumidSensor - case devices.OccupancySensor: return devices.TypeOccupancySensor - case devices.Speaker: return devices.TypeSpeaker - case devices.Gateway: return devices.TypeGateway - case devices.Plug: return devices.TypePlug - default: return "unknown" + case devices.AirConditioner: + return devices.TypeAirConditioner + case devices.Light: + return devices.TypeLight + case devices.Switch: + return devices.TypeSwitch + case devices.Fan: + return devices.TypeFan + case devices.Cover: + return devices.TypeCover + case devices.Humidifier: + return devices.TypeHumidifier + case devices.Vacuum: + return devices.TypeVacuum + case devices.WaterHeater: + return devices.TypeWaterHeater + case devices.TempHumiditySensor: + return devices.TypeTempHumidSensor + case devices.OccupancySensor: + return devices.TypeOccupancySensor + case devices.Speaker: + return devices.TypeSpeaker + case devices.Gateway: + return devices.TypeGateway + case devices.Plug: + return devices.TypePlug + default: + return "unknown" } } @@ -216,85 +226,133 @@ func deviceTypeName(d interface{}) string { func (b *Bridge) AC(did string) (devices.AirConditioner, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } ac, ok := d.(devices.AirConditioner) - if !ok { return nil, fmt.Errorf("bridge: device %s is not an AirConditioner (got %T)", did, d) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not an AirConditioner (got %T)", did, d) + } return ac, nil } func (b *Bridge) Light(did string) (devices.Light, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } lt, ok := d.(devices.Light) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Light", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Light", did) + } return lt, nil } func (b *Bridge) Switch(did string) (devices.Switch, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } sw, ok := d.(devices.Switch) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Switch", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Switch", did) + } return sw, nil } func (b *Bridge) Fan(did string) (devices.Fan, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } f, ok := d.(devices.Fan) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Fan", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Fan", did) + } return f, nil } func (b *Bridge) Cover(did string) (devices.Cover, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } cv, ok := d.(devices.Cover) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Cover", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Cover", did) + } return cv, nil } func (b *Bridge) Humidifier(did string) (devices.Humidifier, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } h, ok := d.(devices.Humidifier) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Humidifier", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Humidifier", did) + } return h, nil } func (b *Bridge) Vacuum(did string) (devices.Vacuum, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } v, ok := d.(devices.Vacuum) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Vacuum", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Vacuum", did) + } return v, nil } func (b *Bridge) WaterHeater(did string) (devices.WaterHeater, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } wh, ok := d.(devices.WaterHeater) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a WaterHeater", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a WaterHeater", did) + } return wh, nil } func (b *Bridge) TempHumidity(did string) (devices.TempHumiditySensor, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } s, ok := d.(devices.TempHumiditySensor) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a TempHumiditySensor", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a TempHumiditySensor", did) + } return s, nil } func (b *Bridge) Occupancy(did string) (devices.OccupancySensor, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } s, ok := d.(devices.OccupancySensor) - if !ok { return nil, fmt.Errorf("bridge: device %s is not an OccupancySensor", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not an OccupancySensor", did) + } return s, nil } func (b *Bridge) Plug(did string) (devices.Plug, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } pg, ok := d.(devices.Plug) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Plug", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Plug", did) + } return pg, nil } func (b *Bridge) Speaker(did string) (devices.Speaker, error) { d, ok := b.dv[did] - if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s not found", did) + } sp, ok := d.(devices.Speaker) - if !ok { return nil, fmt.Errorf("bridge: device %s is not a Speaker", did) } + if !ok { + return nil, fmt.Errorf("bridge: device %s is not a Speaker", did) + } return sp, nil } diff --git a/bridge/subscribe.go b/bridge/subscribe.go index a3e0f56..50d2ab4 100644 --- a/bridge/subscribe.go +++ b/bridge/subscribe.go @@ -6,6 +6,8 @@ import ( "xiaomihome/xiaomi" "xiaomihome/xiaomi/devices" + + "github.com/sirupsen/logrus" ) // ── 回调 ── @@ -88,7 +90,9 @@ func (b *Bridge) subscribeDevice(did string, d interface{}) { }) } // 订阅设备在线/离线 - if bd, ok := d.(interface{ SubDeviceState(xiaomi.DeviceStateHandler) error }); ok { + if bd, ok := d.(interface { + SubDeviceState(xiaomi.DeviceStateHandler) error + }); ok { if err := bd.SubDeviceState(func(did string, state xiaomi.DeviceState) { online := state == xiaomi.StateOnline b.fireOnline(did, online) @@ -96,7 +100,7 @@ func (b *Bridge) subscribeDevice(did string, d interface{}) { go b.client.RefreshDeviceAllProps(did) } }); err != nil { - b.lgr.Warnf("[bridge] %d SubDeviceState %s: %v", b.id, did, err) + logrus.Warnf("[bridge] %d SubDeviceState %s: %v", b.id, did, err) } } } @@ -120,7 +124,9 @@ func (b *Bridge) fire(did, devType, changed string, props interface{}) { } func deviceTypeNameRaw(d interface{}) string { - if d == nil { return "unknown" } + if d == nil { + return "unknown" + } return deviceTypeName(d) } @@ -134,47 +140,47 @@ func (b *Bridge) FetchAllStates(ctx context.Context) { switch ctrl := d.(type) { case devices.AirConditioner: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d AC state: on=%v mode=%s target=%.1f", b.id, s.On, s.Mode, s.TargetTemperature) + logrus.Debugf("[bridge] %d AC state: on=%v mode=%s target=%.1f", b.id, s.On, s.Mode, s.TargetTemperature) } case devices.Light: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Light state: on=%v brightness=%d", b.id, s.On, s.Brightness) + logrus.Debugf("[bridge] %d Light state: on=%v brightness=%d", b.id, s.On, s.Brightness) } case devices.Switch: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Switch state: %v", b.id, s) + logrus.Debugf("[bridge] %d Switch state: %v", b.id, s) } case devices.Fan: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Fan state: on=%v level=%d", b.id, s.On, s.FanLevel) + logrus.Debugf("[bridge] %d Fan state: on=%v level=%d", b.id, s.On, s.FanLevel) } case devices.Humidifier: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Humidifier state: on=%v hum=%d", b.id, s.On, s.TargetHumidity) + logrus.Debugf("[bridge] %d Humidifier state: on=%v hum=%d", b.id, s.On, s.TargetHumidity) } case devices.Vacuum: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Vacuum state: on=%v mode=%d", b.id, s.On, s.Mode) + logrus.Debugf("[bridge] %d Vacuum state: on=%v mode=%d", b.id, s.On, s.Mode) } case devices.WaterHeater: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Heater: on=%v target=%.1f", b.id, s.On, s.TargetTemp) + logrus.Debugf("[bridge] %d Heater: on=%v target=%.1f", b.id, s.On, s.TargetTemp) } case devices.Cover: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Cover: pos=%d status=%d fault=%d", b.id, s.Position, s.Status, s.Fault) + logrus.Debugf("[bridge] %d Cover: pos=%d status=%d fault=%d", b.id, s.Position, s.Status, s.Fault) } case devices.TempHumiditySensor: if t, h, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Sensor: temp=%.1f hum=%.1f", b.id, t, h) + logrus.Debugf("[bridge] %d Sensor: temp=%.1f hum=%.1f", b.id, t, h) } case devices.OccupancySensor: if o, l, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Occupancy: occ=%v lux=%.0f", b.id, o, l) + logrus.Debugf("[bridge] %d Occupancy: occ=%v lux=%.0f", b.id, o, l) } case devices.Plug: if s, err := ctrl.FetchProps(ctx); err == nil { - b.lgr.Debugf("[bridge] %d Plug state: on=%v", b.id, s.On) + logrus.Debugf("[bridge] %d Plug state: on=%v", b.id, s.On) } } } diff --git a/go.mod b/go.mod index e323ae0..005966c 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.25.0 require ( github.com/eclipse/paho.mqtt.golang v1.5.1 github.com/hashicorp/mdns v1.0.7 + github.com/sirupsen/logrus v1.9.4 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/miekg/dns v1.1.72 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect golang.org/x/mod v0.31.0 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sync v0.19.0 // indirect diff --git a/go.sum b/go.sum index 330f292..547b838 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -8,8 +10,12 @@ github.com/hashicorp/mdns v1.0.7 h1:yWoQVMW5JOiDxQnIUcm3IDt0kCjf3TuXHDbdEKPsbAY= github.com/hashicorp/mdns v1.0.7/go.mod h1:yjuhYhZyPDqXXL48xC7cdpGwGUMwu7OViDmsuT5COvg= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= diff --git a/logger/logger.go b/logger/logger.go deleted file mode 100644 index bb40061..0000000 --- a/logger/logger.go +++ /dev/null @@ -1,25 +0,0 @@ -package logger - -import "github.com/sirupsen/logrus" - -// Logger 通用日志接口。logrus.Entry 原生满足,无需适配器。 -type Logger interface { - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) -} - -var defaultLogger Logger = logrus.StandardLogger() - -func init() { - // l := logrus.New() - // l.SetReportCaller(true) - // defaultLogger = logrus.NewEntry(l) -} - -// Default 返回全局默认 Logger(未注入时使用)。 -func Default() Logger { return defaultLogger } - -// SetDefault 设置全局默认 Logger。 -func SetDefault(l Logger) { defaultLogger = l } diff --git a/miot/const.go b/miot/const.go index 4e99a93..76d0015 100644 --- a/miot/const.go +++ b/miot/const.go @@ -12,17 +12,17 @@ import ( // ============================================================================ const ( - DOMAIN = "xiaomi_home" - DEFAULT_NAME = "Xiaomi Home" - DEFAULT_NICK_NAME = "Xiaomi" - OAUTH2_CLIENT_ID = "2882303761520251711" - OAUTH2_AUTH_URL = "https://account.xiaomi.com/oauth2/authorize" - DEFAULT_OAUTH2_API_HOST = "ha.api.io.mi.com" - DEFAULT_CLOUD_BROKER_HOST = "ha.mqtt.io.mi.com" - DEFAULT_CLOUD_SERVER = "cn" - DEFAULT_CTRL_MODE = "auto" + DOMAIN = "xiaomi_home" + DEFAULT_NAME = "Xiaomi Home" + DEFAULT_NICK_NAME = "Xiaomi" + OAUTH2_CLIENT_ID = "2882303761520251711" + OAUTH2_AUTH_URL = "https://account.xiaomi.com/oauth2/authorize" + DEFAULT_OAUTH2_API_HOST = "ha.api.io.mi.com" + DEFAULT_CLOUD_BROKER_HOST = "ha.mqtt.io.mi.com" + DEFAULT_CLOUD_SERVER = "cn" + DEFAULT_CTRL_MODE = "auto" DEFAULT_INTEGRATION_LANGUAGE = "en" - OAUTH_REDIRECT_URL = "http://homeassistant.local:8123" + OAUTH_REDIRECT_URL = "http://homeassistant.local:8123" ) // ============================================================================ @@ -30,13 +30,13 @@ const ( // ============================================================================ const ( - MIHOME_HTTP_API_TIMEOUT = 30 - MIHOME_MQTT_KEEPALIVE = 60 + MIHOME_HTTP_API_TIMEOUT = 30 + MIHOME_MQTT_KEEPALIVE = 60 // seconds, 3 days - MIHOME_CERT_EXPIRE_MARGIN = 3600 * 24 * 3 + MIHOME_CERT_EXPIRE_MARGIN = 3600 * 24 * 3 // seconds, 1 hour — token 过期前刷新(比 Python 60s 更保守) MIHOME_REFRESH_THRESHOLD = 3600 * 6 - NETWORK_REFRESH_INTERVAL = 30 + NETWORK_REFRESH_INTERVAL = 30 // seconds, 14 days SPEC_STD_LIB_EFFECTIVE_TIME = 3600 * 24 * 14 // seconds, 14 days diff --git a/miot/miot_client.go b/miot/miot_client.go index 458c68b..55a84b4 100644 --- a/miot/miot_client.go +++ b/miot/miot_client.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -101,8 +101,6 @@ type MIoTClient struct { mu sync.RWMutex refreshMu sync.Mutex // token 刷新互斥锁 - // === Logger === - lgr logger.Logger } // NewMIoTClient creates a new MIoTClient. @@ -129,14 +127,10 @@ func NewMIoTClient(entryID string, entryData map[string]interface{}, uid string, mipsLocalStateTimers: make(map[string]*time.Timer), ctx: ctx, cancel: cancel, - lgr: logger.Default(), } return c } -// SetLogger sets a custom logger for MIoTClient. -func (c *MIoTClient) SetLogger(l logger.Logger) { c.lgr = l } - // Init initializes the MIoTClient. // Aligns with Python MIoTClient.init_async. func (c *MIoTClient) Init() error { @@ -156,7 +150,7 @@ func (c *MIoTClient) Init() error { if et, ok := authInfo["expires_ts"].(float64); ok { c.entryData["expires_ts"] = et } - c.lgr.Debugf("[MIoTClient] Loaded token from storage\n") + logrus.Debugf("[MIoTClient] Loaded token from storage\n") } else if accessToken != "" { // First run: write entryData token to storage expiresTS := float64(time.Now().Unix()) + 3600*24*30 // default 30 days @@ -168,7 +162,7 @@ func (c *MIoTClient) Init() error { "refresh_token": refreshToken, "expires_ts": expiresTS, }, true) - c.lgr.Debugf("[MIoTClient] Saved initial token to storage\n") + logrus.Debugf("[MIoTClient] Saved initial token to storage\n") } } @@ -179,7 +173,7 @@ func (c *MIoTClient) Init() error { // 2. Load device cache if err := c.loadCacheDevice(); err != nil { - c.lgr.Warnf("[MIoTClient] Warning: failed to load device cache: %v\n", err) + logrus.Warnf("[MIoTClient] Warning: failed to load device cache: %v\n", err) } // 3.1 Initialize OAuth client @@ -187,27 +181,27 @@ func (c *MIoTClient) Init() error { oauthRedirectURL, _ := c.entryData["oauth_redirect_url"].(string) uuid, _ := c.entryData["uuid"].(string) c.oauth = NewMIoTOauthClient(OAUTH2_CLIENT_ID, oauthRedirectURL, c.cloudServer, uuid) - c.lgr.Debugf("[MIoTClient] OAuth client initialized\n") + logrus.Debugf("[MIoTClient] OAuth client initialized\n") } // 3.2 Initialize Cert client cert, err := NewMIoTCert(c.storage, c.uid, c.cloudServer) if err != nil { - c.lgr.Warnf("[MIoTClient] Warning: cert init failed: %v\n", err) + logrus.Warnf("[MIoTClient] Warning: cert init failed: %v\n", err) } else { c.cert = cert c.cert.VerifyCACert() - c.lgr.Debugf("[MIoTClient] Cert client initialized\n") + logrus.Debugf("[MIoTClient] Cert client initialized\n") } // 4. Initialize HTTP client (use token from storage if available) if c.http == nil && accessToken != "" { httpClient, err := NewMIoTHttpClient(c.cloudServer, OAUTH2_CLIENT_ID, accessToken) if err != nil { - c.lgr.Warnf("[MIoTClient] Warning: failed to create HTTP client: %v\n", err) + logrus.Warnf("[MIoTClient] Warning: failed to create HTTP client: %v\n", err) } else { c.http = httpClient - c.lgr.Debugf("[MIoTClient] HTTP client created\n") + logrus.Debugf("[MIoTClient] HTTP client created\n") } } @@ -223,9 +217,9 @@ func (c *MIoTClient) Init() error { // Connect asynchronously to not block Init c.mu.Unlock() if err := mips.Connect(); err != nil { - c.lgr.Warnf("[MIoTClient] Warning: MIPS cloud connect failed: %v\n", err) + logrus.Warnf("[MIoTClient] Warning: MIPS cloud connect failed: %v\n", err) } else { - c.lgr.Debugf("[MIoTClient] MIPS cloud connected\n") + logrus.Debugf("[MIoTClient] MIPS cloud connected\n") } c.mu.Lock() } @@ -239,7 +233,7 @@ func (c *MIoTClient) Init() error { nw.SubNetworkStatus(key, c.onNetworkStatusChanged) status := nw.GetNetworkStatus() c.onNetworkStatusChanged(status) - c.lgr.Debugf("[MIoTClient] Network status subscribed (online=%v)\n", status) + logrus.Debugf("[MIoTClient] Network status subscribed (online=%v)\n", status) } } @@ -258,12 +252,12 @@ func (c *MIoTClient) Init() error { groupID, _ := info["group_id"].(string) if groupID != "" { svc.SubServiceChange(key, groupID, c.onMipsServiceStateChange) - c.lgr.Debugf("[MIoTClient] mDNS service subscribed: home=%s, group=%s\n", homeID, groupID) + logrus.Debugf("[MIoTClient] mDNS service subscribed: home=%s, group=%s\n", homeID, groupID) // Check if service already discovered serviceData := svc.GetServices(groupID) if sd, exists := serviceData[groupID]; exists && sd.validService() { - c.lgr.Debugf("[MIoTClient] Central mips service scanned: %s, %v\n", homeID, sd) + logrus.Debugf("[MIoTClient] Central mips service scanned: %s, %v\n", homeID, sd) mips := NewMipsLocalClient( getVirtualDID(c.entryData), sd.Addresses[0], @@ -294,7 +288,7 @@ func (c *MIoTClient) Init() error { if lan.InitDone() { c.onMiotLanStateChange(true) } - c.lgr.Debugf("[MIoTClient] LAN control initialized\n") + logrus.Debugf("[MIoTClient] LAN control initialized\n") } } } else { @@ -324,9 +318,9 @@ func (c *MIoTClient) Init() error { } c.i18n = NewMIoTI18n(lang) c.i18n.Init() - c.lgr.Debugf("[MIoTClient] i18n initialized (lang=%s)\n", lang) + logrus.Debugf("[MIoTClient] i18n initialized (lang=%s)\n", lang) - c.lgr.Infof("[MIoTClient] Initialized\n") + logrus.Infof("[MIoTClient] Initialized\n") return nil } @@ -358,7 +352,7 @@ func (c *MIoTClient) Deinit() error { if c.mipsCloud != nil { c.mipsCloud.UnsubState(key) c.mipsCloud.Disconnect() - c.lgr.Debugf("[MIoTClient] MIPS cloud disconnected\n") + logrus.Debugf("[MIoTClient] MIPS cloud disconnected\n") } // 4. Cancel refresh cloud devices timer @@ -441,9 +435,9 @@ func (c *MIoTClient) Deinit() error { } deviceKey := fmt.Sprintf("%s_%s", c.uid, c.cloudServer) if err := c.storage.Save("miot_devices", deviceKey, deviceListForStorage); err != nil { - c.lgr.Warnf("[MIoTClient] Warning: failed to save devices on deinit: %v\n", err) + logrus.Warnf("[MIoTClient] Warning: failed to save devices on deinit: %v\n", err) } - c.lgr.Infof("[MIoTClient] Device cache saved to storage\n") + logrus.Infof("[MIoTClient] Device cache saved to storage\n") } // Cancel context @@ -454,7 +448,7 @@ func (c *MIoTClient) Deinit() error { // Clear subscription tree c.subTree.Clear() - c.lgr.Infof("[MIoTClient] Deinitialized\n") + logrus.Infof("[MIoTClient] Deinitialized\n") return nil } @@ -493,7 +487,7 @@ func (c *MIoTClient) loadCacheDevice() error { c.deviceListCache[did] = infoMap } } - c.lgr.Debugf("[MIoTClient] Loaded %d devices from cache\n", len(deviceList)) + logrus.Debugf("[MIoTClient] Loaded %d devices from cache\n", len(deviceList)) return nil } @@ -647,7 +641,7 @@ func (c *MIoTClient) RefreshAuthInfo() (map[string]interface{}, error) { c.mipsCloud.UpdateAccessToken(ac) } - c.lgr.Debugf("[MIoTClient] RefreshAuthInfo: token refreshed\n") + logrus.Debugf("[MIoTClient] RefreshAuthInfo: token refreshed\n") return result, nil } @@ -678,7 +672,7 @@ func (c *MIoTClient) refreshUserCert() { // Verify CA cert if ok, err := c.cert.VerifyCACert(); err != nil || !ok { - c.lgr.Errorf("[MIoTClient] CA cert verification FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] CA cert verification FAILED: %v\n", err) } // Check remaining time @@ -690,14 +684,14 @@ func (c *MIoTClient) refreshUserCert() { nextDelay = 60 } c.RequestRefreshUserCert(nextDelay) - c.lgr.Debugf("[MIoTClient] Cert still valid for %ds, recheck in %ds\n", remainSec, nextDelay) + logrus.Debugf("[MIoTClient] Cert still valid for %ds, recheck in %ds\n", remainSec, nextDelay) return } // Load user key _, err := c.cert.LoadUserKey() if err != nil { - c.lgr.Errorf("[MIoTClient] Cert refresh: load key FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] Cert refresh: load key FAILED: %v\n", err) // Retry after delay c.RequestRefreshUserCert(3600) return @@ -706,7 +700,7 @@ func (c *MIoTClient) refreshUserCert() { // Generate CSR csr, err := c.cert.GenUserCSR() if err != nil { - c.lgr.Errorf("[MIoTClient] Cert refresh: gen CSR FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] Cert refresh: gen CSR FAILED: %v\n", err) c.RequestRefreshUserCert(3600) return } @@ -714,18 +708,18 @@ func (c *MIoTClient) refreshUserCert() { // Request cert from cloud certPEM, err := c.http.GetCentralCert(csr) if err != nil { - c.lgr.Errorf("[MIoTClient] Cert refresh: get cert from cloud FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] Cert refresh: get cert from cloud FAILED: %v\n", err) c.RequestRefreshUserCert(RefreshCertRetryDelay) return } // Save cert if err = c.cert.SaveUserCert(certPEM); err != nil { - c.lgr.Errorf("[MIoTClient] Cert refresh: save cert FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] Cert refresh: save cert FAILED: %v\n", err) return } - c.lgr.Infof("[MIoTClient] Cert refreshed successfully\n") + logrus.Infof("[MIoTClient] Cert refreshed successfully\n") // Update MIPS local clients with new certs (if any are still connected) for _, mips := range c.mipsLocal { @@ -783,13 +777,13 @@ func (c *MIoTClient) ShowCentralStateChangedNotify(connected bool) { // Aligns with Python: __on_mips_cloud_state_changed. func (c *MIoTClient) onMipsCloudStateChanged(key string, connected bool) { if connected { - c.lgr.Debugf("[MIoTClient] MIPS cloud connected\n") + logrus.Debugf("[MIoTClient] MIPS cloud connected\n") // Refresh devices on reconnect c.ScheduleRefreshDevices(RefreshCloudDevicesDelay * time.Millisecond) return } - c.lgr.Debugf("[MIoTClient] MIPS cloud disconnected\n") + logrus.Debugf("[MIoTClient] MIPS cloud disconnected\n") // Disconnect: mark all cloud devices offline c.deviceListMu.Lock() @@ -821,7 +815,7 @@ func (c *MIoTClient) onMipsCloudStateChanged(key string, connected bool) { // onNetworkStatusChanged handles network connectivity changes. // Aligns with Python: __on_network_status_changed. func (c *MIoTClient) onNetworkStatusChanged(status bool) { - c.lgr.Debugf("[MIoTClient] Network status changed: online=%v\n", status) + logrus.Debugf("[MIoTClient] Network status changed: online=%v\n", status) if status { // Network is back; trigger device refresh and reconnect MIPS if c.mipsCloud != nil && !c.mipsCloud.IsConnected() { @@ -834,15 +828,15 @@ func (c *MIoTClient) onNetworkStatusChanged(status bool) { // onMipsServiceStateChange handles mDNS service discoveries for central gateways. // Aligns with Python: __on_mips_service_state_change. func (c *MIoTClient) onMipsServiceStateChange(groupID string, state MipsServiceState, data map[string]interface{}) { - c.lgr.Debugf("[MIoTClient] MIPS service change: group=%s, state=%s\n", groupID, state) + logrus.Debugf("[MIoTClient] MIPS service change: group=%s, state=%s\n", groupID, state) virtualDID := getVirtualDID(c.entryData) if virtualDID == "" { - c.lgr.Debugf("[MIoTClient] MIPS service: missing virtual_did, skip\n") + logrus.Debugf("[MIoTClient] MIPS service: missing virtual_did, skip\n") return } if c.cert == nil { - c.lgr.Debugf("[MIoTClient] MIPS service: cert not initialized, skip\n") + logrus.Debugf("[MIoTClient] MIPS service: cert not initialized, skip\n") return } @@ -851,7 +845,7 @@ func (c *MIoTClient) onMipsServiceStateChange(groupID string, state MipsServiceS // Create new MipsLocalClient host, _ := data["addresses"].([]interface{}) if len(host) == 0 { - c.lgr.Debugf("[MIoTClient] MIPS service: no addresses for %s\n", groupID) + logrus.Debugf("[MIoTClient] MIPS service: no addresses for %s\n", groupID) return } hostStr, _ := host[0].(string) @@ -872,7 +866,7 @@ func (c *MIoTClient) onMipsServiceStateChange(groupID string, state MipsServiceS mips.SubState(groupID, c.onMipsLocalStateChanged) mips.Connect() c.mipsLocal[groupID] = mips - c.lgr.Debugf("[MIoTClient] MIPS local client created: %s @ %s:%d\n", groupID, hostStr, int(port)) + logrus.Debugf("[MIoTClient] MIPS local client created: %s @ %s:%d\n", groupID, hostStr, int(port)) case MipsServiceUpdated: // Disconnect old, connect new @@ -902,7 +896,7 @@ func (c *MIoTClient) onMipsServiceStateChange(groupID string, state MipsServiceS mips.SubState(groupID, c.onMipsLocalStateChanged) mips.Connect() c.mipsLocal[groupID] = mips - c.lgr.Debugf("[MIoTClient] MIPS local client updated: %s @ %s:%d\n", groupID, hostStr, int(port)) + logrus.Debugf("[MIoTClient] MIPS local client updated: %s @ %s:%d\n", groupID, hostStr, int(port)) case MipsServiceRemoved: if mips, exists := c.mipsLocal[groupID]; exists { @@ -910,7 +904,7 @@ func (c *MIoTClient) onMipsServiceStateChange(groupID string, state MipsServiceS mips.UnsubState(groupID) mips.Disconnect() delete(c.mipsLocal, groupID) - c.lgr.Debugf("[MIoTClient] MIPS local client removed: %s\n", groupID) + logrus.Debugf("[MIoTClient] MIPS local client removed: %s\n", groupID) } } } @@ -919,7 +913,7 @@ func (c *MIoTClient) onMipsServiceStateChange(groupID string, state MipsServiceS // Aligns with Python: __on_mips_local_state_changed. // key is the groupID of the gateway. func (c *MIoTClient) onMipsLocalStateChanged(key string, connected bool) { - c.lgr.Debugf("[MIoTClient] MIPS local state changed: group=%s, connected=%v\n", key, connected) + logrus.Debugf("[MIoTClient] MIPS local state changed: group=%s, connected=%v\n", key, connected) if connected { // Reconnect: pull gateway device list @@ -966,7 +960,7 @@ func (c *MIoTClient) onMipsLocalStateChanged(key string, connected bool) { // onMiotLanStateChange handles LAN controller state transitions. // Aligns with Python: __on_miot_lan_state_change. func (c *MIoTClient) onMiotLanStateChange(initialized bool) { - c.lgr.Debugf("[MIoTClient] LAN state changed: initialized=%v\n", initialized) + logrus.Debugf("[MIoTClient] LAN state changed: initialized=%v\n", initialized) if initialized { c.deviceListMu.Lock() lan, ok := c.miotLan.(*MIoTLan) @@ -1026,7 +1020,7 @@ func (c *MIoTClient) onLanDeviceStateChanged(did string, state map[string]interf // Aligns with Python: __on_gw_device_list_changed. // Called when a MipsLocalClient reports that its device list has changed. func (c *MIoTClient) onGWDeviceListChanged() { - c.lgr.Debugf("[MIoTClient] GW device list changed\n") + logrus.Debugf("[MIoTClient] GW device list changed\n") for groupID, mips := range c.mipsLocal { if mips == nil || !mips.IsConnected() { @@ -1036,7 +1030,7 @@ func (c *MIoTClient) onGWDeviceListChanged() { // Pull device list from gateway payload, err := mips.GetDevList("", MipsRequestTimeoutDefault*time.Millisecond) if err != nil { - c.lgr.Errorf("[MIoTClient] GW device list pull failed for %s: %v\n", groupID, err) + logrus.Errorf("[MIoTClient] GW device list pull failed for %s: %v\n", groupID, err) continue } @@ -1045,7 +1039,7 @@ func (c *MIoTClient) onGWDeviceListChanged() { Result map[string]interface{} `json:"result"` } if err := json.Unmarshal([]byte(payload), &result); err != nil { - c.lgr.Errorf("[MIoTClient] GW device list parse failed for %s: %v\n", groupID, err) + logrus.Errorf("[MIoTClient] GW device list parse failed for %s: %v\n", groupID, err) continue } @@ -1090,7 +1084,7 @@ func (c *MIoTClient) updateDevicesFromGW(groupID string, devices map[string]inte } if changed { - c.lgr.Debugf("[MIoTClient] GW devices updated: group=%s, count=%d\n", groupID, len(devices)) + logrus.Debugf("[MIoTClient] GW devices updated: group=%s, count=%d\n", groupID, len(devices)) } } diff --git a/miot/miot_client_api.go b/miot/miot_client_api.go index 3106ea6..d315d90 100644 --- a/miot/miot_client_api.go +++ b/miot/miot_client_api.go @@ -4,6 +4,8 @@ package miot import ( "fmt" + + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -40,7 +42,7 @@ func (c *MIoTClient) SetProp(did string, siid, piid int, value interface{}) erro if results[0].Code != 0 && results[0].Code != 1 { // 设备移除或离线:刷新设备列表(Aligns with Python) if results[0].Code == -704010000 || results[0].Code == -704042011 { - c.lgr.Errorf("[MIoTClient] device may be removed or offline, %s\n", did) + logrus.Errorf("[MIoTClient] device may be removed or offline, %s\n", did) if devs, rerr := c.http.GetDevices(nil); rerr == nil { c.deviceListMu.Lock() for k := range c.deviceListCloud { @@ -54,7 +56,7 @@ func (c *MIoTClient) SetProp(did string, siid, piid int, value interface{}) erro return fmt.Errorf("set property failed, code=%d", results[0].Code) } - c.lgr.Infof("[MIoTClient] SetProp: %s/%d/%d = %v\n", did, siid, piid, value) + logrus.Infof("[MIoTClient] SetProp: %s/%d/%d = %v\n", did, siid, piid, value) return nil } @@ -86,7 +88,7 @@ func (c *MIoTClient) GetProp(did string, siid, piid int) (interface{}, error) { return nil, fmt.Errorf("get property failed, code=%d", results[0].Code) } - c.lgr.Infof("[MIoTClient] GetProp: %s/%d/%d = %v\n", did, siid, piid, results[0].Value) + logrus.Infof("[MIoTClient] GetProp: %s/%d/%d = %v\n", did, siid, piid, results[0].Value) return results[0].Value, nil } @@ -107,7 +109,7 @@ func (c *MIoTClient) Action(did string, siid, aiid int, inList []interface{}) (m return nil, err } - c.lgr.Infof("[MIoTClient] Action: %s/%d/%d, result=%v\n", did, siid, aiid, result) + logrus.Infof("[MIoTClient] Action: %s/%d/%d, result=%v\n", did, siid, aiid, result) return result, nil } diff --git a/miot/miot_client_device.go b/miot/miot_client_device.go index 7f8f8fd..6eb8ab7 100644 --- a/miot/miot_client_device.go +++ b/miot/miot_client_device.go @@ -8,6 +8,8 @@ import ( "regexp" "strings" "time" + + "github.com/sirupsen/logrus" ) // subDevPattern matches .s\d+ suffix for sub-devices. @@ -39,7 +41,7 @@ func (c *MIoTClient) getOrCreateDevice(did string, info map[string]interface{}) spec, err := c.specParser.Parse(urn, model) if err != nil { - c.lgr.Errorf("[MIoTClient] getOrCreateDevice: SPEC parse failed for %s (%s/%s): %v\n", did, urn, model, err) + logrus.Errorf("[MIoTClient] getOrCreateDevice: SPEC parse failed for %s (%s/%s): %v\n", did, urn, model, err) return nil } @@ -169,7 +171,7 @@ func (c *MIoTClient) RefreshDevices(homeIDs []string) error { } else { c.deviceListCache[did] = cloneMap(info) } - c.lgr.Debugf("[MIoTClient] Device added: %s (%v)\n", did, info["name"]) + logrus.Debugf("[MIoTClient] Device added: %s (%v)\n", did, info["name"]) changed = true } else { // 检测并同步所有变更字段(name/model/nickname 等) @@ -196,7 +198,7 @@ func (c *MIoTClient) RefreshDevices(homeIDs []string) error { c.mu.Lock() for _, did := range removed { delete(c.devices, did) - c.lgr.Infof("[MIoTClient] Device removed: %s\n", did) + logrus.Infof("[MIoTClient] Device removed: %s\n", did) } c.mu.Unlock() // 取消该设备在云/网关/局域网的订阅(三源均已清空 → 内部会退订) @@ -224,7 +226,7 @@ func (c *MIoTClient) RefreshDevices(homeIDs []string) error { } deviceKey := fmt.Sprintf("%s_%s", c.uid, c.cloudServer) if err := c.storage.Save("miot_devices", deviceKey, deviceListForStorage); err != nil { - c.lgr.Warnf("[MIoTClient] Warning: failed to save devices: %v\n", err) + logrus.Warnf("[MIoTClient] Warning: failed to save devices: %v\n", err) } } @@ -233,7 +235,7 @@ func (c *MIoTClient) RefreshDevices(homeIDs []string) error { c.ShowDevicesChangedNotify() } - c.lgr.Infof("[MIoTClient] Devices refreshed: cloud=%d, cache=%d\n", len(cloudDevices), len(c.deviceListCache)) + logrus.Infof("[MIoTClient] Devices refreshed: cloud=%d, cache=%d\n", len(cloudDevices), len(c.deviceListCache)) return nil } @@ -317,7 +319,7 @@ func (c *MIoTClient) deviceHomeID(did string) string { // 5. homeScope 非空时只处理该家庭下的设备。 func (c *MIoTClient) pruneRemovedDevicesLocked(cloudDevices map[string]map[string]interface{}, homeScope map[string]struct{}) []string { if len(cloudDevices) == 0 { - c.lgr.Warnf("[MIoTClient] empty cloud device list, skip removal detection\n") + logrus.Warnf("[MIoTClient] empty cloud device list, skip removal detection\n") return nil } @@ -386,7 +388,7 @@ func (c *MIoTClient) aggregateDeviceState(did string) { explicitFalse = true } } else if _, exists := info["online"]; exists { - c.lgr.Debugf("[MIoTClient] %s online type mismatch: %v (%T)\n", did, info["online"], info["online"]) + logrus.Debugf("[MIoTClient] %s online type mismatch: %v (%T)\n", did, info["online"], info["online"]) } } @@ -401,7 +403,7 @@ func (c *MIoTClient) aggregateDeviceState(did string) { } else { online = true // 云列表里有但没 online 字段,假设在线 } - c.lgr.Debugf("[MIoTClient] %s no online field from cloud/gw/lan, fallback online=%v (cache=%v)\n", + logrus.Debugf("[MIoTClient] %s no online field from cloud/gw/lan, fallback online=%v (cache=%v)\n", did, online, cacheInfo["online"]) } @@ -413,7 +415,7 @@ func (c *MIoTClient) aggregateDeviceState(did string) { func (c *MIoTClient) LoadDevices() error { // Try to load from cache first if err := c.loadCacheDevice(); err != nil { - c.lgr.Errorf("[MIoTClient] Failed to load from cache: %v\n", err) + logrus.Errorf("[MIoTClient] Failed to load from cache: %v\n", err) } // Then refresh from cloud @@ -461,15 +463,15 @@ func (c *MIoTClient) ScheduleRefreshDevices(delay time.Duration) { } c.refreshCloudDevicesTimer = time.AfterFunc(delay, func() { - c.lgr.Infof("[MIoTClient] Refreshing devices...\n") + logrus.Infof("[MIoTClient] Refreshing devices...\n") if err := c.RefreshDevices(c.homeIDs); err != nil { - c.lgr.Errorf("[MIoTClient] Refresh failed, will retry: %v\n", err) + logrus.Errorf("[MIoTClient] Refresh failed, will retry: %v\n", err) // Retry after 60s c.ScheduleRefreshDevices(RefreshCloudDevicesRetryDelay * time.Millisecond) } }) - c.lgr.Infof("[MIoTClient] Device refresh scheduled in %v\n", delay) + logrus.Infof("[MIoTClient] Device refresh scheduled in %v\n", delay) } // Start starts the MIoTClient and schedules initial device load. @@ -483,7 +485,7 @@ func (c *MIoTClient) Start() error { // Schedule initial device load (delayed, aligns with REFRESH_CLOUD_DEVICES_DELAY=6 in Python) c.ScheduleRefreshDevices(RefreshCloudDevicesDelay * time.Millisecond) - c.lgr.Infof("[MIoTClient] Started\n") + logrus.Infof("[MIoTClient] Started\n") return nil } diff --git a/miot/miot_client_device_test.go b/miot/miot_client_device_test.go index a8ed635..0cbab15 100644 --- a/miot/miot_client_device_test.go +++ b/miot/miot_client_device_test.go @@ -541,4 +541,3 @@ func TestRefreshDevices_PrunesDeletedDevice(t *testing.T) { t.Error("DeviceList() 应保留云端仍存在的设备") } } - diff --git a/miot/miot_client_prop.go b/miot/miot_client_prop.go index 0d82dc9..c5024db 100644 --- a/miot/miot_client_prop.go +++ b/miot/miot_client_prop.go @@ -5,6 +5,8 @@ package miot import ( "fmt" "time" + + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -44,7 +46,7 @@ func (c *MIoTClient) RefreshDeviceAllProps(did string) error { } } - c.lgr.Debugf("[MIoTClient] RefreshDeviceAllProps: %s → %d props enqueued\n", did, count) + logrus.Debugf("[MIoTClient] RefreshDeviceAllProps: %s → %d props enqueued\n", did, count) return nil } @@ -62,7 +64,7 @@ func (c *MIoTClient) RequestRefreshProp(did string, siid, piid int) { // Check if already in refresh list if _, exists := c.refreshPropsList[key]; exists { - c.lgr.Debugf("[MIoTClient] RequestRefreshProp: SKIPPED (already queued) %s\n", key) + logrus.Debugf("[MIoTClient] RequestRefreshProp: SKIPPED (already queued) %s\n", key) return } @@ -74,7 +76,7 @@ func (c *MIoTClient) RequestRefreshProp(did string, siid, piid int) { // Schedule refresh if not already scheduled if c.refreshPropsTimer == nil { - c.lgr.Debugf("[MIoTClient] RequestRefreshProp: Scheduling refresh timer for %s\n", key) + logrus.Debugf("[MIoTClient] RequestRefreshProp: Scheduling refresh timer for %s\n", key) // Reset retry count for new batch c.refreshRetryCount = 0 @@ -83,14 +85,14 @@ func (c *MIoTClient) RequestRefreshProp(did string, siid, piid int) { c.refreshPropsTimer = nil c.mu.Unlock() - c.lgr.Debugf("[MIoTClient] RequestRefreshProp: Timer fired, calling refreshPropsHandler()\n") + logrus.Debugf("[MIoTClient] RequestRefreshProp: Timer fired, calling refreshPropsHandler()\n") c.refreshPropsHandler() }) } else { - c.lgr.Debugf("[MIoTClient] RequestRefreshProp: Timer already scheduled, added to queue: %s\n", key) + logrus.Debugf("[MIoTClient] RequestRefreshProp: Timer already scheduled, added to queue: %s\n", key) } - c.lgr.Debugf("[MIoTClient] RequestRefreshProp: QUEUED %s (queue size: %d)\n", key, len(c.refreshPropsList)) + logrus.Debugf("[MIoTClient] RequestRefreshProp: QUEUED %s (queue size: %d)\n", key, len(c.refreshPropsList)) } // refreshPropsHandler handles property refresh from cloud. @@ -110,7 +112,7 @@ func (c *MIoTClient) refreshPropsHandler() { c.refreshPropsList = make(map[string]map[string]interface{}) c.mu.Unlock() - c.lgr.Debugf("[MIoTClient] refreshPropsHandler: START, %d props to refresh\n", len(propsToRefresh)) + logrus.Debugf("[MIoTClient] refreshPropsHandler: START, %d props to refresh\n", len(propsToRefresh)) // Group props by source (cloud / gateway / lan) cloudParams := make([]PropParam, 0) @@ -190,12 +192,12 @@ func (c *MIoTClient) refreshPropsHandler() { batch := cloudParams[i:end] results, err := c.http.GetProps(batch) if err != nil { - c.lgr.Errorf("[MIoTClient] refreshPropsHandler: cloud GetProps FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] refreshPropsHandler: cloud GetProps FAILED: %v\n", err) c.retryProps(propsToRefresh) return } allResults = append(allResults, results...) - c.lgr.Debugf("[MIoTClient] refreshPropsHandler: cloud batch %d returned %d results\n", i/150+1, len(results)) + logrus.Debugf("[MIoTClient] refreshPropsHandler: cloud batch %d returned %d results\n", i/150+1, len(results)) } } @@ -203,7 +205,7 @@ func (c *MIoTClient) refreshPropsHandler() { for groupID, params := range gatewayParams { mips, exists := c.mipsLocal[groupID] if !exists { - c.lgr.Infof("[MIoTClient] refreshPropsHandler: gateway %s not found, routing to cloud\n", groupID) + logrus.Infof("[MIoTClient] refreshPropsHandler: gateway %s not found, routing to cloud\n", groupID) // Fallback to cloud if c.http != nil { results, err := c.http.GetProps(params) @@ -215,7 +217,7 @@ func (c *MIoTClient) refreshPropsHandler() { } results, err := mips.GetProps(params, MipsRequestTimeoutDefault*time.Millisecond) if err != nil { - c.lgr.Errorf("[MIoTClient] refreshPropsHandler: gateway %s GetProps FAILED: %v\n", groupID, err) + logrus.Errorf("[MIoTClient] refreshPropsHandler: gateway %s GetProps FAILED: %v\n", groupID, err) // Fallback to cloud if c.http != nil { results, err := c.http.GetProps(params) @@ -226,7 +228,7 @@ func (c *MIoTClient) refreshPropsHandler() { continue } allResults = append(allResults, results...) - c.lgr.Debugf("[MIoTClient] refreshPropsHandler: gateway %s returned %d results\n", groupID, len(results)) + logrus.Debugf("[MIoTClient] refreshPropsHandler: gateway %s returned %d results\n", groupID, len(results)) } // LAN source @@ -235,7 +237,7 @@ func (c *MIoTClient) refreshPropsHandler() { if ok { results, err := lan.GetProps(lanParams) if err != nil { - c.lgr.Errorf("[MIoTClient] refreshPropsHandler: LAN GetProps FAILED: %v\n", err) + logrus.Errorf("[MIoTClient] refreshPropsHandler: LAN GetProps FAILED: %v\n", err) // Fallback to cloud if c.http != nil { results, err := c.http.GetProps(lanParams) @@ -245,7 +247,7 @@ func (c *MIoTClient) refreshPropsHandler() { } } else { allResults = append(allResults, results...) - c.lgr.Debugf("[MIoTClient] refreshPropsHandler: LAN returned %d results\n", len(results)) + logrus.Debugf("[MIoTClient] refreshPropsHandler: LAN returned %d results\n", len(results)) } } } @@ -255,17 +257,17 @@ func (c *MIoTClient) refreshPropsHandler() { c.refreshRetryCount = 0 c.mu.Unlock() - c.lgr.Debugf("[MIoTClient] refreshPropsHandler: total %d results\n", len(allResults)) + logrus.Debugf("[MIoTClient] refreshPropsHandler: total %d results\n", len(allResults)) // Dispatch results to subscribers for _, result := range allResults { if result.Code != 0 { - c.lgr.Errorf("[MIoTClient] refreshPropsHandler: FAILED %s/%d/%d, code=%d (%s)\n", + logrus.Errorf("[MIoTClient] refreshPropsHandler: FAILED %s/%d/%d, code=%d (%s)\n", result.DID, result.Siid, result.Piid, result.Code, ErrDesc(result.Code)) continue } - c.lgr.Debugf("[MIoTClient] refreshPropsHandler: SUCCESS %s/%d/%d = %v\n", result.DID, result.Siid, result.Piid, result.Value) + logrus.Debugf("[MIoTClient] refreshPropsHandler: SUCCESS %s/%d/%d = %v\n", result.DID, result.Siid, result.Piid, result.Value) params := map[string]interface{}{ "did": result.DID, "siid": result.Siid, @@ -284,7 +286,7 @@ func (c *MIoTClient) retryProps(props []map[string]interface{}) { c.mu.Unlock() if retryCount >= RefreshPropsRetryMax { - c.lgr.Infof("[MIoTClient] refreshPropsHandler: GIVING UP after %d retries\n", retryCount) + logrus.Infof("[MIoTClient] refreshPropsHandler: GIVING UP after %d retries\n", retryCount) c.mu.Lock() c.refreshPropsList = make(map[string]map[string]interface{}) c.refreshRetryCount = 0 @@ -292,7 +294,7 @@ func (c *MIoTClient) retryProps(props []map[string]interface{}) { return } - c.lgr.Infof("[MIoTClient] refreshPropsHandler: RETRYING (%d/%d)\n", retryCount, RefreshPropsRetryMax) + logrus.Infof("[MIoTClient] refreshPropsHandler: RETRYING (%d/%d)\n", retryCount, RefreshPropsRetryMax) c.mu.Lock() for _, prop := range props { key := fmt.Sprintf("%s|%d|%d", prop["did"].(string), prop["siid"].(int), prop["piid"].(int)) diff --git a/miot/miot_client_sub.go b/miot/miot_client_sub.go index f80343f..63b4041 100644 --- a/miot/miot_client_sub.go +++ b/miot/miot_client_sub.go @@ -5,6 +5,8 @@ package miot import ( "encoding/json" "fmt" + + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -25,7 +27,7 @@ func (c *MIoTClient) SubProp(did string, handler func(map[string]interface{}, in subID := fmt.Sprintf("%p", handler) c.subTree.Sub(topic, handler, nil) - c.lgr.Debugf("[MIoTClient] Subscribed to property: %s (topic=%s, subID=%s)\n", did, topic, subID) + logrus.Debugf("[MIoTClient] Subscribed to property: %s (topic=%s, subID=%s)\n", did, topic, subID) c.mu.Unlock() @@ -52,7 +54,7 @@ func (c *MIoTClient) UnsubProp(did string, subID string) { // TODO: Implement proper tracking of per-device subscription count // For now, just log - c.lgr.Debugf("[MIoTClient] Unsubscribed from property: %s (subID=%s)\n", did, subID) + logrus.Debugf("[MIoTClient] Unsubscribed from property: %s (subID=%s)\n", did, subID) } // SubEvent subscribes to device events. @@ -69,7 +71,7 @@ func (c *MIoTClient) SubEvent(did string, handler func(map[string]interface{}, i subID := fmt.Sprintf("%p", handler) c.subTree.Sub(topic, handler, nil) - c.lgr.Debugf("[MIoTClient] Subscribed to event: %s (topic=%s, subID=%s)\n", did, topic, subID) + logrus.Debugf("[MIoTClient] Subscribed to event: %s (topic=%s, subID=%s)\n", did, topic, subID) return subID } @@ -83,7 +85,7 @@ func (c *MIoTClient) UnsubEvent(did string, subID string) { // Remove from subTree by subID c.subTree.Unsub(subID) - c.lgr.Debugf("[MIoTClient] Unsubscribed from event: %s (subID=%s)\n", did, subID) + logrus.Debugf("[MIoTClient] Unsubscribed from event: %s (subID=%s)\n", did, subID) } // updateDeviceMsgSub updates device message subscription. @@ -155,7 +157,7 @@ func (c *MIoTClient) updateDeviceMsgSub(did string) { // Update subscription source c.subSourceList[did] = newSource - c.lgr.Debugf("[MIoTClient] updateDeviceMsgSub: %s source %s → %s (gw=%v/%v, lan=%v/%v, cloud=%v)\n", + logrus.Debugf("[MIoTClient] updateDeviceMsgSub: %s source %s → %s (gw=%v/%v, lan=%v/%v, cloud=%v)\n", did, oldSource, newSource, gwOnline, gwPushAvailable, lanOnline, lanPushAvailable, cloudOnline) } @@ -260,13 +262,13 @@ func (c *MIoTClient) handleMIPSMessage(did string, payload []byte) { // Fallback: binary MIPS protocol (used by gateway mipsLocal) msg := UnpackMipsMessage(payload) if msg == nil || msg.Payload == "" { - c.lgr.Debugf("[MIoTClient] handleMIPSMessage: empty or invalid payload for %s (%d bytes)\n", did, len(payload)) + logrus.Debugf("[MIoTClient] handleMIPSMessage: empty or invalid payload for %s (%d bytes)\n", did, len(payload)) return } // Parse JSON from unpacked payload if err := json.Unmarshal([]byte(msg.Payload), ¶ms); err != nil { - c.lgr.Errorf("[MIoTClient] handleMIPSMessage: JSON parse error for %s: %v\n", did, err) + logrus.Errorf("[MIoTClient] handleMIPSMessage: JSON parse error for %s: %v\n", did, err) return } @@ -289,7 +291,7 @@ func (c *MIoTClient) dispatchMIPSMessage(did string, params map[string]interface } else if _, ok := params["eiid"]; ok { c.OnEventMsg(params, nil) } else { - c.lgr.Debugf("[MIoTClient] handleMIPSMessage: unknown message format for %s: %v\n", did, params) + logrus.Debugf("[MIoTClient] handleMIPSMessage: unknown message format for %s: %v\n", did, params) } } @@ -301,11 +303,11 @@ func (c *MIoTClient) handleMIPSStateMessage(did string, payload []byte) { // Fallback: binary MIPS protocol msg := UnpackMipsMessage(payload) if msg == nil || msg.Payload == "" { - c.lgr.Debugf("[MIoTClient] handleMIPSStateMessage: empty payload for %s (%d bytes)\n", did, len(payload)) + logrus.Debugf("[MIoTClient] handleMIPSStateMessage: empty payload for %s (%d bytes)\n", did, len(payload)) return } if err := json.Unmarshal([]byte(msg.Payload), ¶ms); err != nil { - c.lgr.Errorf("[MIoTClient] handleMIPSStateMessage: JSON parse error for %s: %v\n", did, err) + logrus.Errorf("[MIoTClient] handleMIPSStateMessage: JSON parse error for %s: %v\n", did, err) return } } @@ -326,7 +328,7 @@ func (c *MIoTClient) handleMIPSStateMessage(did string, payload []byte) { } if len(state) == 0 { - c.lgr.Debugf("[MIoTClient] handleMIPSStateMessage: no state info in payload for %s: %v\n", did, params) + logrus.Debugf("[MIoTClient] handleMIPSStateMessage: no state info in payload for %s: %v\n", did, params) return } @@ -372,7 +374,7 @@ func (c *MIoTClient) OnPropMsg(params map[string]interface{}, ctx interface{}) { } if did == "" { - c.lgr.Debugf("[MIoTClient] OnPropMsg: missing did in params=%v\n", params) + logrus.Debugf("[MIoTClient] OnPropMsg: missing did in params=%v\n", params) return } @@ -424,7 +426,7 @@ func (c *MIoTClient) OnEventMsg(params map[string]interface{}, ctx interface{}) } if did == "" { - c.lgr.Debugf("[MIoTClient] OnEventMsg: missing did in params=%v\n", params) + logrus.Debugf("[MIoTClient] OnEventMsg: missing did in params=%v\n", params) return } diff --git a/miot/miot_cloud.go b/miot/miot_cloud.go index 3ce1f2c..0d3f78c 100644 --- a/miot/miot_cloud.go +++ b/miot/miot_cloud.go @@ -248,10 +248,10 @@ func (c *MIoTHttpClient) UpdateHTTPHeader(cloudServer, clientID, accessToken str // Aligns with Python: __api_request_headers property. func (c *MIoTHttpClient) apiRequestHeaders() map[string]string { return map[string]string{ - "Host": c.host, + "Host": c.host, "X-Client-BizId": "haapi", - "Content-Type": "application/json", - "Authorization": "Bearer" + c.accessToken, + "Content-Type": "application/json", + "Authorization": "Bearer" + c.accessToken, "X-Client-AppId": c.clientID, } } @@ -514,11 +514,11 @@ func (c *MIoTHttpClient) getDevRoomPage(maxID string) (map[string]map[string]int // Aligns with Python: get_homeinfos_async(). func (c *MIoTHttpClient) GetHomeInfos() (map[string]interface{}, error) { resObj, err := c.mihomeAPIPost("/app/v2/homeroom/gethome", map[string]interface{}{ - "limit": float64(150), - "fetch_share": true, + "limit": float64(150), + "fetch_share": true, "fetch_share_dev": true, - "plat_form": float64(0), - "app_ver": float64(9), + "plat_form": float64(0), + "app_ver": float64(9), }) if err != nil { return nil, err @@ -531,7 +531,7 @@ func (c *MIoTHttpClient) GetHomeInfos() (map[string]interface{}, error) { var uid string homeInfos := map[string]map[string]interface{}{ - "homelist": {}, + "homelist": {}, "share_home_list": {}, } @@ -567,16 +567,16 @@ func (c *MIoTHttpClient) GetHomeInfos() (map[string]interface{}, error) { } homeInfos[deviceSource][hid] = map[string]interface{}{ - "home_id": hid, - "home_name": h["name"], - "city_id": h["city_id"], - "longitude": h["longitude"], - "latitude": h["latitude"], - "address": h["address"], - "dids": toStringSlice(h["dids"]), - "room_info": roomInfo, - "group_id": CalcGroupID(huid, hid), - "uid": huid, + "home_id": hid, + "home_name": h["name"], + "city_id": h["city_id"], + "longitude": h["longitude"], + "latitude": h["latitude"], + "address": h["address"], + "dids": toStringSlice(h["dids"]), + "room_info": roomInfo, + "group_id": CalcGroupID(huid, hid), + "uid": huid, } } } @@ -701,26 +701,26 @@ func (c *MIoTHttpClient) getDeviceListPage(dids []string, startDID string) (map[ } deviceInfos[did] = map[string]interface{}{ - "did": did, - "uid": device["uid"], - "name": name, - "urn": urn, - "model": model, - "connect_type": device["pid"], - "token": device["token"], - "online": device["isOnline"], - "icon": device["icon"], - "parent_id": device["parent_id"], - "manufacturer": strings.Split(model, ".")[0], - "voice_ctrl": device["voice_ctrl"], - "rssi": device["rssi"], - "owner": device["owner"], - "pid": device["pid"], - "local_ip": device["local_ip"], - "ssid": device["ssid"], - "bssid": device["bssid"], - "order_time": device["orderTime"], - "fw_version": fwVersion, + "did": did, + "uid": device["uid"], + "name": name, + "urn": urn, + "model": model, + "connect_type": device["pid"], + "token": device["token"], + "online": device["isOnline"], + "icon": device["icon"], + "parent_id": device["parent_id"], + "manufacturer": strings.Split(model, ".")[0], + "voice_ctrl": device["voice_ctrl"], + "rssi": device["rssi"], + "owner": device["owner"], + "pid": device["pid"], + "local_ip": device["local_ip"], + "ssid": device["ssid"], + "bssid": device["bssid"], + "order_time": device["orderTime"], + "fw_version": fwVersion, } } diff --git a/miot/miot_device.go b/miot/miot_device.go index 5cd21d6..100d256 100644 --- a/miot/miot_device.go +++ b/miot/miot_device.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -53,12 +53,9 @@ type MIoTDevice struct { // Subscription lists (aligned with Python _device_state_sub_list / _value_sub_list) deviceStateSubList map[string]map[string]func(string, interface{}) - valueSubList map[string]map[string]func(map[string]interface{}, interface{}) - subID int - subIDMu sync.Mutex - - // === Logger === - lgr logger.Logger + valueSubList map[string]map[string]func(map[string]interface{}, interface{}) + subID int + subIDMu sync.Mutex } // NewMIoTDevice creates a new MIoTDevice. @@ -68,12 +65,11 @@ type MIoTDevice struct { // specInstance: SPEC instance (can be nil, lazy loaded) func NewMIoTDevice(miotClient *MIoTClient, deviceInfo map[string]interface{}, specInstance *MIoTSpecInstance) *MIoTDevice { dev := &MIoTDevice{ - miotClient: miotClient, - specInstance: specInstance, - deviceStateSubList: make(map[string]map[string]func(string, interface{})), - valueSubList: make(map[string]map[string]func(map[string]interface{}, interface{})), - subDevices: make(map[int]*MIoTDevice), - lgr: logger.Default(), + miotClient: miotClient, + specInstance: specInstance, + deviceStateSubList: make(map[string]map[string]func(string, interface{})), + valueSubList: make(map[string]map[string]func(map[string]interface{}, interface{})), + subDevices: make(map[int]*MIoTDevice), } if specInstance != nil { @@ -140,9 +136,6 @@ func NewMIoTDevice(miotClient *MIoTClient, deviceInfo map[string]interface{}, sp return dev } -// SetLogger sets a custom logger for MIoTDevice. -func (d *MIoTDevice) SetLogger(l logger.Logger) { d.lgr = l } - // LoadFromCloud creates a MIoTDevice from cloud device data. // Aligns with Python: populating device_info from cloud response. func LoadFromCloud(miotClient *MIoTClient, cloudData map[string]interface{}) *MIoTDevice { @@ -206,7 +199,7 @@ func (d *MIoTDevice) SetSpecInstance(specInstance *MIoTSpecInstance) { // Returns: subscription ID for unsubscribing. func (d *MIoTDevice) SubProp(siid, piid int, handler func(map[string]interface{}, interface{})) string { if d.miotClient == nil { - d.lgr.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot subscribe to property\n") + logrus.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot subscribe to property\n") return "" } @@ -229,7 +222,7 @@ func (d *MIoTDevice) SubProp(siid, piid int, handler func(map[string]interface{} // Aligns with Python MIoTDevice.unsub_prop. func (d *MIoTDevice) UnsubProp(subID string) { if d.miotClient == nil { - d.lgr.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot unsubscribe from property\n") + logrus.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot unsubscribe from property\n") return } @@ -255,7 +248,7 @@ func (d *MIoTDevice) UnsubProp(subID string) { // Returns: subscription ID for unsubscribing. func (d *MIoTDevice) SubEvent(siid, eiid int, handler func(map[string]interface{}, interface{})) string { if d.miotClient == nil { - d.lgr.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot subscribe to event\n") + logrus.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot subscribe to event\n") return "" } @@ -278,7 +271,7 @@ func (d *MIoTDevice) SubEvent(siid, eiid int, handler func(map[string]interface{ // Aligns with Python MIoTDevice.unsub_event. func (d *MIoTDevice) UnsubEvent(subID string) { if d.miotClient == nil { - d.lgr.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot unsubscribe from event\n") + logrus.Warnf("[MIoTDevice] Warning: miotClient is nil, cannot unsubscribe from event\n") return } diff --git a/miot/miot_error.go b/miot/miot_error.go index 239c9ad..3a3f0e7 100644 --- a/miot/miot_error.go +++ b/miot/miot_error.go @@ -10,13 +10,13 @@ import "fmt" const ( // Base error codes - CodeUnknown = -10000 - CodeUnavailable = -10001 - CodeInvalidParams = -10002 - CodeResourceError = -10003 - CodeInternalError = -10004 + CodeUnknown = -10000 + CodeUnavailable = -10001 + CodeInvalidParams = -10002 + CodeResourceError = -10003 + CodeInternalError = -10004 CodeUnauthorizedAccess = -10005 - CodeTimeout = -10006 + CodeTimeout = -10006 // OAuth error codes CodeOauthUnauthorized = -10020 diff --git a/miot/miot_errors.go b/miot/miot_errors.go index 6266b83..1b659dd 100644 --- a/miot/miot_errors.go +++ b/miot/miot_errors.go @@ -3,134 +3,134 @@ package miot // MIoT 云端错误码 // 来源: https://iot.mi.com/v2/new/doc/help-support/faq/spec/error-code const ( - ErrCodeOK = -702000000 // OK - ErrCodeAccept = -702010000 // accept - ErrCodeProcessing = -702022036 // 操作正在处理中 - ErrCodeBadRequest = -704000000 // 错误的请求 - ErrCodeBadBody = -704000001 // 错误的请求体 - ErrCodeScheduleOnlyMi = -704001000 // 定时只支持小米设备 - ErrCodeDeviceError = -704002000 // 设备错误(通用) - ErrCodeUnauthenticated = -704010000 // 未认证 - ErrCodeTokenExpired = -704012901 // token 不存在或过期 - ErrCodeTokenInvalid = -704012902 // token 非法 - ErrCodeAuthExpired = -704012903 // 授权过期 - ErrCodeNotAuthToXiaoAi = -704012904 // 设备未授权控制能力给小爱 - ErrCodeDeviceUnbound = -704012905 // 设备未绑定 - ErrCodeAuthFailed = -704012906 // 认证失败 - ErrCodeIRNotSupported = -704013101 // 红外设备不支持此操作 - ErrCodePropertyNotRead = -704030013 // 属性不可读 - ErrCodePropertyNotWrite = -704030023 // 属性不可写 - ErrCodePropertyNotNotify = -704030033 // 属性不可上报 - ErrCodeTooFrequent = -704030992 // 请求过于频繁 - ErrCodeServiceNotFound = -704040002 // 服务不存在 - ErrCodePropertyNotFound = -704040003 // 属性不存在 - ErrCodeEventNotFound = -704040004 // 事件不存在 - ErrCodeMethodNotFound = -704040005 // 方法不存在 - ErrCodeFeatureOffline = -704040999 // 功能未上线 - ErrCodeCloudNotFound = -704041007 // cloud not found - ErrCodeDeviceNotFound2 = -704042001 // 未找到设备 - ErrCodeSceneNotFound = -704042009 // 未找到场景 - ErrCodeSceneError = -704042010 // 触发场景异常 - ErrCodeDeviceOffline = -704042011 // 设备离线 - ErrCodeSceneNoPermission = -704042012 // 场景无权限 - ErrCodeSpecNotFound = -704044006 // 未找到功能定义 - ErrCodeCannotExecute = -704053100 // 无法执行此操作 - ErrCodeCameraSleeping = -704053101 // 摄像机休眠中 - ErrCodeTimeout = -704083036 // 操作超时 - ErrCodeDeviceNotFound = -704090001 // 未找到设备 - ErrCodeInvalidID = -704220008 // 非法的 ID - ErrCodeInvalidUID = -704220009 // 非法的 uid - ErrCodeInvalidSubID = -704220010 // 非法的订阅 ID - ErrCodeActionParamCount = -704220025 // 方法输入参数数量不匹配 - ErrCodeActionParamError = -704220035 // 方法输入参数错误 - ErrCodeInvalidPropValue = -704220043 // 属性值不正确 - ErrCodeEventParamCount = -704222034 // 事件参数数量不匹配 - ErrCodeActionOutParamError = -704222035 // 方法输出参数错误 - ErrCodeOpenPlatformError = -705001000 // 开放平台服务器内部错误 - ErrCodeSpecServerError = -705004000 // MIoT Spec 服务器内部错误 - ErrCodeSubIDEncodeError = -705005000 // 订阅 ID 编码错误 - ErrCodeNotifyError = -705006000 // notify error - ErrCodeReadPropFailed = -705201013 // 读属性失败 - ErrCodeActionFailed = -705201015 // 方法执行失败 - ErrCodeWritePropFailed = -705201023 // 写属性失败 - ErrCodeReportPropFailed = -705201033 // 上报属性失败 - ErrCodeInvalidSpec = -705204006 // 非法的功能定义 - ErrCodeInstanceNotLoaded = -705204007 // 实例未加载 - ErrCodeRemoteServiceError = -706010002 // 远程服务异常 - ErrCodeMIoTError = -706010004 // MIoT 错误 - ErrCodePropCacheFailed = -706010005 // 属性缓存失败 - ErrCodeThirdPartyError = -706012000 // 三方云服务器内部错误 - ErrCodeThirdReadPropFailed = -706012013 // 三方云读属性失败 - ErrCodeThirdActionFailed = -706012015 // 三方云方法执行失败 - ErrCodeThirdWritePropFailed = -706012023 // 三方云写属性失败 + ErrCodeOK = -702000000 // OK + ErrCodeAccept = -702010000 // accept + ErrCodeProcessing = -702022036 // 操作正在处理中 + ErrCodeBadRequest = -704000000 // 错误的请求 + ErrCodeBadBody = -704000001 // 错误的请求体 + ErrCodeScheduleOnlyMi = -704001000 // 定时只支持小米设备 + ErrCodeDeviceError = -704002000 // 设备错误(通用) + ErrCodeUnauthenticated = -704010000 // 未认证 + ErrCodeTokenExpired = -704012901 // token 不存在或过期 + ErrCodeTokenInvalid = -704012902 // token 非法 + ErrCodeAuthExpired = -704012903 // 授权过期 + ErrCodeNotAuthToXiaoAi = -704012904 // 设备未授权控制能力给小爱 + ErrCodeDeviceUnbound = -704012905 // 设备未绑定 + ErrCodeAuthFailed = -704012906 // 认证失败 + ErrCodeIRNotSupported = -704013101 // 红外设备不支持此操作 + ErrCodePropertyNotRead = -704030013 // 属性不可读 + ErrCodePropertyNotWrite = -704030023 // 属性不可写 + ErrCodePropertyNotNotify = -704030033 // 属性不可上报 + ErrCodeTooFrequent = -704030992 // 请求过于频繁 + ErrCodeServiceNotFound = -704040002 // 服务不存在 + ErrCodePropertyNotFound = -704040003 // 属性不存在 + ErrCodeEventNotFound = -704040004 // 事件不存在 + ErrCodeMethodNotFound = -704040005 // 方法不存在 + ErrCodeFeatureOffline = -704040999 // 功能未上线 + ErrCodeCloudNotFound = -704041007 // cloud not found + ErrCodeDeviceNotFound2 = -704042001 // 未找到设备 + ErrCodeSceneNotFound = -704042009 // 未找到场景 + ErrCodeSceneError = -704042010 // 触发场景异常 + ErrCodeDeviceOffline = -704042011 // 设备离线 + ErrCodeSceneNoPermission = -704042012 // 场景无权限 + ErrCodeSpecNotFound = -704044006 // 未找到功能定义 + ErrCodeCannotExecute = -704053100 // 无法执行此操作 + ErrCodeCameraSleeping = -704053101 // 摄像机休眠中 + ErrCodeTimeout = -704083036 // 操作超时 + ErrCodeDeviceNotFound = -704090001 // 未找到设备 + ErrCodeInvalidID = -704220008 // 非法的 ID + ErrCodeInvalidUID = -704220009 // 非法的 uid + ErrCodeInvalidSubID = -704220010 // 非法的订阅 ID + ErrCodeActionParamCount = -704220025 // 方法输入参数数量不匹配 + ErrCodeActionParamError = -704220035 // 方法输入参数错误 + ErrCodeInvalidPropValue = -704220043 // 属性值不正确 + ErrCodeEventParamCount = -704222034 // 事件参数数量不匹配 + ErrCodeActionOutParamError = -704222035 // 方法输出参数错误 + ErrCodeOpenPlatformError = -705001000 // 开放平台服务器内部错误 + ErrCodeSpecServerError = -705004000 // MIoT Spec 服务器内部错误 + ErrCodeSubIDEncodeError = -705005000 // 订阅 ID 编码错误 + ErrCodeNotifyError = -705006000 // notify error + ErrCodeReadPropFailed = -705201013 // 读属性失败 + ErrCodeActionFailed = -705201015 // 方法执行失败 + ErrCodeWritePropFailed = -705201023 // 写属性失败 + ErrCodeReportPropFailed = -705201033 // 上报属性失败 + ErrCodeInvalidSpec = -705204006 // 非法的功能定义 + ErrCodeInstanceNotLoaded = -705204007 // 实例未加载 + ErrCodeRemoteServiceError = -706010002 // 远程服务异常 + ErrCodeMIoTError = -706010004 // MIoT 错误 + ErrCodePropCacheFailed = -706010005 // 属性缓存失败 + ErrCodeThirdPartyError = -706012000 // 三方云服务器内部错误 + ErrCodeThirdReadPropFailed = -706012013 // 三方云读属性失败 + ErrCodeThirdActionFailed = -706012015 // 三方云方法执行失败 + ErrCodeThirdWritePropFailed = -706012023 // 三方云写属性失败 ErrCodeThirdReportPropFailed = -706012033 // 三方云上报属性失败 - ErrCodeThirdSubFailed = -706012043 // 三方云订阅失败 + ErrCodeThirdSubFailed = -706012043 // 三方云订阅失败 ) // errDescriptions 错误码说明 var errDescriptions = map[int]string{ - ErrCodeOK: "OK", - ErrCodeAccept: "accept", - ErrCodeProcessing: "操作正在处理中", - ErrCodeBadRequest: "错误的请求", - ErrCodeBadBody: "错误的请求体", - ErrCodeScheduleOnlyMi: "定时只支持小米设备", - ErrCodeDeviceError: "设备错误(通用)", - ErrCodeUnauthenticated: "未认证", - ErrCodeTokenExpired: "token 不存在或过期", - ErrCodeTokenInvalid: "token 非法", - ErrCodeAuthExpired: "授权过期", - ErrCodeNotAuthToXiaoAi: "设备未授权控制能力给小爱", - ErrCodeDeviceUnbound: "设备未绑定", - ErrCodeAuthFailed: "认证失败", - ErrCodeIRNotSupported: "红外设备不支持此操作", - ErrCodePropertyNotRead: "属性不可读", - ErrCodePropertyNotWrite: "属性不可写", - ErrCodePropertyNotNotify: "属性不可上报", - ErrCodeTooFrequent: "请求过于频繁", - ErrCodeServiceNotFound: "服务不存在", - ErrCodePropertyNotFound: "属性不存在", - ErrCodeEventNotFound: "事件不存在", - ErrCodeMethodNotFound: "方法不存在", - ErrCodeFeatureOffline: "功能未上线", - ErrCodeCloudNotFound: "cloud not found", - ErrCodeDeviceNotFound2: "未找到设备", - ErrCodeSceneNotFound: "未找到场景", - ErrCodeSceneError: "触发场景异常", - ErrCodeDeviceOffline: "设备离线", - ErrCodeSceneNoPermission: "场景无权限", - ErrCodeSpecNotFound: "未找到功能定义", - ErrCodeCannotExecute: "无法执行此操作", - ErrCodeCameraSleeping: "摄像机休眠中", - ErrCodeTimeout: "操作超时", - ErrCodeDeviceNotFound: "未找到设备", - ErrCodeInvalidID: "非法的 ID", - ErrCodeInvalidUID: "非法的 uid", - ErrCodeInvalidSubID: "非法的订阅 ID", - ErrCodeActionParamCount: "方法输入参数数量不匹配", - ErrCodeActionParamError: "方法输入参数错误", - ErrCodeInvalidPropValue: "属性值不正确", - ErrCodeEventParamCount: "事件参数数量不匹配", - ErrCodeActionOutParamError: "方法输出参数错误", - ErrCodeOpenPlatformError: "开放平台服务器内部错误", - ErrCodeSpecServerError: "MIoT Spec 服务器内部错误", - ErrCodeSubIDEncodeError: "订阅 ID 编码错误", - ErrCodeNotifyError: "notify error", - ErrCodeReadPropFailed: "读属性失败", - ErrCodeActionFailed: "方法执行失败", - ErrCodeWritePropFailed: "写属性失败", - ErrCodeReportPropFailed: "上报属性失败", - ErrCodeInvalidSpec: "非法的功能定义", - ErrCodeInstanceNotLoaded: "实例未加载", - ErrCodeRemoteServiceError: "远程服务异常", - ErrCodeMIoTError: "MIoT 错误", - ErrCodePropCacheFailed: "属性缓存失败", - ErrCodeThirdPartyError: "三方云服务器内部错误", - ErrCodeThirdReadPropFailed: "三方云读属性失败", - ErrCodeThirdActionFailed: "三方云方法执行失败", - ErrCodeThirdWritePropFailed: "三方云写属性失败", + ErrCodeOK: "OK", + ErrCodeAccept: "accept", + ErrCodeProcessing: "操作正在处理中", + ErrCodeBadRequest: "错误的请求", + ErrCodeBadBody: "错误的请求体", + ErrCodeScheduleOnlyMi: "定时只支持小米设备", + ErrCodeDeviceError: "设备错误(通用)", + ErrCodeUnauthenticated: "未认证", + ErrCodeTokenExpired: "token 不存在或过期", + ErrCodeTokenInvalid: "token 非法", + ErrCodeAuthExpired: "授权过期", + ErrCodeNotAuthToXiaoAi: "设备未授权控制能力给小爱", + ErrCodeDeviceUnbound: "设备未绑定", + ErrCodeAuthFailed: "认证失败", + ErrCodeIRNotSupported: "红外设备不支持此操作", + ErrCodePropertyNotRead: "属性不可读", + ErrCodePropertyNotWrite: "属性不可写", + ErrCodePropertyNotNotify: "属性不可上报", + ErrCodeTooFrequent: "请求过于频繁", + ErrCodeServiceNotFound: "服务不存在", + ErrCodePropertyNotFound: "属性不存在", + ErrCodeEventNotFound: "事件不存在", + ErrCodeMethodNotFound: "方法不存在", + ErrCodeFeatureOffline: "功能未上线", + ErrCodeCloudNotFound: "cloud not found", + ErrCodeDeviceNotFound2: "未找到设备", + ErrCodeSceneNotFound: "未找到场景", + ErrCodeSceneError: "触发场景异常", + ErrCodeDeviceOffline: "设备离线", + ErrCodeSceneNoPermission: "场景无权限", + ErrCodeSpecNotFound: "未找到功能定义", + ErrCodeCannotExecute: "无法执行此操作", + ErrCodeCameraSleeping: "摄像机休眠中", + ErrCodeTimeout: "操作超时", + ErrCodeDeviceNotFound: "未找到设备", + ErrCodeInvalidID: "非法的 ID", + ErrCodeInvalidUID: "非法的 uid", + ErrCodeInvalidSubID: "非法的订阅 ID", + ErrCodeActionParamCount: "方法输入参数数量不匹配", + ErrCodeActionParamError: "方法输入参数错误", + ErrCodeInvalidPropValue: "属性值不正确", + ErrCodeEventParamCount: "事件参数数量不匹配", + ErrCodeActionOutParamError: "方法输出参数错误", + ErrCodeOpenPlatformError: "开放平台服务器内部错误", + ErrCodeSpecServerError: "MIoT Spec 服务器内部错误", + ErrCodeSubIDEncodeError: "订阅 ID 编码错误", + ErrCodeNotifyError: "notify error", + ErrCodeReadPropFailed: "读属性失败", + ErrCodeActionFailed: "方法执行失败", + ErrCodeWritePropFailed: "写属性失败", + ErrCodeReportPropFailed: "上报属性失败", + ErrCodeInvalidSpec: "非法的功能定义", + ErrCodeInstanceNotLoaded: "实例未加载", + ErrCodeRemoteServiceError: "远程服务异常", + ErrCodeMIoTError: "MIoT 错误", + ErrCodePropCacheFailed: "属性缓存失败", + ErrCodeThirdPartyError: "三方云服务器内部错误", + ErrCodeThirdReadPropFailed: "三方云读属性失败", + ErrCodeThirdActionFailed: "三方云方法执行失败", + ErrCodeThirdWritePropFailed: "三方云写属性失败", ErrCodeThirdReportPropFailed: "三方云上报属性失败", - ErrCodeThirdSubFailed: "三方云订阅失败", + ErrCodeThirdSubFailed: "三方云订阅失败", } // ErrDesc 返回错误码的中文说明。 diff --git a/miot/miot_i18n.go b/miot/miot_i18n.go index 510dfac..ef467fc 100644 --- a/miot/miot_i18n.go +++ b/miot/miot_i18n.go @@ -3,8 +3,9 @@ package miot import ( - "log" "strings" + + "github.com/sirupsen/logrus" ) // MIoTI18n provides internationalization translation. @@ -33,11 +34,11 @@ func (i *MIoTI18n) Init() { data, err := LoadJSONFile(GenAbsolutePath("i18n/" + i.lang + ".json")) if err != nil { - log.Printf("load i18n file error: %v", err) + logrus.Errorf("load i18n file error: %v", err) return } if data == nil { - log.Printf("invalid i18n file: nil data") + logrus.Errorf("invalid i18n file: nil data") return } i.data = data diff --git a/miot/miot_lan.go b/miot/miot_lan.go index f9a35f1..5169d68 100644 --- a/miot/miot_lan.go +++ b/miot/miot_lan.go @@ -19,7 +19,7 @@ import ( "sync/atomic" "time" - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -27,23 +27,23 @@ import ( // ============================================================================ const ( - otHeader = 0x2131 - otHeaderLen = 32 - otPort = 54321 - otProbeLen = 32 - otMsgLen = 1400 - otSupportWildcardSub = 0xFE + otHeader = 0x2131 + otHeaderLen = 32 + otPort = 54321 + otProbeLen = 32 + otMsgLen = 1400 + otSupportWildcardSub = 0xFE // Keep-alive intervals (seconds) - kaIntervalMin = 10.0 - kaIntervalMax = 50.0 - fastPingInterval = 5.0 + kaIntervalMin = 10.0 + kaIntervalMax = 50.0 + fastPingInterval = 5.0 constructStatePending = 15.0 // Network instability detection - networkUnstableCntTh = 10 - networkUnstableTimeTh = 120.0 - networkUnstableResumeTh = 300.0 + networkUnstableCntTh = 10 + networkUnstableTimeTh = 120.0 + networkUnstableResumeTh = 300.0 // Scan intervals (seconds) otProbeIntervalMin = 5.0 @@ -90,26 +90,26 @@ func (s LanDeviceState) String() string { // LanDevice manages the encrypted UDP communication and keep-alive for one // MIoT SPEC-v2 WiFi device on the local network. type LanDevice struct { - DID string - Token []byte - IP string - IfName string + DID string + Token []byte + IP string + IfName string - manager *MIoTLan - cipher cipher.Block - aesIV []byte + manager *MIoTLan + cipher cipher.Block + aesIV []byte - offset int - subscribed bool - subTS int + offset int + subscribed bool + subTS int supportedWildcardSub bool - subLocked bool + subLocked bool state LanDeviceState online bool - onlineHistory []onlineEvent - unstableTimer *time.Timer + onlineHistory []onlineEvent + unstableTimer *time.Timer kaTimer *time.Timer kaInterval float64 @@ -138,15 +138,15 @@ func newLanDevice(mgr *MIoTLan, did, tokenHex, ip string) (*LanDevice, error) { } d := &LanDevice{ - DID: did, - Token: token, - IP: ip, - manager: mgr, - cipher: block, - aesIV: aesIV[:], - state: LanDeviceDead, - online: false, - kaInterval: kaIntervalMin, + DID: did, + Token: token, + IP: ip, + manager: mgr, + cipher: block, + aesIV: aesIV[:], + state: LanDeviceDead, + online: false, + kaInterval: kaIntervalMin, onlineHistory: make([]onlineEvent, 0), } @@ -156,7 +156,7 @@ func newLanDevice(mgr *MIoTLan, did, tokenHex, ip string) (*LanDevice, error) { d.updateKeepAlive(LanDeviceDead) }) - mgr.lgr.Debugf("[lan] device added: did=%s", d.DID) + logrus.Debugf("[lan] device added: did=%s", d.DID) return d, nil } @@ -165,7 +165,7 @@ func (d *LanDevice) KeepAlive(ip, ifName string) { d.IP = ip if d.IfName != ifName { d.IfName = ifName - d.manager.lgr.Debugf("[lan] device if_name changed: %s, did=%s", d.IfName, d.DID) + logrus.Debugf("[lan] device if_name changed: %s, did=%s", d.IfName, d.DID) } d.updateKeepAlive(LanDeviceFresh) } @@ -316,12 +316,12 @@ func (d *LanDevice) Subscribe() { func (d *LanDevice) subscribeHandler(msg map[string]interface{}, subTS int) { result, _ := msg["result"].(map[string]interface{}) if result == nil { - d.manager.lgr.Warnf("[lan] subscribe error: no result, did=%s, msg=%v", d.DID, msg) + logrus.Warnf("[lan] subscribe error: no result, did=%s, msg=%v", d.DID, msg) return } code, _ := result["code"].(float64) if code != 0 { - d.manager.lgr.Warnf("[lan] subscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg) + logrus.Warnf("[lan] subscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg) return } d.subscribed = true @@ -330,7 +330,7 @@ func (d *LanDevice) subscribeHandler(msg map[string]interface{}, subTS int) { "online": d.online, "push_available": d.subscribed, }) - d.manager.lgr.Debugf("[lan] subscribe success: if=%s, did=%s", d.IfName, d.DID) + logrus.Debugf("[lan] subscribe success: if=%s, did=%s", d.IfName, d.DID) } // Unsubscribe sends a miIO.unsub request to the device. @@ -351,11 +351,11 @@ func (d *LanDevice) Unsubscribe() { result, _ := msg["result"].(map[string]interface{}) if result != nil { if code, _ := result["code"].(float64); code != 0 { - d.manager.lgr.Warnf("[lan] unsubscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg) + logrus.Warnf("[lan] unsubscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg) return } } - d.manager.lgr.Debugf("[lan] unsubscribe success: if=%s, did=%s", d.IfName, d.DID) + logrus.Debugf("[lan] unsubscribe success: if=%s, did=%s", d.IfName, d.DID) }, nil, 5000) d.subscribed = false d.manager.broadcastDeviceState(d.DID, map[string]interface{}{ @@ -374,7 +374,7 @@ func (d *LanDevice) OnDelete() { d.unstableTimer.Stop() d.unstableTimer = nil } - d.manager.lgr.Debugf("[lan] device deleted: did=%s", d.DID) + logrus.Debugf("[lan] device deleted: did=%s", d.DID) } // UpdateInfo updates the device token if it has changed. @@ -396,12 +396,12 @@ func (d *LanDevice) UpdateInfo(info map[string]interface{}) { aesIV := md5Sum(append(aesKey[:], d.Token...)) block, err := aes.NewCipher(aesKey[:]) if err != nil { - d.manager.lgr.Warnf("[lan] update token cipher error: %v, did=%s", err, d.DID) + logrus.Warnf("[lan] update token cipher error: %v, did=%s", err, d.DID) return } d.cipher = block d.aesIV = aesIV[:] - d.manager.lgr.Debugf("[lan] token updated: did=%s", d.DID) + logrus.Debugf("[lan] token updated: did=%s", d.DID) } // ============================================================================ @@ -413,7 +413,7 @@ func (d *LanDevice) updateKeepAlive(state LanDeviceState) { d.state = state if d.state != LanDeviceFresh { - d.manager.lgr.Debugf("[lan] device status: did=%s, state=%s", d.DID, d.state) + logrus.Debugf("[lan] device status: did=%s, state=%s", d.DID, d.state) } // Cancel existing timer @@ -441,11 +441,11 @@ func (d *LanDevice) updateKeepAlive(state LanDeviceState) { }) // Fast ping if d.IfName == "" { - d.manager.lgr.Debugf("[lan] if_name not set for device: did=%s", d.DID) + logrus.Debugf("[lan] if_name not set for device: did=%s", d.DID) return } if d.IP == "" { - d.manager.lgr.Debugf("[lan] ip not set for device: did=%s", d.DID) + logrus.Debugf("[lan] ip not set for device: did=%s", d.DID) return } d.manager.ping(d.IfName, d.IP) @@ -464,7 +464,7 @@ func (d *LanDevice) nextKATimeout() float64 { } func (d *LanDevice) changeOnline(online bool) { - d.manager.lgr.Debugf("[lan] change online: did=%s, online=%v", d.DID, online) + logrus.Debugf("[lan] change online: did=%s, online=%v", d.DID, online) tsNow := int(time.Now().Unix()) d.onlineHistory = append(d.onlineHistory, onlineEvent{ts: tsNow, online: online}) if len(d.onlineHistory) > networkUnstableCntTh { @@ -483,9 +483,9 @@ func (d *LanDevice) changeOnline(online bool) { float64(tsNow-d.onlineHistory[0].ts) > networkUnstableTimeTh { d.setOnline(true) } else { - d.manager.lgr.Debugf("[lan] unstable device detected: did=%s", d.DID) + logrus.Debugf("[lan] unstable device detected: did=%s", d.DID) d.unstableTimer = time.AfterFunc(time.Duration(networkUnstableResumeTh*float64(time.Second)), func() { - d.manager.lgr.Debugf("[lan] unstable resume threshold passed: did=%s", d.DID) + logrus.Debugf("[lan] unstable resume threshold passed: did=%s", d.DID) d.setOnline(true) }) } @@ -531,8 +531,8 @@ type MIoTLan struct { initMu sync.Mutex // Network state - availableIFs map[string]bool - scanTimer *time.Timer + availableIFs map[string]bool + scanTimer *time.Timer lastScanInterval float64 // LAN state votes @@ -541,8 +541,6 @@ type MIoTLan struct { // Profile models (loaded from YAML, models that should NOT use LAN control) profileModels map[string]bool - - lgr logger.Logger } type pendingRequest struct { @@ -604,10 +602,8 @@ func NewMIoTLan(netIFs []string, network *MIoTNetwork, mipsService *MipsService, lanStateSubs: make(map[string]func(bool)), lanCtrlVotes: make(map[string]bool), profileModels: make(map[string]bool), - lgr: logger.Default(), } - for _, ifName := range netIFs { m.netIFs[ifName] = true } @@ -620,7 +616,7 @@ func NewMIoTLan(netIFs []string, network *MIoTNetwork, mipsService *MipsService, // Auto-init if no central hub gateway exists and we have interfaces if len(mipsService.GetServices("")) == 0 && len(netIFs) > 0 { - m.lgr.Infof("[lan] no central hub gateway service, scheduling init") + logrus.Infof("[lan] no central hub gateway service, scheduling init") go func() { m.Init() }() @@ -629,9 +625,6 @@ func NewMIoTLan(netIFs []string, network *MIoTNetwork, mipsService *MipsService, return m, nil } -// SetLogger sets a custom logger for MIoTLan. -func (m *MIoTLan) SetLogger(l logger.Logger) { m.lgr = l } - // VirtualDID returns the virtual device ID used for LAN communication. func (m *MIoTLan) VirtualDID() string { return m.virtualDID @@ -656,12 +649,12 @@ func (m *MIoTLan) Init() { m.mu.Lock() if m.initDone { m.mu.Unlock() - m.lgr.Infof("[lan] already initialized") + logrus.Infof("[lan] already initialized") return } if len(m.netIFs) == 0 { m.mu.Unlock() - m.lgr.Infof("[lan] no net_ifs configured") + logrus.Infof("[lan] no net_ifs configured") return } @@ -675,13 +668,13 @@ func (m *MIoTLan) Init() { } if !hasVote { m.mu.Unlock() - m.lgr.Infof("[lan] no vote for lan ctrl") + logrus.Infof("[lan] no vote for lan ctrl") return } if len(m.mipsService.GetServices("")) > 0 { m.mu.Unlock() - m.lgr.Infof("[lan] central hub gateway service exists, skip LAN init") + logrus.Infof("[lan] central hub gateway service exists, skip LAN init") return } @@ -691,7 +684,7 @@ func (m *MIoTLan) Init() { } if len(m.availableIFs) == 0 { m.mu.Unlock() - m.lgr.Infof("[lan] no available network interfaces") + logrus.Infof("[lan] no available network interfaces") return } @@ -705,7 +698,7 @@ func (m *MIoTLan) Init() { } if !hasValid { m.mu.Unlock() - m.lgr.Infof("[lan] no valid net_ifs matching configured list") + logrus.Infof("[lan] no valid net_ifs matching configured list") return } @@ -729,7 +722,7 @@ func (m *MIoTLan) Init() { go h(true) } - m.lgr.Infof("[lan] initialized: netIFs=%v, available=%v", m.netIFs, m.availableIFs) + logrus.Infof("[lan] initialized: netIFs=%v, available=%v", m.netIFs, m.availableIFs) } // Deinit stops the LAN controller. @@ -740,7 +733,7 @@ func (m *MIoTLan) Deinit() { m.mu.Lock() if !m.initDone { m.mu.Unlock() - m.lgr.Infof("[lan] not initialized") + logrus.Infof("[lan] not initialized") return } m.initDone = false @@ -773,7 +766,7 @@ func (m *MIoTLan) Deinit() { go h(false) } - m.lgr.Infof("[lan] deinitialized") + logrus.Infof("[lan] deinitialized") } // ============================================================================ @@ -781,7 +774,7 @@ func (m *MIoTLan) Deinit() { // ============================================================================ func (m *MIoTLan) internalLoop() { - m.lgr.Infof("[lan] internal loop started") + logrus.Infof("[lan] internal loop started") m.initSockets() // Start scan timer @@ -794,7 +787,7 @@ func (m *MIoTLan) internalLoop() { if cmd == nil { // Stop signal m.deinitInternal() - m.lgr.Infof("[lan] internal loop stopped") + logrus.Infof("[lan] internal loop stopped") return } cmd() @@ -866,13 +859,13 @@ func (m *MIoTLan) createSocketLocked(ifName string) { ni := m.network.GetNetworkInfo() info, ok := ni[ifName] if !ok || info.IP == "" { - m.lgr.Infof("[lan] no IP for interface: %s", ifName) + logrus.Infof("[lan] no IP for interface: %s", ifName) return } conn, err := createBoundSocket(info.IP, m.localPort) if err != nil { - m.lgr.Warnf("[lan] create socket error: if=%s, err=%v", ifName, err) + logrus.Warnf("[lan] create socket error: if=%s, err=%v", ifName, err) return } @@ -885,7 +878,7 @@ func (m *MIoTLan) createSocketLocked(ifName string) { // Start reader goroutine for this socket go m.socketReader(ifName, conn) - m.lgr.Infof("[lan] socket created: if=%s, ip=%s, port=%d", ifName, info.IP, m.localPort) + logrus.Infof("[lan] socket created: if=%s, ip=%s, port=%d", ifName, info.IP, m.localPort) } func (m *MIoTLan) destroySocketLocked(ifName string) { @@ -895,7 +888,7 @@ func (m *MIoTLan) destroySocketLocked(ifName string) { } delete(m.socks, ifName) conn.Close() - m.lgr.Infof("[lan] socket destroyed: if=%s", ifName) + logrus.Infof("[lan] socket destroyed: if=%s", ifName) } // createBoundSocket creates a UDP socket bound to a specific IP address. @@ -944,7 +937,7 @@ func (m *MIoTLan) send(ifName, targetIP string, data []byte) { } else { conn, ok := m.socks[ifName] if !ok { - m.lgr.Debugf("[lan] invalid socket: if=%s", ifName) + logrus.Debugf("[lan] invalid socket: if=%s", ifName) return } conn.WriteToUDP(data, &net.UDPAddr{IP: net.ParseIP(targetIP), Port: otPort}) @@ -1118,7 +1111,7 @@ func (m *MIoTLan) handleRawMessage(data []byte, dataLen int, ip, ifName string) if dataLen > otProbeLen { msg, err := dev.DecryptPacket(data[:dataLen]) if err != nil { - m.lgr.Debugf("[lan] decrypt error: did=%s, err=%v", did, err) + logrus.Debugf("[lan] decrypt error: did=%s, err=%v", did, err) return } m.handleMessage(did, msg) @@ -1128,7 +1121,7 @@ func (m *MIoTLan) handleRawMessage(data []byte, dataLen int, ip, ifName string) func (m *MIoTLan) handleMessage(did string, msg map[string]interface{}) { msgID, ok := msg["id"] if !ok { - m.lgr.Debugf("[lan] message without id: did=%s, msg=%v", did, msg) + logrus.Debugf("[lan] message without id: did=%s, msg=%v", did, msg) return } id, ok := toInt(msgID) @@ -1169,7 +1162,7 @@ func (m *MIoTLan) handleMessage(did string, msg map[string]interface{}) { return } - m.lgr.Debugf("[lan] message: did=%s, method=%s", did, method) + logrus.Debugf("[lan] message: did=%s, method=%s", did, method) switch method { case "properties_changed": @@ -1264,7 +1257,7 @@ func (m *MIoTLan) scanDevices() { scanTime := m.nextScanTime() m.scanTimer = time.AfterFunc(time.Duration(scanTime*float64(time.Second)), m.scanDevices) - m.lgr.Debugf("[lan] next scan in %.0fs", scanTime) + logrus.Debugf("[lan] next scan in %.0fs", scanTime) } func (m *MIoTLan) nextScanTime() float64 { @@ -1280,7 +1273,7 @@ func (m *MIoTLan) nextScanTime() float64 { // ============================================================================ func (m *MIoTLan) onNetworkInfoChange(status InterfaceStatus, info *NetworkInfo) { - m.lgr.Infof("[lan] network info change: status=%s, if=%s", status, info.Name) + logrus.Infof("[lan] network info change: status=%s, if=%s", status, info.Name) m.mu.Lock() // Rebuild available IFs @@ -1304,7 +1297,7 @@ func (m *MIoTLan) onNetworkInfoChange(status InterfaceStatus, info *NetworkInfo) } if !valid { m.mu.Unlock() - m.lgr.Infof("[lan] no valid net_ifs after change") + logrus.Infof("[lan] no valid net_ifs after change") go m.Deinit() return } @@ -1340,12 +1333,12 @@ func (m *MIoTLan) onNetworkInfoChange(status InterfaceStatus, info *NetworkInfo) // ============================================================================ func (m *MIoTLan) onMipsServiceChange(groupID string, state MipsServiceState, data map[string]interface{}) { - m.lgr.Infof("[lan] mips service change: groupID=%s, state=%s, data=%v", groupID, state, data) + logrus.Infof("[lan] mips service change: groupID=%s, state=%s, data=%v", groupID, state, data) if len(m.mipsService.GetServices("")) > 0 { - m.lgr.Infof("[lan] central hub gateway found, deinit LAN") + logrus.Infof("[lan] central hub gateway found, deinit LAN") go m.Deinit() } else { - m.lgr.Infof("[lan] no central hub gateway, init LAN") + logrus.Infof("[lan] no central hub gateway, init LAN") go m.Init() } } @@ -1356,7 +1349,7 @@ func (m *MIoTLan) onMipsServiceChange(groupID string, state MipsServiceState, da // VoteForLanCtrl votes for or against LAN control. func (m *MIoTLan) VoteForLanCtrl(key string, vote bool) { - m.lgr.Infof("[lan] vote for lan ctrl: key=%s, vote=%v", key, vote) + logrus.Infof("[lan] vote for lan ctrl: key=%s, vote=%v", key, vote) m.mu.Lock() m.lanCtrlVotes[key] = vote hasVote := false @@ -1377,7 +1370,7 @@ func (m *MIoTLan) VoteForLanCtrl(key string, vote bool) { // UpdateSubscribeOption enables or disables device subscriptions. func (m *MIoTLan) UpdateSubscribeOption(enable bool) { - m.lgr.Infof("[lan] update subscribe option: %v", enable) + logrus.Infof("[lan] update subscribe option: %v", enable) m.mu.Lock() oldEnable := m.enableSub m.enableSub = enable @@ -1406,12 +1399,12 @@ func (m *MIoTLan) UpdateDevices(devices map[string]map[string]interface{}) { for did, info := range devices { if !isNumeric(did) { - m.lgr.Infof("[lan] invalid did (non-numeric): %s", did) + logrus.Infof("[lan] invalid did (non-numeric): %s", did) continue } model, _ := info["model"].(string) if model != "" && m.profileModels[model] { - m.lgr.Debugf("[lan] model not supported for LAN ctrl: did=%s, model=%s", did, model) + logrus.Debugf("[lan] model not supported for LAN ctrl: did=%s, model=%s", did, model) continue } if existing, ok := m.devices[did]; ok { @@ -1419,13 +1412,13 @@ func (m *MIoTLan) UpdateDevices(devices map[string]map[string]interface{}) { } else { tokenStr, _ := info["token"].(string) if len(tokenStr) != 32 { - m.lgr.Debugf("[lan] invalid token for device: did=%s", did) + logrus.Debugf("[lan] invalid token for device: did=%s", did) continue } ip, _ := info["ip"].(string) dev, err := newLanDevice(m, did, tokenStr, ip) if err != nil { - m.lgr.Warnf("[lan] create device error: did=%s, err=%v", did, err) + logrus.Warnf("[lan] create device error: did=%s, err=%v", did, err) continue } m.devices[did] = dev @@ -1491,7 +1484,7 @@ func (m *MIoTLan) SubProp(did string, handler func(map[string]interface{}, inter } m.cmdCh <- func() { m.matcher.Sub(key, handler, ctx) - m.lgr.Debugf("[lan] sub prop: key=%s", key) + logrus.Debugf("[lan] sub prop: key=%s", key) } } @@ -1503,7 +1496,7 @@ func (m *MIoTLan) UnsubProp(did string, siid, piid int) { } m.cmdCh <- func() { m.matcher.Unsub(key) - m.lgr.Debugf("[lan] unsub prop: key=%s", key) + logrus.Debugf("[lan] unsub prop: key=%s", key) } } @@ -1515,7 +1508,7 @@ func (m *MIoTLan) SubEvent(did string, handler func(map[string]interface{}, inte } m.cmdCh <- func() { m.matcher.Sub(key, handler, ctx) - m.lgr.Debugf("[lan] sub event: key=%s", key) + logrus.Debugf("[lan] sub event: key=%s", key) } } @@ -1527,7 +1520,7 @@ func (m *MIoTLan) UnsubEvent(did string, siid, eiid int) { } m.cmdCh <- func() { m.matcher.Unsub(key) - m.lgr.Debugf("[lan] unsub event: key=%s", key) + logrus.Debugf("[lan] unsub event: key=%s", key) } } diff --git a/miot/miot_mdns.go b/miot/miot_mdns.go index e40f14e..34ba474 100644 --- a/miot/miot_mdns.go +++ b/miot/miot_mdns.go @@ -12,8 +12,7 @@ import ( "time" "github.com/hashicorp/mdns" - - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -166,7 +165,6 @@ type MipsService struct { mu sync.Mutex services map[string]*MipsServiceData // keyed by groupID subs map[subKey]MipsServiceChangeHandler - lgr logger.Logger stopCh chan struct{} } @@ -182,14 +180,10 @@ func NewMipsService() *MipsService { s := &MipsService{ services: make(map[string]*MipsServiceData), subs: make(map[subKey]MipsServiceChangeHandler), - lgr: logger.Default(), } return s } -// SetLogger sets a custom logger for MipsService. -func (s *MipsService) SetLogger(l logger.Logger) { s.lgr = l } - // Init starts the mDNS browser in a background goroutine. // Safe to call multiple times; subsequent calls after the first are no-ops // unless preceded by Deinit(). @@ -203,7 +197,7 @@ func (s *MipsService) Init() { s.mu.Unlock() go s.runBrowser() - s.lgr.Infof("[mdns] mips service browser started") + logrus.Infof("[mdns] mips service browser started") } // Deinit stops the browser goroutine and clears all cached services and @@ -225,7 +219,7 @@ func (s *MipsService) Deinit() { s.subs = make(map[subKey]MipsServiceChangeHandler) s.mu.Unlock() - s.lgr.Infof("[mdns] mips service deinitialized") + logrus.Infof("[mdns] mips service deinitialized") } // GetServices returns cached services. When groupID is non-empty, only that @@ -289,7 +283,7 @@ func (s *MipsService) runBrowser() { // Run the blocking Query in a goroutine so we can detect stop. go func() { if err := mdns.Query(params); err != nil { - s.lgr.Debugf("[mdns] query error: %v", err) + logrus.Debugf("[mdns] query error: %v", err) } close(entriesCh) }() @@ -311,7 +305,7 @@ func (s *MipsService) runBrowser() { func (s *MipsService) handleEntry(entry *mdns.ServiceEntry) { svc, err := entryToServiceData(entry) if err != nil { - s.lgr.Debugf("[mdns] skip entry: %v", err) + logrus.Debugf("[mdns] skip entry: %v", err) return } if !svc.validService() { @@ -339,7 +333,7 @@ func (s *MipsService) handleEntry(entry *mdns.ServiceEntry) { // callSubsLocked notifies all subscribers whose groupID matches. // Caller MUST hold s.mu. func (s *MipsService) callSubsLocked(groupID string, state MipsServiceState, data map[string]interface{}) { - s.lgr.Infof("[mdns] service change: groupID=%s, state=%s", groupID, state) + logrus.Infof("[mdns] service change: groupID=%s, state=%s", groupID, state) for sk, handler := range s.subs { if sk.groupID == groupID || sk.groupID == "*" { go handler(groupID, state, data) diff --git a/miot/miot_network.go b/miot/miot_network.go index 74129f7..aaa66a2 100644 --- a/miot/miot_network.go +++ b/miot/miot_network.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -71,8 +71,6 @@ func (n *NetworkInfo) String() string { type MIoTNetwork struct { mu sync.Mutex - lgr logger.Logger - // Detection addresses with cached response times (seconds) ipAddrMap map[string]float64 httpAddrMap map[string]float64 @@ -99,9 +97,9 @@ type MIoTNetwork struct { // Default network detection addresses. var ( defaultIPAddrs = []string{ - "1.2.4.8", // CNNIC sDNS - "8.8.8.8", // Google Public DNS - "9.9.9.9", // Quad9 + "1.2.4.8", // CNNIC sDNS + "8.8.8.8", // Google Public DNS + "9.9.9.9", // Quad9 } defaultHTTPAddrs = []string{ "https://www.bing.com", @@ -126,7 +124,6 @@ func NewMIoTNetwork(ipAddrList, urlAddrList []string, refreshInterval time.Durat } n := &MIoTNetwork{ - lgr: logger.Default(), ipAddrMap: make(map[string]float64), httpAddrMap: make(map[string]float64), httpClient: &http.Client{Timeout: defaultDetectTimeout}, @@ -149,9 +146,6 @@ func NewMIoTNetwork(ipAddrList, urlAddrList []string, refreshInterval time.Durat return n } -// SetLogger sets a custom logger for MIoTNetwork. -func (n *MIoTNetwork) SetLogger(l logger.Logger) { n.lgr = l } - // Init starts the network monitor. func (n *MIoTNetwork) Init() { // Initial refresh @@ -530,7 +524,7 @@ func (n *MIoTNetwork) getNetworkInterfaces() map[string]*NetworkInfo { ifaces, err := net.Interfaces() if err != nil { - n.lgr.Warnf("[network] get interfaces error: %v", err) + logrus.Warnf("[network] get interfaces error: %v", err) return results } diff --git a/miot/miot_storage.go b/miot/miot_storage.go index fee4702..1b134d7 100644 --- a/miot/miot_storage.go +++ b/miot/miot_storage.go @@ -29,13 +29,13 @@ import ( type storageOpType int const ( - opLoad storageOpType = iota // MIoTStorageType.LOAD - opLoadFile // MIoTStorageType.LOAD_FILE - opSave // MIoTStorageType.SAVE - opSaveFile // MIoTStorageType.SAVE_FILE - opDelete // MIoTStorageType.DEL - opDeleteFile // MIoTStorageType.DEL_FILE - opClear // MIoTStorageType.CLEAR + opLoad storageOpType = iota // MIoTStorageType.LOAD + opLoadFile // MIoTStorageType.LOAD_FILE + opSave // MIoTStorageType.SAVE + opSaveFile // MIoTStorageType.SAVE_FILE + opDelete // MIoTStorageType.DEL + opDeleteFile // MIoTStorageType.DEL_FILE + opClear // MIoTStorageType.CLEAR ) // pendingOp tracks an inflight async file operation. @@ -57,7 +57,7 @@ type pendingOp struct { // - Concurrent-safe via sync.RWMutex type MIoTStorage struct { rootPath string - mu sync.Mutex // protects pendingOps + mu sync.Mutex // protects pendingOps pendingOps map[string]*pendingOp } diff --git a/miot/miot_test.go b/miot/miot_test.go index bd41dba..9df2175 100644 --- a/miot/miot_test.go +++ b/miot/miot_test.go @@ -144,8 +144,8 @@ func TestMIoTClient_OnPropMsg(t *testing.T) { params := map[string]interface{}{ "params": map[string]interface{}{ "did": "did1", - "siid": float64(2), - "piid": float64(1), + "siid": float64(2), + "piid": float64(1), "value": true, }, } diff --git a/miot/mips_client.go b/miot/mips_client.go index 750a10f..6f24557 100644 --- a/miot/mips_client.go +++ b/miot/mips_client.go @@ -13,8 +13,7 @@ import ( "time" mqtt "github.com/eclipse/paho.mqtt.golang" - - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -25,8 +24,8 @@ const ( mipsQoS = 2 mipsReconnectIntervalMin = 10.0 // seconds mipsReconnectIntervalMax = 600.0 // seconds - mipsSubInterval = 1 // second - mipsMQTTInterval = 1 // second + mipsSubInterval = 1 // second + mipsMQTTInterval = 1 // second uint32max = 0xFFFFFFFF MipsRequestTimeoutDefault = 10000 // milliseconds ) @@ -218,7 +217,6 @@ type MipsBroadcast struct { // mipsClient is the concrete MIPS client using paho MQTT. type mipsClient struct { mu sync.Mutex - lgr logger.Logger clientID string host string port int @@ -286,7 +284,6 @@ func newMipsClient(clientID, host string, port int, username, password, caFile, caFile: caFile, certFile: certFile, keyFile: keyFile, - lgr: logger.Default(), seedID: rand.Intn(uint32max), pendingSubs: make(map[string]int), subscriptions: make(map[string]*MipsBroadcast), @@ -299,9 +296,6 @@ func newMipsClient(clientID, host string, port int, username, password, caFile, return c } -// SetLogger sets a custom logger for mipsClient. -func (c *mipsClient) SetLogger(l logger.Logger) { c.lgr = l } - // connect starts the MQTT connection. func (c *mipsClient) connect(mqttClient MQTTClient) error { c.mu.Lock() @@ -369,7 +363,7 @@ func (c *mipsClient) subscribe(topic string, handler func(topic string, payload defer c.subMu.Unlock() if c.subscriptions[topic] != nil { - c.lgr.Debugf("[mips] re-register subscription: %s", topic) + logrus.Debugf("[mips] re-register subscription: %s", topic) } c.subscriptions[topic] = &MipsBroadcast{ Topic: topic, @@ -394,7 +388,7 @@ func (c *mipsClient) unsubscribe(topic string) error { if c.mqttConn != nil && c.mqttConn.IsConnected() { if err := c.mqttConn.Unsubscribe(topic); err != nil { - c.lgr.Debugf("[mips] unsubscribe error %s: %v", topic, err) + logrus.Debugf("[mips] unsubscribe error %s: %v", topic, err) } } return nil @@ -534,7 +528,7 @@ func (c *mipsClient) flushPendingSubs() { for topic, count := range c.pendingSubs { if count > 3 { delete(c.pendingSubs, topic) - c.lgr.Debugf("[mips] retry sub exceeded: %s", topic) + logrus.Debugf("[mips] retry sub exceeded: %s", topic) continue } batch[topic] = byte(mipsQoS) @@ -549,7 +543,7 @@ func (c *mipsClient) flushPendingSubs() { c.pendingSubMu.Lock() for t := range batch { delete(c.pendingSubs, t) - c.lgr.Debugf("[mips] sub success: %s", t) + logrus.Debugf("[mips] sub success: %s", t) } hasMore := len(c.pendingSubs) > 0 c.pendingSubMu.Unlock() @@ -558,7 +552,7 @@ func (c *mipsClient) flushPendingSubs() { c.flushPendingSubs() } } else { - c.lgr.Debugf("[mips] batch sub error: %v", err) + logrus.Debugf("[mips] batch sub error: %v", err) c.pendingSubMu.Lock() for t := range batch { c.pendingSubs[t] = c.pendingSubs[t] + 1 @@ -661,13 +655,13 @@ func (c *mipsClient) tryReconnect(immediate bool) { c.reconnectInterval = math.Min(c.reconnectInterval*2, mipsReconnectIntervalMax) } interval = time.Duration(c.reconnectInterval) * time.Second - c.lgr.Debugf("[mips] reconnect after %v", interval) + logrus.Debugf("[mips] reconnect after %v", interval) } c.reconnectTimer = time.AfterFunc(interval, func() { if c.mqttConn != nil { if err := c.mqttConn.Connect(); err != nil { - c.lgr.Debugf("[mips] reconnect failed: %v", err) + logrus.Debugf("[mips] reconnect failed: %v", err) c.tryReconnect(false) } } @@ -951,8 +945,6 @@ type pahoMQTTClient struct { certFile string keyFile string - lgr logger.Logger - onConnect func() onDisconnect func() onMessage func(topic string, payload []byte) @@ -971,14 +963,10 @@ func newPahoMQTTClient(clientID, host string, port int, username, password, caFi caFile: caFile, certFile: certFile, keyFile: keyFile, - lgr: logger.Default(), } return c } -// SetLogger sets a custom logger for pahoMQTTClient. -func (p *pahoMQTTClient) SetLogger(l logger.Logger) { p.lgr = l } - func (p *pahoMQTTClient) Connect() error { opts := mqtt.NewClientOptions() opts.AddBroker(fmt.Sprintf("ssl://%s:%d", p.host, p.port)) @@ -990,7 +978,7 @@ func (p *pahoMQTTClient) Connect() error { opts.SetCleanSession(true) opts.OnConnect = func(c mqtt.Client) { - p.lgr.Debugf("[mips] MQTT connected to %s:%d", p.host, p.port) + logrus.Debugf("[mips] MQTT connected to %s:%d", p.host, p.port) p.connected = true if p.onConnect != nil { p.onConnect() @@ -998,7 +986,7 @@ func (p *pahoMQTTClient) Connect() error { } opts.OnConnectionLost = func(c mqtt.Client, err error) { - p.lgr.Debugf("[mips] MQTT connection lost: %v", err) + logrus.Debugf("[mips] MQTT connection lost: %v", err) p.connected = false if p.onDisconnect != nil { p.onDisconnect() @@ -1006,7 +994,7 @@ func (p *pahoMQTTClient) Connect() error { } opts.DefaultPublishHandler = func(c mqtt.Client, msg mqtt.Message) { - p.lgr.Debugf("[mips] MQTT message: %s (%d bytes) data:%s", msg.Topic(), len(msg.Payload()), string(msg.Payload())) + logrus.Debugf("[mips] MQTT message: %s (%d bytes) data:%s", msg.Topic(), len(msg.Payload()), string(msg.Payload())) if p.onMessage != nil { p.onMessage(msg.Topic(), msg.Payload()) } @@ -1018,7 +1006,7 @@ func (p *pahoMQTTClient) Connect() error { return token.Error() } - p.lgr.Debugf("[mips] MQTT connected to %s:%d as %s", p.host, p.port, p.clientID) + logrus.Debugf("[mips] MQTT connected to %s:%d as %s", p.host, p.port, p.clientID) return nil } @@ -1075,7 +1063,7 @@ func (p *pahoMQTTClient) Unsubscribe(topic string) error { if token.Wait() && token.Error() != nil { return token.Error() } - p.lgr.Debugf("[mips] unsub success: %s", topic) + logrus.Debugf("[mips] unsub success: %s", topic) return nil } diff --git a/miot/spec_parser.go b/miot/spec_parser.go index dd35fc3..ddb6260 100644 --- a/miot/spec_parser.go +++ b/miot/spec_parser.go @@ -17,7 +17,7 @@ import ( "sync" "time" - "xiaomihome/logger" + "github.com/sirupsen/logrus" ) // ============================================================================ @@ -45,8 +45,8 @@ type MIoTSpecService struct { Type string Description string DescriptionTrans string - NeedFilter bool - Proprietary bool + NeedFilter bool + Proprietary bool Properties []*MIoTSpecProperty Actions []*MIoTSpecAction Events []*MIoTSpecEvent @@ -118,13 +118,12 @@ type MIoTSpecEvent struct { // MIoTSpecParser wraps SPEC parsing logic. type MIoTSpecParser struct { - lang string - storage *MIoTStorage - specDir string - stdLib *SpecStdLib - lgr logger.Logger - initDone bool - initMu sync.Mutex + lang string + storage *MIoTStorage + specDir string + stdLib *SpecStdLib + initDone bool + initMu sync.Mutex } // NewMIoTSpecParser creates a new MIoTSpecParser. @@ -137,14 +136,10 @@ func NewMIoTSpecParser(lang string, storage *MIoTStorage, specDir string) *MIoTS storage: storage, specDir: specDir, stdLib: NewSpecStdLib(lang), - lgr: logger.Default(), } return p } -// SetLogger sets a custom logger for MIoTSpecParser. -func (p *MIoTSpecParser) SetLogger(l logger.Logger) { p.lgr = l } - // Init initializes the parser, loading or refreshing the standard library. func (p *MIoTSpecParser) Init() error { p.initMu.Lock() @@ -168,7 +163,7 @@ func (p *MIoTSpecParser) Init() error { ts, _ = v.Int64() } if ts > 0 && time.Now().Unix()-ts < SPEC_STD_LIB_EFFECTIVE_TIME { - p.lgr.Debugf("[spec] using cached spec std lib, ts=%d", ts) + logrus.Debugf("[spec] using cached spec std lib, ts=%d", ts) p.stdLib.Load(dataMap) p.initDone = true return nil @@ -180,12 +175,12 @@ func (p *MIoTSpecParser) Init() error { // Refresh from cloud if err := p.stdLib.Refresh(); err != nil { - p.lgr.Debugf("[spec] refresh std lib failed: %v", err) + logrus.Debugf("[spec] refresh std lib failed: %v", err) // Try stale cache as fallback if cacheMap, ok := stdLibCache.(map[string]interface{}); ok { if dataField, ok := cacheMap["data"]; ok { if dataMap, ok := dataField.(map[string]interface{}); ok { - p.lgr.Debugf("[spec] using stale cached spec std lib") + logrus.Debugf("[spec] using stale cached spec std lib") p.stdLib.Load(dataMap) p.initDone = true return nil @@ -282,7 +277,7 @@ func (p *MIoTSpecParser) downloadSpecFile(urn, filePath string) error { return fmt.Errorf("write %s: %w", filePath, err) } - p.lgr.Debugf("[spec] downloaded spec: %s -> %s", urn, filePath) + logrus.Debugf("[spec] downloaded spec: %s -> %s", urn, filePath) return nil } @@ -574,7 +569,6 @@ func (p *MIoTSpecParser) skipProprietary(item map[string]interface{}) bool { // SpecStdLib is the MIoT-Spec-V2 standard library for multi-language descriptions. type SpecStdLib struct { lang string - lgr logger.Logger devices map[string]map[string]string services map[string]map[string]string properties map[string]map[string]string @@ -590,7 +584,6 @@ func NewSpecStdLib(lang string) *SpecStdLib { } s := &SpecStdLib{ lang: lang, - lgr: logger.Default(), devices: make(map[string]map[string]string), services: make(map[string]map[string]string), properties: make(map[string]map[string]string), @@ -601,9 +594,6 @@ func NewSpecStdLib(lang string) *SpecStdLib { return s } -// SetLogger sets a custom logger for SpecStdLib. -func (s *SpecStdLib) SetLogger(l logger.Logger) { s.lgr = l } - // Load loads standard library data from a parsed map. func (s *SpecStdLib) Load(data map[string]interface{}) { if data == nil { @@ -703,7 +693,7 @@ func (s *SpecStdLib) Refresh() error { var lastErr error for attempt := 0; attempt < 3; attempt++ { if attempt > 0 { - s.lgr.Debugf("[spec] retry std lib refresh, attempt %d", attempt) + logrus.Debugf("[spec] retry std lib refresh, attempt %d", attempt) time.Sleep(time.Duration(attempt) * time.Second) } @@ -736,7 +726,7 @@ func (s *SpecStdLib) Refresh() error { url := fmt.Sprintf("https://cdn.cnbj1.fds.api.mi-img.com/res-conf/xiaomi-home/std_ex_%s.json", name) extData, err := s.fetchJSONMapMap(httpClient, url) if err != nil { - s.lgr.Debugf("[spec] get external std lib failed, %s: %v", name, err) + logrus.Debugf("[spec] get external std lib failed, %s: %v", name, err) continue } if extData != nil { @@ -776,7 +766,7 @@ func (s *SpecStdLib) Refresh() error { if len(errs) > 0 { lastErr = fmt.Errorf("std lib refresh errors: %v", errs) - s.lgr.Debugf("[spec] std lib refresh error: %v", lastErr) + logrus.Debugf("[spec] std lib refresh error: %v", lastErr) } } diff --git a/xiaomi/classify.go b/xiaomi/classify.go index f2990d6..9d7552b 100644 --- a/xiaomi/classify.go +++ b/xiaomi/classify.go @@ -8,66 +8,66 @@ import ( // deviceTypeToKind maps SPEC URN device types to Kind. // URN format: urn:miot-spec-v2:device:{TYPE}:... var deviceTypeToKind = map[string]Kind{ - "air-conditioner": KindAirConditioner, - "air-condition-outlet": KindAirConditioner, - "air-condition-eco": KindAirConditioner, - "airer": KindAirConditioner, - "ac-partner": KindAirConditioner, - "light": KindLight, - "lightbulb": KindLight, - "night-light": KindLight, - "ceiling-light": KindLight, - "switch": KindSwitch, - "powerstrip": KindSwitch, - "toggle-switch": KindSwitch, - "wireless-switch": KindSwitch, - "curtain-switch": KindSwitch, - "fan": KindFan, - "ceiling-fan": KindFan, - "ventilator": KindFan, - "air-fresh": KindFan, - "air-purifier": KindFan, - "curtain": KindCover, - "airer-rack": KindCover, - "humidifier": KindHumidifier, - "dehumidifier": KindHumidifier, + "air-conditioner": KindAirConditioner, + "air-condition-outlet": KindAirConditioner, + "air-condition-eco": KindAirConditioner, + "airer": KindAirConditioner, + "ac-partner": KindAirConditioner, + "light": KindLight, + "lightbulb": KindLight, + "night-light": KindLight, + "ceiling-light": KindLight, + "switch": KindSwitch, + "powerstrip": KindSwitch, + "toggle-switch": KindSwitch, + "wireless-switch": KindSwitch, + "curtain-switch": KindSwitch, + "fan": KindFan, + "ceiling-fan": KindFan, + "ventilator": KindFan, + "air-fresh": KindFan, + "air-purifier": KindFan, + "curtain": KindCover, + "airer-rack": KindCover, + "humidifier": KindHumidifier, + "dehumidifier": KindHumidifier, "humidifier-dehumidifier": KindHumidifier, - "vacuum": KindVacuum, - "sweeper": KindVacuum, - "robot-cleaner": KindVacuum, - "water-heater": KindWaterHeater, - "water-cooler": KindWaterHeater, - "kettle": KindWaterHeater, - "heater": KindAirConditioner, - "electric-blanket": KindAirConditioner, - "thermostat": KindAirConditioner, - "speaker": KindSpeaker, - "bluetooth-speaker": KindSpeaker, - "alarm-clock": KindSpeaker, - "media-player": KindSpeaker, - "playback": KindSpeaker, - "music": KindSpeaker, - "gateway": KindGateway, - "plug": KindPlug, - "outlet": KindPlug, - "sensor-occupy": KindOccupancySensor, - "sensor_occupy": KindOccupancySensor, - "occupancy-sensor": KindOccupancySensor, - "occupancy_sensor": KindOccupancySensor, - "motion-sensor": KindOccupancySensor, - "motion_sensor": KindOccupancySensor, - "magnet": KindOccupancySensor, - "sensor": KindSensor, - "sensor-ht": KindSensor, - "sensor_ht": KindSensor, - "sensor-temp": KindSensor, - "sensor_temp": KindSensor, - "sensor-humidity": KindSensor, - "sensor_humidity": KindSensor, - "illumination": KindSensor, - "sensor-smoke": KindSensor, - "sensor_gasm": KindSensor, - "sensor-gasm": KindSensor, + "vacuum": KindVacuum, + "sweeper": KindVacuum, + "robot-cleaner": KindVacuum, + "water-heater": KindWaterHeater, + "water-cooler": KindWaterHeater, + "kettle": KindWaterHeater, + "heater": KindAirConditioner, + "electric-blanket": KindAirConditioner, + "thermostat": KindAirConditioner, + "speaker": KindSpeaker, + "bluetooth-speaker": KindSpeaker, + "alarm-clock": KindSpeaker, + "media-player": KindSpeaker, + "playback": KindSpeaker, + "music": KindSpeaker, + "gateway": KindGateway, + "plug": KindPlug, + "outlet": KindPlug, + "sensor-occupy": KindOccupancySensor, + "sensor_occupy": KindOccupancySensor, + "occupancy-sensor": KindOccupancySensor, + "occupancy_sensor": KindOccupancySensor, + "motion-sensor": KindOccupancySensor, + "motion_sensor": KindOccupancySensor, + "magnet": KindOccupancySensor, + "sensor": KindSensor, + "sensor-ht": KindSensor, + "sensor_ht": KindSensor, + "sensor-temp": KindSensor, + "sensor_temp": KindSensor, + "sensor-humidity": KindSensor, + "sensor_humidity": KindSensor, + "illumination": KindSensor, + "sensor-smoke": KindSensor, + "sensor_gasm": KindSensor, + "sensor-gasm": KindSensor, } // classifyKeys 按长度降序排列的 key,保证长/具体 key 先匹配(如 sensor-occupy 先于 sensor)。 diff --git a/xiaomi/client.go b/xiaomi/client.go index cbb83f2..2788032 100644 --- a/xiaomi/client.go +++ b/xiaomi/client.go @@ -6,8 +6,9 @@ import ( "sync" "time" - "xiaomihome/logger" "xiaomihome/miot" + + "github.com/sirupsen/logrus" ) // Client 高级封装客户端(包装 miot.MIoTClient + miot.MIoTHttpClient) @@ -18,7 +19,6 @@ type Client struct { mipsCloud *miot.MipsCloudClient specParser *miot.MIoTSpecParser uuid string - lgr logger.Logger deviceMu sync.RWMutex devices map[string]*miot.MIoTDevice @@ -95,16 +95,15 @@ func NewClient(ctx context.Context, cfg Config) (*Client, error) { } c := &Client{ - cfg: cfg, - inner: miotClient, - http: httpClient, - mipsCloud: mipsCloud, - uuid: uuid, - lgr: logger.Default(), - specParser: miot.NewMIoTSpecParser("en", miot.NewMIoTStorage(cfg.DataDir), cfg.DataDir+"/specs"), - devices: make(map[string]*miot.MIoTDevice), - propSubs: make(map[string]string), - eventSubs: make(map[string]string), + cfg: cfg, + inner: miotClient, + http: httpClient, + mipsCloud: mipsCloud, + uuid: uuid, + specParser: miot.NewMIoTSpecParser("en", miot.NewMIoTStorage(cfg.DataDir), cfg.DataDir+"/specs"), + devices: make(map[string]*miot.MIoTDevice), + propSubs: make(map[string]string), + eventSubs: make(map[string]string), readbackModels: make(map[string]struct{}, len(cfg.ReadbackModels)), } for _, m := range cfg.ReadbackModels { @@ -124,12 +123,6 @@ func (c *Client) NeedReadback(model string) bool { return ok } -// SetLogger 设置日志记录器。 -func (c *Client) SetLogger(l logger.Logger) { c.lgr = l } - -// Logger 返回日志记录器。 -func (c *Client) Logger() logger.Logger { return c.lgr } - // SetHomeIDs 动态设置 miot 层的家庭过滤,供后台定时 RefreshDevices 使用。 func (c *Client) SetHomeIDs(homeIDs []string) { c.inner.SetHomeIDs(homeIDs) } @@ -174,7 +167,7 @@ func (c *Client) getOrCreateDevice(did string, info map[string]interface{}) *mio if err == nil { spec = s } else { - c.lgr.Warnf("[xiaomi] spec parse failed for %s (%s): %v", did, urn, err) + logrus.Warnf("[xiaomi] spec parse failed for %s (%s): %v", did, urn, err) } } diff --git a/xiaomi/config.go b/xiaomi/config.go index a5dbc95..2b3eb03 100644 --- a/xiaomi/config.go +++ b/xiaomi/config.go @@ -4,8 +4,8 @@ package xiaomi type AuthInfo struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` - ExpiresIn int32 `json:"expires_in"` // 有效期(秒) - ExpiresTS int64 `json:"expires_ts"` // 过期时间戳(unix),0 则内部按 expires_in 计算 + ExpiresIn int32 `json:"expires_in"` // 有效期(秒) + ExpiresTS int64 `json:"expires_ts"` // 过期时间戳(unix),0 则内部按 expires_in 计算 } // Config 客户端初始化配置。 diff --git a/xiaomi/convert_test.go b/xiaomi/convert_test.go index 814b6b4..83fd876 100644 --- a/xiaomi/convert_test.go +++ b/xiaomi/convert_test.go @@ -4,10 +4,10 @@ import "testing" func TestStrVal(t *testing.T) { tests := []struct { - name string - m map[string]interface{} - key string - want string + name string + m map[string]interface{} + key string + want string }{ {"string value", map[string]interface{}{"name": "test"}, "name", "test"}, {"missing key", map[string]interface{}{"name": "test"}, "nonexistent", ""}, diff --git a/xiaomi/devices/air_conditioner.go b/xiaomi/devices/air_conditioner.go index d12bf0e..4c91847 100644 --- a/xiaomi/devices/air_conditioner.go +++ b/xiaomi/devices/air_conditioner.go @@ -9,6 +9,8 @@ import ( "xiaomihome/miot" "xiaomihome/xiaomi" + + "github.com/sirupsen/logrus" ) // valMapper maps between device raw values and semantic names. Built from SPEC ValueList. @@ -36,8 +38,8 @@ func newValMapper(vl miot.MIoTSpecValueList) *valMapper { display = item.Name } key := strings.ToLower(display) - vm.val2name[v] = display // 原始描述做展示 - vm.name2val[key] = v // 小写 key 做匹配 + vm.val2name[v] = display // 原始描述做展示 + vm.name2val[key] = v // 小写 key 做匹配 vm.names = append(vm.names, display) } return vm @@ -122,7 +124,9 @@ func (vm *valMapper) levelValues() []int { } func (vm *valMapper) allNames() []string { - if vm == nil { return nil } + if vm == nil { + return nil + } return vm.names } @@ -207,7 +211,7 @@ type AirConditioner interface { // 功率 HasPower() bool - GetElectricPower(ctx context.Context) (float64, error) // 当前电功率(W) + GetElectricPower(ctx context.Context) (float64, error) // 当前电功率(W) GetPowerConsumption(ctx context.Context) (float64, error) // 累计耗电量(kWh) // 订阅 @@ -218,47 +222,47 @@ type AirConditioner interface { // ACProps 空调完整状态(字段名对齐 SPEC type) type ACProps struct { - On bool `json:"on"` - Mode string `json:"mode"` // SPEC ValueList 解析后的名称:cool/heat/fan/dry/auto + On bool `json:"on"` + Mode string `json:"mode"` // SPEC ValueList 解析后的名称:cool/heat/fan/dry/auto TargetTemperature float64 `json:"targetTemperature"` Temperature float64 `json:"temperature"` - TargetHumidity int `json:"targetHumidity"` - CurrentHumidity int `json:"currentHumidity"` - FanLevel string `json:"fanLevel"` // SPEC ValueList 解析后的名称:low/medium/high/auto - Swing string `json:"swing"` // SPEC ValueList 解析后的名称:off/vertical/horizontal/both - AuxHeat bool `json:"auxHeat"` - Eco bool `json:"eco"` - ElectricPower float64 `json:"electricPower"` // 当前电功率(W),来自 electric-power - PowerConsumption float64 `json:"powerConsumption"` // 累计耗电量(kWh),来自 power-consumption + TargetHumidity int `json:"targetHumidity"` + CurrentHumidity int `json:"currentHumidity"` + FanLevel string `json:"fanLevel"` // SPEC ValueList 解析后的名称:low/medium/high/auto + Swing string `json:"swing"` // SPEC ValueList 解析后的名称:off/vertical/horizontal/both + AuxHeat bool `json:"auxHeat"` + Eco bool `json:"eco"` + ElectricPower float64 `json:"electricPower"` // 当前电功率(W),来自 electric-power + PowerConsumption float64 `json:"powerConsumption"` // 累计耗电量(kWh),来自 power-consumption } type acDevice struct { BaseDevice - propOn siidPiid // "on" - propMode siidPiid // "mode" - propTargetTemp siidPiid // "target-temperature" - propCurrentTemp siidPiid // "temperature" - propFanSpeed siidPiid // "fan-level" - propSwing siidPiid // "swing-mode" or "horizontal-swing" or "vertical-swing" - propTargetHumidity siidPiid // "target-humidity" - propCurrentHumidity siidPiid // "relative-humidity" - propAuxHeat siidPiid // "aux-heat" or "heater" - propEco siidPiid // "eco" - propElectricPower siidPiid // "electric-power" + propOn siidPiid // "on" + propMode siidPiid // "mode" + propTargetTemp siidPiid // "target-temperature" + propCurrentTemp siidPiid // "temperature" + propFanSpeed siidPiid // "fan-level" + propSwing siidPiid // "swing-mode" or "horizontal-swing" or "vertical-swing" + propTargetHumidity siidPiid // "target-humidity" + propCurrentHumidity siidPiid // "relative-humidity" + propAuxHeat siidPiid // "aux-heat" or "heater" + propEco siidPiid // "eco" + propElectricPower siidPiid // "electric-power" propPowerConsumption siidPiid // "power-consumption" - modeMap *valMapper // SPEC value→name for mode - fanMap *valMapper // SPEC value→name for fan-level + modeMap *valMapper // SPEC value→name for mode + fanMap *valMapper // SPEC value→name for fan-level swingMap *valMapper // SPEC value→name for swing propsCache ACProps // MQTT 推送维护的本地缓存 // set 后回读(验证设置是否成功):记录目标值,防抖 + 重试比较 rbMu sync.Mutex - rbPending bool // 是否有回读调度在跑 - rbLastSet time.Time // 最近一次 set 时间 - rbTargets []rbTarget // 本次待验证的目标(聚合连续 set) - rbHandler func(props ACProps, changed string) // OnPropsChanged 注册的 handler + rbPending bool // 是否有回读调度在跑 + rbLastSet time.Time // 最近一次 set 时间 + rbTargets []rbTarget // 本次待验证的目标(聚合连续 set) + rbHandler func(props ACProps, changed string) // OnPropsChanged 注册的 handler } // rbTarget 回读验证目标:某属性本次 set 的目标值。 @@ -729,9 +733,7 @@ func (d *acDevice) recordReadback(name string, want interface{}) { } d.rbPending = true d.rbMu.Unlock() - if d.client.Logger() != nil { - d.client.Logger().Debugf("[acDevice] readback scheduled: %s (%s) targets=%v\n", d.info.DID, d.info.Model, d.rbTargetNames()) - } + logrus.Debugf("[acDevice] readback scheduled: %s (%s) targets=%v\n", d.info.DID, d.info.Model, d.rbTargetNames()) go d.readbackLoop() } @@ -768,9 +770,7 @@ func (d *acDevice) readbackLoop() { } props, err := d.FetchProps(ctx) if err != nil { - if d.client.Logger() != nil { - d.client.Logger().Warnf("[acDevice] readback %s attempt %d failed: %v\n", d.info.DID, attempt, err) - } + logrus.Warnf("[acDevice] readback %s attempt %d failed: %v\n", d.info.DID, attempt, err) if ctx.Err() != nil { d.finishReadback("timeout") return @@ -782,9 +782,7 @@ func (d *acDevice) readbackLoop() { d.finishReadbackOK(props, targets) return } - if d.client.Logger() != nil { - d.client.Logger().Debugf("[acDevice] readback %s attempt %d not applied yet: missed=%v\n", d.info.DID, attempt, missed) - } + logrus.Debugf("[acDevice] readback %s attempt %d not applied yet: missed=%v\n", d.info.DID, attempt, missed) if attempt >= readbackMaxRetries { d.finishReadback("not applied after retries") return @@ -810,9 +808,7 @@ func (d *acDevice) finishReadbackOK(props ACProps, targets []rbTarget) { handler := d.rbHandler d.rbMu.Unlock() if handler == nil { - if d.client.Logger() != nil { - d.client.Logger().Warnf("[acDevice] readback %s done but handler not registered (OnPropsChanged never called)\n", d.info.DID) - } + logrus.Warnf("[acDevice] readback %s done but handler not registered (OnPropsChanged never called)\n", d.info.DID) d.rbMu.Lock() d.rbPending = false d.rbMu.Unlock() @@ -821,11 +817,9 @@ func (d *acDevice) finishReadbackOK(props ACProps, targets []rbTarget) { for _, t := range targets { handler(props, t.name) } - if d.client.Logger() != nil { - d.client.Logger().Debugf("[acDevice] readback %s success: %v on=%v mode=%s target=%.0f temp=%.0f fan=%s hum=%d\n", - d.info.DID, d.rbTargetNamesLocked(), props.On, props.Mode, props.TargetTemperature, - props.Temperature, props.FanLevel, props.CurrentHumidity) - } + logrus.Debugf("[acDevice] readback %s success: %v on=%v mode=%s target=%.0f temp=%.0f fan=%s hum=%d\n", + d.info.DID, d.rbTargetNamesLocked(), props.On, props.Mode, props.TargetTemperature, + props.Temperature, props.FanLevel, props.CurrentHumidity) d.rbMu.Lock() d.rbPending = false d.rbMu.Unlock() @@ -838,9 +832,7 @@ func (d *acDevice) finishReadback(reason string) { d.rbTargets = nil d.rbPending = false d.rbMu.Unlock() - if d.client.Logger() != nil { - d.client.Logger().Warnf("[acDevice] readback %s finished: %s targets=%v\n", d.info.DID, reason, names) - } + logrus.Warnf("[acDevice] readback %s finished: %s targets=%v\n", d.info.DID, reason, names) } func (d *acDevice) rbTargetNames() []string { diff --git a/xiaomi/devices/cover.go b/xiaomi/devices/cover.go index 3e5d3ba..61a9c34 100644 --- a/xiaomi/devices/cover.go +++ b/xiaomi/devices/cover.go @@ -71,15 +71,21 @@ func NewCover(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Cover, error) { } func (d *coverDevice) Open(ctx context.Context) error { - if !d.propMotor.valid { return xiaomi.ErrNotSupported } + if !d.propMotor.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propMotor.siid, d.propMotor.piid, coverMotorOpen) } func (d *coverDevice) Close(ctx context.Context) error { - if !d.propMotor.valid { return xiaomi.ErrNotSupported } + if !d.propMotor.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propMotor.siid, d.propMotor.piid, coverMotorClose) } func (d *coverDevice) Stop(ctx context.Context) error { - if !d.propMotor.valid { return xiaomi.ErrNotSupported } + if !d.propMotor.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propMotor.siid, d.propMotor.piid, coverMotorStop) } @@ -96,9 +102,13 @@ func (d *coverDevice) SetPosition(ctx context.Context, pct int) error { } func (d *coverDevice) GetPosition(ctx context.Context) (int, error) { - if !d.propPosition.valid { return 0, xiaomi.ErrNotSupported } + if !d.propPosition.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propPosition.siid, d.propPosition.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toInt(prop.Value), nil } @@ -125,9 +135,15 @@ func (d *coverDevice) GetProps() CoverProps { return d.propsCache } func (d *coverDevice) FetchProps(ctx context.Context) (CoverProps, error) { keys := []xiaomi.PropKey{} - if d.propPosition.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propPosition.siid, PIID: d.propPosition.piid}) } - if d.propStatus.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propStatus.siid, PIID: d.propStatus.piid}) } - if d.propFault.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propFault.siid, PIID: d.propFault.piid}) } + if d.propPosition.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propPosition.siid, PIID: d.propPosition.piid}) + } + if d.propStatus.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propStatus.siid, PIID: d.propStatus.piid}) + } + if d.propFault.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propFault.siid, PIID: d.propFault.piid}) + } if len(keys) == 0 { return d.propsCache, xiaomi.ErrNotSupported } diff --git a/xiaomi/devices/devices_test.go b/xiaomi/devices/devices_test.go index 61637f6..d40b8a2 100644 --- a/xiaomi/devices/devices_test.go +++ b/xiaomi/devices/devices_test.go @@ -209,7 +209,11 @@ func TestValMapperLevelFallback(t *testing.T) { {Value: 3, Desc: "Level3"}, {Value: 4, Desc: "Level4"}, {Value: 5, Desc: "Level5"}, } vm := newValMapperLevel(vl) - cases := []struct{ name string; want int; ok bool }{ + cases := []struct { + name string + want int + ok bool + }{ {"auto", 0, true}, {"Auto", 0, true}, {"low", 1, true}, {"medium", 3, true}, {"high", 5, true}, {"level3", 3, true}, @@ -221,7 +225,10 @@ func TestValMapperLevelFallback(t *testing.T) { } } // 反向解析(MQTT 上报值 → 语义枚举),与 val() 正向对称 - rname := []struct{ v int; want string }{ + rname := []struct { + v int + want string + }{ {0, "auto"}, {1, "low"}, {3, "medium"}, {5, "high"}, {2, "Level2"}, } for _, c := range rname { diff --git a/xiaomi/devices/fan.go b/xiaomi/devices/fan.go index 03856b3..2ef0380 100644 --- a/xiaomi/devices/fan.go +++ b/xiaomi/devices/fan.go @@ -55,8 +55,8 @@ type fanDevice struct { propNaturalWind siidPiid // "mode" — 无 ValueList 时为自然风开关 propOscillation siidPiid // "horizontal-swing" or "oscillation" - modeValues []int // 可用模式值列表(来自 SPEC value-list) - fanLevelValues []int // 可用风速档位值列表 + modeValues []int // 可用模式值列表(来自 SPEC value-list) + fanLevelValues []int // 可用风速档位值列表 propsCache FanProps } diff --git a/xiaomi/devices/humidifier.go b/xiaomi/devices/humidifier.go index d10d32c..8830ced 100644 --- a/xiaomi/devices/humidifier.go +++ b/xiaomi/devices/humidifier.go @@ -39,53 +39,87 @@ type humidifierDevice struct { func NewHumidifier(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Humidifier, error) { d := &humidifierDevice{BaseDevice: NewBaseDevice(client, info)} resolver := d.getSpecResolver() - if resolver == nil { return nil, xiaomi.ErrNoSPEC } + if resolver == nil { + return nil, xiaomi.ErrNoSPEC + } siid, piid, ok := resolver.find("on") - if !ok { return nil, xiaomi.ErrNotSupported } + if !ok { + return nil, xiaomi.ErrNotSupported + } d.propOn = siidPiid{siid, piid, true} - if s, p, ok := resolver.find("target-humidity"); ok { d.propTargetHumidity = siidPiid{s, p, true} } - if s, p, ok := resolver.find("fan-level"); ok { d.propFanLevel = siidPiid{s, p, true} } + if s, p, ok := resolver.find("target-humidity"); ok { + d.propTargetHumidity = siidPiid{s, p, true} + } + if s, p, ok := resolver.find("fan-level"); ok { + d.propFanLevel = siidPiid{s, p, true} + } return d, nil } func (d *humidifierDevice) TurnOn(ctx context.Context) error { - if !d.propOn.valid { return xiaomi.ErrNotSupported } + if !d.propOn.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, true) } func (d *humidifierDevice) TurnOff(ctx context.Context) error { - if !d.propOn.valid { return xiaomi.ErrNotSupported } + if !d.propOn.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, false) } func (d *humidifierDevice) IsOn(ctx context.Context) (bool, error) { - if !d.propOn.valid { return false, xiaomi.ErrNotSupported } + if !d.propOn.valid { + return false, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propOn.siid, d.propOn.piid) - if err != nil { return false, err } - if b, ok := prop.Value.(bool); ok { return b, nil } + if err != nil { + return false, err + } + if b, ok := prop.Value.(bool); ok { + return b, nil + } return false, nil } func (d *humidifierDevice) SetTargetHumidity(ctx context.Context, pct int) error { - if !d.propTargetHumidity.valid { return xiaomi.ErrNotSupported } + if !d.propTargetHumidity.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propTargetHumidity.siid, d.propTargetHumidity.piid, pct) } func (d *humidifierDevice) GetTargetHumidity(ctx context.Context) (int, error) { - if !d.propTargetHumidity.valid { return 0, xiaomi.ErrNotSupported } + if !d.propTargetHumidity.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propTargetHumidity.siid, d.propTargetHumidity.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toInt(prop.Value), nil } func (d *humidifierDevice) SetFanLevel(ctx context.Context, level int) error { - if !d.propFanLevel.valid { return xiaomi.ErrNotSupported } + if !d.propFanLevel.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid, level) } func (d *humidifierDevice) GetFanLevel(ctx context.Context) (int, error) { - if !d.propFanLevel.valid { return 0, xiaomi.ErrNotSupported } + if !d.propFanLevel.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toInt(prop.Value), nil } func (d *humidifierDevice) OnPropsChanged(handler func(props HumidifierProps, changed string)) (string, error) { - type field struct{ siid, piid int; name string; fill func(*HumidifierProps, interface{}) } + type field struct { + siid, piid int + name string + fill func(*HumidifierProps, interface{}) + } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HumidifierProps, v interface{}) { s.On, _ = v.(bool) }}) @@ -96,7 +130,9 @@ func (d *humidifierDevice) OnPropsChanged(handler func(props HumidifierProps, ch if d.propFanLevel.valid { fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *HumidifierProps, v interface{}) { s.FanLevel = toInt(v) }}) } - if len(fields) == 0 { return "", xiaomi.ErrNotSupported } + if len(fields) == 0 { + return "", xiaomi.ErrNotSupported + } return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) { for _, f := range fields { if f.siid == prop.SIID && f.piid == prop.PIID { @@ -111,17 +147,28 @@ func (d *humidifierDevice) OnPropsChanged(handler func(props HumidifierProps, ch func (d *humidifierDevice) GetProps() HumidifierProps { return d.propsCache } func (d *humidifierDevice) FetchProps(ctx context.Context) (HumidifierProps, error) { keys := []xiaomi.PropKey{} - if d.propOn.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) } - if d.propTargetHumidity.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetHumidity.siid, PIID: d.propTargetHumidity.piid}) } - if d.propFanLevel.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propFanLevel.siid, PIID: d.propFanLevel.piid}) } + if d.propOn.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) + } + if d.propTargetHumidity.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetHumidity.siid, PIID: d.propTargetHumidity.piid}) + } + if d.propFanLevel.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propFanLevel.siid, PIID: d.propFanLevel.piid}) + } props, err := d.client.GetProps(ctx, keys) - if err != nil { return d.propsCache, err } + if err != nil { + return d.propsCache, err + } result := HumidifierProps{} for _, p := range props { switch { - case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: result.On, _ = p.Value.(bool) - case d.propTargetHumidity.valid && p.SIID == d.propTargetHumidity.siid && p.PIID == d.propTargetHumidity.piid: result.TargetHumidity = toInt(p.Value) - case d.propFanLevel.valid && p.SIID == d.propFanLevel.siid && p.PIID == d.propFanLevel.piid: result.FanLevel = toInt(p.Value) + case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: + result.On, _ = p.Value.(bool) + case d.propTargetHumidity.valid && p.SIID == d.propTargetHumidity.siid && p.PIID == d.propTargetHumidity.piid: + result.TargetHumidity = toInt(p.Value) + case d.propFanLevel.valid && p.SIID == d.propFanLevel.siid && p.PIID == d.propFanLevel.piid: + result.FanLevel = toInt(p.Value) } } d.propsCache = result diff --git a/xiaomi/devices/light.go b/xiaomi/devices/light.go index 0a1bc08..ad9eac8 100644 --- a/xiaomi/devices/light.go +++ b/xiaomi/devices/light.go @@ -38,10 +38,10 @@ type Light interface { // LightProps 灯光完整状态(字段名对齐 SPEC type) type LightProps struct { - On bool `json:"on"` // SPEC: "on" - Brightness int `json:"brightness"` // SPEC: "brightness" - ColorTemperature int `json:"colorTemperature"` // SPEC: "color-temperature" - ColorRGB [3]int `json:"colorRgb"` // SPEC: "color" + On bool `json:"on"` // SPEC: "on" + Brightness int `json:"brightness"` // SPEC: "brightness" + ColorTemperature int `json:"colorTemperature"` // SPEC: "color-temperature" + ColorRGB [3]int `json:"colorRgb"` // SPEC: "color" } type lightDevice struct { @@ -252,20 +252,36 @@ func toInt(v interface{}) int { func (d *lightDevice) GetProps() LightProps { return d.propsCache } func (d *lightDevice) FetchProps(ctx context.Context) (LightProps, error) { keys := []xiaomi.PropKey{} - if d.propOn.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) } - if d.propBrightness.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propBrightness.siid, PIID: d.propBrightness.piid}) } - if d.propColorTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propColorTemp.siid, PIID: d.propColorTemp.piid}) } - if d.propColor.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propColor.siid, PIID: d.propColor.piid}) } + if d.propOn.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) + } + if d.propBrightness.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propBrightness.siid, PIID: d.propBrightness.piid}) + } + if d.propColorTemp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propColorTemp.siid, PIID: d.propColorTemp.piid}) + } + if d.propColor.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propColor.siid, PIID: d.propColor.piid}) + } props, err := d.client.GetProps(ctx, keys) - if err != nil { return d.propsCache, err } + if err != nil { + return d.propsCache, err + } result := LightProps{} for _, p := range props { switch { - case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: result.On, _ = p.Value.(bool) - case d.propBrightness.valid && p.SIID == d.propBrightness.siid && p.PIID == d.propBrightness.piid: result.Brightness = toInt(p.Value) - case d.propColorTemp.valid && p.SIID == d.propColorTemp.siid && p.PIID == d.propColorTemp.piid: result.ColorTemperature = toInt(p.Value) + case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: + result.On, _ = p.Value.(bool) + case d.propBrightness.valid && p.SIID == d.propBrightness.siid && p.PIID == d.propBrightness.piid: + result.Brightness = toInt(p.Value) + case d.propColorTemp.valid && p.SIID == d.propColorTemp.siid && p.PIID == d.propColorTemp.piid: + result.ColorTemperature = toInt(p.Value) case d.propColor.valid && p.SIID == d.propColor.siid && p.PIID == d.propColor.piid: - if v, ok := p.Value.(float64); ok { c := int(v); result.ColorRGB = [3]int{(c>>16)&0xFF, (c>>8)&0xFF, c&0xFF} } + if v, ok := p.Value.(float64); ok { + c := int(v) + result.ColorRGB = [3]int{(c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF} + } } } d.propsCache = result diff --git a/xiaomi/devices/occupancy_sensor.go b/xiaomi/devices/occupancy_sensor.go index a5216c1..75f7f66 100644 --- a/xiaomi/devices/occupancy_sensor.go +++ b/xiaomi/devices/occupancy_sensor.go @@ -20,9 +20,9 @@ type OccupancySensor interface { type occupancyDevice struct { BaseDevice - propOccupancy siidPiid // "occupancy" / "occupancy-status"(bool/uint8) - propNoMotion siidPiid // "no-motion-duration"(uint16,linp.motion.* 等无 occupancy 属性的设备) - propIlluminance siidPiid // "illumination" + propOccupancy siidPiid // "occupancy" / "occupancy-status"(bool/uint8) + propNoMotion siidPiid // "no-motion-duration"(uint16,linp.motion.* 等无 occupancy 属性的设备) + propIlluminance siidPiid // "illumination" propsCache [2]interface{} // [0]=bool occupied, [1]=float64 lux } @@ -35,21 +35,33 @@ func (d *occupancyDevice) occupied(v interface{}) bool { } func NewOccupancySensor(client *xiaomi.Client, info *xiaomi.DeviceInfo) (OccupancySensor, error) { - if info == nil { return nil, xiaomi.ErrDeviceNotFound } + if info == nil { + return nil, xiaomi.ErrDeviceNotFound + } miotDev := client.GetMIoTDevice(info.DID) - if miotDev == nil { return nil, fmt.Errorf("%w: device not initialized", xiaomi.ErrDeviceNotFound) } + if miotDev == nil { + return nil, fmt.Errorf("%w: device not initialized", xiaomi.ErrDeviceNotFound) + } spec := miotDev.SpecInstance() - if spec == nil { return nil, fmt.Errorf("%w: device has no SPEC information", xiaomi.ErrNotSupported) } + if spec == nil { + return nil, fmt.Errorf("%w: device has no SPEC information", xiaomi.ErrNotSupported) + } d := &occupancyDevice{BaseDevice: BaseDevice{client: client, info: info}} for _, svc := range spec.Services { for _, prop := range svc.Properties { switch propertyShortName(prop.Type) { case "occupancy", "occupancy-status": - if !d.propOccupancy.valid { d.propOccupancy = siidPiid{svc.IID, prop.PIID, true} } + if !d.propOccupancy.valid { + d.propOccupancy = siidPiid{svc.IID, prop.PIID, true} + } case "no-motion-duration": - if !d.propNoMotion.valid { d.propNoMotion = siidPiid{svc.IID, prop.PIID, true} } + if !d.propNoMotion.valid { + d.propNoMotion = siidPiid{svc.IID, prop.PIID, true} + } case "illumination": - if !d.propIlluminance.valid { d.propIlluminance = siidPiid{svc.IID, prop.PIID, true} } + if !d.propIlluminance.valid { + d.propIlluminance = siidPiid{svc.IID, prop.PIID, true} + } } } } @@ -70,18 +82,26 @@ func (d *occupancyDevice) occupiedProp() siidPiid { func (d *occupancyDevice) IsOccupied(ctx context.Context) (bool, error) { p := d.occupiedProp() prop, err := d.GetProp(ctx, p.siid, p.piid) - if err != nil { return false, err } + if err != nil { + return false, err + } return d.occupied(prop.Value), nil } func (d *occupancyDevice) GetIlluminance(ctx context.Context) (float64, error) { - if !d.propIlluminance.valid { return 0, xiaomi.ErrNotSupported } + if !d.propIlluminance.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propIlluminance.siid, d.propIlluminance.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *occupancyDevice) OnPropsChanged(handler func(occupied bool, illuminance float64)) (string, error) { - if !d.propOccupancy.valid && !d.propNoMotion.valid && !d.propIlluminance.valid { return "", xiaomi.ErrNotSupported } + if !d.propOccupancy.valid && !d.propNoMotion.valid && !d.propIlluminance.valid { + return "", xiaomi.ErrNotSupported + } return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) { switch { case d.propOccupancy.valid && prop.SIID == d.propOccupancy.siid && prop.PIID == d.propOccupancy.piid: @@ -97,7 +117,7 @@ func (d *occupancyDevice) OnPropsChanged(handler func(occupied bool, illuminance }) } -func (d *occupancyDevice) GetProps() (bool, float64) { +func (d *occupancyDevice) GetProps() (bool, float64) { b, _ := d.propsCache[0].(bool) f, _ := d.propsCache[1].(float64) return b, f @@ -105,8 +125,12 @@ func (d *occupancyDevice) GetProps() (bool, float64) { func (d *occupancyDevice) FetchProps(ctx context.Context) (bool, float64, error) { keys := []xiaomi.PropKey{} p := d.occupiedProp() - if p.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: p.siid, PIID: p.piid}) } - if d.propIlluminance.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propIlluminance.siid, PIID: d.propIlluminance.piid}) } + if p.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: p.siid, PIID: p.piid}) + } + if d.propIlluminance.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propIlluminance.siid, PIID: d.propIlluminance.piid}) + } props, err := d.client.GetProps(ctx, keys) if err != nil { b, _ := d.propsCache[0].(bool) diff --git a/xiaomi/devices/properties.go b/xiaomi/devices/properties.go index 0abf004..d8603be 100644 --- a/xiaomi/devices/properties.go +++ b/xiaomi/devices/properties.go @@ -4,19 +4,19 @@ package devices // 用于 bridge.OnPropsChanged 的 deviceType 字段 / factory 分类 const ( - TypeAirConditioner = "air_conditioner" // 空调 - TypeLight = "light" // 灯 - TypeSwitch = "switch" // 开关 - TypeFan = "fan" // 风扇 - TypeCover = "cover" // 窗帘/遮阳帘 - TypeHumidifier = "humidifier" // 加湿器 - TypeVacuum = "vacuum" // 吸尘器/扫地机器人 - TypeWaterHeater = "water_heater" // 热水器 - TypeOccupancySensor = "sensor_occupy" // 人体传感器 - TypeTempHumidSensor = "sensor" // 温湿度传感器(官方 URN type) - TypeSpeaker = "speaker" // 音箱 - TypeGateway = "gateway" // 网关 - TypePlug = "plug" // 插座 + TypeAirConditioner = "air_conditioner" // 空调 + TypeLight = "light" // 灯 + TypeSwitch = "switch" // 开关 + TypeFan = "fan" // 风扇 + TypeCover = "cover" // 窗帘/遮阳帘 + TypeHumidifier = "humidifier" // 加湿器 + TypeVacuum = "vacuum" // 吸尘器/扫地机器人 + TypeWaterHeater = "water_heater" // 热水器 + TypeOccupancySensor = "sensor_occupy" // 人体传感器 + TypeTempHumidSensor = "sensor" // 温湿度传感器(官方 URN type) + TypeSpeaker = "speaker" // 音箱 + TypeGateway = "gateway" // 网关 + TypePlug = "plug" // 插座 ) diff --git a/xiaomi/devices/speaker.go b/xiaomi/devices/speaker.go index fadfe2b..0983bca 100644 --- a/xiaomi/devices/speaker.go +++ b/xiaomi/devices/speaker.go @@ -80,11 +80,11 @@ type speakerDevice struct { isSiid int // 动作 - actWakeUp isAction - actPlayRadio isAction - actPlayText isAction + actWakeUp isAction + actPlayRadio isAction + actPlayText isAction actExecuteTextDirective isAction - actPlayMusic isAction + actPlayMusic isAction propsCache SpeakerProps } diff --git a/xiaomi/devices/temp_humidity_sensor.go b/xiaomi/devices/temp_humidity_sensor.go index 4fa5f3f..93025a0 100644 --- a/xiaomi/devices/temp_humidity_sensor.go +++ b/xiaomi/devices/temp_humidity_sensor.go @@ -26,19 +26,29 @@ type tempHumidityDevice struct { } func NewTempHumiditySensor(client *xiaomi.Client, info *xiaomi.DeviceInfo) (TempHumiditySensor, error) { - if info == nil { return nil, xiaomi.ErrDeviceNotFound } + if info == nil { + return nil, xiaomi.ErrDeviceNotFound + } miotDev := client.GetMIoTDevice(info.DID) - if miotDev == nil { return nil, fmt.Errorf("%w: device not initialized", xiaomi.ErrDeviceNotFound) } + if miotDev == nil { + return nil, fmt.Errorf("%w: device not initialized", xiaomi.ErrDeviceNotFound) + } spec := miotDev.SpecInstance() - if spec == nil { return nil, fmt.Errorf("%w: device has no SPEC information", xiaomi.ErrNotSupported) } + if spec == nil { + return nil, fmt.Errorf("%w: device has no SPEC information", xiaomi.ErrNotSupported) + } d := &tempHumidityDevice{BaseDevice: BaseDevice{client: client, info: info}} for _, svc := range spec.Services { for _, prop := range svc.Properties { switch propertyShortName(prop.Type) { case "temperature": - if !d.propTemp.valid { d.propTemp = siidPiid{svc.IID, prop.PIID, true} } + if !d.propTemp.valid { + d.propTemp = siidPiid{svc.IID, prop.PIID, true} + } case "relative-humidity": - if !d.propHumidity.valid { d.propHumidity = siidPiid{svc.IID, prop.PIID, true} } + if !d.propHumidity.valid { + d.propHumidity = siidPiid{svc.IID, prop.PIID, true} + } } } } @@ -49,20 +59,30 @@ func NewTempHumiditySensor(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Temp } func (d *tempHumidityDevice) GetTemperature(ctx context.Context) (float64, error) { - if !d.propTemp.valid { return 0, xiaomi.ErrNotSupported } + if !d.propTemp.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propTemp.siid, d.propTemp.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *tempHumidityDevice) GetHumidity(ctx context.Context) (float64, error) { - if !d.propHumidity.valid { return 0, xiaomi.ErrNotSupported } + if !d.propHumidity.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propHumidity.siid, d.propHumidity.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *tempHumidityDevice) OnPropsChanged(handler func(temp, humidity float64)) (string, error) { - if !d.propTemp.valid && !d.propHumidity.valid { return "", xiaomi.ErrNotSupported } + if !d.propTemp.valid && !d.propHumidity.valid { + return "", xiaomi.ErrNotSupported + } return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) { switch { case d.propTemp.valid && prop.SIID == d.propTemp.siid && prop.PIID == d.propTemp.piid: @@ -77,15 +97,23 @@ func (d *tempHumidityDevice) OnPropsChanged(handler func(temp, humidity float64) func (d *tempHumidityDevice) GetProps() (float64, float64) { return d.propsCache[0], d.propsCache[1] } func (d *tempHumidityDevice) FetchProps(ctx context.Context) (float64, float64, error) { keys := []xiaomi.PropKey{} - if d.propTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTemp.siid, PIID: d.propTemp.piid}) } - if d.propHumidity.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propHumidity.siid, PIID: d.propHumidity.piid}) } + if d.propTemp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTemp.siid, PIID: d.propTemp.piid}) + } + if d.propHumidity.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propHumidity.siid, PIID: d.propHumidity.piid}) + } props, err := d.client.GetProps(ctx, keys) - if err != nil { return d.propsCache[0], d.propsCache[1], err } + if err != nil { + return d.propsCache[0], d.propsCache[1], err + } state := [2]float64{d.propsCache[0], d.propsCache[1]} for _, p := range props { switch { - case d.propTemp.valid && p.SIID == d.propTemp.siid && p.PIID == d.propTemp.piid: state[0] = toFloat(p.Value) - case d.propHumidity.valid && p.SIID == d.propHumidity.siid && p.PIID == d.propHumidity.piid: state[1] = toFloat(p.Value) + case d.propTemp.valid && p.SIID == d.propTemp.siid && p.PIID == d.propTemp.piid: + state[0] = toFloat(p.Value) + case d.propHumidity.valid && p.SIID == d.propHumidity.siid && p.PIID == d.propHumidity.piid: + state[1] = toFloat(p.Value) } } d.propsCache = state diff --git a/xiaomi/devices/thermostat.go b/xiaomi/devices/thermostat.go index 732f26b..c5932ef 100644 --- a/xiaomi/devices/thermostat.go +++ b/xiaomi/devices/thermostat.go @@ -34,61 +34,103 @@ type thermostatDevice struct { func NewThermostat(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Thermostat, error) { d := &thermostatDevice{BaseDevice: NewBaseDevice(client, info)} resolver := d.getSpecResolver() - if resolver == nil { return nil, xiaomi.ErrNoSPEC } + if resolver == nil { + return nil, xiaomi.ErrNoSPEC + } siid, piid, ok := resolver.find("on") - if !ok { return nil, xiaomi.ErrNotSupported } + if !ok { + return nil, xiaomi.ErrNotSupported + } d.propOn = siidPiid{siid, piid, true} - if s, p, ok := resolver.find("target-temperature"); ok { d.propTargetTemp = siidPiid{s, p, true} } - if s, p, ok := resolver.find("temperature"); ok { d.propCurrentTemp = siidPiid{s, p, true} } - if s, p, ok := resolver.find("heater"); ok { d.propHeating = siidPiid{s, p, true} } + if s, p, ok := resolver.find("target-temperature"); ok { + d.propTargetTemp = siidPiid{s, p, true} + } + if s, p, ok := resolver.find("temperature"); ok { + d.propCurrentTemp = siidPiid{s, p, true} + } + if s, p, ok := resolver.find("heater"); ok { + d.propHeating = siidPiid{s, p, true} + } return d, nil } func (d *thermostatDevice) TurnOn(ctx context.Context) error { - if !d.propOn.valid { return xiaomi.ErrNotSupported } + if !d.propOn.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, true) } func (d *thermostatDevice) TurnOff(ctx context.Context) error { - if !d.propOn.valid { return xiaomi.ErrNotSupported } + if !d.propOn.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, false) } func (d *thermostatDevice) IsOn(ctx context.Context) (bool, error) { - if !d.propOn.valid { return false, xiaomi.ErrNotSupported } + if !d.propOn.valid { + return false, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propOn.siid, d.propOn.piid) - if err != nil { return false, err } - if b, ok := prop.Value.(bool); ok { return b, nil } + if err != nil { + return false, err + } + if b, ok := prop.Value.(bool); ok { + return b, nil + } return false, nil } func (d *thermostatDevice) SetTargetTemp(ctx context.Context, temp float64) error { - if !d.propTargetTemp.valid { return xiaomi.ErrNotSupported } + if !d.propTargetTemp.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid, temp) } func (d *thermostatDevice) GetTargetTemp(ctx context.Context) (float64, error) { - if !d.propTargetTemp.valid { return 0, xiaomi.ErrNotSupported } + if !d.propTargetTemp.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *thermostatDevice) GetCurrentTemp(ctx context.Context) (float64, error) { - if !d.propCurrentTemp.valid { return 0, xiaomi.ErrNotSupported } + if !d.propCurrentTemp.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propCurrentTemp.siid, d.propCurrentTemp.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *thermostatDevice) SetHeating(ctx context.Context, on bool) error { - if !d.propHeating.valid { return xiaomi.ErrNotSupported } + if !d.propHeating.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propHeating.siid, d.propHeating.piid, on) } func (d *thermostatDevice) IsHeating(ctx context.Context) (bool, error) { - if !d.propHeating.valid { return false, xiaomi.ErrNotSupported } + if !d.propHeating.valid { + return false, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propHeating.siid, d.propHeating.piid) - if err != nil { return false, err } - if b, ok := prop.Value.(bool); ok { return b, nil } + if err != nil { + return false, err + } + if b, ok := prop.Value.(bool); ok { + return b, nil + } return false, nil } func (d *thermostatDevice) OnPropsChanged(handler func(props HeaterProps, changed string)) (string, error) { - type field struct{ siid, piid int; name string; fill func(*HeaterProps, interface{}) } + type field struct { + siid, piid int + name string + fill func(*HeaterProps, interface{}) + } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HeaterProps, v interface{}) { s.On, _ = v.(bool) }}) @@ -99,7 +141,9 @@ func (d *thermostatDevice) OnPropsChanged(handler func(props HeaterProps, change if d.propCurrentTemp.valid { fields = append(fields, field{d.propCurrentTemp.siid, d.propCurrentTemp.piid, "temperature", func(s *HeaterProps, v interface{}) { s.CurrentTemp = toFloat(v) }}) } - if len(fields) == 0 { return "", xiaomi.ErrNotSupported } + if len(fields) == 0 { + return "", xiaomi.ErrNotSupported + } return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) { for _, f := range fields { if f.siid == prop.SIID && f.piid == prop.PIID { @@ -114,17 +158,28 @@ func (d *thermostatDevice) OnPropsChanged(handler func(props HeaterProps, change func (d *thermostatDevice) GetProps() HeaterProps { return d.propsCache } func (d *thermostatDevice) FetchProps(ctx context.Context) (HeaterProps, error) { keys := []xiaomi.PropKey{} - if d.propOn.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) } - if d.propTargetTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetTemp.siid, PIID: d.propTargetTemp.piid}) } - if d.propCurrentTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propCurrentTemp.siid, PIID: d.propCurrentTemp.piid}) } + if d.propOn.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) + } + if d.propTargetTemp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetTemp.siid, PIID: d.propTargetTemp.piid}) + } + if d.propCurrentTemp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propCurrentTemp.siid, PIID: d.propCurrentTemp.piid}) + } props, err := d.client.GetProps(ctx, keys) - if err != nil { return d.propsCache, err } + if err != nil { + return d.propsCache, err + } result := HeaterProps{} for _, p := range props { switch { - case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: result.On, _ = p.Value.(bool) - case d.propTargetTemp.valid && p.SIID == d.propTargetTemp.siid && p.PIID == d.propTargetTemp.piid: result.TargetTemp = toFloat(p.Value) - case d.propCurrentTemp.valid && p.SIID == d.propCurrentTemp.siid && p.PIID == d.propCurrentTemp.piid: result.CurrentTemp = toFloat(p.Value) + case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: + result.On, _ = p.Value.(bool) + case d.propTargetTemp.valid && p.SIID == d.propTargetTemp.siid && p.PIID == d.propTargetTemp.piid: + result.TargetTemp = toFloat(p.Value) + case d.propCurrentTemp.valid && p.SIID == d.propCurrentTemp.siid && p.PIID == d.propCurrentTemp.piid: + result.CurrentTemp = toFloat(p.Value) } } d.propsCache = result diff --git a/xiaomi/devices/vacuum.go b/xiaomi/devices/vacuum.go index e1af3f7..0d633d2 100644 --- a/xiaomi/devices/vacuum.go +++ b/xiaomi/devices/vacuum.go @@ -40,13 +40,23 @@ type vacuumDevice struct { func NewVacuum(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Vacuum, error) { d := &vacuumDevice{BaseDevice: NewBaseDevice(client, info)} resolver := d.getSpecResolver() - if resolver == nil { return nil, xiaomi.ErrNoSPEC } + if resolver == nil { + return nil, xiaomi.ErrNoSPEC + } siid, piid, ok := resolver.find("on") - if !ok { return nil, xiaomi.ErrNotSupported } + if !ok { + return nil, xiaomi.ErrNotSupported + } d.propOn = siidPiid{siid, piid, true} - if s, p, ok := resolver.find("mode"); ok { d.propMode = siidPiid{s, p, true} } - if s, p, ok := resolver.find("fan-level"); ok { d.propFanLevel = siidPiid{s, p, true} } - if s, p, ok := resolver.find("battery-level"); ok { d.propBattery = siidPiid{s, p, true} } + if s, p, ok := resolver.find("mode"); ok { + d.propMode = siidPiid{s, p, true} + } + if s, p, ok := resolver.find("fan-level"); ok { + d.propFanLevel = siidPiid{s, p, true} + } + if s, p, ok := resolver.find("battery-level"); ok { + d.propBattery = siidPiid{s, p, true} + } return d, nil } @@ -63,24 +73,37 @@ func (d *vacuumDevice) ReturnToDock(ctx context.Context) error { return err } func (d *vacuumDevice) SetFanLevel(ctx context.Context, level int) error { - if !d.propFanLevel.valid { return xiaomi.ErrNotSupported } + if !d.propFanLevel.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid, level) } func (d *vacuumDevice) GetFanLevel(ctx context.Context) (int, error) { - if !d.propFanLevel.valid { return 0, xiaomi.ErrNotSupported } + if !d.propFanLevel.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toInt(prop.Value), nil } func (d *vacuumDevice) GetStatus(ctx context.Context) (VacuumProps, error) { // 云端实时查询 keys := []xiaomi.PropKey{} add := func(sp siidPiid) { - if sp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: sp.siid, PIID: sp.piid}) } + if sp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: sp.siid, PIID: sp.piid}) + } } - add(d.propOn); add(d.propMode); add(d.propFanLevel); add(d.propBattery) + add(d.propOn) + add(d.propMode) + add(d.propFanLevel) + add(d.propBattery) props, err := d.client.GetProps(ctx, keys) - if err != nil { return VacuumProps{}, err } + if err != nil { + return VacuumProps{}, err + } status := VacuumProps{} for _, p := range props { switch { @@ -98,7 +121,11 @@ func (d *vacuumDevice) GetStatus(ctx context.Context) (VacuumProps, error) { } func (d *vacuumDevice) OnPropsChanged(handler func(props VacuumProps, changed string)) (string, error) { - type field struct{ siid, piid int; name string; fill func(*VacuumProps, interface{}) } + type field struct { + siid, piid int + name string + fill func(*VacuumProps, interface{}) + } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *VacuumProps, v interface{}) { s.On, _ = v.(bool) }}) @@ -112,7 +139,9 @@ func (d *vacuumDevice) OnPropsChanged(handler func(props VacuumProps, changed st if d.propBattery.valid { fields = append(fields, field{d.propBattery.siid, d.propBattery.piid, "battery", func(s *VacuumProps, v interface{}) { s.Battery = toInt(v) }}) } - if len(fields) == 0 { return "", xiaomi.ErrNotSupported } + if len(fields) == 0 { + return "", xiaomi.ErrNotSupported + } return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) { for _, f := range fields { if f.siid == prop.SIID && f.piid == prop.PIID { @@ -127,7 +156,9 @@ func (d *vacuumDevice) OnPropsChanged(handler func(props VacuumProps, changed st func (d *vacuumDevice) GetProps() VacuumProps { return d.propsCache } func (d *vacuumDevice) FetchProps(ctx context.Context) (VacuumProps, error) { status, err := d.GetStatus(ctx) - if err != nil { return d.propsCache, err } + if err != nil { + return d.propsCache, err + } d.propsCache = status return status, nil } diff --git a/xiaomi/devices/water_heater.go b/xiaomi/devices/water_heater.go index bc15e85..849ae53 100644 --- a/xiaomi/devices/water_heater.go +++ b/xiaomi/devices/water_heater.go @@ -38,49 +38,81 @@ type waterHeaterDevice struct { func NewWaterHeater(client *xiaomi.Client, info *xiaomi.DeviceInfo) (WaterHeater, error) { d := &waterHeaterDevice{BaseDevice: NewBaseDevice(client, info)} resolver := d.getSpecResolver() - if resolver == nil { return nil, xiaomi.ErrNoSPEC } + if resolver == nil { + return nil, xiaomi.ErrNoSPEC + } siid, piid, ok := resolver.find("on") - if !ok { return nil, xiaomi.ErrNotSupported } + if !ok { + return nil, xiaomi.ErrNotSupported + } d.propOn = siidPiid{siid, piid, true} - if s, p, ok := resolver.find("target-temperature"); ok { d.propTargetTemp = siidPiid{s, p, true} } - if s, p, ok := resolver.find("temperature"); ok { d.propCurrentTemp = siidPiid{s, p, true} } + if s, p, ok := resolver.find("target-temperature"); ok { + d.propTargetTemp = siidPiid{s, p, true} + } + if s, p, ok := resolver.find("temperature"); ok { + d.propCurrentTemp = siidPiid{s, p, true} + } return d, nil } func (d *waterHeaterDevice) TurnOn(ctx context.Context) error { - if !d.propOn.valid { return xiaomi.ErrNotSupported } + if !d.propOn.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, true) } func (d *waterHeaterDevice) TurnOff(ctx context.Context) error { - if !d.propOn.valid { return xiaomi.ErrNotSupported } + if !d.propOn.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, false) } func (d *waterHeaterDevice) IsOn(ctx context.Context) (bool, error) { - if !d.propOn.valid { return false, xiaomi.ErrNotSupported } + if !d.propOn.valid { + return false, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propOn.siid, d.propOn.piid) - if err != nil { return false, err } - if b, ok := prop.Value.(bool); ok { return b, nil } + if err != nil { + return false, err + } + if b, ok := prop.Value.(bool); ok { + return b, nil + } return false, nil } func (d *waterHeaterDevice) SetTargetTemp(ctx context.Context, temp float64) error { - if !d.propTargetTemp.valid { return xiaomi.ErrNotSupported } + if !d.propTargetTemp.valid { + return xiaomi.ErrNotSupported + } return d.SetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid, temp) } func (d *waterHeaterDevice) GetTargetTemp(ctx context.Context) (float64, error) { - if !d.propTargetTemp.valid { return 0, xiaomi.ErrNotSupported } + if !d.propTargetTemp.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *waterHeaterDevice) GetCurrentTemp(ctx context.Context) (float64, error) { - if !d.propCurrentTemp.valid { return 0, xiaomi.ErrNotSupported } + if !d.propCurrentTemp.valid { + return 0, xiaomi.ErrNotSupported + } prop, err := d.GetProp(ctx, d.propCurrentTemp.siid, d.propCurrentTemp.piid) - if err != nil { return 0, err } + if err != nil { + return 0, err + } return toFloat(prop.Value), nil } func (d *waterHeaterDevice) OnPropsChanged(handler func(props HeaterProps, changed string)) (string, error) { - type field struct{ siid, piid int; name string; fill func(*HeaterProps, interface{}) } + type field struct { + siid, piid int + name string + fill func(*HeaterProps, interface{}) + } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HeaterProps, v interface{}) { s.On, _ = v.(bool) }}) @@ -91,7 +123,9 @@ func (d *waterHeaterDevice) OnPropsChanged(handler func(props HeaterProps, chang if d.propCurrentTemp.valid { fields = append(fields, field{d.propCurrentTemp.siid, d.propCurrentTemp.piid, "temperature", func(s *HeaterProps, v interface{}) { s.CurrentTemp = toFloat(v) }}) } - if len(fields) == 0 { return "", xiaomi.ErrNotSupported } + if len(fields) == 0 { + return "", xiaomi.ErrNotSupported + } return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) { for _, f := range fields { if f.siid == prop.SIID && f.piid == prop.PIID { @@ -106,17 +140,28 @@ func (d *waterHeaterDevice) OnPropsChanged(handler func(props HeaterProps, chang func (d *waterHeaterDevice) GetProps() HeaterProps { return d.propsCache } func (d *waterHeaterDevice) FetchProps(ctx context.Context) (HeaterProps, error) { keys := []xiaomi.PropKey{} - if d.propOn.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) } - if d.propTargetTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetTemp.siid, PIID: d.propTargetTemp.piid}) } - if d.propCurrentTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propCurrentTemp.siid, PIID: d.propCurrentTemp.piid}) } + if d.propOn.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) + } + if d.propTargetTemp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetTemp.siid, PIID: d.propTargetTemp.piid}) + } + if d.propCurrentTemp.valid { + keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propCurrentTemp.siid, PIID: d.propCurrentTemp.piid}) + } props, err := d.client.GetProps(ctx, keys) - if err != nil { return d.propsCache, err } + if err != nil { + return d.propsCache, err + } result := HeaterProps{} for _, p := range props { switch { - case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: result.On, _ = p.Value.(bool) - case d.propTargetTemp.valid && p.SIID == d.propTargetTemp.siid && p.PIID == d.propTargetTemp.piid: result.TargetTemp = toFloat(p.Value) - case d.propCurrentTemp.valid && p.SIID == d.propCurrentTemp.siid && p.PIID == d.propCurrentTemp.piid: result.CurrentTemp = toFloat(p.Value) + case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: + result.On, _ = p.Value.(bool) + case d.propTargetTemp.valid && p.SIID == d.propTargetTemp.siid && p.PIID == d.propTargetTemp.piid: + result.TargetTemp = toFloat(p.Value) + case d.propCurrentTemp.valid && p.SIID == d.propCurrentTemp.siid && p.PIID == d.propCurrentTemp.piid: + result.CurrentTemp = toFloat(p.Value) } } d.propsCache = result diff --git a/xiaomi/oauth.go b/xiaomi/oauth.go index b2fb294..54ace21 100644 --- a/xiaomi/oauth.go +++ b/xiaomi/oauth.go @@ -22,9 +22,13 @@ type TokenResult struct { // AuthURL 生成小米 OAuth 授权页面 URL。 func AuthURL(cfg Config) string { uuid := cfg.UUID - if uuid == "" { uuid = generateUUID() } + if uuid == "" { + uuid = generateUUID() + } redirectURL := cfg.RedirectURL - if redirectURL == "" { redirectURL = miot.OAUTH_REDIRECT_URL } + if redirectURL == "" { + redirectURL = miot.OAUTH_REDIRECT_URL + } oa := miot.NewMIoTOauthClient(miot.OAUTH2_CLIENT_ID, redirectURL, "cn", uuid) return oa.GenAuthURL("", "", nil, false) } @@ -32,9 +36,13 @@ func AuthURL(cfg Config) string { // ExchangeCode 用 authorization code 交换 access token。 func ExchangeCode(ctx context.Context, cfg Config, code string) (*TokenResult, error) { uuid := cfg.UUID - if uuid == "" { uuid = generateUUID() } + if uuid == "" { + uuid = generateUUID() + } redirectURL := cfg.RedirectURL - if redirectURL == "" { redirectURL = miot.OAUTH_REDIRECT_URL } + if redirectURL == "" { + redirectURL = miot.OAUTH_REDIRECT_URL + } oa := miot.NewMIoTOauthClient(miot.OAUTH2_CLIENT_ID, redirectURL, "cn", uuid) result, err := oa.GetAccessToken(code) if err != nil { diff --git a/xiaomi/specs/resolver.go b/xiaomi/specs/resolver.go index 8960099..b8dcbc0 100644 --- a/xiaomi/specs/resolver.go +++ b/xiaomi/specs/resolver.go @@ -17,10 +17,10 @@ import ( // 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 + 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) + actionMap map[int]map[string]int // siid → (actionType → aiid) } // NewPropertyResolver creates a PropertyResolver from a SPEC instance. diff --git a/xiaomi/types.go b/xiaomi/types.go index 521efa3..656d38e 100644 --- a/xiaomi/types.go +++ b/xiaomi/types.go @@ -146,20 +146,20 @@ type DeviceStateHandler func(did string, state DeviceState) type Kind string const ( - KindAirConditioner Kind = "air_conditioner" - KindLight Kind = "light" - KindSwitch Kind = "switch" - KindFan Kind = "fan" - KindCover Kind = "cover" - KindHumidifier Kind = "humidifier" - KindVacuum Kind = "vacuum" - KindWaterHeater Kind = "water_heater" - KindSpeaker Kind = "speaker" - KindGateway Kind = "gateway" - KindPlug Kind = "plug" - KindOccupancySensor Kind = "sensor_occupy" - KindSensor Kind = "sensor" - KindUnknown Kind = "unknown" + KindAirConditioner Kind = "air_conditioner" + KindLight Kind = "light" + KindSwitch Kind = "switch" + KindFan Kind = "fan" + KindCover Kind = "cover" + KindHumidifier Kind = "humidifier" + KindVacuum Kind = "vacuum" + KindWaterHeater Kind = "water_heater" + KindSpeaker Kind = "speaker" + KindGateway Kind = "gateway" + KindPlug Kind = "plug" + KindOccupancySensor Kind = "sensor_occupy" + KindSensor Kind = "sensor" + KindUnknown Kind = "unknown" ) // DeviceKind 从 URN 快速判断设备种类(不依赖 SPEC 加载) diff --git a/xiaomi/types_test.go b/xiaomi/types_test.go index 8d7805c..d1c36e7 100644 --- a/xiaomi/types_test.go +++ b/xiaomi/types_test.go @@ -14,11 +14,11 @@ func TestDeviceInfoParsing(t *testing.T) { "icon": "https://example.com/icon.png", "urn": "urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c1:1", "token": "abc123token", - "local_ip": "192.168.1.100", + "local_ip": "192.168.1.100", "rssi": float64(-55), "ssid": "MyWiFi", "bssid": "AA:BB:CC:DD:EE:FF", - "online": true, + "online": true, "home_id": "home1", "home_name": "我的家", "room_id": "room1",