From 1e473e6d0b75121f673844e84fa0b05efc5220a8 Mon Sep 17 00:00:00 2001 From: Vinod S R Date: Wed, 7 Jun 2023 17:13:48 +0530 Subject: [PATCH 1/6] feat: added the changes as per the comments from msillano --- src/tuya-smart-device.js | 80 +++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/src/tuya-smart-device.js b/src/tuya-smart-device.js index c639d49..b51268a 100644 --- a/src/tuya-smart-device.js +++ b/src/tuya-smart-device.js @@ -15,7 +15,6 @@ module.exports = function (RED) { let shouldTryReconnect = true; let shouldSubscribeData = true; let shouldSubscribeRefreshData = true; - console.log('CREDS', this.credentials, config.deviceId); this.name = config.deviceName; this.deviceName = config.deviceName; this.storeAsCreds = config.storeAsCreds || false; @@ -23,18 +22,21 @@ module.exports = function (RED) { // If the deviceId and key are stored in the creds take it from there const secretConfig = this.credentials.secretConfig || '{}'; const secret = JSON.parse(secretConfig); - console.log('Using secrets :: ', secret); + console.log( + 'loading the deviceId and deviceKey from secret credentials ...' + ); this.deviceId = secret.deviceId; this.deviceKey = secret.deviceKey; } else { + console.log('loading the deviceId and deviceKey from config ...'); this.deviceId = config.deviceId; this.deviceKey = config.deviceKey; } - console.log('NEW DEVICE ID', this.deviceId); this.deviceIp = config.deviceIp; this.disableAutoStart = config.disableAutoStart; this.eventMode = config.eventMode || 'event-both'; + //TODO: find a perfect method to optimize the printing node.log( `Recieved the config ${JSON.stringify({ ...config, @@ -70,7 +72,7 @@ module.exports = function (RED) { ? '3.1' : config.tuyaVersion.trim(); - this.deviceStatus = CLIENT_STATUS.DISCONNECTED; + this.deviceStatus = null; // Variable definition ends here if (this.eventMode == 'event-data') { @@ -107,11 +109,13 @@ module.exports = function (RED) { // error device not connected let errText = `Device not connected. Can't send the ${operation} commmand`; node.log(errText); + //TODO : make the log anonymous setStatusOnError(errText, 'Device not connected !', { context: { message: errText, deviceVirtualId: node.deviceId, deviceIp: node.deviceIp, + deviceName: node.deviceName, deviceKey: node.deviceKey, }, }); @@ -130,9 +134,15 @@ module.exports = function (RED) { break; case 'CONTROL': if (msg.payload.action == 'CONNECT') { - startComm(); + if (!tuyaDevice.isConnected()) { + // Connect only when disconnected + startComm(); + } } else if (msg.payload.action == 'DISCONNECT') { - closeComm(); + //Disconnect only when connected. + if (tuyaDevice.isConnected()) { + closeComm(); + } } else if (msg.payload.action == 'SET_FIND_TIMEOUT') { if (!isNaN(msg.payload.value) && msg.payload.value > 0) { setFindTimeout(msg.payload.value); @@ -146,7 +156,21 @@ module.exports = function (RED) { node.log('Invalid retry timeout ! - ' + msg.payload.value); } } else if (msg.payload.action == 'RECONNECT') { + if (tuyaDevice.isConnected) { + closeComm(); + } startComm(); + } else if (msg.payload.action == 'SET_DATA_EVENT') { + //TODO: Analyze + shouldSubscribeData = true; + shouldSubscribeRefreshData = true; + if (msg.payload.value === 'event-data') + shouldSubscribeRefreshData = false; + if (msg.payload.value === 'event-dp-refresh') + shouldSubscribeData = false; + node.log( + `Event subscription: shouldSubscribeData=>${shouldSubscribeData} , shouldSubscribeRefreshData=>${shouldSubscribeRefreshData}` + ); } break; } @@ -183,11 +207,11 @@ module.exports = function (RED) { }; const startComm = () => { - closeComm(); // This 1 sec timeout will make sure that the diconnect happens .. // otherwise connect will not hanppen as the state is not changed findTimeoutHandler = setTimeout(() => { shouldTryReconnect = true; + //TODO: Make the log anonymous node.log( `startComm(): Connecting to Tuya with params ${JSON.stringify( connectionParams @@ -247,6 +271,7 @@ module.exports = function (RED) { issueGetOnConnect: false, nullPayloadOnJSONError: false, version: node.tuyaVersion, + issueRefreshOnConnect: false, }; let tuyaDevice = new TuyaDevice(connectionParams); @@ -267,7 +292,12 @@ module.exports = function (RED) { // Add event listeners tuyaDevice.on('connected', () => { - node.log('Connected to device! ' + node.deviceId); + node.log( + 'Connected to device! name : ' + + node.deviceName + + ', ip : ' + + node.deviceIp + ); setStatusConnected(); }); @@ -289,6 +319,7 @@ module.exports = function (RED) { ', error = ' + JSON.stringify(error) ); + // Anonymize setStatusOnError(error, 'Error : ' + JSON.stringify(error), { context: { message: error, @@ -310,8 +341,8 @@ module.exports = function (RED) { } }); - if (shouldSubscribeRefreshData) { - tuyaDevice.on('dp-refresh', (data) => { + tuyaDevice.on('dp-refresh', (data) => { + if (shouldSubscribeRefreshData) { node.log( `Data from device [event:dp-refresh]: ${JSON.stringify(data)}` ); @@ -326,11 +357,11 @@ module.exports = function (RED) { }, null, ]); - }); - } + } + }); - if (shouldSubscribeData) { - tuyaDevice.on('data', (data) => { + tuyaDevice.on('data', (data) => { + if (shouldSubscribeData) { node.log(`Data from device [event:data]: ${JSON.stringify(data)}`); setStatusConnected(); node.send([ @@ -343,8 +374,9 @@ module.exports = function (RED) { }, null, ]); - }); - } + } + }); + let connectDevice = () => { clearTimeout(findTimeoutHandler); if (tuyaDevice.isConnected() === false) { @@ -359,7 +391,9 @@ module.exports = function (RED) { ); if (shouldTryReconnect) { node.log('connectDevice(): retrying the connect'); - clearTimeout(findTimeoutHandler); + if (findTimeoutHandler) { + clearTimeout(findTimeoutHandler); + } findTimeoutHandler = setTimeout(findDevice, node.retryTimeout); } else { node.log( @@ -388,6 +422,7 @@ module.exports = function (RED) { }) .catch((e) => { // We need to retry + // TODO: Make the log anonymous setStatusOnError(e.message, "Can't find device", { context: { message: e, @@ -396,6 +431,7 @@ module.exports = function (RED) { deviceKey: node.deviceKey, }, }); + setStatusDisconnected(); if (shouldTryReconnect) { node.log('findDevice(): Cannot find the device, re-trying...'); findTimeoutHandler = setTimeout(findDevice, node.retryTimeout); @@ -406,16 +442,18 @@ module.exports = function (RED) { } }); }; + + // Initial state + setTimeout(() => { + setStatusDisconnected(); + }, 500); + // Start probing if (!node.disableAutoStart) { node.log('Auto start probe on connect...'); startComm(); } else { node.log('Auto start probe is disabled '); - // If we dont use timeout , state will not be emitted, - setTimeout(() => { - setStatusDisconnected(); - }, 1000); } } RED.nodes.registerType('tuya-smart-device', TuyaSmartDeviceNode, { From cbde074af803d341aaf7e4a1e43017d13c090c71 Mon Sep 17 00:00:00 2001 From: Vinod S R Date: Sat, 10 Jun 2023 10:54:14 +0530 Subject: [PATCH 2/6] fix: optimized the code --- src/tuya-smart-device.js | 45 ++++++++++++++++++++++++---------------- src/utils.js | 11 ++++++++++ 2 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 src/utils.js diff --git a/src/tuya-smart-device.js b/src/tuya-smart-device.js index b51268a..5879374 100644 --- a/src/tuya-smart-device.js +++ b/src/tuya-smart-device.js @@ -1,11 +1,18 @@ const TuyaDevice = require('tuyapi'); const packageInfo = require('../package.json'); +const utils = require('./utils'); const CLIENT_STATUS = { DISCONNECTED: 'DISCONNECTED', CONNECTED: 'CONNECTED', CONNECTING: 'CONNECTING', ERROR: 'ERROR', }; + +const EVENT_MODES = { + BOTH: 'event-both', + DATA: 'event-data', + DP_REFRESH: 'event-dp-refresh', +}; module.exports = function (RED) { function TuyaSmartDeviceNode(config) { RED.nodes.createNode(this, config); @@ -35,15 +42,16 @@ module.exports = function (RED) { this.deviceIp = config.deviceIp; this.disableAutoStart = config.disableAutoStart; - this.eventMode = config.eventMode || 'event-both'; - //TODO: find a perfect method to optimize the printing - node.log( + this.eventMode = config.eventMode || EVENT_MODES.BOTH; + // To be used for local debugging. + // need to find a log method control in the node. + /*node.log( `Recieved the config ${JSON.stringify({ ...config, credentials: this.credentials, moduleVersion: packageInfo.version, })}` - ); + );*/ this.retryTimeout = config.retryTimeout == null || typeof config.retryTimeout == 'undefined' || @@ -75,10 +83,10 @@ module.exports = function (RED) { this.deviceStatus = null; // Variable definition ends here - if (this.eventMode == 'event-data') { + if (this.eventMode == EVENT_MODES.DATA) { shouldSubscribeData = true; shouldSubscribeRefreshData = false; - } else if (this.eventMode == 'event-dp-refresh') { + } else if (this.eventMode == EVENT_MODES.DP_REFRESH) { shouldSubscribeData = false; shouldSubscribeRefreshData = true; } else { @@ -109,7 +117,6 @@ module.exports = function (RED) { // error device not connected let errText = `Device not connected. Can't send the ${operation} commmand`; node.log(errText); - //TODO : make the log anonymous setStatusOnError(errText, 'Device not connected !', { context: { message: errText, @@ -140,9 +147,8 @@ module.exports = function (RED) { } } else if (msg.payload.action == 'DISCONNECT') { //Disconnect only when connected. - if (tuyaDevice.isConnected()) { - closeComm(); - } + // Make disconnect force + closeComm(); } else if (msg.payload.action == 'SET_FIND_TIMEOUT') { if (!isNaN(msg.payload.value) && msg.payload.value > 0) { setFindTimeout(msg.payload.value); @@ -160,16 +166,20 @@ module.exports = function (RED) { closeComm(); } startComm(); - } else if (msg.payload.action == 'SET_DATA_EVENT') { - //TODO: Analyze + } else if (msg.payload.action == 'SET_EVENT_MODE') { shouldSubscribeData = true; shouldSubscribeRefreshData = true; - if (msg.payload.value === 'event-data') + // if any incorrect value set the event mode as BOTH + node.eventMode = EVENT_MODES.BOTH; + if (msg.payload.value === EVENT_MODES.DATA) { shouldSubscribeRefreshData = false; - if (msg.payload.value === 'event-dp-refresh') + node.eventMode = EVENT_MODES.DATA; + } else if (msg.payload.value === EVENT_MODES.DP_REFRESH) { shouldSubscribeData = false; + node.eventMode = EVENT_MODES.DP_REFRESH; + } node.log( - `Event subscription: shouldSubscribeData=>${shouldSubscribeData} , shouldSubscribeRefreshData=>${shouldSubscribeRefreshData}` + `SET_EVENT_MODE : shouldSubscribeData=>${shouldSubscribeData} , shouldSubscribeRefreshData=>${shouldSubscribeRefreshData}` ); } break; @@ -211,10 +221,9 @@ module.exports = function (RED) { // otherwise connect will not hanppen as the state is not changed findTimeoutHandler = setTimeout(() => { shouldTryReconnect = true; - //TODO: Make the log anonymous node.log( `startComm(): Connecting to Tuya with params ${JSON.stringify( - connectionParams + utils.maskSensitiveData(connectionParams) )} , findTimeout : ${node.findTimeout} , retryTimeout: ${ node.retryTimeout } ` @@ -422,13 +431,13 @@ module.exports = function (RED) { }) .catch((e) => { // We need to retry - // TODO: Make the log anonymous setStatusOnError(e.message, "Can't find device", { context: { message: e, deviceVirtualId: node.deviceId, deviceIp: node.deviceIp, deviceKey: node.deviceKey, + deviceName: node.deviceName, }, }); setStatusDisconnected(); diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..e54f532 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,11 @@ +const maskSensitiveData = (obj) => { + return { + ...obj, + key: '**MASKED**', + id: '**MASKED**', + }; +}; + +module.exports = { + maskSensitiveData, +}; From cffdb91dd6a9784efbfd3cb32025d6d4f7566d78 Mon Sep 17 00:00:00 2001 From: Vinod S R Date: Sun, 11 Jun 2023 11:28:20 +0530 Subject: [PATCH 3/6] fix: Improve score card https://flows.nodered.org/node/node-red-contrib-tuya-smart-device/scorecard# --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4268fc7..e2915ed 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,16 @@ "node-red" ], "node-red": { + "version": ">=2.5.0", "nodes": { "tuya-smart-device": "src/tuya-smart-device.js", "tuya-smart-device-generic": "src/tuya-smart-device-generic.js" } }, "dependencies": { - "tuyapi": "7.5.1" + "tuyapi": "~7.5.1" + }, + "engines": { + "node": ">=16.0.0" } } From 8c1082dee2692034d12c37f0d1c59c4a04405bd1 Mon Sep 17 00:00:00 2001 From: Vinod S R Date: Sun, 11 Jun 2023 11:43:06 +0530 Subject: [PATCH 4/6] doc: updated help --- src/tuya-smart-device.html | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/tuya-smart-device.html b/src/tuya-smart-device.html index b4e2106..4b84557 100644 --- a/src/tuya-smart-device.html +++ b/src/tuya-smart-device.html @@ -335,7 +335,7 @@

payload

} } -
dps action
+
action string
This property works only with operation is set as "CONTROL" @@ -353,7 +353,7 @@

