node-red-contrib-tuya-smart-device
Version:
A node-red module to interact with the tuya smart devices
298 lines (290 loc) • 10.3 kB
JavaScript
const TuyaDevice = require('tuyapi');
const packageInfo = require('../package.json');
const utils = require('./utils');
module.exports = function (RED) {
function TuyaSmartDeviceSelfNode(config) {
RED.nodes.createNode(this, config);
let node = this;
let shouldSubscribeData = true;
let shouldSubscribeRefreshData = true;
this.name = config.name;
this.eventMode = config.eventMode || 'event-both';
this.logLevel = config.logLevel || 'log-level-debug';
const logLevelNum = utils.UI_LOG_LEVELS[this.logLevel] || 3; // default incase of any error is debug
node.log(`Setting custom log level as ${this.logLevel}`);
this.logger = utils.createLogger(this, logLevelNum);
this.operations = [];
if (this.eventMode == 'event-data') {
shouldSubscribeData = true;
shouldSubscribeRefreshData = false;
} else if (this.eventMode == 'event-dp-refresh') {
shouldSubscribeData = false;
shouldSubscribeRefreshData = true;
} else {
// both case or default case
shouldSubscribeData = true;
shouldSubscribeRefreshData = true;
}
node.on('input', function (msg) {
let requestID = new Date().getTime();
try {
let operation = msg.payload.operation || 'SET';
delete msg.payload.operation;
node.logger.log(
`[${requestID}] recieved data on input : ${JSON.stringify({
...msg,
moduleVersion: packageInfo.version,
})}`
);
// Initiate the device connection and then send the payload
node.operations.push(1);
const tuyaProtocolVersion =
msg.payload.version == null ||
typeof msg.payload.version == 'undefined' ||
msg.payload.version.trim() == '' ||
isNaN(msg.payload.version)
? '3.1'
: msg.payload.version.trim();
const connectionParams = {
id: msg.payload.deviceVirtualId,
key: msg.payload.deviceKey,
ip: msg.payload.deviceIp,
issueGetOnConnect: false,
nullPayloadOnJSONError: false,
version: tuyaProtocolVersion,
};
node.logger.debug(
`Connecting to tuya with params : ${JSON.stringify(connectionParams)}`
);
let tuyaDevice = new TuyaDevice(connectionParams);
tuyaDevice.on('disconnected', () => {
node.logger.log(`[${requestID}] Disconnected from tuyaDevice.`);
node.operations.pop();
if (node.operations.length === 0) {
// Show the disconnected status if there is no more active connections.
setStatusDisconnected();
}
});
tuyaDevice.on('error', (error) => {
node.operations.pop();
setStatusOnError(error, requestID, 'Error', {
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
});
});
tuyaDevice.on('connected', () => {
try {
node.logger.log(
`[${requestID}] Connected to device! ${msg.payload.deviceVirtualId}`
);
setStatusConnected();
switch (operation) {
case 'SET':
node.logger.debug(
`[${requestID}] sending command SET : ${JSON.stringify(
msg.payload.payload
)}`
);
tuyaDevice.set(msg.payload.payload).catch((error) => {
setStatusOnError(
error.message,
requestID,
'Uncaught error : ' + error.message,
{
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
}
);
});
break;
default:
node.logger.error(
`[${requestID}] Invalid operation ${operation}`
);
}
} catch (error) {
setStatusOnError(
error.message,
requestID,
'Uncaught error : ' + error.message,
{
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
}
);
}
});
if (shouldSubscribeRefreshData) {
tuyaDevice.on('dp-refresh', (data) => {
try {
node.logger.debug(
`[${requestID}] Data from device [event:dp-refresh]: ${JSON.stringify(
data
)}`
);
tuyaDevice.disconnect();
node.send({
payload: {
data: data,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceName: msg.payload.deviceName,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
});
} catch (error) {
setStatusOnError(
error.message,
requestID,
'Uncaught error : ' + error.message,
{
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
}
);
}
});
}
if (shouldSubscribeData) {
tuyaDevice.on('data', (data) => {
try {
node.logger.debug(
`[${requestID}] Data from device [event:data]: ${JSON.stringify(
data
)}`
);
tuyaDevice.disconnect();
node.send({
payload: {
data: data,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceName: msg.payload.deviceName,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
});
} catch (error) {
setStatusOnError(
error.message,
requestID,
'Uncaught error : ' + error.message,
{
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
}
);
}
});
}
let findDevice = () => {
try {
setStatusConnecting();
node.logger.debug(`[${requestID}] initiating the find command`);
tuyaDevice
.find({})
.then(() => {
// Connect to device
tuyaDevice.connect();
})
.catch((e) => {
// We need to retry
setStatusOnError(e.message, requestID, "Can't find device", {
context: {
message: e,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
},
});
node.logger.error(`[${requestID}] Cannot find the device`);
//setTimeout(findDevice, 1000);
});
} catch (error) {
setStatusOnError(
error.message,
requestID,
'Uncaught error : ' + error.message,
{
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
requestID: requestID,
},
}
);
}
};
findDevice();
} catch (error) {
console.error('Uncaught exception occured ! ', error);
setStatusOnError(
error.message,
requestID,
'Uncaught error : ' + error.message,
{
context: {
message: error,
deviceVirtualId: msg.payload.deviceVirtualId,
deviceKey: msg.payload.deviceKey,
deviceIp: msg.payload.deviceIp,
},
}
);
}
});
let setStatusConnecting = function () {
return node.status({ fill: 'yellow', shape: 'ring', text: 'connecting' });
};
let setStatusConnected = function () {
return node.status({ fill: 'green', shape: 'ring', text: 'connected' });
};
let setStatusDisconnected = function () {
return node.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
};
var setStatusOnError = function (
errorText,
requestID,
errorShortText = 'error',
data
) {
node.logger.error(`[${requestID}] An error had occured : ${errorText}`);
return node.status({ fill: 'red', shape: 'ring', text: errorShortText });
};
node.on('close', function () {
// tidy up any state
// clearInterval(int);
//shouldTryReconnect = false;
//tuyaDevice.disconnect();
});
}
RED.nodes.registerType('tuya-smart-device-generic', TuyaSmartDeviceSelfNode);
};