UNPKG

@switchbot/homebridge-switchbot

Version:

The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.

884 lines (883 loc) • 180 kB
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);