From ae70b9eb8661974980ccadbc73aa17975f5b7e07 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 27 May 2018 17:01:13 +0200 Subject: [PATCH] replaced Ticker library --- README.md | 50 +++++++++---------- lib/ESPHelper/ESPHelper.cpp | 44 +++++------------ platformio.ini-example | 1 - src/boiler.ino | 98 +++++++++++++++++-------------------- 4 files changed, 81 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 62ef96c54..7615cb2fa 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,9 @@ My circuit will work with both 3.3V and 5V. It's easiest though to power directl Powering the ESP89266 can be either via the USB from a PC or external 5V power supply or from the EMS line itself using a buck step-down converter. The EMS provides about 15V AC current. The advantage of using the EMS is obviously less power cables and it's neater to place inline with the thermostat. I use a [Pololu D24C22F5](https://www.pololu.com/product/2858) which is 5V/2A buck step-down module and probably slightly overkill for what we need. The additional part of the circuit is shown below along with an earlier breadboard prototype using a NodeMCU2 (with the additional LEDs): -Power circuit | Example | ---- | --- -![Power circuit](doc/schematics/power.PNG) | ![Breadboard](doc/schematics/breadboard_example.png) | +| Power circuit | Example | +| ------------------------------------------ | ---------------------------------------------------- | +| ![Power circuit](doc/schematics/power.PNG) | ![Breadboard](doc/schematics/breadboard_example.png) | ## Known Issues and ToDo's @@ -176,19 +176,19 @@ When a device is broadcasting to everyone there is no specific destination neede The Boiler (ID 0x08) will send out these broadcast telegrams regularly: -Type | Description (see [here](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme)) | Data length (excluding header) | Frequency ---- | --- | --- | --- | -0x34 | UBAMonitorWWMessage | 19 bytes | 10 seconds -0x18 | UBAMonitorFast | 25 bytes | 10 seconds -0x19 | UBAMonitorSlow | 22 bytes | every minute -0x1C | UBAWartungsmelding | 27 bytes | every minute -0x2A | status, specific to boiler type | - | 10 seconds +| Type | Description (see [here](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme)) | Data length (excluding header) | Frequency | +| ---- | ---------------------------------------------------------------------------------------- | ------------------------------ | ------------ | +| 0x34 | UBAMonitorWWMessage | 19 bytes | 10 seconds | +| 0x18 | UBAMonitorFast | 25 bytes | 10 seconds | +| 0x19 | UBAMonitorSlow | 22 bytes | every minute | +| 0x1C | UBAWartungsmelding | 27 bytes | every minute | +| 0x2A | status, specific to boiler type | - | 10 seconds | And a thermostat (ID 0x17 for a RC20) would broadcast these messages regularly: -Type | Description ---- | --- | -0x06 | time on thermostat Y,M,H,D,M,S,wd +| Type | Description | +| ---- | ----------- | undefined |undefined |undefined |undefined |undefined | +| 0x06 | time on thermostat Y,M,H,D,M,S,wd | Refer to the code in `ems.cpp` for further explanation on how to parse these message types and also reference the EMS Wiki. @@ -211,7 +211,6 @@ Every telegram sent is echo'd back to Rx. * Time http://playground.arduino.cc/code/time * PubSubClient http://pubsubclient.knolleary.net * ArduinoJson https://github.com/bblanchon/ArduinoJson - * Ticker https://github.com/sstaub/Ticker `src\emsuart.cpp` handles the low level UART read and write logic. You shouldn't need to touch this. All receive commands from the EMS bus are handled asynchronously using a circular buffer via an interrupt. A separate function processes the buffer and extracts the telegrams. Since we don't send too many write commands this is done sequentially. I couldn't use the standard Arduino Serial implementation because of the 11-bit break signal causes a frame-error which gets ignored. @@ -225,15 +224,15 @@ Every telegram sent is echo'd back to Rx. `ems.cpp` defines callback functions that handle all the broadcast types listed above (e.g. 0x34, 0x18, 0x19 etc) plus these extra types: -Device | Type | Description | What ---- | --- | --- | --- | -Boiler (0x08) | 0x33 | UBAParameterWW | reads selected & desired warm water temp -Boiler (0x08) | 0x14 | UBATotalUptimeMessage | -Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | -Boiler (0x08) | 0x16 | UBAParametersMessage | -Thermostat (0x17) | 0xA8 | RC20Temperature | sets temperature and operating modes -Thermostat (0x17) | 0xA3 | RCOutdoorTempMessage | -Thermostat (0x17) | 0x91 | RC20StatusMessage | reads set & current room temperatures +| Device | Type | Description | What | +| ----------------- | ---- | ----------------------------- | ---------------------------------------- | +| Boiler (0x08) | 0x33 | UBAParameterWW | reads selected & desired warm water temp | +| Boiler (0x08) | 0x14 | UBATotalUptimeMessage | | +| Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | | +| Boiler (0x08) | 0x16 | UBAParametersMessage | | +| Thermostat (0x17) | 0xA8 | RC20Temperature | sets temperature and operating modes | +| Thermostat (0x17) | 0xA3 | RCOutdoorTempMessage | | +| Thermostat (0x17) | 0x91 | RC20StatusMessage | reads set & current room temperatures | Note the thermostat types are based on a RC20 model thermostat. If using an RC30/RC35 use types 0x3E and 0x48 to read the values. @@ -427,10 +426,10 @@ Next copy the files custom.h, index.html, boiler.ino and the esp*.cpp/h files fr I will eventually put pre-built version based on ESPurna in the directory `/firmware` which you can upload using esptool (https://github.com/espressif/esptool) bootloader. On Windows, follow these instructions: -1. Check if you have python 2.7 installed. If not [download it](https://www.python.org/downloads/) and make sure you add Python to the windows PATH so it'll recognize .py files. +1. Check if you have python 2.7 installed. If not [download it](https://www.python.org/downloads/) and make sure you select the option to add Python to the windows PATH. 2. Install the ESPTool by running `pip install esptool` from a command prompt. 3. Connect the ESP via USB, figure out the COM port. -4. run `esptool.py -p write_flash 0x00000 ` where firmware is the .bin file and \ is the com port, e.g. COM3 +4. run `esptool.py -p write_flash 0x00000 ` where firmware is the `.bin` file and \ is the COM port, e.g. `COM3` ## Using Arduino IDE (*unsupported!*) @@ -449,7 +448,6 @@ Porting to the Arduino is tricky and messy (which is one of the reasons I don't #define MQTT_PASS "" ``` * Put all the files in a single sketch folder (`ESPHelper.*, boiler.ino, ems.*, emsuart.*`) -* Download the new and improved Ticker library from https://github.com/sstaub/Ticker copying the .cpp and .h files to the same folder you just created * Possibly change some the #includes to use the local files, replacing `` with `"lib"` * cross your fingers and CTRL-R to compile... diff --git a/lib/ESPHelper/ESPHelper.cpp b/lib/ESPHelper/ESPHelper.cpp index a72328362..83ad5c8ac 100644 --- a/lib/ESPHelper/ESPHelper.cpp +++ b/lib/ESPHelper/ESPHelper.cpp @@ -119,13 +119,9 @@ bool ESPHelper::begin(const char * hostname) { if (_mqttSet) { //make mqtt client use either the secure or non-secure wifi client depending on the setting if (_useSecureClient) { - client = PubSubClient(_currentNet.mqttHost, - _currentNet.mqttPort, - wifiClientSecure); + client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClientSecure); } else { - client = PubSubClient(_currentNet.mqttHost, - _currentNet.mqttPort, - wifiClient); + client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClient); } //set the mqtt message callback if needed @@ -139,13 +135,9 @@ bool ESPHelper::begin(const char * hostname) { //make mqtt client use either the secure or non-secure wifi client depending on the setting //(this shouldnt be needed if making a dummy connection since the idea would be that there wont be mqtt in this case) if (_useSecureClient) { - client = PubSubClient("192.168.1.255", - _currentNet.mqttPort, - wifiClientSecure); + client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClientSecure); } else { - client = PubSubClient("192.168.1.255", - _currentNet.mqttPort, - wifiClient); + client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClient); } } @@ -161,8 +153,7 @@ bool ESPHelper::begin(const char * hostname) { timeout++; } }); - ArduinoOTA.onProgress([](unsigned int progress, - unsigned int total) { /* ota progress code */ }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { /* ota progress code */ }); ArduinoOTA.onError([](ota_error_t error) { /* ota error code */ }); //initially attempt to connect to wifi when we begin (but only block for 2 seconds before timing out) @@ -229,8 +220,7 @@ void ESPHelper::end() { int ESPHelper::loop() { if (_ssidSet) { //check for good connections and attempt a reconnect if needed - if (((_mqttSet && !client.connected()) || setConnectionStatus() < WIFI_ONLY) - && _connectionStatus != BROADCAST) { + if (((_mqttSet && !client.connected()) || setConnectionStatus() < WIFI_ONLY) && _connectionStatus != BROADCAST) { reconnect(); } @@ -355,8 +345,7 @@ void ESPHelper::setWifiCallback(void (*callback)()) { void ESPHelper::reconnect() { static uint8_t tryCount = 0; - if (_connectionStatus != BROADCAST - && setConnectionStatus() != FULL_CONNECTION) { + if (_connectionStatus != BROADCAST && setConnectionStatus() != FULL_CONNECTION) { logger(LOG_CONSOLE, "Attempting WiFi Connection..."); //attempt to connect to the wifi if connection is lost if (WiFi.status() != WL_CONNECTED) { @@ -385,8 +374,7 @@ void ESPHelper::reconnect() { //attempt to connect to mqtt when we finally get connected to WiFi if (_mqttSet) { - static uint8_t timeout = - 0; //allow a max of 5 mqtt connection attempts before timing out + static uint8_t timeout = 0; //allow a max of 5 mqtt connection attempts before timing out if (!client.connected() && timeout < 5) { logger(LOG_CONSOLE, "Attempting MQTT connection..."); @@ -394,9 +382,7 @@ void ESPHelper::reconnect() { //connect to mqtt with user/pass if (_mqttUserSet) { - connected = client.connect(_clientName, - _currentNet.mqttUser, - _currentNet.mqttPass); + connected = client.connect(_clientName, _currentNet.mqttUser, _currentNet.mqttPass); } //connect to mqtt without credentials @@ -621,7 +607,7 @@ void ESPHelper::consoleHandle() { // Set client telnetClient.setNoDelay(true); // faster - telnetClient.flush(); // clear input buffer, to prevent strange characters + telnetClient.flush(); // clear input buffer, to prevent strange characters _lastTimeCommand = millis(); // To mark time for inactivity @@ -836,8 +822,7 @@ void ESPHelper::consoleProcessCommand() { uint8_t cmd = _command[0]; if (!_verboseMessages) { - telnetClient.println( - "Warning, verbose messaging is off. Use v to toggle."); + telnetClient.println("Warning, verbose messaging is off. Use v to toggle."); } // Process the command @@ -853,8 +838,7 @@ void ESPHelper::consoleProcessCommand() { resetESP(); } else if (cmd == '&') { _verboseMessages = !_verboseMessages; // toggle - telnetClient.printf("Verbose messaging is %s\n", - _verboseMessages ? "on" : "off"); + telnetClient.printf("Verbose messaging is %s\n", _verboseMessages ? "on" : "off"); } else { // custom Project commands if (_consoleCallbackProjectCmds) { @@ -874,9 +858,7 @@ void ESPHelper::logger(log_level_t level, const char * message) { telnetClient.flush(); } else if (level == LOG_HA) { char s[100]; - sprintf(s, - "%s: %s\n", - _hostname, + sprintf(s, "%s: %s\n", _hostname, message); // add new line, for the debug telnet printer publish(MQTT_NOTIFICATION, s, false); } diff --git a/platformio.ini-example b/platformio.ini-example index 07fad2ca2..5d4c88102 100644 --- a/platformio.ini-example +++ b/platformio.ini-example @@ -10,7 +10,6 @@ lib_deps = Time PubSubClient ArduinoJson - Ticker [env:nodemcuv2] board = nodemcuv2 diff --git a/src/boiler.ino b/src/boiler.ino index 56e557ff7..f8d43c53a 100644 --- a/src/boiler.ino +++ b/src/boiler.ino @@ -1,7 +1,7 @@ /* * Boiler Project * Paul Derbyshire - May 2018 - https://github.com/proddy/EMS-ESP-Boiler - * + * * See Readme for Acknowledgments */ @@ -14,7 +14,14 @@ // public libraries #include -#include // https://github.com/sstaub/Ticker +#include // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker + +// these are set as -D build flags. If you're not using PlatformIO hard code them +//#define WIFI_SSID "" +//#define WIFI_PASSWORD "" +//#define MQTT_IP "" +//#define MQTT_USER "" +//#define MQTT_PASS "" // private function prototypes void heartbeat(); @@ -22,13 +29,16 @@ void systemCheck(); void publishValues(); void _showerColdShotStart(); void _showerColdShotStop(); +void _toggleHeartbeat(); -// these are set as -D build flags. If you're not using PlatformIO hard code them -//#define WIFI_SSID "" -//#define WIFI_PASSWORD "" -//#define MQTT_IP "" -//#define MQTT_USER "" -//#define MQTT_PASS "" +// timers +Ticker publishValuesTimer; +Ticker systemCheckTimer; +Ticker heartbeatTimer; +Ticker showerResetTimer; +#define publishValuesTime 300 // every 5 mins post HA values +#define systemCheckTime 10 // every 10 seconds check if Boiler is online +#define heartbeatTime 1 // every second blink heartbeat LED // hostname is also used as the MQTT topic identifier (home/) #define HOSTNAME "boiler" @@ -96,7 +106,7 @@ typedef struct { netInfo homeNet = {.mqttHost = MQTT_IP, .mqttUser = MQTT_USER, .mqttPass = MQTT_PASS, - .mqttPort = 1883, + .mqttPort = 1883, // this is the default, change if using another port .ssid = WIFI_SSID, .pass = WIFI_PASSWORD}; @@ -110,12 +120,8 @@ _Boiler_Shower Boiler_Shower; #define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__); // Timers -Ticker updateHATimer(publishValues, 300000); // every 5 mins (300000) post HA values -Ticker hearbeatTimer(heartbeat, 500); // changing onboard heartbeat led every 500ms -Ticker systemCheckTimer(systemCheck, 10000); // every 10 seconds check if Boiler is online -Ticker showerResetTimer(_showerColdShotStop, SHOWER_OFF_DURATION, 1); // timer for how long we turn off the hot water const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline -bool heartbeat_state = false; +bool heartbeatEnabled = false; const unsigned long TX_HOLD_LED_TIME = 2000; // how long to hold the Tx LED because its so quick @@ -312,16 +318,13 @@ void myDebugCallback() { ems_setWarmWaterTemp((uint8_t)strtol(&cmd[2], 0, 10)); break; case 'q': // quiet - b = !ems_getLogVerbose(); - ems_setLogVerbose(b); - enableHeartbeat(b); + _toggleHeartbeat(); break; case 'a': // set ww activate on or off - if ((cmd[2] - '0') == 1) { + if ((cmd[2] - '0') == 1) ems_setWarmWaterActivated(true); - } else if ((cmd[2] - '0') == 0) { + else if ((cmd[2] - '0') == 0) ems_setWarmWaterActivated(false); - } break; case 'T': // toggle Thermostat b = !ems_getThermostatEnabled(); @@ -337,6 +340,14 @@ void myDebugCallback() { } } +// toggle heartbeat LED +void _toggleHeartbeat() { + bool b = !ems_getLogVerbose(); + ems_setLogVerbose(b); + heartbeatEnabled = b; + digitalWrite(LED_BUILTIN, (b) ? LOW : HIGH); // set the LED +} + // MQTT Callback to handle incoming/outgoing changes void MQTTcallback(char * topic, byte * payload, uint8_t length) { // check if start is received, if so return boottime - defined in ESPHelper.h @@ -403,8 +414,9 @@ void setup() { digitalWrite(LED_ERR, HIGH); // Timers - updateHATimer.start(); - systemCheckTimer.start(); + publishValuesTimer.attach(publishValuesTime, publishValues); // every 5 mins (300000) post HA values + systemCheckTimer.attach(systemCheckTime, systemCheck); // every 10 seconds check if Boiler is online + heartbeatTimer.attach(heartbeatTime, heartbeat); // every second blink heartbeat LED // set up Wifi, MQTT, Telnet myESP.setWifiCallback(WIFIcallback); @@ -424,21 +436,12 @@ void setup() { _initBoiler(); // heartbeat, only if setting is enabled - enableHeartbeat(ems_getLogVerbose()); + heartbeatEnabled = ems_getLogVerbose(); } // flash LEDs // Using a faster way to write to pins as digitalWrite does a lot of overhead like pin checking & disabling interrupts void showLEDs() { - // update Ticker - hearbeatTimer.update(); - - // hearbeat timer, using internal LED on board - if (hearbeatTimer.counter() == 20) - hearbeatTimer.interval(200); - if (hearbeatTimer.counter() == 80) - hearbeatTimer.interval(1000); - if (ems_getLogVerbose()) { // ERR LED if (!Boiler_Status.boiler_online) { @@ -461,19 +464,10 @@ void showLEDs() { // heartbeat callback to light up the LED, called via Ticker void heartbeat() { - digitalWrite(LED_BUILTIN, heartbeat_state); - heartbeat_state = !heartbeat_state; -} - -// enables or disables the heartbeat LED -// using the Ticker library -void enableHeartbeat(bool on) { - heartbeat_state = (on) ? LOW : HIGH; - heartbeat(); - if (on) - hearbeatTimer.resume(); - else - hearbeatTimer.pause(); + if (heartbeatEnabled) { + int state = digitalRead(LED_BUILTIN); + digitalWrite(LED_BUILTIN, !state); + } } // do a healthcheck every now and then to see if we connections @@ -497,6 +491,7 @@ void _showerColdShotStop() { myDebug("Shower: turning back hot shower water.\n"); ems_setWarmWaterActivated(true); Boiler_Shower.isColdShot = false; + showerResetTimer.detach(); } } @@ -507,10 +502,6 @@ void loop() { connectionStatus = myESP.loop(); timestamp = millis(); - // Timers - updateHATimer.update(); - systemCheckTimer.update(); - // update the Rx Tx and ERR LEDs showLEDs(); @@ -537,11 +528,10 @@ void loop() { * Shower Logic */ if (Boiler_Status.shower_enabled) { - showerResetTimer.update(); // update Ticker - // if already in cold mode, ignore all this logic until we're out of the cold blast if (!Boiler_Shower.isColdShot) { - // these values come from UBAMonitorFast - type 0x18) which is broadcasted every second so we're pretty accurate + // 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.showerOn = ((EMS_Boiler.selBurnPow >= SHOWER_BURNPOWER_MIN) && (EMS_Boiler.selFlowTemp == 0) && EMS_Boiler.burnGas); @@ -559,7 +549,8 @@ void loop() { if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION) && !Boiler_Shower.isColdShot) && Boiler_Status.shower_timer) { _showerColdShotStart(); - showerResetTimer.start(); // start the timer for n seconds which will reset the water back to hot + // start the timer for n seconds which will reset the water back to hot + showerResetTimer.attach(SHOWER_OFF_DURATION, _showerColdShotStop); } } } else { // shower is off @@ -594,6 +585,5 @@ void loop() { } // yield to prevent watchdog from timing out - // if using delay() this is not needed, but confuses the Ticker library yield(); }