From d3009495c761a13508d8d3f93f35383fdf7f73b4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 12 Oct 2019 13:12:00 +0200 Subject: [PATCH] changes to MQTT topics --- CHANGELOG.md | 11 +- doc/home assistant/climate.yaml | 70 +-- doc/home assistant/script.yaml | 7 +- doc/home assistant/switch.yaml | 30 +- doc/home assistant/ui-lovelace.yaml | 2 +- src/ems-esp.cpp | 652 +++++++++++----------------- src/ems.cpp | 76 ++-- src/ems.h | 11 +- src/my_config.h | 58 +-- 9 files changed, 377 insertions(+), 540 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae009d136..af4f3f461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.2 beta] #### Important! This build has breaking changes: - - the Thermostat MQTT topics are all always suffixed with the heat controller number, e.g. `thermostat_data1` - - the web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm i` from within the `tools/webfilesbuilder` folder. + - MQTT topics have changed. Use `mqttlog` to see the names of the subscriptions and the format of the payload data + - the web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm ci` from within the `tools/webfilesbuilder` folder. ### Added - Handling of MM100 Status Messages (thanks @kstaniek) - Retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) -- Added error fall-back for MQTT publishes that fail +- Added sending of Mixer Module data via MQTT (thanks @peclik) +- Reporting of MQTT publish and subscribe errors ### Fixed @@ -27,11 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - External dallas sensor values sent in MQTT payload as float values and not strings - All MQTT topics for the Thermostat have the Heating Circuit appended (e.g. `thermostat_data1`). This includes the commands. - Shower timer and shower alert and not MQTT published at boot up +- Heating Active logic change to use Selected Flow Temp of min 30 instead of 70 (https://github.com/proddy/EMS-ESP/issues/193) ### Removed - Removed telnet command `shower timer` and `shower alert` to toggle the switches -- Removed the heatingcircuit key/value from the MQTT Thermostat topic payload ## [1.9.1] 2019-10-05 @@ -40,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for multiple Heating Circuits - https://github.com/proddy/EMS-ESP/issues/162 - new `mqttlog` command also shows which MQTT topics it is subscribed too - Optimized event log loading in web and added integrity checks on all config and log files during boot -- `autodetect quick` +- `autodetect quick` for detecting known devices from our database list - `log_events` option, now optional to save the log events to SPIFFS ### Fixed diff --git a/doc/home assistant/climate.yaml b/doc/home assistant/climate.yaml index d2e70f472..80e2c0783 100644 --- a/doc/home assistant/climate.yaml +++ b/doc/home assistant/climate.yaml @@ -1,37 +1,39 @@ - - platform: mqtt - name: Thermostat - modes: - - "auto" - - "heat" - - "off" +- platform: mqtt + name: Thermostat + modes: + - "auto" + - "heat" + - "off" + mode_command_topic: "home/ems-esp/thermostat_cmd_mode1" + temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1" + + mode_state_topic: "home/ems-esp/thermostat_data" + current_temperature_topic: "home/ems-esp/thermostat_data" + temperature_state_topic: "home/ems-esp/thermostat_data" - mode_state_topic: "home/ems-esp/thermostat_data1" - current_temperature_topic: "home/ems-esp/thermostat_data1" - temperature_state_topic: "home/ems-esp/thermostat_data1" + mode_state_template: "{{ value_json.hc1.mode }}" + current_temperature_template: "{{ value_json.hc1.currtemp }}" + temperature_state_template: "{{ value_json.hc1.seltemp }}" + + temp_step: 0.5 + +- platform: mqtt + name: boiler + modes: + - "auto" + - "off" + min_temp: 40 + max_temp: 60 + temp_step: 1 + + current_temperature_topic: "home/ems-esp/boiler_data" + temperature_state_topic: "home/ems-esp/boiler_data" + mode_state_topic: "home/ems-esp/boiler_data" - temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1" - mode_command_topic: "home/ems-esp/thermostat_cmd_mode1" + current_temperature_template: "{{ value_json.wWCurTmp }}" + temperature_state_template: "{{ value_json.wWSelTemp }}" + mode_state_template: "{% if value_json.wWActivated == 'off' %} off {% else %} auto {% endif %}" - 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 - - - platform: mqtt - name: boiler - modes: - - "auto" - - "off" - min_temp: 40 - max_temp: 60 - temp_step: 1 - current_temperature_topic: "home/ems-esp/boiler_data" - temperature_state_topic: "home/ems-esp/boiler_data" - temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp" - current_temperature_template: "{{ value_json.wWCurTmp }}" - temperature_state_template: "{{ value_json.wWSelTemp }}" - mode_state_template: "{% if value_json.wWActivated == 'off' %} off {% else %} auto {% endif %}" - mode_state_topic: "home/ems-esp/boiler_data" - mode_command_topic: "home/ems-esp/wwactivated" - \ No newline at end of file + temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp" + mode_command_topic: "home/ems-esp/boiler_cmd_wwactivated" + \ No newline at end of file diff --git a/doc/home assistant/script.yaml b/doc/home assistant/script.yaml index 860d6a2c9..49b33a6d2 100644 --- a/doc/home assistant/script.yaml +++ b/doc/home assistant/script.yaml @@ -1,7 +1,8 @@ +# ems-esp shower_coldshot: sequence: - service: mqtt.publish data_template: - topic: 'home/ems-esp/shower_coldshot' - payload: '1' - + topic: 'home/ems-esp/generic_cmd' + payload: '{cmd:"coldshot"}' + \ No newline at end of file diff --git a/doc/home assistant/switch.yaml b/doc/home assistant/switch.yaml index 0649e3c97..0ebeba4f9 100644 --- a/doc/home assistant/switch.yaml +++ b/doc/home assistant/switch.yaml @@ -1,20 +1,20 @@ +# EMS-ESP - platform: mqtt name: "Shower Timer" - state_topic: "home/ems-esp/shower_timer" - command_topic: "home/ems-esp/shower_timer" - payload_on: "1" - payload_off: "0" - optimistic: false - qos: 1 - retain: false + state_topic: "home/ems-esp/shower_data" + value_template: "{{ value_json.timer }}" + command_topic: "home/ems-esp/shower_data" + payload_on: '{"timer":"1"}' + payload_off: '{"timer":"0"}' + state_on: "1" + state_off: "0" - platform: mqtt name: "Long Shower Alert" - state_topic: "home/ems-esp/shower_alert" - command_topic: "home/ems-esp/shower_alert" - payload_on: "1" - payload_off: "0" - optimistic: false - qos: 1 - retain: false - + state_topic: "home/ems-esp/shower_data" + value_template: "{{ value_json.alert }}" + command_topic: "home/ems-esp/shower_data" + payload_on: '{"alert":"1"}' + payload_off: '{"alert":"0"}' + state_on: "1" + state_off: "0" diff --git a/doc/home assistant/ui-lovelace.yaml b/doc/home assistant/ui-lovelace.yaml index 59f96d36e..d3a8e88d1 100644 --- a/doc/home assistant/ui-lovelace.yaml +++ b/doc/home assistant/ui-lovelace.yaml @@ -56,7 +56,7 @@ views: - sensor.current_room_temperature - sensor.dark_sky_temperature - type: thermostat - entity: climate.thermostat + entity: climate.thermostat_hc1 - type: thermostat name: WarmWater entity: climate.boiler diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 02eb627f0..c07c11581 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -11,6 +11,7 @@ #include "MyESP.h" #include "ems.h" #include "ems_devices.h" +#include "ems_utils.h" #include "emsuart.h" #include "my_config.h" #include "version.h" @@ -32,10 +33,6 @@ DS18 ds18; #define APP_URL "https://github.com/proddy/EMS-ESP" #define APP_UPDATEURL "https://api.github.com/repos/proddy/EMS-ESP/releases/latest" -// macros for easy debugging -#define myDebug(...) myESP.myDebug(__VA_ARGS__) -#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) - // set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1. #define EMSESP_DELAY 0 // initially set to 0 for no delay. Change to 1 if getting WDT resets from wifi @@ -160,231 +157,6 @@ void myDebugLog(const char * s) { } } -// convert float to char -char * _float_to_char(char * a, float f, uint8_t precision = 2) { - long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000}; - - char * ret = a; - long whole = (long)f; - itoa(whole, a, 10); - while (*a != '\0') - a++; - *a++ = '.'; - long decimal = abs((long)((f - whole) * p[precision])); - itoa(decimal, a, 10); - - return ret; -} - -// convert bool to text. bools are stored as bytes -char * _bool_to_char(char * s, uint8_t value) { - if (value == EMS_VALUE_INT_ON) { - strlcpy(s, "on", sizeof(s)); - } else if (value == EMS_VALUE_INT_OFF) { - strlcpy(s, "off", sizeof(s)); - } else { - strlcpy(s, "?", sizeof(s)); - } - return s; -} - -// convert short (two bytes) to text string -// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100 -// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c) -char * _short_to_char(char * s, int16_t value, uint8_t decimals = 1) { - // remove errors or invalid values - if (value == EMS_VALUE_SHORT_NOTSET) { - strlcpy(s, "?", 10); - return (s); - } - - // just print - if (decimals == 0) { - ltoa(value, s, 10); - return (s); - } - - // do floating point - char s2[10] = {0}; - // check for negative values - if (value < 0) { - strlcpy(s, "-", 10); - value *= -1; // convert to positive - } - - if (decimals == 2) { - // divide by 2 - strlcpy(s, ltoa(value / 2, s2, 10), 10); - strlcat(s, ".", 10); - strlcat(s, ((value & 0x01) ? "5" : "0"), 10); - - } else { - strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10); - strlcat(s, ".", 10); - strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10); - } - - return s; -} - -// convert short (two bytes) to text string -// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100 -char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals = 1) { - // remove errors or invalid values - if (value == EMS_VALUE_USHORT_NOTSET) { - strlcpy(s, "?", 10); - return (s); - } - - // just print - if (decimals == 0) { - ltoa(value, s, 10); - return (s); - } - - // do floating point - char s2[10] = {0}; - - if (decimals == 2) { - // divide by 2 - strlcpy(s, ltoa(value / 2, s2, 10), 10); - strlcat(s, ".", 10); - strlcat(s, ((value & 0x01) ? "5" : "0"), 10); - - } else { - strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10); - strlcat(s, ".", 10); - strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10); - } - - return s; -} - -// takes a signed short value (2 bytes), converts to a fraction -// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100 -void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals = 1) { - static char buffer[200] = {0}; - static char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - - strlcat(buffer, _short_to_char(s, value, decimals), sizeof(buffer)); - - if (postfix != nullptr) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - - myDebug(buffer); -} - -// takes a unsigned short value (2 bytes), converts to a fraction -// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100 -void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals = 1) { - static char buffer[200] = {0}; - static char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - - strlcat(buffer, _ushort_to_char(s, value, decimals), sizeof(buffer)); - - if (postfix != nullptr) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - - myDebug(buffer); -} - -// convert int (single byte) to text value -char * _int_to_char(char * s, uint8_t value, uint8_t div = 1) { - if (value == EMS_VALUE_INT_NOTSET) { - strlcpy(s, "?", sizeof(s)); - return (s); - } - - static char s2[5] = {0}; - - switch (div) { - case 1: - itoa(value, s, 10); - break; - - case 2: - strlcpy(s, itoa(value >> 1, s2, 10), 5); - strlcat(s, ".", sizeof(s)); - strlcat(s, ((value & 0x01) ? "5" : "0"), 5); - break; - - case 10: - strlcpy(s, itoa(value / 10, s2, 10), 5); - strlcat(s, ".", sizeof(s)); - strlcat(s, itoa(value % 10, s2, 10), 5); - break; - - default: - itoa(value, s, 10); - break; - } - - return s; -} - -// takes an int value (1 byte), converts to a fraction -void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1) { - static char buffer[200] = {0}; - static char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - - strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer)); - - if (postfix != nullptr) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - - myDebug(buffer); -} - -// takes a long value at prints it to debug log -void _renderLongValue(const char * prefix, const char * postfix, uint32_t value) { - static char buffer[200] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - - if (value == EMS_VALUE_LONG_NOTSET) { - strlcat(buffer, "?", sizeof(buffer)); - } else { - char s[20] = {0}; - strlcat(buffer, ltoa(value, s, 10), sizeof(buffer)); - } - - if (postfix != nullptr) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - - myDebug(buffer); -} - -// takes a bool value at prints it to debug log -void _renderBoolValue(const char * prefix, uint8_t value) { - static char buffer[200] = {0}; - static char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - - strlcat(buffer, _bool_to_char(s, value), sizeof(buffer)); - - myDebug(buffer); -} - // figures out the thermostat mode // returns 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day // hc_num is 1 to 4 @@ -657,9 +429,9 @@ void showInfo() { } // Render Termostat Mode, if we have a mode - uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day + uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=off, 1=manual, 2=auto, 3=night, 4=day if (thermoMode == 0) { - myDebug_P(PSTR(" Mode is set to low")); + myDebug_P(PSTR(" Mode is set to off")); } else if (thermoMode == 1) { myDebug_P(PSTR(" Mode is set to manual")); } else if (thermoMode == 2) { @@ -764,11 +536,11 @@ void publishValues(bool force) { uint32_t fchecksum; uint8_t jsonSize; - static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off - static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values + static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off + static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values static uint32_t previousThermostatPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for thermostat values static uint32_t previousMixingPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for mixing values - static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) + static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) JsonObject rootBoiler = doc.to(); @@ -904,53 +676,57 @@ void publishValues(bool force) { doc.clear(); JsonObject rootThermostat = doc.to(); - // rootThermostat[THERMOSTAT_HC] = _int_to_char(s, thermostat->hc); // heating circuit 1..4 + // hc{1-4} + char hc[10]; + strncpy(hc, THERMOSTAT_HC, sizeof(hc)); + strncat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); + JsonObject dataThermostat = rootThermostat.createNestedObject(hc); // different logic depending on thermostat types if (ems_getThermostatModel() == EMS_MODEL_EASY) { if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 100; + dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 100; if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 100; + dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 100; } else if ((ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100) || (ems_getThermostatModel() == EMS_MODEL_FW120)) { if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 10; + dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 10; if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; + dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; } else { if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 2; + dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 2; if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; + dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; if (thermostat->daytemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_DAYTEMP] = (double)thermostat->daytemp / 2; + dataThermostat[THERMOSTAT_DAYTEMP] = (double)thermostat->daytemp / 2; if (thermostat->nighttemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_NIGHTTEMP] = (double)thermostat->nighttemp / 2; + dataThermostat[THERMOSTAT_NIGHTTEMP] = (double)thermostat->nighttemp / 2; if (thermostat->holidaytemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_HOLIDAYTEMP] = (double)thermostat->holidaytemp / 2; + dataThermostat[THERMOSTAT_HOLIDAYTEMP] = (double)thermostat->holidaytemp / 2; if (thermostat->heatingtype != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype; + dataThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype; if (thermostat->circuitcalctemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp; + dataThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp; } uint8_t thermoMode = _getThermostatMode(hc_v); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day // Termostat Mode if (thermoMode == 0) { - rootThermostat[THERMOSTAT_MODE] = "low"; + dataThermostat[THERMOSTAT_MODE] = "off"; } else if (thermoMode == 1) { - rootThermostat[THERMOSTAT_MODE] = "manual"; + dataThermostat[THERMOSTAT_MODE] = "heat"; } else if (thermoMode == 2) { - rootThermostat[THERMOSTAT_MODE] = "auto"; + dataThermostat[THERMOSTAT_MODE] = "auto"; } else if (thermoMode == 3) { - rootThermostat[THERMOSTAT_MODE] = "night"; + dataThermostat[THERMOSTAT_MODE] = "night"; } else if (thermoMode == 4) { - rootThermostat[THERMOSTAT_MODE] = "day"; + dataThermostat[THERMOSTAT_MODE] = "day"; } data[0] = '\0'; // reset data for next package @@ -967,13 +743,8 @@ void publishValues(bool force) { fchecksum = crc.finalize(); if ((previousThermostatPublishCRC[hc_v - 1] != fchecksum) || force) { previousThermostatPublishCRC[hc_v - 1] = fchecksum; - char thermostat_topicname[20]; - char buffer[4]; - // "thermostat_data" + Heating Cicruit # - strlcpy(thermostat_topicname, TOPIC_THERMOSTAT_DATA, sizeof(thermostat_topicname)); - strlcat(thermostat_topicname, itoa(hc_v, buffer, 10), sizeof(thermostat_topicname)); myDebugLog("Publishing thermostat data via MQTT"); - myESP.mqttPublish(thermostat_topicname, data); + myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); } } } @@ -1103,20 +874,6 @@ void publishValues(bool force) { } } -// sets the shower timer on/off -void set_showerTimer() { - if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug_P(PSTR("Shower timer has been set to %s"), EMSESP_Settings.shower_timer ? "enabled" : "disabled"); - } -} - -// sets the shower alert on/off -void set_showerAlert() { - if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug_P(PSTR("Shower alert has been set to %s"), EMSESP_Settings.shower_alert ? "enabled" : "disabled"); - } -} - // used to read the next string from an input buffer and convert to an 8 bit int uint8_t _readIntNumber() { char * numTextPtr = strtok(nullptr, ", \n"); @@ -1332,6 +1089,33 @@ bool LoadSaveCallback(MYESP_FSACTION action, JsonObject settings) { return false; } +// Publish shower data +bool do_publishShowerData() { + StaticJsonDocument<200> doc; + JsonObject rootShower = doc.to(); + rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0"; + rootShower[TOPIC_SHOWER_ALERT] = EMSESP_Settings.shower_alert ? "1" : "0"; + + char s[50] = {0}; + if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) { + char buffer[16] = {0}; + strlcpy(s, itoa((uint8_t)((EMSESP_Shower.duration / (1000 * 60)) % 60), buffer, 10), sizeof(s)); + strlcat(s, " minutes and ", sizeof(s)); + strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s)); + strlcat(s, " seconds", sizeof(s)); + } else { + strlcpy(s, "n/a", sizeof(s)); + } + rootShower[TOPIC_SHOWER_DURATION] = s; + + char data[300] = {0}; + serializeJson(doc, data, sizeof(data)); + + myDebugLog("Publishing shower data via MQTT"); + + return (myESP.mqttPublish(TOPIC_SHOWER_DATA, data)); +} + // callback for custom settings when showing Stored Settings with the 'set' command // wc is number of arguments after the 'set' command // returns true if the setting was recognized and changed and should be saved back to SPIFFs @@ -1403,12 +1187,10 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Settings.shower_timer = true; - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else if (strcmp(value, "off") == 0) { EMSESP_Settings.shower_timer = false; - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else { myDebug_P(PSTR("Error. Usage: set shower_timer ")); } @@ -1418,12 +1200,10 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Settings.shower_alert = true; - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else if (strcmp(value, "off") == 0) { EMSESP_Settings.shower_alert = false; - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else { myDebug_P(PSTR("Error. Usage: set shower_alert ")); } @@ -1524,6 +1304,7 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { if (strcmp(first_cmd, "publish") == 0) { do_publishValues(); do_publishSensorValues(); + do_publishShowerData(); ok = true; } @@ -1690,11 +1471,11 @@ void OTACallback_post() { // used to identify a heating circuit // returns HC number 1 - 4 // or the default (1) is no suffix can be found -uint8_t _hasHCspecified(const char * topic, const char * input) { - int orig_len = strlen(topic); // original length of the topic we're comparing too +uint8_t _hasHCspecified(const char * key, const char * input) { + int orig_len = strlen(key); // original length of the topic we're comparing too // check if the strings match ignoring any suffix - if (strncmp(input, topic, orig_len) == 0) { + if (strncmp(input, key, orig_len) == 0) { // see if we have additional chars at the end, we want none or 1 uint8_t diff = (strlen(input) - orig_len); if (diff > 1) { @@ -1716,11 +1497,7 @@ uint8_t _hasHCspecified(const char * topic, const char * input) { void MQTTCallback(unsigned int type, const char * topic, const char * message) { // we're connected. lets subscribe to some topics if (type == MQTT_CONNECT_EVENT) { - myESP.mqttSubscribe(TOPIC_SHOWER_TIMER); - myESP.mqttSubscribe(TOPIC_SHOWER_ALERT); - myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT); - - // subscribe to the 4 heating circuits + // subscribe to the 4 heating circuits for receiving setpoint temperature and modes char topic_s[50]; char buffer[4]; for (uint8_t hc = 1; hc <= EMS_THERMOSTAT_MAXHC; hc++) { @@ -1731,158 +1508,229 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_MODE, sizeof(topic_s)); strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); - - strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_DAYTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); - myESP.mqttSubscribe(topic_s); - - strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_NIGHTTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); - myESP.mqttSubscribe(topic_s); - - strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); - myESP.mqttSubscribe(topic_s); } + // generic incoming MQTT command for Thermostat + // this is used for example for setting daytemp, nighttemp, holidaytemp + myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD); + + // generic incoming MQTT command for Boiler + // this is used for example for comfort, flowtemp + myESP.mqttSubscribe(TOPIC_BOILER_CMD); + + // these two need to be unqiue topics myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWACTIVATED); myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP); - myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT); - myESP.mqttSubscribe(TOPIC_BOILER_CMD_FLOWTEMP); - // publish the status of the Shower parameters - // myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - // myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); + // generic incoming MQTT command for EMS-ESP + // this is used for example for shower_coldshot + myESP.mqttSubscribe(TOPIC_GENERIC_CMD); + + // shower data + // for receiving shower_Timer and shower_alert switches + myESP.mqttSubscribe(TOPIC_SHOWER_DATA); + + return; } // handle incoming MQTT publish events - if (type == MQTT_MESSAGE_EVENT) { - uint8_t hc; - // thermostat temp changes - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: thermostat HC%d temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc); - publishValues(true); // publish back immediately + if (type != MQTT_MESSAGE_EVENT) { + return; + } + + // check first for generic commands + if (strcmp(topic, TOPIC_GENERIC_CMD) == 0) { + // convert JSON and get the command + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); + return; + } + const char * command = doc["cmd"]; + + // Check whatever the command is and act accordingly + if (strcmp(command, TOPIC_SHOWER_COLDSHOT) == 0) { + _showerColdShotStart(); return; } - // thermostat mode changes - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, topic); - if (hc) { - myDebug_P(PSTR("MQTT topic: thermostat HC%d mode value %s"), hc, message); - if (strcmp((char *)message, "auto") == 0) { - ems_setThermostatMode(2, hc); - } else if (strcmp((char *)message, "day") == 0 || (strcmp((char *)message, "manual") == 0) || (strcmp((char *)message, "heat") == 0)) { - ems_setThermostatMode(1, hc); - } else if (strcmp((char *)message, "night") == 0 || strcmp((char *)message, "off") == 0) { - ems_setThermostatMode(0, hc); + return; // no match for generic commands + } + + // check for shower commands + if (strcmp(topic, TOPIC_SHOWER_DATA) == 0) { + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); + return; + } + + // assumes payload is "1" or "0" + const char * shower_alert = doc[TOPIC_SHOWER_ALERT]; + if (shower_alert) { + if ((shower_alert[0] - MYESP_MQTT_PAYLOAD_OFF) != EMSESP_Settings.shower_alert) { + EMSESP_Settings.shower_alert = shower_alert[0] - MYESP_MQTT_PAYLOAD_OFF; + myDebug_P(PSTR("Shower alert has been set to %s"), EMSESP_Settings.shower_alert ? "enabled" : "disabled"); } - return; } - // set night temp value - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: new thermostat HC%d night temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc, 1); // night - return; - } - - // set daytemp value - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_DAYTEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: new thermostat HC%d day temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc, 2); // day - return; - } - - // set holiday value - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: new thermostat HC%d holiday temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc, 3); // holiday - return; - } - - // wwActivated - if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) { - if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) { - ems_setWarmWaterActivated(true); - } else if (message[0] == '0' || strcmp(message, "off") == 0) { - ems_setWarmWaterActivated(false); + // assumes payload is "1" or "0" + const char * shower_timer = doc[TOPIC_SHOWER_TIMER]; + if (shower_timer) { + if ((shower_timer[0] - MYESP_MQTT_PAYLOAD_OFF) != EMSESP_Settings.shower_timer) { + EMSESP_Settings.shower_timer = shower_timer[0] - MYESP_MQTT_PAYLOAD_OFF; + myDebug_P(PSTR("Shower timer has been set to %s"), EMSESP_Settings.shower_timer ? "enabled" : "disabled"); } - return; } - // boiler wwtemp changes - if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) { - uint8_t t = atoi((char *)message); - myDebug_P(PSTR("MQTT topic: boiler warm water temperature value %d"), t); - ems_setWarmWaterTemp(t); - publishValues(true); // publish back immediately, can't remember why I do this?! + return; + } + + // check for boiler commands + if (strcmp(topic, TOPIC_BOILER_CMD) == 0) { + // convert JSON and get the command + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); return; } + const char * command = doc["cmd"]; // boiler ww comfort setting - if (strcmp(topic, TOPIC_BOILER_CMD_COMFORT) == 0) { - myDebug_P(PSTR("MQTT topic: boiler warm water comfort value is %s"), message); - if (strcmp((char *)message, "hot") == 0) { + if (strcmp(command, TOPIC_BOILER_CMD_COMFORT) == 0) { + const char * data = doc["data"]; + if (strcmp((char *)data, "hot") == 0) { ems_setWarmWaterModeComfort(1); - } else if (strcmp((char *)message, "comfort") == 0) { + } else if (strcmp((char *)data, "comfort") == 0) { ems_setWarmWaterModeComfort(2); - } else if (strcmp((char *)message, "intelligent") == 0) { + } else if (strcmp((char *)data, "intelligent") == 0) { ems_setWarmWaterModeComfort(3); } return; } // boiler flowtemp setting - if (strcmp(topic, TOPIC_BOILER_CMD_FLOWTEMP) == 0) { - uint8_t t = atoi((char *)message); - myDebug_P(PSTR("MQTT topic: boiler flowtemp value %d"), t); + if (strcmp(command, TOPIC_BOILER_CMD_FLOWTEMP) == 0) { + uint8_t t = doc["data"]; ems_setFlowTemp(t); return; } - // shower timer - if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) { - if (message[0] == '1') { - EMSESP_Settings.shower_timer = true; - } else if (message[0] == '0') { - EMSESP_Settings.shower_timer = false; - } - set_showerTimer(); + return; // unknown boiler command + } + + // check for unique boiler commands + + // wwActivated + if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) { + if ((message[0] == MYESP_MQTT_PAYLOAD_ON || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) { + ems_setWarmWaterActivated(true); + } else if (message[0] == MYESP_MQTT_PAYLOAD_OFF || strcmp(message, "off") == 0) { + ems_setWarmWaterActivated(false); + } + return; + } + + // boiler wwtemp changes + if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) { + uint8_t t = atoi((char *)message); + ems_setWarmWaterTemp(t); + publishValues(true); + return; + } + + uint8_t hc; + // thermostat temp changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, topic); + if (hc) { + float f = strtof((char *)message, 0); + ems_setThermostatTemp(f, hc); + publishValues(true); // publish back immediately + return; + } + + // thermostat mode changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, topic); + if (hc) { + if (strncmp(message, "auto", 4) == 0) { + ems_setThermostatMode(2, hc); + } else if ((strncmp(message, "day", 4) == 0) || (strncmp(message, "manual", 6) == 0) || (strncmp(message, "heat", 4) == 0)) { + ems_setThermostatMode(1, hc); + } else if ((strncmp(message, "night", 5) == 0) || (strncmp(message, "off", 3) == 0)) { + ems_setThermostatMode(0, hc); + } + return; + } + + // check for generic thermostat commands + if (strcmp(topic, TOPIC_THERMOSTAT_CMD) == 0) { + // convert JSON and get the command + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); + return; + } + const char * command = doc["cmd"]; + + uint8_t hc; + // thermostat temp changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc); + publishValues(true); // publish back immediately return; } - // shower alert - if (strcmp(topic, TOPIC_SHOWER_ALERT) == 0) { - if (message[0] == '1') { - EMSESP_Settings.shower_alert = true; - } else if (message[0] == '0') { - EMSESP_Settings.shower_alert = false; + // thermostat mode changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, command); + if (hc) { + const char * data = doc["data"]; + if (strcmp(data, "auto") == 0) { + ems_setThermostatMode(2, hc); + } else if ((strcmp(data, "day") == 0) || (strcmp(data, "manual") == 0) || (strcmp(data, "heat") == 0)) { + ems_setThermostatMode(1, hc); + } else if ((strcmp(data, "night") == 0) || (strcmp(data, "off") == 0)) { + ems_setThermostatMode(0, hc); } - set_showerAlert(); return; } - // shower cold shot - if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) { - _showerColdShotStart(); + // set night temp value + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc, 1); // night + return; + } + + // set daytemp value + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_DAYTEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc, 2); // day + return; + } + + // set holiday value + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc, 3); // holiday return; } } } + // Init callback, which is used to set functions and call methods after a wifi connection has been established void WIFICallback() { // This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals @@ -1967,11 +1815,11 @@ void WebCallback(JsonObject root) { } // Render Termostat Mode, if we have a mode - uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day + uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=off, 1=manual, 2=auto, 3=night, 4=day if (thermoMode == 0) { - thermostat["tmode"] = "low"; + thermostat["tmode"] = "off"; } else if (thermoMode == 1) { - thermostat["tmode"] = "manual"; + thermostat["tmode"] = "heat"; } else if (thermoMode == 2) { thermostat["tmode"] = "auto"; } else if (thermoMode == 3) { @@ -2131,16 +1979,10 @@ void showerCheck() { if ((EMSESP_Shower.timerPause - EMSESP_Shower.timerStart) > SHOWER_OFFSET_TIME) { EMSESP_Shower.duration = (EMSESP_Shower.timerPause - EMSESP_Shower.timerStart - SHOWER_OFFSET_TIME); if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) { - char s[50] = {0}; - char buffer[16] = {0}; - strlcpy(s, itoa((uint8_t)((EMSESP_Shower.duration / (1000 * 60)) % 60), buffer, 10), sizeof(s)); - strlcat(s, " minutes and ", sizeof(s)); - strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s)); - strlcat(s, " seconds", sizeof(s)); if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug_P(PSTR("[Shower] finished with duration %s"), s); + myDebug_P(PSTR("[Shower] finished with duration %d"), EMSESP_Shower.duration); } - myESP.mqttPublish(TOPIC_SHOWERTIME, s); // publish to MQTT + do_publishShowerData(); // publish to MQTT } } diff --git a/src/ems.cpp b/src/ems.cpp index b0a1bc61a..b639fd965 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -7,6 +7,7 @@ */ #include "ems.h" +#include "ems_utils.h" #include "MyESP.h" #include "ems_devices.h" #include "emsuart.h" @@ -17,10 +18,6 @@ uint8_t _TEST_DATA_max = ArraySize(TEST_DATA); #endif -// MyESP class for logging to telnet and serial -#define myDebug(...) myESP.myDebug(__VA_ARGS__) -#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) - _EMS_Sys_Status EMS_Sys_Status; // EMS Status CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO queue for Tx send buffer @@ -231,7 +228,6 @@ const uint8_t TX_WRITE_TIMEOUT_COUNT = 2; // 3 retries before timeout const uint32_t EMS_BUS_TIMEOUT = 15000; // timeout in ms before recognizing the ems bus is offline (15 seconds) const uint32_t EMS_POLL_TIMEOUT = 5000000; // timeout in microseconds before recognizing the ems bus is offline (5 seconds) - // init stats and counters and buffers void ems_init() { ems_clearDeviceList(); // init the device map @@ -496,36 +492,6 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len) { return crc; } -// like itoa but for hex, and quicker -char * _hextoa(uint8_t value, char * buffer) { - char * p = buffer; - byte nib1 = (value >> 4) & 0x0F; - byte nib2 = (value >> 0) & 0x0F; - *p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA; - *p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA; - *p = '\0'; // null terminate just in case - return buffer; -} - -// for decimals 0 to 99, printed as a 2 char string -char * _smallitoa(uint8_t value, char * buffer) { - buffer[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0'; - buffer[1] = (value % 10) + '0'; - buffer[2] = '\0'; - return buffer; -} - -/* for decimals 0 to 999, printed as a string - * From @nomis - */ -char * _smallitoa3(uint16_t value, char * buffer) { - buffer[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0'; - buffer[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0'; - buffer[2] = (value % 10) + '0'; - buffer[3] = '\0'; - return buffer; -} - /** * Find the pointer to the EMS_Types array for a given type ID * or -1 if not found @@ -630,11 +596,11 @@ void _ems_sendTelegram() { EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { - _EMS_RxTelegram EMS_RxTelegram; // create new Rx object - EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram - EMS_RxTelegram.telegram = EMS_TxTelegram.data; - EMS_RxTelegram.data_length = 0; // ignore #data= - EMS_RxTelegram.timestamp = millis(); // now + _EMS_RxTelegram EMS_RxTelegram; // create new Rx object + EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram + EMS_RxTelegram.telegram = EMS_TxTelegram.data; + EMS_RxTelegram.data_length = 0; // ignore #data= + EMS_RxTelegram.timestamp = millis(); // now _debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true); } @@ -1160,7 +1126,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) { // release the lock on the TxQueue EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE; - // at this point we can assume TxStatus is EMS_TX_STATUS_WAIT as we just sent a read or validate + // at this point we can assume TxStatus was EMS_TX_STATUS_WAIT as we just sent a read or validate telegram // for READ or VALIDATE the dest (telegram[1]) is always us, so check for this // and if not we probably didn't get any response so remove the last Tx from the queue and process the telegram anyway if ((telegram[1] & 0x7F) != EMS_ID_ME) { @@ -2820,7 +2786,11 @@ void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype) EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE; EMS_TxTelegram.dest = device_id; - myDebug_P(PSTR("Setting new thermostat temperature for heating circuit %d type %d (0=auto,1=night,2=day,3=holiday)"), hc_num, temptype); + char s[10] = {0}; + myDebug_P(PSTR("Setting new thermostat temperature to %s for heating circuit %d type %d (0=auto,1=night,2=day,3=holiday)"), + _float_to_char(s, temperature), + hc_num, + temptype); if (model_id == EMS_MODEL_RC20) { EMS_TxTelegram.type = EMS_TYPE_RC20Set; @@ -2922,17 +2892,31 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) { uint8_t model_id = EMS_Thermostat.model_id; uint8_t device_id = EMS_Thermostat.device_id; + uint8_t set_mode; // RC300/1000/3000 have different settings if (model_id == EMS_MODEL_RC300) { if (mode == 1) { - mode = 0; // manual + set_mode = 0; // manual/heat } else { - mode = 0xFF; // auto + set_mode = 0xFF; // auto } + } else { + set_mode = mode; } - myDebug_P(PSTR("Setting thermostat mode to %d for heating circuit %d"), mode, hc_num); + // 0=off, 1=manual, 2=auto, 3=night, 4=day + if (mode == 0) { + myDebug_P(PSTR("Setting thermostat mode to off for heating circuit %d"), hc_num); + } else if (set_mode == 1) { + myDebug_P(PSTR("Setting thermostat mode to manual for heating circuit %d"), hc_num); + } else if (set_mode == 2) { + myDebug_P(PSTR("Setting thermostat mode to auto for heating circuit %d"), hc_num); + } else if (set_mode == 3) { + myDebug_P(PSTR("Setting thermostat mode to night for heating circuit %d"), hc_num); + } else if (set_mode == 4) { + myDebug_P(PSTR("Setting thermostat mode to day for heating circuit %d"), hc_num); + } _EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx EMS_TxTelegram.timestamp = millis(); // set timestamp @@ -2941,7 +2925,7 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) { EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE; EMS_TxTelegram.dest = device_id; EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; - EMS_TxTelegram.dataValue = mode; + EMS_TxTelegram.dataValue = set_mode; // handle different thermostat types if (model_id == EMS_MODEL_RC20) { diff --git a/src/ems.h b/src/ems.h index bb4659851..17de5c35d 100644 --- a/src/ems.h +++ b/src/ems.h @@ -87,10 +87,10 @@ #define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway // Product IDs -#define EMS_PRODUCTID_SM10 73 // SM10 solar module -#define EMS_PRODUCTID_SM50 162 // SM50 solar module -#define EMS_PRODUCTID_SM100 163 // SM100 solar module -#define EMS_PRODUCTID_ISM1 101 // Junkers ISM1 solar module +#define EMS_PRODUCTID_SM10 73 // SM10 solar module +#define EMS_PRODUCTID_SM50 162 // SM50 solar module +#define EMS_PRODUCTID_SM100 163 // SM100 solar module +#define EMS_PRODUCTID_ISM1 101 // Junkers ISM1 solar module #define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC #define EMS_MAX_TELEGRAM_LENGTH 32 // max length of a telegram, including CRC, for Rx and Tx. @@ -111,7 +111,7 @@ // trigger settings to determine if hot tap water or the heating is active #define EMS_BOILER_BURNPOWER_TAPWATER 100 -#define EMS_BOILER_SELFLOWTEMP_HEATING 70 +#define EMS_BOILER_SELFLOWTEMP_HEATING 30 // was 70, changed to 30 for https://github.com/proddy/EMS-ESP/issues/193 // define maximum setable tapwater temperature #define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60 @@ -503,7 +503,6 @@ bool ems_getTxCapable(); uint32_t ems_getPollFrequency(); bool ems_getTxDisabled(); - // private functions uint8_t _crcCalculator(uint8_t * data, uint8_t len); void _processType(_EMS_RxTelegram * EMS_RxTelegram); diff --git a/src/my_config.h b/src/my_config.h index 0262c93ba..c87396483 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -12,36 +12,42 @@ // TOPICS with _CMD_ are used for receiving commands from an MQTT Broker // EMS-ESP will subscribe to these topics +#define TOPIC_GENERIC_CMD "generic_cmd" // for receiving generic system commands via MQTT // MQTT for thermostat // these topics can be suffixed with a Heating Circuit number, e.g. thermostat_cmd_temp1 and thermostat_data1 -#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT -#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // temp changes via MQTT -#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // mode changes via MQTT -#define TOPIC_THERMOSTAT_CMD_DAYTEMP "thermostat_daytemp" // day temp (RC35 specific) -#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // night temp (RC35 specific) -#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "thermostat_holidayttemp" // holiday temp (RC35 specific) -#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature -#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature -#define THERMOSTAT_HC "thermostat_hc" // which heating circuit number -#define THERMOSTAT_MODE "thermostat_mode" // mode -#define THERMOSTAT_DAYTEMP "thermostat_daytemp" // RC35 specific -#define THERMOSTAT_NIGHTTEMP "thermostat_nighttemp" // RC35 specific -#define THERMOSTAT_HOLIDAYTEMP "thermostat_holidayttemp" // RC35 specific -#define THERMOSTAT_HEATINGTYPE "thermostat_heatingtype" // RC35 specific (3=floorheating) -#define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific +#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT + +#define TOPIC_THERMOSTAT_CMD "thermostat_cmd" // for receiving thermostat commands via MQTT +#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // temp changes via MQTT +#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // mode changes via MQTT +#define TOPIC_THERMOSTAT_CMD_DAYTEMP "daytemp" // day temp (RC35 specific) +#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "nighttemp" // night temp (RC35 specific) +#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "holidayttemp" // holiday temp (RC35 specific) + +#define THERMOSTAT_CURRTEMP "currtemp" // current temperature +#define THERMOSTAT_SELTEMP "seltemp" // selected temperature +#define THERMOSTAT_HC "hc" // which heating circuit number +#define THERMOSTAT_MODE "mode" // mode +#define THERMOSTAT_DAYTEMP "daytemp" // RC35 specific +#define THERMOSTAT_NIGHTTEMP "nighttemp" // RC35 specific +#define THERMOSTAT_HOLIDAYTEMP "holidayttemp" // RC35 specific +#define THERMOSTAT_HEATINGTYPE "heatingtype" // RC35 specific (3=floorheating) +#define THERMOSTAT_CIRCUITCALCTEMP "circuitcalctemp" // RC35 specific // MQTT for boiler -#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT -#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running -#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on +#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT +#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running +#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on + +#define TOPIC_BOILER_CMD "boiler_cmd" // for receiving boiler commands via MQTT #define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // change water on/off #define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // wwtemp changes via MQTT -#define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // ww comfort setting via MQTT -#define TOPIC_BOILER_CMD_FLOWTEMP "boiler_cmd_flowtemp" // flowtemp value via MQTT +#define TOPIC_BOILER_CMD_COMFORT "comfort" // ww comfort setting via MQTT +#define TOPIC_BOILER_CMD_FLOWTEMP "flowtemp" // flowtemp value via MQTT // MQTT for mixing device -#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT +#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT // MQTT for SM10/SM100 Solar Module #define TOPIC_SM_DATA "sm_data" // topic name @@ -60,10 +66,12 @@ #define HP_PUMPSPEED "pumpspeed" // pump speed // shower time -#define TOPIC_SHOWERTIME "showertime" // for sending shower time results -#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic -#define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic -#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command +#define TOPIC_SHOWER_DATA "shower_data" // for sending shower time results +#define TOPIC_SHOWER_TIMER "timer" // toggle switch for enabling the shower logic +#define TOPIC_SHOWER_ALERT "alert" // toggle switch for enabling the shower alarm logic +#define TOPIC_SHOWER_COLDSHOT "coldshot" // used to trigger a coldshot from an MQTT command +#define TOPIC_SHOWER_DURATION "duration" // duration of the last shower + // MQTT for EXTERNAL SENSORS #define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT