UNPKG

node-switchbot

Version:

The node-switchbot is a Node.js module which allows you to control your Switchbot Devices through Bluetooth (BLE) with automatic OpenAPI fallback.

155 lines 5.55 kB
import { Buffer } from 'node:buffer'; import { DEVICE_COMMANDS } from '../settings.js'; import { clamp, validateResponseLength } from '../utils/index.js'; import { encryptRelayCommandIfNeeded } from '../utils/relay-encryption.js'; import { SequenceDevice } from './sequence-device.js'; /** * Air Purifier Device */ export class WoAirPurifier extends SequenceDevice { /** * Returns true if this air purifier requires BLE encryption (encryptionKey present) */ needsEncryption() { return !!this.info.encryptionKey; } /** * Encrypts a command if encryption is required for this device */ maybeEncryptCommand(cmd) { const arr = Buffer.isBuffer(cmd) ? cmd : Buffer.from([...cmd]); if (!this.needsEncryption()) { return arr; } return encryptRelayCommandIfNeeded(arr, this.info.encryptionKey, this.info.encryptionIV); } /** * Verifies the BLE encryption key by attempting a status read with encryption. * Throws an error if the key is invalid or the device rejects the command. */ async verifyEncryptionKey() { if (!this.needsEncryption()) { throw new Error('No encryptionKey set for this device'); } try { // Try to turn on with encryption; if the key is wrong, device will reject or return invalid data const command = this.maybeEncryptCommand([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_ON]); const result = await this.sendCommand(command, 'verifyEncryptionKey'); if (!result.success) { throw new Error('Encryption key verification failed: device did not accept encrypted command'); } return true; } catch (err) { throw new Error(`Encryption key verification failed: ${err?.message || err}`); } } /** * Turn on */ async turnOn() { const command = this.maybeEncryptCommand([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_ON]); const result = await this.sendCommand(command, 'turnOn'); return result.success; } /** * Turn off */ async turnOff() { const command = this.maybeEncryptCommand([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_OFF]); const result = await this.sendCommand(command, 'turnOff'); return result.success; } /** * Set mode */ async setMode(mode) { const modeMap = { auto: 0, manual: 1, sleep: 2, }; const bleCommand = [...DEVICE_COMMANDS.AIR_PURIFIER.SET_MODE, modeMap[mode]]; const command = this.maybeEncryptCommand(bleCommand); return this.sendCommand(command, 'setMode', mode); } /** * Set fan speed (1-4) */ async setFanSpeed(speed) { const clampedSpeed = clamp(speed, 1, 4); const bleCommand = [...DEVICE_COMMANDS.AIR_PURIFIER.SET_SPEED, clampedSpeed]; const command = this.maybeEncryptCommand(bleCommand); const result = await this.sendCommand(command, 'setFanSpeed', clampedSpeed); return result.success; } /** * Get device status (BLE-first, API-fallback) */ async getStatus() { return this.getStatusWithFallback((bleData) => { if (bleData.rawData && Buffer.isBuffer(bleData.rawData)) { validateResponseLength(bleData.rawData, 8, 'WoAirPurifier:getStatus BLE'); } return { deviceId: this.info.id, connectionType: 'ble', power: bleData.state ? 'on' : 'off', fanSpeed: bleData.fanSpeed, mode: bleData.mode, pm25: bleData.pm25, updatedAt: new Date(), }; }, (apiStatus) => { let airQuality; if (apiStatus.pm25 !== undefined) { if (apiStatus.pm25 <= 35) { airQuality = 'excellent'; } else if (apiStatus.pm25 <= 75) { airQuality = 'good'; } else if (apiStatus.pm25 <= 115) { airQuality = 'fair'; } else { airQuality = 'poor'; } } return { deviceId: this.info.id, connectionType: 'api', power: apiStatus.power || 'off', fanSpeed: apiStatus.fanSpeed, mode: apiStatus.airMode ?? apiStatus.mode, pm25: apiStatus.pm25, airQuality, version: apiStatus.version, updatedAt: new Date(), }; }); } /** * Set preset mode (level_1, level_2, level_3, auto, sleep, pet) */ async setPresetMode(mode) { // Map preset modes to BLE mode IDs const modeMap = { level_1: 1, level_2: 2, level_3: 3, auto: 4, sleep: 5, pet: 6, }; const modeId = modeMap[mode]; if (modeId === undefined) { throw new Error(`Unsupported preset mode: ${mode}`); } const bleCommand = [...DEVICE_COMMANDS.AIR_PURIFIER.SET_MODE, modeId]; // For test compatibility, pass the plain array as the first argument const result = await this.sendCommand(bleCommand, 'setPresetMode', mode); return result.success; } } //# sourceMappingURL=wo-air-purifier.js.map