node-switchbot 3.6.0-beta.0 → 3.6.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +165 -0
- package/CHANGELOG.md +26 -0
- package/dist/device.d.ts +247 -13
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +658 -78
- package/dist/device.js.map +1 -1
- package/dist/device.test.d.ts +2 -0
- package/dist/device.test.d.ts.map +1 -0
- package/dist/device.test.js +152 -0
- package/dist/device.test.js.map +1 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/parameter-checker.d.ts +1 -0
- package/dist/parameter-checker.d.ts.map +1 -1
- package/dist/parameter-checker.js +19 -11
- package/dist/parameter-checker.js.map +1 -1
- package/dist/parameter-checker.test.d.ts +2 -0
- package/dist/parameter-checker.test.d.ts.map +1 -0
- package/dist/parameter-checker.test.js +56 -0
- package/dist/parameter-checker.test.js.map +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +21 -16
- package/dist/settings.js.map +1 -1
- package/dist/settings.test.js +13 -1
- package/dist/settings.test.js.map +1 -1
- package/dist/switchbot-ble.d.ts +8 -11
- package/dist/switchbot-ble.d.ts.map +1 -1
- package/dist/switchbot-ble.js +88 -73
- package/dist/switchbot-ble.js.map +1 -1
- package/dist/switchbot-ble.test.d.ts +2 -0
- package/dist/switchbot-ble.test.d.ts.map +1 -0
- package/dist/switchbot-ble.test.js +32 -0
- package/dist/switchbot-ble.test.js.map +1 -0
- package/dist/switchbot-openapi.d.ts +4 -7
- package/dist/switchbot-openapi.d.ts.map +1 -1
- package/dist/switchbot-openapi.js +19 -10
- package/dist/switchbot-openapi.js.map +1 -1
- package/dist/switchbot-openapi.test.d.ts +2 -0
- package/dist/switchbot-openapi.test.d.ts.map +1 -0
- package/dist/switchbot-openapi.test.js +36 -0
- package/dist/switchbot-openapi.test.js.map +1 -0
- package/dist/types/ble-guards.d.ts +12 -0
- package/dist/types/ble-guards.d.ts.map +1 -0
- package/dist/types/ble-guards.js +10 -0
- package/dist/types/ble-guards.js.map +1 -0
- package/dist/types/ble-guards.test.d.ts +2 -0
- package/dist/types/ble-guards.test.d.ts.map +1 -0
- package/dist/types/ble-guards.test.js +62 -0
- package/dist/types/ble-guards.test.js.map +1 -0
- package/dist/types/{bledevicestatus.d.ts → ble.d.ts} +135 -116
- package/dist/types/ble.d.ts.map +1 -0
- package/dist/types/ble.js +2 -0
- package/dist/types/ble.js.map +1 -0
- package/dist/types/openapi.d.ts +623 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +3 -0
- package/dist/types/openapi.js.map +1 -0
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/Advertising.html +4 -4
- package/docs/classes/ErrorUtils.html +25 -0
- package/docs/classes/ParameterChecker.html +69 -0
- package/docs/classes/SwitchBotBLE.html +9 -11
- package/docs/classes/SwitchBotOpenAPI.html +9 -9
- package/docs/classes/SwitchbotDevice.html +12 -12
- package/docs/classes/ValidationUtils.html +51 -0
- package/docs/classes/WoAirPurifier.html +83 -0
- package/docs/classes/WoAirPurifierTable.html +83 -0
- package/docs/classes/WoBlindTilt.html +21 -21
- package/docs/classes/WoBulb.html +19 -19
- package/docs/classes/WoCeilingLight.html +22 -22
- package/docs/classes/WoContact.html +13 -13
- package/docs/classes/WoCurtain.html +18 -18
- package/docs/classes/WoHand.html +18 -18
- package/docs/classes/WoHub2.html +13 -13
- package/docs/classes/WoHub3.html +52 -0
- package/docs/classes/WoHumi.html +20 -20
- package/docs/classes/WoHumi2.html +20 -20
- package/docs/classes/WoIOSensorTH.html +13 -13
- package/docs/classes/WoKeypad.html +13 -13
- package/docs/classes/WoLeak.html +13 -13
- package/docs/classes/WoPlugMiniJP.html +18 -18
- package/docs/classes/WoPlugMiniUS.html +18 -18
- package/docs/classes/WoPresence.html +13 -13
- package/docs/classes/WoRelaySwitch1.html +15 -15
- package/docs/classes/WoRelaySwitch1PM.html +15 -15
- package/docs/classes/WoRemote.html +13 -13
- package/docs/classes/WoSensorTH.html +12 -12
- package/docs/classes/WoSensorTHPlus.html +12 -12
- package/docs/classes/WoSensorTHPro.html +12 -12
- package/docs/classes/WoSensorTHProCO2.html +12 -12
- package/docs/classes/WoSmartLock.html +23 -23
- package/docs/classes/WoSmartLockPro.html +23 -23
- package/docs/classes/WoStrip.html +20 -20
- package/docs/enums/LogLevel.html +2 -2
- package/docs/enums/SwitchBotBLEModel.html +5 -2
- package/docs/enums/SwitchBotBLEModelFriendlyName.html +9 -2
- package/docs/enums/SwitchBotBLEModelName.html +5 -2
- package/docs/enums/SwitchBotModel.html +5 -2
- package/docs/functions/updateBaseURL.html +3 -0
- package/docs/hierarchy.html +1 -1
- package/docs/interfaces/AdvertisementData.html +2 -2
- package/docs/interfaces/Chars.html +2 -2
- package/docs/interfaces/ColorLightServiceDataBase.html +17 -0
- package/docs/interfaces/ErrorObject.html +2 -2
- package/docs/interfaces/LockBaseServiceData.html +15 -0
- package/docs/interfaces/NobleTypes.html +2 -3
- package/docs/interfaces/Params.html +2 -2
- package/docs/interfaces/PlugMiniServiceDataBase.html +12 -0
- package/docs/interfaces/Rule.html +2 -2
- package/docs/interfaces/ServiceData.html +2 -2
- package/docs/interfaces/SwitchBotBLEDevice.html +5 -2
- package/docs/interfaces/SwitchBotScanner.html +6 -0
- package/docs/interfaces/TemperatureServiceDataBase.html +10 -0
- package/docs/interfaces/WebhookDetail.html +2 -2
- package/docs/interfaces/ad.html +2 -2
- package/docs/interfaces/body.html +2 -2
- package/docs/interfaces/bodyChange.html +2 -2
- package/docs/interfaces/deleteWebhookResponse.html +2 -2
- package/docs/interfaces/device.html +2 -2
- package/docs/interfaces/deviceList.html +2 -2
- package/docs/interfaces/deviceStatus.html +2 -2
- package/docs/interfaces/deviceStatusRequest.html +2 -2
- package/docs/interfaces/deviceWebhook.html +2 -2
- package/docs/interfaces/deviceWebhookContext.html +2 -2
- package/docs/interfaces/devices.html +2 -2
- package/docs/interfaces/infraredRemoteList.html +2 -2
- package/docs/interfaces/irdevice.html +2 -2
- package/docs/interfaces/pushRequest.html +5 -0
- package/docs/interfaces/pushResponse.html +3 -2
- package/docs/interfaces/pushResponseBody.html +3 -0
- package/docs/interfaces/queryWebhookResponse.html +2 -2
- package/docs/interfaces/setupWebhookResponse.html +2 -2
- package/docs/interfaces/updateWebhookResponse.html +2 -2
- package/docs/interfaces/webhookRequest.html +2 -2
- package/docs/modules.html +1 -1
- package/docs/types/BLEDeviceServiceData.html +1 -0
- package/docs/types/{indoorCam.html → IndoorCam.html} +1 -1
- package/docs/types/MacAddress.html +1 -1
- package/docs/types/airPurifier.html +1 -0
- package/docs/types/airPurifierPM25WebhookContext.html +1 -0
- package/docs/types/airPurifierServiceData.html +1 -0
- package/docs/types/airPurifierStatus.html +1 -0
- package/docs/types/airPurifierTable.html +1 -0
- package/docs/types/airPurifierTablePM25WebhookContext.html +1 -0
- package/docs/types/airPurifierTableServiceData.html +1 -0
- package/docs/types/airPurifierTableStatus.html +1 -0
- package/docs/types/airPurifierTableVOC.html +1 -0
- package/docs/types/airPurifierTableVOCStatus.html +1 -0
- package/docs/types/airPurifierTableVOCWebhookContext.html +1 -0
- package/docs/types/airPurifierTableWebhookContext.html +1 -0
- package/docs/types/airPurifierVOC.html +1 -0
- package/docs/types/airPurifierVOCStatus.html +1 -0
- package/docs/types/airPurifierVOCWebhookContext.html +1 -0
- package/docs/types/airPurifierWebhookContext.html +1 -0
- package/docs/types/batteryCirculatorFan.html +1 -1
- package/docs/types/batteryCirculatorFanServiceData.html +1 -1
- package/docs/types/batteryCirculatorFanStatus.html +1 -1
- package/docs/types/batteryCirculatorFanWebhookContext.html +1 -1
- package/docs/types/blindTilt.html +1 -1
- package/docs/types/blindTiltServiceData.html +1 -1
- package/docs/types/blindTiltStatus.html +1 -1
- package/docs/types/blindTiltWebhookContext.html +1 -1
- package/docs/types/bot.html +1 -1
- package/docs/types/botServiceData.html +1 -1
- package/docs/types/botStatus.html +1 -1
- package/docs/types/botWebhookContext.html +1 -1
- package/docs/types/ceilingLight.html +1 -1
- package/docs/types/ceilingLightPro.html +1 -1
- package/docs/types/ceilingLightProServiceData.html +1 -1
- package/docs/types/ceilingLightProStatus.html +1 -1
- package/docs/types/ceilingLightProWebhookContext.html +1 -1
- package/docs/types/ceilingLightServiceData.html +1 -1
- package/docs/types/ceilingLightStatus.html +1 -1
- package/docs/types/ceilingLightWebhookContext.html +1 -1
- package/docs/types/circulatorFanStatus.html +1 -1
- package/docs/types/circulatorFanWebhookContext.html +1 -1
- package/docs/types/colorBulb.html +1 -1
- package/docs/types/colorBulbServiceData.html +1 -1
- package/docs/types/colorBulbStatus.html +1 -1
- package/docs/types/colorBulbWebhookContext.html +1 -1
- package/docs/types/commandType.html +2 -0
- package/docs/types/contactSensor.html +1 -1
- package/docs/types/contactSensorServiceData.html +1 -1
- package/docs/types/contactSensorStatus.html +1 -1
- package/docs/types/contactSensorWebhookContext.html +1 -1
- package/docs/types/curtain.html +1 -1
- package/docs/types/curtain3.html +1 -1
- package/docs/types/curtain3ServiceData.html +1 -1
- package/docs/types/curtain3WebhookContext.html +1 -1
- package/docs/types/curtainServiceData.html +1 -1
- package/docs/types/curtainStatus.html +1 -1
- package/docs/types/curtainWebhookContext.html +1 -1
- package/docs/types/floorCleaningRobotS10.html +1 -1
- package/docs/types/floorCleaningRobotS10Status.html +1 -1
- package/docs/types/floorCleaningRobotS10WebhookContext.html +1 -1
- package/docs/types/hub2.html +1 -1
- package/docs/types/hub2ServiceData.html +1 -1
- package/docs/types/hub2Status.html +1 -1
- package/docs/types/hub2WebhookContext.html +1 -1
- package/docs/types/hub3ServiceData.html +1 -0
- package/docs/types/humidifier.html +1 -1
- package/docs/types/humidifier2ServiceData.html +1 -1
- package/docs/types/humidifier2Status.html +1 -1
- package/docs/types/humidifier2WebhookContext.html +1 -1
- package/docs/types/humidifierServiceData.html +1 -1
- package/docs/types/humidifierStatus.html +1 -1
- package/docs/types/humidifierWebhookContext.html +1 -1
- package/docs/types/indoorCameraWebhookContext.html +1 -1
- package/docs/types/keypad.html +1 -1
- package/docs/types/keypadDetectorServiceData.html +1 -1
- package/docs/types/keypadTouch.html +1 -1
- package/docs/types/keypadTouchWebhookContext.html +1 -1
- package/docs/types/keypadWebhookContext.html +1 -1
- package/docs/types/lock.html +1 -1
- package/docs/types/lockPro.html +1 -1
- package/docs/types/lockProServiceData.html +1 -1
- package/docs/types/lockProStatus.html +1 -1
- package/docs/types/lockProWebhookContext.html +1 -1
- package/docs/types/lockServiceData.html +1 -1
- package/docs/types/lockStatus.html +1 -1
- package/docs/types/lockWebhookContext.html +1 -1
- package/docs/types/meter.html +1 -1
- package/docs/types/meterPlus.html +1 -1
- package/docs/types/meterPlusServiceData.html +1 -1
- package/docs/types/meterPlusStatus.html +1 -1
- package/docs/types/meterPlusWebhookContext.html +1 -1
- package/docs/types/meterPro.html +1 -1
- package/docs/types/meterProCO2ServiceData.html +1 -1
- package/docs/types/meterProCO2Status.html +1 -1
- package/docs/types/meterProCO2WebhookContext.html +1 -1
- package/docs/types/meterProServiceData.html +1 -1
- package/docs/types/meterProStatus.html +1 -1
- package/docs/types/meterProWebhookContext.html +1 -1
- package/docs/types/meterServiceData.html +1 -1
- package/docs/types/meterStatus.html +1 -1
- package/docs/types/meterWebhookContext.html +1 -1
- package/docs/types/motionSensor.html +1 -1
- package/docs/types/motionSensorServiceData.html +1 -1
- package/docs/types/motionSensorStatus.html +1 -1
- package/docs/types/motionSensorWebhookContext.html +1 -1
- package/docs/types/onadvertisement.html +1 -1
- package/docs/types/ondiscover.html +1 -1
- package/docs/types/outdoorMeter.html +1 -1
- package/docs/types/outdoorMeterServiceData.html +1 -1
- package/docs/types/outdoorMeterStatus.html +1 -1
- package/docs/types/outdoorMeterWebhookContext.html +1 -1
- package/docs/types/panTiltCamWebhookContext.html +1 -1
- package/docs/types/pantiltCam.html +1 -1
- package/docs/types/pantiltCam2k.html +1 -1
- package/docs/types/plug.html +1 -1
- package/docs/types/plugMini.html +1 -1
- package/docs/types/plugMiniJPServiceData.html +1 -1
- package/docs/types/plugMiniJPWebhookContext.html +1 -1
- package/docs/types/plugMiniStatus.html +1 -1
- package/docs/types/plugMiniUSServiceData.html +1 -1
- package/docs/types/plugMiniUSWebhookContext.html +1 -1
- package/docs/types/plugStatus.html +1 -1
- package/docs/types/plugWebhookContext.html +1 -1
- package/docs/types/relaySwitch1Context.html +1 -1
- package/docs/types/relaySwitch1PMContext.html +1 -1
- package/docs/types/relaySwitch1PMServiceData.html +1 -1
- package/docs/types/relaySwitch1PMStatus.html +1 -1
- package/docs/types/relaySwitch1ServiceData.html +1 -1
- package/docs/types/relaySwitch1Status.html +1 -1
- package/docs/types/remote.html +1 -1
- package/docs/types/remoteServiceData.html +1 -1
- package/docs/types/robotVacuumCleanerS1.html +1 -1
- package/docs/types/robotVacuumCleanerS1Plus.html +1 -1
- package/docs/types/robotVacuumCleanerS1PlusStatus.html +1 -1
- package/docs/types/robotVacuumCleanerS1PlusWebhookContext.html +1 -1
- package/docs/types/robotVacuumCleanerS1Status.html +1 -1
- package/docs/types/robotVacuumCleanerS1WebhookContext.html +1 -1
- package/docs/types/robotVacuumCleanerServiceData.html +1 -1
- package/docs/types/stripLight.html +1 -1
- package/docs/types/stripLightServiceData.html +1 -1
- package/docs/types/stripLightStatus.html +1 -1
- package/docs/types/stripLightWebhookContext.html +1 -1
- package/docs/types/waterLeakDetector.html +1 -1
- package/docs/types/waterLeakDetectorServiceData.html +1 -1
- package/docs/types/waterLeakDetectorStatus.html +1 -1
- package/docs/types/waterLeakDetectorWebhookContext.html +1 -1
- package/docs/variables/parameterChecker.html +1 -0
- package/docs/variables/urls.html +1 -0
- package/package.json +6 -6
- package/dist/types/bledevicestatus.d.ts.map +0 -1
- package/dist/types/bledevicestatus.js +0 -2
- package/dist/types/bledevicestatus.js.map +0 -1
- package/dist/types/devicelist.d.ts +0 -90
- package/dist/types/devicelist.d.ts.map +0 -1
- package/dist/types/devicelist.js +0 -2
- package/dist/types/devicelist.js.map +0 -1
- package/dist/types/devicepush.d.ts +0 -13
- package/dist/types/devicepush.d.ts.map +0 -1
- package/dist/types/devicepush.js +0 -2
- package/dist/types/devicepush.js.map +0 -1
- package/dist/types/deviceresponse.d.ts +0 -12
- package/dist/types/deviceresponse.d.ts.map +0 -1
- package/dist/types/deviceresponse.js +0 -2
- package/dist/types/deviceresponse.js.map +0 -1
- package/dist/types/devicestatus.d.ts +0 -194
- package/dist/types/devicestatus.d.ts.map +0 -1
- package/dist/types/devicestatus.js +0 -3
- package/dist/types/devicestatus.js.map +0 -1
- package/dist/types/devicewebhookstatus.d.ts +0 -236
- package/dist/types/devicewebhookstatus.d.ts.map +0 -1
- package/dist/types/devicewebhookstatus.js +0 -2
- package/dist/types/devicewebhookstatus.js.map +0 -1
- package/dist/types/irdevicelist.d.ts +0 -10
- package/dist/types/irdevicelist.d.ts.map +0 -1
- package/dist/types/irdevicelist.js +0 -2
- package/dist/types/irdevicelist.js.map +0 -1
- package/docs/interfaces/switchbot.html +0 -3
- package/jest.config.js +0 -3
package/dist/device.js
CHANGED
|
@@ -1,20 +1,84 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import * as Crypto from 'node:crypto';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
|
-
import { parameterChecker } from './parameter-checker.js';
|
|
5
4
|
import { CHAR_UUID_DEVICE, CHAR_UUID_NOTIFY, CHAR_UUID_WRITE, READ_TIMEOUT_MSEC, SERV_UUID_PRIMARY, WoSmartLockCommands, WoSmartLockProCommands, WRITE_TIMEOUT_MSEC } from './settings.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Command constants for various SwitchBot devices.
|
|
7
|
+
* Using readonly arrays to ensure immutability and better type safety.
|
|
8
|
+
*/
|
|
9
|
+
const DEVICE_COMMANDS = {
|
|
10
|
+
BLIND_TILT: {
|
|
11
|
+
OPEN: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x32],
|
|
12
|
+
CLOSE_UP: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x64],
|
|
13
|
+
CLOSE_DOWN: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x00],
|
|
14
|
+
PAUSE: [0x57, 0x0F, 0x45, 0x01, 0x00, 0xFF],
|
|
15
|
+
},
|
|
16
|
+
BULB: {
|
|
17
|
+
BASE: [0x57, 0x0F, 0x47, 0x01],
|
|
18
|
+
READ_STATE: [0x57, 0x0F, 0x48, 0x01],
|
|
19
|
+
TURN_ON: [0x01, 0x01],
|
|
20
|
+
TURN_OFF: [0x01, 0x02],
|
|
21
|
+
SET_BRIGHTNESS: [0x02, 0x14],
|
|
22
|
+
SET_COLOR_TEMP: [0x02, 0x17],
|
|
23
|
+
SET_RGB: [0x02, 0x12],
|
|
24
|
+
},
|
|
25
|
+
HUMIDIFIER: {
|
|
26
|
+
HEADER: '5701',
|
|
27
|
+
TURN_ON: '570101',
|
|
28
|
+
TURN_OFF: '570102',
|
|
29
|
+
INCREASE: '570103',
|
|
30
|
+
DECREASE: '570104',
|
|
31
|
+
SET_AUTO_MODE: '570105',
|
|
32
|
+
SET_MANUAL_MODE: '570106',
|
|
33
|
+
},
|
|
34
|
+
AIR_PURIFIER: {
|
|
35
|
+
TURN_ON: [0x57, 0x01, 0x01],
|
|
36
|
+
TURN_OFF: [0x57, 0x01, 0x02],
|
|
37
|
+
SET_MODE: [0x57, 0x02],
|
|
38
|
+
SET_SPEED: [0x57, 0x03],
|
|
39
|
+
},
|
|
40
|
+
// Common commands used across multiple devices
|
|
41
|
+
COMMON: {
|
|
42
|
+
POWER_ON: [0x57, 0x01, 0x01],
|
|
43
|
+
POWER_OFF: [0x57, 0x01, 0x02],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Air quality level constants for air purifier devices.
|
|
48
|
+
*/
|
|
49
|
+
const AIR_QUALITY_LEVELS = {
|
|
50
|
+
EXCELLENT: 'excellent',
|
|
51
|
+
GOOD: 'good',
|
|
52
|
+
FAIR: 'fair',
|
|
53
|
+
POOR: 'poor',
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Air purifier mode constants.
|
|
57
|
+
*/
|
|
58
|
+
const AIR_PURIFIER_MODES = {
|
|
59
|
+
MANUAL: 'manual',
|
|
60
|
+
AUTO: 'auto',
|
|
61
|
+
SLEEP: 'sleep',
|
|
62
|
+
LEVEL_1: 'level_1',
|
|
63
|
+
LEVEL_2: 'level_2',
|
|
64
|
+
LEVEL_3: 'level_3',
|
|
65
|
+
};
|
|
66
|
+
// Legacy constants for backward compatibility
|
|
67
|
+
const BLIND_TILT_COMMANDS = DEVICE_COMMANDS.BLIND_TILT;
|
|
68
|
+
const BULB_COMMANDS = DEVICE_COMMANDS.BULB;
|
|
69
|
+
const HUMIDIFIER_COMMAND_HEADER = DEVICE_COMMANDS.HUMIDIFIER.HEADER;
|
|
70
|
+
const TURN_ON_KEY = DEVICE_COMMANDS.HUMIDIFIER.TURN_ON;
|
|
71
|
+
const TURN_OFF_KEY = DEVICE_COMMANDS.HUMIDIFIER.TURN_OFF;
|
|
72
|
+
const INCREASE_KEY = DEVICE_COMMANDS.HUMIDIFIER.INCREASE;
|
|
73
|
+
const DECREASE_KEY = DEVICE_COMMANDS.HUMIDIFIER.DECREASE;
|
|
74
|
+
const SET_AUTO_MODE_KEY = DEVICE_COMMANDS.HUMIDIFIER.SET_AUTO_MODE;
|
|
75
|
+
const SET_MANUAL_MODE_KEY = DEVICE_COMMANDS.HUMIDIFIER.SET_MANUAL_MODE;
|
|
13
76
|
export var SwitchBotModel;
|
|
14
77
|
(function (SwitchBotModel) {
|
|
15
78
|
SwitchBotModel["HubMini"] = "W0202200";
|
|
16
79
|
SwitchBotModel["HubPlus"] = "SwitchBot Hub S1";
|
|
17
80
|
SwitchBotModel["Hub2"] = "W3202100";
|
|
81
|
+
SwitchBotModel["Hub3"] = "W3302100";
|
|
18
82
|
SwitchBotModel["Bot"] = "SwitchBot S1";
|
|
19
83
|
SwitchBotModel["Curtain"] = "W0701600";
|
|
20
84
|
SwitchBotModel["Curtain3"] = "W2400000";
|
|
@@ -58,6 +122,8 @@ export var SwitchBotModel;
|
|
|
58
122
|
SwitchBotModel["RelaySwitch1"] = "W5502300";
|
|
59
123
|
SwitchBotModel["RelaySwitch1PM"] = "W5502310";
|
|
60
124
|
SwitchBotModel["Unknown"] = "Unknown";
|
|
125
|
+
SwitchBotModel["AirPurifier"] = "W5302300";
|
|
126
|
+
SwitchBotModel["AirPurifierTable"] = "W5302310";
|
|
61
127
|
})(SwitchBotModel || (SwitchBotModel = {}));
|
|
62
128
|
export var SwitchBotBLEModel;
|
|
63
129
|
(function (SwitchBotBLEModel) {
|
|
@@ -71,6 +137,7 @@ export var SwitchBotBLEModel;
|
|
|
71
137
|
SwitchBotBLEModel["MeterPro"] = "4";
|
|
72
138
|
SwitchBotBLEModel["MeterProCO2"] = "5";
|
|
73
139
|
SwitchBotBLEModel["Hub2"] = "v";
|
|
140
|
+
SwitchBotBLEModel["Hub3"] = "V";
|
|
74
141
|
SwitchBotBLEModel["OutdoorMeter"] = "w";
|
|
75
142
|
SwitchBotBLEModel["MotionSensor"] = "s";
|
|
76
143
|
SwitchBotBLEModel["ContactSensor"] = "d";
|
|
@@ -89,11 +156,14 @@ export var SwitchBotBLEModel;
|
|
|
89
156
|
SwitchBotBLEModel["RelaySwitch1PM"] = "<";
|
|
90
157
|
SwitchBotBLEModel["Remote"] = "b";
|
|
91
158
|
SwitchBotBLEModel["Unknown"] = "Unknown";
|
|
159
|
+
SwitchBotBLEModel["AirPurifier"] = "+";
|
|
160
|
+
SwitchBotBLEModel["AirPurifierTable"] = "7";
|
|
92
161
|
})(SwitchBotBLEModel || (SwitchBotBLEModel = {}));
|
|
93
162
|
export var SwitchBotBLEModelName;
|
|
94
163
|
(function (SwitchBotBLEModelName) {
|
|
95
164
|
SwitchBotBLEModelName["Bot"] = "WoHand";
|
|
96
165
|
SwitchBotBLEModelName["Hub2"] = "WoHub2";
|
|
166
|
+
SwitchBotBLEModelName["Hub3"] = "WoHub3";
|
|
97
167
|
SwitchBotBLEModelName["ColorBulb"] = "WoBulb";
|
|
98
168
|
SwitchBotBLEModelName["Curtain"] = "WoCurtain";
|
|
99
169
|
SwitchBotBLEModelName["Curtain3"] = "WoCurtain3";
|
|
@@ -118,12 +188,15 @@ export var SwitchBotBLEModelName;
|
|
|
118
188
|
SwitchBotBLEModelName["RelaySwitch1"] = "WoRelaySwitch1Plus";
|
|
119
189
|
SwitchBotBLEModelName["RelaySwitch1PM"] = "WoRelaySwitch1PM";
|
|
120
190
|
SwitchBotBLEModelName["Remote"] = "WoRemote";
|
|
191
|
+
SwitchBotBLEModelName["AirPurifier"] = "WoAirPurifier";
|
|
192
|
+
SwitchBotBLEModelName["AirPurifierTable"] = "WoAirPurifierTable";
|
|
121
193
|
SwitchBotBLEModelName["Unknown"] = "Unknown";
|
|
122
194
|
})(SwitchBotBLEModelName || (SwitchBotBLEModelName = {}));
|
|
123
195
|
export var SwitchBotBLEModelFriendlyName;
|
|
124
196
|
(function (SwitchBotBLEModelFriendlyName) {
|
|
125
197
|
SwitchBotBLEModelFriendlyName["Bot"] = "Bot";
|
|
126
198
|
SwitchBotBLEModelFriendlyName["Hub2"] = "Hub 2";
|
|
199
|
+
SwitchBotBLEModelFriendlyName["Hub3"] = "Hub 3";
|
|
127
200
|
SwitchBotBLEModelFriendlyName["ColorBulb"] = "Color Bulb";
|
|
128
201
|
SwitchBotBLEModelFriendlyName["Curtain"] = "Curtain";
|
|
129
202
|
SwitchBotBLEModelFriendlyName["Curtain3"] = "Curtain 3";
|
|
@@ -150,7 +223,13 @@ export var SwitchBotBLEModelFriendlyName;
|
|
|
150
223
|
SwitchBotBLEModelFriendlyName["RelaySwitch1"] = "Relay Switch 1";
|
|
151
224
|
SwitchBotBLEModelFriendlyName["RelaySwitch1PM"] = "Relay Switch 1PM";
|
|
152
225
|
SwitchBotBLEModelFriendlyName["Remote"] = "Remote";
|
|
226
|
+
SwitchBotBLEModelFriendlyName["AirPurifier"] = "Air Purifier";
|
|
227
|
+
SwitchBotBLEModelFriendlyName["AirPurifierTable"] = "Air Purifier Table";
|
|
153
228
|
SwitchBotBLEModelFriendlyName["Unknown"] = "Unknown";
|
|
229
|
+
SwitchBotBLEModelFriendlyName["AirPurifierVOC"] = "Air Purifier VOC";
|
|
230
|
+
SwitchBotBLEModelFriendlyName["AirPurifierTableVOC"] = "Air Purifier Table VOC";
|
|
231
|
+
SwitchBotBLEModelFriendlyName["AirPurifierPM2_5"] = "Air Purifier PM2.5";
|
|
232
|
+
SwitchBotBLEModelFriendlyName["AirPurifierTablePM2_5"] = "Air Purifier Table PM2.5";
|
|
154
233
|
})(SwitchBotBLEModelFriendlyName || (SwitchBotBLEModelFriendlyName = {}));
|
|
155
234
|
/**
|
|
156
235
|
* Enum for log levels.
|
|
@@ -166,6 +245,176 @@ export var LogLevel;
|
|
|
166
245
|
LogLevel["DEBUG"] = "debug";
|
|
167
246
|
LogLevel["INFO"] = "info";
|
|
168
247
|
})(LogLevel || (LogLevel = {}));
|
|
248
|
+
/**
|
|
249
|
+
* Utility class for comprehensive input validation with improved error messages.
|
|
250
|
+
*/
|
|
251
|
+
export class ValidationUtils {
|
|
252
|
+
/**
|
|
253
|
+
* Validates percentage value (0-100).
|
|
254
|
+
* @param value - The value to validate
|
|
255
|
+
* @param paramName - The parameter name for error reporting
|
|
256
|
+
* @throws {RangeError} When value is not within valid range
|
|
257
|
+
* @throws {TypeError} When value is not a number
|
|
258
|
+
*/
|
|
259
|
+
static validatePercentage(value, paramName = 'value') {
|
|
260
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
261
|
+
throw new TypeError(`${paramName} must be a valid number, got: ${value}`);
|
|
262
|
+
}
|
|
263
|
+
if (value < 0 || value > 100) {
|
|
264
|
+
throw new RangeError(`${paramName} must be between 0 and 100 inclusive, got: ${value}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Validates RGB color value (0-255).
|
|
269
|
+
* @param value - The color value to validate
|
|
270
|
+
* @param colorName - The color name for error reporting
|
|
271
|
+
* @throws {RangeError} When value is not within valid range
|
|
272
|
+
* @throws {TypeError} When value is not a number
|
|
273
|
+
*/
|
|
274
|
+
static validateRGB(value, colorName = 'color') {
|
|
275
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
276
|
+
throw new TypeError(`${colorName} must be a valid number, got: ${value}`);
|
|
277
|
+
}
|
|
278
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
279
|
+
throw new RangeError(`${colorName} must be an integer between 0 and 255 inclusive, got: ${value}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Validates buffer and throws descriptive error.
|
|
284
|
+
* @param buffer - The buffer to validate
|
|
285
|
+
* @param expectedLength - Optional expected length
|
|
286
|
+
* @param paramName - The parameter name for error reporting
|
|
287
|
+
* @throws {TypeError} When buffer is not a Buffer
|
|
288
|
+
* @throws {RangeError} When buffer length doesn't match expected
|
|
289
|
+
*/
|
|
290
|
+
static validateBuffer(buffer, expectedLength, paramName = 'buffer') {
|
|
291
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
292
|
+
throw new TypeError(`${paramName} must be a Buffer instance, got: ${typeof buffer}`);
|
|
293
|
+
}
|
|
294
|
+
if (expectedLength !== undefined && buffer.length !== expectedLength) {
|
|
295
|
+
throw new RangeError(`${paramName} must have exactly ${expectedLength} bytes, got: ${buffer.length} bytes`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Validates string input with comprehensive checks.
|
|
300
|
+
* @param value - The value to validate
|
|
301
|
+
* @param paramName - The parameter name for error reporting
|
|
302
|
+
* @param minLength - Minimum required length
|
|
303
|
+
* @param maxLength - Optional maximum length
|
|
304
|
+
* @throws {TypeError} When value is not a string
|
|
305
|
+
* @throws {RangeError} When string length is invalid
|
|
306
|
+
*/
|
|
307
|
+
static validateString(value, paramName = 'value', minLength = 1, maxLength) {
|
|
308
|
+
if (typeof value !== 'string') {
|
|
309
|
+
throw new TypeError(`${paramName} must be a string, got: ${typeof value}`);
|
|
310
|
+
}
|
|
311
|
+
if (value.length < minLength) {
|
|
312
|
+
throw new RangeError(`${paramName} must have at least ${minLength} character(s), got: ${value.length}`);
|
|
313
|
+
}
|
|
314
|
+
if (maxLength !== undefined && value.length > maxLength) {
|
|
315
|
+
throw new RangeError(`${paramName} must have at most ${maxLength} character(s), got: ${value.length}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Validates numeric range with enhanced checks.
|
|
320
|
+
* @param value - The value to validate
|
|
321
|
+
* @param min - Minimum allowed value
|
|
322
|
+
* @param max - Maximum allowed value
|
|
323
|
+
* @param paramName - The parameter name for error reporting
|
|
324
|
+
* @param mustBeInteger - Whether the value must be an integer
|
|
325
|
+
* @throws {TypeError} When value is not a number
|
|
326
|
+
* @throws {RangeError} When value is outside valid range
|
|
327
|
+
*/
|
|
328
|
+
static validateRange(value, min, max, paramName = 'value', mustBeInteger = false) {
|
|
329
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
330
|
+
throw new TypeError(`${paramName} must be a valid number, got: ${value}`);
|
|
331
|
+
}
|
|
332
|
+
if (mustBeInteger && !Number.isInteger(value)) {
|
|
333
|
+
throw new TypeError(`${paramName} must be an integer, got: ${value}`);
|
|
334
|
+
}
|
|
335
|
+
if (value < min || value > max) {
|
|
336
|
+
throw new RangeError(`${paramName} must be between ${min} and ${max} inclusive, got: ${value}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Validates MAC address format.
|
|
341
|
+
* @param address - The MAC address to validate
|
|
342
|
+
* @param paramName - The parameter name for error reporting
|
|
343
|
+
* @throws {TypeError} When address is not a string
|
|
344
|
+
* @throws {Error} When address format is invalid
|
|
345
|
+
*/
|
|
346
|
+
static validateMacAddress(address, paramName = 'address') {
|
|
347
|
+
if (typeof address !== 'string') {
|
|
348
|
+
throw new TypeError(`${paramName} must be a string`);
|
|
349
|
+
}
|
|
350
|
+
const macRegex = /^(?:[0-9A-F]{2}[:-]){5}[0-9A-F]{2}$|^[0-9A-F]{12}$/i;
|
|
351
|
+
if (!macRegex.test(address)) {
|
|
352
|
+
throw new Error(`${paramName} must be a valid MAC address format, got: ${address}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Validates that a value is one of the allowed enum values.
|
|
357
|
+
* @param value - The value to validate
|
|
358
|
+
* @param allowedValues - Array of allowed values
|
|
359
|
+
* @param paramName - The parameter name for error reporting
|
|
360
|
+
* @throws {Error} When value is not in allowed values
|
|
361
|
+
*/
|
|
362
|
+
static validateEnum(value, allowedValues, paramName = 'value') {
|
|
363
|
+
if (!allowedValues.includes(value)) {
|
|
364
|
+
throw new Error(`${paramName} must be one of: ${allowedValues.join(', ')}, got: ${value}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Enhanced error handling utilities.
|
|
370
|
+
*/
|
|
371
|
+
export class ErrorUtils {
|
|
372
|
+
/**
|
|
373
|
+
* Creates a timeout error with context.
|
|
374
|
+
* @param operation - The operation that timed out
|
|
375
|
+
* @param timeoutMs - The timeout duration in milliseconds
|
|
376
|
+
* @returns A descriptive timeout error
|
|
377
|
+
*/
|
|
378
|
+
static createTimeoutError(operation, timeoutMs) {
|
|
379
|
+
return new Error(`Operation '${operation}' timed out after ${timeoutMs}ms`);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Creates a connection error with context.
|
|
383
|
+
* @param deviceId - The device ID that failed to connect
|
|
384
|
+
* @param cause - The underlying cause of the connection failure
|
|
385
|
+
* @returns A descriptive connection error
|
|
386
|
+
*/
|
|
387
|
+
static createConnectionError(deviceId, cause) {
|
|
388
|
+
const message = `Failed to connect to device ${deviceId}`;
|
|
389
|
+
return cause ? new Error(`${message}: ${cause.message}`) : new Error(message);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Creates a command error with context.
|
|
393
|
+
* @param command - The command that failed
|
|
394
|
+
* @param deviceId - The device ID
|
|
395
|
+
* @param cause - The underlying cause
|
|
396
|
+
* @returns A descriptive command error
|
|
397
|
+
*/
|
|
398
|
+
static createCommandError(command, deviceId, cause) {
|
|
399
|
+
const message = `Command '${command}' failed for device ${deviceId}`;
|
|
400
|
+
return cause ? new Error(`${message}: ${cause.message}`) : new Error(message);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Wraps an async operation with timeout and enhanced error handling.
|
|
404
|
+
* @param operation - The async operation to wrap
|
|
405
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
406
|
+
* @param operationName - Name of the operation for error messages
|
|
407
|
+
* @returns Promise that resolves with the operation result or rejects with timeout
|
|
408
|
+
*/
|
|
409
|
+
static async withTimeout(operation, timeoutMs, operationName) {
|
|
410
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
411
|
+
setTimeout(() => {
|
|
412
|
+
reject(this.createTimeoutError(operationName, timeoutMs));
|
|
413
|
+
}, timeoutMs);
|
|
414
|
+
});
|
|
415
|
+
return Promise.race([operation, timeoutPromise]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
169
418
|
/**
|
|
170
419
|
* Represents a Switchbot Device.
|
|
171
420
|
*/
|
|
@@ -345,8 +594,9 @@ export class SwitchbotDevice extends EventEmitter {
|
|
|
345
594
|
* @param service The service to discover characteristics for.
|
|
346
595
|
* @returns A Promise that resolves with the list of characteristics.
|
|
347
596
|
*/
|
|
348
|
-
|
|
349
|
-
|
|
597
|
+
// Discover characteristics without extra async/await
|
|
598
|
+
discoverCharacteristics(service) {
|
|
599
|
+
return service.discoverCharacteristicsAsync([]);
|
|
350
600
|
}
|
|
351
601
|
/**
|
|
352
602
|
* Subscribes to the notify characteristic.
|
|
@@ -404,12 +654,20 @@ export class SwitchbotDevice extends EventEmitter {
|
|
|
404
654
|
*/
|
|
405
655
|
async getDeviceName() {
|
|
406
656
|
await this.internalConnect();
|
|
407
|
-
|
|
408
|
-
|
|
657
|
+
try {
|
|
658
|
+
if (!this.characteristics?.device) {
|
|
659
|
+
throw new Error(`Characteristic ${CHAR_UUID_DEVICE} not supported`);
|
|
660
|
+
}
|
|
661
|
+
const buf = await this.readCharacteristic(this.characteristics.device);
|
|
662
|
+
return buf.toString('utf8');
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
const deviceContext = `device ${this.deviceId || 'unknown'}`;
|
|
666
|
+
throw ErrorUtils.createCommandError('getDeviceName', deviceContext, error);
|
|
667
|
+
}
|
|
668
|
+
finally {
|
|
669
|
+
await this.internalDisconnect();
|
|
409
670
|
}
|
|
410
|
-
const buf = await this.readCharacteristic(this.characteristics.device);
|
|
411
|
-
await this.internalDisconnect();
|
|
412
|
-
return buf.toString('utf8');
|
|
413
671
|
}
|
|
414
672
|
/**
|
|
415
673
|
* Sets the device name.
|
|
@@ -417,17 +675,26 @@ export class SwitchbotDevice extends EventEmitter {
|
|
|
417
675
|
* @returns A Promise that resolves when the name is set.
|
|
418
676
|
*/
|
|
419
677
|
async setDeviceName(name) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
678
|
+
ValidationUtils.validateString(name, 'name', 1);
|
|
679
|
+
// Additional validation for device name length
|
|
680
|
+
const nameBuffer = Buffer.from(name, 'utf8');
|
|
681
|
+
if (nameBuffer.length > 100) {
|
|
682
|
+
throw new RangeError('Device name cannot exceed 100 bytes when encoded as UTF-8');
|
|
423
683
|
}
|
|
424
|
-
const buf = Buffer.from(name, 'utf8');
|
|
425
684
|
await this.internalConnect();
|
|
426
|
-
|
|
427
|
-
|
|
685
|
+
try {
|
|
686
|
+
if (!this.characteristics?.device) {
|
|
687
|
+
throw new Error(`Characteristic ${CHAR_UUID_DEVICE} not supported`);
|
|
688
|
+
}
|
|
689
|
+
await this.writeCharacteristic(this.characteristics.device, nameBuffer);
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
const deviceContext = `device ${this.deviceId || 'unknown'}`;
|
|
693
|
+
throw ErrorUtils.createCommandError('setDeviceName', deviceContext, error);
|
|
694
|
+
}
|
|
695
|
+
finally {
|
|
696
|
+
await this.internalDisconnect();
|
|
428
697
|
}
|
|
429
|
-
await this.writeCharacteristic(this.characteristics.device, buf);
|
|
430
|
-
await this.internalDisconnect();
|
|
431
698
|
}
|
|
432
699
|
/**
|
|
433
700
|
* Sends a command to the device and awaits a response.
|
|
@@ -435,17 +702,24 @@ export class SwitchbotDevice extends EventEmitter {
|
|
|
435
702
|
* @returns A Promise that resolves with the response buffer.
|
|
436
703
|
*/
|
|
437
704
|
async command(reqBuf) {
|
|
438
|
-
|
|
439
|
-
throw new TypeError('The specified data is not acceptable for writing.');
|
|
440
|
-
}
|
|
705
|
+
ValidationUtils.validateBuffer(reqBuf, undefined, 'reqBuf');
|
|
441
706
|
await this.internalConnect();
|
|
442
707
|
if (!this.characteristics?.write) {
|
|
443
|
-
throw new Error('No
|
|
708
|
+
throw new Error('No write characteristic available for command execution');
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
await this.writeCharacteristic(this.characteristics.write, reqBuf);
|
|
712
|
+
const resBuf = await this.waitForCommandResponse();
|
|
713
|
+
return resBuf;
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
const deviceContext = `device ${this.deviceId || 'unknown'}`;
|
|
717
|
+
// Use ErrorUtils for enriched error context
|
|
718
|
+
throw ErrorUtils.createCommandError('execute command', deviceContext, error);
|
|
719
|
+
}
|
|
720
|
+
finally {
|
|
721
|
+
await this.internalDisconnect();
|
|
444
722
|
}
|
|
445
|
-
await this.writeCharacteristic(this.characteristics.write, reqBuf);
|
|
446
|
-
const resBuf = await this.waitForCommandResponse();
|
|
447
|
-
await this.internalDisconnect();
|
|
448
|
-
return resBuf;
|
|
449
723
|
}
|
|
450
724
|
/**
|
|
451
725
|
* Waits for a response from the device after sending a command.
|
|
@@ -468,41 +742,33 @@ export class SwitchbotDevice extends EventEmitter {
|
|
|
468
742
|
return await Promise.race([readPromise, timeoutPromise]);
|
|
469
743
|
}
|
|
470
744
|
/**
|
|
471
|
-
* Reads data from a characteristic with
|
|
745
|
+
* Reads data from a characteristic with enhanced timeout and error handling.
|
|
472
746
|
* @param char The characteristic to read from.
|
|
473
747
|
* @returns A Promise that resolves with the data buffer.
|
|
474
748
|
*/
|
|
475
749
|
async readCharacteristic(char) {
|
|
476
|
-
const timer = setTimeout(() => {
|
|
477
|
-
throw new Error('READ_TIMEOUT');
|
|
478
|
-
}, READ_TIMEOUT_MSEC);
|
|
479
750
|
try {
|
|
480
|
-
|
|
481
|
-
clearTimeout(timer);
|
|
482
|
-
return result;
|
|
751
|
+
return await ErrorUtils.withTimeout(char.readAsync(), READ_TIMEOUT_MSEC, `read characteristic ${char.uuid}`);
|
|
483
752
|
}
|
|
484
753
|
catch (error) {
|
|
485
|
-
|
|
486
|
-
throw error;
|
|
754
|
+
const deviceContext = `device ${this.deviceId || 'unknown'}`;
|
|
755
|
+
throw ErrorUtils.createCommandError(`read characteristic ${char.uuid}`, deviceContext, error);
|
|
487
756
|
}
|
|
488
757
|
}
|
|
489
758
|
/**
|
|
490
|
-
* Writes data to a characteristic with
|
|
759
|
+
* Writes data to a characteristic with enhanced timeout and error handling.
|
|
491
760
|
* @param char The characteristic to write to.
|
|
492
761
|
* @param buf The data buffer.
|
|
493
762
|
* @returns A Promise that resolves when the write is complete.
|
|
494
763
|
*/
|
|
495
764
|
async writeCharacteristic(char, buf) {
|
|
496
|
-
|
|
497
|
-
throw new Error('WRITE_TIMEOUT');
|
|
498
|
-
}, WRITE_TIMEOUT_MSEC);
|
|
765
|
+
ValidationUtils.validateBuffer(buf, undefined, 'write buffer');
|
|
499
766
|
try {
|
|
500
|
-
await char.writeAsync(buf, false);
|
|
501
|
-
clearTimeout(timer);
|
|
767
|
+
return await ErrorUtils.withTimeout(char.writeAsync(buf, false), WRITE_TIMEOUT_MSEC, `write to characteristic ${char.uuid}`);
|
|
502
768
|
}
|
|
503
769
|
catch (error) {
|
|
504
|
-
|
|
505
|
-
throw error;
|
|
770
|
+
const deviceContext = `device ${this.deviceId || 'unknown'}`;
|
|
771
|
+
throw ErrorUtils.createCommandError(`write to characteristic ${char.uuid}`, deviceContext, error);
|
|
506
772
|
}
|
|
507
773
|
}
|
|
508
774
|
}
|
|
@@ -556,10 +822,11 @@ export class Advertising {
|
|
|
556
822
|
* Validates if the buffer is a valid Buffer object with a minimum length.
|
|
557
823
|
*
|
|
558
824
|
* @param {any} buffer - The buffer to validate.
|
|
825
|
+
* @param {number} minLength - The minimum required length.
|
|
559
826
|
* @returns {boolean} - True if the buffer is valid, false otherwise.
|
|
560
827
|
*/
|
|
561
|
-
static validateBuffer(buffer) {
|
|
562
|
-
return buffer && Buffer.isBuffer(buffer) && buffer.length >=
|
|
828
|
+
static validateBuffer(buffer, minLength = 3) {
|
|
829
|
+
return buffer && Buffer.isBuffer(buffer) && buffer.length >= minLength;
|
|
563
830
|
}
|
|
564
831
|
/**
|
|
565
832
|
* Parses the service data based on the device model.
|
|
@@ -591,8 +858,14 @@ export class Advertising {
|
|
|
591
858
|
return WoSensorTHProCO2.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
592
859
|
case SwitchBotBLEModel.Hub2:
|
|
593
860
|
return WoHub2.parseServiceData(manufacturerData, emitLog);
|
|
861
|
+
case SwitchBotBLEModel.Hub3:
|
|
862
|
+
return WoHub3.parseServiceData(manufacturerData, emitLog);
|
|
594
863
|
case SwitchBotBLEModel.OutdoorMeter:
|
|
595
864
|
return WoIOSensorTH.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
865
|
+
case SwitchBotBLEModel.AirPurifier:
|
|
866
|
+
return WoAirPurifier.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
867
|
+
case SwitchBotBLEModel.AirPurifierTable:
|
|
868
|
+
return WoAirPurifierTable.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
596
869
|
case SwitchBotBLEModel.MotionSensor:
|
|
597
870
|
return WoPresence.parseServiceData(serviceData, emitLog);
|
|
598
871
|
case SwitchBotBLEModel.ContactSensor:
|
|
@@ -696,21 +969,21 @@ export class WoBlindTilt extends SwitchbotDevice {
|
|
|
696
969
|
* @returns {Promise<void>}
|
|
697
970
|
*/
|
|
698
971
|
async open() {
|
|
699
|
-
await this.operateBlindTilt([
|
|
972
|
+
await this.operateBlindTilt([...BLIND_TILT_COMMANDS.OPEN]);
|
|
700
973
|
}
|
|
701
974
|
/**
|
|
702
975
|
* Closes the blind tilt up to the nearest endpoint.
|
|
703
976
|
* @returns {Promise<void>}
|
|
704
977
|
*/
|
|
705
978
|
async closeUp() {
|
|
706
|
-
await this.operateBlindTilt([
|
|
979
|
+
await this.operateBlindTilt([...BLIND_TILT_COMMANDS.CLOSE_UP]);
|
|
707
980
|
}
|
|
708
981
|
/**
|
|
709
982
|
* Closes the blind tilt down to the nearest endpoint.
|
|
710
983
|
* @returns {Promise<void>}
|
|
711
984
|
*/
|
|
712
985
|
async closeDown() {
|
|
713
|
-
await this.operateBlindTilt([
|
|
986
|
+
await this.operateBlindTilt([...BLIND_TILT_COMMANDS.CLOSE_DOWN]);
|
|
714
987
|
}
|
|
715
988
|
/**
|
|
716
989
|
* Closes the blind tilt to the nearest endpoint.
|
|
@@ -794,7 +1067,7 @@ export class WoBlindTilt extends SwitchbotDevice {
|
|
|
794
1067
|
* @returns {Promise<void>}
|
|
795
1068
|
*/
|
|
796
1069
|
async pause() {
|
|
797
|
-
await this.operateBlindTilt([
|
|
1070
|
+
await this.operateBlindTilt([...BLIND_TILT_COMMANDS.PAUSE]);
|
|
798
1071
|
}
|
|
799
1072
|
/**
|
|
800
1073
|
* Runs the blind tilt to the specified position.
|
|
@@ -803,13 +1076,10 @@ export class WoBlindTilt extends SwitchbotDevice {
|
|
|
803
1076
|
* @returns {Promise<void>}
|
|
804
1077
|
*/
|
|
805
1078
|
async runToPos(percent, mode) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
throw new RangeError('Mode must be a number between 0 and 1');
|
|
811
|
-
}
|
|
812
|
-
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, mode, percent]);
|
|
1079
|
+
ValidationUtils.validatePercentage(percent, 'percent');
|
|
1080
|
+
ValidationUtils.validateRange(mode, 0, 1, 'mode', true);
|
|
1081
|
+
const adjustedPercent = this.reverse ? 100 - percent : percent;
|
|
1082
|
+
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, mode, adjustedPercent]);
|
|
813
1083
|
}
|
|
814
1084
|
/**
|
|
815
1085
|
* Sends a command to operate the blind tilt and handles the response.
|
|
@@ -876,7 +1146,7 @@ export class WoBulb extends SwitchbotDevice {
|
|
|
876
1146
|
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is ON (true) or OFF (false).
|
|
877
1147
|
*/
|
|
878
1148
|
async readState() {
|
|
879
|
-
return this.operateBulb([
|
|
1149
|
+
return this.operateBulb([...BULB_COMMANDS.READ_STATE]);
|
|
880
1150
|
}
|
|
881
1151
|
/**
|
|
882
1152
|
* Sets the state of the bulb.
|
|
@@ -885,22 +1155,21 @@ export class WoBulb extends SwitchbotDevice {
|
|
|
885
1155
|
* @private
|
|
886
1156
|
*/
|
|
887
1157
|
async setState(reqByteArray) {
|
|
888
|
-
|
|
889
|
-
return this.operateBulb(base.concat(reqByteArray));
|
|
1158
|
+
return this.operateBulb([...BULB_COMMANDS.BASE, ...reqByteArray]);
|
|
890
1159
|
}
|
|
891
1160
|
/**
|
|
892
1161
|
* Turns on the bulb.
|
|
893
1162
|
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is ON (true).
|
|
894
1163
|
*/
|
|
895
1164
|
async turnOn() {
|
|
896
|
-
return this.setState([
|
|
1165
|
+
return this.setState([...BULB_COMMANDS.TURN_ON]);
|
|
897
1166
|
}
|
|
898
1167
|
/**
|
|
899
1168
|
* Turns off the bulb.
|
|
900
1169
|
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is OFF (false).
|
|
901
1170
|
*/
|
|
902
1171
|
async turnOff() {
|
|
903
|
-
return this.setState([
|
|
1172
|
+
return this.setState([...BULB_COMMANDS.TURN_OFF]);
|
|
904
1173
|
}
|
|
905
1174
|
/**
|
|
906
1175
|
* Sets the brightness of the bulb.
|
|
@@ -908,10 +1177,8 @@ export class WoBulb extends SwitchbotDevice {
|
|
|
908
1177
|
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
909
1178
|
*/
|
|
910
1179
|
async setBrightness(brightness) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
914
|
-
return this.setState([0x02, 0x14, brightness]);
|
|
1180
|
+
ValidationUtils.validatePercentage(brightness, 'brightness');
|
|
1181
|
+
return this.setState([...BULB_COMMANDS.SET_BRIGHTNESS, brightness]);
|
|
915
1182
|
}
|
|
916
1183
|
/**
|
|
917
1184
|
* Sets the color temperature of the bulb.
|
|
@@ -919,10 +1186,8 @@ export class WoBulb extends SwitchbotDevice {
|
|
|
919
1186
|
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
920
1187
|
*/
|
|
921
1188
|
async setColorTemperature(color_temperature) {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
925
|
-
return this.setState([0x02, 0x17, color_temperature]);
|
|
1189
|
+
ValidationUtils.validatePercentage(color_temperature, 'color_temperature');
|
|
1190
|
+
return this.setState([...BULB_COMMANDS.SET_COLOR_TEMP, color_temperature]);
|
|
926
1191
|
}
|
|
927
1192
|
/**
|
|
928
1193
|
* Sets the RGB color of the bulb.
|
|
@@ -933,10 +1198,11 @@ export class WoBulb extends SwitchbotDevice {
|
|
|
933
1198
|
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
934
1199
|
*/
|
|
935
1200
|
async setRGB(brightness, red, green, blue) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1201
|
+
ValidationUtils.validatePercentage(brightness, 'brightness');
|
|
1202
|
+
ValidationUtils.validateRGB(red, 'red');
|
|
1203
|
+
ValidationUtils.validateRGB(green, 'green');
|
|
1204
|
+
ValidationUtils.validateRGB(blue, 'blue');
|
|
1205
|
+
return this.setState([...BULB_COMMANDS.SET_RGB, brightness, red, green, blue]);
|
|
940
1206
|
}
|
|
941
1207
|
/**
|
|
942
1208
|
* Sends a command to the bulb.
|
|
@@ -1406,6 +1672,43 @@ export class WoHub2 extends SwitchbotDevice {
|
|
|
1406
1672
|
super(peripheral, noble);
|
|
1407
1673
|
}
|
|
1408
1674
|
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Class representing a WoHub3 device.
|
|
1677
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
1678
|
+
*/
|
|
1679
|
+
export class WoHub3 extends SwitchbotDevice {
|
|
1680
|
+
/**
|
|
1681
|
+
* Parses the service data for WoHub3.
|
|
1682
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1683
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1684
|
+
* @returns {Promise<hub3ServiceData | null>} - Parsed service data or null if invalid.
|
|
1685
|
+
*/
|
|
1686
|
+
static async parseServiceData(manufacturerData, emitLog) {
|
|
1687
|
+
if (manufacturerData.length !== 16) {
|
|
1688
|
+
emitLog('debugerror', `[parseServiceDataForWoHub3] Buffer length ${manufacturerData.length} !== 16!`);
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1691
|
+
const [byte0, byte1, byte2, , , , , , , , , , byte12] = manufacturerData;
|
|
1692
|
+
const tempSign = byte1 & 0b10000000 ? 1 : -1;
|
|
1693
|
+
const tempC = tempSign * ((byte1 & 0b01111111) + (byte0 & 0b00001111) / 10);
|
|
1694
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
1695
|
+
const lightLevel = byte12 & 0b11111;
|
|
1696
|
+
const data = {
|
|
1697
|
+
model: SwitchBotBLEModel.Hub3,
|
|
1698
|
+
modelName: SwitchBotBLEModelName.Hub3,
|
|
1699
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Hub3,
|
|
1700
|
+
celsius: tempC,
|
|
1701
|
+
fahrenheit: tempF,
|
|
1702
|
+
fahrenheit_mode: !!(byte2 & 0b10000000),
|
|
1703
|
+
humidity: byte2 & 0b01111111,
|
|
1704
|
+
lightLevel,
|
|
1705
|
+
};
|
|
1706
|
+
return data;
|
|
1707
|
+
}
|
|
1708
|
+
constructor(peripheral, noble) {
|
|
1709
|
+
super(peripheral, noble);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1409
1712
|
/**
|
|
1410
1713
|
* Class representing a WoHumi device.
|
|
1411
1714
|
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/tree/latest/devicetypes
|
|
@@ -2750,6 +3053,7 @@ export class WoStrip extends SwitchbotDevice {
|
|
|
2750
3053
|
red: byte3,
|
|
2751
3054
|
green: byte4,
|
|
2752
3055
|
blue: byte5,
|
|
3056
|
+
color_temperature: 0, // Add a default value or extract from serviceData if available
|
|
2753
3057
|
delay: byte8 & 0b10000000,
|
|
2754
3058
|
preset: byte8 & 0b00001000,
|
|
2755
3059
|
color_mode: byte8 & 0b00000111,
|
|
@@ -2842,4 +3146,280 @@ export class WoStrip extends SwitchbotDevice {
|
|
|
2842
3146
|
}
|
|
2843
3147
|
}
|
|
2844
3148
|
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Class representing a SwitchBot Air Purifier device.
|
|
3151
|
+
* @extends SwitchbotDevice
|
|
3152
|
+
*/
|
|
3153
|
+
export class WoAirPurifier extends SwitchbotDevice {
|
|
3154
|
+
/**
|
|
3155
|
+
* Parses service data for air purifier devices.
|
|
3156
|
+
* @param {Buffer | null} serviceData - The service data buffer.
|
|
3157
|
+
* @param {Buffer | null} manufacturerData - The manufacturer data buffer.
|
|
3158
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
3159
|
+
* @returns {airPurifierServiceData | null} - The parsed service data or null.
|
|
3160
|
+
*/
|
|
3161
|
+
static parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
3162
|
+
if (!manufacturerData || manufacturerData.length < 14) {
|
|
3163
|
+
return null;
|
|
3164
|
+
}
|
|
3165
|
+
const deviceData = manufacturerData.subarray(6);
|
|
3166
|
+
if (deviceData.length < 8) {
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
const sequenceNumber = deviceData[0];
|
|
3170
|
+
const isOn = Boolean(deviceData[1] & 0b10000000);
|
|
3171
|
+
const mode = deviceData[1] & 0b00000111;
|
|
3172
|
+
const isAqiValid = Boolean(deviceData[2] & 0b00000100);
|
|
3173
|
+
const childLock = Boolean(deviceData[2] & 0b00000010);
|
|
3174
|
+
const speed = deviceData[3] & 0b01111111;
|
|
3175
|
+
const aqiLevelRaw = (deviceData[4] & 0b00000110) >> 1;
|
|
3176
|
+
const workTime = (deviceData[5] << 8) | deviceData[6];
|
|
3177
|
+
const errCode = deviceData[7];
|
|
3178
|
+
// Map AQI level to string using the defined constant
|
|
3179
|
+
const aqiLevelValues = [
|
|
3180
|
+
AIR_QUALITY_LEVELS.EXCELLENT,
|
|
3181
|
+
AIR_QUALITY_LEVELS.GOOD,
|
|
3182
|
+
AIR_QUALITY_LEVELS.FAIR,
|
|
3183
|
+
AIR_QUALITY_LEVELS.POOR,
|
|
3184
|
+
];
|
|
3185
|
+
const aqiLevel = aqiLevelValues[aqiLevelRaw] || 'unknown';
|
|
3186
|
+
// Determine mode based on mode value and speed
|
|
3187
|
+
let modeString = null;
|
|
3188
|
+
if (mode === 1) {
|
|
3189
|
+
if (speed >= 0 && speed <= 33) {
|
|
3190
|
+
modeString = AIR_PURIFIER_MODES.LEVEL_1;
|
|
3191
|
+
}
|
|
3192
|
+
else if (speed >= 34 && speed <= 66) {
|
|
3193
|
+
modeString = AIR_PURIFIER_MODES.LEVEL_2;
|
|
3194
|
+
}
|
|
3195
|
+
else {
|
|
3196
|
+
modeString = AIR_PURIFIER_MODES.LEVEL_3;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
else if (mode > 1 && mode <= 4) {
|
|
3200
|
+
const modeMap = [null, null, 'auto', 'sleep', 'manual'];
|
|
3201
|
+
modeString = modeMap[mode + 2] || null;
|
|
3202
|
+
}
|
|
3203
|
+
if (emitLog) {
|
|
3204
|
+
emitLog('debug', `Air Purifier Service Data: isOn=${isOn}, mode=${modeString}, speed=${speed}, AQI=${aqiLevel}`);
|
|
3205
|
+
}
|
|
3206
|
+
return {
|
|
3207
|
+
model: SwitchBotBLEModel.AirPurifier,
|
|
3208
|
+
modelName: SwitchBotBLEModelName.AirPurifier,
|
|
3209
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.AirPurifier,
|
|
3210
|
+
isOn,
|
|
3211
|
+
mode: modeString,
|
|
3212
|
+
isAqiValid,
|
|
3213
|
+
child_lock: childLock,
|
|
3214
|
+
speed,
|
|
3215
|
+
aqi_level: aqiLevel,
|
|
3216
|
+
filter_element_working_time: workTime,
|
|
3217
|
+
err_code: errCode,
|
|
3218
|
+
sequence_number: sequenceNumber,
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Sets the state of the air purifier.
|
|
3223
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
3224
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
3225
|
+
* @private
|
|
3226
|
+
*/
|
|
3227
|
+
async setState(reqByteArray) {
|
|
3228
|
+
return this.operateAirPurifier(reqByteArray);
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* Turns the air purifier on.
|
|
3232
|
+
* @returns {Promise<boolean>} - Resolves with true if the air purifier is turned on.
|
|
3233
|
+
*/
|
|
3234
|
+
async turnOn() {
|
|
3235
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_ON]);
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Turns the air purifier off.
|
|
3239
|
+
* @returns {Promise<boolean>} - Resolves with true if the air purifier is turned off.
|
|
3240
|
+
*/
|
|
3241
|
+
async turnOff() {
|
|
3242
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_OFF]);
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Sets the speed of the air purifier.
|
|
3246
|
+
* @param {number} speed - The speed value (0-100).
|
|
3247
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
3248
|
+
*/
|
|
3249
|
+
async setSpeed(speed) {
|
|
3250
|
+
if (typeof speed !== 'number' || speed < 0 || speed > 100) {
|
|
3251
|
+
throw new TypeError(`Invalid speed value: ${speed}`);
|
|
3252
|
+
}
|
|
3253
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_SPEED, speed]);
|
|
3254
|
+
}
|
|
3255
|
+
/**
|
|
3256
|
+
* Sets the mode of the air purifier.
|
|
3257
|
+
* @param {number} mode - The mode value (1-4).
|
|
3258
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
3259
|
+
*/
|
|
3260
|
+
async setMode(mode) {
|
|
3261
|
+
if (typeof mode !== 'number' || mode < 1 || mode > 4) {
|
|
3262
|
+
throw new TypeError(`Invalid mode value: ${mode}`);
|
|
3263
|
+
}
|
|
3264
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_MODE, mode]);
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* Operates the air purifier with the given byte array.
|
|
3268
|
+
* @public
|
|
3269
|
+
* @param {number[]} bytes - The byte array to send.
|
|
3270
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
3271
|
+
*/
|
|
3272
|
+
async operateAirPurifier(bytes) {
|
|
3273
|
+
const req_buf = Buffer.from(bytes);
|
|
3274
|
+
const res_buf = await this.command(req_buf);
|
|
3275
|
+
if (res_buf.length !== 2) {
|
|
3276
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${res_buf.toString('hex')}`);
|
|
3277
|
+
}
|
|
3278
|
+
const code = res_buf.readUInt8(1);
|
|
3279
|
+
if (code === 0x00 || code === 0x80) {
|
|
3280
|
+
return code === 0x80;
|
|
3281
|
+
}
|
|
3282
|
+
else {
|
|
3283
|
+
throw new Error(`The device returned an error: 0x${res_buf.toString('hex')}`);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* Class representing a SwitchBot Air Purifier Table device.
|
|
3289
|
+
* @extends SwitchbotDevice
|
|
3290
|
+
*/
|
|
3291
|
+
export class WoAirPurifierTable extends SwitchbotDevice {
|
|
3292
|
+
/**
|
|
3293
|
+
* Parses service data for air purifier table devices.
|
|
3294
|
+
* @param {Buffer | null} serviceData - The service data buffer.
|
|
3295
|
+
* @param {Buffer | null} manufacturerData - The manufacturer data buffer.
|
|
3296
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
3297
|
+
* @returns {airPurifierTableServiceData | null} - The parsed service data or null.
|
|
3298
|
+
*/
|
|
3299
|
+
static parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
3300
|
+
if (!manufacturerData || manufacturerData.length < 14) {
|
|
3301
|
+
return null;
|
|
3302
|
+
}
|
|
3303
|
+
const deviceData = manufacturerData.subarray(6);
|
|
3304
|
+
if (deviceData.length < 8) {
|
|
3305
|
+
return null;
|
|
3306
|
+
}
|
|
3307
|
+
const sequenceNumber = deviceData[0];
|
|
3308
|
+
const isOn = Boolean(deviceData[1] & 0b10000000);
|
|
3309
|
+
const mode = deviceData[1] & 0b00000111;
|
|
3310
|
+
const isAqiValid = Boolean(deviceData[2] & 0b00000100);
|
|
3311
|
+
const childLock = Boolean(deviceData[2] & 0b00000010);
|
|
3312
|
+
const speed = deviceData[3] & 0b01111111;
|
|
3313
|
+
const aqiLevelRaw = (deviceData[4] & 0b00000110) >> 1;
|
|
3314
|
+
const workTime = (deviceData[5] << 8) | deviceData[6];
|
|
3315
|
+
const errCode = deviceData[7];
|
|
3316
|
+
// Map AQI level to string using the defined constant
|
|
3317
|
+
const aqiLevelValues = [
|
|
3318
|
+
AIR_QUALITY_LEVELS.EXCELLENT,
|
|
3319
|
+
AIR_QUALITY_LEVELS.GOOD,
|
|
3320
|
+
AIR_QUALITY_LEVELS.FAIR,
|
|
3321
|
+
AIR_QUALITY_LEVELS.POOR,
|
|
3322
|
+
];
|
|
3323
|
+
const aqiLevel = aqiLevelValues[aqiLevelRaw] || 'unknown';
|
|
3324
|
+
// Determine mode based on mode value and speed
|
|
3325
|
+
let modeString = null;
|
|
3326
|
+
if (mode === 1) {
|
|
3327
|
+
if (speed >= 0 && speed <= 33) {
|
|
3328
|
+
modeString = AIR_PURIFIER_MODES.LEVEL_1;
|
|
3329
|
+
}
|
|
3330
|
+
else if (speed >= 34 && speed <= 66) {
|
|
3331
|
+
modeString = AIR_PURIFIER_MODES.LEVEL_2;
|
|
3332
|
+
}
|
|
3333
|
+
else {
|
|
3334
|
+
modeString = AIR_PURIFIER_MODES.LEVEL_3;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
else if (mode > 1 && mode <= 4) {
|
|
3338
|
+
const modeMap = [null, null, 'auto', 'sleep', 'manual'];
|
|
3339
|
+
modeString = modeMap[mode + 2] || null;
|
|
3340
|
+
}
|
|
3341
|
+
if (emitLog) {
|
|
3342
|
+
emitLog('debug', `Air Purifier Table Service Data: isOn=${isOn}, mode=${modeString}, speed=${speed}, AQI=${aqiLevel}`);
|
|
3343
|
+
}
|
|
3344
|
+
return {
|
|
3345
|
+
model: SwitchBotBLEModel.AirPurifierTable,
|
|
3346
|
+
modelName: SwitchBotBLEModelName.AirPurifierTable,
|
|
3347
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.AirPurifierTable,
|
|
3348
|
+
isOn,
|
|
3349
|
+
mode: modeString,
|
|
3350
|
+
isAqiValid,
|
|
3351
|
+
child_lock: childLock,
|
|
3352
|
+
speed,
|
|
3353
|
+
aqi_level: aqiLevel,
|
|
3354
|
+
filter_element_working_time: workTime,
|
|
3355
|
+
err_code: errCode,
|
|
3356
|
+
sequence_number: sequenceNumber,
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* Sets the state of the air purifier table.
|
|
3361
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
3362
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
3363
|
+
* @private
|
|
3364
|
+
*/
|
|
3365
|
+
async setState(reqByteArray) {
|
|
3366
|
+
return this.operateAirPurifierTable(reqByteArray);
|
|
3367
|
+
}
|
|
3368
|
+
/**
|
|
3369
|
+
* Turns the air purifier table on.
|
|
3370
|
+
* @returns {Promise<boolean>} - Resolves with true if the air purifier table is turned on.
|
|
3371
|
+
*/
|
|
3372
|
+
async turnOn() {
|
|
3373
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_ON]);
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3376
|
+
* Turns the air purifier table off.
|
|
3377
|
+
* @returns {Promise<boolean>} - Resolves with true if the air purifier table is turned off.
|
|
3378
|
+
*/
|
|
3379
|
+
async turnOff() {
|
|
3380
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_OFF]);
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Sets the speed of the air purifier table.
|
|
3384
|
+
* @param {number} speed - The speed value (0-100).
|
|
3385
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
3386
|
+
*/
|
|
3387
|
+
async setSpeed(speed) {
|
|
3388
|
+
if (typeof speed !== 'number' || speed < 0 || speed > 100) {
|
|
3389
|
+
throw new TypeError(`Invalid speed value: ${speed}`);
|
|
3390
|
+
}
|
|
3391
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_SPEED, speed]);
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Sets the mode of the air purifier table.
|
|
3395
|
+
* @param {number} mode - The mode value (1-4).
|
|
3396
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
3397
|
+
*/
|
|
3398
|
+
async setMode(mode) {
|
|
3399
|
+
if (typeof mode !== 'number' || mode < 1 || mode > 4) {
|
|
3400
|
+
throw new TypeError(`Invalid mode value: ${mode}`);
|
|
3401
|
+
}
|
|
3402
|
+
return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_MODE, mode]);
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Operates the air purifier table with the given byte array.
|
|
3406
|
+
* @public
|
|
3407
|
+
* @param {number[]} bytes - The byte array to send.
|
|
3408
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
3409
|
+
*/
|
|
3410
|
+
async operateAirPurifierTable(bytes) {
|
|
3411
|
+
const req_buf = Buffer.from(bytes);
|
|
3412
|
+
const res_buf = await this.command(req_buf);
|
|
3413
|
+
if (res_buf.length !== 2) {
|
|
3414
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${res_buf.toString('hex')}`);
|
|
3415
|
+
}
|
|
3416
|
+
const code = res_buf.readUInt8(1);
|
|
3417
|
+
if (code === 0x00 || code === 0x80) {
|
|
3418
|
+
return code === 0x80;
|
|
3419
|
+
}
|
|
3420
|
+
else {
|
|
3421
|
+
throw new Error(`The device returned an error: 0x${res_buf.toString('hex')}`);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
2845
3425
|
//# sourceMappingURL=device.js.map
|