diff --git a/README.md b/README.md index 4222eca2c..07c07aa9c 100644 --- a/README.md +++ b/README.md @@ -9,32 +9,32 @@ There are 3 parts to this project, first the design of the circuit, second the c [![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE) - [EMS-ESP-Boiler](#ems-esp-boiler) - - [Introduction](#introduction) - - [Supported Boilers Types](#supported-boilers-types) - - [Supported ESP8266 devices](#supported-esp8266-devices) - - [Getting Started](#getting-started) - - [Monitoring The Output](#monitoring-the-output) - - [Building The Circuit](#building-the-circuit) - - [Powering The EMS Circuit](#powering-the-ems-circuit) - - [How The EMS Bus Works](#how-the-ems-bus-works) - - [EMS IDs](#ems-ids) - - [EMS Polling](#ems-polling) - - [EMS Broadcasting](#ems-broadcasting) - - [EMS Reading and Writing](#ems-reading-and-writing) - - [The ESP8266 Source Code](#the-esp8266-source-code) - - [Supported EMS Types](#supported-ems-types) - - [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) - - [Home Assistant Configuration](#home-assistant-configuration) - - [Building The Firmware](#building-the-firmware) - - [Using PlatformIO Standalone](#using-platformio-standalone) - - [Using ESPurna](#using-espurna) - - [Using Pre-built Firmware](#using-pre-built-firmware) - - [Building Using Arduino IDE](#building-using-arduino-ide) - - [Known Issues and ToDo's](#known-issues-and-todos) - - [Your Comments and Feedback](#your-comments-and-feedback) + - [Introduction](#introduction) + - [Supported Boilers Types](#supported-boilers-types) + - [Supported ESP8266 devices](#supported-esp8266-devices) + - [Getting Started](#getting-started) + - [Monitoring The Output](#monitoring-the-output) + - [Building The Circuit](#building-the-circuit) + - [Powering The EMS Circuit](#powering-the-ems-circuit) + - [How The EMS Bus Works](#how-the-ems-bus-works) + - [EMS IDs](#ems-ids) + - [EMS Polling](#ems-polling) + - [EMS Broadcasting](#ems-broadcasting) + - [EMS Reading and Writing](#ems-reading-and-writing) + - [The ESP8266 Source Code](#the-esp8266-source-code) + - [Supported EMS Types](#supported-ems-types) + - [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) + - [Home Assistant Configuration](#home-assistant-configuration) + - [Building The Firmware](#building-the-firmware) + - [Using PlatformIO Standalone](#using-platformio-standalone) + - [Using ESPurna](#using-espurna) + - [Using Pre-built Firmware](#using-pre-built-firmware) + - [Building Using Arduino IDE](#building-using-arduino-ide) + - [Known Issues and ToDo's](#known-issues-and-todos) + - [Your Comments and Feedback](#your-comments-and-feedback) ## Introduction @@ -173,7 +173,7 @@ The tables below shows which types are broadcasted regularly by the boiler (ID 0 | Source (ID) | Type ID | Name | Description | Frequency | | ----------------- | ------- | ----------------- | --------------------------------------------------- | ---------- | -| Thermostat (0x17) | 0x06 | RC20Time | returns time and date on the thermostat | 60 seconds | +| Thermostat (0x17) | 0x06 | RCTime | returns time and date on the thermostat | 60 seconds | | Thermostat (0x17) | 0x91 | RC20StatusMessage | returns current and set temperatures | 60 seconds | | Thermostat (0x17) | 0xA3 | RCTempMessage | returns temp values from external (outdoor) sensors | 60 seconds | @@ -233,11 +233,11 @@ I will add further support for the other thermostats (such as the Nefit Easy) as ### Customizing The Code -Most of the changes will be done in `boiler.ino` and `ems.cpp`. - -- To add new handlers for data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h` -- To change your thermostat type set `EMS_ID_THERMOSTAT` in `ems.cpp`. The default is 0x17 for an RC20. -- The `#DEFINES` `BOILER_THERMOSTAT_ENABLED`, `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER` enabled the thermostat logic, the shower logic and the shower timer alert logic respectively. 1 is on (enabled) and 0 is off (disabled). +- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can + - set the thermostat type. The default ID is 0x17 for an RC20 Moduline 300. + - set flags for enabled/disabling functionality such as `BOILER_THERMOSTAT_ENABLED`, `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`. + - Set WIFI and MQTT settings, instead of doing this in `platformio.ini` +- To add new handlers for EMS data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h` ### Using MQTT diff --git a/src/boiler.ino b/src/boiler.ino index 5d66cabeb..797887510 100644 --- a/src/boiler.ino +++ b/src/boiler.ino @@ -3,13 +3,14 @@ * Paul Derbyshire - May 2018 - https://github.com/proddy/EMS-ESP-Boiler * https://community.home-assistant.io/t/thermostat-and-boiler-controller-for-ems-based-boilers-nefit-buderus-bosch-using-esp/53382 * - * See ReadMe.md for Acknowledgments + * See README for Acknowledgments */ // local libraries #include "ESPHelper.h" #include "ems.h" #include "emsuart.h" +#include "my_config.h" // public libraries #include // https://github.com/bblanchon/ArduinoJson @@ -18,30 +19,21 @@ // standard arduino libs #include // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker -// these are set as -D build flags during compilation -//#define WIFI_SSID "" -//#define WIFI_PASSWORD "" -//#define MQTT_IP "" -//#define MQTT_USER "" -//#define MQTT_PASS "" - // timers, all values are in seconds #define PUBLISHVALUES_TIME 300 // every 5 mins post HA values #define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests #define REGULARUPDATES_TIME 60 // every minute a call is made, so for our 2 calls theres a write cmd every 30seconds #define HEARTBEAT_TIME 1 // every second blink heartbeat LED +#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates) + Ticker publishValuesTimer; Ticker systemCheckTimer; Ticker regularUpdatesTimer; Ticker heartbeatTimer; Ticker showerColdShotStopTimer; uint8_t regularUpdatesCount = 0; -#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates) // GPIOs -#define LED_RX D1 // GPIO5 -#define LED_TX D2 // GPIO4 -#define LED_ERR D3 // GPIO0 #define LED_HEARTBEAT LED_BUILTIN // onboard LED // hostname is also used as the MQTT topic identifier (home/) @@ -73,24 +65,16 @@ uint8_t regularUpdatesCount = 0; #define TOPIC_SHOWER_ALERT MQTT_BOILER "shower_alert" // toggle switch for enabling the shower alarm logic #define TOPIC_SHOWER_COLDSHOT MQTT_BOILER "shower_coldshot" // used to trigger a coldshot from HA publish -// default values -// thermostat support, shower timing and shower alert all enabled (1) (disabled = 0) -#define BOILER_THERMOSTAT_ENABLED 1 -#define BOILER_SHOWER_TIMER 1 -#define BOILER_SHOWER_ALERT 0 - // logging - EMS_SYS_LOGGING_VERBOSE, EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC (see ems.h) #define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_NONE -// shower settings -#ifndef SHOWER_TEST -const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower -const unsigned long SHOWER_MIN_DURATION = 180000; // 3 minutes, before recognizing its a shower -const unsigned long SHOWER_MAX_DURATION = 420000; // 7 minutes, before trigger a shot of cold water -const unsigned long SHOWER_COLDSHOT_DURATION = 5; // 5 seconds for cold water - note, must be in seconds -const unsigned long SHOWER_OFFSET_TIME = 8000; // 8 seconds grace time, to calibrate actual time under the shower -#else -// for DEBUGGING only +// shower settings for DEBUGGING only +#ifdef SHOWER_TEST +#undef SHOWER_PAUSE_TIME +#undef SHOWER_MIN_DURATION +#undef SHOWER_MAX_DURATION +#undef SHOWER_COLDSHOT_DURATION +#undef SHOWER_OFFSET_TIME const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower const unsigned long SHOWER_MIN_DURATION = 20000; // 20 secs, before recognizing its a shower const unsigned long SHOWER_MAX_DURATION = 25000; // 25 secs, before trigger a shot of cold water @@ -355,7 +339,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", "mA", EMS_Boiler.flameCurr); + _renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr); _renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress); // UBAMonitorSlow @@ -393,7 +377,8 @@ void showInfo() { } else if (EMS_Thermostat.mode == 2) { myDebug("auto\n"); } else { - myDebug("<%d>\n", EMS_Thermostat.mode); + myDebug("?\n"); + // myDebug("? (value is %d)\n", EMS_Thermostat.mode); } } @@ -790,6 +775,7 @@ void setup() { // flash LEDs // Using a faster way to write to pins as digitalWrite does a lot of overhead like pin checking & disabling interrupts void showLEDs() { +#ifdef USE_LED // ERR LED if (!Boiler_Status.boiler_online) { WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 4, (1 << LED_ERR)); // turn on @@ -806,6 +792,7 @@ void showLEDs() { // because sends are quick, if we did a recent send show the LED for a short while uint64_t t = (timestamp - EMS_Sys_Status.emsLastTx); WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + ((t < TX_HOLD_LED_TIME) ? 4 : 8), (1 << LED_TX)); +#endif } // heartbeat callback to light up the LED, called via Ticker diff --git a/src/ems.cpp b/src/ems.cpp index 5ecd3c8bc..32c824b49 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -10,16 +10,6 @@ #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" @@ -31,6 +21,9 @@ extern ESPHelper myESP; #define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__) #endif +// include custom configuration settings +#include "my_config.h" + // calculates size of an 2d array at compile time template constexpr size_t ArraySize(T (&)[N]) { @@ -46,10 +39,11 @@ bool _process_UBAMonitorSlow(uint8_t * data, uint8_t length); bool _process_UBAMonitorWWMessage(uint8_t * data, uint8_t length); bool _process_UBAParameterWW(uint8_t * data, uint8_t length); bool _process_RC20StatusMessage(uint8_t * data, uint8_t length); -bool _process_RC20Time(uint8_t * data, uint8_t length); +bool _process_RCTime(uint8_t * data, uint8_t length); 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); +bool _process_SetPoints(uint8_t * data, uint8_t length); const _EMS_Types EMS_Types[] = { @@ -63,10 +57,11 @@ const _EMS_Types EMS_Types[] = { {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_RCTime, "RCTime", _process_RCTime}, {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_THERMOSTAT, EMS_TYPE_Version, "Version", _process_Version}, + {EMS_ID_THERMOSTAT, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints} }; uint8_t _EMS_Types_max = ArraySize(EMS_Types); // number of defined types @@ -129,7 +124,7 @@ void ems_init() { EMS_Boiler.wWActivated = EMS_VALUE_INT_NOTSET; // Warm Water activated EMS_Boiler.wWSelTemp = EMS_VALUE_INT_NOTSET; // Warm Water selected temperature EMS_Boiler.wWCircPump = EMS_VALUE_INT_NOTSET; // Warm Water circulation pump available - EMS_Boiler.wWDesiredTemp = EMS_VALUE_INT_NOTSET; // Warm Water desired temperature + EMS_Boiler.wWDesiredTemp = EMS_VALUE_INT_NOTSET; // Warm Water desired temperature to prevent infection // UBAMonitorFast EMS_Boiler.selFlowTemp = EMS_VALUE_INT_NOTSET; // Selected flow temperature @@ -237,7 +232,7 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len) { return crc; } -// function to turn a telegram int (2 bytes) to a float +// function to turn a telegram int (2 bytes) to a float. The source is *10 float _toFloat(uint8_t i, uint8_t * data) { if ((data[i] == 0x80) && (data[i + 1] == 0)) // 0x8000 is used when sensor is missing return (float)-1; // return -1 to indicate that is unknown @@ -559,6 +554,22 @@ bool _checkWriteQueueFull() { return false; // nothing queue, we can do a write command } +/* + * Check if hot tap water or heating is active + * using a quick hack: + * heating is on if Selected Flow Temp >= 70 (in my case) + * tap water is on if Selected Flow Temp = 0 and Selected Burner Power >= 115 + */ +bool _checkActive() { + // hot tap water + EMS_Boiler.tapwaterActive = + ((EMS_Boiler.selFlowTemp == 0) + && (EMS_Boiler.selBurnPow >= EMS_BOILER_BURNPOWER_TAPWATER) & (EMS_Boiler.burnGas == EMS_VALUE_INT_ON)); + + EMS_Boiler.heatingActive = + ((EMS_Boiler.selFlowTemp >= EMS_BOILER_SELFLOWTEMP_HEATING) && (EMS_Boiler.burnGas == EMS_VALUE_INT_ON)); +} + /* * UBAParameterWW - type 0x33 - warm water parameters * received only after requested (not broadcasted) @@ -594,24 +605,17 @@ 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); + 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); if (data[17] == 0xFF) { // missing value for system pressure @@ -620,6 +624,9 @@ bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) { EMS_Boiler.sysPress = (((float)data[17]) / (float)10); } + // at this point do a quick check to see if the hot water or heating is active + (void)_checkActive(); + return false; // no need to update mqtt } @@ -694,16 +701,34 @@ bool _process_Version(uint8_t * data, uint8_t length) { if (length == 8) { uint8_t major = data[1]; uint8_t minor = data[2]; - myDebug("Version %d.%d\n", major, minor); + if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { + myDebug("Version %d.%d\n", major, minor); + } } return false; // don't update mqtt } /* - * process_RC20Time - type 0x06 - date and time from the RC20 thermostat (0x17) - 14 bytes long + * UBASetPoint 0x1A */ -bool _process_RC20Time(uint8_t * data, uint8_t length) { +bool _process_SetPoints(uint8_t * data, uint8_t length) { + uint8_t setpoint = data[0]; + uint8_t hk_power = data[1]; + uint8_t ww_power = data[2]; + + if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { + myDebug("UBASetPoint: SetPoint=%d, hk_power=%d ww_power=%d\n", setpoint, hk_power, ww_power); + } + + return false; +} + + +/* + * process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long + */ +bool _process_RCTime(uint8_t * data, uint8_t length) { EMS_Thermostat.hour = data[2]; EMS_Thermostat.minute = data[4]; EMS_Thermostat.second = data[5]; diff --git a/src/ems.h b/src/ems.h index c83a62289..91b5e0c49 100644 --- a/src/ems.h +++ b/src/ems.h @@ -37,10 +37,11 @@ #define EMS_TYPE_UBATotalUptimeMessage 0x14 #define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15 #define EMS_TYPE_UBAParametersMessage 0x16 +#define EMS_TYPE_UBASetPoints 0x1A // Thermostat... #define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast -#define EMS_TYPE_RC20Time 0x06 // is an automatic thermostat broadcast +#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast #define EMS_TYPE_RCTempMessage 0xA3 // is an automatic thermostat broadcast #define EMS_TYPE_RC20Temperature 0xA8 #define EMS_TYPE_Version 0x02 // version of the UBA controller diff --git a/src/my_config.h b/src/my_config.h new file mode 100644 index 000000000..4281cc354 --- /dev/null +++ b/src/my_config.h @@ -0,0 +1,46 @@ +/* + * my_config.h + * All configurations and customization's go here + * + * Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler + */ + +#pragma once + +// these are set as -D build flags during compilation +// they can be set in platformio.ini or alternatively hard coded here + +// WIFI settings +//#define WIFI_SSID "" +//#define WIFI_PASSWORD "" + +// MQTT settings +// Note port is the default 1883 +//#define MQTT_IP "" +//#define MQTT_USER "" +//#define MQTT_PASS "" + +// default values +#define BOILER_THERMOSTAT_ENABLED 1 // thermostat support is enabled (1) +#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken +#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded + +// define here the Thermostat type. see ems.h for options +#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20 // your thermostat ID + +// 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 + +// if using the shower timer, change these settings (in ms) +#define SHOWER_PAUSE_TIME 15000 // 15 seconds, max time if water is switched off & on during a shower +#define SHOWER_MIN_DURATION 180000 // 3 minutes, before recognizing its a shower +#define SHOWER_MAX_DURATION 420000 // 7 minutes, before trigger a shot of cold water +#define SHOWER_COLDSHOT_DURATION 5 // 5 seconds for cold water - note, must be in seconds +#define SHOWER_OFFSET_TIME 8000 // 8 seconds grace time, to calibrate actual time under the shower + +// if using LEDs to show traffic, configure the GPIOs here +// only works if -DUSE_LED is set in platformio.ini +#define LED_RX D1 // GPIO5 +#define LED_TX D2 // GPIO4 +#define LED_ERR D3 // GPIO0