diff --git a/README.md b/README.md index dbff0bdfb..88e7215a4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ There are 3 parts to this project, first the design of the circuit, second the c - [EMS Reading and Writing](#ems-reading-and-writing) - [The ESP8266 Source Code](#the-esp8266-source-code) - [Supported EMS Types](#supported-ems-types) - - [Supporting A Type RC35 Thermostat](#supporting-a-type-rc35-thermostat) + - [Supporting other Thermostats types](#supporting-other-thermostats-types) - [Customizing The Code](#customizing-the-code) - [Using MQTT](#using-mqtt) - [The Basic Shower Logic](#the-basic-shower-logic) @@ -223,13 +223,13 @@ The code is built on the Arduino framework and is dependent on these external li In `boiler.ino` you can make calls to automatically send these read commands. See the function *regularUpdates()* -#### Supporting A Type RC35 Thermostat +#### Supporting other Thermostats types -The code is designed for a Moduline300 (RC20) thermostat. To adjust for a RC35 first change `EMS_ID_THERMOSTAT` in `ems.h`. A RC35 thermostat has 4 heating circuits and to read the values use different Monitor type IDs (e.g. 0x3E, 0x48, etc). The mode (0=night, 1=day, 2=holiday) is the first byte of the telegram and the temperature is the value of the 2nd byte divided by 2. +The code is originally designed for a Moduline300 (RC20) thermostat. -Then to set temperature values use the Working Mode with type IDs (0x3D, 0x47,0x51 and 0x5B) respectively. Set the offset (byte 4 of the header) to determine which temperature you're changing; 1 for night, 2 for day and 3 for holiday. The data value is the desired temperature multiplied by 2 as a single byte. +To adjust for a RC35 first change `EMS_ID_THERMOSTAT` in `ems.cpp`. A RC35 thermostat has 4 heating circuits and to read the values use different Monitor type IDs (e.g. 0x3E, 0x48, etc). The mode (0=night, 1=day, 2=holiday) is the first byte of the telegram and the temperature is the value of the 2nd byte divided by 2. Then to set temperature values use the Working Mode with type IDs (0x3D, 0x47,0x51 and 0x5B) respectively. Set the offset (byte 4 of the header) to determine which temperature you're changing; 1 for night, 2 for day and 3 for holiday. The data value is the desired temperature multiplied by 2 as a single byte. -Consult the wiki documentation for the actual data format. +I will add further support for the other thermostats (such as the Nefit Easy) as soon as I can get my hands on a physical device. I do however welcome contribtions to this code repository which is essentially the purpose of GitHub. By inspecting the telegram packets and looking up the codes in the German wiki (and with lots of trial and error) it is possible to easily extend the existing functions to support other EMS devices. ### Customizing The Code @@ -243,7 +243,7 @@ Most of the changes will be done in `boiler.ino` and `ems.cpp`. When the ESP8266 boots it will send a start signal via MQTT. This is picked up by Home Assistant and sends a notification informing me that the device has booted. Useful for knowing when the ESP gets reset. -I'm using the standard PubSubClient client so make sure you set `-DMQTT_MAX_PACKET_SIZE=300` as the default package size is 128 and our JSON messages are around 220 bytes. +I'm using the standard PubSubClient client so make sure you set `-DMQTT_MAX_PACKET_SIZE=400` as the default package size is 128 and our JSON messages are around 300 bytes. I run Mosquitto on my Raspberry PI 3 as the MQTT broker. diff --git a/doc/ha/ha.png b/doc/ha/ha.png deleted file mode 100644 index 33f4d39b9..000000000 Binary files a/doc/ha/ha.png and /dev/null differ diff --git a/doc/ha/input_number.yaml b/doc/ha/input_number.yaml deleted file mode 100644 index d5ecd4113..000000000 --- a/doc/ha/input_number.yaml +++ /dev/null @@ -1,18 +0,0 @@ - thermostat_temp: - name: Set Temperature - icon: mdi:temperature-celsius - min: 10 - max: 25 - step: 0.5 - unit_of_measurement: "°C" - mode: slider - - boiler_wwtemp: - name: Warm Water temp - icon: mdi:temperature-celsius - min: 30 - max: 60 - step: 1 - unit_of_measurement: "°C" - mode: slider - diff --git a/doc/ha/automations.yaml b/doc/home assistant/automations.yaml similarity index 66% rename from doc/ha/automations.yaml rename to doc/home assistant/automations.yaml index f5466fc5a..7207f0391 100644 --- a/doc/ha/automations.yaml +++ b/doc/home assistant/automations.yaml @@ -1,7 +1,3 @@ -########################################################## -## Boiler -########################################################## - - id: boiler_shower alias: Alert shower time trigger: @@ -43,31 +39,6 @@ payload: > {{ now().strftime("%H:%M:%S %-d/%b/%Y") }} -# Set thermostat temp -- id: thermostat_temp - alias: 'Adjust Theromostat Temperature' - trigger: - platform: state - entity_id: input_number.thermostat_temp - action: - service: mqtt.publish - data_template: - topic: 'home/boiler/thermostat_cmd_temp' - payload: > - {{ states.input_number.thermostat_temp.state }} - -# See if thermostat_temp has changed in recent mqtt payload, then adjust input_number -- id: thermostat_temp_incoming - trigger: - platform: state - entity_id: sensor.current_set_temperature - action: - service: input_number.set_value - data_template: - entity_id: input_number.thermostat_temp - value: '{{ states.sensor.current_set_temperature.state }}' - - # Boiler warm water temp - id: boiler_wwtemp trigger: diff --git a/doc/home assistant/binary_sensor.yaml b/doc/home assistant/binary_sensor.yaml new file mode 100644 index 000000000..0fadb8d19 --- /dev/null +++ b/doc/home assistant/binary_sensor.yaml @@ -0,0 +1,12 @@ +- platform: mqtt + name: 'Tap Water' + state_topic: 'home/boiler/tapwater_active' + payload_on: "1" + payload_off: "0" + +- platform: mqtt + name: 'Heating' + state_topic: 'home/boiler/heating_active' + payload_on: "1" + payload_off: "0" + \ No newline at end of file diff --git a/doc/ha/climate.yaml b/doc/home assistant/climate.yaml similarity index 100% rename from doc/ha/climate.yaml rename to doc/home assistant/climate.yaml diff --git a/doc/ha/customize.yaml b/doc/home assistant/customize.yaml similarity index 100% rename from doc/ha/customize.yaml rename to doc/home assistant/customize.yaml diff --git a/doc/home assistant/ha.png b/doc/home assistant/ha.png new file mode 100644 index 000000000..632ac8638 Binary files /dev/null and b/doc/home assistant/ha.png differ diff --git a/doc/ha/ha_notify.jpg b/doc/home assistant/ha_notify.jpg similarity index 100% rename from doc/ha/ha_notify.jpg rename to doc/home assistant/ha_notify.jpg diff --git a/doc/home assistant/input_number.yaml b/doc/home assistant/input_number.yaml new file mode 100644 index 000000000..37cbb1f2f --- /dev/null +++ b/doc/home assistant/input_number.yaml @@ -0,0 +1,9 @@ + boiler_wwtemp: + name: Warm Water temp + icon: mdi:temperature-celsius + min: 30 + max: 60 + step: 1 + unit_of_measurement: "°C" + mode: slider + diff --git a/doc/ha/notify.yaml b/doc/home assistant/notify.yaml similarity index 100% rename from doc/ha/notify.yaml rename to doc/home assistant/notify.yaml diff --git a/doc/ha/scripts.yaml b/doc/home assistant/scripts.yaml similarity index 100% rename from doc/ha/scripts.yaml rename to doc/home assistant/scripts.yaml diff --git a/doc/ha/sensors.yaml b/doc/home assistant/sensors.yaml similarity index 93% rename from doc/ha/sensors.yaml rename to doc/home assistant/sensors.yaml index b70912ddc..ecbe341a1 100644 --- a/doc/ha/sensors.yaml +++ b/doc/home assistant/sensors.yaml @@ -26,6 +26,16 @@ name: 'Last shower duration' force_update: true +- platform: mqtt + state_topic: 'home/boiler/boiler_data' + name: 'Tap Water' + value_template: '{{ value_json.tapwaterActive }}' + +- platform: mqtt + state_topic: 'home/boiler/boiler_data' + name: 'Heating' + value_template: '{{ value_json.heatingActive }}' + - platform: mqtt state_topic: 'home/boiler/boiler_data' name: 'Warm Water selected temperature' diff --git a/doc/ha/switches.yaml b/doc/home assistant/switches.yaml similarity index 100% rename from doc/ha/switches.yaml rename to doc/home assistant/switches.yaml diff --git a/doc/ha/ui-lovelace.yaml b/doc/home assistant/ui-lovelace.yaml similarity index 88% rename from doc/ha/ui-lovelace.yaml rename to doc/home assistant/ui-lovelace.yaml index 289fc57f4..f4d799fbd 100644 --- a/doc/ha/ui-lovelace.yaml +++ b/doc/home assistant/ui-lovelace.yaml @@ -51,13 +51,6 @@ views: - type: vertical-stack cards: - - type: entities - title: Thermostat - show_header_toggle: true - entities: - - sensor.current_room_temperature - - sensor.current_mode - - input_number.thermostat_temp - type: history-graph entities: - sensor.current_room_temperature diff --git a/espurna/boiler-espurna.ino b/espurna/boiler-espurna.ino index 164050179..886caad92 100644 --- a/espurna/boiler-espurna.ino +++ b/espurna/boiler-espurna.ino @@ -245,7 +245,7 @@ void showInfo() { _renderBoolValue("Circulation pump", EMS_Boiler.wWCirc); _renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow); _renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow); - _renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr); + _renderFloatValue("Flame current", "mA", EMS_Boiler.flameCurr); _renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress); // UBAMonitorSlow diff --git a/platformio.ini-example b/platformio.ini-example index ae260008b..f9bf4eaf3 100644 --- a/platformio.ini-example +++ b/platformio.ini-example @@ -5,7 +5,7 @@ env_default = nodemcuv2 [common] platform = espressif8266 ; optional flags are -DUSE_LED -DSHOWER_TEST -DDEBUG -DUSE_SERIAL -DNO_TX -build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=300 +build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400 build_flags_custom = '-DWIFI_SSID="my_ssid"' '-DWIFI_PASSWORD="my_password"' '-DMQTT_IP="my_broker_ip"' '-DMQTT_USER="my_broker_username"' '-DMQTT_PASS="my_broker_password"' lib_deps = Time diff --git a/src/boiler.ino b/src/boiler.ino index f9bd7c569..5d66cabeb 100644 --- a/src/boiler.ino +++ b/src/boiler.ino @@ -61,7 +61,10 @@ uint8_t regularUpdatesCount = 0; // boiler #define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values +#define TOPIC_BOILER_ MQTT_BOILER "boiler_wwtemp" // warm water selected temp #define TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE MQTT_BOILER "boiler_wwtemp" // warm water selected temp +#define TOPIC_BOILER_TAPWATER_ACTIVE MQTT_BOILER "tapwater_active" // if hot tap water is running +#define TOPIC_BOILER_HEATING_ACTIVE MQTT_BOILER "heating_active" // if heating is on // shower time #define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results @@ -95,7 +98,6 @@ const unsigned long SHOWER_COLDSHOT_DURATION = 5; // in seconds! how long for co const unsigned long SHOWER_OFFSET_TIME = 0; // 0 seconds grace time, to calibrate actual time under the shower #endif -const uint8_t SHOWER_BURNPOWER_MIN = 80; typedef struct { bool wifi_connected; bool boiler_online; @@ -106,7 +108,6 @@ typedef struct { typedef struct { bool showerOn; - bool hotWaterOn; unsigned long timerStart; // ms unsigned long timerPause; // ms unsigned long duration; // ms @@ -124,18 +125,18 @@ netInfo homeNet = {.mqttHost = MQTT_IP, ESPHelper myESP(&homeNet); command_t PROGMEM project_cmds[] = {{"s", "show statistics"}, - {"h", "list supported EMS telegram type ids"}, + {"h", "list EMS telegram type ids with supported logic"}, {"P", "publish all stat to MQTT"}, {"v", "[n] set logging (0=none, 1=basic, 2=verbose)"}, - {"p", "poll ems response on/off (default is off)"}, - {"T", "thermostat monitoring on/off"}, - {"S", "shower timer on/off"}, - {"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"}, + {"p", "toggle EMS Poll response on/off"}, + {"T", "toggle Thermostat monitoring on/off"}, + {"S", "toggle Shower timer on/off"}, + {"A", "toggle shower Alert on/off"}, + {"r", "[n] send EMS request (n=any telegram type id. Use 'h' for suppported types)"}, + {"t", "[n] set thermostat temperature"}, {"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)"}, + {"w", "[n] set boiler warm water temperature (min 30)"}, + {"a", "[n] boiler warm water (1=on, 2=off)"}, {"x", "[n] experimental (warning: for debugging only!)"}}; // calculates size of an 2d array at compile time @@ -163,6 +164,8 @@ unsigned long timestamp; // for internal timings, via millis() static int connectionStatus = NO_CONNECTION; bool startMQTTsent = false; +uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off + // toggle for heartbeat LED bool heartbeatEnabled; @@ -322,6 +325,10 @@ void showInfo() { myDebug("\n\n%sBoiler stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF); + // active stats + myDebug(" Hot tap water is %s\n", (EMS_Boiler.tapwaterActive ? "running" : "off")); + myDebug(" Central Heating is %s\n", (EMS_Boiler.heatingActive ? "active" : "off")); + // UBAParameterWW _renderBoolValue("Warm Water activated", EMS_Boiler.wWActivated); _renderBoolValue("Warm Water circulation pump available", EMS_Boiler.wWCircPump); @@ -348,7 +355,7 @@ void showInfo() { _renderBoolValue("Circulation pump", EMS_Boiler.wWCirc); _renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow); _renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow); - _renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr); + _renderFloatValue("Flame current", "mA", EMS_Boiler.flameCurr); _renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress); // UBAMonitorSlow @@ -392,15 +399,16 @@ void showInfo() { // show the Shower Info if (Boiler_Status.shower_timer) { - myDebug("\n%sShower stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF); - myDebug(" Hot water is %s\n", (Boiler_Shower.hotWaterOn ? "running" : "stopped")); - myDebug(" Shower is %s\n", (Boiler_Shower.showerOn ? "on" : "off")); + myDebug("\n%s Shower stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF); + myDebug(" Shower Timer is %s\n", (Boiler_Shower.showerOn ? "active" : "off")); } myDebug("\n"); } // send values to HA via MQTT +// a json object is created for the boiler and one for the thermostat +// CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic void publishValues(bool force) { char s[20]; // for formatting strings @@ -435,7 +443,7 @@ void publishValues(bool force) { crc.update(data[i]); } uint32_t checksum = crc.finalize(); - //myDebug("HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data); + //myDebug("Boiler HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data); if ((previousBoilerPublishCRC != checksum) || force) { previousBoilerPublishCRC = checksum; @@ -447,6 +455,19 @@ void publishValues(bool force) { myESP.publish(TOPIC_BOILER_DATA, data); } + // see if the heating or hot tap water has changed, if so send + // last_boilerActive stores heating in bit 1 and tap water in bit 2 + if (last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) { + if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { + myDebug("Publishing hot water and heating state via MQTT\n"); + } + myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0"); + myESP.publish(TOPIC_BOILER_HEATING_ACTIVE, EMS_Boiler.heatingActive == 1 ? "1" : "0"); + + last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state + } + + // handle the thermostat values separately if (EMS_Sys_Status.emsThermostatEnabled) { // only send thermostat values if we actually have them @@ -476,6 +497,7 @@ void publishValues(bool force) { crc.update(data[i]); } uint32_t checksum = crc.finalize(); + //myDebug("Thermostat HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data); if ((previousThermostatPublishCRC != checksum) || force) { previousThermostatPublishCRC = checksum; @@ -517,7 +539,7 @@ void myDebugCallback() { ems_setPoll(b); break; case 'P': - myESP.logger(LOG_HA, "Force publish values"); + //myESP.logger(LOG_HA, "Force publish values"); publishValues(true); break; case 'r': // read command for Boiler or Thermostat @@ -751,6 +773,8 @@ void setup() { myESP.addSubscription(TOPIC_SHOWER_TIMER); myESP.addSubscription(TOPIC_SHOWER_ALERT); myESP.addSubscription(TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE); + myESP.addSubscription(TOPIC_BOILER_TAPWATER_ACTIVE); + myESP.addSubscription(TOPIC_BOILER_HEATING_ACTIVE); myESP.addSubscription(TOPIC_SHOWER_COLDSHOT); myESP.consoleSetCallBackProjectCmds(project_cmds, ArraySize(project_cmds), myDebugCallback); // set up Telnet commands @@ -812,11 +836,8 @@ void regularUpdates() { // only do calls if the EMS is connected and alive if (Boiler_Status.boiler_online) { if ((cycle == 0) && Boiler_Status.thermostat_enabled) { - // force get the thermostat mode which is not broadcasted - // important! this is only configured for the RC20. Look up the correct telegram type for other thermostat versions - if (EMS_ID_THERMOSTAT == 0x17) { // RC20 is type 0x17 - ems_doReadCommand(EMS_TYPE_RC20Temperature); - } + // force get the thermostat data which are not usually automatically broadcasted + ems_getThermostatTemps(); } else if (cycle == 1) { ems_doReadCommand(EMS_TYPE_UBAParameterWW); // get Warm Water values } @@ -880,10 +901,11 @@ void loop() { #endif } + // publish the values to MQTT (only if there are changes) // if we received new data and flagged for pushing, do it if (EMS_Sys_Status.emsRefreshed) { EMS_Sys_Status.emsRefreshed = false; - publishValues(true); + publishValues(false); } /* @@ -892,13 +914,8 @@ void loop() { if (Boiler_Status.shower_timer) { // if already in cold mode, ignore all this logic until we're out of the cold blast if (!Boiler_Shower.doingColdShot) { - // these values come from UBAMonitorFast - type 0x18) which is broadcasted every second so our timings are accurate enough - // and no need to fetch the values from the boiler - Boiler_Shower.hotWaterOn = - ((EMS_Boiler.selBurnPow >= SHOWER_BURNPOWER_MIN) && (EMS_Boiler.selFlowTemp == 0) && EMS_Boiler.burnGas); - // is the hot water running? - if (Boiler_Shower.hotWaterOn) { + if (EMS_Boiler.tapwaterActive) { // if heater was previously off, start the timer if (Boiler_Shower.timerStart == 0) { // hot water just started... diff --git a/src/ems.cpp b/src/ems.cpp index 1262cfeec..5ecd3c8bc 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -10,6 +10,16 @@ #include #include +// add you custom setings here like thermostat IDs and thresholds +const uint8_t SHOWER_BURNPOWER_MIN = 80; + +// define here the Thermostat type +#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20 // your thermostat ID + +// define here the boiler power settings (selBurnPow) when hot tap water is running and the heating is on +#define EMS_BOILER_BURNPOWER_TAPWATER 115 +#define EMS_BOILER_BURNPOWER_HEATING 75 + // Check for ESPurna vs ESPHelper (standalone) #ifdef USE_CUSTOM_H #include "debug.h" @@ -41,21 +51,24 @@ bool _process_RC20Temperature(uint8_t * data, uint8_t length); bool _process_RCTempMessage(uint8_t * data, uint8_t length); bool _process_Version(uint8_t * data, uint8_t length); -const _EMS_Types EMS_Types[] = - {{EMS_ID_BOILER, EMS_TYPE_UBAMonitorFast, "UBAMonitorFast", _process_UBAMonitorFast}, - {EMS_ID_BOILER, EMS_TYPE_UBAMonitorSlow, "UBAMonitorSlow", _process_UBAMonitorSlow}, - {EMS_ID_BOILER, EMS_TYPE_UBAMonitorWWMessage, "UBAMonitorWWMessage", _process_UBAMonitorWWMessage}, - {EMS_ID_BOILER, EMS_TYPE_UBAParameterWW, "UBAParameterWW", _process_UBAParameterWW}, - {EMS_ID_BOILER, EMS_TYPE_UBATotalUptimeMessage, "UBATotalUptimeMessage", NULL}, - {EMS_ID_BOILER, EMS_TYPE_UBAMaintenanceSettingsMessage, "UBAMaintenanceSettingsMessage", NULL}, - {EMS_ID_BOILER, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", NULL}, - {EMS_ID_BOILER, EMS_TYPE_UBAMaintenanceStatusMessage, "UBAMaintenanceStatusMessage", NULL}, +const _EMS_Types EMS_Types[] = { - {EMS_ID_THERMOSTAT, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage}, - {EMS_ID_THERMOSTAT, EMS_TYPE_RC20Time, "RC20Time", _process_RC20Time}, - {EMS_ID_THERMOSTAT, EMS_TYPE_RC20Temperature, "RC20Temperature", _process_RC20Temperature}, - {EMS_ID_THERMOSTAT, EMS_TYPE_RCTempMessage, "RCTempMessage", _process_RCTempMessage}, - {EMS_ID_THERMOSTAT, EMS_TYPE_Version, "Version", _process_Version}}; + {EMS_ID_BOILER, EMS_TYPE_UBAMonitorFast, "UBAMonitorFast", _process_UBAMonitorFast}, + {EMS_ID_BOILER, EMS_TYPE_UBAMonitorSlow, "UBAMonitorSlow", _process_UBAMonitorSlow}, + {EMS_ID_BOILER, EMS_TYPE_UBAMonitorWWMessage, "UBAMonitorWWMessage", _process_UBAMonitorWWMessage}, + {EMS_ID_BOILER, EMS_TYPE_UBAParameterWW, "UBAParameterWW", _process_UBAParameterWW}, + {EMS_ID_BOILER, EMS_TYPE_UBATotalUptimeMessage, "UBATotalUptimeMessage", NULL}, + {EMS_ID_BOILER, EMS_TYPE_UBAMaintenanceSettingsMessage, "UBAMaintenanceSettingsMessage", NULL}, + {EMS_ID_BOILER, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", NULL}, + {EMS_ID_BOILER, EMS_TYPE_UBAMaintenanceStatusMessage, "UBAMaintenanceStatusMessage", NULL}, + + {EMS_ID_THERMOSTAT, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage}, + {EMS_ID_THERMOSTAT, EMS_TYPE_RC20Time, "RC20Time", _process_RC20Time}, + {EMS_ID_THERMOSTAT, EMS_TYPE_RC20Temperature, "RC20Temperature", _process_RC20Temperature}, + {EMS_ID_THERMOSTAT, EMS_TYPE_RCTempMessage, "RCTempMessage", _process_RCTempMessage}, + {EMS_ID_THERMOSTAT, EMS_TYPE_Version, "Version", _process_Version} + +}; uint8_t _EMS_Types_max = ArraySize(EMS_Types); // number of defined types // reserve space for the data we collect from the Boiler and Thermostat @@ -89,20 +102,21 @@ uint8_t emsLastRxCount; // used for retries when sending failed // uses -1 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET) void ems_init() { // overall status - EMS_Sys_Status.emsRxPgks = 0; - EMS_Sys_Status.emsTxPkgs = 0; - EMS_Sys_Status.emxCrcErr = 0; - EMS_Sys_Status.emsRxStatus = EMS_RX_IDLE; - EMS_Sys_Status.emsTxStatus = EMS_TX_IDLE; - EMS_Sys_Status.emsLastPoll = 0; - EMS_Sys_Status.emsLastRx = 0; - EMS_Sys_Status.emsLastTx = 0; - EMS_Sys_Status.emsRefreshed = false; - + EMS_Sys_Status.emsRxPgks = 0; + EMS_Sys_Status.emsTxPkgs = 0; + EMS_Sys_Status.emxCrcErr = 0; + EMS_Sys_Status.emsRxStatus = EMS_RX_IDLE; + EMS_Sys_Status.emsTxStatus = EMS_TX_IDLE; + EMS_Sys_Status.emsLastPoll = 0; + EMS_Sys_Status.emsLastRx = 0; + EMS_Sys_Status.emsLastTx = 0; + EMS_Sys_Status.emsRefreshed = false; EMS_Sys_Status.emsPollEnabled = false; // start up with Poll disabled EMS_Sys_Status.emsThermostatEnabled = true; // there is a RCxx thermostat active as default EMS_Sys_Status.emsLogging = EMS_SYS_LOGGING_NONE; // Verbose logging is off + // thermostat + EMS_Thermostat.type = EMS_ID_THERMOSTAT; // type, see ems.h EMS_Thermostat.hour = 0; EMS_Thermostat.minute = 0; EMS_Thermostat.second = 0; @@ -146,6 +160,9 @@ void ems_init() { EMS_Boiler.wWWorkM = EMS_VALUE_INT_NOTSET; // Warm Water # minutes EMS_Boiler.wWOneTime = EMS_VALUE_INT_NOTSET; // Warm Water one time function on/off + EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off + EMS_Boiler.heatingActive = EMS_VALUE_INT_NOTSET; // Central heating is on/off + // init the Tx package _initTxBuffer(); } @@ -316,13 +333,18 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { && ((millis() - EMS_Sys_Status.emsLastTx) > RX_READ_TIMEOUT)) { if (emsLastRxCount++ >= RX_READ_TIMEOUT_COUNT) { // give up and reset tx - myDebug("Error! Failed to send telegram, cancelling last write command.\n"); + if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { + myDebug("Error! Failed to send telegram, cancelling last write command.\n"); + } + // re-initialise _initTxBuffer(); } else { - myDebug("Didn't receive acknowledgement from the 0x%02x, so resending (attempt #%d/%d)...\n", - EMS_TxTelegram.type, - emsLastRxCount, - RX_READ_TIMEOUT_COUNT); + if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { + myDebug("Didn't receive acknowledgement from the 0x%02x, so resending (attempt #%d/%d)...\n", + EMS_TxTelegram.type, + emsLastRxCount, + RX_READ_TIMEOUT_COUNT); + } EMS_Sys_Status.emsTxStatus = EMS_TX_PENDING; // set to pending will trigger sending the same package again } } @@ -415,10 +437,11 @@ void _processType(uint8_t * telegram, uint8_t length) { // we have a match typeFound = true; // call callback to fetch the values from the telegram - // ignoring the return value for now + // return value tells us if we need to force send values back to MQTT if ((EMS_Types[i].processType_cb) != (void *)NULL) { - (void)EMS_Types[i].processType_cb(data, length); + EMS_Sys_Status.emsRefreshed = EMS_Types[i].processType_cb(data, length); } + break; } i++; @@ -530,7 +553,7 @@ bool _checkWriteQueueFull() { myDebug("Delaying write command as there is already a telegram (type 0x%02x) in the queue\n", EMS_TxTelegram.type); } - return true; + return true; // something in queue } return false; // nothing queue, we can do a write command @@ -538,7 +561,7 @@ bool _checkWriteQueueFull() { /* * UBAParameterWW - type 0x33 - warm water parameters - * received only after requested + * received only after requested (not broadcasted) */ bool _process_UBAParameterWW(uint8_t * data, uint8_t length) { EMS_Boiler.wWSelTemp = data[2]; @@ -546,9 +569,7 @@ bool _process_UBAParameterWW(uint8_t * data, uint8_t length) { EMS_Boiler.wWCircPump = (data[6] == 0xFF); // 0xFF means on EMS_Boiler.wWDesiredTemp = data[8]; - EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back to Home Assistant via MQTT - - return true; + return true; // triggers a send the values back to Home Assistant via MQTT } /* @@ -561,7 +582,7 @@ bool _process_UBAMonitorWWMessage(uint8_t * data, uint8_t length) { EMS_Boiler.wWWorkM = _toLong(10, data); EMS_Boiler.wWOneTime = bitRead(data[5], 1); - return true; + return false; // no need to update mqtt } /* @@ -573,16 +594,23 @@ bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) { EMS_Boiler.curFlowTemp = _toFloat(1, data); EMS_Boiler.retTemp = _toFloat(13, data); - uint8_t v = data[7]; - EMS_Boiler.burnGas = bitRead(v, 0); - EMS_Boiler.fanWork = bitRead(v, 2); - EMS_Boiler.ignWork = bitRead(v, 3); - EMS_Boiler.heatPmp = bitRead(v, 5); - EMS_Boiler.wWHeat = bitRead(v, 6); - EMS_Boiler.wWCirc = bitRead(v, 7); - - EMS_Boiler.selBurnPow = data[3]; // max power + uint8_t v = data[7]; + EMS_Boiler.burnGas = bitRead(v, 0); + EMS_Boiler.fanWork = bitRead(v, 2); + EMS_Boiler.ignWork = bitRead(v, 3); + EMS_Boiler.heatPmp = bitRead(v, 5); + EMS_Boiler.wWHeat = bitRead(v, 6); + EMS_Boiler.wWCirc = bitRead(v, 7); EMS_Boiler.curBurnPow = data[4]; + EMS_Boiler.selBurnPow = data[3]; // burn power max setting + + // check if the boiler is providing hot water to the tap or hot water to the central heating + // we use a quick hack: + // the heating on, if burner selected max power = 75 (UBAMonitorFast:EMS_Boiler.selBurnPow) + // hot tap water running, if burner selected max power=115 (UBAMonitorFast:EMS_Boiler.selBurnPow) + // we could also add (EMS_Boiler.selFlowTemp == 0) && EMS_Boiler.burnGas) for more precision + EMS_Boiler.tapwaterActive = ((EMS_Boiler.selBurnPow == EMS_BOILER_BURNPOWER_TAPWATER) ? 1 : 0); + EMS_Boiler.heatingActive = ((EMS_Boiler.selBurnPow == EMS_BOILER_BURNPOWER_HEATING) ? 1 : 0); EMS_Boiler.flameCurr = _toFloat(15, data); @@ -592,7 +620,7 @@ bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) { EMS_Boiler.sysPress = (((float)data[17]) / (float)10); } - return true; + return false; // no need to update mqtt } /* @@ -607,9 +635,7 @@ bool _process_UBAMonitorSlow(uint8_t * data, uint8_t length) { EMS_Boiler.burnWorkMin = _toLong(13, data); EMS_Boiler.heatWorkMin = _toLong(19, data); - EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back to Home Assistant via MQTT - - return true; + return true; // triggers a send the values back to Home Assistant via MQTT } /* @@ -620,9 +646,7 @@ bool _process_RC20StatusMessage(uint8_t * data, uint8_t length) { EMS_Thermostat.setpoint_roomTemp = ((float)data[1]) / (float)2; EMS_Thermostat.curr_roomTemp = _toFloat(2, data); - EMS_Sys_Status.emsRefreshed = true; // set the updated flag to trigger a send back to HA - - return true; + return true; // triggers a send the values back to Home Assistant via MQTT } /* @@ -642,13 +666,12 @@ bool _process_RC20Temperature(uint8_t * data, uint8_t length) { } // and send the values back to HA (Home Assistant MQTT) - EMS_Sys_Status.emsRefreshed = true; - } else { - // Process the whole telegram package - EMS_Thermostat.mode = data[EMS_OFFSET_RC20Temperature_mode]; // get the mode + return true; } - return true; + // Process the whole telegram package + EMS_Thermostat.mode = data[EMS_OFFSET_RC20Temperature_mode]; // get the mode + return false; // don't update mqtt } /* @@ -657,21 +680,24 @@ bool _process_RC20Temperature(uint8_t * data, uint8_t length) { bool _process_RCTempMessage(uint8_t * data, uint8_t length) { // add support here if you're reading external sensors - return true; + return false; // don't update mqtt } /* * Version - type 0x02 - get the version of the Thermostat firmware - * We don't bother storing these values anywhere + * When a thermostat is connecting it will send out 0x02 messages too, which we'll ignore + * We don't bother storing these values anywhere, just print */ bool _process_Version(uint8_t * data, uint8_t length) { - uint8_t major = data[1]; - uint8_t minor = data[2]; + // ignore short messages + if (length == 8) { + uint8_t major = data[1]; + uint8_t minor = data[2]; + myDebug("Version %d.%d\n", major, minor); + } - myDebug("Version %d.%d\n", major, minor); - - return true; + return false; // don't update mqtt } /* @@ -696,7 +722,7 @@ bool _process_RC20Time(uint8_t * data, uint8_t length) { EMS_Thermostat.year + 2000); */ - return true; + return false; // don't update mqtt } /* @@ -720,6 +746,15 @@ void _buildTxTelegram(uint8_t data_value) { EMS_Sys_Status.emsTxStatus = EMS_TX_PENDING; // armed and ready to send } +/* + * Generic function to return temperature settings from the thermostat + */ +void ems_getThermostatTemps() { + if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_RC20) { + ems_doReadCommand(EMS_TYPE_RC20Temperature); + } +} + /* * Send a command to UART Tx to Read from another device * Read commands when sent must respond by the destination (target) immediately (or within 10ms) diff --git a/src/ems.h b/src/ems.h index 97b2ef6d4..c83a62289 100644 --- a/src/ems.h +++ b/src/ems.h @@ -21,8 +21,10 @@ #define EMS_TX_MAXBUFFERSIZE 128 // max size of the buffer. packets are 32 bits -// define here the Thermostat type -#define EMS_ID_THERMOSTAT 0x17 // x17=RC20 (Moduline300), x10=RC30/RC35 (Moduline 400) +#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (older Moduline 300) +#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (Moduline 300) +#define EMS_ID_THERMOSTAT_RC35 0x10 // RC35 (Moduline 400) +#define EMS_ID_THERMOSTAT_EASY 0x18 // Nefit Easy // define here the EMS telegram types you need @@ -143,10 +145,16 @@ typedef struct { uint32_t wWStarts; // Warm Water # starts uint32_t wWWorkM; // Warm Water # minutes uint8_t wWOneTime; // Warm Water one time function on/off + + // calculated values + uint8_t tapwaterActive; // Hot tap water is on/off + uint8_t heatingActive; // Central heating is on/off + } _EMS_Boiler; -// RC20 data +// Thermostat data typedef struct { + uint8_t type; // thermostat type (RC20, RC30, RC35 etc) float setpoint_roomTemp; // current set temp float curr_roomTemp; // current room temp uint8_t mode; // 0=low, 1=manual, 2=auto @@ -183,22 +191,26 @@ typedef struct { #define COLOR_BOLD_OFF "\x1B[21m" // function definitions -extern void ems_parseTelegram(uint8_t * telegram, uint8_t len); -void ems_init(); -void ems_doReadCommand(uint8_t type); -void ems_setThermostatTemp(float temp); -void ems_setThermostatMode(uint8_t mode); -void ems_setWarmWaterTemp(uint8_t temperature); -void ems_setWarmWaterActivated(bool activated); -void ems_setExperimental(uint8_t value); -void ems_setPoll(bool b); +extern void ems_parseTelegram(uint8_t * telegram, uint8_t len); +void ems_init(); +void ems_doReadCommand(uint8_t type); + +void ems_setThermostatTemp(float temp); +void ems_setThermostatMode(uint8_t mode); +void ems_setWarmWaterTemp(uint8_t temperature); +void ems_setWarmWaterActivated(bool activated); +void ems_setExperimental(uint8_t value); +void ems_setPoll(bool b); +void ems_setThermostatEnabled(bool b); +void ems_setLogging(_EMS_SYS_LOGGING loglevel); + +void ems_getThermostatTemps(); bool ems_getPoll(); bool ems_getThermostatEnabled(); -void ems_setThermostatEnabled(bool b); -void ems_setLogging(_EMS_SYS_LOGGING loglevel); _EMS_SYS_LOGGING ems_getLogging(); uint8_t ems_getEmsTypesCount(); -void ems_printAllTypes(); + +void ems_printAllTypes(); // private functions uint8_t _crcCalculator(uint8_t * data, uint8_t len);