payload

Disconnect from the device - RECONNECTS + RECONNECT Reconnects to the device @@ -368,7 +368,34 @@

payload

Sets the retry timeout dynamically. This is not saved in the config + + SET_EVENT_MODE + + Sets the event mode dynamically. This is not saved in the config. +
+
+ value = event-data //for subscribing to only data event +
+
+ value = event-dp-refresh //for subscribing to only dp-refresh event +
+
+ value = both //for subscribing to both events + + + example : +
+ action : CONNECT + payload = { "operation": "CONTROL", "action": "CONNECT" } +
+
+ action : SET_EVENT_MODE + + payload = { "operation": "CONTROL", "action": "SET_EVENT_MODE", + "value": "event-data" } + +
From b9c9af74c02154d0d6f926a9c107fce2931a1566 Mon Sep 17 00:00:00 2001 From: Vinod S R Date: Sun, 11 Jun 2023 11:44:46 +0530 Subject: [PATCH 5/6] Bump to version 5.2.0 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8b362..4280557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 5.2.0 + +- Added new control action "SET_EVENT_MODE" +- Improve the code base +- Improved example + ## 5.1.0 - Update tuya CLI to v7.5.1 (https://github.com/vinodsr/node-red-contrib-tuya-smart-device/pull/110) diff --git a/package.json b/package.json index e2915ed..e6b8d9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red-contrib-tuya-smart-device", - "version": "5.1.0", + "version": "5.2.0", "description": "A node-red module to interact with the tuya smart devices", "repository": "https://github.com/vinodsr/node-red-contrib-tuya-smart-device", "main": "index.js", From 2a88442ef3668b70e1de31d46849b48e1ccbd681 Mon Sep 17 00:00:00 2001 From: Vinod S R Date: Sun, 11 Jun 2023 11:50:07 +0530 Subject: [PATCH 6/6] doc: updated example flow --- examples/latest.json | 137 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 8 deletions(-) diff --git a/examples/latest.json b/examples/latest.json index 9a9ad33..925b621 100644 --- a/examples/latest.json +++ b/examples/latest.json @@ -2,7 +2,7 @@ { "id": "b2e04683.825318", "type": "tab", - "label": "Flow 1", + "label": "Example Flow", "disabled": false, "info": "" }, @@ -14,11 +14,13 @@ "disableAutoStart": true, "deviceId": "", "deviceKey": "", + "storeAsCreds": false, "deviceIp": "", "retryTimeout": "1001", "findTimeout": "2000", "tuyaVersion": "3.1", "eventMode": "event-both", + "credentials": { "secretConfig": "{}" }, "x": 680, "y": 360, "wires": [["ff4bd686.53ae18"], ["15d22ba6.824b84"]] @@ -77,7 +79,7 @@ "z": "b2e04683.825318", "name": "", "x": 660, - "y": 540, + "y": 740, "wires": [["1e77cd28.a8c303"]] }, { @@ -91,7 +93,7 @@ "initialize": "", "finalize": "", "x": 450, - "y": 540, + "y": 740, "wires": [["e4cfcdc.9520a3"]] }, { @@ -108,7 +110,7 @@ "payload": "true", "payloadType": "bool", "x": 240, - "y": 580, + "y": 780, "wires": [["81a451cc.919f9"]] }, { @@ -124,7 +126,7 @@ "statusVal": "", "statusType": "auto", "x": 940, - "y": 540, + "y": 740, "wires": [] }, { @@ -141,7 +143,7 @@ "payload": "false", "payloadType": "bool", "x": 240, - "y": 500, + "y": 700, "wires": [["81a451cc.919f9"]] }, { @@ -219,8 +221,8 @@ "topic": "", "payload": "{\"operation\":\"CONTROL\",\"action\":\"RECONNECT\"}", "payloadType": "json", - "x": 330, - "y": 220, + "x": 340, + "y": 260, "wires": [["e63245d0.e6cdc8"]] }, { @@ -236,6 +238,125 @@ "topic": "", "payload": "{\"operation\":\"CONTROL\",\"action\":\"DISCONNECT\"}", "payloadType": "json", + "x": 340, + "y": 200, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "c820897454429637", + "type": "inject", + "z": "b2e04683.825318", + "name": "Get Schema", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"GET\",\"schema\":true}", + "payloadType": "json", + "x": 310, + "y": 100, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "7b416b83a6edfd2b", + "type": "inject", + "z": "b2e04683.825318", + "name": "Get Status of dps 1", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"GET\" , \"dps\": 1}", + "payloadType": "json", + "x": 330, + "y": 40, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "8616fca1bda4ddbf", + "type": "inject", + "z": "b2e04683.825318", + "name": "SET EVENT MODE - DATA", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"CONTROL\",\"action\":\"SET_EVENT_MODE\",\"value\":\"event-data\"}", + "payloadType": "json", + "x": 260, + "y": 460, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "cbe30d8393fbb66a", + "type": "inject", + "z": "b2e04683.825318", + "name": "SET EVENT MODE - DP REFRESH", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"CONTROL\",\"action\":\"SET_EVENT_MODE\",\"value\":\"event-dp-refresh\"}", + "payloadType": "json", + "x": 280, + "y": 500, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "4df579385b195b55", + "type": "inject", + "z": "b2e04683.825318", + "name": "Update Find Timeout", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"CONTROL\",\"action\":\"SET_FIND_TIMEOUT\",\"value\":1234}", + "payloadType": "json", + "x": 230, + "y": 540, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "65ab15b279337d86", + "type": "inject", + "z": "b2e04683.825318", + "name": "Update Retry Timeout", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"CONTROL\",\"action\":\"SET_RETRY_TIMEOUT\",\"value\":1234}", + "payloadType": "json", + "x": 240, + "y": 580, + "wires": [["e63245d0.e6cdc8"]] + }, + { + "id": "51d98c1b9e3ce54b", + "type": "inject", + "z": "b2e04683.825318", + "name": "CONNECT ACTION", + "props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"operation\":\"CONTROL\",\"action\":\"CONNECT\"}", + "payloadType": "json", "x": 330, "y": 160, "wires": [["e63245d0.e6cdc8"]]