Files
xiaomihome/xiaomi/devices/occupancy_sensor.go
T
4566704 cdc459b5ff fix(xiaomihome): 模块路径改为 git.zeroonesoft.cn/golib/xiaomihome
- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步
- 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
2026-10-05 08:53:23 +08:00

153 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}