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.
289 lines • 9.07 kB
JavaScript
/* Copyright(C) 2024-2026, donavanbecker (https://github.com/donavanbecker). All rights reserved.
*
* api.ts: SwitchBot v4.0.0 - OpenAPI Client
*/
import { request } from 'undici';
import { urls } from './settings.js';
import { createSignature, generateNonce, generateTimestamp, Logger } from './utils/index.js';
/**
* OpenAPI Client for SwitchBot API v1.1
*/
export class OpenAPIClient {
token;
secret;
baseURL;
logger;
constructor(token, secret, baseURL = urls.base, logLevel) {
this.token = token;
this.secret = secret;
this.baseURL = baseURL;
this.logger = new Logger('OpenAPIClient', logLevel);
if (!token || !secret) {
throw new Error('OpenAPI token and secret are required');
}
}
/**
* Generate authentication headers for API requests
*/
async generateHeaders() {
const timestamp = generateTimestamp();
const nonce = generateNonce();
const sign = await createSignature(this.token, this.secret, timestamp, nonce);
return {
'Authorization': this.token,
'Content-Type': 'application/json',
't': timestamp,
'sign': sign,
'nonce': nonce,
};
}
/**
* Make an API request
*/
async makeRequest(method, path, body) {
const headers = await this.generateHeaders();
const url = `${this.baseURL}${path}`;
this.logger.debug(`${method} ${url}`, body ? { body } : {});
try {
const response = await request(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const data = await response.body.json();
if (data.statusCode !== 100 && data.statusCode !== 200) {
this.logger.error('API request failed', data);
throw new Error(`API Error ${data.statusCode}: ${data.message}`);
}
this.logger.debug('API response', data);
return data;
}
catch (error) {
this.logger.error('API request error', error);
throw error;
}
}
/**
* Get all devices
*/
async getDevices() {
const response = await this.makeRequest('GET', '/v1.1/devices');
return response.body;
}
/**
* Get device status
*/
async getStatus(deviceId) {
const response = await this.makeRequest('GET', `/v1.1/devices/${deviceId}/status`);
return response.body;
}
/**
* Send command to device
*/
async sendCommand(deviceId, command, parameter) {
const body = {
command,
parameter: parameter !== undefined ? parameter : 'default',
};
const response = await this.makeRequest('POST', `/v1.1/devices/${deviceId}/commands`, body);
return {
statusCode: response.statusCode,
message: response.message,
body: response.body,
};
}
/**
* Get all scenes
*/
async getScenes() {
const response = await this.makeRequest('GET', '/v1.1/scenes');
return response.body;
}
/**
* Execute a scene
*/
async executeScene(sceneId) {
await this.makeRequest('POST', `/v1.1/scenes/${sceneId}/execute`);
}
/**
* Setup webhook
*/
async setupWebhook(config) {
const response = await this.makeRequest('POST', '/v1.1/webhook/setupWebhook', config);
return {
statusCode: response.statusCode,
message: response.message,
body: response.body,
};
}
/**
* Query webhook configuration
*/
async queryWebhook(urls) {
const body = urls ? { urls } : {};
const response = await this.makeRequest('POST', '/v1.1/webhook/queryWebhook', body);
return {
statusCode: response.statusCode,
message: response.message,
body: response.body,
};
}
/**
* Update webhook
*/
async updateWebhook(config) {
await this.makeRequest('POST', '/v1.1/webhook/updateWebhook', config);
}
/**
* Delete webhook
*/
async deleteWebhook(url) {
await this.makeRequest('POST', '/v1.1/webhook/deleteWebhook', { url });
}
/**
* Get specific device information
*/
async getDevice(deviceId) {
const devices = await this.getDevices();
return devices.deviceList.find(d => d.deviceId === deviceId);
}
/**
* Get devices by type
*/
async getDevicesByType(deviceType) {
const devices = await this.getDevices();
return devices.deviceList.filter(d => d.deviceType === deviceType);
}
/**
* Check if device has cloud service enabled
*/
async isCloudServiceEnabled(deviceId) {
const device = await this.getDevice(deviceId);
return device?.enableCloudService ?? false;
}
/**
* Bot-specific commands
*/
async botPress(deviceId) {
return this.sendCommand(deviceId, 'press');
}
async botTurnOn(deviceId) {
return this.sendCommand(deviceId, 'turnOn');
}
async botTurnOff(deviceId) {
return this.sendCommand(deviceId, 'turnOff');
}
/**
* Curtain-specific commands
*/
async curtainOpen(deviceId, speed = 255) {
const clampedSpeed = Math.min(255, Math.max(1, speed));
return this.sendCommand(deviceId, 'setPosition', `0,${clampedSpeed.toString(16).padStart(2, '0')},0`);
}
async curtainClose(deviceId, speed = 255) {
const clampedSpeed = Math.min(255, Math.max(1, speed));
return this.sendCommand(deviceId, 'setPosition', `0,${clampedSpeed.toString(16).padStart(2, '0')},100`);
}
async curtainPause(deviceId) {
return this.sendCommand(deviceId, 'pause');
}
async curtainSetPosition(deviceId, position, speed = 255) {
const clampedSpeed = Math.min(255, Math.max(1, speed));
return this.sendCommand(deviceId, 'setPosition', `0,${clampedSpeed.toString(16).padStart(2, '0')},${position}`);
}
/**
* Lock-specific commands
*/
async lockLock(deviceId) {
return this.sendCommand(deviceId, 'lock');
}
async lockUnlock(deviceId) {
return this.sendCommand(deviceId, 'unlock');
}
/**
* Plug-specific commands
*/
async plugTurnOn(deviceId) {
return this.sendCommand(deviceId, 'turnOn');
}
async plugTurnOff(deviceId) {
return this.sendCommand(deviceId, 'turnOff');
}
async plugToggle(deviceId) {
return this.sendCommand(deviceId, 'toggle');
}
/**
* Bulb/Light-specific commands
*/
async lightTurnOn(deviceId) {
return this.sendCommand(deviceId, 'turnOn');
}
async lightTurnOff(deviceId) {
return this.sendCommand(deviceId, 'turnOff');
}
async lightSetBrightness(deviceId, brightness) {
return this.sendCommand(deviceId, 'setBrightness', brightness);
}
async lightSetColor(deviceId, red, green, blue) {
return this.sendCommand(deviceId, 'setColor', `${red}:${green}:${blue}`);
}
async lightSetColorTemperature(deviceId, temperature) {
return this.sendCommand(deviceId, 'setColorTemperature', temperature);
}
/**
* Humidifier-specific commands
*/
async humidifierTurnOn(deviceId) {
return this.sendCommand(deviceId, 'turnOn');
}
async humidifierTurnOff(deviceId) {
return this.sendCommand(deviceId, 'turnOff');
}
async humidifierSetMode(deviceId, mode) {
return this.sendCommand(deviceId, 'setMode', mode === 'auto' ? 'auto' : '101');
}
/**
* Air Purifier-specific commands
*/
async airPurifierTurnOn(deviceId) {
return this.sendCommand(deviceId, 'turnOn');
}
async airPurifierTurnOff(deviceId) {
return this.sendCommand(deviceId, 'turnOff');
}
async airPurifierSetMode(deviceId, mode) {
return this.sendCommand(deviceId, 'setMode', mode);
}
async airPurifierSetFanSpeed(deviceId, speed) {
return this.sendCommand(deviceId, 'setFanSpeed', speed);
}
/**
* Blind Tilt-specific commands
*/
async blindTiltOpen(deviceId) {
return this.sendCommand(deviceId, 'setPosition', '0,ff,0');
}
async blindTiltClose(deviceId) {
return this.sendCommand(deviceId, 'setPosition', '0,ff,100');
}
async blindTiltSetPosition(deviceId, position) {
return this.sendCommand(deviceId, 'setPosition', `0,ff,${position}`);
}
/**
* Get client configuration
*/
getConfig() {
return {
token: this.token,
baseURL: this.baseURL,
};
}
/**
* Update base URL
*/
setBaseURL(newBaseURL) {
this.baseURL = newBaseURL;
this.logger.info(`Base URL updated to ${newBaseURL}`);
}
}
//# sourceMappingURL=api.js.map