This commit is contained in:
proddy
2019-03-07 08:29:20 +01:00
parent f35cf9701c
commit 541c0c2e10
11 changed files with 161 additions and 129 deletions

View File

@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.5] 2019-03-07
### Fixed
- Support the latest ArduinoJson v6 and espressif8266 2.0.4 libraries (in PlatformIO do a `pio lib update` and `pio update`)
### Changed
- MQTT keep alive to 2 minutes (60 seconds was just too short for slower networks)
- Improved MQTT startup time
- Setting wifi or mqtt settings are immediate, no need to restart the ESP
- Text changes in the help
### Added
- Show if MQTT is connected
- Show version of MyESP (the custom MQTT, Wifi, OTA, MDNS, Telnet library)
- EMS-OT OpenTherm connector
## [1.5.4] 2019-03-03 ## [1.5.4] 2019-03-03
### Changed ### Changed
@@ -14,7 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Callback for OTA. This is used to disable EMS bus during a firmware OTA update, which caused problems with the latest ESP89266 core libraries - Callback for OTA. This is used to disable EMS bus during a firmware OTA update, which caused problems with the latest ESP89266 core libraries
- Added rough estimate of WiFi signal strength to info page - Added rough estimate of WiFi signal strength to info page
- Added the build time & date to the info page (optional in platformio.ini)## [1.5.3] 2019-02-22 - Added the build time & date to the info page (optional in platformio.ini)
## [1.5.3] 2019-02-22
### Changed ### Changed
@@ -84,7 +103,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- some minor improvements to autodetect - some minor improvements to autodetect
## [1.4.0] 2019-01-27 ## [1.4.0] 2019-01-27
### Changed ### Changed

View File

