From debe4968d25e3047e4e21dfe67f40093314fbb5c Mon Sep 17 00:00:00 2001 From: KarstenSiedentopp Date: Sat, 27 Oct 2018 18:31:49 +0200 Subject: [PATCH] updated mqtt script to event based TuyAPI --- cleanup.js | 29 +++++ package-lock.json | 178 +++++++++++++++++++++------ package.json | 8 +- tuya-device.js | 294 ++++++++++++++++++++++++++++++++++++++++++++ tuya-mqtt-2.js | 100 +++++++++++++++ tuyaapi-extended.js | 120 +++++++++--------- 6 files changed, 628 insertions(+), 101 deletions(-) create mode 100644 cleanup.js create mode 100644 tuya-device.js create mode 100644 tuya-mqtt-2.js diff --git a/cleanup.js b/cleanup.js new file mode 100644 index 0000000..9d628e8 --- /dev/null +++ b/cleanup.js @@ -0,0 +1,29 @@ +// Object to capture process exits and call app specific cleanup function +var debug = require('debug')('Cleanup'); + +function noOp() {}; + +exports.Cleanup = function Cleanup(callback) { + + // attach user callback to the process event emitter + // if no callback, it will still exit gracefully on Ctrl-C + callback = callback || noOp; + process.on('cleanup', callback); + + // do app specific cleaning before exiting + process.on('exit', function () { + process.emit('cleanup'); + }); + + // catch ctrl+c event and exit normally + process.on('SIGINT', function () { + debug('Ctrl-C...'); + process.exit(2); + }); + + //catch uncaught exceptions, trace, then exit normally + process.on('uncaughtException', function (e) { + debug('Uncaught Exception...', e.stack); + process.exit(99); + }); +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aed0386..ff5ad9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,18 +43,18 @@ } }, "buffer": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", - "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "callback-stream": { "version": "1.1.0", @@ -86,17 +86,17 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "color-name": "1.1.1" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "commist": { "version": "1.0.0", @@ -137,9 +137,9 @@ } }, "cron": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/cron/-/cron-1.3.0.tgz", - "integrity": "sha512-K/SF7JlgMmNjcThWxkKvsHhey2EDB4CeOEWJ9aXWj3fbQJppsvTPIeyLdHfNq5IbbsMUUjRW1nr5dSO95f2E4w==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.4.1.tgz", + "integrity": "sha512-HlglwQUNh6bhgfoDR6aEzyHN2T4bc0XhxJxkNPp+Ry7lK7Noby94pHcngYf634+MtxplwZm8okFgNe+R9PGDjg==", "requires": { "moment-timezone": "^0.5.x" } @@ -159,12 +159,20 @@ "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "ms": "2.0.0" + "es5-ext": "^0.10.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" } }, "decamelize": { @@ -191,6 +199,69 @@ "once": "^1.4.0" } }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -234,9 +305,9 @@ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -272,6 +343,11 @@ "unique-stream": "^2.0.2" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, "help-me": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz", @@ -451,21 +527,22 @@ "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, "moment-timezone": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.17.tgz", - "integrity": "sha512-Y/JpVEWIOA9Gho4vO15MTnW1FCmHi3ypprrkUaxsZ1TKg3uqC8q/qMBjTddkHoiwwZN3qvZSr4zJP7x9V3LpXA==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", + "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", "requires": { "moment": ">= 2.9.0" } }, "mqtt": { - "version": "2.18.3", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.18.3.tgz", - "integrity": "sha512-BXCUugFgA6FOWJGxhvUWtVLOdt6hYTmiMGPksEyKuuF1FQ0ji7UJBJ/0kVRMUtUWCAtPGnt4mZZZgJpzNLcuQg==", + "version": "2.18.8", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.18.8.tgz", + "integrity": "sha512-3h6oHlPY/yWwtC2J3geraYRtVVoRM6wdI+uchF4nvSSafXPZnaKqF8xnX+S22SU/FcgEAgockVIlOaAX3fkMpA==", "requires": { "commist": "^1.0.0", "concat-stream": "^1.6.2", "end-of-stream": "^1.4.1", + "es6-map": "^0.1.5", "help-me": "^1.0.1", "inherits": "^2.0.3", "minimist": "^1.2.0", @@ -490,14 +567,19 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha1-bBUsNFzhHFL0ZcKr2VfoY5zWdN8=" + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" }, "npm-run-path": { "version": "2.0.2", @@ -562,7 +644,7 @@ "p-timeout": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha1-2N0ZeVldLcATnh/ka4tkbLPN8Dg=", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "requires": { "p-finally": "^1.0.0" } @@ -742,6 +824,14 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, "through2": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", @@ -770,14 +860,24 @@ } }, "tuyapi": { - "version": "github:codetheweb/tuyapi#9b31d7c74b2e97ef30069ca0d9faabd59ef932b7", + "version": "github:codetheweb/tuyapi#79f21968c31b5a19c15afe3a6e77cebd6eed909f", "from": "github:codetheweb/tuyapi", "requires": { "crc": "^3.5.0", - "debug": "^3.1.0", + "debug": "^4.0.0", "node-forge": "^0.7.1", "p-timeout": "^2.0.1", "retry": "^0.10.1" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "requires": { + "ms": "^2.1.1" + } + } } }, "typedarray": { diff --git a/package.json b/package.json index eb6c03b..af19247 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ "author": "", "license": "ISC", "dependencies": { - "color-convert": "^1.9.2", - "cron": "^1.3.0", + "color-convert": "^1.9.3", + "cron": "^1.4.1", "crypto": "^1.0.1", - "mqtt": "^2.18.3", + "debug": "^3.2.6", + "mqtt": "^2.18.8", + "supports-color": "^5.5.0", "tuyapi": "github:codetheweb/tuyapi", "yargs": "^11.1.0" } diff --git a/tuya-device.js b/tuya-device.js new file mode 100644 index 0000000..5b120ef --- /dev/null +++ b/tuya-device.js @@ -0,0 +1,294 @@ +const TuyAPI = require('tuyapi'); +const TuyColor = require('./tuya-color'); +const debug = require('debug')('TuyAPI-device'); +const debugTuya = require('debug')('TuyAPI-ext'); +const debugTimer = require('debug')('TuyAPI-device-timer'); + +/** + * Sets a property on a device. + * @param {Object} options + * @param {Number} [options.dps=1] DPS index to set + * @param {*} options.set value to set + * @example + * // set default property + * tuya.set({set: true}).then(() => console.log('device was changed')) + * @example + * // set custom property + * tuya.set({dps: 2, set: true}).then(() => console.log('device was changed')) + * @returns {Promise} - returns `true` if the command succeeded + */ +const Parser = require('tuyapi/lib/message-parser') +TuyAPI.prototype.set = function (options) { + let dps = {}; + + if (options.dps != undefined || options.set != undefined) { + if (options.dps === undefined) { + dps = { + 1: options.set + }; + } else { + dps = { + [options.dps.toString()]: options.set + }; + } + } else { + dps = options; + } + + const now = new Date(); + const timeStamp = (parseInt(now.getTime() / 1000, 10)).toString(); + + const payload = { + devId: this.device.id, + uid: '', + t: timeStamp, + dps + }; + + debugTuya('Payload:', this.device.ip); + debugTuya(payload); + + // Encrypt data + const data = this.device.cipher.encrypt({ + data: JSON.stringify(payload) + }); + + // Create MD5 signature + const md5 = this.device.cipher.md5('data=' + data + + '||lpv=' + this.device.version + + '||' + this.device.key); + + // Create byte buffer from hex data + const thisData = Buffer.from(this.device.version + md5 + data); + const buffer = Parser.encode({ + data: thisData, + commandByte: 7 // 0x07 + }); + + // Send request to change status + return new Promise((resolve, reject) => { + this._send(buffer, 7, false).then(() => { + resolve(true); + }).catch(error => { + reject(error); + }); + }); +} + +/** + * + var steckdose = new TuyaDevice({ + id: '03200240600194781244', + key: 'b8bdebab418f5b55', + ip: '192.168.178.45', + type: "socket" + }); + */ + +var TuyaDevice = (function () { + var devices = []; + var events = {}; + + var autoTimeout = undefined; + + function checkExisiting(id) { + var existing = false; + // Check for existing instance + devices.forEach(device => { + if (device.hasOwnProperty("options")) { + if (id === device.options.id) { + existing = device; + } + } + }); + return existing; + } + + function resetTimer() { + return; + debugTimer("Reset timer for auto disconnect all devices"); + clearTimeout(autoTimeout); + autoTimeout = setTimeout(() => { + debugTimer("Auto disconnect all devices"); + TuyaDevice.disconnectAll(); + }, 10000); + } + + function TuyaDevice(options) { + var device = this; + // Check for existing instance + if (existing = checkExisiting(options.id)) { + return existing; + } + + if (!(this instanceof TuyaDevice)) { + return new TuyaDevice(options); + } + + options.type = options.type || "socket"; + options.persistentConnection = true; + + Object.defineProperty(this, 'type', { + value: options.type + }); + + Object.defineProperty(this, 'options', { + value: options + }); + + Object.defineProperty(this, 'device', { + value: new TuyAPI(this.options) + }); + + this.device.on('connected', () => { + debug('Connected to device.'); + device.triggerAll('connected'); + }); + + this.device.on('disconnected', () => { + debug('Disconnected from device.'); + device.triggerAll('disconnected'); + }); + + this.device.on('data', data => { + debug('Data from device:', data); + device.triggerAll('data', data); + }); + + this.device.on('error', (err) => { + debug('Error: ' + err); + device.triggerAll('error', err); + }); + + this.device.connect(); + devices.push(this); + resetTimer(); + } + + TuyaDevice.prototype.triggerAll = function (name, argument) { + var device = this; + var e = events[name] || []; + e.forEach(event => { + event.call(device, argument); + }); + } + + TuyaDevice.prototype.on = function (name, callback) { + var device = this; + this.device.on(name, function () { + callback.apply(device, arguments); + }); + } + + TuyaDevice.prototype.get = function (options) { + resetTimer(); + return this.device.get(options); + } + + TuyaDevice.prototype.set = function (options, callback) { + var device = this; + debug('Setting status:', options); + return this.device.set(options).then(result => { + device.get().then(status => { + debug('Result of setting status to', status); + if (callback != undefined) { + callback.call(device, status); + } + return; + }); + }); + resetTimer(); + } + + TuyaDevice.prototype.onoff = function (newStatus, callback) { + newStatus = newStatus.toLowerCase(); + debug("onoff: " + newStatus); + if (newStatus == "on") { + this.on(callback); + } + if (newStatus == "off") { + this.off(callback); + } + if (newStatus == "toggle") { + this.toggle(callback); + } + } + + TuyaDevice.prototype.on = function (callback) { + var device = this; + device.get().then(status => { + device.set({ + set: true + }, callback); + }); + } + + TuyaDevice.prototype.off = function (callback) { + var device = this; + device.get().then(status => { + device.set({ + set: false + }, callback); + }); + } + + TuyaDevice.prototype.toggle = function (callback) { + var device = this; + device.get().then(status => { + device.set({ + set: !status + }, callback); + }); + } + + TuyaDevice.prototype.setColor = function (hexColor, callback) { + debug("Set color to", hexColor); + var device = this; + var tuya = this.device; + var color = new TuyColor(tuya); + var dps = color.setColor(hexColor); + + device.get().then(status => { + device.set(dps, callback); + }); + resetTimer(); + } + + TuyaDevice.prototype.connect = function (callback) { + this.device.connect(callback); + } + + TuyaDevice.prototype.disconnect = function (callback) { + this.device.disconnect(callback); + } + + Object.defineProperty(TuyaDevice, 'devices', { + value: devices + }); + + TuyaDevice.connectAll = function () { + devices.forEach(device => { + device.connect(); + }); + } + + TuyaDevice.disconnectAll = function () { + devices.forEach(device => { + device.disconnect(); + }); + } + + TuyaDevice.onAll = function (name, callback) { + if (events[name] == undefined) { + events[name] = []; + } + events[name].push(callback); + devices.forEach(device => { + device.triggerAll(name); + }); + } + + return TuyaDevice; +}()); + +module.exports = TuyaDevice; \ No newline at end of file diff --git a/tuya-mqtt-2.js b/tuya-mqtt-2.js new file mode 100644 index 0000000..5870b69 --- /dev/null +++ b/tuya-mqtt-2.js @@ -0,0 +1,100 @@ +const mqtt = require('mqtt'); +const TuyaDevice = require('./tuya-device'); +const debug = require('debug')('tuya-mqtt'); +var cleanup = require('./cleanup').Cleanup(onExit); + +function bmap(istate) { + return istate ? 'ON' : "OFF"; +} + +function bmap(istate) { + return istate ? 'ON' : "OFF"; +} + +const CONFIG = { + host: 'localhost', + port: 1883, + topic: "tuya/" +} + +const mqtt_client = mqtt.connect({ + host: CONFIG.host, + port: CONFIG.port +}); + +mqtt_client.on('connect', function () { + var topic = CONFIG.topic + '#'; + mqtt_client.subscribe(topic, function (err) { + if (!err) { + //mqtt_client.publish(CONFIG.topic + 'presence', 'Hello mqtt') + } + }); +}); + +mqtt_client.on('message', function (topic, message) { + try { + message = message.toString(); + message = message.toLowerCase(); + var topic = topic.split("/"); + var options = { + type: topic[1], + id: topic[2], + key: topic[3], + ip: topic[4], + }; + var exec = topic[5]; + + if (options.type == "socket" || options.type == "lightbulb") { + debug("device", options); + debug("message", message); + var device = new TuyaDevice(options); + + if (exec == "command") { + var status = topic[6]; + device.onoff(status); + } + if (exec == "color") { + var color = message; + device.setColor(color); + } + } + } catch (e) { + debug(e); + } +}); + +function publishStatus(device, status) { + try { + var type = device.type; + var tuyaID = device.options.id; + var tuyaKey = device.options.key; + var tuyaIP = device.options.ip; + + if (tuyaID != undefined && tuyaKey != undefined && tuyaIP != undefined) { + var topic = "tuya/" + type + "/" + tuyaID + "/" + tuyaKey + "/" + tuyaIP + "/state"; + mqtt_client.publish(topic, status, { + retain: true, + qos: 2 + }); + debug("mqtt status updated to:" + topic + " -> " + status); + } else { + debug("mqtt status not updated"); + } + } catch (e) { + debug(e); + } +} + +TuyaDevice.onAll('data', function (data) { + debug('Data from device ' + this.type + ' :', data); + var status = data.dps['1']; + if (this.type == "lightbulb" && status == undefined) { + status = true; + } + publishStatus(this, bmap(status)); +}); + +// defines app specific callback... +function onExit() { + TuyaDevice.disconnectAll(); +}; \ No newline at end of file diff --git a/tuyaapi-extended.js b/tuyaapi-extended.js index 90d78fb..e2bad6a 100644 --- a/tuyaapi-extended.js +++ b/tuyaapi-extended.js @@ -81,8 +81,7 @@ TuyaDevice.prototype.set = function (options) { dps }; - debug('Payload:'); - debug(payload); + debug('Payload:', payload); // Encrypt data const data = this.device.cipher.encrypt({ @@ -111,76 +110,76 @@ TuyaDevice.prototype.set = function (options) { }); }; -/** - * Sends a query to a device. - * @private - * @param {String} ip IP of device - * @param {Buffer} buffer buffer of data - * @returns {Promise} returned data - */ -TuyaDevice.prototype._sendUnwrapped = function (ip, buffer) { - debug('Sending this data: ', buffer.toString('hex')); +// /** +// * Sends a query to a device. +// * @private +// * @param {String} ip IP of device +// * @param {Buffer} buffer buffer of data +// * @returns {Promise} returned data +// */ +// TuyaDevice.prototype._sendUnwrapped = function (ip, buffer) { +// debug('Sending this data: ', buffer.toString('hex')); - const client = new net.Socket(); +// const client = new net.Socket(); - return new Promise((resolve, reject) => { - // Attempt to connect - client.connect(6668, ip); +// return new Promise((resolve, reject) => { +// // Attempt to connect +// client.connect(6668, ip); - // Default connect timeout is ~1 minute, - // 10 seconds is a more reasonable default - // since `retry` is used. - client.setTimeout(1000, () => { - client.emit('error', new Error('connection timed out')); - client.destroy(); - }); +// // Default connect timeout is ~1 minute, +// // 10 seconds is a more reasonable default +// // since `retry` is used. +// client.setTimeout(1000, () => { +// client.emit('error', new Error('connection timed out')); +// client.destroy(); +// }); - // Send data when connected - client.on('connect', () => { - debug('Socket connected.'); +// // Send data when connected +// client.on('connect', () => { +// debug('Socket connected.'); - // Remove connect timeout - client.setTimeout(0); +// // Remove connect timeout +// client.setTimeout(0); - // Transmit data - client.write(buffer); +// // Transmit data +// client.write(buffer); - this._sendTimeout = setTimeout(() => { - client.destroy(); - reject(new Error('Timeout waiting for response')); - }, this._responseTimeout * 1000); - }); +// this._sendTimeout = setTimeout(() => { +// client.destroy(); +// reject(new Error('Timeout waiting for response')); +// }, this._responseTimeout * 1000); +// }); - // Parse response data - client.on('data', data => { - debug('Received data back:'); - debug(data.toString('hex')); +// // Parse response data +// client.on('data', data => { +// debug('Received data back:'); +// debug(data.toString('hex')); - clearTimeout(this._sendTimeout); - client.destroy(); +// clearTimeout(this._sendTimeout); +// client.destroy(); - data = Parser.parse(data); +// data = Parser.parse(data); - if (typeof data === 'object' || typeof data === 'undefined') { - } else { - // Message is encrypted - data =this.device.cipher.decrypt(data); - } - client.destroy(); // kill client after server's response - resolve(data); - }); +// if (typeof data === 'object' || typeof data === 'undefined') { +// } else { +// // Message is encrypted +// data =this.device.cipher.decrypt(data); +// } +// client.destroy(); // kill client after server's response +// resolve(data); +// }); - // Handle errors - client.on('error', err => { - debug('Error event from socket.'); - client.destroy(); // kill client after server's response +// // Handle errors +// client.on('error', err => { +// debug('Error event from socket.'); +// client.destroy(); // kill client after server's response - // eslint-disable-next-line max-len - err.message = 'Error communicating with device. Make sure nothing else is trying to control it or connected to it.'; - reject(err); - }); - }); -}; +// // eslint-disable-next-line max-len +// err.message = 'Error communicating with device. Make sure nothing else is trying to control it or connected to it.'; +// reject(err); +// }); +// }); +// }; TuyaDevice.prototype.getStatus = function (callback) { var tuya = this; @@ -225,6 +224,9 @@ TuyaDevice.prototype.onoff = function (newStatus, callback) { if (newStatus == "off") { this.off(callback); } + if (newStatus == "toggle") { + this.toggle(callback); + } } TuyaDevice.prototype.setColor = function (hexColor, callback) {