Skip to content

Commit d2eafba

Browse files
committed
Add support for presence sensor
1 parent f16306a commit d2eafba

File tree

4 files changed

+82
-3
lines changed

4 files changed

+82
-3
lines changed

src/device.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
import type { Characteristic, Noble, Peripheral, Service } from '@stoprocent/noble'
66

7-
import type { airPurifierServiceData, airPurifierTableServiceData, batteryCirculatorFanServiceData, blindTiltServiceData, botServiceData, ceilingLightProServiceData, ceilingLightServiceData, colorBulbServiceData, contactSensorServiceData, curtain3ServiceData, curtainServiceData, hub2ServiceData, hub3ServiceData, humidifier2ServiceData, humidifierServiceData, keypadDetectorServiceData, lockProServiceData, lockServiceData, meterPlusServiceData, meterProCO2ServiceData, meterProServiceData, meterServiceData, motionSensorServiceData, outdoorMeterServiceData, plugMiniJPServiceData, plugMiniUSServiceData, relaySwitch1PMServiceData, relaySwitch1ServiceData, remoteServiceData, robotVacuumCleanerServiceData, stripLightServiceData, waterLeakDetectorServiceData } from './types/ble.js'
7+
import type { airPurifierServiceData, airPurifierTableServiceData, batteryCirculatorFanServiceData, blindTiltServiceData, botServiceData, ceilingLightProServiceData, ceilingLightServiceData, colorBulbServiceData, contactSensorServiceData, curtain3ServiceData, curtainServiceData, hub2ServiceData, hub3ServiceData, humidifier2ServiceData, humidifierServiceData, keypadDetectorServiceData, lockProServiceData, lockServiceData, meterPlusServiceData, meterProCO2ServiceData, meterProServiceData, meterServiceData, motionSensorServiceData, outdoorMeterServiceData, plugMiniJPServiceData, plugMiniUSServiceData, presenceSensorServiceData, relaySwitch1PMServiceData, relaySwitch1ServiceData, remoteServiceData, robotVacuumCleanerServiceData, stripLightServiceData, waterLeakDetectorServiceData } from './types/ble.js'
88

99
import { Buffer } from 'node:buffer'
1010
import * as Crypto from 'node:crypto'
@@ -93,7 +93,7 @@ export interface ad {
9393
id: string
9494
address: string
9595
rssi: number
96-
serviceData: airPurifierServiceData | airPurifierTableServiceData | botServiceData | colorBulbServiceData | contactSensorServiceData | curtainServiceData | curtain3ServiceData | stripLightServiceData | lockServiceData | lockProServiceData | meterServiceData | meterPlusServiceData | meterProServiceData | meterProCO2ServiceData | motionSensorServiceData | outdoorMeterServiceData | plugMiniUSServiceData | plugMiniJPServiceData | blindTiltServiceData | ceilingLightServiceData | ceilingLightProServiceData | hub2ServiceData | hub3ServiceData | batteryCirculatorFanServiceData | waterLeakDetectorServiceData | humidifierServiceData | humidifier2ServiceData | robotVacuumCleanerServiceData | keypadDetectorServiceData | relaySwitch1PMServiceData | relaySwitch1ServiceData | remoteServiceData
96+
serviceData: airPurifierServiceData | airPurifierTableServiceData | botServiceData | colorBulbServiceData | contactSensorServiceData | curtainServiceData | curtain3ServiceData | stripLightServiceData | lockServiceData | lockProServiceData | meterServiceData | meterPlusServiceData | meterProServiceData | meterProCO2ServiceData | motionSensorServiceData | presenceSensorServiceData | outdoorMeterServiceData | plugMiniUSServiceData | plugMiniJPServiceData | blindTiltServiceData | ceilingLightServiceData | ceilingLightProServiceData | hub2ServiceData | hub3ServiceData | batteryCirculatorFanServiceData | waterLeakDetectorServiceData | humidifierServiceData | humidifier2ServiceData | robotVacuumCleanerServiceData | keypadDetectorServiceData | relaySwitch1PMServiceData | relaySwitch1ServiceData | remoteServiceData
9797
[key: string]: unknown
9898
}
9999

