package bridge import ( "context" "fmt" "xiaomihome/xiaomi" "xiaomihome/xiaomi/devices" ) // ── 回调 ── // OnPropsChanged 注册全局属性变化回调。props 是 devices 包的类型化 Props 结构体, // 调用方自行 switch 类型(ACProps / LightProps / FanProps ...)。 func (b *Bridge) OnPropsChanged(handler func(id int64, did, deviceType, changed string, props interface{})) { b.mu.Lock() b.onPropsChanged = handler b.mu.Unlock() } // OnOnlineChanged 注册设备在线/离线变化回调。 func (b *Bridge) OnOnlineChanged(handler func(id int64, did string, online bool)) { b.mu.Lock() b.onOnlineChanged = handler b.mu.Unlock() } // StartReceiving 为所有设备注册 MQTT 消息接收回调(MQTT 订阅已在 miot 层完成)。 func (b *Bridge) StartReceiving() { b.mu.RLock() defer b.mu.RUnlock() for did, d := range b.dv { b.subscribeDevice(did, d) } } // subscribeDevice 为单个设备注册 MQTT 属性和在线状态订阅。 func (b *Bridge) subscribeDevice(did string, d interface{}) { devType := deviceTypeName(d) switch ctrl := d.(type) { case devices.AirConditioner: ctrl.OnPropsChanged(func(props devices.ACProps, ch string) { b.fire(did, devType, ch, props) }) case devices.Light: ctrl.OnPropsChanged(func(props devices.LightProps, ch string) { b.fire(did, devType, ch, props) }) case devices.Switch: ctrl.OnPropsChanged(func(ch int, on bool) { b.fire(did, devType, fmt.Sprintf("ch%d", ch), ctrl.GetProps()) }) case devices.Fan: ctrl.OnPropsChanged(func(props devices.FanProps, ch string) { b.fire(did, devType, ch, props) }) case devices.Cover: ctrl.OnPropsChanged(func(props devices.CoverProps) { b.fire(did, devType, devices.PropPosition, props) }) case devices.Humidifier: ctrl.OnPropsChanged(func(props devices.HumidifierProps, ch string) { b.fire(did, devType, ch, props) }) case devices.Vacuum: ctrl.OnPropsChanged(func(props devices.VacuumProps, ch string) { b.fire(did, devType, ch, props) }) case devices.WaterHeater: ctrl.OnPropsChanged(func(props devices.HeaterProps, ch string) { b.fire(did, devType, ch, props) }) case devices.TempHumiditySensor: ctrl.OnPropsChanged(func(temp, hum float64) { b.fire(did, devType, "temperature_humidity", map[string]float64{"temperature": temp, "humidity": hum}) }) case devices.OccupancySensor: ctrl.OnPropsChanged(func(occ bool, lux float64) { b.fire(did, devType, "occupancy", map[string]interface{}{"occupied": occ, "illuminance": lux}) }) case devices.Speaker: ctrl.OnPropsChanged(func(props devices.SpeakerProps, ch string) { b.fire(did, devType, ch, props) }) case devices.Plug: ctrl.OnPropsChanged(func(on bool) { b.fire(did, devType, devices.PropOn, on) }) } // 订阅设备在线/离线 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) if online { go b.client.RefreshDeviceAllProps(did) } }); err != nil { b.lgr.Warnf("[bridge] %d SubDeviceState %s: %v", b.id, did, err) } } } func (b *Bridge) fireOnline(did string, online bool) { b.mu.RLock() cb := b.onOnlineChanged b.mu.RUnlock() if cb != nil { cb(b.id, did, online) } } func (b *Bridge) fire(did, devType, changed string, props interface{}) { b.mu.RLock() cb := b.onPropsChanged b.mu.RUnlock() if cb != nil { cb(b.id, did, devType, changed, props) } } func deviceTypeNameRaw(d interface{}) string { if d == nil { return "unknown" } return deviceTypeName(d) } // ── 状态 ── // FetchAllStates 批量云端拉取所有设备状态,更新缓存。 func (b *Bridge) FetchAllStates(ctx context.Context) { b.mu.RLock() defer b.mu.RUnlock() for _, d := range b.dv { 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) } 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) } case devices.Switch: if s, err := ctrl.FetchProps(ctx); err == nil { b.lgr.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) } 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) } 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) } 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) } 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) } 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) } 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) } case devices.Plug: if s, err := ctrl.FetchProps(ctx); err == nil { b.lgr.Debugf("[bridge] %d Plug state: on=%v", b.id, s.On) } } } }