replaced Ticker library

This commit is contained in:
proddy
2018-05-27 17:01:13 +02:00
parent be078224d1
commit ae70b9eb86
4 changed files with 81 additions and 112 deletions

View File

@@ -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 <com> write_flash 0x00000 <firmware>` where firmware is the .bin file and \<com\> is the com port, e.g. COM3
4. run `esptool.py -p <com> write_flash 0x00000 <firmware>` where firmware is the `.bin` file and \<com\> 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 "<broker_password>"
```
* 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 `<lib>` with `"lib"`
* cross your fingers and CTRL-R to compile...

View File

@@ -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);
}

View File

@@ -10,7 +10,6 @@ lib_deps =
Time
PubSubClient
ArduinoJson
Ticker
[env:nodemcuv2]
board = nodemcuv2

View File

@@ -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 <ArduinoJson.h>
#include <Ticker.h> // https://github.com/sstaub/Ticker
#include <Ticker.h> // 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 "<my_ssid>"
//#define WIFI_PASSWORD "<my_password>"
//#define MQTT_IP "<broker_ip>"
//#define MQTT_USER "<broker_username>"
//#define MQTT_PASS "<broker_password>"
// 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 "<my_ssid>"
//#define WIFI_PASSWORD "<my_password>"
//#define MQTT_IP "<broker_ip>"
//#define MQTT_USER "<broker_username>"
//#define MQTT_PASS "<broker_password>"
// 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/<hostname>)
#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();
}