@@ -121,6 +121,7 @@ export declare interface SwitchBotBLEDevice {
121121
Hub3: DeviceInfo
122122
OutdoorMeter: DeviceInfo
123123
MotionSensor: DeviceInfo
124+
PresenceSensor: DeviceInfo
124125
ContactSensor: DeviceInfo
125126
ColorBulb: DeviceInfo
126127
StripLight: DeviceInfo
@@ -154,6 +155,7 @@ export enum SwitchBotModel {
154155
MeterProCO2 = 'W4900010',
155156
OutdoorMeter = 'W3400010',
156157
MotionSensor = 'W1101500',
158+
PresenceSensor = 'W8200000',
157159
ContactSensor = 'W1201500',
158160
ColorBulb = 'W1401400',
159161
StripLight = 'W1701100',
@@ -202,6 +204,7 @@ export enum SwitchBotBLEModel {
202204
Hub3 = 'V',
203205
OutdoorMeter = 'w',
204206
MotionSensor = 's',
207+
PresenceSensor = 'p',
205208
ContactSensor = 'd',
206209
ColorBulb = 'u',
207210
StripLight = 'r',
@@ -237,6 +240,7 @@ export enum SwitchBotBLEModelName {
237240
MeterProCO2 = 'WoSensorTHPc',
238241
Lock = 'WoSmartLock',
239242
LockPro = 'WoSmartLockPro',
243+
PresenceSensor = 'WoPresence',
240244
PlugMini = 'WoPlugMini',
241245
StripLight = 'WoStrip',
242246
OutdoorMeter = 'WoIOSensorTH',
@@ -277,6 +281,7 @@ export enum SwitchBotBLEModelFriendlyName {
277281
OutdoorMeter = 'Outdoor Meter',
278282
ContactSensor = 'Contact Sensor',
279283
MotionSensor = 'Motion Sensor',
284+
PresenceSensor = 'Presence Sensor',
280285
BlindTilt = 'Blind Tilt',
281286
CeilingLight = 'Ceiling Light',
282287
CeilingLightPro = 'Ceiling Light Pro',
@@ -1066,6 +1071,8 @@ export class Advertising {
10661071
return WoAirPurifierTable.parseServiceData(serviceData, manufacturerData, emitLog)
10671072
case SwitchBotBLEModel.MotionSensor:
10681073
return WoPresence.parseServiceData(serviceData, emitLog)
1074+
case SwitchBotBLEModel.PresenceSensor:
1075+
return WoPresence.parsePresenceSensorServiceData(serviceData, manufacturerData, emitLog)
10691076
case SwitchBotBLEModel.ContactSensor:
10701077
return WoContact.parseServiceData(serviceData, emitLog)
10711078
case SwitchBotBLEModel.Remote:
@@ -2741,6 +2748,8 @@ export class WoPlugMiniUS extends SwitchbotDevice {
27412748
}
27422749
}
27432750

2751+
const PRESENCE_SENSOR_BATTERY_RANGE_MAP = ['<10%', '10-19%', '20-59%', '>=60%'] as const
2752+
27442753
/**
27452754
* Class representing a WoPresence device.
27462755
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
@@ -2780,6 +2789,46 @@ export class WoPresence extends SwitchbotDevice {
27802789
return data
27812790
}
27822791

2792+
/**
2793+
* Parses the manufacturer data for presence sensors.
2794+
* @param {Buffer | null} serviceData - The optional service data buffer.
2795+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
2796+
* @param {Function} emitLog - The function to emit log messages.
2797+
* @returns {Promise<presenceSensorServiceData | null>} - Parsed service data or null if invalid.
2798+
*/
2799+
static async parsePresenceSensorServiceData(
2800+
serviceData: Buffer | null,
2801+
manufacturerData: Buffer,
2802+
emitLog: (level: string, message: string) => void,
2803+
): Promise<presenceSensorServiceData | null> {
2804+
if (!manufacturerData || manufacturerData.length < 12) {
2805+
emitLog('debugerror', `[parsePresenceSensorServiceData] Manufacturer buffer length ${manufacturerData?.length ?? 0} < 12!`)
2806+
return null
2807+
}
2808+
2809+
const statusByte = manufacturerData[7]
2810+
const batteryBits = (statusByte >> 2) & 0b11
2811+
2812+
const data: presenceSensorServiceData = {
2813+
model: SwitchBotBLEModel.PresenceSensor,
2814+
modelName: SwitchBotBLEModelName.PresenceSensor,
2815+
modelFriendlyName: SwitchBotBLEModelFriendlyName.PresenceSensor,
2816+
sequenceNumber: manufacturerData[6],
2817+
adaptiveState: !!(statusByte & 0b10000000),
2818+
motionDetected: !!(statusByte & 0b01000000),
2819+
batteryRange: PRESENCE_SENSOR_BATTERY_RANGE_MAP[batteryBits] ?? 'Unknown',
2820+
triggerFlag: manufacturerData[10],
2821+
ledState: !!(manufacturerData[11] & 0b10000000),
2822+
lightLevel: manufacturerData[11] & 0x0F,
2823+
}
2824+
2825+
if (serviceData && serviceData.length >= 3) {
2826+
data.battery = serviceData[2] & 0x7F
2827+
}
2828+
2829+
return data
2830+
}
2831+
27832832
constructor(peripheral: NobleTypes['peripheral'], noble: NobleTypes['noble']) {
27842833
super(peripheral, noble)
27852834
}

src/switchbot-ble.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export class SwitchBotBLE extends EventEmitter {
220220
case SwitchBotBLEModel.Hub2: return new WoHub2(peripheral, this.noble)
221221
case SwitchBotBLEModel.OutdoorMeter: return new WoIOSensorTH(peripheral, this.noble)
222222
case SwitchBotBLEModel.MotionSensor: return new WoPresence(peripheral, this.noble)
223+
case SwitchBotBLEModel.PresenceSensor: return new WoPresence(peripheral, this.noble)
223224
case SwitchBotBLEModel.ContactSensor: return new WoContact(peripheral, this.noble)
224225
case SwitchBotBLEModel.Remote: return new WoRemote(peripheral, this.noble)
225226
case SwitchBotBLEModel.ColorBulb: return new WoBulb(peripheral, this.noble)
@@ -282,7 +283,7 @@ export class SwitchBotBLE extends EventEmitter {
282283
const p = { model: params.model || '', id: params.id || '' }
283284

284285
this.noble.removeAllListeners('discover')
285-
286+
286287
this.noble.on('discover', async (peripheral: NobleTypes['peripheral']) => {
287288
try {
288289
const ad = await Advertising.parse(peripheral, (level: string, message: string) => this.log(level as LogLevel, message))

src/types/ble.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ export type motionSensorServiceData = BLEServiceData & {
195195
lightLevel: string
196196
is_light: boolean
197197
}
198+
export type presenceSensorServiceData = BLEServiceData & {
199+
model: SwitchBotBLEModel.PresenceSensor
200+
modelName: SwitchBotBLEModelName.PresenceSensor
201+
modelFriendlyName: SwitchBotBLEModelFriendlyName.PresenceSensor
202+
sequenceNumber: number
203+
adaptiveState: boolean
204+
motionDetected: boolean
205+
batteryRange: string
206+
triggerFlag: number
207+
ledState: boolean
208+
lightLevel: number
209+
battery?: number
210+
}
198211

199212
export type plugMiniUSServiceData = PlugMiniServiceDataBase & {
200213
model: SwitchBotBLEModel.PlugMiniUS
@@ -424,6 +437,7 @@ export type BLEDeviceServiceData
424437
| meterServiceData
425438
| motionSensorServiceData
426439
| outdoorMeterServiceData
440+
| presenceSensorServiceData
427441
| plugMiniJPServiceData
428442
| plugMiniUSServiceData
429443
| relaySwitch1PMServiceData

src/types/openapi.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export type remote = device
8383

8484
export type motionSensor = device
8585

86+
export type occupancySensor = device
87+
8688
export type contactSensor = device
8789

8890
export type waterLeakDetector = device
@@ -262,6 +264,13 @@ export type motionSensorStatus = deviceStatus & {
262264
brightness: 'bright' | 'dim'
263265
}
264266

267+
export type occupancySensorStatus = deviceStatus & {
268+
battery: number
269+
version: string
270+
Detected: boolean
271+
lightLevel: number // 1~20
272+
}
273+
265274
export type contactSensorStatus = deviceStatus & {
266275
battery: number
267276
moveDetected: boolean
@@ -510,6 +519,12 @@ export type motionSensorWebhookContext = deviceWebhookContext & {
510519
battery: number // 0~100
511520
}
512521

522+
export type occupancySensorWebhookContext = deviceWebhookContext & {
523+
detectionState: 'NOT_DETECTED' | 'DETECTED'
524+
battery: number // 0~100
525+
lightLevel: number // 1~20
526+
}
527+
513528
export type contactSensorWebhookContext = deviceWebhookContext & {
514529
detectionState: 'NOT_DETECTED' | 'DETECTED'
515530
battery: number // 0~100

0 commit comments

Comments
 (0)