Comments (9)
I’ve done this manually in node-red and really struggled... I found a HeaterCooler service to map better than a ThermostatService for what most zigbee TRV’s expose.
It’s certainly doable thoigh.
from homebridge-z2m.
this is the API doc I meant
https://developers.homebridge.io/#/service/Thermostat
from homebridge-z2m.
I'm currently focusing on getting a new major version ready with several internal improvements (see #18).
I've had a brief look into thermostats in general. I think adding support for them should not be too difficult, but I have yet to look at the specifics.
Biggest challenge is probably testing it, as I don't have a Zigbee thermostat myself.
I'll keep this issue in the "backlog" and hopefully I'll be able to get to it in a couple of weeks.
from homebridge-z2m.
Cool
I got them so I can test if that helps :)
from homebridge-z2m.
@jhm47 Can you already post the following information?
- Entry for this device from
zigbee2mqtt/bridge/info/devices
(you'll need a recent version of zigbee2mqtt for this) - Some MQTT messages of status updates from this device
This will help in writing some tests and writing the implementation, once I get to it.
from homebridge-z2m.
from homebridge-z2m.
@sjorge As more people are requesting support for TRVs and other climate devices, I wonder if you would be able to share how you've mapped your devices (which properties map to which characteristics).
If I have a look at devices.js
, at first glance, I would think most of them can be mapped to a Thermostat. With the Heater Cooler I'm missing the setpoint (Target Temperature). Of course I can just add the characteristic, but it's not part of the "default" set of characteristics specified for the Heater Cooler.
from homebridge-z2m.
I'm using node-red, but the characteristics should be the same looks like I ended up switching back to Thermostat on the latest version of my subflow.
{
"CurrentTemperature": true,
"TargetTemperature": {
"minValue": 8,
"maxValue": 30,
"minStep": 0.5
},
"CurrentHeatingCoolingState": true,
"TargetHeatingCoolingState": {
"validValues": [
1
]
}
}
Here is the function that does my mapping in node red,
/*
* processor
* ----------------------------------------------------
* A ticker is used to trigger the main event loop.
* The ticker sets the pace of handling the flow state.
*/
/*
* homekit mappings
* ----------------------------------------------------
* Active: 0=off, 1=heat, 2=cool, 3=auto
* TargetHeaterCoolerState: 0=auto, 1=heat, 2=cool
*
* zigbee mappings (TUYA)
* ----------------------------------------------------
* system_mode: off, auto, manual, boost
* child_lock (get): LOCKED, UNLOCKED
* child_lock (set): LOCK, UNLOCK
* auto_lock (set): AUTO, MANUAL
* position: 0-100 (valve open %) [??? missing]
*
* zigbee mappings (eurotronic)
* ----------------------------------------------------
* system_mode: off, heat (boost), auto
* eurotronic_host_flags.child_protection: true or false
* pi_heating_demand: 0-100 (valve open %)
*
* zigbee mappings (ViCare)
* ----------------------------------------------------
* pi_heating_demand: 0-5 (0 closed, 1-5 ???) -- REMOVED
*
*/
// variables
var state_base = `$parent.${env.get("MQTT_PREFIX")}/${env.get("MQTT_DEVICE")}`;
var sync_timeout = 5000;
var brand_supported = ["TUYA", "EUROTRONIC", "VICARE"];
var brand = (env.get("BRAND")||"EUROTRONIC").toUpperCase();
var low_battery = env.get("BATTERY_LOW_PCT")||0;
var ret = {
"continue": {"reset": true},
"mqtt": null,
"homekit_thermostat": null,
"homekit_battery": null,
};
// helpers functions
function stateGet(state_name, state_default=null, persistent=true) {
return flow.get(`${state_base}/${state_name}`, (persistent ? "persistent" : null))||state_default;
}
function stateSet(state_name, state, persistent=true) {
flow.set(`${state_base}/${state_name}`, state, (persistent ? "persistent" : null));
}
function stateGetAndSet(state_name, state, state_default=null, persistent=true) {
var ret_state = stateGet(state_name, state_default, persistent);
stateSet(state_name, state, persistent);
return ret_state;
}
function homekitThermostatPublish(ret, prop, value) {
// initialize ret.homekit_thermostat
if (ret.homekit_thermostat === null) {
ret.homekit_thermostat = {
"topic": `${env.get("MQTT_PREFIX")}/${env.get("MQTT_DEVICE")}`,
"payload": {},
};
}
// update payload
ret.homekit_thermostat.payload[prop] = value;
}
function homekitBatteryPublish(ret, prop, value) {
// initialize ret.homekit_battery
if (ret.homekit_battery === null) {
ret.homekit_battery = {
"topic": `${env.get("MQTT_PREFIX")}/${env.get("MQTT_DEVICE")}`,
"payload": {},
};
}
// update payload
ret.homekit_battery.payload[prop] = value;
}
function mqttPublish(ret, prop, value) {
// initialize ret.homekit
if (ret.mqtt === null) {
ret.mqtt = {
"topic": `${env.get("MQTT_PREFIX")}/${env.get("MQTT_DEVICE")}/set`,
"payload": {},
};
}
// update payload
ret.mqtt.payload[prop] = value;
}
function timestamp() {
return Math.round((new Date()).getTime());
}
// main logic controller
if (!brand_supported.includes(brand)) {
node.error(`Unsupported TRV brand ${brand}!`);
return;
} else if (stateGet("sync", null, false) === null) {
stateSet("queue_mqtt", stateGet("state", {}));
stateSet("sync", (timestamp() - sync_timeout), false);
}
if (Object.keys(stateGet("queue_homekit", {})).length > 0) {
// process homekit change queue
var homekit = stateGetAndSet("queue_homekit", {}, {});
// convert homekit to mqtt
for (var hp in homekit) {
var hv = homekit[hp];
switch(hp) {
case "Active":
if (hv === 0) {
switch(brand) {
case "TUYA":
case "EUROTRONIC":
mqttPublish(ret, "system_mode", "off");
break;
}
} else {
switch(brand) {
case "TUYA":
mqttPublish(ret, "system_mode", "manual");
break;
case "EUROTRONIC":
mqttPublish(ret, "system_mode", "auto");
break;
}
}
break;
case "TargetTemperature":
case "HeatingThresholdTemperature":
switch(brand) {
case "TUYA":
mqttPublish(ret, "current_heating_setpoint", Math.max(Math.min(hv, 30), 5));
break;
case "EUROTRONIC":
mqttPublish(ret, "current_heating_setpoint", Math.max(Math.min(hv, 30), 10));
break;
case "VICARE":
mqttPublish(ret, "occupied_heating_setpoint", Math.max(Math.min(hv, 30), 8));
break;
}
break;
case "LockPhysicalControls":
switch(brand) {
case "TUYA":
mqttPublish(ret, "child_lock", ((hv == 1) ? "LOCK" : "UNLOCK"));
mqttPublish(ret, "auto_lock", ((hv == 1) ? "AUTO" : "MANUAL"));
break;
case "EUROTRONIC":
mqttPublish(ret, "eurotronic_host_flags", {"child_protection": ((hv == 1) ? true : false)});
break;
}
break;
case "TemperatureDisplayUnits":
switch(brand) {
case "VICARE":
homekitThermostatPublish(ret, "TemperatureDisplayUnits", 0);
break;
}
break;
}
}
// update cache
if (ret.mqtt !== null) {
stateSet("sync", timestamp(), false);
stateSet("state",
Object.assign(stateGet("state", {}, true), ret.mqtt.payload));
}
} else if (
(Object.keys(stateGet("queue_mqtt", {})).length > 0) &&
((timestamp() - stateGet("sync", 0, false)) >= sync_timeout)
) {
// process mqtt change queue
var mqtt = stateGetAndSet("queue_mqtt", {}, {});
if (!mqtt.hasOwnProperty("available") || mqtt.available) {
// convert mqtt to homekit
for (var mp in mqtt) {
var mv = mqtt[mp];
switch(mp) {
case "available":
delete mqtt.available;
break;
case "local_temperature":
homekitThermostatPublish(ret, "CurrentTemperature", mv);
// NOTE: TuYa and ViCare lack pi_heating_demand
if (["VICARE", "TUYA"].includes(brand)) {
var temp_target = 0.0;
var temp_actual = 0.0;
if (mqtt.current_heating_setpoint) {
temp_target = parseFloat(mqtt.current_heating_setpoint);
} else if (stateGet("state", {}).current_heating_setpoint) {
temp_target = parseFloat(stateGet("state", {}).current_heating_setpoint);
}
temp_actual = parseFloat(mv);
switch(brand) {
case "VICARE":
homekitThermostatPublish(ret, "CurrentHeatingCoolingState", ((temp_actual >= temp_target)? 0 : 1));
break;
case "TUYA":
homekitThermostatPublish(ret, "CurrentHeaterCoolerState", ((temp_actual >= temp_target) ? 1 : 2));
break;
}
}
break;
case "occupied_heating_setpoint":
case "current_heating_setpoint":
switch(brand) {
case "TUYA":
homekitThermostatPublish(ret, "HeatingThresholdTemperature", Math.max(Math.min(mv, 30), 5));
break;
case "EUROTRONIC":
homekitThermostatPublish(ret, "HeatingThresholdTemperature", Math.max(Math.min(mv, 30), 10));
break;
case "VICARE":
homekitThermostatPublish(ret, "TargetTemperature", Math.max(Math.min(mv, 30), 8));
break;
}
break;
case "system_mode":
switch(brand) {
case "VICARE":
// NOTE: not mapped for thermostat service
break;
default:
homekitThermostatPublish(ret, "Active", ((mv == "off") ? 0 : 1));
homekitThermostatPublish(ret, "TargetHeaterCoolerState", 1);
break;
}
break;
case "pi_heating_demand":
case "position":
switch(brand) {
case "VICARE":
homekitThermostatPublish(ret, "CurrentHeatingCoolingState", ((mv === 0) ? 0 : 1));
break;
default:
homekitThermostatPublish(ret, "CurrentHeaterCoolerState", ((mv === 0) ? 1 : 2));
break;
}
break;
case "child_lock":
homekitThermostatPublish(ret, "LockPhysicalControls", ((mv == "LOCKED") ? 1 : 0));
break;
case "eurotronic_host_flags":
homekitThermostatPublish(ret, "LockPhysicalControls", mv.child_protection ? 1 : 0);
break;
case "battery":
homekitBatteryPublish(ret, "BatteryLevel", mv);
if (low_battery !== 0) {
homekitBatteryPublish(ret, "StatusLowBattery", (mv <= low_battery ? 1 : 0));
}
break;
case "battery_low":
if (low_battery === 0) {
homekitBatteryPublish(ret, "StatusLowBattery", (mv ? 1 : 0));
}
break;
}
}
// always mark 'ViCare' as active
if (brand == 'VICARE') {
homekitThermostatPublish(ret, "TargetHeatingCoolingState", 1);
}
// update cache
stateSet("state",
Object.assign(stateGet("state", {}), mqtt));
} else {
// mark dev offline
switch(brand) {
case "VICARE":
homekitThermostatPublish(ret, "TargetHeatingCoolingState", "NO_RESPONSE");
break;
default:
homekitThermostatPublish(ret, "Active", "NO_RESPONSE");
break;
}
}
}
// publish messages
return [
ret.continue,
ret.mqtt,
ret.homekit_thermostat,
ret.homekit_battery,
];
Thermostats/TRV are one of my most hated things in zigbee2mqtt, half follow the zigbee mapping (IMHO, the correct way) other follow the hassio mapping of certain things like system_mode. It's a royal mess :(
from homebridge-z2m.
Opened up #40 to group all the individual request for thermostat/TRV support in one place, as these will most likely be handled by the same code.
Because of that I'm closing this issue. Updates/discussion should be done in this new issue.
from homebridge-z2m.
Related Issues (20)
- [Bug] Min brightness ignored - Lonsonho QS-Zigbee-D02-TRIAC-L HOT 17
- IKEA Tradfri E1812 Action always reset HOT 2
- Occasional No Response after performing action (switch) on homekit HOT 1
- [Device] Niko Wireless Zigbee Switch (battery, single button) HOT 4
- [Bug] Failed to process MQTT message on 'zigbee2mqtt/bridge/devices'. (Maybe check the MQTT version?) HOT 10
- [Bug] problem after upgrading zigbee2mqtt to 1.35 HOT 37
- [Bug] HOT 1
- CI: set-output is deprecated
- [Bug] TypeError: Cannot read properties of undefined (reading 'getCharacteristic') HOT 8
- Ikea SOMRIG Switch activities not reaching Homebridge [Bug] HOT 5
- [Bug] Can not get new devices into HomeKit after upgrading to 1.35 and 1.9.3 HOT 2
- [Bug] plugin crashes HOT 4
- [Device] Vimar roller shutter HOT 1
- [device support] Frient EMIZB-141 HOT 7
- [Bug] The BIG scene in Homekit doesn't work completely. HOT 23
- [Device] Aqara ZNLDP13LM - full brightness instead of previous when switched ON via Home App HOT 6
- [Bug] Tuya button not working in Homekit - Tuya SH-SC07 HOT 13
- [Bug] Specific grouped devices with scenes In z2m do not survive restarts HOT 8
- [Bug] Running the Mac OS - Homebridge Zigbee2MQTT - doesn't really start up when the Mac OS boots up. HOT 9
- [Feature] HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from homebridge-z2m.