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
JavaScript
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