mirror of
https://github.com/lehanspb/tuya-mqtt.git
synced 2025-12-16 17:54:36 +00:00
update package and code cleanup
This commit is contained in:
1038
package-lock.json
generated
1038
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.3",
|
||||
"cron": "^1.4.1",
|
||||
"cron": "^1.5.0",
|
||||
"crypto": "^1.0.1",
|
||||
"debug": "^3.2.6",
|
||||
"mqtt": "^2.18.8",
|
||||
|
||||
100
tuya-mqtt-2.js
100
tuya-mqtt-2.js
@@ -1,100 +0,0 @@
|
||||
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();
|
||||
};
|
||||
190
tuya-mqtt.js
190
tuya-mqtt.js
@@ -1,143 +1,78 @@
|
||||
const mqtt = require('mqtt');
|
||||
const TuyaDevice = require('./tuyaapi-extended');
|
||||
const CronJob = require('cron').CronJob;
|
||||
const crypto = require('crypto');
|
||||
const debug = require('debug')('TuyAPI-mqtt');
|
||||
const autoUpdate = {};
|
||||
|
||||
/**
|
||||
* MQTT Settings
|
||||
*/
|
||||
var options = {
|
||||
clientId: 'tuya_mqtt',
|
||||
port: 1883,
|
||||
keepalive: 60
|
||||
};
|
||||
const client = mqtt.connect({
|
||||
host: 'localhost',
|
||||
port: options.port
|
||||
});
|
||||
const TuyaDevice = require('./tuya-device');
|
||||
const debug = require('debug')('tuya-mqtt');
|
||||
var cleanup = require('./cleanup').Cleanup(onExit);
|
||||
|
||||
function bmap(istate) {
|
||||
return istate ? 'ON' : "OFF";
|
||||
}
|
||||
|
||||
client.on('connect', function () {
|
||||
var topic = 'tuya/#';
|
||||
client.subscribe(topic);
|
||||
debug("MQTT Subscribed");
|
||||
updateDeviceStatus();
|
||||
})
|
||||
|
||||
function createHash(tuyaID, tuyaKey, tuyaIP) {
|
||||
try {
|
||||
return crypto.createHmac('sha256', "")
|
||||
.update(tuyaID + tuyaKey + tuyaIP)
|
||||
.digest('hex');
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
return tuyaID + tuyaKey + tuyaIP;
|
||||
function bmap(istate) {
|
||||
return istate ? 'ON' : "OFF";
|
||||
}
|
||||
|
||||
function isKnowDevice(tuyaID, tuyaKey, tuyaIP) {
|
||||
try {
|
||||
var isKnown = false;
|
||||
var searchKey = createHash(tuyaID, tuyaKey, tuyaIP);
|
||||
if (autoUpdate[searchKey] != undefined) {
|
||||
isKnown = true;
|
||||
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')
|
||||
}
|
||||
return isKnown;
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getKnownDevice(tuyaID, tuyaKey, tuyaIP) {
|
||||
try {
|
||||
var searchKey = createHash(tuyaID, tuyaKey, tuyaIP);
|
||||
return autoUpdate[searchKey];
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
function addDevice(device) {
|
||||
try {
|
||||
var infos = device.getDevice();
|
||||
var tuyaID = infos.id;
|
||||
var tuyaKey = infos.key;
|
||||
var tuyaIP = infos.ip;
|
||||
var key = createHash(tuyaID, tuyaKey, tuyaIP);
|
||||
autoUpdate[key] = device;
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
function createDevice(tuyaID, tuyaKey, tuyaIP, tuyaType) {
|
||||
try {
|
||||
if (tuyaID != undefined && tuyaKey != undefined) {
|
||||
var tuya = undefined;
|
||||
if (isKnowDevice(tuyaID, tuyaKey, tuyaIP)) {
|
||||
tuya = getKnownDevice(tuyaID, tuyaKey, tuyaIP);
|
||||
} else {
|
||||
var tuya = new TuyaDevice({
|
||||
id: tuyaID,
|
||||
key: tuyaKey,
|
||||
ip: tuyaIP,
|
||||
type: tuyaType
|
||||
});
|
||||
addDevice(tuya);
|
||||
}
|
||||
return tuya;
|
||||
}
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
client.on('message', function (topic, message) {
|
||||
mqtt_client.on('message', function (topic, message) {
|
||||
try {
|
||||
message = message.toString();
|
||||
message = message.toLowerCase();
|
||||
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 ((type == "socket" || type == "lightbulb") && exec == "command" && topic.length == 7) {
|
||||
var tuya = createDevice(topic[2], topic[3], topic[4], type);
|
||||
tuya.onoff(topic[6], function (status) {
|
||||
publishStatus(tuya, bmap(status));
|
||||
debug("Device status updated to: " + bmap(status));
|
||||
});
|
||||
}
|
||||
if (type == "lightbulb" && exec == "color" && topic.length == 6) {
|
||||
message = message.toString();
|
||||
message = message.toLowerCase();
|
||||
debug("Recevied color: " + message);
|
||||
var tuya = createDevice(topic[2], topic[3], topic[4], type);
|
||||
tuya.setColor(message, function (status) {
|
||||
publishStatus(tuya, bmap(status));
|
||||
debug("Color is updated: " + bmap(status));
|
||||
});
|
||||
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(tuya, status) {
|
||||
function publishStatus(device, status) {
|
||||
try {
|
||||
var device = tuya.getDevice();
|
||||
var type = device.type;
|
||||
var tuyaID = device.id;
|
||||
var tuyaKey = device.key;
|
||||
var tuyaIP = device.ip;
|
||||
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";
|
||||
client.publish(topic, status, {
|
||||
mqtt_client.publish(topic, status, {
|
||||
retain: true,
|
||||
qos: 2
|
||||
});
|
||||
@@ -150,19 +85,16 @@ function publishStatus(tuya, status) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateDeviceStatus() {
|
||||
try {
|
||||
Object.keys(autoUpdate).forEach(function (k) {
|
||||
var tuya = autoUpdate[k];
|
||||
tuya.getStatus(function (status) {
|
||||
publishStatus(tuya, bmap(status));
|
||||
})
|
||||
});
|
||||
} 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));
|
||||
});
|
||||
|
||||
new CronJob('0 */10 * * * *', function () {
|
||||
//updateDeviceStatus();
|
||||
}, null, true, 'America/Los_Angeles');
|
||||
// defines app specific callback...
|
||||
function onExit() {
|
||||
TuyaDevice.disconnectAll();
|
||||
};
|
||||
@@ -1,268 +0,0 @@
|
||||
const TuyaDevice = require('tuyapi');
|
||||
const TuyaColor = require('./tuya-color');
|
||||
|
||||
// Import packages
|
||||
const dgram = require('dgram');
|
||||
const net = require('net');
|
||||
const timeout = require('p-timeout');
|
||||
const retry = require('retry');
|
||||
const debug = require('debug')('TuyAPI-ext');
|
||||
|
||||
// Helpers
|
||||
const Cipher = require('tuyapi/lib/cipher');
|
||||
const Parser = require('tuyapi/lib/message-parser')
|
||||
|
||||
TuyaDevice.prototype.getDevice = function () {
|
||||
return this.device;
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.get = function (options) {
|
||||
// Set empty object as default
|
||||
options = options ? options : {};
|
||||
|
||||
const payload = {
|
||||
gwId: this.device.id,
|
||||
devId: this.device.id
|
||||
};
|
||||
|
||||
debug('Payload: ', payload);
|
||||
|
||||
// Create byte buffer
|
||||
const buffer = Parser.encode({
|
||||
data: payload,
|
||||
commandByte: '0a'
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._send(this.device.ip, buffer).then(data => {
|
||||
var dps = data.dps;
|
||||
if (options.schema === true) {
|
||||
resolve(data);
|
||||
} else if (options.dps) {
|
||||
resolve(dps[options.dps]);
|
||||
} else {
|
||||
if (dps != undefined && dps["1"] != undefined) {
|
||||
resolve(dps['1']);
|
||||
} else {
|
||||
resolve(dps);
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
TuyaDevice.prototype.set = function (options) {
|
||||
let dps = {};
|
||||
var count = Object.keys(options).length;
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
debug('Payload:', 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: '07'
|
||||
});
|
||||
|
||||
// Send request to change status
|
||||
return new Promise((resolve, reject) => {
|
||||
this._send(this.device.ip, buffer).then(() => {
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Sends a query to a device.
|
||||
// * @private
|
||||
// * @param {String} ip IP of device
|
||||
// * @param {Buffer} buffer buffer of data
|
||||
// * @returns {Promise<string>} returned data
|
||||
// */
|
||||
// TuyaDevice.prototype._sendUnwrapped = function (ip, buffer) {
|
||||
// debug('Sending this data: ', buffer.toString('hex'));
|
||||
|
||||
// const client = new net.Socket();
|
||||
|
||||
// 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();
|
||||
// });
|
||||
|
||||
// // Send data when connected
|
||||
// client.on('connect', () => {
|
||||
// debug('Socket connected.');
|
||||
|
||||
// // Remove connect timeout
|
||||
// client.setTimeout(0);
|
||||
|
||||
// // Transmit data
|
||||
// client.write(buffer);
|
||||
|
||||
// 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'));
|
||||
|
||||
// clearTimeout(this._sendTimeout);
|
||||
// client.destroy();
|
||||
|
||||
// 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);
|
||||
// });
|
||||
|
||||
// // 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);
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
|
||||
TuyaDevice.prototype.getStatus = function (callback) {
|
||||
var tuya = this;
|
||||
tuya.get().then(status => {
|
||||
debug('Current Status: ' + status);
|
||||
callback.call(this, status);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.setStatus = function (options, callback) {
|
||||
var tuya = this;
|
||||
tuya.set(options).then(result => {
|
||||
tuya.get().then(status => {
|
||||
debug('New status: ' + status);
|
||||
if (callback != undefined) {
|
||||
callback.call(this, status);
|
||||
} else {
|
||||
debug(status);
|
||||
}
|
||||
return;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.toggle = function (callback) {
|
||||
var tuya = this;
|
||||
//tuya.resolveId().then(() => {
|
||||
tuya.get().then(status => {
|
||||
tuya.setStatus({
|
||||
set: !status
|
||||
}, callback);
|
||||
});
|
||||
//});
|
||||
}
|
||||
|
||||
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.setColor = function (hexColor, callback) {
|
||||
debug("Set color to", hexColor);
|
||||
var tuya = this;
|
||||
var color = new TuyaColor(tuya);
|
||||
var dps = color.setColor(hexColor);
|
||||
|
||||
//tuya.resolveId().then(() => {
|
||||
tuya.get().then(status => {
|
||||
tuya.setStatus(dps, callback);
|
||||
});
|
||||
//});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.on = function (callback) {
|
||||
var tuya = this;
|
||||
//tuya.resolveId().then(() => {
|
||||
tuya.get().then(status => {
|
||||
tuya.setStatus({
|
||||
set: true
|
||||
}, callback);
|
||||
});
|
||||
//});
|
||||
}
|
||||
|
||||
TuyaDevice.prototype.off = function (callback) {
|
||||
debug("off: ");
|
||||
var tuya = this;
|
||||
//tuya.resolveId().then(() => {
|
||||
tuya.get().then(status => {
|
||||
tuya.setStatus({
|
||||
set: false
|
||||
}, callback);
|
||||
});
|
||||
//});
|
||||
}
|
||||
|
||||
module.exports = TuyaDevice;
|
||||
Reference in New Issue
Block a user