package devices import ( "context" "fmt" "git.zeroonesoft.cn/golib/xiaomihome/xiaomi" ) // OccupancySensor 人在传感器读取接口(只读)。 type OccupancySensor interface { Device IsOccupied(ctx context.Context) (bool, error) GetIlluminance(ctx context.Context) (float64, error) OnPropsChanged(handler func(occupied bool, illuminance float64)) (string, error) GetProps() (bool, float64) FetchProps(ctx context.Context) (bool, float64, error) } type occupancyDevice struct { BaseDevice 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 } // occupied 根据设备支持的属性类型计算有人状态。 func (d *occupancyDevice) occupied(v interface{}) bool { if d.propOccupancy.valid { return toBool(v) } return toFloat(v) == 0 // no-motion-duration==0 → 有人 } func NewOccupancySensor(client *xiaomi.Client, info *xiaomi.DeviceInfo) (OccupancySensor, error) { 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) } spec := miotDev.SpecInstance() 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} } case "no-motion-duration": 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.propOccupancy.valid && !d.propNoMotion.valid { return nil, fmt.Errorf("%w: no occupancy/no-motion-duration property in SPEC", xiaomi.ErrNotSupported) } return d, nil } // occupiedProp 返回判断有人状态所用的属性。 func (d *occupancyDevice) occupiedProp() siidPiid { if d.propOccupancy.valid { return d.propOccupancy } return d.propNoMotion } 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 } return d.occupied(prop.Value), nil } func (d *occupancyDevice) GetIlluminance(ctx context.Context) (float64, error) { 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 } 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 } 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: d.propsCache[0] = toBool(prop.Value) case d.propNoMotion.valid && prop.SIID == d.propNoMotion.siid && prop.PIID == d.propNoMotion.piid: d.propsCache[0] = toFloat(prop.Value) == 0 case d.propIlluminance.valid && prop.SIID == d.propIlluminance.siid && prop.PIID == d.propIlluminance.piid: d.propsCache[1] = toFloat(prop.Value) } occ, _ := d.propsCache[0].(bool) lux, _ := d.propsCache[1].(float64) handler(occ, lux) }) } func (d *occupancyDevice) GetProps() (bool, float64) { b, _ := d.propsCache[0].(bool) f, _ := d.propsCache[1].(float64) return b, f } 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}) } props, err := d.client.GetProps(ctx, keys) if err != nil { b, _ := d.propsCache[0].(bool) f, _ := d.propsCache[1].(float64) return b, f, err } var occupied bool var illuminance float64 for _, r := range props { switch { case p.valid && r.SIID == p.siid && r.PIID == p.piid: occupied = d.occupied(r.Value) case d.propIlluminance.valid && r.SIID == d.propIlluminance.siid && r.PIID == d.propIlluminance.piid: illuminance = toFloat(r.Value) } } d.propsCache = [2]interface{}{occupied, illuminance} return occupied, illuminance, nil }