@@ -5,7 +5,7 @@ EMS-ESP is a project to build an electronic controller circuit using an Espressi
There are 3 parts to this project, first the design of the circuit, secondly the code for the ESP8266 microcontroller firmware with telnet and MQTT support, and lastly an example configuration for Home Assistant to monitor the data and issue direct commands via a MQTT broker. There are 3 parts to this project, first the design of the circuit, secondly the code for the ESP8266 microcontroller firmware with telnet and MQTT support, and lastly an example configuration for Home Assistant to monitor the data and issue direct commands via a MQTT broker.
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP&utm_campaign=Badge_Grade_Settings) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP&utm_campaign=Badge_Grade_Settings)
[![version](https://img.shields.io/badge/version-1.5.4-brightgreen.svg)](CHANGELOG.md) [![version](https://img.shields.io/badge/version-1.5.5-brightgreen.svg)](CHANGELOG.md)
- [EMS-ESP](#ems-esp) - [EMS-ESP](#ems-esp)
- [Introduction](#introduction) - [Introduction](#introduction)

View File

@@ -2,21 +2,27 @@
* MyESP - my ESP helper class to handle Wifi, MQTT and Telnet * MyESP - my ESP helper class to handle Wifi, MQTT and Telnet
* *
* Paul Derbyshire - December 2018 * Paul Derbyshire - December 2018
* Version 1.1 - Feb 22 2019. Added support for ESP32
* Version 1.1.1 - March 3 2019. Added OTA callback
* *
* Ideas borrowed from Espurna https://github.com/xoseperez/espurna * Ideas borrowed from Espurna https://github.com/xoseperez/espurna
*/ */
#include "MyESP.h" #include "MyESP.h"
#define RTC_LEAP_YEAR(year) ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0))
/* Days in a month */
static uint8_t RTC_Months[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, /* Not leap year */
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} /* Leap year */
};
// constructor // constructor
MyESP::MyESP() { MyESP::MyESP() {
_app_hostname = strdup("MyESP"); _app_hostname = strdup("MyESP");
_app_name = strdup("MyESP"); _app_name = strdup("MyESP");
_app_version = strdup("1.1.1"); _app_version = strdup(MYESP_VERSION);
_boottime = strdup("unknown"); _boottime = strdup("<unknown>");
_load_average = 100; // calculated load average _load_average = 100; // calculated load average
_telnetcommand_callback = NULL; _telnetcommand_callback = NULL;
@@ -42,14 +48,14 @@ MyESP::MyESP() {
_mqtt_topic = NULL; _mqtt_topic = NULL;
_mqtt_qos = 0; _mqtt_qos = 0;
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
_mqtt_last_connection = 0;
_mqtt_connecting = false;
_wifi_password = NULL; _wifi_password = NULL;
_wifi_ssid = NULL; _wifi_ssid = NULL;
_wifi_callback = NULL; _wifi_callback = NULL;
_wifi_connected = false; _wifi_connected = false;
_ota_callback = NULL;
_suspendOutput = false; _suspendOutput = false;
} }
@@ -240,6 +246,8 @@ void MyESP::_mqttOnConnect() {
myDebug_P(PSTR("[MQTT] Connected")); myDebug_P(PSTR("[MQTT] Connected"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
_mqtt_last_connection = millis();
// say we're alive to the Last Will topic // say we're alive to the Last Will topic
mqttClient.publish(_mqttTopic(_mqtt_will_topic), 1, true, _mqtt_will_online_payload); mqttClient.publish(_mqttTopic(_mqtt_will_topic), 1, true, _mqtt_will_online_payload);
@@ -253,8 +261,6 @@ void MyESP::_mqtt_setup() {
myDebug_P(PSTR("[MQTT] disabled")); myDebug_P(PSTR("[MQTT] disabled"));
} }
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
mqttClient.onConnect([this](bool sessionPresent) { _mqttOnConnect(); }); mqttClient.onConnect([this](bool sessionPresent) { _mqttOnConnect(); });
mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
@@ -274,6 +280,10 @@ void MyESP::_mqtt_setup() {
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
myDebug_P(PSTR("[MQTT] Not authorized")); myDebug_P(PSTR("[MQTT] Not authorized"));
} }
// Reset reconnection delay
_mqtt_last_connection = millis();
_mqtt_connecting = false;
}); });
//mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); }); //mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); });
@@ -391,15 +401,6 @@ void MyESP::_telnet_setup() {
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); memset(_command, 0, TELNET_MAX_COMMAND_LENGTH);
} }
#define RTC_LEAP_YEAR(year) ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0))
/* Days in a month */
static uint8_t RTC_Months[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, /* Not leap year */
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} /* Leap year */
};
// https://stackoverflow.com/questions/43063071/the-arduino-ntp-i-want-print-out-datadd-mm-yyyy // https://stackoverflow.com/questions/43063071/the-arduino-ntp-i-want-print-out-datadd-mm-yyyy
void MyESP::_printBuildTime(unsigned long unix) { void MyESP::_printBuildTime(unsigned long unix) {
// compensate for summer/winter time and CET. Can't be bothered to work out DST. // compensate for summer/winter time and CET. Can't be bothered to work out DST.
@@ -462,7 +463,7 @@ void MyESP::_consoleShowHelp() {
SerialAndTelnet.println(); SerialAndTelnet.println();
if (WiFi.getMode() & WIFI_AP) { if (WiFi.getMode() & WIFI_AP) {
SerialAndTelnet.printf("* ESP8266 is in AP mode with SSID %s", jw.getAPSSID().c_str()); SerialAndTelnet.printf("* ESP is in AP mode with SSID %s", jw.getAPSSID().c_str());
SerialAndTelnet.println(); SerialAndTelnet.println();
} else { } else {
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
@@ -477,6 +478,7 @@ void MyESP::_consoleShowHelp() {
#ifdef ARDUINO_BOARD #ifdef ARDUINO_BOARD
SerialAndTelnet.printf(" Board: %s", ARDUINO_BOARD); SerialAndTelnet.printf(" Board: %s", ARDUINO_BOARD);
#endif #endif
SerialAndTelnet.printf(" (MyESP v%s)", MYESP_VERSION);
#ifdef BUILD_TIME #ifdef BUILD_TIME
SerialAndTelnet.print(" (Build "); SerialAndTelnet.print(" (Build ");
@@ -486,10 +488,13 @@ void MyESP::_consoleShowHelp() {
SerialAndTelnet.println(); SerialAndTelnet.println();
SerialAndTelnet.printf("* Connected to WiFi SSID: %s (signal %d%%)", WiFi.SSID().c_str(), getWifiQuality()); SerialAndTelnet.printf("* Connected to WiFi SSID: %s (signal %d%%)", WiFi.SSID().c_str(), getWifiQuality());
SerialAndTelnet.println(); SerialAndTelnet.println();
SerialAndTelnet.printf("* MQTT is %s", mqttClient.connected() ? "connected" : "disconnected");
SerialAndTelnet.println();
SerialAndTelnet.printf("* Boot time: %s", _boottime); SerialAndTelnet.printf("* Boot time: %s", _boottime);
SerialAndTelnet.println(); SerialAndTelnet.println();
} }
SerialAndTelnet.printf("* Free RAM:%d KB, Load:%d%%", (ESP.getFreeHeap() / 1024), getSystemLoadAverage());
SerialAndTelnet.printf("* Free RAM: %d KB Load: %d%%", (ESP.getFreeHeap() / 1024), getSystemLoadAverage());
SerialAndTelnet.println(); SerialAndTelnet.println();
// for battery power is ESP.getVcc() // for battery power is ESP.getVcc()
@@ -498,7 +503,7 @@ void MyESP::_consoleShowHelp() {
SerialAndTelnet.println(FPSTR("* ?=help, CTRL-D=quit")); SerialAndTelnet.println(FPSTR("* ?=help, CTRL-D=quit"));
SerialAndTelnet.println(FPSTR("* reboot")); SerialAndTelnet.println(FPSTR("* reboot"));
SerialAndTelnet.println(FPSTR("* set")); SerialAndTelnet.println(FPSTR("* set"));
SerialAndTelnet.println(FPSTR("* set wifi <ssid> <password>")); SerialAndTelnet.println(FPSTR("* set wifi [ssid] [password]"));
SerialAndTelnet.println(FPSTR("* set <mqtt_host | mqtt_username | mqtt_password> [value]")); SerialAndTelnet.println(FPSTR("* set <mqtt_host | mqtt_username | mqtt_password> [value]"));
SerialAndTelnet.println(FPSTR("* set erase")); SerialAndTelnet.println(FPSTR("* set erase"));
SerialAndTelnet.println(FPSTR("* set serial")); SerialAndTelnet.println(FPSTR("* set serial"));
@@ -538,8 +543,7 @@ void MyESP::resetESP() {
// read next word from string buffer // read next word from string buffer
char * MyESP::_telnet_readWord() { char * MyESP::_telnet_readWord() {
char * word = strtok(NULL, ", \n"); return (strtok(NULL, ", \n"));
return word;
} }
// change setting for 2 params (set <command> <value1> <value2>) // change setting for 2 params (set <command> <value1> <value2>)
@@ -561,7 +565,10 @@ void MyESP::_changeSetting2(const char * setting, const char * value1, const cha
} }
(void)fs_saveConfig(); (void)fs_saveConfig();
SerialAndTelnet.println("Wifi credentials set. Type 'reboot' to restart..."); SerialAndTelnet.println("WiFi settings changed. Reconnecting...");
jw.disconnect();
jw.cleanNetworks();
jw.addNetwork(_wifi_ssid, _wifi_password);
} }
} }
@@ -778,15 +785,20 @@ void MyESP::_telnetHandle() {
// ensure we have a connection to MQTT broker // ensure we have a connection to MQTT broker
void MyESP::_mqttConnect() { void MyESP::_mqttConnect() {
if (!_mqtt_host || mqttClient.connected() || (WiFi.status() != WL_CONNECTED)) { if (!_mqtt_host)
return; // MQTT not enabled
// Do not connect if already connected or still trying to connect
if (mqttClient.connected() || _mqtt_connecting || (WiFi.status() != WL_CONNECTED)) {
return; return;
} }
// Check reconnect interval // Check reconnect interval
static unsigned long last = 0; if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) {
if (millis() - last < _mqtt_reconnect_delay)
return; return;
last = millis(); }
_mqtt_connecting = true; // we're doing a connection
// Increase the reconnect delay // Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP; _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
@@ -801,7 +813,7 @@ void MyESP::_mqttConnect() {
// last will // last will
if (_mqtt_will_topic) { if (_mqtt_will_topic) {
myDebug_P(PSTR("[MQTT] Setting last will topic %s"), _mqttTopic(_mqtt_will_topic)); //myDebug_P(PSTR("[MQTT] Setting last will topic %s"), _mqttTopic(_mqtt_will_topic));
mqttClient.setWill(_mqttTopic(_mqtt_will_topic), 1, true, _mqtt_will_offline_payload); // retain always true mqttClient.setWill(_mqttTopic(_mqtt_will_topic), 1, true, _mqtt_will_offline_payload); // retain always true
} }
@@ -923,15 +935,21 @@ char * MyESP::_mqttTopic(const char * topic) {
// print contents of file // print contents of file
// assume Serial is open // assumes Serial is open
void MyESP::_fs_printConfig() { void MyESP::_fs_printConfig() {
myDebug_P(PSTR("[FS] Contents:")); myDebug_P(PSTR("[FS] Contents:"));
File configFile = SPIFFS.open(MYEMS_CONFIG_FILE, "r"); File configFile = SPIFFS.open(MYEMS_CONFIG_FILE, "r");
if (!configFile) {
Serial.println(F("[FS] Failed to read file for printing"));
return;
}
while (configFile.available()) { while (configFile.available()) {
SerialAndTelnet.print((char)configFile.read()); SerialAndTelnet.print((char)configFile.read());
} }
myDebug_P(PSTR("")); SerialAndTelnet.println();
configFile.close(); configFile.close();
} }
@@ -965,14 +983,15 @@ bool MyESP::_fs_loadConfig() {
return false; return false;
} }
// assign buffer StaticJsonDocument<SPIFFS_MAXSIZE> doc;
std::unique_ptr<char[]> buf(new char[size]); JsonObject json = doc.to<JsonObject>();
// use configFile.readString // Deserialize the JSON document
configFile.readBytes(buf.get(), size); DeserializationError error = deserializeJson(doc, configFile);
if (error) {
StaticJsonBuffer<SPIFFS_MAXSIZE> jsonBuffer; Serial.println(F("[FS] Failed to read file"));
JsonObject & json = jsonBuffer.parseObject(buf.get()); return false;
}
const char * value; const char * value;
@@ -991,18 +1010,12 @@ bool MyESP::_fs_loadConfig() {
value = json["mqtt_password"]; value = json["mqtt_password"];
_mqtt_password = (value) ? strdup(value) : NULL; _mqtt_password = (value) ? strdup(value) : NULL;
_use_serial = (bool)json["use_serial"];
// callback for loading custom settings // callback for loading custom settings
// ok is false if there's a problem loading a custom setting (e.g. does not exist) // ok is false if there's a problem loading a custom setting (e.g. does not exist)
bool ok = (_fs_callback)(MYESP_FSACTION_LOAD, json); bool ok = (_fs_callback)(MYESP_FSACTION_LOAD, json);
// new configs after release 1.3.x
if (json.containsKey("use_serial")) {
_use_serial = (bool)json["use_serial"];
} else {
_use_serial = false; // if first time, set serial to off
ok = false;
}
configFile.close(); configFile.close();
return ok; return ok;
@@ -1010,9 +1023,10 @@ bool MyESP::_fs_loadConfig() {
// save settings to spiffs // save settings to spiffs
bool MyESP::fs_saveConfig() { bool MyESP::fs_saveConfig() {
StaticJsonBuffer<SPIFFS_MAXSIZE> jsonBuffer; StaticJsonDocument<SPIFFS_MAXSIZE> doc;
JsonObject & json = jsonBuffer.createObject(); JsonObject json = doc.to<JsonObject>();
json["app_version"] = _app_version;
json["wifi_ssid"] = _wifi_ssid; json["wifi_ssid"] = _wifi_ssid;
json["wifi_password"] = _wifi_password; json["wifi_password"] = _wifi_password;
json["mqtt_host"] = _mqtt_host; json["mqtt_host"] = _mqtt_host;
@@ -1029,7 +1043,10 @@ bool MyESP::fs_saveConfig() {
return false; return false;
} }
json.printTo(configFile); // Serialize JSON to file
if (serializeJson(json, configFile) == 0) {
Serial.println(F("[FS] Failed to write to file"));
}
configFile.close(); configFile.close();
@@ -1042,16 +1059,17 @@ bool MyESP::fs_saveConfig() {
void MyESP::_fs_setup() { void MyESP::_fs_setup() {
if (!SPIFFS.begin()) { if (!SPIFFS.begin()) {
Serial.println("[FS] Failed to mount the file system"); Serial.println("[FS] Failed to mount the file system");
_fs_eraseConfig(); // fix for ESP32
return; return;
} }
// load the config file. if it doesn't exist create it // load the config file. if it doesn't exist (function returns false) create it
if (!_fs_loadConfig()) { if (!_fs_loadConfig()) {
Serial.println("[FS] Re-creating config file"); // Serial.println("[FS] Re-creating config file");
fs_saveConfig(); fs_saveConfig();
} }
// _fs_printConfig(); // for debugging // _fs_printConfig(); // TODO: for debugging
} }
uint16_t MyESP::getSystemLoadAverage() { uint16_t MyESP::getSystemLoadAverage() {
@@ -1078,7 +1096,8 @@ void MyESP::_calculateLoad() {
} }
} }
// return true if wifi is connected: // return true if wifi is connected
// WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
// WL_IDLE_STATUS = 0, // WL_IDLE_STATUS = 0,
// WL_NO_SSID_AVAIL = 1, // WL_NO_SSID_AVAIL = 1,
// WL_SCAN_COMPLETED = 2, // WL_SCAN_COMPLETED = 2,
@@ -1091,13 +1110,16 @@ bool MyESP::isWifiConnected() {
} }
/* /*
* Return the quality (Received Signal Strength Indicator) of the WiFi network. Return the quality (Received Signal Strength Indicator)
* Returns -1 if WiFi is disconnected. of the WiFi network.
* High quality: 90% ~= -55dBm Returns a number between 0 and 100 if WiFi is connected.
* Medium quality: 50% ~= -75dBm Returns -1 if WiFi is disconnected.
* Low quality: 30% ~= -85dBm
* Unusable quality: 8% ~= -96dBm High quality: 90% ~= -55dBm
*/ Medium quality: 50% ~= -75dBm
Low quality: 30% ~= -85dBm
Unusable quality: 8% ~= -96dBm
*/
int MyESP::getWifiQuality() { int MyESP::getWifiQuality() {
if (WiFi.status() != WL_CONNECTED) if (WiFi.status() != WL_CONNECTED)
return -1; return -1;

View File

@@ -1,5 +1,5 @@
/* /*
* MyEsp.h * MyESP.h
* *
* Paul Derbyshire - December 2018 * Paul Derbyshire - December 2018
*/ */
@@ -9,6 +9,8 @@
#ifndef MyEMS_h #ifndef MyEMS_h
#define MyEMS_h #define MyEMS_h
#define MYESP_VERSION "1.1.4"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127 #include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
@@ -38,8 +40,8 @@
// MQTT // MQTT
#define MQTT_PORT 1883 // MQTT port #define MQTT_PORT 1883 // MQTT port
#define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection #define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
#define MQTT_RECONNECT_DELAY_STEP 5000 // Increase the reconnect delay in 5 seconds after each failed attempt #define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most #define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
#define MQTT_MAX_SIZE 600 // max length of MQTT message #define MQTT_MAX_SIZE 600 // max length of MQTT message
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT message #define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT message
@@ -68,7 +70,6 @@
#define COLOR_BOLD_ON "\x1B[1m" #define COLOR_BOLD_ON "\x1B[1m"
#define COLOR_BOLD_OFF "\x1B[21m" #define COLOR_BOLD_OFF "\x1B[21m"
// SPIFFS // SPIFFS
#define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/ #define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/
@@ -84,7 +85,7 @@ typedef std::function<void()>
typedef std::function<void()> ota_callback_f; typedef std::function<void()> ota_callback_f;
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f; typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f; typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION, JsonObject & json)> fs_callback_f; typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f; typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
// calculates size of an 2d array at compile time // calculates size of an 2d array at compile time
@@ -165,6 +166,8 @@ class MyESP {
char * _mqtt_will_online_payload; char * _mqtt_will_online_payload;
char * _mqtt_will_offline_payload; char * _mqtt_will_offline_payload;
char * _mqtt_topic; char * _mqtt_topic;
unsigned long _mqtt_last_connection;
bool _mqtt_connecting;
// wifi // wifi
DNSServer dnsServer; // For Access Point (AP) support DNSServer dnsServer; // For Access Point (AP) support

View File

@@ -16,8 +16,8 @@ lib_deps =
CircularBuffer CircularBuffer
JustWifi JustWifi
AsyncMqttClient AsyncMqttClient
; ArduinoJson ArduinoJson
https://github.com/bblanchon/ArduinoJson#v5.13.5 ; https://github.com/bblanchon/ArduinoJson#v5.13.5
OneWire OneWire
[env:d1_mini] [env:d1_mini]

View File

@@ -79,24 +79,24 @@ command_t PROGMEM project_cmds[] = {
{"set led <on | off>", "toggle status LED on/off"}, {"set led <on | off>", "toggle status LED on/off"},
{"set led_gpio <pin>", "set the LED pin. Default is the onboard LED (D1=5)"}, {"set led_gpio <pin>", "set the LED pin. Default is the onboard LED (D1=5)"},
{"set dallas_gpio <pin>", "set the pin for the external Dallas temperature sensor (D5=14)"}, {"set dallas_gpio <pin>", "set the pin for external Dallas temperature sensors (D5=14)"},
{"set thermostat_type <hex type ID>", "set the thermostat type id (e.g. 10 for 0x10)"}, {"set thermostat_type <type ID>", "set the thermostat type id (e.g. 10 for 0x10)"},
{"set boiler_type <hex type ID>", "set the boiler type id (e.g. 8 for 0x08)"}, {"set boiler_type <type ID>", "set the boiler type id (e.g. 8 for 0x08)"},
{"info", "show the values"}, {"info", "show data captured on the EMS bus"},
{"log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"}, {"log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"},
{"publish", "publish values to MQTT"}, {"publish", "forice a publish of all values to MQTT"},
{"types", "list supported EMS telegram type IDs"}, {"types", "list supported EMS telegram type IDs"},
{"queue", "list Tx queue"}, {"queue", "show current Tx queue"},
{"autodetect", "discover EMS devices and set boiler and thermostat automatically"}, {"autodetect", "discover EMS devices and attempt to automatically set boiler and thermostat"},
{"shower <timer | alert>", "toggle either timer or alert on/off"}, {"shower <timer | alert>", "toggle either timer or alert on/off"},
{"send XX...", "send raw telegram data in hex to EMS bus"}, {"send XX ...", "send raw telegram data as hex to EMS bus"},
{"thermostat read <hex type ID>", "send read request to thermostat"}, {"thermostat read <type ID>", "send read request to the thermostat"},
{"thermostat temp <degrees>", "set current thermostat temperature"}, {"thermostat temp <degrees>", "set current thermostat temperature"},
{"thermostat mode <mode>", "set mode (0=low/night, 1=manual/day, 2=auto)"}, {"thermostat mode <mode>", "set mode (0=low/night, 1=manual/day, 2=auto)"},
{"thermostat scan <hex type ID>", "do a force read on all type IDs starting at n"}, {"thermostat scan <type ID>", "do a read on all type IDs"},
{"boiler read <hex type ID>", "send read request to boiler"}, {"boiler read <type ID>", "send read request to boiler"},
{"boiler wwtemp <degrees>", "set warm water temperature"}, {"boiler wwtemp <degrees>", "set boiler warm water temperature"},
{"boiler tapwater <on | off>", "set warm tap water on or off"} {"boiler tapwater <on | off>", "set boiler warm tap water on/off"}
}; };
@@ -400,7 +400,7 @@ void showInfo() {
// show the Shower Info // show the Shower Info
if (EMSESP_Status.shower_timer) { if (EMSESP_Status.shower_timer) {
myDebug("%sShower stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug("%sShower stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug(" Shower Timer is %s", (EMSESP_Shower.showerOn ? "active" : "off")); myDebug(" Shower is %s", (EMSESP_Shower.showerOn ? "running" : "off"));
} }
} }
@@ -408,18 +408,18 @@ void showInfo() {
// a json object is created for the boiler and one for the thermostat // 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 // 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) { void publishValues(bool force) {
char s[20] = {0}; // for formatting strings char s[20] = {0}; // for formatting strings
StaticJsonBuffer<MQTT_MAX_SIZE> jsonBuffer; StaticJsonDocument<MQTT_MAX_SIZE> doc;
char data[MQTT_MAX_SIZE] = {0}; char data[MQTT_MAX_SIZE] = {0};
JsonObject & rootBoiler = jsonBuffer.createObject(); CRC32 crc;
size_t rlen; uint32_t fchecksum;
CRC32 crc;
uint32_t fchecksum;
static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off 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 static uint32_t previousBoilerPublishCRC = 0; // CRC check
static uint32_t previousThermostatPublishCRC = 0; // CRC check static uint32_t previousThermostatPublishCRC = 0; // CRC check
JsonObject rootBoiler = doc.to<JsonObject>();
rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp); rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp);
rootBoiler["selFlowTemp"] = _float_to_char(s, EMS_Boiler.selFlowTemp); rootBoiler["selFlowTemp"] = _float_to_char(s, EMS_Boiler.selFlowTemp);
rootBoiler["outdoorTemp"] = _float_to_char(s, EMS_Boiler.extTemp); rootBoiler["outdoorTemp"] = _float_to_char(s, EMS_Boiler.extTemp);
@@ -443,11 +443,10 @@ void publishValues(bool force) {
rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod); rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod);
rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar;
rlen = rootBoiler.measureLength(); serializeJson(doc, data, sizeof(data));
rootBoiler.printTo(data, rlen + 1); // form the json string
// calculate hash and send values if something has changed, to save unnecessary wifi traffic // calculate hash and send values if something has changed, to save unnecessary wifi traffic
for (size_t i = 0; i < rlen - 1; i++) { for (size_t i = 0; i < measureJson(doc) - 1; i++) {
crc.update(data[i]); crc.update(data[i]);
} }
fchecksum = crc.finalize(); fchecksum = crc.finalize();
@@ -470,13 +469,16 @@ void publishValues(bool force) {
} }
// handle the thermostat values separately // handle the thermostat values separately
if (ems_getThermostatEnabled()) { //if (ems_getThermostatEnabled()) {
if (true) {
// only send thermostat values if we actually have them // only send thermostat values if we actually have them
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0))
return; return;
// build json object // build new json object
JsonObject & rootThermostat = jsonBuffer.createObject(); doc.clear();
JsonObject rootThermostat = doc.to<JsonObject>();
rootThermostat[THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp); rootThermostat[THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp);
rootThermostat[THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp); rootThermostat[THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp);
@@ -500,12 +502,11 @@ void publishValues(bool force) {
} }
data[0] = '\0'; // reset data for next package data[0] = '\0'; // reset data for next package
rlen = rootThermostat.measureLength(); serializeJson(doc, data, sizeof(data));
rootThermostat.printTo(data, rlen + 1); // form the json string
// calculate new CRC // calculate new CRC
crc.reset(); crc.reset();
for (size_t i = 0; i < rlen - 1; i++) { for (size_t i = 0; i < measureJson(doc) - 1; i++) {
crc.update(data[i]); crc.update(data[i]);
} }
uint32_t checksum = crc.finalize(); uint32_t checksum = crc.finalize();
@@ -579,48 +580,34 @@ void startThermostatScan(uint8_t start) {
} }
// callback for loading/saving settings to the file system (SPIFFS) // callback for loading/saving settings to the file system (SPIFFS)
bool FSCallback(MYESP_FSACTION action, JsonObject & json) { bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
bool ok = true;
if (action == MYESP_FSACTION_LOAD) { if (action == MYESP_FSACTION_LOAD) {
// led // led
if (json.containsKey("led")) { if (!(EMSESP_Status.led_enabled = json["led"])) {
EMSESP_Status.led_enabled = (bool)json["led"];
} else {
EMSESP_Status.led_enabled = LED_BUILTIN; // default value EMSESP_Status.led_enabled = LED_BUILTIN; // default value
ok = false;
} }
// led_gpio // led_gpio
if (json.containsKey("led_gpio")) { if (!(EMSESP_Status.led_gpio = json["led_gpio"])) {
EMSESP_Status.led_gpio = json["led_gpio"];
} else {
EMSESP_Status.led_gpio = EMSESP_LED_GPIO; // default value EMSESP_Status.led_gpio = EMSESP_LED_GPIO; // default value
ok = false;
} }
// dallas_gpio // dallas_gpio
if (json.containsKey("dallas_gpio")) { if (!(EMSESP_Status.dallas_gpio = json["dallas_gpio"])) {
EMSESP_Status.dallas_gpio = json["dallas_gpio"];
} else {
EMSESP_Status.dallas_gpio = EMSESP_DALLAS_GPIO; // default value EMSESP_Status.dallas_gpio = EMSESP_DALLAS_GPIO; // default value
ok = false;
} }
// thermostat_type // thermostat_type
if (json.containsKey("thermostat_type")) { if (!(EMS_Thermostat.type_id = json["thermostat_type"])) {
EMS_Thermostat.type_id = json["thermostat_type"];
} else {
EMS_Thermostat.type_id = EMSESP_THERMOSTAT_TYPE; // set default EMS_Thermostat.type_id = EMSESP_THERMOSTAT_TYPE; // set default
ok = false;
} }
// boiler_type // boiler_type
if (json.containsKey("boiler_type")) { if (!(EMS_Boiler.type_id = json["boiler_type"])) {
EMS_Boiler.type_id = json["boiler_type"];
} else {
EMS_Boiler.type_id = EMSESP_BOILER_TYPE; // set default EMS_Boiler.type_id = EMSESP_BOILER_TYPE; // set default
ok = false;
} }
return false; // always save the settings
} }
if (action == MYESP_FSACTION_SAVE) { if (action == MYESP_FSACTION_SAVE) {
@@ -629,9 +616,9 @@ bool FSCallback(MYESP_FSACTION action, JsonObject & json) {
json["dallas_gpio"] = EMSESP_Status.dallas_gpio; json["dallas_gpio"] = EMSESP_Status.dallas_gpio;
json["thermostat_type"] = EMS_Thermostat.type_id; json["thermostat_type"] = EMS_Thermostat.type_id;
json["boiler_type"] = EMS_Boiler.type_id; json["boiler_type"] = EMS_Boiler.type_id;
}
return ok; // all ok return true;
}
} }
// callback for custom settings when showing Stored Settings // callback for custom settings when showing Stored Settings
@@ -949,7 +936,7 @@ void WIFICallback() {
// This is done after we have a WiFi signal to avoid any resource conflicts // This is done after we have a WiFi signal to avoid any resource conflicts
if (myESP.getUseSerial()) { if (myESP.getUseSerial()) {
myDebug("EMS UART disabled when in Serial mode. Use 'set serial off' to change."); myDebug("Warning! EMS bus disabled when in Serial mode. Use 'set serial off' to enable.");
} else { } else {
emsuart_init(); emsuart_init();
myDebug("[UART] Opened Rx/Tx connection"); myDebug("[UART] Opened Rx/Tx connection");

View File

@@ -1447,7 +1447,7 @@ char * ems_getBoilerDescription(char * buffer) {
* Find the versions of our connected devices * Find the versions of our connected devices
*/ */
void ems_scanDevices() { void ems_scanDevices() {
myDebug("Scanning EMS bus for devices."); myDebug("Started scan of EMS bus for known devices");
std::list<uint8_t> Device_Ids; // new list std::list<uint8_t> Device_Ids; // new list

View File

@@ -102,7 +102,8 @@ typedef enum {
EMS_MODEL_EASY, EMS_MODEL_EASY,
EMS_MODEL_BOSCHEASY, EMS_MODEL_BOSCHEASY,
EMS_MODEL_RC310, EMS_MODEL_RC310,
EMS_MODEL_CW100 EMS_MODEL_CW100,
EMS_MODEL_OT
} _EMS_MODEL_ID; } _EMS_MODEL_ID;
@@ -139,6 +140,7 @@ const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_BOSCHEASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_BOSCHEASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_RC310, 158, 0x10, "RC310", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_RC310, 158, 0x10, "RC310", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_CW100, 255, 0x18, "Bosch CW100", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO} {EMS_MODEL_CW100, 255, 0x18, "Bosch CW100", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_OT, 171, 0x02, "EMS-OT OpenTherm converter", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}
}; };

View File

@@ -20,7 +20,7 @@
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload #define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload #define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
#define MQTT_RETAIN false #define MQTT_RETAIN false
#define MQTT_KEEPALIVE 60 // 1 minute #define MQTT_KEEPALIVE 120 // 2 minutes
#define MQTT_QOS 1 #define MQTT_QOS 1
// MQTT for thermostat // MQTT for thermostat

View File

@@ -6,5 +6,5 @@
#pragma once #pragma once
#define APP_NAME "EMS-ESP" #define APP_NAME "EMS-ESP"
#define APP_VERSION "1.5.4" #define APP_VERSION "1.5.5"
#define APP_HOSTNAME "ems-esp" #define APP_HOSTNAME "ems-esp"