mirror of
https://github.com/lehanspb/tuya-mqtt.git
synced 2025-12-16 17:54:36 +00:00
Update tuya-mqtt.js
This commit is contained in:
261
tuya-mqtt.js
261
tuya-mqtt.js
@@ -35,17 +35,22 @@ function getCommandFromMessage(_message) {
|
||||
let command = _message
|
||||
|
||||
if (command != "1" && command != "0" && isJsonString(command)) {
|
||||
debug("command is JSON");
|
||||
debug("Received command is JSON");
|
||||
command = JSON.parse(command);
|
||||
} else {
|
||||
if (command.toLowerCase() != "toggle") {
|
||||
// convert simple commands (on, off, 1, 0) to TuyAPI-Commands
|
||||
const convertString = command.toLowerCase() == "on" || command == "1" || command == 1 ? true : false;
|
||||
command = {
|
||||
set: convertString
|
||||
}
|
||||
} else {
|
||||
command = command.toLowerCase();
|
||||
switch(command.toLowerCase()) {
|
||||
case "on":
|
||||
case "off":
|
||||
case "0":
|
||||
case "1":
|
||||
// convert simple commands (on, off, 1, 0) to TuyAPI-Commands
|
||||
const convertString = command.toLowerCase() == "on" || command == "1" || command == 1 ? true : false;
|
||||
command = {
|
||||
set: convertString
|
||||
}
|
||||
break;
|
||||
default:
|
||||
command = command.toLowerCase();
|
||||
}
|
||||
}
|
||||
return command;
|
||||
@@ -77,16 +82,29 @@ function guessDeviceType(device, dps) {
|
||||
if (typeof dps['1'] === "boolean" && dps['2'] >= 0 && dps['2'] <= 255) {
|
||||
// A "dimmer" is a switch/light with brightness control only
|
||||
device.options.type = "dimmer"
|
||||
device.options.template =
|
||||
{
|
||||
"state": { "dpsKey": 1, "dpsType": "bool" },
|
||||
"brightness_state": { "dpsKey": 2, "dpsType": "int", "minVal": 0, "maxVal": 255 }
|
||||
}
|
||||
}
|
||||
} else if (keys === 1) {
|
||||
if (typeof dps['1'] === "boolean") {
|
||||
// If it only has one value and it's a boolean, it's probably a switch/socket
|
||||
device.options.type = "switch"
|
||||
device.options.template =
|
||||
{
|
||||
"state": { "dpsKey": 1, "dpsType": "bool" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!device.options.type) {
|
||||
device.options.type = "unknown"
|
||||
device.options.template =
|
||||
{
|
||||
"state": { "dpsKey": 1, "dpsType": "bool" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,31 +113,28 @@ function publishColorState(device, state) {
|
||||
}
|
||||
|
||||
function publishDeviceTopics(device, dps) {
|
||||
const baseTopic = CONFIG.topic + device.topicLevel
|
||||
let state
|
||||
let brightness_state
|
||||
switch (device.options.type) {
|
||||
case "switch":
|
||||
case "unknown":
|
||||
state = (dps['1']) ? 'ON' : 'OFF';
|
||||
topic = baseTopic+"/state"
|
||||
debugTuya("MQTT state ("+device.options.type+"): "+topic+" -> ", state);
|
||||
if (!device.options.template) {
|
||||
debugTuya ("No device template found!")
|
||||
return
|
||||
}
|
||||
const baseTopic = CONFIG.topic + device.topicLevel + "/"
|
||||
for (let stateTopic in device.options.template) {
|
||||
const template = device.options.template[stateTopic]
|
||||
const topic = baseTopic + stateTopic
|
||||
let state
|
||||
switch (template.dpsType) {
|
||||
case "bool":
|
||||
state = (dps[template.dpsKey]) ? 'ON' : 'OFF';
|
||||
break;
|
||||
case "int":
|
||||
state = (dps[template.dpsKey])
|
||||
state = (state > template.minVal && state < template.maxVal) ? state.toString() : ""
|
||||
break;
|
||||
}
|
||||
if (state) {
|
||||
debugTuya("MQTT "+device.options.type+" "+topic+" -> ", state);
|
||||
publishMQTT(topic, state);
|
||||
break;
|
||||
case "dimmer":
|
||||
if ('1' in dps) {
|
||||
state = (dps['1']) ? 'ON' : 'OFF';
|
||||
topic = baseTopic+"/state"
|
||||
debugTuya("MQTT state ("+device.options.type+"): "+topic+" -> ", state);
|
||||
publishMQTT(topic, state);
|
||||
}
|
||||
if ('2' in dps) {
|
||||
brightness_state = JSON.stringify(dps['2']);
|
||||
topic = baseTopic+"/brightness_state"
|
||||
debugTuya("MQTT brightness ("+device.options.type+"): "+topic+" -> ", brightness_state);
|
||||
publishMQTT(topic, brightness_state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +181,7 @@ function publishDPS(device, dps) {
|
||||
TuyaDevice.onAll('data', function (data) {
|
||||
try {
|
||||
if (typeof data.dps != "undefined") {
|
||||
debugTuya('Data from device ' + this.tuyID + ' :', data);
|
||||
debugTuya('Data from device Id ' + data.devId + ' ->', data.dps);
|
||||
publishDPS(this, data.dps);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -187,7 +202,7 @@ function sleep(sec) {
|
||||
}
|
||||
|
||||
function initTuyaDevices(tuyaDevices) {
|
||||
for (const tuyaDevice of tuyaDevices) {
|
||||
for (let tuyaDevice of tuyaDevices) {
|
||||
let options = {
|
||||
id: tuyaDevice.id,
|
||||
key: tuyaDevice.key
|
||||
@@ -205,6 +220,100 @@ function initTuyaDevices(tuyaDevices) {
|
||||
}
|
||||
}
|
||||
|
||||
// Process MQTT commands for all command topics at device level
|
||||
function processDeviceCommand(message, device, commandTopic) {
|
||||
let command = getCommandFromMessage(message);
|
||||
// If it's the color command topic handle it manually
|
||||
if (commandTopic === "color_command") {
|
||||
const color = message.toLowerCase();
|
||||
debugColor("Set color: ", color);
|
||||
device.setColor(color).then((data) => {
|
||||
debug("Set device color completed: ", data);
|
||||
});
|
||||
} else if (commandTopic === "command" && (command === "toggle" || command === "schema" )) {
|
||||
// Handle special commands "toggle" and "schema" to primary device command topic
|
||||
debug("Received command: ", command);
|
||||
switch(command) {
|
||||
case "toggle":
|
||||
device.switch(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
break;
|
||||
case "schema":
|
||||
// Trigger device schema to update state
|
||||
device.schema(command).then((data) => {
|
||||
debug("Get schema status command complete.");
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Recevied command on device topic level, check for matching device template
|
||||
// and process command accordingly
|
||||
const stateTopic = commandTopic.replace("command", "state")
|
||||
const template = device.options.template[stateTopic]
|
||||
if (template) {
|
||||
debug("Received device "+commandTopic.replace("_"," "), message);
|
||||
const tuyaCommand = new Object()
|
||||
tuyaCommand.dps = template.dpsKey
|
||||
switch (template.dpsType) {
|
||||
case "bool":
|
||||
if (command === "true") {
|
||||
tuyaCommand.set = true
|
||||
} else if (command === "false") {
|
||||
tuyaCommand.set = false
|
||||
} else if (typeof command.set === "boolean") {
|
||||
tuyaCommand.set = command.set
|
||||
} else {
|
||||
tuyaCommand.set = "!!!!!"
|
||||
}
|
||||
break;
|
||||
case "int":
|
||||
tuyaCommand.set = (command > template.minVal && command < template.maxVal ) ? parseInt(command) : "!!!!!"
|
||||
break;
|
||||
}
|
||||
if (tuyaCommand.set === "!!!!!") {
|
||||
debug("Received invalid value for ", commandTopic, ", value:", command)
|
||||
} else {
|
||||
device.set(tuyaCommand).then((data) => {
|
||||
debug("Set device "+commandTopic.replace("_"," ")+": ", data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
debug("Received unknown command topic for device: ", commandTopic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process raw Tuya JSON commands via DPS command topic
|
||||
function processDpsCommand(message, device) {
|
||||
if (isJsonString(message)) {
|
||||
const command = getCommandFromMessage(message);
|
||||
debug("Received command: ", command);
|
||||
device.set(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
} else {
|
||||
debug("DPS command topic requires Tuya style JSON value")
|
||||
}
|
||||
}
|
||||
|
||||
// Process text base Tuya command via DPS key command topics
|
||||
function processDpsKeyCommand(message, device, dpsKey) {
|
||||
if (isJsonString(message)) {
|
||||
debug("Individual DPS command topics do not accept JSON values")
|
||||
} else {
|
||||
const dpsMessage = parseDpsMessage(message)
|
||||
debug("Received command for DPS"+dpsKey+": ", message);
|
||||
const command = {
|
||||
dps: dpsKey,
|
||||
set: dpsMessage
|
||||
}
|
||||
device.set(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Main code function
|
||||
const main = async() => {
|
||||
let tuyaDevices
|
||||
@@ -272,91 +381,33 @@ const main = async() => {
|
||||
message = message.toString();
|
||||
const splitTopic = topic.split("/");
|
||||
const topicLength = splitTopic.length
|
||||
const action = splitTopic[topicLength - 1];
|
||||
const commandTopic = splitTopic[topicLength - 1];
|
||||
const options = {
|
||||
topicLevel: splitTopic[1]
|
||||
}
|
||||
|
||||
// If it looks like a valid command topic try to process it
|
||||
if (action.includes("command")) {
|
||||
if (commandTopic.includes("command")) {
|
||||
debug("Receive settings", JSON.stringify({
|
||||
topic: topic,
|
||||
action: action,
|
||||
message: message,
|
||||
topicLevel: options.topicLevel
|
||||
message: message
|
||||
}));
|
||||
|
||||
// Uses device topic level to find matching device
|
||||
var device = new TuyaDevice(options);
|
||||
|
||||
device.then(function (params) {
|
||||
var device = params.device;
|
||||
switch (action) {
|
||||
case "command":
|
||||
if (topicLength === 3) {
|
||||
const command = getCommandFromMessage(message);
|
||||
debug("Received command: ", command);
|
||||
if (command == "toggle") {
|
||||
device.switch(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
}
|
||||
if (command == "schema") {
|
||||
// Trigger device schema update to update state
|
||||
device.schema(command).then((data) => {
|
||||
});
|
||||
debug("Get schema status command complete");
|
||||
} else {
|
||||
device.set(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
}
|
||||
} else if (topicLength === 4) {
|
||||
if (isJsonString(message)) {
|
||||
const command = getCommandFromMessage(message);
|
||||
debug("Received command: ", command);
|
||||
device.set(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
} else {
|
||||
debug("DPS command topic requires Tuya style JSON value")
|
||||
}
|
||||
} else if (topicLength === 5) {
|
||||
if (isJsonString(message)) {
|
||||
debug("Individual DPS command topics require string value")
|
||||
} else {
|
||||
const dpsMessage = parseDpsMessage(message)
|
||||
debug("Received DPS "+splitTopic[topicLength-2]+" command: ", message);
|
||||
const command = {
|
||||
dps: splitTopic[topicLength-2],
|
||||
set: dpsMessage
|
||||
}
|
||||
device.set(command).then((data) => {
|
||||
debug("Set device status completed: ", data);
|
||||
});
|
||||
}
|
||||
}
|
||||
let device = params.device;
|
||||
switch (topicLength) {
|
||||
case 3:
|
||||
processDeviceCommand(message, device, commandTopic);
|
||||
break;
|
||||
case "color":
|
||||
const color = message.toLowerCase();
|
||||
debugColor("Set color: ", color);
|
||||
device.setColor(color).then((data) => {
|
||||
debug("Set device color completed: ", data);
|
||||
});
|
||||
case 4:
|
||||
processDpsCommand(message, device);
|
||||
break;
|
||||
case "brightness_command":
|
||||
if (message >= 0 && message <= 255) {
|
||||
const brightness = {
|
||||
dps: 2,
|
||||
set: parseInt(message)
|
||||
}
|
||||
debug("Set brighness: ", message)
|
||||
device.set(brightness).then((data) => {
|
||||
debug("Set device brightness completed: ",data);
|
||||
});
|
||||
} else {
|
||||
debug("Received invalid brightness value: " + message)
|
||||
}
|
||||
case 5:
|
||||
const dpsKey = splitTopic[topicLength-2]
|
||||
processDpsKeyCommand(message, device, dpsKey);
|
||||
break;
|
||||
}
|
||||
}).catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user