package devices import ( "context" "fmt" "xiaomihome/xiaomi" ) // OccupancySensor 人在传感器读取接口(只读)。 type OccupancySensor interface { Device IsOccupied(ctx context.Context) (bool, error) GetIlluminance(ctx context.Context) (float64, error) OnStateChanged(handler func(occupied bool, illuminance float64)) (string, error) GetState() (bool, float64) FetchState(ctx context.Context) (bool, float64, error) } type occupancyDevice struct { BaseDevice propOccupancy siidPiid propIlluminance siidPiid stateCache [2]interface{} // [0]=bool occupied, [1]=float64 lux } 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": if !d.propOccupancy.valid { d.propOccupancy = siidPiid{svc.IID, prop.PIID, true} } case "illumination": if !d.propIlluminance.valid { d.propIlluminance = siidPiid{svc.IID, prop.PIID, true} } } } } if !d.propOccupancy.valid { return nil, fmt.Errorf("%w: no occupancy property in SPEC", xiaomi.ErrNotSupported) } return d, nil } func (d *occupancyDevice) IsOccupied(ctx context.Context) (bool, error) { prop, err := d.GetProp(ctx, d.propOccupancy.siid, d.propOccupancy.piid) if err != nil { return false, err } if b, ok := prop.Value.(bool); ok { return b, nil } return false, 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) OnStateChanged(handler func(occupied bool, illuminance float64)) (string, error) { if !d.propOccupancy.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: if b, ok := prop.Value.(bool); ok { d.stateCache[0] = b } case d.propIlluminance.valid && prop.SIID == d.propIlluminance.siid && prop.PIID == d.propIlluminance.piid: d.stateCache[1] = toFloat(prop.Value) } occ, _ := d.stateCache[0].(bool) lux, _ := d.stateCache[1].(float64) handler(occ, lux) }) } func (d *occupancyDevice) GetState() (bool, float64) { b, _ := d.stateCache[0].(bool) f, _ := d.stateCache[1].(float64) return b, f } func (d *occupancyDevice) FetchState(ctx context.Context) (bool, float64, error) { keys := []xiaomi.PropKey{} if d.propOccupancy.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOccupancy.siid, PIID: d.propOccupancy.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.stateCache[0].(bool) f, _ := d.stateCache[1].(float64) return b, f, err } var occupied bool var illuminance float64 for _, p := range props { switch { case d.propOccupancy.valid && p.SIID == d.propOccupancy.siid && p.PIID == d.propOccupancy.piid: occupied, _ = p.Value.(bool) case d.propIlluminance.valid && p.SIID == d.propIlluminance.siid && p.PIID == d.propIlluminance.piid: illuminance = toFloat(p.Value) } } d.stateCache = [2]interface{}{occupied, illuminance} return occupied, illuminance, nil }