@switchbot/homebridge-switchbot
Version:
The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.
884 lines (883 loc) • 180 kB
JavaScript
import { readFileSync } from 'node:fs';
import { argv } from 'node:process';
import asyncmqtt from 'async-mqtt';
import fakegato from 'fakegato-history';
import { EveHomeKitTypes } from 'homebridge-lib/EveHomeKitTypes';
import { LogLevel, SwitchBotBLE, SwitchBotModel, SwitchBotOpenAPI } from 'node-switchbot';
import { queueScheduler } from 'rxjs';
import { BlindTilt } from './device/blindtilt.js';
import { Bot } from './device/bot.js';
import { CeilingLight } from './device/ceilinglight.js';
import { ColorBulb } from './device/colorbulb.js';
import { Contact } from './device/contact.js';
import { Curtain } from './device/curtain.js';
import { Fan } from './device/fan.js';
import { Hub } from './device/hub.js';
import { Humidifier } from './device/humidifier.js';
import { IOSensor } from './device/iosensor.js';
import { StripLight } from './device/lightstrip.js';
import { Lock } from './device/lock.js';
import { Meter } from './device/meter.js';
import { MeterPlus } from './device/meterplus.js';
import { MeterPro } from './device/meterpro.js';
import { Motion } from './device/motion.js';
import { Plug } from './device/plug.js';
import { RelaySwitch } from './device/relayswitch.js';
import { RobotVacuumCleaner } from './device/robotvacuumcleaner.js';
import { WaterDetector } from './device/waterdetector.js';
import { AirConditioner } from './irdevice/airconditioner.js';
import { AirPurifier } from './irdevice/airpurifier.js';
import { Camera } from './irdevice/camera.js';
import { IRFan } from './irdevice/fan.js';
import { Light } from './irdevice/light.js';
import { Others } from './irdevice/other.js';
import { TV } from './irdevice/tv.js';
import { VacuumCleaner } from './irdevice/vacuumcleaner.js';
import { WaterHeater } from './irdevice/waterheater.js';
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
import { formatDeviceIdAsMac, isBlindTiltDevice, isCurtainDevice, safeStringify, sleep } from './utils.js';
/**
* HomebridgePlatform
* This class is the main constructor for your plugin, this is where you should
* parse the user config and discover/register accessories with Homebridge.
*/
export class SwitchBotPlatform {
// Platform properties
accessories = [];
api;
log;
// Configuration properties
platformConfig;
platformLogging;
platformRefreshRate;
platformPushRate;
platformUpdateRate;
platformMaxRetries;
platformDelayBetweenRetries;
config;
debugMode;
version;
// MQTT and Webhook properties
mqttClient = null;
webhookEventListener = null;
// SwitchBot APIs
switchBotAPI;
switchBotBLE;
// External APIs
eve;
fakegatoAPI;
// Event Handlers
webhookEventHandler = {};
bleEventHandler = {};
constructor(log, config, api) {
this.api = api;
this.log = log;
// only load if configured
if (!config) {
this.log.error('No configuration found for the plugin, please check your config.');
return;
}
// Plugin options into our config variables.
this.config = {
platform: 'SwitchBotPlatform',
name: config.name,
credentials: config.credentials,
options: config.options,
devices: config.devices,
};
// Plugin Configuration
this.getPlatformLogSettings();
this.getPlatformRateSettings();
this.getPlatformConfigSettings();
this.getVersion();
// Finish initializing the platform
this.debugLog(`Finished initializing platform: ${config.name}`);
// verify the config
try {
this.verifyConfig();
this.debugLog('Config OK');
}
catch (e) {
this.errorLog(`Verify Config, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
this.debugErrorLog(`Verify Config, Error: ${e.message ?? e}`);
return;
}
// SwitchBot OpenAPI
if (this.config.credentials?.token && this.config.credentials?.secret) {
this.switchBotAPI = new SwitchBotOpenAPI(this.config.credentials.token, this.config.credentials.secret, this.config.options?.hostname);
}
else {
this.debugErrorLog('Missing SwitchBot API credentials (token or secret).');
}
// Listen for log events
if (!this.config.options?.disableLogsforOpenAPI && this.switchBotAPI) {
this.switchBotAPI.on('log', (log) => {
switch (log.level) {
case LogLevel.SUCCESS:
this.successLog(log.message);
break;
case LogLevel.DEBUGSUCCESS:
this.debugSuccessLog(log.message);
break;
case LogLevel.WARN:
this.warnLog(log.message);
break;
case LogLevel.DEBUGWARN:
this.debugWarnLog(log.message);
break;
case LogLevel.ERROR:
this.errorLog(log.message);
break;
case LogLevel.DEBUGERROR:
this.debugErrorLog(log.message);
break;
case LogLevel.DEBUG:
this.debugLog(log.message);
break;
case LogLevel.INFO:
default:
this.infoLog(log.message);
}
});
}
else {
this.debugErrorLog(`SwitchBot OpenAPI logs are disabled, enable it by setting disableLogsforOpenAPI to false.`);
this.debugLog(`SwitchBot OpenAPI: ${JSON.stringify(this.switchBotAPI)}, disableLogsforOpenAPI: ${this.config.options?.disableLogsforOpenAPI}`);
}
// import fakegato-history module and EVE characteristics
this.fakegatoAPI = fakegato(api);
this.eve = new EveHomeKitTypes(api);
// When this event is fired it means Homebridge has restored all cached accessories from disk.
// Dynamic Platform plugins should only register new accessories after this event was fired,
// in order to ensure they weren't added to homebridge already. This event can also be used
// to start discovery of new accessories.
this.api.on('didFinishLaunching', async () => {
this.debugLog('Executed didFinishLaunching callback');
// run the method to discover / register your devices as accessories
try {
await this.discoverDevices();
}
catch (e) {
this.errorLog(`Failed to Discover, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
this.debugErrorLog(`Failed to Discover, Error: ${e.message ?? e}`);
}
});
try {
this.setupMqtt();
}
catch (e) {
this.errorLog(`Setup MQTT, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
}
try {
this.setupwebhook();
}
catch (e) {
this.errorLog(`Setup Webhook, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
}
try {
this.setupBlE();
}
catch (e) {
this.errorLog(`Setup Platform BLE, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
}
}
async setupMqtt() {
if (this.config.options?.mqttURL) {
try {
const { connectAsync } = asyncmqtt;
this.mqttClient = await connectAsync(this.config.options?.mqttURL, this.config.options.mqttOptions || {});
this.debugLog('MQTT connection has been established successfully.');
this.mqttClient.on('error', async (e) => {
this.errorLog(`Failed to publish MQTT messages. ${e.message ?? e}`);
});
if (!this.config.options?.webhookURL) {
// receive webhook events via MQTT
this.infoLog(`Webhook is configured to be received through ${this.config.options.mqttURL}/homebridge-switchbot/webhook.`);
this.mqttClient.subscribe('homebridge-switchbot/webhook/+');
this.mqttClient.on('message', async (topic, message) => {
try {
this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`);
const context = JSON.parse(message.toString());
this.webhookEventHandler[context.deviceMac]?.(context);
}
catch (e) {
this.errorLog(`Failed to handle webhook event. Error:${e.message ?? e}`);
}
});
}
}
catch (e) {
this.mqttClient = null;
this.errorLog(`Failed to establish MQTT connection. ${e.message ?? e}`);
}
}
}
async setupwebhook() {
// webhook configuration
if (this.config.options?.webhookURL) {
const url = this.config.options?.webhookURL;
try {
this.switchBotAPI.setupWebhook(url);
// Listen for webhook events
this.switchBotAPI.on('webhookEvent', (body) => {
if (this.config.options?.mqttURL) {
const mac = body.context.deviceMac?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':');
const options = this.config.options?.mqttPubOptions || {};
this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options);
}
this.webhookEventHandler[body.context.deviceMac]?.(body.context);
});
}
catch (e) {
this.errorLog(`Failed to setup webhook. Error:${e.message ?? e}`);
}
this.api.on('shutdown', async () => {
try {
this.switchBotAPI.deleteWebhook(url);
}
catch (e) {
this.errorLog(`Failed to delete webhook. Error:${e.message ?? e}`);
}
});
}
}
async setupBlE() {
this.switchBotBLE = new SwitchBotBLE();
// Listen for log events
if (!this.config.options?.disableLogsforBLE) {
this.switchBotBLE.on('log', (log) => {
switch (log.level) {
case LogLevel.SUCCESS:
this.successLog(log.message);
break;
case LogLevel.DEBUGSUCCESS:
this.debugSuccessLog(log.message);
break;
case LogLevel.WARN:
this.warnLog(log.message);
break;
case LogLevel.DEBUGWARN:
this.debugWarnLog(log.message);
break;
case LogLevel.ERROR:
this.errorLog(log.message);
break;
case LogLevel.DEBUGERROR:
this.debugErrorLog(log.message);
break;
case LogLevel.DEBUG:
this.debugLog(log.message);
break;
case LogLevel.INFO:
default:
this.infoLog(log.message);
}
});
}
if (this.config.options?.BLE) {
this.debugLog('setupBLE');
if (this.switchBotBLE === undefined) {
this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${JSON.stringify(this.switchBotBLE)}`);
}
else {
// Start to monitor advertisement packets
(async () => {
// Start to monitor advertisement packets
this.debugLog('Scanning for BLE SwitchBot devices...');
try {
await this.switchBotBLE.startScan();
}
catch (e) {
this.errorLog(`Failed to start BLE scanning. Error:${e.message ?? e}`);
}
// Set an event handler to monitor advertisement packets
this.switchBotBLE.onadvertisement = async (ad) => {
try {
this.bleEventHandler[ad.address]?.(ad.serviceData);
}
catch (e) {
this.errorLog(`Failed to handle BLE event. Error:${e.message ?? e}`);
}
};
})();
this.api.on('shutdown', async () => {
try {
// this.switchBotBLE.stopScan()
this.infoLog('Stopped BLE scanning to close listening.');
}
catch (e) {
this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message ?? e}`);
}
});
}
}
else {
this.debugLog('Platform BLE is not enabled');
}
}
/**
* This function is invoked when homebridge restores cached accessories from disk at startup.
* It should be used to setup event handlers for characteristics and update respective values.
*/
async configureAccessory(accessory) {
const { displayName } = accessory;
this.debugLog(`Loading accessory from cache: ${displayName}`);
// add the restored accessory to the accessories cache so we can track if it has already been registered
this.accessories.push(accessory);
}
/**
* Verify the config passed to the plugin is valid
*/
async verifyConfig() {
this.debugLog('Verifying Config');
this.config = this.config || {};
this.config.options = this.config.options || {};
if (this.config.options) {
// Device Config
if (this.config.options.devices) {
for (const deviceConfig of this.config.options.devices) {
if (!deviceConfig.hide_device) {
if (!deviceConfig.deviceId) {
throw new Error('The devices config section is missing the *Device ID* in the config. Please check your config.');
}
if (!deviceConfig.configDeviceType && deviceConfig.connectionType) {
throw new Error('The devices config section is missing the *Device Type* in the config. Please check your config.');
}
}
}
}
// IR Device Config
if (this.config.options.irdevices) {
for (const irDeviceConfig of this.config.options.irdevices) {
if (!irDeviceConfig.hide_device) {
if (!irDeviceConfig.deviceId) {
this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.');
}
if (!irDeviceConfig.deviceId && !irDeviceConfig.configRemoteType) {
this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.');
}
}
}
}
}
if (!this.config.credentials && !this.config.options) {
this.debugWarnLog('Missing Credentials');
}
else if (this.config.credentials && !this.config.credentials.notice) {
if (!this.config.credentials?.token) {
this.debugErrorLog('Missing token');
this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
}
if (this.config.credentials?.token) {
if (!this.config.credentials?.secret) {
this.debugErrorLog('Missing secret');
this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
}
}
}
}
async discoverDevices() {
if (!this.config.credentials?.token) {
return this.handleManualConfig();
}
let retryCount = 0;
const maxRetries = this.platformMaxRetries ?? 5;
const delayBetweenRetries = this.platformDelayBetweenRetries || 5000;
this.debugWarnLog(`Retry Count: ${retryCount}`);
this.debugWarnLog(`Max Retries: ${this.platformMaxRetries}`);
this.debugWarnLog(`Delay Between Retries: ${this.platformDelayBetweenRetries}`);
while (retryCount < maxRetries) {
try {
const { response, statusCode } = await this.switchBotAPI.getDevices();
this.debugLog(`response: ${JSON.stringify(response)}`);
if (this.isSuccessfulResponse(statusCode)) {
await this.handleDevices(Array.isArray(response.body.deviceList) ? response.body.deviceList : []);
await this.handleIRDevices(Array.isArray(response.body.infraredRemoteList) ? response.body.infraredRemoteList : []);
break;
}
else {
await this.handleErrorResponse(statusCode, retryCount, maxRetries, delayBetweenRetries);
retryCount++;
}
}
catch (e) {
retryCount++;
this.debugErrorLog(`Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`);
this.debugErrorLog(`Failed to Discover Devices, Error: ${e.message ?? e}`);
}
}
}
async handleManualConfig() {
if (this.config.options?.devices) {
this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`);
const devices = this.config.options.devices.map((v) => v);
for (const device of devices) {
device.deviceType = device.configDeviceType !== undefined ? device.configDeviceType : 'Unknown';
device.deviceName = device.configDeviceName !== undefined ? device.configDeviceName : 'Unknown';
try {
device.deviceId = formatDeviceIdAsMac(device.deviceId, true);
this.debugLog(`deviceId: ${device.deviceId}`);
if (device.deviceType) {
await this.createDevice(device);
}
}
catch (error) {
this.errorLog(`failed to format device ID as MAC, Error: ${error}`);
}
}
}
else {
this.errorLog('Neither SwitchBot Token or Device Config are set.');
}
}
isSuccessfulResponse(apiStatusCode) {
return (apiStatusCode === 200 || apiStatusCode === 100);
}
async handleDevices(deviceLists) {
if (!this.config.options?.devices && !this.config.options?.deviceConfig) {
this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
if (deviceLists.length === 0) {
this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled');
}
else {
for (const device of deviceLists) {
if (device.deviceType) {
if (device.configDeviceName) {
device.deviceName = device.configDeviceName;
}
await this.createDevice(device);
}
}
}
}
else if (this.config.options?.devices || this.config.options?.deviceConfig) {
this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
// Step 1: Check and assign configDeviceType to deviceType if deviceType is not present
const devicesWithTypeConfigPromises = deviceLists.map(async (device) => {
if (!device.deviceType) {
device.deviceType = device.configDeviceType !== undefined ? device.configDeviceType : 'Unknown';
this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
}
// Retrieve deviceTypeConfig for each device and merge it
const deviceTypeConfig = this.config.options?.deviceConfig?.[device.deviceType] || {};
return Object.assign({}, device, deviceTypeConfig);
});
// Wait for all promises to resolve
const devicesWithTypeConfig = (await Promise.all(devicesWithTypeConfigPromises)).filter(device => device !== null); // Filter out skipped devices
const devices = this.mergeByDeviceId(this.config.options.devices ?? [], devicesWithTypeConfig ?? []);
this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
for (const device of devices) {
const deviceIdConfig = this.config.options?.devices?.[device.deviceId] || {};
const deviceWithConfig = Object.assign({}, device, deviceIdConfig);
if (device.configDeviceName) {
device.deviceName = device.configDeviceName;
}
// Pass the merged device object to createDevice
await this.createDevice(deviceWithConfig);
}
}
}
async handleIRDevices(irDeviceLists) {
if (!this.config.options?.irdevices && !this.config.options?.irdeviceConfig) {
this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
for (const device of irDeviceLists) {
if (device.remoteType) {
await this.createIRDevice(device);
}
}
}
else if (this.config.options?.irdevices || this.config.options?.irdeviceConfig) {
this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
// Step 1: Check and assign configRemoteType to remoteType if remoteType is not present
const devicesWithTypeConfigPromises = irDeviceLists.map(async (device) => {
if (!device.remoteType && device.configRemoteType) {
device.remoteType = device.configRemoteType;
this.warnLog(`API is displaying no remoteType: ${device.remoteType}, So using configRemoteType: ${device.configRemoteType}`);
}
else if (!device.remoteType && !device.configDeviceName) {
this.errorLog('No remoteType or configRemoteType for device. No device will be created.');
return null; // Skip this device
}
// Retrieve remoteTypeConfig for each device and merge it
const remoteTypeConfig = this.config.options?.irdeviceConfig?.[device.remoteType] || {};
return Object.assign({}, device, remoteTypeConfig);
});
// Wait for all promises to resolve
const devicesWithRemoteTypeConfig = (await Promise.all(devicesWithTypeConfigPromises)).filter(device => device !== null); // Filter out skipped devices
const devices = this.mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithRemoteTypeConfig ?? []);
this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
for (const device of devices) {
const irdeviceIdConfig = this.config.options?.irdevices?.[device.deviceId] || {};
const irdeviceWithConfig = Object.assign({}, device, irdeviceIdConfig);
if (device.configDeviceName) {
device.deviceName = device.configDeviceName;
}
await this.createIRDevice(irdeviceWithConfig);
}
}
}
mergeByDeviceId(a1, a2) {
const normalizeDeviceId = (deviceId) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '');
return a1.map((itm) => {
const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId));
return { ...matchingItem, ...itm };
});
}
async handleErrorResponse(apiStatusCode, retryCount, maxRetries, delayBetweenRetries) {
await this.statusCode(apiStatusCode);
if (apiStatusCode === 500) {
this.infoLog(`statusCode: ${apiStatusCode} Attempt ${retryCount + 1} of ${maxRetries}`);
await sleep(delayBetweenRetries);
}
}
async createDevice(device) {
const deviceTypeHandlers = {
'Humidifier': this.createHumidifier.bind(this),
'Humidifier2': this.createHumidifier.bind(this),
'Hub 2': this.createHub2.bind(this),
'Bot': this.createBot.bind(this),
'Relay Switch 1': this.createRelaySwitch.bind(this),
'Relay Switch 1PM': this.createRelaySwitch.bind(this),
'Meter': this.createMeter.bind(this),
'MeterPlus': this.createMeterPlus.bind(this),
'Meter Plus (JP)': this.createMeterPlus.bind(this),
'Meter Pro': this.createMeterPro.bind(this),
'MeterPro(CO2)': this.createMeterPro.bind(this),
'WoIOSensor': this.createIOSensor.bind(this),
'Water Detector': this.createWaterDetector.bind(this),
'Motion Sensor': this.createMotion.bind(this),
'Contact Sensor': this.createContact.bind(this),
'Curtain': this.createCurtain.bind(this),
'Curtain3': this.createCurtain.bind(this),
'WoRollerShade': this.createCurtain.bind(this),
'Roller Shade': this.createCurtain.bind(this),
'Blind Tilt': this.createBlindTilt.bind(this),
'Plug': this.createPlug.bind(this),
'Plug Mini (US)': this.createPlug.bind(this),
'Plug Mini (JP)': this.createPlug.bind(this),
'Smart Lock': this.createLock.bind(this),
'Smart Lock Pro': this.createLock.bind(this),
'Color Bulb': this.createColorBulb.bind(this),
'K10+': this.createRobotVacuumCleaner.bind(this),
'K10+ Pro': this.createRobotVacuumCleaner.bind(this),
'WoSweeper': this.createRobotVacuumCleaner.bind(this),
'WoSweeperMini': this.createRobotVacuumCleaner.bind(this),
'Robot Vacuum Cleaner S1': this.createRobotVacuumCleaner.bind(this),
'Robot Vacuum Cleaner S1 Plus': this.createRobotVacuumCleaner.bind(this),
'Robot Vacuum Cleaner S10': this.createRobotVacuumCleaner.bind(this),
'Ceiling Light': this.createCeilingLight.bind(this),
'Ceiling Light Pro': this.createCeilingLight.bind(this),
'Strip Light': this.createStripLight.bind(this),
'Battery Circulator Fan': this.createFan.bind(this),
};
if (deviceTypeHandlers[device.deviceType]) {
this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
await deviceTypeHandlers[device.deviceType](device);
}
else if (['Hub Mini', 'Hub Plus', 'Remote', 'Indoor Cam', 'remote with screen'].includes(device.deviceType)) {
this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`);
}
else {
this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported. Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
}
}
async createIRDevice(device) {
device.connectionType = device.connectionType ?? 'OpenAPI';
const deviceTypeHandlers = {
'TV': this.createTV.bind(this),
'DIY TV': this.createTV.bind(this),
'Projector': this.createTV.bind(this),
'DIY Projector': this.createTV.bind(this),
'Set Top Box': this.createTV.bind(this),
'DIY Set Top Box': this.createTV.bind(this),
'IPTV': this.createTV.bind(this),
'DIY IPTV': this.createTV.bind(this),
'DVD': this.createTV.bind(this),
'DIY DVD': this.createTV.bind(this),
'Speaker': this.createTV.bind(this),
'DIY Speaker': this.createTV.bind(this),
'Fan': this.createIRFan.bind(this),
'DIY Fan': this.createIRFan.bind(this),
'Air Conditioner': this.createAirConditioner.bind(this),
'DIY Air Conditioner': this.createAirConditioner.bind(this),
'Light': this.createLight.bind(this),
'DIY Light': this.createLight.bind(this),
'Air Purifier': this.createAirPurifier.bind(this),
'DIY Air Purifier': this.createAirPurifier.bind(this),
'Water Heater': this.createWaterHeater.bind(this),
'DIY Water Heater': this.createWaterHeater.bind(this),
'Vacuum Cleaner': this.createVacuumCleaner.bind(this),
'DIY Vacuum Cleaner': this.createVacuumCleaner.bind(this),
'Camera': this.createCamera.bind(this),
'DIY Camera': this.createCamera.bind(this),
'Others': this.createOthers.bind(this),
};
if (deviceTypeHandlers[device.remoteType]) {
this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
if (device.remoteType.startsWith('DIY') && device.external === undefined) {
device.external = true;
}
await deviceTypeHandlers[device.remoteType](device);
}
else {
this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported. Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
}
}
async createHumidifier(device) {
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
// see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
// the accessory already exists
if (await this.registerDevice(device)) {
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
existingAccessory.context.device = device;
existingAccessory.context.deviceId = device.deviceId;
existingAccessory.context.deviceType = device.deviceType;
existingAccessory.context.model = device.deviceType === 'Humidifier2' ? SwitchBotModel.Humidifier2 : SwitchBotModel.Humidifier;
existingAccessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
existingAccessory.context.connectionType = await this.connectionType(device);
existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`);
this.api.updatePlatformAccessories([existingAccessory]);
// create the accessory handler for the restored accessory
// this is imported from `platformAccessory.ts`
new Humidifier(this, existingAccessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`);
}
else {
this.unregisterPlatformAccessories(existingAccessory);
}
}
else if (await this.registerDevice(device)) {
// create a new accessory
const accessory = new this.api.platformAccessory(device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid);
// store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the accessory you may need
accessory.context.device = device;
accessory.context.deviceId = device.deviceId;
accessory.context.deviceType = device.deviceType;
accessory.context.model = device.deviceType === 'Humidifier2' ? SwitchBotModel.Humidifier2 : SwitchBotModel.Humidifier;
accessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
// create the accessory handler for the newly create accessory
// this is imported from `platformAccessory.ts`
new Humidifier(this, accessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`);
// publish device externally or link the accessory to your platform
this.externalOrPlatform(device, accessory);
this.accessories.push(accessory);
}
else {
this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`);
}
}
async createBot(device) {
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
// see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
// the accessory already exists
if (await this.registerDevice(device)) {
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
existingAccessory.context.device = device;
existingAccessory.context.deviceId = device.deviceId;
existingAccessory.context.deviceType = device.deviceType;
existingAccessory.context.model = SwitchBotModel.Bot;
existingAccessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
existingAccessory.context.connectionType = await this.connectionType(device);
existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`);
this.api.updatePlatformAccessories([existingAccessory]);
// create the accessory handler for the restored accessory
// this is imported from `platformAccessory.ts`
new Bot(this, existingAccessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`);
}
else {
this.unregisterPlatformAccessories(existingAccessory);
}
}
else if (await this.registerDevice(device)) {
// create a new accessory
const accessory = new this.api.platformAccessory(device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid);
// store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the accessory you may need
accessory.context.device = device;
accessory.context.deviceId = device.deviceId;
accessory.context.deviceType = device.deviceType;
accessory.context.model = SwitchBotModel.Bot;
accessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
// accessory.context.version = findaccessories.accessoryAttribute.softwareRevision;
// create the accessory handler for the newly create accessory
// this is imported from `platformAccessory.ts`
new Bot(this, accessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`);
// publish device externally or link the accessory to your platform
this.externalOrPlatform(device, accessory);
this.accessories.push(accessory);
}
else {
this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`);
}
}
async createRelaySwitch(device) {
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
// see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
// the accessory already exists
if (await this.registerDevice(device)) {
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
existingAccessory.context.device = device;
existingAccessory.context.deviceId = device.deviceId;
existingAccessory.context.deviceType = device.deviceType;
existingAccessory.context.model = device.deviceType === 'Relay Switch 1' ? SwitchBotModel.RelaySwitch1 : SwitchBotModel.RelaySwitch1PM;
existingAccessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
existingAccessory.context.connectionType = await this.connectionType(device);
existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`);
this.api.updatePlatformAccessories([existingAccessory]);
// create the accessory handler for the restored accessory
// this is imported from `platformAccessory.ts`
new RelaySwitch(this, existingAccessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`);
}
else {
this.unregisterPlatformAccessories(existingAccessory);
}
}
else if (await this.registerDevice(device)) {
// create a new accessory
const accessory = new this.api.platformAccessory(device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid);
// store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the accessory you may need
accessory.context.device = device;
accessory.context.deviceId = device.deviceId;
accessory.context.deviceType = device.deviceType;
accessory.context.model = device.deviceType === 'Relay Switch 1' ? SwitchBotModel.RelaySwitch1 : SwitchBotModel.RelaySwitch1PM;
accessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
// accessory.context.version = findaccessories.accessoryAttribute.softwareRevision;
// create the accessory handler for the newly create accessory
// this is imported from `platformAccessory.ts`
new RelaySwitch(this, accessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`);
// publish device externally or link the accessory to your platform
this.externalOrPlatform(device, accessory);
this.accessories.push(accessory);
}
else {
this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`);
}
}
async createMeter(device) {
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
// see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
// the accessory already exists
if (await this.registerDevice(device)) {
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
existingAccessory.context.device = device;
existingAccessory.context.model = SwitchBotModel.Meter;
existingAccessory.context.deviceId = device.deviceId;
existingAccessory.context.deviceType = device.deviceType;
existingAccessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
existingAccessory.context.connectionType = await this.connectionType(device);
existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`);
this.api.updatePlatformAccessories([existingAccessory]);
// create the accessory handler for the restored accessory
// this is imported from `platformAccessory.ts`
new Meter(this, existingAccessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`);
}
else {
this.unregisterPlatformAccessories(existingAccessory);
}
}
else if (await this.registerDevice(device)) {
// create a new accessory
const accessory = new this.api.platformAccessory(device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid);
// store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the accessory you may need
accessory.context.device = device;
accessory.context.model = SwitchBotModel.Meter;
accessory.context.deviceId = device.deviceId;
accessory.context.deviceType = device.deviceType;
accessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.connectionType = await this.connectionType(device);
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
// create the accessory handler for the newly create accessory
// this is imported from `platformAccessory.ts`
new Meter(this, accessory, device);
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`);
// publish device externally or link the accessory to your platform
this.externalOrPlatform(device, accessory);
this.accessories.push(accessory);
}
else {
this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`);
}
}
async createMeterPlus(device) {
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
// see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
// the accessory already exists
if (await this.registerDevice(device)) {
// console.log("existingAccessory", existingAccessory);
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
existingAccessory.context.device = device;
existingAccessory.context.model = SwitchBotModel.MeterPlusUS ?? SwitchBotModel.MeterPlusJP;
existingAccessory.context.deviceId = device.deviceId;
existingAccessory.context.deviceType = device.deviceType;
existingAccessory.displayName = device.configDeviceName
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);