From 357d2a56ade3964b44abf3c709de1f4df93bfcb4 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 6 Dec 2018 18:37:38 +0100 Subject: [PATCH] thermostat mqtt is now json. removed 'low' setting in thermostat mode --- README.md | 6 +- doc/ha/automations.yaml | 2 +- doc/ha/climate.yaml | 19 +++-- doc/ha/sensors.yaml | 11 +-- espurna/boiler-espurna.ino | 6 +- src/boiler.ino | 163 ++++++++++++++++++++++++------------- src/ems.h | 2 +- 7 files changed, 132 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 4849cc6cd..dbff0bdfb 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Commands can be issued directly to the EMS bus typing in a letter followed by an - **a** to turn the warm water on and off - **h** to list all the recognized EMS types - **p** to toggle the Polling response on/off. It's not necessary to have Polling enabled to work. I use this for debugging purposes. -- **m** to set the thermostat mode from low, manual and clock/auto. +- **m** to set the thermostat mode to manual or auto. - **T** to toggle thermostat readings on/off - **S** to toggle the Shower Timer functionality on/off @@ -251,9 +251,7 @@ The boiler data is collected and sent as a single JSON object to MQTT TOPIC `hom `{"wWCurTmp":"43.0","wWHeat":"on","curFlowTemp":"51.7","retTemp":"48.0","burnGas":"off","heatPmp":"off","fanWork":"off","ignWork":"off","wWCirc":"off","selBurnPow":"0","curBurnPow":"0","sysPress":"1.6","boilTemp":"54.7","pumpMod":"4"}` -The temperature values of the thermostat as two separate topics `home/boiler/thermostat_currtemp` and `home/boiler/thermostat_seltemp` - -Values sent from HA to set the temperature come in via the subscribed topic `home/boiler/thermostat_temp` +Similarly the thermostat values are sent as a json package under a topic named `home/boiler/thermostat_data` with the current mode, room temperature and set temperature. These topics can be configured in the `TOPIC_*` defines in `boiler.ino`. Make sure you change the HA configuration too to match. diff --git a/doc/ha/automations.yaml b/doc/ha/automations.yaml index 8c10ef9b0..f5466fc5a 100644 --- a/doc/ha/automations.yaml +++ b/doc/ha/automations.yaml @@ -52,7 +52,7 @@ action: service: mqtt.publish data_template: - topic: 'home/boiler/thermostat_temp' + topic: 'home/boiler/thermostat_cmd_temp' payload: > {{ states.input_number.thermostat_temp.state }} diff --git a/doc/ha/climate.yaml b/doc/ha/climate.yaml index f697d7e63..392021b80 100644 --- a/doc/ha/climate.yaml +++ b/doc/ha/climate.yaml @@ -1,11 +1,20 @@ - - platform: mqtt + - platform: mqtt name: Thermostat modes: - low - manual - auto - mode_state_topic: "home/boiler/thermostat_mode" - temperature_command_topic: "home/boiler/thermostat_temp" - temperature_state_topic: "home/boiler/thermostat_seltemp" - current_temperature_topic: "home/boiler/thermostat_currtemp" + + mode_state_topic: "home/boiler/thermostat_data" + current_temperature_topic: "home/boiler/thermostat_data" + temperature_state_topic: "home/boiler/thermostat_data" + + temperature_command_topic: "home/boiler/thermostat_cmd_temp" + mode_command_topic: "home/boiler/thermostat_cmd_mode" + + mode_state_template: "{{ value_json.thermostat_mode }}" + current_temperature_template: "{{ value_json.thermostat_currtemp }}" + temperature_state_template: "{{ value_json.thermostat_seltemp }}" + + temp_step: 0.5 diff --git a/doc/ha/sensors.yaml b/doc/ha/sensors.yaml index 26233b2b1..b70912ddc 100644 --- a/doc/ha/sensors.yaml +++ b/doc/ha/sensors.yaml @@ -1,16 +1,19 @@ - platform: mqtt - state_topic: 'home/boiler/thermostat_currtemp' + state_topic: 'home/boiler/thermostat_data' name: 'Current Room Temperature' unit_of_measurement: '°C' + value_template: "{{ value_json.thermostat_currtemp }}" - platform: mqtt - state_topic: 'home/boiler/thermostat_seltemp' + state_topic: 'home/boiler/thermostat_data' name: 'Current Set Temperature' unit_of_measurement: '°C' + value_template: "{{ value_json.thermostat_seltemp }}" - platform: mqtt - state_topic: 'home/boiler/thermostat_mode' + state_topic: 'home/boiler/thermostat_data' name: 'Current Mode' + value_template: "{{ value_json.thermostat_mode }}" # last time boiler was started - platform: template @@ -22,8 +25,6 @@ state_topic: 'home/boiler/showertime' name: 'Last shower duration' force_update: true -# unit_of_measurement: "minutes" -# value_template: '{{ states("sensor.last_shower_duration") | float / 60}}' - platform: mqtt state_topic: 'home/boiler/boiler_data' diff --git a/espurna/boiler-espurna.ino b/espurna/boiler-espurna.ino index 9fc969203..164050179 100644 --- a/espurna/boiler-espurna.ino +++ b/espurna/boiler-espurna.ino @@ -281,7 +281,7 @@ void showInfo() { } else if (EMS_Thermostat.mode == 1) { myDebug("manual\n"); } else if (EMS_Thermostat.mode == 2) { - myDebug("clock/auto\n"); + myDebug("auto\n"); } else { myDebug("?\n"); } @@ -386,13 +386,13 @@ void publishValues() { mqttSend(TOPIC_THERMOSTAT_CURRTEMP, _float_to_char(s, EMS_Thermostat.curr_roomTemp)); mqttSend(TOPIC_THERMOSTAT_SELTEMP, _float_to_char(s, EMS_Thermostat.setpoint_roomTemp)); - // send mode 0=low, 1=manual, 2=clock/auto + // send mode 0=low, 1=manual, 2=auto if (EMS_Thermostat.mode == 0) { mqttSend(TOPIC_THERMOSTAT_MODE, "low"); } else if (EMS_Thermostat.mode == 1) { mqttSend(TOPIC_THERMOSTAT_MODE, "manual"); } else { - mqttSend(TOPIC_THERMOSTAT_MODE, "auto"); // must be auto + mqttSend(TOPIC_THERMOSTAT_MODE, "auto"); } } } diff --git a/src/boiler.ino b/src/boiler.ino index b6eae008d..f9bd7c569 100644 --- a/src/boiler.ino +++ b/src/boiler.ino @@ -51,14 +51,19 @@ uint8_t regularUpdatesCount = 0; #define MQTT_BOILER MQTT_BASE HOSTNAME "/" #define TOPIC_START MQTT_BOILER MQTT_TOPIC_START -#define TOPIC_THERMOSTAT_TEMP MQTT_BOILER "thermostat_temp" // for received thermostat temp changes -#define TOPIC_THERMOSTAT_CURRTEMP MQTT_BOILER "thermostat_currtemp" // current temperature -#define TOPIC_THERMOSTAT_SELTEMP MQTT_BOILER "thermostat_seltemp" // selected temperature -#define TOPIC_THERMOSTAT_MODE MQTT_BOILER "thermostat_mode" // selected temperature +// thermostat +#define TOPIC_THERMOSTAT_DATA MQTT_BOILER "thermostat_data" // for sending thermostat values +#define TOPIC_THERMOSTAT_CMD_TEMP MQTT_BOILER "thermostat_cmd_temp" // for received thermostat temp changes +#define TOPIC_THERMOSTAT_CMD_MODE MQTT_BOILER "thermostat_cmd_mode" // for received thermostat mode changes +#define TOPIC_THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature +#define TOPIC_THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature +#define TOPIC_THERMOSTAT_MODE "thermostat_mode" // mode + +// boiler +#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values #define TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE MQTT_BOILER "boiler_wwtemp" // warm water selected temp -#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values - +// shower time #define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results #define TOPIC_SHOWER_ALARM "shower_alarm" // for notifying HA that shower time has reached its limit #define TOPIC_SHOWER_TIMER MQTT_BOILER "shower_timer" // toggle switch for enabling the shower logic @@ -128,7 +133,7 @@ command_t PROGMEM project_cmds[] = {{"s", "show statistics"}, {"A", "shower alert on/off"}, {"r", "[n] send EMS request (n=telegram type id. Use 'h' for list)"}, {"t", "[n] set thermostat temperature to n"}, - {"m", "[n] set thermostat mode (0=low, 1=manual, 2=clock)"}, + {"m", "[n] set thermostat mode (1=manual, 2=auto)"}, {"w", "[n] set boiler warm water temperature to n (min 30)"}, {"a", "[n] boiler warm water on (n=1) or off (n=0)"}, {"x", "[n] experimental (warning: for debugging only!)"}}; @@ -146,8 +151,9 @@ _Boiler_Shower Boiler_Shower; // Debugger to telnet #define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__); -// CRC -uint32_t previousPublishCRC = 0; +// CRC checks +uint32_t previousBoilerPublishCRC = 0; +uint32_t previousThermostatPublishCRC = 0; // Times const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline @@ -378,9 +384,9 @@ void showInfo() { } else if (EMS_Thermostat.mode == 1) { myDebug("manual\n"); } else if (EMS_Thermostat.mode == 2) { - myDebug("clock/auto\n"); + myDebug("auto\n"); } else { - myDebug("?\n"); + myDebug("<%d>\n", EMS_Thermostat.mode); } } @@ -395,35 +401,35 @@ void showInfo() { } // send values to HA via MQTT -void publishValues() { +void publishValues(bool force) { char s[20]; // for formatting strings // Boiler values as one JSON object StaticJsonBuffer<512> jsonBuffer; char data[512]; - JsonObject & root = jsonBuffer.createObject(); + JsonObject & rootBoiler = jsonBuffer.createObject(); - root["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp); - root["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated); - root["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp); - root["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); - root["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp); - root["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp); - root["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); - root["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); - root["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); - root["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork); - root["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); - root["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow); - root["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow); - root["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress); - root["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp); - root["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod); + rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp); + rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated); + rootBoiler["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp); + rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); + rootBoiler["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp); + rootBoiler["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp); + rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); + rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); + rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); + rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork); + rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); + rootBoiler["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow); + rootBoiler["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow); + rootBoiler["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress); + rootBoiler["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp); + rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod); - size_t len = root.measureLength(); - root.printTo(data, len + 1); // form the json string + size_t len = rootBoiler.measureLength(); + rootBoiler.printTo(data, len + 1); // form the json string - // calculate hash + // calculate hash and send values if something has changed, to save unnecessary wifi traffic CRC32 crc; for (size_t i = 0; i < len - 1; i++) { crc.update(data[i]); @@ -431,11 +437,10 @@ void publishValues() { uint32_t checksum = crc.finalize(); //myDebug("HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data); - // only send values if something has changed, to save unnecessary wifi traffic - if (previousPublishCRC != checksum) { - previousPublishCRC = checksum; + if ((previousBoilerPublishCRC != checksum) || force) { + previousBoilerPublishCRC = checksum; if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug("Publishing data to MQTT topics\n"); + myDebug("Publishing boiler data via MQTT\n"); } // send values via MQTT @@ -445,20 +450,41 @@ void publishValues() { // handle the thermostat values separately if (EMS_Sys_Status.emsThermostatEnabled) { // only send thermostat values if we actually have them - if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) { + if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) return; + + // build json object + JsonObject & rootThermostat = jsonBuffer.createObject(); + rootThermostat[TOPIC_THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp); + rootThermostat[TOPIC_THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp); + + // send mode 0=low, 1=manual, 2=auto + if (EMS_Thermostat.mode == 0) { + rootThermostat[TOPIC_THERMOSTAT_MODE] = "low"; + } else if (EMS_Thermostat.mode == 1) { + rootThermostat[TOPIC_THERMOSTAT_MODE] = "manual"; + } else { + rootThermostat[TOPIC_THERMOSTAT_MODE] = "auto"; } - myESP.publish(TOPIC_THERMOSTAT_CURRTEMP, _float_to_char(s, EMS_Thermostat.curr_roomTemp)); - myESP.publish(TOPIC_THERMOSTAT_SELTEMP, _float_to_char(s, EMS_Thermostat.setpoint_roomTemp)); + size_t len = rootThermostat.measureLength(); + rootThermostat.printTo(data, len + 1); // form the json string - // send mode 0=low, 1=manual, 2=clock/auto - if (EMS_Thermostat.mode == 0) { - myESP.publish(TOPIC_THERMOSTAT_MODE, "low"); - } else if (EMS_Thermostat.mode == 1) { - myESP.publish(TOPIC_THERMOSTAT_MODE, "manual"); - } else { - myESP.publish(TOPIC_THERMOSTAT_MODE, "auto"); // must be auto + // calculate new CRC + crc.reset(); + for (size_t i = 0; i < len - 1; i++) { + crc.update(data[i]); + } + uint32_t checksum = crc.finalize(); + + if ((previousThermostatPublishCRC != checksum) || force) { + previousThermostatPublishCRC = checksum; + if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { + myDebug("Publishing thermostat data via MQTT\n"); + } + + // send values via MQTT + myESP.publish(TOPIC_THERMOSTAT_DATA, data); } } } @@ -492,7 +518,7 @@ void myDebugCallback() { break; case 'P': myESP.logger(LOG_HA, "Force publish values"); - publishValues(); + publishValues(true); break; case 'r': // read command for Boiler or Thermostat ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16)); @@ -504,7 +530,10 @@ void myDebugCallback() { ems_printAllTypes(); break; case 'm': // set thermostat mode - ems_setThermostatMode(cmd[2] - '0'); + if ((cmd[2] - '0') == 1) + ems_setThermostatMode(1); + else if ((cmd[2] - '0') == 2) + ems_setThermostatMode(2); break; case 'w': // set warm water temp ems_setWarmWaterTemp((uint8_t)strtol(&cmd[2], 0, 10)); @@ -551,14 +580,26 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) { return; } - // thermostat_temp - if (strcmp(topic, TOPIC_THERMOSTAT_TEMP) == 0) { + // thermostat temp changes + if (strcmp(topic, TOPIC_THERMOSTAT_CMD_TEMP) == 0) { float f = strtof((char *)payload, 0); char s[10]; - myDebug("MQTT topic: thermostat_temp value %s\n", _float_to_char(s, f)); + myDebug("MQTT topic: thermostat temp value %s\n", _float_to_char(s, f)); ems_setThermostatTemp(f); // publish back so HA is immediately updated - publishValues(); + publishValues(true); + return; + } + + // thermostat mode changes + if (strcmp(topic, TOPIC_THERMOSTAT_CMD_MODE) == 0) { + payload[length] = '\0'; // add null terminator + myDebug("MQTT topic: thermostat mode value %s\n", payload); + if (strcmp((char *)payload, "auto") == 0) { + ems_setThermostatMode(2); + } else if (strcmp((char *)payload, "manual") == 0) { + ems_setThermostatMode(1); + } return; } @@ -570,7 +611,7 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) { ems_setWarmWaterTemp(i); #endif // publish back so HA is immediately updated - publishValues(); + publishValues(true); return; } @@ -667,6 +708,11 @@ void _initBoiler() { updateHeartbeat(); } +// call PublishValues without forcing, so using CRC to see if we really need to publish +void do_publishValues() { + publishValues(false); +} + // // SETUP // Note: we don't init the UART here as we should wait until everything is loaded first. It's done in loop() @@ -686,8 +732,8 @@ void setup() { #endif // Timers using Ticker library - publishValuesTimer.attach(PUBLISHVALUES_TIME, publishValues); // post HA values - systemCheckTimer.attach(SYSTEMCHECK_TIME, systemCheck); // check if Boiler is online + publishValuesTimer.attach(PUBLISHVALUES_TIME, do_publishValues); // post HA values + systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if Boiler is online #ifndef NO_TX regularUpdatesTimer.attach((REGULARUPDATES_TIME / MAX_MANUAL_CALLS), regularUpdates); // regular reads from the EMS @@ -700,7 +746,8 @@ void setup() { myESP.setMQTTCallback(MQTTcallback); myESP.addSubscription(MQTT_HA); myESP.addSubscription(TOPIC_START); - myESP.addSubscription(TOPIC_THERMOSTAT_TEMP); + myESP.addSubscription(TOPIC_THERMOSTAT_CMD_TEMP); + myESP.addSubscription(TOPIC_THERMOSTAT_CMD_MODE); myESP.addSubscription(TOPIC_SHOWER_TIMER); myESP.addSubscription(TOPIC_SHOWER_ALERT); myESP.addSubscription(TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE); @@ -748,7 +795,7 @@ void heartbeat() { } // do a healthcheck every now and then to see if we connections -void systemCheck() { +void do_systemCheck() { // first do a system check to see if there is still a connection to the EMS Boiler_Status.boiler_online = ((timestamp - EMS_Sys_Status.emsLastPoll) < POLL_TIMEOUT_ERR); if (!Boiler_Status.boiler_online) { @@ -836,7 +883,7 @@ void loop() { // if we received new data and flagged for pushing, do it if (EMS_Sys_Status.emsRefreshed) { EMS_Sys_Status.emsRefreshed = false; - publishValues(); + publishValues(true); } /* diff --git a/src/ems.h b/src/ems.h index 47c6dc8e1..97b2ef6d4 100644 --- a/src/ems.h +++ b/src/ems.h @@ -149,7 +149,7 @@ typedef struct { typedef struct { float setpoint_roomTemp; // current set temp float curr_roomTemp; // current room temp - uint8_t mode; // 0=low, 1=manual, 2=clock/auto + uint8_t mode; // 0=low, 1=manual, 2=auto uint8_t hour; uint8_t minute; uint8_t second;