Files
tuya-mqtt/tuya-device.js
GadgetAngel 61433e74a9 This update allows for an essential TuyAPI command to be implemented via the tuya-mqtt.exe MQTT server. {"schema": true} is the ONLY COMMAND that the TuyAPI GET method implements.
{"schema": true} allows the user to establish that proper communications with the tuya device can occur WITHOUT actually changing the present STATE of the device.  This is the only command that will query the tuya device.

The current documentation says that you can query the tuya device over the "dps" TOPIC but from what I see the present state of the software does not force the tuya device to respond when using the "dps" TOPIC.

Since {"schema": true} is a command that forces a response from the tuya device, this command has been implemented under the "command" TOPIC. So "command" is the action and '{"schema": true}' becomes the command.  The response is returned just like all other commands.  I, use this command to guarantee communications has been established with the tuya device.  If this "schema" command fails, tuya-mqtt will indicate the result in the openhab.log file and then I, can find out what is physically wrong with the communications.  If this command fails the first time due to "socket" error and then goes through on the second attempt then I know that the error was due to TCP communications problem on initial startup.  This command helps as a work-a-round for the "ERROR: socket problem"
2019-04-20 09:29:01 -04:00

328 lines
9.5 KiB
JavaScript

const TuyAPI = require('tuyapi');
const TuyColor = require('./tuya-color');
const debug = require('debug')('TuyAPI:device');
const debugError = require('debug')('TuyAPI:device:error');
const debugColor = require('debug')('TuyAPI:device:color');
/**
*
var steckdose = new TuyaDevice({
id: '03200240600194781244',
key: 'b8bdebab418f5b55',
ip: '192.168.178.45',
type: "socket"
});
*/
// Helpers
const Parser = require('tuyapi/lib/message-parser');
/**
* Extends default TuyAPI-Class to add some more error handlers
*/
class CustomTuyAPI 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 = {};
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 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, callback) {
var device = this;
// Check for existing instance
if (existing = checkExisiting(options.id)) {
return new Promise((resolve, reject) => {
resolve({
status: "connected",
device: existing
});
});
}
if (!(this instanceof TuyaDevice)) {
return new TuyaDevice(options);
}
options.type = options.type || undefined;
this.type = options.type;
this.options = options;
Object.defineProperty(this, 'device', {
value: new CustomTuyAPI(JSON.parse(JSON.stringify(this.options)))
});
this.device.on('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);
}
});
devices.push(this);
// 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) {
var device = this;
var e = events[name] || [];
e.forEach(event => {
event.call(device, argument);
});
}
TuyaDevice.prototype.on = function (name, callback) {
if (!this.connected) return;
var device = this;
this.device.on(name, function () {
callback.apply(device, arguments);
});
}
TuyaDevice.prototype.find = function () {
return this.device.find();
}
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);
});
});
});
}
TuyaDevice.prototype.switch = function (newStatus, callback) {
if (!this.connected) return;
newStatus = newStatus.toLowerCase();
if (newStatus == "on") {
return this.switchOn(callback);
}
if (newStatus == "off") {
return this.switchOff(callback);
}
if (newStatus == "toggle") {
return this.toggle(callback);
}
}
TuyaDevice.prototype.switchOn = function () {
if (!this.connected) return;
debug("switch -> ON");
return this.set({
set: true
});
}
TuyaDevice.prototype.switchOff = function () {
if (!this.connected) return;
debug("switch -> OFF");
return this.set({
set: false
});
}
TuyaDevice.prototype.toggle = function () {
if (!this.connected) return;
return new Promise((resolve, reject) => {
this.get().then((status) => {
debug("toogle state", status);
this.set({
set: !status
});
});
});
}
TuyaDevice.prototype.schema = function(obj){
return this.get(obj).then((status) => {
debug("get", obj);
});
}
TuyaDevice.prototype.setColor = function (hexColor) {
if (!this.connected) return;
debugColor("Set color to: ", hexColor);
var tuya = this.device;
var color = new TuyColor(tuya);
var dps = color.setColor(hexColor);
debugColor("dps values:", dps);
return this.set({
multiple: true,
data: dps
});
}
TuyaDevice.prototype.connect = function (callback) {
debug("Connect to TuyAPI Device");
return this.device.connect(callback);
}
TuyaDevice.prototype.disconnect = function (callback) {
debug("Disconnect from TuyAPI Device");
return 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;