From 9a5a2a471d8e4cec6cef8877b3c88882084a7c4b Mon Sep 17 00:00:00 2001 From: Milo Cesar Date: Thu, 18 Jun 2020 19:39:21 +0200 Subject: [PATCH] Switch request library to axios --- index.js | 303 ++++++++++++----------- lib/base_accessory.js | 224 +++++++++-------- lib/dimmer_accessory.js | 212 ++++++++-------- lib/fan_accessory.js | 202 ++++++++------- lib/light_accessory.js | 461 +++++++++++++++++----------------- lib/outlet_accessory.js | 103 ++++---- lib/switch_accessory.js | 103 ++++---- lib/tuyawebapi.js | 534 +++++++++++++++++++--------------------- package-lock.json | 354 ++------------------------ package.json | 2 +- 10 files changed, 1066 insertions(+), 1432 deletions(-) diff --git a/index.js b/index.js index bfc8433..b8a890f 100644 --- a/index.js +++ b/index.js @@ -8,178 +8,177 @@ const TuyaWebApi = require('./lib/tuyawebapi'); var Accessory, Service, Characteristic, UUIDGen; module.exports = function (homebridge) { - // Accessory must be created from PlatformAccessory Constructor - Accessory = homebridge.platformAccessory; + // Accessory must be created from PlatformAccessory Constructor + Accessory = homebridge.platformAccessory; - // Service and Characteristic are from hap-nodejs - Service = homebridge.hap.Service; - Characteristic = homebridge.hap.Characteristic; - UUIDGen = homebridge.hap.uuid; + // Service and Characteristic are from hap-nodejs + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + UUIDGen = homebridge.hap.uuid; - // For platform plugin to be considered as dynamic platform plugin, - // registerPlatform(pluginName, platformName, constructor, dynamic), dynamic must be true - homebridge.registerPlatform("homebridge-tuya-web", "TuyaWebPlatform", TuyaWebPlatform, true); + // For platform plugin to be considered as dynamic platform plugin, + homebridge.registerPlatform("homebridge-tuya-web", "TuyaWebPlatform", TuyaWebPlatform, true); } class TuyaWebPlatform { - constructor(log, config, api) { - this.log = log; - this.config = config; - this.pollingInterval = 10; // default 10 seconds - this.refreshInterval; - - if (!config || !config.options) { - this.log('No config found, disabling plugin.') - return; + constructor(log, config, api) { + this.log = log; + this.config = config; + this.pollingInterval = 10; // default 10 seconds + this.refreshInterval; + + if (!config || !config.options) { + this.log('No config found, disabling plugin.') + return; + } + + // Set cloud polling interval + this.pollingInterval = this.config.options.pollingInterval || 10; + + // Create Tuya Web API instance + this.tuyaWebApi = new TuyaWebApi( + this.config.options.username, + this.config.options.password, + this.config.options.countryCode, + this.config.options.platform, + this.log + ); + + this.accessories = new Map(); + this.failedToInitAccessories = []; + + if (api) { + // Save the API object as plugin needs to register new accessory via this object + this.api = api; + + // Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories. + // Platform Plugin should only register new accessory that doesn't exist in homebridge after this event. + // Or start discover new accessories. + this.api.on('didFinishLaunching', function () { + this.log("Initializing TuyaWebPlatform..."); + + // Get access token + this.tuyaWebApi.getOrRefreshToken().then((token) => { + this.tuyaWebApi.token = token; + + // Start discovery for devices + this.tuyaWebApi.discoverDevices().then((devices) => { + // Add devices to Homebridge + for (const device of devices) { + this.addAccessory(device); + } + // Get device state of all devices - once + this.refreshDeviceStates(); + }).catch((error) => { + this.log.error(error); + }); + + // Set interval for refreshing device states + this.refreshInterval = setInterval(() => { + this.refreshDeviceStates(); + }, this.pollingInterval * 1000); + }).catch((error) => { + this.log.error(error); + }); + + }.bind(this)); + } } - // Set cloud polling interval - this.pollingInterval = this.config.options.pollingInterval || 10; - - // Create Tuya Web API instance - this.tuyaWebApi = new TuyaWebApi( - this.config.options.username, - this.config.options.password, - this.config.options.countryCode, - this.config.options.platform, - this.log - ); - - this.accessories = new Map(); - this.failedToInitAccessories = []; - - if (api) { - // Save the API object as plugin needs to register new accessory via this object - this.api = api; - - // Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories. - // Platform Plugin should only register new accessory that doesn't exist in homebridge after this event. - // Or start discover new accessories. - this.api.on('didFinishLaunching', function () { - this.log("Initializing TuyaWebPlatform..."); - - // Get access token - this.tuyaWebApi.getOrRefreshToken().then((token) => { - this.tuyaWebApi.token = token; - - // Start discovery for devices - this.tuyaWebApi.discoverDevices().then((devices) => { - // Add devices to Homebridge + refreshDeviceStates() { + this.log.debug('Refreshing state for all devices...'); + this.tuyaWebApi.getAllDeviceStates().then((devices) => { + // Refresh device states for (const device of devices) { - this.addAccessory(device); + const uuid = this.api.hap.uuid.generate(device.id); + const homebridgeAccessory = this.accessories.get(uuid); + if (homebridgeAccessory) { + homebridgeAccessory.controller.updateAccessory(device); + } else if (!this.failedToInitAccessories.includes(uuid)) { + this.log.error('Could not find accessory in dictionary (%s)', uuid); + } } - // Get device state of all devices - once - this.refreshDeviceStates(); - }).catch((error) => { - this.log.error(error); - }); - - // Set interval for refreshing device states - this.refreshInterval = setInterval(() => { - this.refreshDeviceStates(); - }, this.pollingInterval * 1000); }).catch((error) => { - this.log.error(error); + this.log.error('Error retrieving devices states', error); }); - - }.bind(this)); } - } - refreshDeviceStates() { - this.log.debug('Refreshing state for all devices...'); - this.tuyaWebApi.getAllDeviceStates().then((devices) => { - // Refresh device states - for (const device of devices) { + addAccessory(device) { + var deviceType = device.dev_type || 'switch'; + + // Get UUID const uuid = this.api.hap.uuid.generate(device.id); const homebridgeAccessory = this.accessories.get(uuid); - if (homebridgeAccessory) { - homebridgeAccessory.controller.updateAccessory(device); - } else if(!this.failedToInitAccessories.includes(uuid)){ - this.log.error('Could not find accessory in dictionary (%s)', uuid); + + // Is device type overruled in config defaults? + if (this.config.defaults) { + for (const def of this.config.defaults) { + if (def.id === device.id) { + deviceType = def.device_type || deviceType; + this.log('Device type is overruled in config to: ', deviceType); + } + } } - } - }).catch((error) => { - this.log.error('Error retrieving devices states', error); - }); - } - - addAccessory(device) { - var deviceType = device.dev_type || 'switch'; - - // Get UUID - const uuid = this.api.hap.uuid.generate(device.id); - const homebridgeAccessory = this.accessories.get(uuid); - - // Is device type overruled in config defaults? - if (this.config.defaults) { - for (const def of this.config.defaults) { - if (def.id === device.id) { - deviceType = def.device_type || deviceType; - this.log('Device type is overruled in config to: ', deviceType); + + // Construct new accessory + let deviceAccessory; + switch (deviceType) { + case 'light': + deviceAccessory = new LightAccessory(this, homebridgeAccessory, device); + this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); + break; + case 'fan': + deviceAccessory = new FanAccessory(this, homebridgeAccessory, device); + this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); + break; + case 'dimmer': + deviceAccessory = new DimmerAccessory(this, homebridgeAccessory, device); + this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); + break; + case 'switch': + case 'outlet': + deviceAccessory = new OutletAccessory(this, homebridgeAccessory, device); + this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); + break; + default: + this.log.warn('Could not init class for device type [%s]', deviceType); + this.failedToInitAccessories.push(uuid); + break; } - } } - // Construct new accessory - let deviceAccessory; - switch (deviceType) { - case 'light': - deviceAccessory = new LightAccessory(this, homebridgeAccessory, device); - this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); - break; - case 'fan': - deviceAccessory = new FanAccessory(this, homebridgeAccessory, device); - this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); - break; - case 'dimmer': - deviceAccessory = new DimmerAccessory(this, homebridgeAccessory, device); - this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); - break; - case 'switch': - case 'outlet': - deviceAccessory = new OutletAccessory(this, homebridgeAccessory, device); - this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); - break; - default: - this.log.warn('Could not init class for device type [%s]', deviceType); - this.failedToInitAccessories.push(uuid); - break; + // Called from device classes + registerPlatformAccessory(platformAccessory) { + this.log.debug('Register Platform Accessory (%s)', platformAccessory.displayName); + this.api.registerPlatformAccessories('homebridge-tuya-web', 'TuyaWebPlatform', [platformAccessory]); + } + + // Function invoked when homebridge tries to restore cached accessory. + // Developer can configure accessory at here (like setup event handler). + // Update current value. + configureAccessory(accessory) { + this.log("Configuring cached accessory [%s]", accessory.displayName, accessory.context.deviceId, accessory.UUID); + + // Set the accessory to reachable if plugin can currently process the accessory, + // otherwise set to false and update the reachability later by invoking + // accessory.updateReachability() + accessory.reachable = true; + + accessory.on('identify', function (paired, callback) { + this.log.debug('[IDENTIFY][%s]', accessory.displayName); + callback(); + }); + + this.accessories.set(accessory.UUID, accessory); + } + + // Sample function to show how developer can remove accessory dynamically from outside event + removeAccessory(accessory) { + this.log("Remove Accessory [%s]", accessory.displayName); + this.api.unregisterPlatformAccessories("homebridge-tuya-web", "TuyaWebPlatform", [accessory]); + + this.accessories.delete(accessory.uuid); } - } - - // Called from device classes - registerPlatformAccessory(platformAccessory) { - this.log.debug('Register Platform Accessory (%s)', platformAccessory.displayName); - this.api.registerPlatformAccessories('homebridge-tuya-web', 'TuyaWebPlatform', [platformAccessory]); - } - - // Function invoked when homebridge tries to restore cached accessory. - // Developer can configure accessory at here (like setup event handler). - // Update current value. - configureAccessory(accessory) { - this.log("Configuring cached accessory [%s]", accessory.displayName, accessory.context.deviceId, accessory.UUID); - - // Set the accessory to reachable if plugin can currently process the accessory, - // otherwise set to false and update the reachability later by invoking - // accessory.updateReachability() - accessory.reachable = true; - - accessory.on('identify', function (paired, callback) { - this.log.debug('[IDENTIFY][%s]', accessory.displayName); - callback(); - }); - - this.accessories.set(accessory.UUID, accessory); - } - - // Sample function to show how developer can remove accessory dynamically from outside event - removeAccessory(accessory) { - this.log("Remove Accessory [%s]", accessory.displayName); - this.api.unregisterPlatformAccessories("homebridge-tuya-web", "TuyaWebPlatform", [accessory]); - - this.accessories.delete(accessory.uuid); - } } diff --git a/lib/base_accessory.js b/lib/base_accessory.js index 4fef0ef..33569b9 100644 --- a/lib/base_accessory.js +++ b/lib/base_accessory.js @@ -7,130 +7,128 @@ let Characteristic; let UUIDGen; class BaseAccessory { - constructor(platform, homebridgeAccessory, deviceConfig, categoryType) { - this.platform = platform; - this.deviceId = deviceConfig.id; - this.categoryType = categoryType; - PlatformAccessory = platform.api.platformAccessory; - - ({ Accessory, Service, Characteristic, uuid: UUIDGen } = platform.api.hap); - - this.log = platform.log; - this.homebridgeAccessory = homebridgeAccessory; - this.deviceConfig = deviceConfig; - - // Setup caching - this.cachedState = new Map(); - this.validCache = false; - - this.serviceType; - switch (categoryType) { - case Accessory.Categories.LIGHTBULB: - this.serviceType = Service.Lightbulb - break; - case Accessory.Categories.SWITCH: - this.serviceType = Service.Switch; - break; - case Accessory.Categories.OUTLET: - this.serviceType = Service.Outlet; - break; - case Accessory.Categories.FAN: - this.serviceType = Service.Fanv2; - break; - default: - this.serviceType = Service.AccessoryInformation; + constructor(platform, homebridgeAccessory, deviceConfig, categoryType) { + this.platform = platform; + this.deviceId = deviceConfig.id; + this.categoryType = categoryType; + PlatformAccessory = platform.api.platformAccessory; + + ({Accessory, Service, Characteristic, uuid: UUIDGen} = platform.api.hap); + + this.log = platform.log; + this.homebridgeAccessory = homebridgeAccessory; + this.deviceConfig = deviceConfig; + + // Setup caching + this.cachedState = new Map(); + this.validCache = false; + + this.serviceType; + switch (categoryType) { + case Accessory.Categories.LIGHTBULB: + this.serviceType = Service.Lightbulb + break; + case Accessory.Categories.SWITCH: + this.serviceType = Service.Switch; + break; + case Accessory.Categories.OUTLET: + this.serviceType = Service.Outlet; + break; + case Accessory.Categories.FAN: + this.serviceType = Service.Fanv2; + break; + default: + this.serviceType = Service.AccessoryInformation; + } + + // Retrieve existing of create new Bridged Accessory + if (this.homebridgeAccessory) { + this.homebridgeAccessory.controller = this; + if (!this.homebridgeAccessory.context.deviceId) { + this.homebridgeAccessory.context.deviceId = this.deviceConfig.id; + } + this.log.info( + 'Existing Accessory found [%s] [%s] [%s]', + homebridgeAccessory.displayName, + homebridgeAccessory.context.deviceId, + homebridgeAccessory.UUID); + this.homebridgeAccessory.displayName = this.deviceConfig.name; + } else { + this.log.info('Creating New Accessory %s', this.deviceConfig.id); + this.homebridgeAccessory = new PlatformAccessory( + this.deviceConfig.name, + UUIDGen.generate(this.deviceConfig.id), + categoryType); + this.homebridgeAccessory.context.deviceId = this.deviceConfig.id; + this.homebridgeAccessory.controller = this; + this.platform.registerPlatformAccessory(this.homebridgeAccessory); + } + + // Create service + this.service = this.homebridgeAccessory.getService(this.serviceType); + if (this.service) { + this.service.setCharacteristic(Characteristic.Name, this.deviceConfig.name); + } else { + this.log.debug('Creating New Service %s', this.deviceConfig.id); + this.service = this.homebridgeAccessory.addService(this.serviceType, this.deviceConfig.name); + } + + this.homebridgeAccessory.on('identify', (paired, callback) => { + this.log.debug('[IDENTIFY][%s]', this.homebridgeAccessory.displayName); + callback(); + }); } - // Retrieve existing of create new Bridged Accessory - if (this.homebridgeAccessory) { - this.homebridgeAccessory.controller = this; - if (!this.homebridgeAccessory.context.deviceId) { - this.homebridgeAccessory.context.deviceId = this.deviceConfig.id; - } - this.log.info( - 'Existing Accessory found [%s] [%s] [%s]', - homebridgeAccessory.displayName, - homebridgeAccessory.context.deviceId, - homebridgeAccessory.UUID); - this.homebridgeAccessory.displayName = this.deviceConfig.name; - } - else { - this.log.info('Creating New Accessory %s', this.deviceConfig.id); - this.homebridgeAccessory = new PlatformAccessory( - this.deviceConfig.name, - UUIDGen.generate(this.deviceConfig.id), - categoryType); - this.homebridgeAccessory.context.deviceId = this.deviceConfig.id; - this.homebridgeAccessory.controller = this; - this.platform.registerPlatformAccessory(this.homebridgeAccessory); + updateAccessory(device) { + // Update general accessory information + if (device.name) { + this.homebridgeAccessory.displayName = device.name; + this.homebridgeAccessory._associatedHAPAccessory.displayName = device.name; + var accessoryInformationService = ( + this.homebridgeAccessory.getService(Service.AccessoryInformation) || + this.homebridgeAccessory.addService(Service.AccessoryInformation)); + var characteristicName = ( + accessoryInformationService.getCharacteristic(Characteristic.Name) || + accessoryInformationService.addCharacteristic(Characteristic.Name)); + if (characteristicName) { + characteristicName.setValue(device.name); + } + var characteristicSerialNumber = ( + accessoryInformationService.getCharacteristic(Characteristic.SerialNumber) || + accessoryInformationService.addCharacteristic(Characteristic.SerialNumber)); + if (characteristicSerialNumber) { + characteristicSerialNumber.setValue(this.deviceConfig.id); + } + } + + // Update device specific state + this.updateState(device.data); } - // Create service - this.service = this.homebridgeAccessory.getService(this.serviceType); - if (this.service) { - this.service.setCharacteristic(Characteristic.Name, this.deviceConfig.name); - } - else { - this.log.debug('Creating New Service %s', this.deviceConfig.id); - this.service = this.homebridgeAccessory.addService(this.serviceType, this.deviceConfig.name); + getServiceType() { + return this.serviceType; } - this.homebridgeAccessory.on('identify', (paired, callback) => { - this.log.debug('[IDENTIFY][%s]', this.homebridgeAccessory.displayName); - callback(); - }); - } - - updateAccessory(device) { - // Update general accessory information - if (device.name) { - this.homebridgeAccessory.displayName = device.name; - this.homebridgeAccessory._associatedHAPAccessory.displayName = device.name; - var accessoryInformationService = ( - this.homebridgeAccessory.getService(Service.AccessoryInformation) || - this.homebridgeAccessory.addService(Service.AccessoryInformation)); - var characteristicName = ( - accessoryInformationService.getCharacteristic(Characteristic.Name) || - accessoryInformationService.addCharacteristic(Characteristic.Name)); - if (characteristicName) { - characteristicName.setValue(device.name); - } - var characteristicSerialNumber = ( - accessoryInformationService.getCharacteristic(Characteristic.SerialNumber) || - accessoryInformationService.addCharacteristic(Characteristic.SerialNumber)); - if (characteristicSerialNumber) { - characteristicSerialNumber.setValue(this.deviceConfig.id); - } + getCategoryType() { + return this.categoryType; } - // Update device specific state - this.updateState(device.data); - } - - getServiceType() { - return this.serviceType; - } - - getCategoryType() { - return this.categoryType; - } - - setCachedState(characteristic, value) { - this.cachedState.set(characteristic, value); - this.validCache = true; - } + setCachedState(characteristic, value) { + this.cachedState.set(characteristic, value); + this.validCache = true; + } - getCachedState(characteristic) { - return this.cachedState.get(characteristic); - } + getCachedState(characteristic) { + return this.cachedState.get(characteristic); + } - hasValidCache() { - return this.validCache && this.cachedState.size > 0; - } + hasValidCache() { + return this.validCache && this.cachedState.size > 0; + } - invalidateCache() { - this.validCache = false; - } + invalidateCache() { + this.validCache = false; + } } module.exports = BaseAccessory; diff --git a/lib/dimmer_accessory.js b/lib/dimmer_accessory.js index 7bdb054..0094642 100644 --- a/lib/dimmer_accessory.js +++ b/lib/dimmer_accessory.js @@ -1,123 +1,117 @@ -const TuyaWebApi = require('./tuyawebapi'); const BaseAccessory = require('./base_accessory') -let PlatformAccessory; let Accessory; let Service; let Characteristic; -let UUIDGen; class DimmerAccessory extends BaseAccessory { - constructor(platform, homebridgeAccessory, deviceConfig) { - - ({ Accessory, Characteristic, Service } = platform.api.hap); - - super( - platform, - homebridgeAccessory, - deviceConfig, - Accessory.Categories.LIGHTBULB - ) - - // Characteristic.On - this.service.getCharacteristic(Characteristic.On) - .on('get', (callback) => { - - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.On)); - } - else { - - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); - this.getCachedState(Characteristic.On, data.state); - callback(null, data.state); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); + constructor(platform, homebridgeAccessory, deviceConfig) { + ({Accessory, Characteristic, Service} = platform.api.hap); + + super( + platform, + homebridgeAccessory, + deviceConfig, + Accessory.Categories.LIGHTBULB + ) + + // Characteristic.On + this.service.getCharacteristic(Characteristic.On) + .on('get', (callback) => { + + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.On)); + } else { + + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); + this.getCachedState(Characteristic.On, data.state); + callback(null, data.state); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + + } + }) + .on('set', (state, callback) => { + + // Set device state in Tuya Web API + const value = state ? 1 : 0; + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, state, value); + this.setCachedState(Characteristic.On, state); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + + }); + + // Characteristic.Brightness + this.service.getCharacteristic(Characteristic.Brightness) + .on('get', callback => { + + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.Brightness)); + } else { + + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + const percentage = Math.floor((parseInt(data.brightness) / 255) * 100); + this.log.debug('[GET][%s] Characteristic.Brightness: %s (%s percent)', this.homebridgeAccessory.displayName, data.brightness, percentage); + this.getCachedState(Characteristic.Brightness, percentage); + callback(null, percentage); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + + } + }) + .on('set', (percentage, callback) => { + + // NOTE: For some strange reason, the set value for brightness is in percentage. + + // Set device state in Tuya Web API + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'brightnessSet', {value: percentage}).then(() => { + this.log.debug('[SET][%s] Characteristic.Brightness: %s percent', this.homebridgeAccessory.displayName, percentage); + this.setCachedState(Characteristic.Brightness, percentage); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + + }); + } + updateState(data) { + // Update device type specific state + this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); + if (data.state) { + const state = (data.state === 'true'); + this.service + .getCharacteristic(Characteristic.On) + .updateValue(state); + this.setCachedState(Characteristic.On, state); } - }) - .on('set', (state, callback) => { - - // Set device state in Tuya Web API - const value = state ? 1 : 0; - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', { value: value }).then(() => { - this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, state, value); - this.setCachedState(Characteristic.On, state); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - - }); - - // Characteristic.Brightness - this.service.getCharacteristic(Characteristic.Brightness) - .on('get', callback => { - - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.Brightness)); - } - else { - - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + if (data.percentage) { const percentage = Math.floor((parseInt(data.brightness) / 255) * 100); - this.log.debug('[GET][%s] Characteristic.Brightness: %s (%s percent)', this.homebridgeAccessory.displayName, data.brightness, percentage); - this.getCachedState(Characteristic.Brightness, percentage); - callback(null, percentage); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - + this.service + .getCharacteristic(Characteristic.Brightness) + .updateValue(percentage); + this.setCachedState(Characteristic.Brightness, percentage); } - }) - .on('set', (percentage, callback) => { - - // NOTE: For some strange reason, the set value for brightness is in percentage. - - // Set device state in Tuya Web API - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'brightnessSet', { value: percentage }).then(() => { - this.log.debug('[SET][%s] Characteristic.Brightness: %s percent', this.homebridgeAccessory.displayName, percentage); - this.setCachedState(Characteristic.Brightness, percentage); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - - }); - } - - updateState(data) { - // Update device type specific state - this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); - if (data.state) { - const state = (data.state === 'true'); - this.service - .getCharacteristic(Characteristic.On) - .updateValue(state); - this.setCachedState(Characteristic.On, state); - } - if (data.percentage) { - const percentage = Math.floor((parseInt(data.brightness) / 255) * 100); - this.service - .getCharacteristic(Characteristic.Brightness) - .updateValue(percentage); - this.setCachedState(Characteristic.Brightness, percentage); } - } } -module.exports = DimmerAccessory; \ No newline at end of file +module.exports = DimmerAccessory; diff --git a/lib/fan_accessory.js b/lib/fan_accessory.js index d34af8e..8976553 100644 --- a/lib/fan_accessory.js +++ b/lib/fan_accessory.js @@ -1,124 +1,120 @@ -const TuyaWebApi = require('./tuyawebapi'); const BaseAccessory = require('./base_accessory') -let PlatformAccessory; let Accessory; let Service; let Characteristic; -let UUIDGen; class FanAccessory extends BaseAccessory { - constructor(platform, homebridgeAccessory, deviceConfig) { + constructor(platform, homebridgeAccessory, deviceConfig) { + ({Accessory, Characteristic, Service} = platform.api.hap); - ({Accessory, Characteristic, Service} = platform.api.hap); + super( + platform, + homebridgeAccessory, + deviceConfig, + Accessory.Categories.FAN + ); - super( - platform, - homebridgeAccessory, - deviceConfig, - Accessory.Categories.FAN - ); + // Characteristic.Active + this.service.getCharacteristic(Characteristic.Active) + .on('get', (callback) => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.Active)); + } else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + var isOn = data.state === 'true' ? 1 : 0; + this.log.debug('[GET][%s] Characteristic.Active: %s', this.homebridgeAccessory.displayName, isOn); + this.setCachedState(Characteristic.On, isOn); + callback(null, data.state); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.Active Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } + }) + .on('set', (isOn, callback) => { + // Set device state in Tuya Web API + const value = isOn ? 1 : 0; - // Characteristic.Active - this.service.getCharacteristic(Characteristic.Active) - .on('get', (callback) => { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.Active)); - } else { - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - var isOn = data.state === 'true' ? 1 : 0; - this.log.debug('[GET][%s] Characteristic.Active: %s', this.homebridgeAccessory.displayName, isOn); - this.setCachedState(Characteristic.On, isOn); - callback(null, data.state); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.Active Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.Active: %s %s', this.homebridgeAccessory.displayName, isOn, value); + this.setCachedState(Characteristic.Active, isOn); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.Active Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); }); - } - }) - .on('set', (isOn, callback) => { - // Set device state in Tuya Web API - const value = isOn ? 1 : 0; - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', {value: value}).then(() => { - this.log.debug('[SET][%s] Characteristic.Active: %s %s', this.homebridgeAccessory.displayName, isOn, value); - this.setCachedState(Characteristic.Active, isOn); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.Active Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - }); + if (deviceConfig.data && deviceConfig.data.speed_level) { + var maxSpeedLevel = deviceConfig.data.speed_level; + var stepSize = Math.floor(100 / maxSpeedLevel); + // Characteristic.RotationSpeed + this.service.getCharacteristic(Characteristic.RotationSpeed) + .setProps({ + minStep: stepSize + }) + .on('get', (callback) => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.RotationSpeed)); + } else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + var floatValue = Number(data.speed) * stepSize; + this.log.debug('[GET][%s] Characteristic.RotationSpeed: %s', this.homebridgeAccessory.displayName, floatValue); + this.setCachedState(Characteristic.RotationSpeed, floatValue); + callback(null, floatValue); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.RotationSpeed Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } + }) + .on('set', (floatValue, callback) => { + // Set device state in Tuya Web API + const value = Math.round(floatValue / stepSize) - if(deviceConfig.data && deviceConfig.data.speed_level) { - var maxSpeedLevel = deviceConfig.data.speed_level; - var stepSize = Math.floor(100/maxSpeedLevel); - // Characteristic.RotationSpeed - this.service.getCharacteristic(Characteristic.RotationSpeed) - .setProps({ - minStep: stepSize - }) - .on('get', (callback) => { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.RotationSpeed)); - } else { - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - var floatValue = Number(data.speed) * stepSize; - this.log.debug('[GET][%s] Characteristic.RotationSpeed: %s', this.homebridgeAccessory.displayName, floatValue); - this.setCachedState(Characteristic.RotationSpeed, floatValue); - callback(null, floatValue); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.RotationSpeed Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - } - }) - .on('set', (floatValue, callback) => { - // Set device state in Tuya Web API - const value = Math.round(floatValue / stepSize) - - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'windSpeedSet', {value: value}).then(() => { - this.log.debug('[SET][%s] Characteristic.RotationSpeed: %s %s', this.homebridgeAccessory.displayName, floatValue, value); - this.setCachedState(Characteristic.RotationSpeed, value); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.RotationSpeed Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - }); + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'windSpeedSet', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.RotationSpeed: %s %s', this.homebridgeAccessory.displayName, floatValue, value); + this.setCachedState(Characteristic.RotationSpeed, value); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.RotationSpeed Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + }); + } } - } - updateState(data) { - // Update device type specific state - this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); + updateState(data) { + // Update device type specific state + this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); - if (data.state !== undefined) { - const isOn = (data.state === 'true'); - this.service - .getCharacteristic(Characteristic.Active) - .updateValue(isOn ? 1 : 0); - this.setCachedState(Characteristic.Active, isOn ? 1 : 0); - } + if (data.state !== undefined) { + const isOn = (data.state === 'true'); + this.service + .getCharacteristic(Characteristic.Active) + .updateValue(isOn ? 1 : 0); + this.setCachedState(Characteristic.Active, isOn ? 1 : 0); + } - if (data.speed_level && data.speed) { - var maxSpeedLevel = data.speed_level; - var stepSize = Math.floor(100/maxSpeedLevel); - var value = Number(data.speed) * stepSize - this.service - .getCharacteristic(Characteristic.RotationSpeed) - .updateValue(value); - this.setCachedState(Characteristic.RotationSpeed, value); + if (data.speed_level && data.speed) { + var maxSpeedLevel = data.speed_level; + var stepSize = Math.floor(100 / maxSpeedLevel); + var value = Number(data.speed) * stepSize + this.service + .getCharacteristic(Characteristic.RotationSpeed) + .updateValue(value); + this.setCachedState(Characteristic.RotationSpeed, value); + } } - } } module.exports = FanAccessory; diff --git a/lib/light_accessory.js b/lib/light_accessory.js index 53bea4a..005853d 100644 --- a/lib/light_accessory.js +++ b/lib/light_accessory.js @@ -1,4 +1,3 @@ -const TuyaWebApi = require('./tuyawebapi'); const BaseAccessory = require('./base_accessory') let PlatformAccessory; @@ -8,262 +7,260 @@ let Characteristic; let UUIDGen; class LightAccessory extends BaseAccessory { - constructor(platform, homebridgeAccessory, deviceConfig) { + constructor(platform, homebridgeAccessory, deviceConfig) { - ({ Accessory, Characteristic, Service } = platform.api.hap); + ({Accessory, Characteristic, Service} = platform.api.hap); - super( - platform, - homebridgeAccessory, - deviceConfig, - Accessory.Categories.LIGHTBULB - ); + super( + platform, + homebridgeAccessory, + deviceConfig, + Accessory.Categories.LIGHTBULB + ); - // homekit compatible defaults - const defaultBrightness = 100; // 100% - const defaultSaturation = 100; // 100% - const defaultHue = 359; // red (max hue) + // homekit compatible defaults + const defaultBrightness = 100; // 100% + const defaultSaturation = 100; // 100% + const defaultHue = 359; // red (max hue) - // Characteristic.On - this.service.getCharacteristic(Characteristic.On) - .on('get', (callback) => { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.On)); - } else { - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); - this.setCachedState(Characteristic.On, data.state); - callback(null, data.state); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - } - }) - .on('set', (isOn, callback) => { - // Set device state in Tuya Web API - const value = isOn ? 1 : 0; + // Characteristic.On + this.service.getCharacteristic(Characteristic.On) + .on('get', (callback) => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.On)); + } else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); + this.setCachedState(Characteristic.On, data.state); + callback(null, data.state); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } + }) + .on('set', (isOn, callback) => { + // Set device state in Tuya Web API + const value = isOn ? 1 : 0; - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', { value: value }).then(() => { - this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, isOn, value); - this.setCachedState(Characteristic.On, isOn); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - }); + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, isOn, value); + this.setCachedState(Characteristic.On, isOn); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + }); - // Characteristic.Brightness - this.service.getCharacteristic(Characteristic.Brightness) - .on('get', callback => { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.Brightness)); - } else { - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - // data.brightness only valid for color_mode!=color > https://github.com/PaulAnnekov/tuyaha/blob/master/tuyaha/devices/light.py - // however, according to local tuya app, calculation for color_mode=color is stil incorrect (even more so in lower range) + // Characteristic.Brightness + this.service.getCharacteristic(Characteristic.Brightness) + .on('get', callback => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.Brightness)); + } else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + // data.brightness only valid for color_mode!=color > https://github.com/PaulAnnekov/tuyaha/blob/master/tuyaha/devices/light.py + // however, according to local tuya app, calculation for color_mode=color is stil incorrect (even more so in lower range) - let rawValue; - let percentage; + let rawValue; + let percentage; - if (data.color_mode == 'color' || data.color_mode == 'colour') { - rawValue = data.color.brightness; // 0-100 - percentage = rawValue; - } else { - rawValue = data.brightness; // 0-255 - percentage = Math.floor(rawValue / 255) * 100; // 0-100 - } + if (data.color_mode == 'color' || data.color_mode == 'colour') { + rawValue = data.color.brightness; // 0-100 + percentage = rawValue; + } else { + rawValue = data.brightness; // 0-255 + percentage = Math.floor(rawValue / 255) * 100; // 0-100 + } - this.log.debug('[GET][%s] Characteristic.Brightness: %s (%s percent)', this.homebridgeAccessory.displayName, rawValue, percentage); - this.setCachedState(Characteristic.Brightness, percentage); - callback(null, percentage); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - } - }) - .on('set', (percentage, callback) => { - // NOTE: For some strange reason, the set value for brightness is in percentage - const value = percentage; // 0-100 + this.log.debug('[GET][%s] Characteristic.Brightness: %s (%s percent)', this.homebridgeAccessory.displayName, rawValue, percentage); + this.setCachedState(Characteristic.Brightness, percentage); + callback(null, percentage); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } + }) + .on('set', (percentage, callback) => { + // NOTE: For some strange reason, the set value for brightness is in percentage + const value = percentage; // 0-100 - // Set device state in Tuya Web API - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'brightnessSet', { value: value }).then(() => { - this.log.debug('[SET][%s] Characteristic.Brightness: %s percent', this.homebridgeAccessory.displayName, percentage); - this.setCachedState(Characteristic.Brightness, percentage); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - }); + // Set device state in Tuya Web API + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'brightnessSet', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.Brightness: %s percent', this.homebridgeAccessory.displayName, percentage); + this.setCachedState(Characteristic.Brightness, percentage); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.Brightness Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + }); - // Characteristic.Saturation - this.service.getCharacteristic(Characteristic.Saturation) - .on('get', callback => { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.Saturation)); - } else { - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - if (data.color) { - const saturation = data.color.saturation; // 0-100 - const hue = data.color.hue; // 0-359 - this.log.debug('[GET][%s] Characteristic.Saturation: %s', this.homebridgeAccessory.displayName, saturation); - this.setCachedState(Characteristic.Saturation, saturation); - this.setCachedState(Characteristic.Hue, hue); - callback(null, saturation); - } - else { - callback(null, null); - } - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.Saturation Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - } - }) - .on('set', (percentage, callback) => { - let color = {}; + // Characteristic.Saturation + this.service.getCharacteristic(Characteristic.Saturation) + .on('get', callback => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.Saturation)); + } else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + if (data.color) { + const saturation = data.color.saturation; // 0-100 + const hue = data.color.hue; // 0-359 + this.log.debug('[GET][%s] Characteristic.Saturation: %s', this.homebridgeAccessory.displayName, saturation); + this.setCachedState(Characteristic.Saturation, saturation); + this.setCachedState(Characteristic.Hue, hue); + callback(null, saturation); + } else { + callback(null, null); + } + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.Saturation Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } + }) + .on('set', (percentage, callback) => { + let color = {}; - const cachedBrightness = this.getCachedState(Characteristic.Brightness); - const cachedHue = this.getCachedState(Characteristic.Hue); + const cachedBrightness = this.getCachedState(Characteristic.Brightness); + const cachedHue = this.getCachedState(Characteristic.Hue); - color.brightness = cachedBrightness ? cachedBrightness : defaultBrightness; // 0-100 - color.saturation = Math.floor(percentage / 100) * 255; // 0-255 - color.hue = cachedHue ? cachedHue : defaultHue; // 0-359 + color.brightness = cachedBrightness ? cachedBrightness : defaultBrightness; // 0-100 + color.saturation = Math.floor(percentage / 100) * 255; // 0-255 + color.hue = cachedHue ? cachedHue : defaultHue; // 0-359 - // Set device state in Tuya Web API - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'colorSet', { color: color }).then(() => { - this.log.debug('[SET][%s] Characteristic.Saturation: (%s) %s percent', this.homebridgeAccessory.displayName, color.saturation, percentage); - this.setCachedState(Characteristic.Brightness, color.brightness); - this.setCachedState(Characteristic.Saturation, percentage); - this.setCachedState(Characteristic.Hue, color.hue); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.Saturation Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - }); + // Set device state in Tuya Web API + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'colorSet', {color: color}).then(() => { + this.log.debug('[SET][%s] Characteristic.Saturation: (%s) %s percent', this.homebridgeAccessory.displayName, color.saturation, percentage); + this.setCachedState(Characteristic.Brightness, color.brightness); + this.setCachedState(Characteristic.Saturation, percentage); + this.setCachedState(Characteristic.Hue, color.hue); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.Saturation Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + }); - // Characteristic.Hue - this.service.getCharacteristic(Characteristic.Hue) - .on('get', callback => { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.Hue)); - } else { - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - if (data.color) { - const saturation = data.color.saturation; // 0-100 - const hue = data.color.hue; // 0-359 - this.log.debug('[GET][%s] Characteristic.Hue: %s', this.homebridgeAccessory.displayName, hue); - this.setCachedState(Characteristic.Saturation, saturation); - this.setCachedState(Characteristic.Hue, hue); - callback(null, hue); - } - else { - callback(null, null); - } - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.Hue Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - } - }) - .on('set', (hue, callback) => { - let color = {}; + // Characteristic.Hue + this.service.getCharacteristic(Characteristic.Hue) + .on('get', callback => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.Hue)); + } else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + if (data.color) { + const saturation = data.color.saturation; // 0-100 + const hue = data.color.hue; // 0-359 + this.log.debug('[GET][%s] Characteristic.Hue: %s', this.homebridgeAccessory.displayName, hue); + this.setCachedState(Characteristic.Saturation, saturation); + this.setCachedState(Characteristic.Hue, hue); + callback(null, hue); + } else { + callback(null, null); + } + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.Hue Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } + }) + .on('set', (hue, callback) => { + let color = {}; - const cachedBrightness = this.getCachedState(Characteristic.Brightness); - const cachedSaturation = this.getCachedState(Characteristic.Saturation); + const cachedBrightness = this.getCachedState(Characteristic.Brightness); + const cachedSaturation = this.getCachedState(Characteristic.Saturation); - color.brightness = cachedBrightness ? cachedBrightness : defaultBrightness; // 0-100 - color.saturation = cachedSaturation ? Math.floor(cachedSaturation / 100) * 255 : defaultSaturation; // 0-255 - const newSaturationPercentage = Math.floor(color.saturation / 255) * 100; - color.hue = hue; + color.brightness = cachedBrightness ? cachedBrightness : defaultBrightness; // 0-100 + color.saturation = cachedSaturation ? Math.floor(cachedSaturation / 100) * 255 : defaultSaturation; // 0-255 + const newSaturationPercentage = Math.floor(color.saturation / 255) * 100; + color.hue = hue; - // Set device state in Tuya Web API - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'colorSet', { color: color }).then(() => { - this.log.debug('[SET][%s] Characteristic.Hue: %s', this.homebridgeAccessory.displayName, hue); - this.setCachedState(Characteristic.Brightness, color.brightness); - this.setCachedState(Characteristic.Saturation, newSaturationPercentage); - this.setCachedState(Characteristic.Hue, color.hue); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.Hue Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - }); - } + // Set device state in Tuya Web API + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'colorSet', {color: color}).then(() => { + this.log.debug('[SET][%s] Characteristic.Hue: %s', this.homebridgeAccessory.displayName, hue); + this.setCachedState(Characteristic.Brightness, color.brightness); + this.setCachedState(Characteristic.Saturation, newSaturationPercentage); + this.setCachedState(Characteristic.Hue, color.hue); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.Hue Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + }); + } - updateState(data) { - // Update device type specific state - this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); + updateState(data) { + // Update device type specific state + this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); - if (data.state) { - const isOn = (data.state === 'true'); - this.service - .getCharacteristic(Characteristic.On) - .updateValue(isOn); - this.setCachedState(Characteristic.On, isOn); - } - if (data.brightness || data.color.brightness) { - let rawValue; - let percentage; + if (data.state) { + const isOn = (data.state === 'true'); + this.service + .getCharacteristic(Characteristic.On) + .updateValue(isOn); + this.setCachedState(Characteristic.On, isOn); + } + if (data.brightness || data.color.brightness) { + let rawValue; + let percentage; - if (data.color_mode == 'color' || data.color_mode == 'colour') { - rawValue = data.color.brightness; // 0-100 - percentage = rawValue; - } else { - rawValue = data.brightness; // 0-255 - percentage = Math.floor(rawValue / 255) * 100; // 0-100 - } + if (data.color_mode == 'color' || data.color_mode == 'colour') { + rawValue = data.color.brightness; // 0-100 + percentage = rawValue; + } else { + rawValue = data.brightness; // 0-255 + percentage = Math.floor(rawValue / 255) * 100; // 0-100 + } - this.service - .getCharacteristic(Characteristic.Brightness) - .updateValue(percentage); - this.setCachedState(Characteristic.Brightness, percentage); - } - if (data.color && data.color.saturation) { - let rawValue; - let percentage; + this.service + .getCharacteristic(Characteristic.Brightness) + .updateValue(percentage); + this.setCachedState(Characteristic.Brightness, percentage); + } + if (data.color && data.color.saturation) { + let rawValue; + let percentage; - if (data.color_mode == 'color' || data.color_mode == 'colour') { - rawValue = data.color.brightness; // 0-100 - percentage = rawValue; - } else { - rawValue = data.brightness; // 0-255 - percentage = Math.floor(rawValue / 255) * 100; // 0-100 - } + if (data.color_mode == 'color' || data.color_mode == 'colour') { + rawValue = data.color.brightness; // 0-100 + percentage = rawValue; + } else { + rawValue = data.brightness; // 0-255 + percentage = Math.floor(rawValue / 255) * 100; // 0-100 + } - this.service - .getCharacteristic(Characteristic.Saturation) - .updateValue(percentage); - this.setCachedState(Characteristic.Saturation, percentage); - } - if (data.color && data.color.hue) { - const hue = data.color.hue; - this.service - .getCharacteristic(Characteristic.Hue) - .updateValue(hue); - this.setCachedState(Characteristic.Hue, hue); + this.service + .getCharacteristic(Characteristic.Saturation) + .updateValue(percentage); + this.setCachedState(Characteristic.Saturation, percentage); + } + if (data.color && data.color.hue) { + const hue = data.color.hue; + this.service + .getCharacteristic(Characteristic.Hue) + .updateValue(hue); + this.setCachedState(Characteristic.Hue, hue); + } } - } } module.exports = LightAccessory; diff --git a/lib/outlet_accessory.js b/lib/outlet_accessory.js index 04dabf1..8f64c90 100644 --- a/lib/outlet_accessory.js +++ b/lib/outlet_accessory.js @@ -1,71 +1,66 @@ -const TuyaWebApi = require('./tuyawebapi'); const BaseAccessory = require('./base_accessory') -let PlatformAccessory; let Accessory; let Service; let Characteristic; -let UUIDGen; class OutletAccessory extends BaseAccessory { - constructor(platform, homebridgeAccessory, deviceConfig) { + constructor(platform, homebridgeAccessory, deviceConfig) { + ({Accessory, Characteristic, Service} = platform.api.hap); - ({ Accessory, Characteristic, Service } = platform.api.hap); + super( + platform, + homebridgeAccessory, + deviceConfig, + Accessory.Categories.OUTLET + ) - super( - platform, - homebridgeAccessory, - deviceConfig, - Accessory.Categories.OUTLET - ) + this.service.getCharacteristic(Characteristic.On) + .on('get', (callback) => { - this.service.getCharacteristic(Characteristic.On) - .on('get', (callback) => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.On)); + } else { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.On)); - } - else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.homebridgeAccessory.context.deviceId).then((data) => { + this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); + this.getCachedState(Characteristic.On, data.state); + callback(null, data.state); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.homebridgeAccessory.context.deviceId).then((data) => { - this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); - this.getCachedState(Characteristic.On, data.state); - callback(null, data.state); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); + } + }) + .on('set', (state, callback) => { - } - }) - .on('set', (state, callback) => { + // Set device state in Tuya Web API + const value = state ? 1 : 0; + this.platform.tuyaWebApi.setDeviceState(this.homebridgeAccessory.context.deviceId, 'turnOnOff', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, state, value); + this.setCachedState(Characteristic.On, state); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); - // Set device state in Tuya Web API - const value = state ? 1 : 0; - this.platform.tuyaWebApi.setDeviceState(this.homebridgeAccessory.context.deviceId, 'turnOnOff', { value: value }).then(() => { - this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, state, value); - this.setCachedState(Characteristic.On, state); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); + }); + } - }); - } - - updateState(data) { - this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); - const state = (data.state === true); - this.service - .getCharacteristic(Characteristic.On) - .updateValue(data.state); - this.setCachedState(Characteristic.On, state); - } + updateState(data) { + this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); + const state = (data.state === true); + this.service + .getCharacteristic(Characteristic.On) + .updateValue(data.state); + this.setCachedState(Characteristic.On, state); + } } -module.exports = OutletAccessory; \ No newline at end of file +module.exports = OutletAccessory; diff --git a/lib/switch_accessory.js b/lib/switch_accessory.js index 14f4e6c..5e3a0dd 100644 --- a/lib/switch_accessory.js +++ b/lib/switch_accessory.js @@ -1,71 +1,66 @@ -const TuyaWebApi = require('./tuyawebapi'); const BaseAccessory = require('./base_accessory') -let PlatformAccessory; let Accessory; let Service; let Characteristic; -let UUIDGen; class SwitchAccessory extends BaseAccessory { - constructor(platform, homebridgeAccessory, deviceConfig) { + constructor(platform, homebridgeAccessory, deviceConfig) { + ({Accessory, Characteristic, Service} = platform.api.hap); - ({ Accessory, Characteristic, Service } = platform.api.hap); + super( + platform, + homebridgeAccessory, + deviceConfig, + Accessory.Categories.SWITCH + ) - super( - platform, - homebridgeAccessory, - deviceConfig, - Accessory.Categories.SWITCH - ) + this.service.getCharacteristic(Characteristic.On) + .on('get', (callback) => { - this.service.getCharacteristic(Characteristic.On) - .on('get', (callback) => { + // Retrieve state from cache + if (this.hasValidCache()) { + callback(null, this.getCachedState(Characteristic.On)); + } else { - // Retrieve state from cache - if (this.hasValidCache()) { - callback(null, this.getCachedState(Characteristic.On)); - } - else { + // Retrieve device state from Tuya Web API + this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { + this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); + this.getCachedState(Characteristic.On, data.state); + callback(null, data.state); + }).catch((error) => { + this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + } - // Retrieve device state from Tuya Web API - this.platform.tuyaWebApi.getDeviceState(this.deviceId).then((data) => { - this.log.debug('[GET][%s] Characteristic.On: %s', this.homebridgeAccessory.displayName, data.state); - this.getCachedState(Characteristic.On, data.state); - callback(null, data.state); - }).catch((error) => { - this.log.error('[GET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); - } + }) + .on('set', (state, callback) => { - }) - .on('set', (state, callback) => { + // Set device state in Tuya Web API + const value = state ? 1 : 0; + this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', {value: value}).then(() => { + this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, state, value); + this.setCachedState(Characteristic.On, state); + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); - // Set device state in Tuya Web API - const value = state ? 1 : 0; - this.platform.tuyaWebApi.setDeviceState(this.deviceId, 'turnOnOff', { value: value }).then(() => { - this.log.debug('[SET][%s] Characteristic.On: %s %s', this.homebridgeAccessory.displayName, state, value); - this.setCachedState(Characteristic.On, state); - callback(); - }).catch((error) => { - this.log.error('[SET][%s] Characteristic.On Error: %s', this.homebridgeAccessory.displayName, error); - this.invalidateCache(); - callback(error); - }); + }); + } - }); - } - - updateState(data) { - this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); - const state = (data.state === true); - this.service - .getCharacteristic(Characteristic.On) - .updateValue(data.state); - this.setCachedState(Characteristic.On, state); - } + updateState(data) { + this.log.debug('[UPDATING][%s]:', this.homebridgeAccessory.displayName, data); + const state = (data.state === true); + this.service + .getCharacteristic(Characteristic.On) + .updateValue(data.state); + this.setCachedState(Characteristic.On, state); + } } -module.exports = SwitchAccessory; \ No newline at end of file +module.exports = SwitchAccessory; diff --git a/lib/tuyawebapi.js b/lib/tuyawebapi.js index 62e3c4d..679abc9 100644 --- a/lib/tuyawebapi.js +++ b/lib/tuyawebapi.js @@ -1,332 +1,292 @@ -const request = require('request'); +const axios = require('axios').default; const querystring = require('querystring'); class Session { - constructor(accessToken, refreshToken, expiresIn, areaCode, areaBaseUrl) { - this.accessToken; - this.refreshToken; - this.expiresOn; - this.areaCode = areaCode; - this.areaBaseUrl = areaBaseUrl; - this.resetToken(accessToken, refreshToken, expiresIn); - } + constructor(accessToken, refreshToken, expiresIn, areaCode, areaBaseUrl) { + this.accessToken; + this.refreshToken; + this.expiresOn; + this.areaCode = areaCode; + this.areaBaseUrl = areaBaseUrl; + this.resetToken(accessToken, refreshToken, expiresIn); + } - resetToken(accessToken, refreshToken, expiresIn) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - this.expiresOn = this._getCurrentEpoch() + expiresIn - 100; // subtract 100 ticks to expire token before it actually does - } + resetToken(accessToken, refreshToken, expiresIn) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.expiresOn = this._getCurrentEpoch() + expiresIn - 100; // subtract 100 ticks to expire token before it actually does + } - hasToken() { - return this.accessToken && true; - } + hasToken() { + return this.accessToken && true; + } - isTokenExpired() { - return this.token.expiresOn > this._getCurrentEpoch(); - } + isTokenExpired() { + return this.token.expiresOn > this._getCurrentEpoch(); + } - hasValidToken() { - return this.accessToken && this.expiresOn > this._getCurrentEpoch(); - } + hasValidToken() { + return this.accessToken && this.expiresOn > this._getCurrentEpoch(); + } - _getCurrentEpoch() { - return Math.round((new Date()).getTime() / 1000); - } + _getCurrentEpoch() { + return Math.round((new Date()).getTime() / 1000); + } } class TuyaWebApi { - constructor(username, password, countryCode, tuyaPlatform = 'tuya', log = null) { - this.username = username; - this.password = password; - this.countryCode = countryCode; - this.tuyaPlatform = tuyaPlatform; + constructor(username, password, countryCode, tuyaPlatform = 'tuya', log = null) { + this.username = username; + this.password = password; + this.countryCode = countryCode; + this.tuyaPlatform = tuyaPlatform; - this.session = new Session(); + this.session = new Session(); - this.authBaseUrl = 'https://px1.tuyaeu.com'; + this.authBaseUrl = 'https://px1.tuyaeu.com'; - this.log = log; - } - - discoverDevices() { - if (!this.session.hasValidToken()) { - throw new Error('No valid token'); + this.log = log; } - var data = { - 'header': { - 'name': 'Discovery', - 'namespace': 'discovery', - 'payloadVersion': 1 - }, - 'payload': { - 'accessToken': this.session.accessToken - } - } + discoverDevices() { + if (!this.session.hasValidToken()) { + throw new Error('No valid token'); + } - return new Promise((resolve, reject) => { - this.sendRequestJson( - this.session.areaBaseUrl + '/homeassistant/skill', - JSON.stringify(data), - 'GET', - (response, obj) => { - if (obj.header && obj.header.code === 'SUCCESS') { - if (obj.payload && obj.payload.devices) { - resolve(obj.payload.devices); + var data = { + 'header': { + 'name': 'Discovery', + 'namespace': 'discovery', + 'payloadVersion': 1 + }, + 'payload': { + 'accessToken': this.session.accessToken } - } - else if (obj.header && obj.header.code === 'FrequentlyInvoke') { - this.log.debug('Requesting too quickly.'); - } else { - reject(new Error('No valid response from API', obj)); - } - }, - (error) => { - reject(error); } - ) - }); - } - getAllDeviceStates() { - return this.discoverDevices(); - } - - getDeviceState(deviceId) { - if (!this.session.hasValidToken()) { - throw new Error('No valid token'); + return new Promise((resolve, reject) => { + this.sendRequestJson( + this.session.areaBaseUrl + '/homeassistant/skill', + JSON.stringify(data), + 'GET', + (response, obj) => { + if (obj.header && obj.header.code === 'SUCCESS') { + if (obj.payload && obj.payload.devices) { + resolve(obj.payload.devices); + } + } else if (obj.header && obj.header.code === 'FrequentlyInvoke') { + this.log.debug('Requesting too quickly.'); + } else { + reject(new Error('No valid response from API', obj)); + } + }, + (error) => { + reject(error); + } + ) + }); } - var data = { - 'header': { - 'name': 'QueryDevice', - 'namespace': 'query', - 'payloadVersion': 1 - }, - 'payload': { - 'accessToken': this.session.accessToken, - 'devId': deviceId, - 'value': 1 - } + getAllDeviceStates() { + return this.discoverDevices(); } - return new Promise((resolve, reject) => { - this.sendRequestJson( - this.session.areaBaseUrl + '/homeassistant/skill', - JSON.stringify(data), - 'GET', - (response, obj) => { - if (obj.payload && obj.header && obj.header.code === 'SUCCESS') { - resolve(obj.payload.data); - } - else if (obj.header && obj.header.code === 'FrequentlyInvoke') { - this.log.debug('Requesting too quickly.'); - } - else { - reject(new Error('Invalid payload in response: ', obj)) - } - }, - (error) => { - reject(error); + getDeviceState(deviceId) { + if (!this.session.hasValidToken()) { + throw new Error('No valid token'); } - ) - }); - } - setDeviceState(deviceId, method, payload = {}) { - if (!this.session.hasValidToken()) { - throw new Error('No valid token'); - } - - /* Methods - * turnOnOff -> 0 = off, 1 = on - * brightnessSet --> 0..100 - */ + var data = { + 'header': { + 'name': 'QueryDevice', + 'namespace': 'query', + 'payloadVersion': 1 + }, + 'payload': { + 'accessToken': this.session.accessToken, + 'devId': deviceId, + 'value': 1 + } + } - var data = { - 'header': { - 'name': method, - 'namespace': 'control', - 'payloadVersion': 1 - }, - 'payload': payload + return new Promise((resolve, reject) => { + this.sendRequestJson( + this.session.areaBaseUrl + '/homeassistant/skill', + JSON.stringify(data), + 'GET', + (response, obj) => { + if (obj.payload && obj.header && obj.header.code === 'SUCCESS') { + resolve(obj.payload.data); + } else if (obj.header && obj.header.code === 'FrequentlyInvoke') { + this.log.debug('Requesting too quickly.'); + } else { + reject(new Error('Invalid payload in response: ', obj)) + } + }, + (error) => { + reject(error); + } + ) + }); } - data.payload.accessToken = this.session.accessToken; - data.payload.devId = deviceId; - - return new Promise((resolve, reject) => { - this.sendRequestJson( - this.session.areaBaseUrl + '/homeassistant/skill', - JSON.stringify(data), - 'POST', - (response, obj) => { - if (obj.header && obj.header.code === 'SUCCESS') { - resolve(); - } - else if (obj.header && obj.header.code === 'FrequentlyInvoke') { - this.log.debug('Requesting too quickly.'); - } - else { - reject(new Error('Invalid payload in response: ', obj)) - } - }, - (error) => { - reject(error); + setDeviceState(deviceId, method, payload = {}) { + if (!this.session.hasValidToken()) { + throw new Error('No valid token'); } - ) - }); - } - getOrRefreshToken() { - if (!this.session.hasToken()) { - // No token, lets get a token from the Tuya Web API - if (!this.username) { - throw new Error('No username configured'); - } - else { - if (!this.password) { - throw new Error('No password configured'); - } - else { - if (!this.countryCode) { - throw new Error('No country code configured'); - } - else { + /* Methods + * turnOnOff -> 0 = off, 1 = on + * brightnessSet --> 0..100 + */ - var form = { - userName: this.username, - password: this.password, - countryCode: this.countryCode, - bizType: this.tuyaPlatform, - from: "tuya" - } + var data = { + 'header': { + 'name': method, + 'namespace': 'control', + 'payloadVersion': 1 + }, + 'payload': payload + } - var formData = querystring.stringify(form); - var contentLength = formData.length; + data.payload.accessToken = this.session.accessToken; + data.payload.devId = deviceId; - return new Promise((resolve, reject) => { - request({ - headers: { - 'Content-Length': contentLength, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - uri: this.authBaseUrl + '/homeassistant/auth.do', - body: formData, - method: 'POST' - }, - (err, res, body) => { - if (err) { - reject(new Error('Authentication fault, could not retreive token.', err)); - } - else { - let obj; - try { - obj = JSON.parse(body); - } - catch (error) { - reject(new Error(`Could not parse json. Body: ${body}`, error)); - } - if (obj.responseStatus === 'error') { - reject(new Error('Authentication fault: ' + obj.errorMsg)); - } - else { - // Received token - this.session.resetToken( - obj.access_token, - obj.refresh_token, - obj.expires_in - ); - // Change url based on areacode in accesstoken first two chars - this.session.areaCode = obj.access_token.substr(0, 2); - switch (this.session.areaCode) { - case 'AY': - this.session.areaBaseUrl = 'https://px1.tuyacn.com'; - break; - case 'EU': - this.session.areaBaseUrl = 'https://px1.tuyaeu.com'; - break; - case 'US': - default: - this.session.areaBaseUrl = 'https://px1.tuyaus.com'; - } - resolve(this.session); - } - } - }); - }); - } - } - } - } - else { - if (this.session.hasToken() && this.session.isTokenExpired()) { - // Refresh token return new Promise((resolve, reject) => { - this.sendRequestJson( - this.session.areaBaseUrl + '/homeassistant/access.do?grant_type=refresh_token&refresh_token=' + this.session.refreshToken, - '', - 'GET', - (response, obj) => { - // Received token - this.session.resetToken( - obj.access_token, - obj.refresh_token, - obj.expires_in - ); - resolve(this.session); - }, - (error) => { - reject(error); - } - ) + this.sendRequestJson( + this.session.areaBaseUrl + '/homeassistant/skill', + JSON.stringify(data), + 'POST', + (response, obj) => { + if (obj.header && obj.header.code === 'SUCCESS') { + resolve(); + } else if (obj.header && obj.header.code === 'FrequentlyInvoke') { + this.log.debug('Requesting too quickly.'); + } else { + reject(new Error('Invalid payload in response: ', obj)) + } + }, + (error) => { + reject(error); + } + ) }); - } } - } - /* - * -------------------------------------- - * HTTP methods - */ + getOrRefreshToken() { + if (!this.session.hasToken()) { + // No token, lets get a token from the Tuya Web API + if (!this.username) { + throw new Error('No username configured'); + } else { + if (!this.password) { + throw new Error('No password configured'); + } else { + if (!this.countryCode) { + throw new Error('No country code configured'); + } else { - sendRequest(url, body, method, callbackSuccess, callbackError) { - request({ - url: url, - body: body, - method: method, - rejectUnauthorized: false, - }, - (error, response, body) => { - if (error) { - callbackError(error); - } - else { - callbackSuccess(response, body) - } - }); - } + var form = { + userName: this.username, + password: this.password, + countryCode: this.countryCode, + bizType: this.tuyaPlatform, + from: "tuya" + } - sendRequestJson(url, body, method, callbackSuccess, callbackError) { - // this.log.debug(JSON.stringify(body)); - - this.sendRequest(url, body, method, - (response, body) => { - // this.log.debug(JSON.stringify(body)); + var formData = querystring.stringify(form); + var contentLength = formData.length; - try { - const obj = JSON.parse(body); - callbackSuccess(response, obj); - } - catch (error) { - callbackError(new Error(`Could not parse json. Body: ${body}`, error)); + return new Promise((resolve, reject) => { + axios({ + headers: { + 'Content-Length': contentLength, + 'Content-Type': 'application/x-www-form-urlencoded' + }, + url: '/homeassistant/auth.do', + baseURL: this.authBaseUrl, + data: formData, + method: 'POST' + }).then((res) => { + if (res.data.responseStatus === 'error') { + reject(new Error('Authentication fault: ' + res.data.errorMsg)); + } else { + // Received token + this.session.resetToken( + res.data.access_token, + res.data.refresh_token, + res.data.expires_in + ); + // Change url based on areacode in accesstoken first two chars + this.session.areaCode = res.data.access_token.substr(0, 2); + switch (this.session.areaCode) { + case 'AY': + this.session.areaBaseUrl = 'https://px1.tuyacn.com'; + break; + case 'EU': + this.session.areaBaseUrl = 'https://px1.tuyaeu.com'; + break; + case 'US': + default: + this.session.areaBaseUrl = 'https://px1.tuyaus.com'; + } + resolve(this.session); + } + }).catch((err) => { + this.log.debug("Authentication error - %s", JSON.stringify(err)) + reject(new Error('Authentication fault, could not retreive token.', err)); + }); + }); + } + } + } + } else { + if (this.session.hasToken() && this.session.isTokenExpired()) { + // Refresh token + return new Promise((resolve, reject) => { + this.sendRequestJson( + this.session.areaBaseUrl + '/homeassistant/access.do?grant_type=refresh_token&refresh_token=' + this.session.refreshToken, + '', + 'GET', + (response, obj) => { + // Received token + this.session.resetToken( + obj.access_token, + obj.refresh_token, + obj.expires_in + ); + resolve(this.session); + }, + (error) => { + reject(error); + } + ) + }); + } } - }, - (error) => { - callbackError(error); - } - ); - } + } + + /* + * -------------------------------------- + * HTTP methods + */ + + sendRequest(url, data, method, callbackSuccess, callbackError) { + axios({ + url, + data, + method + }).then((response) => { + callbackSuccess(response, response.data) + }).catch((error) => { + this.log.error(error); + callbackError(error) + }); + } + + sendRequestJson(url, body, method, callbackSuccess, callbackError) { + this.sendRequest(url, body, method, callbackSuccess, callbackError) + } } module.exports = TuyaWebApi; diff --git a/package-lock.json b/package-lock.json index 562e084..11f001d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,17 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -45,39 +34,19 @@ "sprintf-js": "~1.0.2" } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } }, "balanced-match": { "version": "1.0.0", @@ -85,14 +54,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -115,11 +76,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -194,25 +150,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -226,14 +169,6 @@ "which": "^1.2.9" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -267,26 +202,12 @@ "object-keys": "^1.0.12" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -354,26 +275,6 @@ "strip-eof": "^1.0.0" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -392,19 +293,27 @@ "is-buffer": "~2.0.3" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } }, "fs.realpath": { @@ -440,14 +349,6 @@ "pump": "^3.0.0" } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -468,20 +369,6 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -509,16 +396,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -589,22 +466,12 @@ "has-symbols": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, "js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -615,37 +482,6 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -700,19 +536,6 @@ "p-is-promise": "^2.0.0" } }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -811,11 +634,6 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -930,16 +748,6 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -950,43 +758,6 @@ "once": "^1.3.1" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -999,16 +770,6 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -1048,22 +809,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1104,57 +849,12 @@ "has-flag": "^3.0.0" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 5571bd4..4672af4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "https://github.com/milo526/homebridge-tuya-web" }, "dependencies": { - "request": "^2.88.2" + "axios": "^0.19.2" }, "devDependencies": { "chai": "^4.2.0",