From 60b50c760e70bb7693c2e5c0d9dbef63af536ce6 Mon Sep 17 00:00:00 2001 From: tsightler Date: Mon, 12 Oct 2020 16:14:22 -0400 Subject: [PATCH] 3.0.0-beta4 * Default to generic device * Improved debugging granularity/increased categories * Add heartbeat monitoring for availability * Catch more failure cases with retry (still some missing I'd guess) * Switch to MathJS evaluate for simple math transforms * RGBTW: Switch base scale for all friendly topics to 100 (automatic conversion on backend) * RGBTW: Add color temperature support * RGBTW: Improve autodetection * RGBTW: Improved white/color mode handling (still work to do here) --- devices/generic-device.js | 2 +- devices/rgbtw-light.js | 82 ++++++++++++------- devices/simple-dimmer.js | 7 +- devices/simple-switch.js | 7 +- devices/tuya-device.js | 163 ++++++++++++++++++++++++++------------ lib/utils.js | 4 + package-lock.json | 68 ++++++++++++++-- package.json | 5 +- tuya-mqtt.js | 22 ++--- 9 files changed, 248 insertions(+), 112 deletions(-) diff --git a/devices/generic-device.js b/devices/generic-device.js index b51722a..a4f974e 100644 --- a/devices/generic-device.js +++ b/devices/generic-device.js @@ -1,5 +1,5 @@ const TuyaDevice = require('./tuya-device') -const debug = require('debug')('tuya-mqtt:tuya') +const debug = require('debug')('tuya-mqtt:device') const utils = require('../lib/utils') class GenericDevice extends TuyaDevice { diff --git a/devices/rgbtw-light.js b/devices/rgbtw-light.js index a863454..4cae050 100644 --- a/devices/rgbtw-light.js +++ b/devices/rgbtw-light.js @@ -1,5 +1,6 @@ const TuyaDevice = require('./tuya-device') -const debug = require('debug')('tuya-mqtt:tuya') +const debug = require('debug')('tuya-mqtt:device-detect') +const debugDiscovery = require('debug')('tuya-mqtt:discovery') const utils = require('../lib/utils') class RGBTWLight extends TuyaDevice { @@ -12,9 +13,11 @@ class RGBTWLight extends TuyaDevice { this.config.dpsWhiteValue = this.config.dpsWhiteValue ? this.config.dpsWhiteValue : this.guess.dpsWhiteValue this.config.whiteValueScale = this.config.whiteValueScale ? this.config.whiteValueScale : this.guess.whiteValueScale this.config.dpsColorTemp = this.config.dpsColorTemp ? this.config.dpsColorTemp : this.guess.dpsColorTemp + this.config.minColorTemp = this.config.minColorTemp ? this.config.minColorTemp : 165 + this.config.maxColorTemp = this.config.maxColorTemp ? this.config.maxColorTemp : 375 + this.config.colorTempScale = this.config.colorTempScale ? this.config.colorTempScale : this.guess.colorTempScale this.config.dpsColor = this.config.dpsColor ? this.config.dpsColor : this.guess.dpsColor this.config.colorType = this.config.colorType ? this.config.colorType : this.guess.colorType - this.config.colorType = 'hsb' this.deviceData.mdl = 'RGBTW Light' @@ -32,8 +35,8 @@ class RGBTWLight extends TuyaDevice { min: 1, max: 100, scale: this.config.whiteValueScale, - stateMath: (this.config.whiteValueScale == 1000) ? '/10' : '/2.55', - commandMath: (this.config.whiteValueScale == 1000) ? '*10' : '*2.55' + stateMath: '/('+this.config.whiteValueScale+'/100)', + commandMath: '*('+this.config.whiteValueScale+'/100)' }, hs_state: { key: this.config.dpsColor, @@ -56,6 +59,23 @@ class RGBTWLight extends TuyaDevice { } } + // If device supports Color Temperature add color temp device topic + if (this.config.dpsColorTemp) { + // Values used for tranform + const rangeFactor = (this.config.maxColorTemp-this.config.minColorTemp)/100 + const scaleFactor = this.config.colorTempScale/100 + const tuyaMaxColorTemp = this.config.maxColorTemp/rangeFactor*scaleFactor + + this.deviceTopics.color_temp_state = { + key: this.config.dpsColorTemp, + type: 'int', + min: this.config.minColorTemp, + max: this.config.maxColorTemp, + stateMath: '/'+scaleFactor+'*-'+rangeFactor+'+'+this.config.maxColorTemp, + commandMath: '/'+rangeFactor+'*-'+scaleFactor+'+'+tuyaMaxColorTemp + } + } + // Send home assistant discovery data and give it a second before sending state updates this.initDiscovery() await utils.sleep(1) @@ -83,44 +103,46 @@ class RGBTWLight extends TuyaDevice { device: this.deviceData } - debug('Home Assistant config topic: '+configTopic) - debug(discoveryData) + if (this.config.dpsColorTemp) { + discoveryData.color_temp_state_topic = this.baseTopic+'color_temp_state' + discoveryData.color_temp_command_topic = this.baseTopic+'color_temp_command' + discoveryData.min_mireds = this.config.minColorTemp + discoveryData.max_mireds = this.config.maxColorTemp + } + + debugDiscovery('Home Assistant config topic: '+configTopic) + debugDiscovery(discoveryData) this.publishMqtt(configTopic, JSON.stringify(discoveryData)) } async guessLightInfo() { this.guess = new Object() + debug('Attempting to detect light capabilites and DPS values...') + debug('Querying DPS 2 for white/color mode setting...') let mode = await this.device.get({"dps": 2}) - if (mode && (mode === 'white' || mode === 'colour')) { - this.guess.dpsPower = 1 - this.guess.dpsMode = 2 - this.guess.dpsWhiteValue = 3 - this.guess.whiteValueScale = 255 - const colorTemp = await this.device.get({"dps": 4}) - if (colorTemp) { - this.guess.dpsColorTemp = 4 - } else { - this.guess.dpsColorTemp = 0 - } - this.guess.dpsColor = 5 - const color = await this.device.get({"dps": this.guess.dpsColor}) - this.guess.colorType = (color && color.length === 14) ? 'hsbhex' : 'hsb' + if (mode && (mode === 'white' || mode === 'colour' || mode.toString().includes('scene'))) { + debug('Detected probably Tuya color bulb at DPS 1-5, checking more details...') + this.guess = {'dpsPower': 1, 'dpsMode': 2, 'dpsWhiteValue': 3, 'whiteValueScale': 255, 'dpsColorTemp': 4, 'colorTempScale': 255, 'dpsColor': 5} } else { - mode = await this.device.get({"dps": 20}) - this.guess.dpsPower = 20 - this.guess.dpsMode = 21 - this.guess.dpsWhiteValue = 22 - this.guess.whiteValueScale = 1000 - const colorTemp = await this.device.get({"dps": 23}) - if (colorTemp) { - this.guess.dpsColorTemp = 23 + debug('Detected likely Tuya color bulb at DPS 20-24, checking more details...') + this.guess = {'dpsPower': 20, 'dpsMode': 21, 'dpsWhiteValue': 22, 'whiteValueScale': 1000, 'dpsColorTemp': 23, 'colorTempScale': 1000, 'dpsColor': 24} + } + if (this.guess.dpsPower) { + debug('Attempting to detect if bulb supports color temperature...') + const colorTemp = await this.device.get({"dps": this.guess.dpsColorTemp}) + if (colorTemp !== '' && colorTemp >= 0 && colorTemp <= this.guess.colorTempScale) { + debug('Detected likely color temerature support') } else { + debug('No color temperature support detected') this.guess.dpsColorTemp = 0 } - this.guess.dpsColor = 24 + debug('Attempting to detect Tuya color format used by device...') const color = await this.device.get({"dps": this.guess.dpsColor}) this.guess.colorType = (color && color.length === 12) ? 'hsb' : 'hsbhex' - } + debug ('Detected Tuya color format '+this.guess.colorType.toUpperCase()) + } else { + debug('No Tuya color bulb detected, if this device is definitely a Tuya bulb please manually specify settings.') + } } } diff --git a/devices/simple-dimmer.js b/devices/simple-dimmer.js index d4d8448..23e2c3e 100644 --- a/devices/simple-dimmer.js +++ b/devices/simple-dimmer.js @@ -1,5 +1,6 @@ const TuyaDevice = require('./tuya-device') -const debug = require('debug')('tuya-mqtt:tuya') +const debug = require('debug')('tuya-mqtt:device') +const debugDiscovery = require('debug')('tuya-mqtt:discovery') const utils = require('../lib/utils') class SimpleDimmer extends TuyaDevice { @@ -47,8 +48,8 @@ class SimpleDimmer extends TuyaDevice { device: this.deviceData } - debug('Home Assistant config topic: '+configTopic) - debug(discoveryData) + debugDiscovery('Home Assistant config topic: '+configTopic) + debugDiscovery(discoveryData) this.publishMqtt(configTopic, JSON.stringify(discoveryData)) } } diff --git a/devices/simple-switch.js b/devices/simple-switch.js index f6c92d4..e0f57c7 100644 --- a/devices/simple-switch.js +++ b/devices/simple-switch.js @@ -1,5 +1,6 @@ const TuyaDevice = require('./tuya-device') -const debug = require('debug')('tuya-mqtt:tuya') +const debug = require('debug')('tuya-mqtt:device') +const debugDiscovery = require('debug')('tuya-mqtt:discovery') const utils = require('../lib/utils') class SimpleSwitch extends TuyaDevice { @@ -36,8 +37,8 @@ class SimpleSwitch extends TuyaDevice { device: this.deviceData } - debug('Home Assistant config topic: '+configTopic) - debug(discoveryData) + debugDiscovery('Home Assistant config topic: '+configTopic) + debugDiscovery(discoveryData) this.publishMqtt(configTopic, JSON.stringify(discoveryData)) } } diff --git a/devices/tuya-device.js b/devices/tuya-device.js index bc7f94d..2db749f 100644 --- a/devices/tuya-device.js +++ b/devices/tuya-device.js @@ -1,7 +1,10 @@ const TuyAPI = require('tuyapi') +const { evaluate } = require('mathjs') const utils = require('../lib/utils') -const debug = require('debug')('tuya-mqtt:tuya') -const debugMqtt = require('debug')('tuya-mqtt:mqtt') +const { msSleep } = require('../lib/utils') +const debug = require('debug')('tuya-mqtt:tuyapi') +const debugState = require('debug')('tuya-mqtt:state') +const debugCommand = require('debug')('tuya-mqtt:command') const debugError = require('debug')('tuya-mqtt:error') class TuyaDevice { @@ -39,15 +42,15 @@ class TuyaDevice { "color": {'h': 0, 's': 0, 'b': 0} } - // Property to hold friendly topics template - this.deviceTopics = {} + this.deviceTopics = {} // Property to hold friendly topics template + this.heartbeatsMissed = 0 // Used to monitor heartbeat status to detect offline device // Build the MQTT topic for this device (friendly name or device id) if (this.options.name) { this.baseTopic = this.topic + this.options.name + '/' } else { this.baseTopic = this.topic + this.options.id + '/' - } + } // Create the new Tuya Device this.device = new TuyAPI(JSON.parse(JSON.stringify(this.options))) @@ -55,7 +58,7 @@ class TuyaDevice { // Listen for device data and call update DPS function if valid this.device.on('data', (data) => { if (typeof data === 'object') { - debug('Received JSON data from device '+this.options.id+' ->', data.dps) + debug('Received JSON data from device '+this.options.id+' ->', JSON.stringify(data.dps)) this.updateState(data) } else { if (data !== 'json obj data unvalid') { @@ -64,36 +67,46 @@ class TuyaDevice { } }) - // Find device on network - debug('Search for device id '+this.options.id) - this.device.find().then(() => { - debug('Found device id '+this.options.id) - // Attempt connection to device - this.device.connect() - }) + // Attempt to find/connect to device and start heartbeat monitor + this.connectDevice() + this.monitorHeartbeat() // On connect perform device specific init - this.device.on('connected', () => { - debug('Connected to device ' + this.toString()) - this.init() + this.device.on('connected', async () => { + // Sometimes TuyAPI reports connection even on socket error + // Wait one second to check if device is really connected before initializing + await utils.sleep(1) + if (this.device.isConnected()) { + debug('Connected to device ' + this.toString()) + this.heartbeatsMissed = 0 + this.publishMqtt(this.baseTopic+'status', 'online') + this.init() + } }) // On disconnect perform device specific disconnect this.device.on('disconnected', () => { this.connected = false + this.publishMqtt(this.baseTopic+'status', 'offline') debug('Disconnected from device ' + this.toString()) }) // On connect error call reconnect - this.device.on('error', (err) => { + this.device.on('error', async (err) => { debugError(err) - if (err.message === 'Error from socket') { + await utils.sleep(1) + if (!this.device.isConnected()) { this.reconnect() } }) + + // On heartbeat reset heartbeat timer + this.device.on('heartbeat', () => { + this.heartbeatsMissed = 0 + }) } - // Update dps properties with device data updates + // Update cached DPS states on data updates updateState(data) { if (typeof data.dps != 'undefined') { // Update cached device state data @@ -148,7 +161,7 @@ class TuyaDevice { } data = JSON.stringify(data) const dpsStateTopic = dpsTopic + '/state' - debugMqtt('MQTT DPS JSON: ' + dpsStateTopic + ' -> ', data) + debugState('MQTT DPS JSON: ' + dpsStateTopic + ' -> ', data) this.publishMqtt(dpsStateTopic, data, false) // Publish dps/<#>/state value for each device DPS @@ -156,7 +169,7 @@ class TuyaDevice { if (this.state.dps[key].updated) { const dpsKeyTopic = dpsTopic + '/' + key + '/state' const data = this.state.dps.hasOwnProperty(key) ? this.state.dps[key].val.toString() : 'None' - debugMqtt('MQTT DPS'+key+': '+dpsKeyTopic+' -> ', data) + debugState('MQTT DPS'+key+': '+dpsKeyTopic+' -> ', data) this.publishMqtt(dpsKeyTopic, data, false) this.state.dps[key].updated = false } @@ -193,7 +206,7 @@ class TuyaDevice { return state } - // Parse the received state value based on deviceTopic config + // Parse the received state numeric value based on deviceTopic rules parseStateNumber(value, deviceTopic) { // Check if it's a number and it's not outside of defined range if (isNaN(value)) { @@ -204,14 +217,14 @@ class TuyaDevice { switch (deviceTopic.type) { case 'int': if (deviceTopic.stateMath) { - value = parseInt(Math.round(eval(value+deviceTopic.stateMath))) + value = parseInt(Math.round(evaluate(value+deviceTopic.stateMath))) } else { value = parseInt(value) } break; case 'float': if (deviceTopic.stateMath) { - value = parseFloat(eval(value+deviceTopic.stateMath)) + value = parseFloat(evaluate(value+deviceTopic.stateMath)) } else { value = parseFloat(value) } @@ -222,12 +235,12 @@ class TuyaDevice { } // Process MQTT commands for all command topics at device level - async processCommand(message, commandTopic) { + processCommand(message, commandTopic) { const command = this.getCommandFromMessage(message) if (commandTopic === 'command' && command === 'get-states') { // Handle "get-states" command to update device state - debug('Received command: ', command) - await this.getStates() + debugCommand('Received command: ', command) + this.getStates() } else { // Call device specific command topic handler this.processDeviceCommand(message, commandTopic) @@ -241,14 +254,14 @@ class TuyaDevice { const deviceTopic = this.deviceTopics.hasOwnProperty(stateTopic) ? this.deviceTopics[stateTopic] : '' if (deviceTopic) { - debug('Device '+this.options.id+' received command topic: '+commandTopic+', message: '+message) + debugCommand('Device '+this.options.id+' received command topic: '+commandTopic+', message: '+message) const command = this.getCommandFromMessage(message) let commandResult = this.sendTuyaCommand(command, deviceTopic) if (!commandResult) { - debug('Command topic '+this.baseTopic+commandTopic+' received invalid value: '+command) + debugCommand('Command topic '+this.baseTopic+commandTopic+' received invalid value: '+command) } } else { - debug('Invalid command topic '+this.baseTopic+commandTopic+' for device: '+this.config.name) + debugCommand('Invalid command topic '+this.baseTopic+commandTopic+' for device: '+this.config.name) return } } @@ -258,7 +271,7 @@ class TuyaDevice { let command if (message != '1' && message != '0' && utils.isJsonString(message)) { - debugMqtt('MQTT message is JSON') + debugCommand('MQTT message is JSON') command = JSON.parse(message); } else { switch(message.toLowerCase()) { @@ -284,20 +297,20 @@ class TuyaDevice { processDpsCommand(message) { if (utils.isJsonString(message)) { const tuyaCommand = this.getCommandFromMessage(message) - debugMqtt('Received command: '+tuyaCommand) + debugCommand('Received command: '+tuyaCommand) this.set(tuyaCommand) } else { - debugError('DPS command topic requires Tuya style JSON value') + debugCommand('DPS command topic requires Tuya style JSON value') } } // Process text base Tuya command via DPS key command topics processDpsKeyCommand(message, dpsKey) { if (utils.isJsonString(message)) { - debugError('Individual DPS command topics do not accept JSON values') + debugCommand('Individual DPS command topics do not accept JSON values') } else { const dpsMessage = this.parseDpsMessage(message) - debugMqtt('Received command for DPS'+dpsKey+': ', message) + debugCommand('Received command for DPS'+dpsKey+': ', message) const tuyaCommand = { dps: dpsKey, set: dpsMessage @@ -325,7 +338,13 @@ class TuyaDevice { this.connected = false for (let topic in this.deviceTopics) { const key = this.deviceTopics[topic].key - const result = await this.device.get({"dps": key}) + try { + const result = await this.device.get({"dps": key}) + this.state.dps[key].val = result + this.state.dps[key].updated = true + } catch { + debugError('Could not get value for device DPS key '+key) + } } this.connected = true // Force topic update now that all states are fully in sync @@ -390,14 +409,14 @@ class TuyaDevice { switch (deviceTopic.type) { case 'int': if (deviceTopic.commandMath) { - value = parseInt(Math.round(eval(command+deviceTopic.commandMath))) + value = parseInt(Math.round(evaluate(command+deviceTopic.commandMath))) } else { value = parseInt(command) } break; case 'float': if (deviceTopic.commandMath) { - value = parseFloat(eval(command+deviceTopic.commandMath)) + value = parseFloat(evaluate(command+deviceTopic.commandMath)) } else { value = parseFloat(command) } @@ -421,7 +440,7 @@ class TuyaDevice { // Convert from Hex to Decimal and cache values this.state.color.h = parseInt(h, 16) this.state.color.s = Math.round(parseInt(s, 16) / 10) // Convert saturation to 100 Scale - this.state.color.b = Math.round(parseInt(b, 16) / 10) // Convert brightness to 1000 scale + this.state.color.b = Math.round(parseInt(b, 16) / 10) // Convert brightness to 100 scale } // Initialize the set color values for first time. Used to conflicts @@ -491,12 +510,16 @@ class TuyaDevice { // Set white/colour mode based on async setLight(topic, command) { - const currentMode = this.state.dps[this.config.dpsMode].val + let targetMode = undefined + const currentMode = this.config.dpsMode.val + if (topic.key === this.config.dpsWhiteValue || topic.key === this.config.dpsColorTemp) { // If setting white level or color temperature, light should be in white mode targetMode = 'white' } else if (topic.key === this.config.dpsColor) { + // Short sleep for cases where mulitple updates occur quickly + await msSleep(100) if (this.state.setColor.s < 10) { // If saturation is < 10 then white mode targetMode = 'white' @@ -505,17 +528,18 @@ class TuyaDevice { targetMode = 'colour' } } - // If mode change required, add it to the set command - if (targetMode && currentMode !== targetMode) { - command = { - multiple: true, - data: { - [command.dps]: command.set, - [this.config.dpsMode]: targetMode - } - } - } + + // Set the proper value this.set(command) + + // Put the bulb in the correct mode + if (targetMode) { + command = { + dps: this.config.dpsMode, + set: targetMode + } + this.set(command) + } } // Simple function to help debug output @@ -532,9 +556,27 @@ class TuyaDevice { }) } + connectDevice() { + // Find device on network + debug('Search for device id '+this.options.id) + this.device.find().then(() => { + debug('Found device id '+this.options.id) + // Attempt connection to device + this.device.connect().catch((error) => { + debugError(error.message) + this.reconnect() + }) + }).catch(async (error) => { + debugError(error.message) + debugError('Will attempt to find device again in 60 seconds') + await utils.sleep(60) + this.connectDevice() + }) + } + // Retry connection every 10 seconds if unable to connect async reconnect() { - debug('Error connecting to device id '+this.options.id+'...retry in 10 seconds.') + debugError('Error connecting to device id '+this.options.id+'...retry in 10 seconds.') await utils.sleep(10) if (this.connected) { return } debug('Search for device id '+this.options.id) @@ -545,10 +587,27 @@ class TuyaDevice { }) } + // Simple function to monitor heartbeats to determine if + monitorHeartbeat() { + setInterval(async () => { + if (this.connected) { + if (this.heartbeatsMissed > 3) { + debugError('Device id '+this.options.id+' not responding to heartbeats...disconnecting') + this.device.disconnect() + await utils.sleep(1) + this.connectDevice() + } else if (this.heartbeatsMissed > 0) { + const errMessage = this.heartbeatsMissed > 1 ? " consecutive heartbeats" : " heartbeat" + debugError('Device id '+this.options.id+' has missed '+this.heartbeatsMissed+errMessage) + } + this.heartbeatsMissed++ + } + }, 10000) + } // Publish MQTT publishMqtt(topic, message, isDebug) { - if (isDebug) { debugMqtt(topic, message) } + if (isDebug) { debugState(topic, message) } this.mqttClient.publish(topic, message, { qos: 1 }); } } diff --git a/lib/utils.js b/lib/utils.js index 0943f9f..e80e21b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -19,6 +19,10 @@ class Utils return new Promise(res => setTimeout(res, sec*1000)) } + msSleep(ms) { + return new Promise(res => setTimeout(res, ms)) + } + } module.exports = new Utils() \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 656fecb..783b057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tuya-mqtt", - "version": "3.0.0-beta3", + "version": "3.0.0-beta4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -148,9 +148,9 @@ } }, "@types/node": { - "version": "14.11.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.5.tgz", - "integrity": "sha512-jVFzDV6NTbrLMxm4xDSIW/gKnk8rQLF9wAzLWIOg+5nU6ACrIMndeBdXci0FGtqJbP9tQvm6V39eshc96TO2wQ==" + "version": "14.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", + "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==" }, "@types/responselike": { "version": "1.0.0", @@ -487,6 +487,11 @@ "minimist": "^1.1.0" } }, + "complex.js": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", + "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -543,6 +548,11 @@ "ms": "2.1.2" } }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, "decompress-response": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", @@ -773,6 +783,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -864,6 +879,11 @@ } } }, + "fraction.js": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz", + "integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -1271,6 +1291,11 @@ "iterate-iterator": "^1.0.1" } }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -1387,6 +1412,21 @@ "semver": "^6.0.0" } }, + "mathjs": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-7.5.1.tgz", + "integrity": "sha512-H2q/Dq0qxBLMw+G84SSXmGqo/znihuxviGgAQwAcyeFLwK2HksvSGNx4f3dllZF51bWOnu2op60VZxH2Sb51Pw==", + "requires": { + "complex.js": "^2.0.11", + "decimal.js": "^10.2.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.0.12", + "javascript-natural-sort": "^0.7.1", + "seed-random": "^2.2.0", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + } + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -1982,6 +2022,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" + }, "semaphore": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", @@ -2178,6 +2223,11 @@ "xtend": "~4.0.0" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -2211,8 +2261,9 @@ "integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==" }, "tuyapi": { - "version": "github:tsightler/tuyapi#5e99e36a41be43768451ff844375abfb6061ad5e", - "from": "github:tsightler/tuyapi#bugfix-null-get", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tuyapi/-/tuyapi-6.0.1.tgz", + "integrity": "sha512-2Qg0/avg3gtsDLRIktssFtxUA/WT6fvB+GCmQwb1uRQq6KoTsS9he2vDGzmExwaek8Fz3vgAACH7WNIRemCgDw==", "requires": { "debug": "4.1.1", "p-queue": "6.6.1", @@ -2240,6 +2291,11 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==" }, + "typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", diff --git a/package.json b/package.json index 984be8a..a80dd3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tuya-mqtt", - "version": "3.0.0-beta3", + "version": "3.0.0-beta4", "description": "Control Tuya devices locally via MQTT", "homepage": "https://github.com/TheAgentK/tuya-mqtt#readme", "main": "tuya-mqtt.js", @@ -19,7 +19,8 @@ "json5": "^2.1.3", "mqtt": "^4.2.1", "supports-color": "^7.2.0", - "tuyapi": "github:tsightler/tuyapi#bugfix-null-get" + "tuyapi": "^6.0.1", + "mathjs": "7.5.1" }, "repository": { "type": "git", diff --git a/tuya-mqtt.js b/tuya-mqtt.js index bdd0e08..ecf30bf 100644 --- a/tuya-mqtt.js +++ b/tuya-mqtt.js @@ -2,7 +2,8 @@ const fs = require('fs') const mqtt = require('mqtt') const json5 = require('json5') -const debug = require('debug')('tuya-mqtt:mqtt') +const debug = require('debug')('tuya-mqtt:info') +const debugCommand = require('debug')('tuya-mqtt:command') const debugError = require('debug')('tuya-mqtt:error') const SimpleSwitch = require('./devices/simple-switch') const SimpleDimmer = require('./devices/simple-dimmer') @@ -28,23 +29,14 @@ function getDevice(configDevice, mqttClient) { case 'RGBTWLight': return new RGBTWLight(deviceInfo) break; - case 'GenericDevice': - return new GenericDevice(deviceInfo) - break; } - return null + return new GenericDevice(deviceInfo) } function initDevices(configDevices, mqttClient) { for (let configDevice of configDevices) { - if (!configDevice.type) { - debug('Device type not specified, skipping creation of this device') - } else { - const newDevice = getDevice(configDevice, mqttClient) - if (newDevice) { - tuyaDevices.push(newDevice) - } - } + const newDevice = getDevice(configDevice, mqttClient) + tuyaDevices.push(newDevice) } } @@ -62,7 +54,7 @@ const main = async() => { } if (typeof CONFIG.qos == 'undefined') { - CONFIG.qos = 2 + CONFIG.qos = 1 } if (typeof CONFIG.retain == 'undefined') { CONFIG.retain = false @@ -121,7 +113,7 @@ const main = async() => { // If it looks like a valid command topic try to process it if (commandTopic.includes('command')) { - debug('Received MQTT message -> ', JSON.stringify({ + debugCommand('Received MQTT message -> ', JSON.stringify({ topic: topic, message: message }))