mirror of
https://github.com/lehanspb/tuya-mqtt.git
synced 2025-12-16 17:54:36 +00:00
updated TuyAPI to v 4.x
This commit is contained in:
26
package-lock.json
generated
26
package-lock.json
generated
@@ -440,10 +440,18 @@
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-retry": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
|
||||
"integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
|
||||
"requires": {
|
||||
"retry": "^0.12.0"
|
||||
}
|
||||
},
|
||||
"p-timeout": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
|
||||
"integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
|
||||
"integrity": "sha1-2N0ZeVldLcATnh/ka4tkbLPN8Dg=",
|
||||
"requires": {
|
||||
"p-finally": "^1.0.0"
|
||||
}
|
||||
@@ -518,9 +526,9 @@
|
||||
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
|
||||
},
|
||||
"retry": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
|
||||
"integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
@@ -576,15 +584,15 @@
|
||||
}
|
||||
},
|
||||
"tuyapi": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tuyapi/-/tuyapi-3.2.3.tgz",
|
||||
"integrity": "sha512-3KJKPbjLz5UaSpZaIPgcZ5nGGE71S/S2mp2tzVrM0zHbMCeSNAzt3YIUv6rHXv1XTnCCjBQCXDGE46zMePZSng==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tuyapi/-/tuyapi-4.0.3.tgz",
|
||||
"integrity": "sha512-ZaGm7LX43zLZhS6fSVLdGHfg9dT8IVeDPmJT7KV9u9+t0PGVS+b/jfgCMpjvAI4tYsPfDFEfgO6vr/G5pMAJYg==",
|
||||
"requires": {
|
||||
"crc": "^3.5.0",
|
||||
"debug": "^4.0.0",
|
||||
"node-forge": "^0.8.0",
|
||||
"p-timeout": "^2.0.1",
|
||||
"retry": "^0.10.1"
|
||||
"p-retry": "^3.0.1",
|
||||
"p-timeout": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"color-convert": "^1.9.3",
|
||||
"debug": "^3.2.6",
|
||||
"mqtt": "^2.18.8",
|
||||
"tuyapi": "^3.2.3"
|
||||
"tuyapi": "^4.0.3"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
263
tuya-device.js
263
tuya-device.js
@@ -1,8 +1,8 @@
|
||||
const TuyAPI = require('tuyapi');
|
||||
const TuyColor = require('./tuya-color');
|
||||
const debug = require('debug')('TuyAPI-device');
|
||||
const debugColor = require('debug')('TuyAPI-device-color');
|
||||
const debugTimer = require('debug')('TuyAPI-device-timer');
|
||||
const debug = require('debug')('TuyAPI:device');
|
||||
const debugError = require('debug')('TuyAPI:device:error');
|
||||
const debugColor = require('debug')('TuyAPI:device:color');
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -14,12 +14,68 @@ const debugTimer = require('debug')('TuyAPI-device-timer');
|
||||
});
|
||||
*/
|
||||
|
||||
// Helpers
|
||||
const Parser = require('tuyapi/lib/message-parser');
|
||||
|
||||
class CustomTujAPI extends TuyAPI {
|
||||
get(options) {
|
||||
// Set empty object as default
|
||||
options = options ? options : {};
|
||||
|
||||
const payload = {
|
||||
gwId: this.device.gwID,
|
||||
devId: this.device.id
|
||||
};
|
||||
|
||||
debug('GET Payload:');
|
||||
debug(payload);
|
||||
|
||||
// Create byte buffer
|
||||
const buffer = Parser.encode({
|
||||
data: payload,
|
||||
commandByte: 10 // 0x0a
|
||||
});
|
||||
|
||||
// Send request and parse response
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Send request
|
||||
this._send(buffer).then(() => {
|
||||
// Runs when data event is emitted
|
||||
const resolveGet = data => {
|
||||
// Remove self listener
|
||||
this.removeListener('data', resolveGet);
|
||||
|
||||
try {
|
||||
if (options.schema === true) {
|
||||
// Return whole response
|
||||
resolve(data);
|
||||
} else if (options.dps) {
|
||||
// Return specific property
|
||||
resolve(data.dps[options.dps]);
|
||||
} else {
|
||||
// Return first property by default
|
||||
resolve(data.dps['1']);
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Add listener
|
||||
this.on('data', resolveGet);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var TuyaDevice = (function () {
|
||||
var devices = [];
|
||||
var events = {};
|
||||
|
||||
var autoTimeout = undefined;
|
||||
|
||||
function checkExisiting(id) {
|
||||
var existing = false;
|
||||
// Check for existing instance
|
||||
@@ -33,21 +89,27 @@ var TuyaDevice = (function () {
|
||||
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 deleteDevice(id) {
|
||||
devices.forEach((device, key) => {
|
||||
if (device.hasOwnProperty("options")) {
|
||||
if (id === device.options.id) {
|
||||
debug("delete Device", devices[key].toString());
|
||||
delete devices[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function TuyaDevice(options) {
|
||||
function TuyaDevice(options, callback) {
|
||||
var device = this;
|
||||
// Check for existing instance
|
||||
if (existing = checkExisiting(options.id)) {
|
||||
return existing;
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
status: "connected",
|
||||
device: existing
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!(this instanceof TuyaDevice)) {
|
||||
@@ -55,43 +117,69 @@ var TuyaDevice = (function () {
|
||||
}
|
||||
|
||||
options.type = options.type || "socket";
|
||||
options.persistentConnection = true;
|
||||
|
||||
Object.defineProperty(this, 'type', {
|
||||
value: options.type
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'options', {
|
||||
value: options
|
||||
});
|
||||
this.type = options.type;
|
||||
this.options = 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');
|
||||
value: new CustomTujAPI(JSON.parse(JSON.stringify(this.options)))
|
||||
});
|
||||
|
||||
this.device.on('data', data => {
|
||||
debug('Data from device:', data);
|
||||
device.triggerAll('data', data);
|
||||
if (typeof data == "string") {
|
||||
debugError('Data from device not encrypted:', data.replace(/[^a-zA-Z0-9 ]/g, ""));
|
||||
} else {
|
||||
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();
|
||||
// Find device on network
|
||||
debug("Search device in network");
|
||||
this.find().then(() => {
|
||||
debug("Device found in network");
|
||||
// Connect to device
|
||||
this.device.connect();
|
||||
});
|
||||
|
||||
/**
|
||||
* @return promis to wait for connection
|
||||
*/
|
||||
return new Promise((resolve, reject) => {
|
||||
this.device.on('connected', () => {
|
||||
device.triggerAll('connected');
|
||||
device.connected = true;
|
||||
debug('Connected to device.', device.toString());
|
||||
resolve({
|
||||
status: "connected",
|
||||
device: this
|
||||
});
|
||||
});
|
||||
this.device.on('disconnected', () => {
|
||||
device.triggerAll('disconnected');
|
||||
device.connected = false;
|
||||
debug('Disconnected from device.', device.toString());
|
||||
deleteDevice(options.id);
|
||||
return reject({
|
||||
status: "disconnect",
|
||||
device: null
|
||||
});
|
||||
});
|
||||
|
||||
this.device.on('error', (err) => {
|
||||
debugError(err);
|
||||
device.triggerAll('error', err);
|
||||
return reject({
|
||||
error: err,
|
||||
device: this
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.toString = function () {
|
||||
return this.type + " (" + this.options.ip + ", " + this.options.id + ", " + this.options.key + ")";
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.triggerAll = function (name, argument) {
|
||||
@@ -103,98 +191,95 @@ var TuyaDevice = (function () {
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.on = function (name, callback) {
|
||||
if (!this.connected) return;
|
||||
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.find = function () {
|
||||
return this.device.find();
|
||||
}
|
||||
|
||||
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;
|
||||
TuyaDevice.prototype.get = function () {
|
||||
return this.device.get();
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.set = function (options) {
|
||||
debug('set:', options);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.device.set(options).then((result) => {
|
||||
this.get().then(() => {
|
||||
debug("set completed ");
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.switch = function (newStatus, callback) {
|
||||
if (!this.connected) return;
|
||||
newStatus = newStatus.toLowerCase();
|
||||
debug("switch: " + newStatus);
|
||||
if (newStatus == "on") {
|
||||
this.switchOn(callback);
|
||||
return this.switchOn(callback);
|
||||
}
|
||||
if (newStatus == "off") {
|
||||
this.switchOff(callback);
|
||||
return this.switchOff(callback);
|
||||
}
|
||||
if (newStatus == "toggle") {
|
||||
this.toggle(callback);
|
||||
return this.toggle(callback);
|
||||
}
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.switchOn = function (callback) {
|
||||
var device = this;
|
||||
TuyaDevice.prototype.switchOn = function () {
|
||||
if (!this.connected) return;
|
||||
debug("switch -> ON");
|
||||
device.get().then(status => {
|
||||
device.set({
|
||||
set: true
|
||||
}, callback);
|
||||
|
||||
return this.set({
|
||||
set: true
|
||||
});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.switchOff = function (callback) {
|
||||
var device = this;
|
||||
TuyaDevice.prototype.switchOff = function () {
|
||||
if (!this.connected) return;
|
||||
debug("switch -> OFF");
|
||||
device.get().then(status => {
|
||||
device.set({
|
||||
set: false
|
||||
}, callback);
|
||||
|
||||
return this.set({
|
||||
set: false
|
||||
});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.toggle = function (callback) {
|
||||
var device = this;
|
||||
device.get().then(status => {
|
||||
device.set({
|
||||
set: !status
|
||||
}, callback);
|
||||
TuyaDevice.prototype.toggle = function () {
|
||||
if (!this.connected) return;
|
||||
debug("toogle state");
|
||||
return this.set({
|
||||
set: !status
|
||||
});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.setColor = function (hexColor, callback) {
|
||||
TuyaDevice.prototype.setColor = function (hexColor) {
|
||||
if (!this.connected) return;
|
||||
debugColor("Set color to: ", hexColor);
|
||||
var device = this;
|
||||
var tuya = this.device;
|
||||
var color = new TuyColor(tuya);
|
||||
var dps = color.setColor(hexColor);
|
||||
debugColor("dps values:", dps);
|
||||
|
||||
device.get().then(status => {
|
||||
device.set({
|
||||
multiple: true,
|
||||
data: dps
|
||||
}, callback);
|
||||
return this.set({
|
||||
multiple: true,
|
||||
data: dps
|
||||
});
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.connect = function (callback) {
|
||||
this.device.connect(callback);
|
||||
debug("Connect to TuyAPI Device");
|
||||
return this.device.connect(callback);
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.disconnect = function (callback) {
|
||||
this.device.disconnect(callback);
|
||||
debug("Disconnect from TuyAPI Device");
|
||||
return this.device.disconnect(callback);
|
||||
}
|
||||
|
||||
Object.defineProperty(TuyaDevice, 'devices', {
|
||||
|
||||
87
tuya-mqtt.js
87
tuya-mqtt.js
@@ -1,10 +1,9 @@
|
||||
const mqtt = require('mqtt');
|
||||
const TuyaDevice = require('./tuya-device');
|
||||
const debug = require('debug')('tuya-mqtt');
|
||||
const debugColor = require('debug')('color');
|
||||
const debugMqtt = require('debug')('mqtt');
|
||||
const debugTuya = require('debug')('tuyAPI-Events');
|
||||
const debugError = require('debug')('error');
|
||||
const debug = require('debug')('TuyAPI:mqtt');
|
||||
const debugColor = require('debug')('TuyAPI:mqtt:color');
|
||||
const debugTuya = require('debug')('TuyAPI:mqtt:device');
|
||||
const debugError = require('debug')('TuyAPI:mqtt:error');
|
||||
var cleanup = require('./cleanup').Cleanup(onExit);
|
||||
|
||||
function bmap(istate) {
|
||||
@@ -41,7 +40,7 @@ const mqtt_client = mqtt.connect({
|
||||
});
|
||||
|
||||
mqtt_client.on('connect', function (err) {
|
||||
debugMqtt("Verbindung mit MQTT-Server hergestellt");
|
||||
debug("Verbindung mit MQTT-Server hergestellt");
|
||||
connected = true;
|
||||
var topic = CONFIG.topic + '#';
|
||||
mqtt_client.subscribe(topic, {
|
||||
@@ -52,15 +51,15 @@ mqtt_client.on('connect', function (err) {
|
||||
|
||||
mqtt_client.on("reconnect", function (error) {
|
||||
if (connected) {
|
||||
debugMqtt("Verbindung mit MQTT-Server wurde unterbrochen. Erneuter Verbindungsversuch!");
|
||||
debug("Verbindung mit MQTT-Server wurde unterbrochen. Erneuter Verbindungsversuch!");
|
||||
} else {
|
||||
debugMqtt("Verbindung mit MQTT-Server konnte nicht herrgestellt werden.");
|
||||
debug("Verbindung mit MQTT-Server konnte nicht herrgestellt werden.");
|
||||
}
|
||||
connected = false;
|
||||
});
|
||||
|
||||
mqtt_client.on("error", function (error) {
|
||||
debugMqtt("Verbindung mit MQTT-Server konnte nicht herrgestellt werden.", error);
|
||||
debug("Verbindung mit MQTT-Server konnte nicht herrgestellt werden.", error);
|
||||
connected = false;
|
||||
});
|
||||
|
||||
@@ -82,33 +81,49 @@ mqtt_client.on('message', function (topic, message) {
|
||||
try {
|
||||
var cMessage = convertMessage(message);
|
||||
var topic = topic.split("/");
|
||||
var type = topic[1];
|
||||
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", cMessage);
|
||||
var device = new TuyaDevice(options);
|
||||
var commands = [
|
||||
"command",
|
||||
"color"
|
||||
]
|
||||
|
||||
if (exec == "command") {
|
||||
var status = topic[6];
|
||||
if (status == null) {
|
||||
device.switch(cMessage);
|
||||
} else {
|
||||
device.switch(status);
|
||||
}
|
||||
}
|
||||
if (exec == "color") {
|
||||
var color = message.toString();
|
||||
color = color.toLowerCase();
|
||||
debugColor("topic: ", topic);
|
||||
debugColor("onColor: ", color);
|
||||
device.setColor(color);
|
||||
if (type == "socket" || type == "lightbulb") {
|
||||
if (commands.includes(exec)) {
|
||||
var device = new TuyaDevice(options);
|
||||
device.then(function (params) {
|
||||
// wait for connection to Device and run commands
|
||||
debug("receive message", cMessage);
|
||||
var device = params.device;
|
||||
|
||||
if (exec == "command") {
|
||||
var status = topic[6];
|
||||
if (status == null) {
|
||||
device.switch(cMessage).then((data) => {
|
||||
debug("completed");
|
||||
});
|
||||
} else {
|
||||
device.switch(status).then((data) => {
|
||||
debug("completed");
|
||||
});
|
||||
}
|
||||
}
|
||||
if (exec == "color") {
|
||||
var color = message.toString();
|
||||
color = color.toLowerCase();
|
||||
debugColor("topic: ", topic);
|
||||
debugColor("onColor: ", color);
|
||||
device.setColor(color);
|
||||
}
|
||||
}).catch((err) => {
|
||||
debugError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -195,12 +210,14 @@ function publishDPS(device, dps) {
|
||||
*/
|
||||
TuyaDevice.onAll('data', function (data) {
|
||||
try {
|
||||
debugTuya('Data from device ' + this.type + ' :', data);
|
||||
var status = data.dps['1'];
|
||||
if (typeof status != "undefined") {
|
||||
publishStatus(this, bmap(status));
|
||||
if (typeof data.dps != "undefined") {
|
||||
debugTuya('Data from device ' + this.type + ' :', data);
|
||||
var status = data.dps['1'];
|
||||
if (typeof status != "undefined") {
|
||||
publishStatus(this, bmap(status));
|
||||
}
|
||||
publishDPS(this, data.dps);
|
||||
}
|
||||
publishDPS(this, data.dps);
|
||||
} catch (e) {
|
||||
debugError(e);
|
||||
}
|
||||
@@ -216,9 +233,9 @@ function MQTT_Tester() {
|
||||
if (mqtt_client.connected != connected) {
|
||||
connected = mqtt_client.connected;
|
||||
if (connected) {
|
||||
debugMqtt('MQTT-Server verbunden.');
|
||||
debug('MQTT-Server verbunden.');
|
||||
} else {
|
||||
debugMqtt('MQTT-Server nicht verbunden.');
|
||||
debug('MQTT-Server nicht verbunden.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user