Couldn’t find the guide on how to intergrate Eurotronic SPZB0001 via zigbee2mqtt with OpenHAB so here is my way of doing it.
things/mqtt.thing
Thing mqtt:topic:thermostat4 "Eurotronic Thermostat" @ "Office" {
Channels:
Type number : local_temperature "Local Temperature" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.local_temperature" ]
Type number : current_heating_setpoint "current_heating_setpoint" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.current_heating_setpoint",
commandTopic="zigbee2mqtt/thermostat4/set", formatBeforePublish="{ \"current_heating_setpoint\": %s }"]
Type switch : mirror_display "Mirror Display" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.eurotronic_host_flags.mirror_display", on="true", off="false",
commandTopic="zigbee2mqtt/thermostat4/set", transformationPatternOut="JS:eurotronic_host_flags-mirror_display.js", on="true", off="false"]
Type switch : boost "Boost" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.eurotronic_host_flags.boost", on="true", off="false",
commandTopic="zigbee2mqtt/thermostat4/set", transformationPatternOut="JS:eurotronic_host_flags-boost.js", on="true", off="false"]
Type switch : window_open "Window Open" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.eurotronic_host_flags.window_open", on="true", off="false",
commandTopic="zigbee2mqtt/thermostat4/set", transformationPatternOut="JS:eurotronic_host_flags-window_open.js", on="true", off="false"]
Type switch : child_protection "Child Protecton" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.eurotronic_host_flags.child_protection", on="true", off="false",
commandTopic="zigbee2mqtt/thermostat4/set", transformationPatternOut="JS:eurotronic_host_flags-child_protection.js",on="true", off="false"]
Type string : system_mode "System Mode" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.system_mode",
commandTopic="zigbee2mqtt/thermostat4/set", formatBeforePublish="{ \"system_mode\": \"%s\" }"]
Type number : batt "Batt" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.battery" ]
Type number : link "Link" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.linkquality" ]
Type number : lastseen_epoch "LastSeen" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.last_seen" ]
Type number : pi_heating_demand "pi_heating_demand" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.pi_heating_demand" ]
Type number : eurotronic_error_status "eurotronic_error_status" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.eurotronic_error_status" ]
// Type number : eurotronic_system_mode "Eurotronic System Mode" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.eurotronic_system_mode" ]
// Type number : occupied_heating_setpoint "occupied_heating_setpoint" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.occupied_heating_setpoint" ]
// Type number : unoccupied_heating_setpoint "unoccupied_heating_setpoint" [ stateTopic="zigbee2mqtt/thermostat4", transformationPattern="JSONPATH:$.unoccupied_heating_setpoint" ]
There is a problem with using formamtBeforePublish outgoing transformation in OH 2.5.1 with switches, as discussed here HERE. So we have to use JS to form the correct outgoing JSON when setting the extra eurotronic flags.
transform/eurotronic_host_flags-boost.js
(function(flag) {
if (flag == 'true') {
var data = '{"eurotronic_host_flags": {"boost": true}}';
} else if (flag == 'false') {
var data = '{"eurotronic_host_flags": {"boost": false}}';
}
return data;
})(input)
You need to create three more .js transformations, one for each of the flags used.
Substitute the “boost” with “child_protection” “mirror_display” “window_open” and save them naming accordingly.
Here is the error map to give some meanings to the numeric errors. Havent tried myseld, as had no operational errors so far.
transform/eurotronic_error.map
0=No errors
4=Valve adaption failed (E1)
8=Valve movement too slow (E2)
16=Valve not moving/blocked (E3)
-=Unknown
items/mqtt.items
Number TS4_local_temperature "Current Temperature [%.1f°C]" <temperature> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:local_temperature"}
Number TS4_current_heating_setpoint "Temperature Setpoint [%.1f°C]" <heating> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:current_heating_setpoint"}
String TS4_system_mode "Mode" <radiator> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:system_mode"}
Number TS4_pi_heating_demand "Valve open [%d %%]" <pressure> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:pi_heating_demand"}
Switch TS4_mirror_display "Mirror Display" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:mirror_display"}
Switch TS4_boost "Boost" <switch> (gPers_Change_Day, gTS_Boost) {channel="mqtt:topic:thermostat4:boost"}
Switch TS4_boost_Timer <switch> (gTS_Boost_Timer) {expire="15m, command=OFF"} // Timer
Switch TS4_window_open "Window Open" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:window_open"}
Switch TS4_window_open_Timer <switch> {expire="15m, command=OFF"} // Timer
Switch TS4_child_protection "Child Protection" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:child_protection"}
Number TS4_batt "Battery: [%s]" <battery> (gPers_Change_Day, gZB_bat) {channel="mqtt:topic:thermostat4:batt"}
Number TS4_link "Link: [%s]" <qualityofservice> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:link"}
Number TS4_lastseen_epoch "Last Seen: [%s]" (gZB_lastseen) {channel="mqtt:topic:thermostat4:lastseen_epoch"}
DateTime TS4_lastseen_datetime "Last Seen: [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]"
Number TS4_eurotronic_error_status "Error [MAP(eurotronic_error.map):%s]" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:eurotronic_error_status"}
//Number TS4_eurotronic_system_mode "eurotronic_system_mode" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:eurotronic_system_mode"}
//Number TS4_occupied_heating_setpoint "occupied_heating_setpoint" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:occupied_heating_setpoint"}
//Number TS4_unoccupied_heating_setpoint "unoccupied_heating_setpoint" <switch> (gPers_Change_Day) {channel="mqtt:topic:thermostat4:unoccupied_heating_setpoint"}
Make sure to define all the used groups as well as the items.
The manufacturer claims that there is an inbuilt algorithm that senses the drastic change in temperature and makes a decision regarding status of the window, but since I have contact sensors on all of the windows I have implemented this logic manually. The thermostat would go into window open mode 1 minute after the window is open and will stay in that mode for 15 minutes.
rule "office window timer "
when
Item sens_contact9_status changed
then
if ( sens_contact9_status.state == "false" ) {
postUpdate(sens_contact9_Timer, "ON") // Set timer
}
else if ( sens_contact9_status.state == "true" ) {
postUpdate(sens_contact9_Timer, "OFF")
}
end
rule "office window timer expires and window is still open"
when
Item sens_contact9_Timer received update "OFF"
then
if ( sens_contact9_status.state == "false" ) {
sendCommand(TS4_window_open, "ON") // Set Window Open mode
postUpdate(TS4_window_open_Timer, "ON") // Set Window Open Timer
}
else if ( sens_contact9_status.state == "true" ) {
postUpdate(TS4_window_open_Timer, "OFF") // Cancel Window Open Timer
}
end
rule "office window timer expires"
when
Item TS4_window_open_Timer received update "OFF"
then
sendCommand(TS4_window_open, "OFF") // Set Window Open mode to OFF
if ( sens_contact9_status.state == "false" ) {
sendPushoverMessage(pushoverBuilder("Office Window is OPEN for 15 minutes. Consider closing"))
}
end
By default the boost mode simply sets the temperature setpoint to 30 degrees, I decided to limit the time it heats at full power to 15 minutes. The rule works both, when the boost mode is initiated via mqtt or via a physical press of the button on the thermostat itself. Note that this rule applies to all thermostats that you have in the group, while the “window open” is linked individually to a particurlar contact sensor.
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Thermostat boost enabled"
when
Member of gTS_Boost received update "ON"
then
val triggeringItem_Timer = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString + "_Timer")
if ( triggeringItem.state == ON && triggeringItem_Timer.state != ON ) {
postUpdate(triggeringItem_Timer, "ON") // Set timer
}
end
rule "Thermostat boost timer expires"
when
Member of gTS_Boost_Timer received update "OFF"
then
val triggeringItem_main = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString.split("_Timer").get(0))
sendCommand(triggeringItem_main, "OFF") // Set Boost mode to OFF
end
rule "Thermostat boost manually switched OFF"
when
Member of gTS_Boost received update "OFF"
then
val triggeringItem_Timer = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString + "_Timer")
if ( triggeringItem_Timer.state == ON ) {
sendCommand(triggeringItem_Timer, "OFF") // Disable timer
//postUpdate(triggeringItem_Timer, "OFF") // Disable timer
}
end
Another couple of rules that i use to convert the last report time of the zibee sensors to human readable for and alert in cases the sensors was quiet for more then 2 hrs. The low level sensor battery status is checked and reported as well.
rules/zigbee_sensors.rules
rule "convert lastseen time from epoch to ISO8601"
when
Member of gZB_lastseen changed
then
// Determine new variable name that will contain DateTime type lastseen value
val triggeringItem_with_datetime = triggeringItem.name.toString.split("_epoch").get(0)+ "_datetime"
// Convert from epoch to ISO8601 DateTime type lastseen value
var DateTime DateTimeFromEpoch = new DateTime((triggeringItem.state as Number).longValue)
postUpdate(triggeringItem_with_datetime,DateTimeFromEpoch.toString)
logInfo("ZigBeeRules", triggeringItem.name.toString + ": " + DateTimeFromEpoch.toString)
end
rule "Check last report time and battery level"
when
Time cron "0 0 0/1 * * ? *"
// Time cron "0 0/1 * * * ? *"
then
val current_epoch = now.getMillis()
//logInfo("ZigBeeRules", current_epoch.toString)
gZB_lastseen.members.forEach[member |
logInfo("ZigBeeRules", "'{}' lastseen is " + member.state.toString, member.name)
if ( member.state != UNDEF && member.state != NULL ) {
// logInfo("ZigBeeRules", "'{}' lastseen is " + member.state.toString, member.name)
val timesincelastcontact = current_epoch - member.state as Number
if ( timesincelastcontact > 7200000 ) {
logInfo("ZigBeeRules", "'{}' time since contact is " + timesincelastcontact.toString, member.name)
sendPushoverMessage(pushoverBuilder("ZigBeeRules " + member.name.toString + " time since contact is " + timesincelastcontact.toString))
}
}
else {
logInfo("ZigBeeRules", "'{}' is in " + member.state.toString + " state. Please investigate", member.name)
sendPushoverMessage(pushoverBuilder("ZigBeeRules " + member.name.toString + " is in " + member.state.toString + " state. Please investigate"))
}
]
gZB_bat.members.forEach[ member |
// logInfo("MyRules", "processing member '{}'", member.name)
if ( member.state < 40 ) {
logInfo("ZigBeeRules", "'{}' battery level is " + member.state.toString, member.name)
}
if ( member.state < 20 ) {
logInfo("ZigBeeRules", "'{}' battery level is " + member.state.toString, member.name)
sendPushoverMessage(pushoverBuilder("ZigBeeRules " + member.name.toString + " battery level is " + member.state.toString))
}
]
end
Finally the sitemap part.
Frame label="Thermostat" {
Setpoint item=TS4_current_heating_setpoint minValue=5 maxValue=30 step=1
Default item=TS4_local_temperature labelcolor=[>24="orange",>20="green",>15="blue"] valuecolor=[>24="orange",>20="green",>15="blue"]
Switch item=TS4_system_mode mappings=[off='OFF', heat='ON', auto='AUTO']
Text item=TS4_pi_heating_demand labelcolor=[>75="red",>50="orange",>25="green",>5="blue"] valuecolor=[>75="red",>50="orange",>25="green",>5="blue"]{
Frame label="Options" {
Default item=TS4_mirror_display
Default item=TS4_boost
Default item=TS4_window_open
Default item=TS4_child_protection
}
Frame label="Misc" {
Default item=TS4_batt
Default item=TS4_link
Default item=TS4_lastseen_datetime
Default item=TS4_eurotronic_error_status
}
}
}
I guess thats it, hope that someone will find it useful.