The beauty of mqtt-ad is that you can build many different interfaces and give each their own HW in domoticz. Very flexible. easy to change/adapt and all async so no devices will lock domotics causing the dreaded unresponsiveness messages.
It does require a bit work to define every device but, once done, it's a simple thing to operate. The only downside of node red is that it's not easy to do versioning on and node red is susceptible to installation/changes that may render it unfunctional (crash, lock). Make sure you have a decent backup before making substantial changes. I learnt the hard way
So now for the integration, I will use an example that I have built (well rigged) for Tado but the principle is the same for any other node red library:
Starup function:
1) you define the devices for which autodiscovery messages need to be sent to domoticz, all in one array
2) every type of device has its own function to create these messages
3) for all array items, messages are created and sent to a domoticz mqtt interface where these messages are sent as persistent
here's the code that goes with that:
Code: Select all
let discovery_topic = "tado_devs";
let root_topic = "tado";
context.flow.tadosetpoint = 15; // Thermostat setpoint
context.flow.presence="Home";
context.flow.tadoonoff="On";
context.flow.tadohum=50;
context.flow.tadotemp = 21;
let pobject = [
{ "WK Thermostaat Setpoint": ["climate", "wk_tado_setpoint", "0x9990000001", "C", "14|30", 1] },
{ "WK Thermostaat Presence": ["select", "wk_tado_presence", "0x9990000002", "Home|Away|Auto", "", 0] },
{ "WK Thermostaat State": ["switch", "wk_tado_onoff", "0x9990000003", "On", "Off", 0] },
{ "WK Thermostaat Resume Schedule": ["switch", "wk_tado_resume", "0x9990000004", 0] },
{ "WK Thermostaat Temp": ["sensor", "wk_tado_temp", "0x9990000005", "\u00b0c", "", 0] },
{ "WK Thermostaat Hum": ["sensor", "wk_tado_hum", "0x9990000006", "%", "", 0] },
];
function createBinaryObject(root, name, varname, unique_id, payload_on = "ON", payload_off = "OFF")
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["payload_on"] = payload_on;
vjson["payload_off"] = payload_off;
vjson["uniq_id"] = unique_id;
vjson["val_tpl"] = "{{ value_json." + varname + " }}";
return vjson;
}
function createButtonObject(root, name, varname, unique_id, payload_on = "ON")
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["payload_on"] = payload_on;
vjson["cmd_t"] = "~/set/" + varname;
vjson["uniq_id"] = unique_id;
vjson["pl_prs"] = "On";
vjson["val_tpl"] = "{{ value_json." + varname + " }}";
return vjson;
}
function createLightObject(root, name, varname, unique_id, payload_on = "ON", payload_off = "OFF")
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["payload_on"] = payload_on;
vjson["payload_off"] = payload_off;
vjson["cmd_t"] = "~/set/" + varname;
vjson["uniq_id"] = unique_id;
vjson["val_tpl"] = "{{ value_json." + varname + " }}";
return vjson;
}
function createSelectObject(root, name, varname, unique_id, select_options)
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["options"] = [];
select_options.split("|").forEach(function (item) {
vjson["options"].push(item);
});
vjson["cmd_t"] = "~/set/" + varname;
vjson["uniq_id"] = unique_id;
vjson["val_tpl"] = "{{ value_json." + varname + " }}";
return vjson;
}
function createNumberObject(root, name, varname, unique_id, step = 1)
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["step"] = step;
vjson["cmd_t"] = "~/set/" + varname;
vjson["uniq_id"] = unique_id;
vjson["val_tpl"] = "{{ value_json." + varname + " }}";
return vjson;
}
function createSensorObject(root, name, varname, unique_id, unit_of_measurement)
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["uniq_id"] = unique_id;
vjson["unit_of_meas"] = unit_of_measurement;
vjson["val_tpl"] = "{{ value_json." + varname + " }}";
return vjson
}
function createClimateObject(root, name, varname, unique_id, unit_of_measurement, min_value = 6.5, max_value = 35.0, step_size = 1.0)
{
let device_identifier = unique_id;
if (device_identifier.indexOf("_") != -1)
{
device_identifier = unique_id.split("_")[0];
}
let vjson = {
"temp_stat_t": "~/" + varname + "/state",
"dev": {
"manufacturer": "PA1DVB",
"ids": []
}
};
vjson.dev.ids.push(device_identifier);
vjson["~"] = root;
vjson["name"] = name;
vjson["uniq_id"] = unique_id;
vjson["temp_unit"] = unit_of_measurement;
vjson["temp_cmd_t"] = "~/set/" + varname;
vjson["temp_stat_tpl"] = "{{ value_json." + varname + " }}";
vjson["temp_step"] = step_size;
vjson["min_temp"] = min_value;
vjson["max_temp"] = max_value;
return vjson
}
node.log("Flow Initialisation");
pobject.forEach((vvar) => {
let name = Object.keys(vvar)[0];
let vobj = vvar[Object.keys(vvar)];
let object_type = vobj[0]
let varname = vobj[1]
let unique_id = vobj[2]
let jobj = null
if (object_type == "light") {
let payload_on = vobj[3]
let payload_off = vobj[4]
jobj = createLightObject(root_topic, name, varname, unique_id, payload_on, payload_off);
}
else if(object_type == "button") {
let payload_on = vobj[3]
jobj = createButtonObject(root_topic, name, varname, unique_id, payload_on);
}
else if (object_type == "switch") {
let payload_on = vobj[3]
let payload_off = vobj[4]
jobj = createLightObject(root_topic, name, varname, unique_id, payload_on, payload_off);
}
else if (object_type == "binary_sensor") {
let payload_on = vobj[3]
let payload_off = vobj[4]
jobj = createBinaryObject(root_topic, name, varname, unique_id, payload_on, payload_off);
}
else if (object_type == "select") {
let soptions = vobj[3]
jobj = createSelectObject(root_topic, name, varname, unique_id, soptions);
}
else if (object_type == "number") {
let step = vobj[5]
jobj = createNumberObject(root_topic, name, varname, unique_id, step);
}
else if (object_type == "sensor") {
let unit_of_measurement = vobj[3]
jobj = createSensorObject(root_topic, name, varname, unique_id, unit_of_measurement);
}
else if (object_type == "climate") {
let unit_of_measurement = vobj[3]
let minmax = vobj[4];
let step_size = vobj[5]
let min_value = 6.5;
let max_value = 35;
let sresults = minmax.split("|");
if (sresults.length == 2) {
min_value = parseFloat(sresults[0]);
max_value = parseFloat(sresults[1]);
}
jobj = createClimateObject(root_topic, name, varname, unique_id, unit_of_measurement, min_value, max_value, step_size);
}
if (jobj != null) {
if (unique_id.indexOf("_") != -1)
{
unique_id = unique_id.split("_")[0];
}
let dtopic = discovery_topic + "/" + object_type + "/" + unique_id + "/" + varname + "/config"
//node.log("publishing config ->: " + dtopic);
msg.payload = jobj;
msg.topic = dtopic;
msg.retain = true;
node.send(msg);
}
});
return null;
All the code above gets put in a function node in node red (in my case called "startup function") and triggered on start (so when when the flow in node red starts, it sends the device discovery messages).
Next is the handling of updates. For the tado example there is an update side where at specific intervals, devices are pinged from node red (setpoint, temperature, home/away, ..) and updates sent to domoticz. An example of reporting the setpoint state would be (function called Report setpoint state):
Code: Select all
msg.topic="tado/wk_tado_setpoint/state";
msg.payload = context.flow.tadosetpoint;
return msg;Nowe we are sending updates but we still need to receive messages from domoticz (like change setpoint). That gets done in a mqtt node that listens on the topic tado/set/wk_tado_setpoint which is what the autodiscovery message tells domoticz to send updates on. This node triggers a function that does tha Tado api call and then reports the result back to domoticz (without confirmation, domoticz does not assume it happens and will not change the state so don't forget to report every change back).
Function save setpoint:
Code: Select all
context.flow.tadosetpoint=msg.payload;
let setpoint=msg.payload;
//return msg;
return {
"apiCall": "manualControl",
"home_id": 1906297,
"room_id": 2,
"power": "ON",
"termination": "MANUAL",
"temperature": setpoint
}My plan is to build a wiki page for it where the flow itself is stored so stay tuned.