This commit is contained in:
Paul
2019-06-15 15:43:10 +02:00
26 changed files with 990 additions and 531 deletions

3
.gitignore vendored
View File

@@ -6,5 +6,4 @@
platformio.ini platformio.ini
lib/readme.txt lib/readme.txt
.travis.yml .travis.yml
;stackdmp.txt scripts/stackdmp.txt
;*.jar

View File

@@ -5,15 +5,19 @@ 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.7.1] 2019-05-17 ## [1.8.0] 2019-06-15
### Added ### Added
- new device: MM50 Mixer ([thanks GARLAGANDIS](https://github.com/proddy/EMS-ESP/issues/111)) - HeatPump support (e.g. the Nefit Enviline)
- new device: Buderus MM50 Mixer
- new devices: Junkers FW100 and ISM1 (thanks Vuego123)
- Improved Tx logic to support EMS, EMS+ and Heatronics (thanks kwertie01, susisstrolch, philrich)
- MQTT heartbeat
### Fixed ### Fixed
- runtime exceptions with latest 2.5.1 arduino core library - runtime exceptions with latest 2.5.1 arduino core
## [1.7.0] 2019-05-11 ## [1.7.0] 2019-05-11

View File

@@ -1,7 +1,23 @@
# EMS-ESP # EMS-ESP
EMS-ESP is a project to build an electronic controller circuit using an Espressif ESP8266 microcontroller to communicate with EMS (Energy Management System) based Boilers and Thermostats from the Bosch range and compatibles such as Buderus, Nefit, Junkers etc. EMS-ESP is a open-source system to communicate with **EMS** (Energy Management System) based boilers, thermostats and other modules from manufacturers like Bosch, Buderus, Nefit, Junkers and Sieger.
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. The code is writen for the Espressif **ESP8266** microcontroller and supports a telnet console for real-time monitoring and configuration and customizable MQTT support for publishing the information to a home automation system such as Home Assistant or Domoticz.
Please refer to the [Wiki](https://github.com/proddy/EMS-ESP/wiki) for further documentation. ### Please reference the [Wiki](https://github.com/proddy/EMS-ESP/wiki) for further details and instructions on how to build and configure the firmware.
---
**An example of the Home Assistant integration:**
![ha](https://github.com/proddy/EMS-ESP/raw/dev/doc/home_assistant/ha.png)
**Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) circuit:**
| ![on boiler](https://github.com/proddy/EMS-ESP/raw/dev/doc/ems%20gateway/on-boiler.jpg) | ![kit](https://github.com/proddy/EMS-ESP/raw/dev/doc/ems%20gateway/ems-kit-2.jpg) | ![basic circuit](https://github.com/proddy/EMS-ESP/raw/dev/doc/ems%20gateway/ems-board-white.jpg) |
| - | - | - |
**Example of the EMS-ESP's telnet console:**
| ![telnet menu](https://github.com/proddy/EMS-ESP/raw/dev/doc/telnet/telnet_menu.jpg) | ![telnet menu](https://github.com/proddy/EMS-ESP/raw/dev/doc/telnet/telnet_stats.PNG) |
| - | - |

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 189 KiB

View File

@@ -8,9 +8,19 @@
#include "MyESP.h" #include "MyESP.h"
#ifdef CRASH
EEPROM_Rotate EEPROMr; EEPROM_Rotate EEPROMr;
#endif
union system_rtcmem_t {
struct {
uint8_t stability_counter;
uint8_t reset_reason;
uint16_t _reserved_;
} parts;
uint32_t value;
};
uint8_t RtcmemSize = (sizeof(RtcmemData) / 4u);
auto Rtcmem = reinterpret_cast<volatile RtcmemData *>(RTCMEM_ADDR);
// constructor // constructor
MyESP::MyESP() { MyESP::MyESP() {
@@ -33,6 +43,7 @@ MyESP::MyESP() {
_helpProjectCmds_count = 0; _helpProjectCmds_count = 0;
_use_serial = false; _use_serial = false;
_heartbeat = false;
_mqtt_host = NULL; _mqtt_host = NULL;
_mqtt_password = NULL; _mqtt_password = NULL;
_mqtt_username = NULL; _mqtt_username = NULL;
@@ -57,6 +68,9 @@ MyESP::MyESP() {
_ota_post_callback = NULL; _ota_post_callback = NULL;
_suspendOutput = false; _suspendOutput = false;
_rtcmem_status = false;
_systemStable = true;
} }
MyESP::~MyESP() { MyESP::~MyESP() {
@@ -124,6 +138,26 @@ bool MyESP::getUseSerial() {
return (_use_serial); return (_use_serial);
} }
// heartbeat
bool MyESP::getHeartbeat() {
return (_heartbeat);
}
// init heap ram
uint32_t MyESP::getInitialFreeHeap() {
static uint32_t _heap = 0;
if (0 == _heap) {
_heap = ESP.getFreeHeap();
}
return _heap;
}
uint32_t MyESP::getUsedHeap() {
return getInitialFreeHeap() - ESP.getFreeHeap();
}
// called when WiFi is connected, and used to start OTA, MQTT // called when WiFi is connected, and used to start OTA, MQTT
void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) { void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
if ((code == MESSAGE_CONNECTED)) { if ((code == MESSAGE_CONNECTED)) {
@@ -154,10 +188,9 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
// finally if we don't want Serial anymore, turn it off // finally if we don't want Serial anymore, turn it off
if (!_use_serial) { if (!_use_serial) {
myDebug_P(PSTR("Disabling serial port")); myDebug_P(PSTR("Disabling serial port communication."));
SerialAndTelnet.flush(); // flush so all buffer is printed to serial
SerialAndTelnet.setSerial(NULL); SerialAndTelnet.setSerial(NULL);
} else {
myDebug_P(PSTR("Using serial port output"));
} }
// call any final custom settings // call any final custom settings
@@ -362,7 +395,6 @@ void MyESP::setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_po
void MyESP::_OTACallback() { void MyESP::_OTACallback() {
myDebug_P(PSTR("[OTA] Start")); myDebug_P(PSTR("[OTA] Start"));
#ifdef CRASH
// If we are not specifically reserving the sectors we are using as // If we are not specifically reserving the sectors we are using as
// EEPROM in the memory layout then any OTA upgrade will overwrite // EEPROM in the memory layout then any OTA upgrade will overwrite
// all but the last one. // all but the last one.
@@ -375,7 +407,6 @@ void MyESP::_OTACallback() {
// See onError callback below. // See onError callback below.
EEPROMr.rotate(false); EEPROMr.rotate(false);
EEPROMr.commit(); EEPROMr.commit();
#endif
if (_ota_pre_callback) { if (_ota_pre_callback) {
(_ota_pre_callback)(); // call custom function (_ota_pre_callback)(); // call custom function
@@ -392,7 +423,11 @@ void MyESP::_ota_setup() {
ArduinoOTA.setHostname(_app_hostname); ArduinoOTA.setHostname(_app_hostname);
ArduinoOTA.onStart([this]() { _OTACallback(); }); ArduinoOTA.onStart([this]() { _OTACallback(); });
ArduinoOTA.onEnd([this]() { myDebug_P(PSTR("[OTA] Done, restarting...")); }); ArduinoOTA.onEnd([this]() {
myDebug_P(PSTR("[OTA] Done, restarting..."));
_deferredReset(100, CUSTOM_RESET_OTA);
});
ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) { ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) {
static unsigned int _progOld; static unsigned int _progOld;
unsigned int _prog = (progress / (total / 100)); unsigned int _prog = (progress / (total / 100));
@@ -414,10 +449,8 @@ void MyESP::_ota_setup() {
else if (error == OTA_END_ERROR) else if (error == OTA_END_ERROR)
myDebug_P(PSTR("[OTA] End Failed")); myDebug_P(PSTR("[OTA] End Failed"));
#ifdef CRASH
// There's been an error, reenable rotation // There's been an error, reenable rotation
EEPROMr.rotate(true); EEPROMr.rotate(true);
#endif
}); });
} }
@@ -431,10 +464,8 @@ void MyESP::setBoottime(const char * boottime) {
// eeprom // eeprom
void MyESP::_eeprom_setup() { void MyESP::_eeprom_setup() {
#ifdef CRASH
EEPROMr.size(4); EEPROMr.size(4);
EEPROMr.begin(SPI_FLASH_SEC_SIZE); EEPROMr.begin(SPI_FLASH_SEC_SIZE);
#endif
} }
// Set callback of sketch function to process project messages // Set callback of sketch function to process project messages
@@ -450,17 +481,17 @@ void MyESP::_telnetConnected() {
_consoleShowHelp(); // Show the initial message _consoleShowHelp(); // Show the initial message
// show crash dump if just restarted after a fatal crash // show crash dump if just restarted after a fatal crash
#ifdef CRASH
uint32_t crash_time; uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time != 0) && (crash_time != 0xFFFFFFFF)) { if ((crash_time != 0) && (crash_time != 0xFFFFFFFF)) {
crashDump(); crashDump();
/*
// clear crash data // clear crash data
crash_time = 0xFFFFFFFF; crash_time = 0xFFFFFFFF;
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit(); EEPROMr.commit();
*/
} }
#endif
// call callback // call callback
if (_telnet_callback) { if (_telnet_callback) {
@@ -497,16 +528,18 @@ void MyESP::_consoleShowHelp() {
} else { } else {
myDebug_P(PSTR("* Hostname: %s (%s)"), _getESPhostname().c_str(), WiFi.localIP().toString().c_str()); myDebug_P(PSTR("* Hostname: %s (%s)"), _getESPhostname().c_str(), WiFi.localIP().toString().c_str());
myDebug_P(PSTR("* WiFi SSID: %s (signal %d%%)"), WiFi.SSID().c_str(), getWifiQuality()); myDebug_P(PSTR("* WiFi SSID: %s (signal %d%%)"), WiFi.SSID().c_str(), getWifiQuality());
myDebug_P(PSTR("* MQTT is %s"), mqttClient.connected() ? "connected" : "disconnected"); if (isMQTTConnected()) {
myDebug_P(PSTR("* MQTT connected (heartbeat %s)"), getHeartbeat() ? "enabled" : "disabled");
} else {
myDebug_P(PSTR("* MQTT disconnected"));
}
} }
myDebug_P(PSTR("*")); myDebug_P(PSTR("*"));
myDebug_P(PSTR("* Commands:")); myDebug_P(PSTR("* Commands:"));
myDebug_P(PSTR("* ?=help, CTRL-D/quit=exit telnet session")); myDebug_P(PSTR("* ?=help, CTRL-D/quit=exit telnet session"));
myDebug_P(PSTR("* set, system, reboot")); myDebug_P(PSTR("* set, system, reboot"));
#ifdef CRASH myDebug_P(PSTR("* crash <dump | clear>"));
myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
#endif
// print custom commands if available. Taken from progmem // print custom commands if available. Taken from progmem
if (_telnetcommand_callback) { if (_telnetcommand_callback) {
@@ -590,6 +623,7 @@ void MyESP::_printSetCommands() {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR(" serial=%s"), (_use_serial) ? "on" : "off"); myDebug_P(PSTR(" serial=%s"), (_use_serial) ? "on" : "off");
myDebug_P(PSTR(" heartbeat=%s"), (_heartbeat) ? "on" : "off");
// print any custom settings // print any custom settings
(_fs_settings_callback)(MYESP_FSACTION_LIST, 0, NULL, NULL); (_fs_settings_callback)(MYESP_FSACTION_LIST, 0, NULL, NULL);
@@ -600,6 +634,7 @@ void MyESP::_printSetCommands() {
// reset / restart // reset / restart
void MyESP::resetESP() { void MyESP::resetESP() {
myDebug_P(PSTR("* Reboot ESP...")); myDebug_P(PSTR("* Reboot ESP..."));
_deferredReset(100, CUSTOM_RESET_TERMINAL);
end(); end();
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
ESP.restart(); ESP.restart();
@@ -638,7 +673,7 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
} }
ok = true; ok = true;
jw.enableSTA(false); jw.enableSTA(false);
myDebug_P(PSTR("Note: please reboot to apply new WiFi settings")); myDebug_P(PSTR("Note: please 'reboot' ESP to apply new WiFi settings"));
} else if (strcmp(setting, "wifi_password") == 0) { } else if (strcmp(setting, "wifi_password") == 0) {
if (_wifi_password) if (_wifi_password)
free(_wifi_password); free(_wifi_password);
@@ -648,7 +683,7 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
} }
ok = true; ok = true;
jw.enableSTA(false); jw.enableSTA(false);
myDebug_P(PSTR("Note: please reboot to apply new WiFi settings")); myDebug_P(PSTR("Note: please 'reboot' ESP to apply new WiFi settings"));
} else if (strcmp(setting, "mqtt_host") == 0) { } else if (strcmp(setting, "mqtt_host") == 0) {
if (_mqtt_host) if (_mqtt_host)
@@ -691,6 +726,23 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
ok = false; ok = false;
} }
} }
} else if (strcmp(setting, "heartbeat") == 0) {
ok = true;
_heartbeat = false;
if (value) {
if (strcmp(value, "on") == 0) {
_heartbeat = true;
ok = true;
myDebug_P(PSTR("Heartbeat on"));
} else if (strcmp(value, "off") == 0) {
_heartbeat = false;
ok = true;
myDebug_P(PSTR("Heartbeat off"));
} else {
ok = false;
}
}
} else { } else {
// finally check for any custom commands // finally check for any custom commands
ok = (_fs_settings_callback)(MYESP_FSACTION_SET, wc, setting, value); ok = (_fs_settings_callback)(MYESP_FSACTION_SET, wc, setting, value);
@@ -791,21 +843,18 @@ void MyESP::_telnetCommand(char * commandLine) {
} }
// crash command // crash command
#ifdef CRASH if ((strcmp(ptrToCommandName, "crash") == 0) && (wc == 2)) {
if ((strcmp(ptrToCommandName, "crash") == 0) && (wc >= 2)) {
char * cmd = _telnet_readWord(false); char * cmd = _telnet_readWord(false);
if (strcmp(cmd, "dump") == 0) { if (strcmp(cmd, "dump") == 0) {
crashDump(); crashDump();
} else if (strcmp(cmd, "clear") == 0) { } else if (strcmp(cmd, "clear") == 0) {
crashClear(); crashClear();
} else if ((strcmp(cmd, "test") == 0) && (wc == 3)) { } else {
char * value = _telnet_readWord(false); myDebug_P(PSTR("Error. Usage: crash <dump | clear>"));
crashTest(atoi(value));
} }
return; // don't call custom command line callback return; // don't call custom command line callback
} }
#endif
// call callback function // call callback function
(_telnetcommand_callback)(wc, commandLine); (_telnetcommand_callback)(wc, commandLine);
@@ -852,17 +901,145 @@ String MyESP::_buildTime() {
// returns system uptime in seconds - copied for espurna. see (c) // returns system uptime in seconds - copied for espurna. see (c)
unsigned long MyESP::_getUptime() { unsigned long MyESP::_getUptime() {
static unsigned long last_uptime = 0; static uint32_t last_uptime = 0;
static unsigned char uptime_overflows = 0; static uint8_t uptime_overflows = 0;
if (millis() < last_uptime) if (millis() < last_uptime) {
++uptime_overflows; ++uptime_overflows;
last_uptime = millis(); }
unsigned long uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000); last_uptime = millis();
uint32_t uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000);
return uptime_seconds; return uptime_seconds;
} }
// reason code
void MyESP::_rtcmemInit() {
memset((uint32_t *)RTCMEM_ADDR, 0, sizeof(uint32_t) * RTCMEM_BLOCKS);
Rtcmem->magic = RTCMEM_MAGIC;
}
uint8_t MyESP::_getSystemStabilityCounter() {
system_rtcmem_t data;
data.value = Rtcmem->sys;
return data.parts.stability_counter;
}
void MyESP::_setSystemStabilityCounter(uint8_t counter) {
system_rtcmem_t data;
data.value = Rtcmem->sys;
data.parts.stability_counter = counter;
Rtcmem->sys = data.value;
}
uint8_t MyESP::_getSystemResetReason() {
system_rtcmem_t data;
data.value = Rtcmem->sys;
return data.parts.reset_reason;
}
void MyESP::_setSystemResetReason(uint8_t reason) {
system_rtcmem_t data;
data.value = Rtcmem->sys;
data.parts.reset_reason = reason;
Rtcmem->sys = data.value;
}
uint32_t MyESP::getSystemResetReason() {
return resetInfo.reason;
}
void MyESP::_rtcmemSetup() {
_rtcmem_status = _rtcmemStatus();
if (!_rtcmem_status) {
_rtcmemInit();
}
}
void MyESP::_setCustomResetReason(uint8_t reason) {
_setSystemResetReason(reason);
}
bool MyESP::_rtcmemStatus() {
bool readable;
switch (getSystemResetReason()) {
case REASON_EXT_SYS_RST:
case REASON_WDT_RST:
case REASON_DEFAULT_RST:
readable = false;
break;
default:
readable = true;
}
readable = readable and (RTCMEM_MAGIC == Rtcmem->magic);
return readable;
}
bool MyESP::rtcmemStatus() {
return _rtcmem_status;
}
unsigned char MyESP::_getCustomResetReason() {
static unsigned char status = 255;
if (status == 255) {
if (_rtcmemStatus())
status = _getSystemResetReason();
if (status > 0)
_setCustomResetReason(0);
if (status > CUSTOM_RESET_MAX)
status = 0;
}
return status;
}
void MyESP::_deferredReset(unsigned long delaytime, unsigned char reason) {
delay(delaytime);
_setCustomResetReason(reason);
}
// Call this method on boot with start=true to increase the crash counter
// Call it again once the system is stable to decrease the counter
// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable
void MyESP::_setSystemCheck(bool stable) {
uint8_t value = 0;
if (stable) {
value = 0; // system is ok
// myDebug_P(PSTR("[SYSTEM] System OK\n"));
} else {
if (!rtcmemStatus()) {
_setSystemStabilityCounter(1);
return;
}
value = _getSystemStabilityCounter();
if (++value > SYSTEM_CHECK_MAX) {
_systemStable = false;
value = 0; // system is unstable
myDebug_P(PSTR("[SYSTEM] Warning, system UNSTABLE\n"));
}
}
_setSystemStabilityCounter(value);
}
bool MyESP::getSystemCheck() {
return _systemStable;
}
void MyESP::_systemCheckLoop() {
static bool checked = false;
if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
_setSystemCheck(true); // Flag system as stable
checked = true;
}
}
// print out ESP system stats // print out ESP system stats
// for battery power is ESP.getVcc() // for battery power is ESP.getVcc()
void MyESP::showSystemStats() { void MyESP::showSystemStats() {
@@ -881,16 +1058,21 @@ void MyESP::showSystemStats() {
} }
// uptime // uptime
uint32_t t = _getUptime(); // seconds uint32_t t = _getUptime(); // seconds
uint32_t d = t / 86400L; uint32_t d = t / 86400L;
uint32_t h = (t / 3600L) % 60; uint32_t h = ((t % 86400L) / 3600L) % 60;
uint32_t rem = t % 3600L; uint32_t rem = t % 3600L;
uint8_t m = rem / 60; uint8_t m = rem / 60;
uint8_t s = rem % 60; uint8_t s = rem % 60;
myDebug_P(PSTR(" [APP] Uptime: %d days, %d hours, %d minutes, %d seconds"), d, h, m, s); myDebug_P(PSTR(" [APP] Uptime: %d days %d hours %d minutes %d seconds"), d, h, m, s);
myDebug_P(PSTR(" [APP] System Load: %d%%"), getSystemLoadAverage()); myDebug_P(PSTR(" [APP] System Load: %d%%"), getSystemLoadAverage());
if (!getSystemCheck()) {
myDebug_P(PSTR(" [SYSTEM] Device is in SAFE MODE"));
}
if (isAPmode()) { if (isAPmode()) {
myDebug_P(PSTR(" [WIFI] Device is in AP mode with SSID %s"), jw.getAPSSID().c_str()); myDebug_P(PSTR(" [WIFI] Device is in AP mode with SSID %s"), jw.getAPSSID().c_str());
} else { } else {
@@ -901,10 +1083,8 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [WIFI] WiFi MAC: %s"), WiFi.macAddress().c_str()); myDebug_P(PSTR(" [WIFI] WiFi MAC: %s"), WiFi.macAddress().c_str());
#ifdef CRASH
char output_str[80] = {0}; char output_str[80] = {0};
char buffer[16] = {0}; char buffer[16] = {0};
/* Crash info */
myDebug_P(PSTR(" [EEPROM] EEPROM size: %u"), EEPROMr.reserved() * SPI_FLASH_SEC_SIZE); myDebug_P(PSTR(" [EEPROM] EEPROM size: %u"), EEPROMr.reserved() * SPI_FLASH_SEC_SIZE);
strlcpy(output_str, " [EEPROM] EEPROM Sector pool size is ", sizeof(output_str)); strlcpy(output_str, " [EEPROM] EEPROM Sector pool size is ", sizeof(output_str));
strlcat(output_str, itoa(EEPROMr.size(), buffer, 10), sizeof(output_str)); strlcat(output_str, itoa(EEPROMr.size(), buffer, 10), sizeof(output_str));
@@ -914,7 +1094,6 @@ void MyESP::showSystemStats() {
strlcat(output_str, " ", sizeof(output_str)); strlcat(output_str, " ", sizeof(output_str));
} }
myDebug(output_str); myDebug(output_str);
#endif
#ifdef ARDUINO_BOARD #ifdef ARDUINO_BOARD
myDebug_P(PSTR(" [SYSTEM] Board: %s"), ARDUINO_BOARD); myDebug_P(PSTR(" [SYSTEM] Board: %s"), ARDUINO_BOARD);
@@ -929,6 +1108,21 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [SYSTEM] Boot version: %d"), ESP.getBootVersion()); myDebug_P(PSTR(" [SYSTEM] Boot version: %d"), ESP.getBootVersion());
myDebug_P(PSTR(" [SYSTEM] Boot mode: %d"), ESP.getBootMode()); myDebug_P(PSTR(" [SYSTEM] Boot mode: %d"), ESP.getBootMode());
//myDebug_P(PSTR("[SYSTEM] Firmware MD5: %s"), (char *)ESP.getSketchMD5().c_str()); //myDebug_P(PSTR("[SYSTEM] Firmware MD5: %s"), (char *)ESP.getSketchMD5().c_str());
unsigned char reason = _getCustomResetReason();
if (reason > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[reason - 1]);
myDebug_P(PSTR(" [SYSTEM] Last reset reason: %s"), buffer);
} else {
myDebug_P(PSTR(" [SYSTEM] Last reset reason: %s"), (char *)ESP.getResetReason().c_str());
myDebug_P(PSTR(" [SYSTEM] Last reset info: %s"), (char *)ESP.getResetInfo().c_str());
}
myDebug_P(PSTR(" [SYSTEM] Restart count: %d"), _getSystemStabilityCounter());
myDebug_P(PSTR(" [SYSTEM] rtcmem status:%u blocks:%u addr:0x%p"), _rtcmemStatus(), RtcmemSize, Rtcmem);
for (uint8_t block = 0; block < RtcmemSize; ++block) {
myDebug_P(PSTR(" [SYSTEM] rtcmem %02u: %u"), block, reinterpret_cast<volatile uint32_t *>(RTCMEM_ADDR)[block]);
}
#endif #endif
FlashMode_t mode = ESP.getFlashChipMode(); FlashMode_t mode = ESP.getFlashChipMode();
@@ -945,10 +1139,58 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [MEM] Firmware size: %d"), ESP.getSketchSize()); myDebug_P(PSTR(" [MEM] Firmware size: %d"), ESP.getSketchSize());
myDebug_P(PSTR(" [MEM] Max OTA size: %d"), (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); myDebug_P(PSTR(" [MEM] Max OTA size: %d"), (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
myDebug_P(PSTR(" [MEM] OTA Reserved: %d"), 4 * SPI_FLASH_SEC_SIZE); myDebug_P(PSTR(" [MEM] OTA Reserved: %d"), 4 * SPI_FLASH_SEC_SIZE);
myDebug_P(PSTR(" [MEM] Free Heap: %d"), ESP.getFreeHeap());
uint32_t total_memory = getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap();
myDebug(" [MEM] Free Heap: %d bytes initially | %d bytes used (%2u%%) | %d bytes free (%2u%%)",
total_memory,
total_memory - free_memory,
100 * (total_memory - free_memory) / total_memory,
free_memory,
100 * free_memory / total_memory);
myDebug_P(PSTR("")); myDebug_P(PSTR(""));
} }
/*
* Send heartbeat via MQTT with all system data
*/
void MyESP::_heartbeatCheck(bool force = false) {
static uint32_t last_heartbeat = 0;
if ((millis() - last_heartbeat > HEARTBEAT_INTERVAL) || force) {
last_heartbeat = millis();
if (!isMQTTConnected() || !(_heartbeat)) {
return;
}
uint32_t total_memory = getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap();
uint8_t mem_available = 100 * free_memory / total_memory; // as a %
char payload[300] = {0};
char s[10];
strlcpy(payload, "version=", sizeof(payload));
strlcat(payload, _app_version, sizeof(payload)); // version
strlcat(payload, ", IP=", sizeof(payload));
strlcat(payload, WiFi.localIP().toString().c_str(), sizeof(payload)); // IP address
strlcat(payload, ", rssid=", sizeof(payload));
strlcat(payload, itoa(getWifiQuality(), s, 10), sizeof(payload)); // rssi %
strlcat(payload, "%, load=", sizeof(payload));
strlcat(payload, ltoa(getSystemLoadAverage(), s, 10), sizeof(payload)); // load
strlcat(payload, "%, uptime=", sizeof(payload));
strlcat(payload, ltoa(_getUptime(), s, 10), sizeof(payload)); // uptime in secs
strlcat(payload, "secs, freemem=", sizeof(payload));
strlcat(payload, itoa(mem_available, s, 10), sizeof(payload)); // free mem as a %
strlcat(payload, "%", sizeof(payload));
// send to MQTT
myESP.mqttPublish(MQTT_TOPIC_HEARTBEAT, payload);
}
}
// handler for Telnet // handler for Telnet
void MyESP::_telnetHandle() { void MyESP::_telnetHandle() {
SerialAndTelnet.handle(); SerialAndTelnet.handle();
@@ -1187,7 +1429,7 @@ void MyESP::_fs_eraseConfig() {
"automatically restart when finished.")); "automatically restart when finished."));
if (SPIFFS.format()) { if (SPIFFS.format()) {
delay(1000); // wait 1 seconds delay(1000); // wait 1 second
resetESP(); resetESP();
} }
} }
@@ -1242,6 +1484,8 @@ bool MyESP::_fs_loadConfig() {
_use_serial = (bool)json["use_serial"]; _use_serial = (bool)json["use_serial"];
_heartbeat = (bool)json["heartbeat"];
// 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);
@@ -1270,6 +1514,7 @@ bool MyESP::fs_saveConfig() {
json["mqtt_username"] = _mqtt_username; json["mqtt_username"] = _mqtt_username;
json["mqtt_password"] = _mqtt_password; json["mqtt_password"] = _mqtt_password;
json["use_serial"] = _use_serial; json["use_serial"] = _use_serial;
json["heartbeat"] = _heartbeat;
// callback for saving custom settings // callback for saving custom settings
(void)(_fs_callback)(MYESP_FSACTION_SAVE, json); (void)(_fs_callback)(MYESP_FSACTION_SAVE, json);
@@ -1321,19 +1566,19 @@ void MyESP::_fs_setup() {
// _fs_printConfig(); // enable for debugging // _fs_printConfig(); // enable for debugging
} }
uint16_t MyESP::getSystemLoadAverage() { uint32_t MyESP::getSystemLoadAverage() {
return _load_average; return _load_average;
} }
// calculate load average // calculate load average
void MyESP::_calculateLoad() { void MyESP::_calculateLoad() {
static unsigned long last_loadcheck = 0; static uint32_t last_loadcheck = 0;
static unsigned long load_counter_temp = 0; static uint32_t load_counter_temp = 0;
load_counter_temp++; load_counter_temp++;
if (millis() - last_loadcheck > LOADAVG_INTERVAL) { if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
static unsigned long load_counter = 0; static uint32_t load_counter = 0;
static unsigned long load_counter_max = 1; static uint32_t load_counter_max = 1;
load_counter = load_counter_temp; load_counter = load_counter_temp;
load_counter_temp = 0; load_counter_temp = 0;
@@ -1385,7 +1630,6 @@ int MyESP::getWifiQuality() {
return 2 * (dBm + 100); return 2 * (dBm + 100);
} }
#ifdef CRASH
/** /**
* Save crash information in EEPROM * Save crash information in EEPROM
* This function is called automatically if ESP8266 suffers an exception * This function is called automatically if ESP8266 suffers an exception
@@ -1421,46 +1665,6 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
EEPROMr.commit(); EEPROMr.commit();
} }
void MyESP::crashTest(uint8_t t) {
if (t == 1) {
myDebug_P(PSTR("[CRASH] Attempting to divide by zero ..."));
int result, zero;
zero = 0;
result = 1 / zero;
myDebug_P(PSTR("Result = %d"), result);
}
if (t == 2) {
myDebug_P(PSTR("[CRASH] Attempting to read through a pointer to no object ..."));
int * nullPointer;
nullPointer = NULL;
// null pointer dereference - read
// attempt to read a value through a null pointer
Serial.println(*nullPointer);
}
if (t == 3) {
myDebug_P(PSTR("[CRASH] Crashing with hardware WDT (%ld ms) ...\n"), millis());
ESP.wdtDisable();
while (true) {
// stay in an infinite loop doing nothing
// this way other process can not be executed
//
// Note:
// Hardware wdt kicks in if software wdt is unable to perfrom
// Nothing will be saved in EEPROM for the hardware wdt
}
}
if (t == 4) {
myDebug_P(PSTR("[CRASH] Crashing with software WDT (%ld ms) ...\n"), millis());
while (true) {
// stay in an infinite loop doing nothing
// this way other process can not be executed
}
}
}
/** /**
* Clears crash info * Clears crash info
*/ */
@@ -1473,18 +1677,71 @@ void MyESP::crashClear() {
/** /**
* Print out crash information that has been previously saved in EEPROM * Print out crash information that has been previously saved in EEPROM
* Copied from https://github.com/krzychb/EspSaveCrash
*/ */
void MyESP::crashDump() { void MyESP::crashDump() {
uint32_t crash_time; uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) { if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
myDebug_P(PSTR("[CRASH] No crash info")); myDebug_P(PSTR("[CRASH] No crash data captured."));
return; return;
} }
myDebug_P(PSTR("[CRASH] Latest crash was at %lu ms after boot"), crash_time); uint32_t t = crash_time / 1000; // convert to seconds
myDebug_P(PSTR("[CRASH] Reason of restart: %u"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON)); uint32_t d = t / 86400L;
myDebug_P(PSTR("[CRASH] Exception cause: %u"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE)); uint32_t h = (t / 3600L) % 60;
uint32_t rem = t % 3600L;
uint8_t m = rem / 60;
uint8_t s = rem % 60;
myDebug_P(PSTR("[CRASH] Last crash was %d days %d hours %d minutes %d seconds since boot time"), d, h, m, s);
// get reason and exception
// https://www.espressif.com/sites/default/files/documentation/esp8266_reset_causes_and_common_fatal_exception_causes_en.pdf
char buffer[80] = {0};
char ss[16] = {0};
strlcpy(buffer, "[CRASH] Reason of restart: ", sizeof(buffer));
uint8_t reason = EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON);
switch (reason) {
case REASON_WDT_RST:
strlcat(buffer, "1 - Hardware WDT reset", sizeof(buffer));
break;
case REASON_EXCEPTION_RST:
strlcat(buffer, "2 - Fatal exception", sizeof(buffer));
break;
case REASON_SOFT_WDT_RST:
strlcat(buffer, "3 - Software watchdog reset", sizeof(buffer));
break;
case REASON_EXT_SYS_RST:
strlcat(buffer, "6 - Hardware reset", sizeof(buffer));
break;
case REASON_SOFT_RESTART:
strlcat(buffer, "4 - Software reset", sizeof(buffer));
break;
default:
strlcat(buffer, itoa(reason, ss, 10), sizeof(buffer));
}
myDebug(buffer);
// check for exception
// see https://github.com/esp8266/Arduino/blob/master/doc/exception_causes.rst
if (reason == REASON_EXCEPTION_RST) {
// get exception cause
uint8_t cause = EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE);
strlcpy(buffer, "[CRASH] Exception cause: ", sizeof(buffer));
if (cause == 0) {
strlcat(buffer, "0 - IllegalInstructionCause", sizeof(buffer));
} else if (cause == 3) {
strlcat(buffer, "3 - LoadStoreErrorCause", sizeof(buffer));
} else if (cause == 6) {
strlcat(buffer, "6 - IntegerDivideByZeroCause", sizeof(buffer));
} else if (cause == 9) {
strlcat(buffer, "9 - LoadStoreAlignmentCause", sizeof(buffer));
} else {
strlcat(buffer, itoa(cause, ss, 10), sizeof(buffer));
}
}
myDebug(buffer);
uint32_t epc1, epc2, epc3, excvaddr, depc; uint32_t epc1, epc2, epc3, excvaddr, depc;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1); EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
@@ -1519,17 +1776,8 @@ void MyESP::crashDump() {
SerialAndTelnet.println(); SerialAndTelnet.println();
} }
myDebug_P(PSTR("<<<stack<<<")); myDebug_P(PSTR("<<<stack<<<"));
myDebug_P(PSTR("\nTo clean this dump use the command: %scrash clear%s\n"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
} }
#else
void MyESP::crashTest(uint8_t t) {
}
void MyESP::crashClear() {
}
void MyESP::crashDump() {
}
void MyESP::crashInfo() {
}
#endif
// setup MyESP // setup MyESP
void MyESP::begin(const char * app_hostname, const char * app_name, const char * app_version) { void MyESP::begin(const char * app_hostname, const char * app_name, const char * app_version) {
@@ -1537,11 +1785,21 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char *
_app_name = strdup(app_name); _app_name = strdup(app_name);
_app_version = strdup(app_version); _app_version = strdup(app_version);
_telnet_setup(); // Telnet setup, does first to set Serial getInitialFreeHeap(); // get initial free mem
_eeprom_setup(); // set up eeprom for storing crash data
_rtcmemSetup();
_telnet_setup(); // Telnet setup, called first to set Serial
_eeprom_setup(); // set up EEPROM for storing crash data, if compiled with -DCRASH
_fs_setup(); // SPIFFS setup, do this first to get values _fs_setup(); // SPIFFS setup, do this first to get values
_wifi_setup(); // WIFI setup _wifi_setup(); // WIFI setup
_ota_setup(); // init OTA _ota_setup(); // init OTA
// print a welcome message
myDebug_P(PSTR("\n* %s version %s"), _app_name, _app_version);
SerialAndTelnet.flush();
_setSystemCheck(false); // reset system check
_heartbeatCheck(true); // force heartbeat
} }
/* /*
@@ -1549,17 +1807,11 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char *
*/ */
void MyESP::loop() { void MyESP::loop() {
_calculateLoad(); _calculateLoad();
_systemCheckLoop();
_heartbeatCheck();
_telnetHandle(); _telnetHandle();
jw.loop(); // WiFi
jw.loop(); // WiFi
/*
// do nothing else until we've got a wifi connection
if (WiFi.getMode() & WIFI_AP) {
return;
}
*/
ArduinoOTA.handle(); // OTA ArduinoOTA.handle(); // OTA
_mqttConnect(); // MQTT _mqttConnect(); // MQTT

View File

@@ -9,7 +9,7 @@
#ifndef MyEMS_h #ifndef MyEMS_h
#define MyEMS_h #define MyEMS_h
#define MYESP_VERSION "1.1.11" #define MYESP_VERSION "1.1.16"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
@@ -19,12 +19,12 @@
#include <JustWifi.h> // https://github.com/xoseperez/justwifi #include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy #include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#ifdef CRASH
#include <EEPROM_Rotate.h> #include <EEPROM_Rotate.h>
extern "C" { extern "C" {
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t); #include "user_interface.h"
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
extern struct rst_info resetInfo;
} }
#endif
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
//#include <ESPmDNS.h> //#include <ESPmDNS.h>
@@ -49,8 +49,9 @@ void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection #define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
#define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 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_TOPIC_SIZE 50 // max length of MQTT message #define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
#define MQTT_TOPIC_START "start" #define MQTT_TOPIC_START "start"
#define MQTT_TOPIC_HEARTBEAT "heartbeat"
#define MQTT_TOPIC_START_PAYLOAD "start" #define MQTT_TOPIC_START_PAYLOAD "start"
#define MQTT_TOPIC_RESTART "restart" #define MQTT_TOPIC_RESTART "restart"
@@ -86,6 +87,18 @@ void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#define COLOR_BRIGHT_CYAN "\x1B[0;96m" #define COLOR_BRIGHT_CYAN "\x1B[0;96m"
#define COLOR_BRIGHT_WHITE "\x1B[0;97m" #define COLOR_BRIGHT_WHITE "\x1B[0;97m"
// reset reason codes
PROGMEM const char custom_reset_hardware[] = "Hardware button";
PROGMEM const char custom_reset_terminal[] = "Reboot from terminal";
PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT";
PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update";
PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, custom_reset_terminal, custom_reset_mqtt, custom_reset_ota};
#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button
#define CUSTOM_RESET_TERMINAL 2 // Reset from terminal
#define CUSTOM_RESET_MQTT 3 // Reset via MQTT
#define CUSTOM_RESET_OTA 4 // Reset after successful OTA update
#define CUSTOM_RESET_MAX 4
// SPIFFS // SPIFFS
#define SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/ #define SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/
@@ -119,6 +132,31 @@ void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes #define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable #define SAVE_CRASH_STACK_TRACE 0x22 // variable
// Base address of USER RTC memory
// https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map#memmory-mapped-io-registers
#define RTCMEM_ADDR_BASE (0x60001200)
// RTC memory is accessed using blocks of 4 bytes.
// Blocks 0..63 are reserved by the SDK, 64..192 are available to the user.
// Blocks 64..96 are reserved by the eboot 'struct eboot_command' (128 -> (128 / 4) -> 32):
// https://github.com/esp8266/Arduino/blob/master/bootloaders/eboot/eboot_command.h
#define RTCMEM_OFFSET 32u
#define RTCMEM_ADDR (RTCMEM_ADDR_BASE + (RTCMEM_OFFSET * 4u))
#define RTCMEM_BLOCKS 96u
#define RTCMEM_MAGIC 0x45535075
struct RtcmemData {
uint32_t magic; // RTCMEM_MAGIC
uint32_t sys; // system reset reason (1-4)
uint32_t energy; // store energy count
};
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute)
#define SYSTEM_CHECK_MAX 5 // After this many crashes on boot
#define HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins)
typedef struct { typedef struct {
bool set; // is it a set command bool set; // is it a set command
char key[50]; char key[50];
@@ -193,14 +231,18 @@ class MyESP {
void crashInfo(); void crashInfo();
// general // general
void end(); void end();
void loop(); void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version); void begin(const char * app_hostname, const char * app_name, const char * app_version);
void setBoottime(const char * boottime); void setBoottime(const char * boottime);
void resetESP(); void resetESP();
uint16_t getSystemLoadAverage(); int getWifiQuality();
int getWifiQuality(); void showSystemStats();
void showSystemStats(); bool getHeartbeat();
// rtcmem and reset reason
bool rtcmemStatus();
uint32_t getSystemResetReason();
private: private:
// mqtt // mqtt
@@ -226,6 +268,7 @@ class MyESP {
char * _mqtt_topic; char * _mqtt_topic;
unsigned long _mqtt_last_connection; unsigned long _mqtt_last_connection;
bool _mqtt_connecting; bool _mqtt_connecting;
bool _rtcmem_status;
// wifi // wifi
DNSServer dnsServer; // For Access Point (AP) support DNSServer dnsServer; // For Access Point (AP) support
@@ -280,12 +323,41 @@ class MyESP {
char * _boottime; char * _boottime;
bool _suspendOutput; bool _suspendOutput;
bool _use_serial; bool _use_serial;
bool _heartbeat;
unsigned long _getUptime(); unsigned long _getUptime();
String _buildTime(); String _buildTime();
// load average (0..100) // reset reason and rtcmem
void _calculateLoad(); bool _rtcmemStatus();
unsigned short int _load_average; void _rtcmemInit();
void _rtcmemSetup();
void _deferredReset(unsigned long delay, uint8_t reason);
uint8_t _getSystemStabilityCounter();
void _setSystemStabilityCounter(uint8_t counter);
uint8_t _getSystemResetReason();
void _setSystemResetReason(uint8_t reason);
unsigned char _getCustomResetReason();
void _setCustomResetReason(unsigned char reason);
bool _systemStable;
bool getSystemCheck();
void _systemCheckLoop();
void _setSystemCheck(bool stable);
// load average (0..100) and heap ram
uint32_t getSystemLoadAverage();
void _calculateLoad();
uint32_t _load_average;
uint32_t getInitialFreeHeap();
uint32_t getUsedHeap();
// heartbeat
void _heartbeatCheck(bool force);
}; };
extern MyESP myESP; extern MyESP myESP;

View File

@@ -1,50 +0,0 @@
;
; PlatformIO Project Configuration File for EMS-ESP
; Uses PlatformIO 4.0
;
[platformio]
env_default = debug
[common]
; hard code if you prefer. Recommendation is to set from within the app when in Serial or AP mode
; wifi_settings = '-DWIFI_SSID="XXXX"' '-DWIFI_PASSWORD="XXXX"'
wifi_settings =
[env]
platform = espressif8266
; platform = https://github.com/platformio/platform-espressif8266.git
board = d1_mini
; board = nodemcuv2
; board = d1_mini_pro
framework = arduino
lib_deps =
CRC32
CircularBuffer
JustWifi
AsyncMqttClient
ArduinoJson
OneWire
EEPROM_rotate
lib_ignore =
;lib_extra_dirs = D:\dev\lib
upload_speed = 921600
monitor_speed = 115200
; Set the ESP8266 clock frequency to 160MHz
;board_f_cpu = 160000000L
upload_protocol = espota
upload_port = ems-esp.local
[env:debug]
build_flags = -g -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DCRASH -DTESTS ${common.wifi_settings}
[env:clean]
extra_scripts = pre:scripts/clean_fw.py
[env:release]
build_flags = -g -w
extra_scripts = pre:scripts/rename_fw.py
[env:checkcode]
build_flags = -g -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DCRASH -DTESTS
extra_scripts = scripts/checkcode.py

View File

@@ -1,19 +1,31 @@
;
; PlatformIO Project Configuration File for EMS-ESP
; Uses PlatformIO 4.0
;
[platformio] [platformio]
; add here your board, e.g. nodemcuv2, d1_mini, d1_mini_pro default_envs = release
env_default = d1_mini ;default_envs = debug
[common] [common]
flash_mode = dout debug_flags = -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DTESTS
general_flags = -g -w -DNO_GLOBAL_EEPROM
build_flags = -g -w arduino_core_2_3_0 = espressif8266@1.5.0
arduino_core_2_4_0 = espressif8266@1.6.0
; for debug use these... arduino_core_2_4_1 = espressif8266@1.7.3
; build_flags = -g -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DCRASH -DTESTS arduino_core_2_4_2 = espressif8266@1.8.0
arduino_core_2_5_0 = espressif8266@2.0.4
wifi_settings = arduino_core_2_5_1 = espressif8266@2.1.1
; hard code if you prefer. Recommendation is to set from within the app when in Serial or AP mode arduino_core_2_5_2 = espressif8266@2.2.1
; wifi_settings = '-DWIFI_SSID="XXXX"' '-DWIFI_PASSWORD="XXXX"' arduino_core_latest = espressif8266
[env]
board = d1_mini
; board = nodemcuv2
; board = d1_mini_pro
framework = arduino
platform = ${common.arduino_core_latest}
lib_deps = lib_deps =
CRC32 CRC32
CircularBuffer CircularBuffer
@@ -22,17 +34,23 @@ lib_deps =
ArduinoJson ArduinoJson
OneWire OneWire
EEPROM_rotate EEPROM_rotate
[env:d1_mini]
board = d1_mini
platform = espressif8266
framework = arduino
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags} ${common.wifi_settings}
board_build.flash_mode = ${common.flash_mode}
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
; for OTA comment out these sections ; uncomment next 2 lines for OTA
;upload_protocol = espota ;upload_protocol = espota
;upload_port = ems-esp.local ;upload_port = ems-esp.local
;upload_port = <or add here the IP of the ESP8266>
[env:debug]
build_flags = ${common.general_flags} ${common.debug_flags}
extra_scripts = pre:scripts/rename_fw.py
[env:clean]
extra_scripts = pre:scripts/clean_fw.py
[env:release]
build_flags = ${common.general_flags}
extra_scripts = pre:scripts/rename_fw.py
[env:checkcode]
build_flags = ${common.general_flags}
extra_scripts = scripts/checkcode.py

Binary file not shown.

View File

@@ -17,11 +17,4 @@ import os
# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01 # 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01
# <<<stack<<< # <<<stack<<<
# two ways of analyzing the dumps call(['python', 'scripts/decoder.py ', '-s', '-e', os.getcwd()+"/.pio/build/debug/firmware_d1_mini.elf", 'scripts/stackdmp.txt'])
# 1. java -jar .\EspStackTraceDecoder.jar C:\Users\Paul\.platformio\packages\toolchain-xtensa\bin\xtensa-lx106-elf-addr2line.exe .pioenvs/d1_mini/firmware_d1_mini.elf stackdmp.txt
# 2. python decoder.py -p ESP8266 -t C:\Users\Paul\.platformio\packages\toolchain-xtensa -e .pioenvs/nodemcuv2/firmware.elf stackdmp.txt
# for linux use
# /mnt/c/users/paul/.platformio/packages/toolchain-xtensa/bin
call(['python', 'decoder.py ', '-s', '-e', os.getcwd()+"/.pioenvs/d1_mini/firmware_d1_mini.elf", 'stackdmp.txt'])

View File

@@ -1,11 +1,34 @@
>>>stack>>> >>>stack>>>
3fffff30: 00000020 3fff4dcc 0000000d 40216e4e 3ffffdc0: 3fff472c 00000001 3ffec811 40224020
3fffff40: 00000020 3fff4dcc 3fff4db8 3fff4e3c 3ffffdd0: 3fff472c 00000019 3fff8acd 0000001b
3fffff50: 0000000d 3fff4db8 3fff4b64 4021280d 3ffffde0: 3fff4a5c 0000072a 0000072a 402113c8
3fffff60: 0000000d 3fff5094 3ffe8560 3fff5038 3ffffdf0: 3ffec4b5 3fff44d8 3fff8ab4 402117f0
3fffff70: 3fff4b64 3fff316c 3fff4b54 3fff5038 3ffffe00: 3fff8ab4 3fff44d8 3fff472c 4022d20a
3fffff80: 3fffdad0 3fff316c 3fff4b64 402128df 3ffffe10: 00000000 40209737 3fff472c 4022d1f8
3fffff90: 3fffdad0 00000000 3fff316c 4020bc20 3ffffe20: 3ffec4b5 3fff44d8 3fff8ab4 00000003
3fffffa0: 3fffdad0 00000000 3fff5008 402143d4 3ffffe30: 00000002 3fff4884 3fff44d8 40246fb8
3fffffb0: feefeffe feefeffe 3ffe8560 40100b01 3ffffe40: 3fff8ca0 ff000000 00004000 4020fb00
3ffffe50: 3fff8ca0 ff000000 3fff44d8 4024f9d5
3ffffe60: 4020d0d0 3fff4884 3fff44d8 00000003
3ffffe70: 00000002 3fff4884 3fff44d8 4020aa5c
3ffffe80: 45455b20 4d4f5250 4545205d 4d4f5250
3ffffe90: 63655320 20726f74 6c6f6f70 7a697320
3ffffea0: 73692065 202c3420 20646e61 75206e69
3ffffeb0: 61206573 203a6572 39313031 31303120
3ffffec0: 30312038 31203731 20363130 00000000
3ffffed0: 6f626552 6120746f 72657466 63757320
3ffffee0: 73736563 206c7566 2041544f 61647075
3ffffef0: 36006574 00000000 00000000 00000000
3fffff00: 00000000 00000007 3ffe8304 40227977
3fffff10: 0000000d 00000001 3fff44d8 3fff47b0
3fffff20: 3fff8e64 00000001 3fff44d8 4020c9e1
3fffff30: 0000006d 3fff4740 0000000d 4021813e
3fffff40: 0000006d 3fff4740 3fff472c 3fff47b0
3fffff50: 0000000d 3fff472c 3fff44d8 4020cfbd
3fffff60: 0000000d 3fff4a1c 3ffe97b8 3fff49c0
3fffff70: 3fff44d8 3fff2adc 3fff44c8 3fff49c0
3fffff80: 3fffdad0 3fff2adc 3fff44d8 4020d090
3fffff90: 3fffdad0 00000000 3fff2adc 40205cbc
3fffffa0: 3fffdad0 00000000 3fff4990 4020f088
3fffffb0: feefeffe feefeffe 3ffe97b8 401006f1
<<<stack<<< <<<stack<<<

View File

@@ -106,10 +106,14 @@ command_t project_cmds[] = {
{true, "shower_alert <on | off>", "send a warning of cold water after shower time is exceeded"}, {true, "shower_alert <on | off>", "send a warning of cold water after shower time is exceeded"},
{true, "publish_wait <seconds>", "set frequency for publishing to MQTT"}, {true, "publish_wait <seconds>", "set frequency for publishing to MQTT"},
{true, "heating_circuit <1 | 2>", "set the thermostat HC to work with if using multiple heating circuits"}, {true, "heating_circuit <1 | 2>", "set the thermostat HC to work with if using multiple heating circuits"},
{true, "tx_delay <on | off>", "turn on if Tx not working on newer boilers"},
{false, "info", "show data captured on the EMS bus"}, {false, "info", "show data captured on the EMS bus"},
{false, "log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"}, {false, "log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"},
#ifdef TESTS
{false, "test <n>", "insert a test telegram on to the EMS bus"},
#endif
{false, "publish", "publish all values to MQTT"}, {false, "publish", "publish all values to MQTT"},
{false, "refresh", "fetch values from the EMS devices"}, {false, "refresh", "fetch values from the EMS devices"},
{false, "devices", "list all supported and detected EMS devices and types IDs"}, {false, "devices", "list all supported and detected EMS devices and types IDs"},
@@ -338,11 +342,13 @@ void showInfo() {
if (ems_getBusConnected()) { if (ems_getBusConnected()) {
myDebug_P(PSTR(" Bus is connected")); myDebug_P(PSTR(" Bus is connected"));
myDebug_P(PSTR(" Rx: # successful read requests=%d, # CRC errors=%d"), EMS_Sys_Status.emsRxPgks, EMS_Sys_Status.emxCrcErr);
myDebug_P(PSTR(" Rx: Poll=%d ms, # Rx telegrams read=%d, # CRC errors=%d"), ems_getPollFrequency(), EMS_Sys_Status.emsRxPgks, EMS_Sys_Status.emxCrcErr);
if (ems_getTxCapable()) { if (ems_getTxCapable()) {
myDebug_P(PSTR(" Tx: available, Tx delay is %s, # Tx telegrams sent=%d"), (ems_getTxDelay() ? "on" : "off"), EMS_Sys_Status.emsTxPkgs); char valuestr[8] = {0}; // for formatting floats
myDebug_P(PSTR(" Tx: Last poll=%s seconds ago, # successful write requests=%d"),
_float_to_char(valuestr, (ems_getPollFrequency() / (float)1000000), 3),
EMS_Sys_Status.emsTxPkgs);
} else { } else {
myDebug_P(PSTR(" Tx: no signal")); myDebug_P(PSTR(" Tx: no signal"));
} }
@@ -447,13 +453,21 @@ void showInfo() {
if (EMS_Other.SM) { if (EMS_Other.SM) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
_renderShortValue(" Collector temperature", "C", EMS_Other.SMcollectorTemp); _renderShortValue("Collector temperature", "C", EMS_Other.SMcollectorTemp);
_renderShortValue(" Bottom temperature", "C", EMS_Other.SMbottomTemp); _renderShortValue("Bottom temperature", "C", EMS_Other.SMbottomTemp);
_renderIntValue(" Pump modulation", "%", EMS_Other.SMpumpModulation); _renderIntValue("Pump modulation", "%", EMS_Other.SMpumpModulation);
_renderBoolValue(" Pump active", EMS_Other.SMpump); _renderBoolValue("Pump active", EMS_Other.SMpump);
_renderShortValue(" Energy Last Hour", "Wh", EMS_Other.SMEnergyLastHour, 1); // *10 _renderShortValue("Energy Last Hour", "Wh", EMS_Other.SMEnergyLastHour, 1); // *10
_renderShortValue(" Energy Today", "Wh", EMS_Other.SMEnergyToday, 0); _renderShortValue("Energy Today", "Wh", EMS_Other.SMEnergyToday, 0);
_renderShortValue(" Energy Total", "kWH", EMS_Other.SMEnergyTotal, 1); // *10 _renderShortValue("Energy Total", "kWH", EMS_Other.SMEnergyTotal, 1); // *10
}
// For HeatPumps
if (EMS_Other.HP) {
myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sHeat Pump stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
_renderIntValue("Pump modulation", "%", EMS_Other.HPModulation);
_renderIntValue("Pump speed", "%", EMS_Other.HPSpeed);
} }
// Thermostat stats // Thermostat stats
@@ -461,50 +475,55 @@ void showInfo() {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type)); myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type));
if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) {
// for easy temps are * 100, also we don't have the time or mode // Render Current & Setpoint Room Temperature
if ((ems_getThermostatModel() == EMS_MODEL_EASY)) {
// Temperatures are *100
_renderShortValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 10); // *100 _renderShortValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 10); // *100
_renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // *100 _renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // *100
} else if (ems_getThermostatModel() == EMS_MODEL_FR10) { }
// Junkers are *10 else if ((ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100)) {
_renderIntValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 10); // *10 // Temperatures are *10
_renderIntValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // *10 _renderShortValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 1); // *10
} else { _renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 1); // *10
}
else {
// because we store in 2 bytes short, when converting to a single byte we'll loose the negative value if its unset // because we store in 2 bytes short, when converting to a single byte we'll loose the negative value if its unset
if (EMS_Thermostat.setpoint_roomTemp <= 0) { if (EMS_Thermostat.setpoint_roomTemp <= 0) {
EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_INT_NOTSET; EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_INT_NOTSET;
} }
if (EMS_Thermostat.curr_roomTemp <= 0) { if (EMS_Thermostat.curr_roomTemp <= 0) {
EMS_Thermostat.curr_roomTemp = EMS_VALUE_INT_NOTSET; EMS_Thermostat.curr_roomTemp = EMS_VALUE_INT_NOTSET;
} }
_renderIntValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 2); // convert to a single byte * 2 _renderIntValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 2); // convert to a single byte * 2
_renderIntValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // is *10 _renderIntValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // is *10
}
if ((EMS_Thermostat.holidaytemp > 0) && (EMSESP_Status.heating_circuit == 2)) { // only if we are on a RC35 we show more info // Render Day/Night/Holiday Temperature
_renderIntValue("Day temperature", "C", EMS_Thermostat.daytemp, 2); // convert to a single byte * 2 if ((EMS_Thermostat.holidaytemp > 0) && (EMSESP_Status.heating_circuit == 2)) { // only if we are on a RC35 we show more info
_renderIntValue("Night temperature", "C", EMS_Thermostat.nighttemp, 2); // convert to a single byte * 2 _renderIntValue("Day temperature", "C", EMS_Thermostat.daytemp, 2); // convert to a single byte * 2
_renderIntValue("Vacation temperature", "C", EMS_Thermostat.holidaytemp, 2); // convert to a single byte * 2 _renderIntValue("Night temperature", "C", EMS_Thermostat.nighttemp, 2); // convert to a single byte * 2
} _renderIntValue("Vacation temperature", "C", EMS_Thermostat.holidaytemp, 2); // convert to a single byte * 2
}
myDebug_P(PSTR(" Thermostat time is %02d:%02d:%02d %d/%d/%d"), // Render Thermostat Date & Time
EMS_Thermostat.hour, myDebug_P(PSTR(" Thermostat time is %02d:%02d:%02d %d/%d/%d"),
EMS_Thermostat.minute, EMS_Thermostat.hour,
EMS_Thermostat.second, EMS_Thermostat.minute,
EMS_Thermostat.day, EMS_Thermostat.second,
EMS_Thermostat.month, EMS_Thermostat.day,
EMS_Thermostat.year + 2000); EMS_Thermostat.month,
EMS_Thermostat.year + 2000);
if (EMS_Thermostat.mode == 0) { // Render Termostat Mode
myDebug_P(PSTR(" Mode is set to low")); if (EMS_Thermostat.mode == 0) {
} else if (EMS_Thermostat.mode == 1) { myDebug_P(PSTR(" Mode is set to low"));
myDebug_P(PSTR(" Mode is set to manual")); } else if (EMS_Thermostat.mode == 1) {
} else if (EMS_Thermostat.mode == 2) { myDebug_P(PSTR(" Mode is set to manual"));
myDebug_P(PSTR(" Mode is set to auto")); } else if (EMS_Thermostat.mode == 2) {
} else { myDebug_P(PSTR(" Mode is set to auto"));
myDebug_P(PSTR(" Mode is set to ?")); } else {
} myDebug_P(PSTR(" Mode is set to ?"));
} }
} }
@@ -679,13 +698,7 @@ void publishValues(bool force) {
rootThermostat[THERMOSTAT_HC] = _int_to_char(s, EMSESP_Status.heating_circuit); rootThermostat[THERMOSTAT_HC] = _int_to_char(s, EMSESP_Status.heating_circuit);
// different logic depending on thermostat types // different logic depending on thermostat types
if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) { if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100)) {
if (abs(EMS_Thermostat.setpoint_roomTemp) < EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_SELTEMP] = (double)EMS_Thermostat.setpoint_roomTemp / 10;
if (abs(EMS_Thermostat.curr_roomTemp) < EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_CURRTEMP] = (double)EMS_Thermostat.curr_roomTemp / 10;
} else if (ems_getThermostatModel() == EMS_MODEL_FR10) {
if (abs(EMS_Thermostat.setpoint_roomTemp) < EMS_VALUE_SHORT_NOTSET) if (abs(EMS_Thermostat.setpoint_roomTemp) < EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_SELTEMP] = (double)EMS_Thermostat.setpoint_roomTemp / 10; rootThermostat[THERMOSTAT_SELTEMP] = (double)EMS_Thermostat.setpoint_roomTemp / 10;
if (abs(EMS_Thermostat.curr_roomTemp) < EMS_VALUE_SHORT_NOTSET) if (abs(EMS_Thermostat.curr_roomTemp) < EMS_VALUE_SHORT_NOTSET)
@@ -749,6 +762,7 @@ void publishValues(bool force) {
} }
// handle the other values separately // handle the other values separately
// For SM10 and SM100 Solar Modules // For SM10 and SM100 Solar Modules
if (EMS_Other.SM) { if (EMS_Other.SM) {
// build new json object // build new json object
@@ -794,6 +808,27 @@ void publishValues(bool force) {
myESP.mqttPublish(TOPIC_SM_DATA, data); myESP.mqttPublish(TOPIC_SM_DATA, data);
} }
} }
// handle HeatPump
if (EMS_Other.HP) {
// build new json object
doc.clear();
JsonObject rootSM = doc.to<JsonObject>();
if (EMS_Other.HPModulation != EMS_VALUE_INT_NOTSET)
rootSM[HP_PUMPMODULATION] = EMS_Other.HPModulation;
if (EMS_Other.HPSpeed != EMS_VALUE_INT_NOTSET)
rootSM[HP_PUMPSPEED] = EMS_Other.HPSpeed;
data[0] = '\0'; // reset data for next package
serializeJson(doc, data, sizeof(data));
myDebugLog("Publishing HeatPump data via MQTT");
// send values via MQTT
myESP.mqttPublish(TOPIC_HP_DATA, data);
}
} }
// sets the shower timer on/off // sets the shower timer on/off
@@ -931,6 +966,7 @@ void startDeviceScan() {
regularUpdatesTimer.detach(); regularUpdatesTimer.detach();
publishSensorValuesTimer.detach(); publishSensorValuesTimer.detach();
scanDevices_count = 1; // starts at 1 scanDevices_count = 1; // starts at 1
ems_clearDeviceList(); // empty the current list
ems_setLogging(EMS_SYS_LOGGING_NONE); ems_setLogging(EMS_SYS_LOGGING_NONE);
myDebug_P(PSTR("Starting a deep EMS device scan. This can take up to 2 minutes. Please wait...")); myDebug_P(PSTR("Starting a deep EMS device scan. This can take up to 2 minutes. Please wait..."));
scanThermostat.attach_ms(SCANDEVICES_TIME, do_scanDevices); scanThermostat.attach_ms(SCANDEVICES_TIME, do_scanDevices);
@@ -975,7 +1011,7 @@ void runUnitTest(uint8_t test_num) {
publishValuesTimer.detach(); publishValuesTimer.detach();
systemCheckTimer.detach(); systemCheckTimer.detach();
regularUpdatesTimer.detach(); regularUpdatesTimer.detach();
EMSESP_Status.listen_mode = true; // temporary go into listen mode to disable Tx // EMSESP_Status.listen_mode = true; // temporary go into listen mode to disable Tx
ems_testTelegram(test_num); ems_testTelegram(test_num);
} }
@@ -1020,9 +1056,6 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
// shower_alert // shower_alert
EMSESP_Status.shower_alert = json["shower_alert"]; EMSESP_Status.shower_alert = json["shower_alert"];
// tx delay
ems_setTxDelay(json["tx_delay"]);
// publish_wait // publish_wait
if (!(EMSESP_Status.publish_wait = json["publish_wait"])) { if (!(EMSESP_Status.publish_wait = json["publish_wait"])) {
EMSESP_Status.publish_wait = DEFAULT_PUBLISHWAIT; // default value EMSESP_Status.publish_wait = DEFAULT_PUBLISHWAIT; // default value
@@ -1049,7 +1082,6 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
json["shower_alert"] = EMSESP_Status.shower_alert; json["shower_alert"] = EMSESP_Status.shower_alert;
json["publish_wait"] = EMSESP_Status.publish_wait; json["publish_wait"] = EMSESP_Status.publish_wait;
json["heating_circuit"] = EMSESP_Status.heating_circuit; json["heating_circuit"] = EMSESP_Status.heating_circuit;
json["tx_delay"] = ems_getTxDelay();
return true; return true;
} }
@@ -1090,7 +1122,7 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
EMSESP_Status.listen_mode = false; EMSESP_Status.listen_mode = false;
ok = true; ok = true;
ems_setTxDisabled(false); ems_setTxDisabled(false);
myDebug_P(PSTR("* out of listen mode. Tx is enabled.")); myDebug_P(PSTR("* out of listen mode. Tx is now enabled."));
} else { } else {
myDebug_P(PSTR("Error. Usage: set listen_mode <on | off>")); myDebug_P(PSTR("Error. Usage: set listen_mode <on | off>"));
} }
@@ -1180,18 +1212,6 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
} }
} }
// tx delay
if ((strcmp(setting, "tx_delay") == 0) && (wc == 2)) {
if (strcmp(value, "on") == 0) {
ems_setTxDelay(true);
ok = true;
} else if (strcmp(value, "off") == 0) {
ems_setTxDelay(false);
ok = true;
} else {
myDebug_P(PSTR("Error. Usage: set tx_delay <on | off>"));
}
}
} }
if (action == MYESP_FSACTION_LIST) { if (action == MYESP_FSACTION_LIST) {
@@ -1218,7 +1238,6 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off"); myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off");
myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off"); myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off");
myDebug_P(PSTR(" publish_wait=%d"), EMSESP_Status.publish_wait); myDebug_P(PSTR(" publish_wait=%d"), EMSESP_Status.publish_wait);
myDebug_P(PSTR(" tx_delay=%s"), ems_getTxDelay() ? "on" : "off");
} }
return ok; return ok;
@@ -1456,7 +1475,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
if ((hc >= 1) && (hc <= 2)) { if ((hc >= 1) && (hc <= 2)) {
EMSESP_Status.heating_circuit = hc; EMSESP_Status.heating_circuit = hc;
ems_setThermostatHC(hc); ems_setThermostatHC(hc);
// TODO: save setting to SPIFFS??
} }
} }
@@ -1546,7 +1564,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_P(PSTR("Warning! EMS bus disabled when in Serial mode. Use 'set serial off' to start EMS.")); myDebug_P(PSTR("Warning! EMS bus communication disabled when Serial mode enabled. Use 'set serial off' to start communication."));
} else { } else {
emsuart_init(); emsuart_init();
myDebug_P(PSTR("[UART] Opened Rx/Tx connection")); myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
@@ -1659,11 +1677,7 @@ void setup() {
// set up myESP for Wifi, MQTT, MDNS and Telnet // set up myESP for Wifi, MQTT, MDNS and Telnet
myESP.setTelnet(project_cmds, ArraySize(project_cmds), TelnetCommandCallback, TelnetCallback); // set up Telnet commands myESP.setTelnet(project_cmds, ArraySize(project_cmds), TelnetCommandCallback, TelnetCallback); // set up Telnet commands
#ifdef WIFI_SSID myESP.setWIFI(NULL, NULL, WIFICallback);
myESP.setWIFI(WIFI_SSID, WIFI_PASSWORD, WIFICallback);
#else
myESP.setWIFI(NULL, NULL, WIFICallback); // pull the wifi settings from the SPIFFS stored settings
#endif
// MQTT host, username and password taken from the SPIFFS settings // MQTT host, username and password taken from the SPIFFS settings
myESP.setMQTT( myESP.setMQTT(

View File

@@ -50,7 +50,6 @@ void _process_UBAMonitorWWMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_UBAParameterWW(_EMS_RxTelegram * EMS_RxTelegram); void _process_UBAParameterWW(_EMS_RxTelegram * EMS_RxTelegram);
void _process_UBATotalUptimeMessage(_EMS_RxTelegram * EMS_RxTelegram); void _process_UBATotalUptimeMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_UBAParametersMessage(_EMS_RxTelegram * EMS_RxTelegram); void _process_UBAParametersMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_SetPoints(_EMS_RxTelegram * EMS_RxTelegram); void _process_SetPoints(_EMS_RxTelegram * EMS_RxTelegram);
// SM10 // SM10
@@ -62,6 +61,13 @@ void _process_SM100Status(_EMS_RxTelegram * EMS_RxTelegram);
void _process_SM100Status2(_EMS_RxTelegram * EMS_RxTelegram); void _process_SM100Status2(_EMS_RxTelegram * EMS_RxTelegram);
void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram); void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram);
// ISM1
void _process_ISM1StatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
// HeatPump HP
void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram);
void _process_HPMonitor2(_EMS_RxTelegram * EMS_RxTelegram);
// Common for most thermostats // Common for most thermostats
void _process_RCTime(_EMS_RxTelegram * EMS_RxTelegram); void _process_RCTime(_EMS_RxTelegram * EMS_RxTelegram);
void _process_RCOutdoorTempMessage(_EMS_RxTelegram * EMS_RxTelegram); void _process_RCOutdoorTempMessage(_EMS_RxTelegram * EMS_RxTelegram);
@@ -92,8 +98,8 @@ void _process_RCPLUSStatusHeating(_EMS_RxTelegram * EMS_RxTelegram);
void _process_RCPLUSStatusHeating(_EMS_RxTelegram * EMS_RxTelegram); void _process_RCPLUSStatusHeating(_EMS_RxTelegram * EMS_RxTelegram);
void _process_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram); void _process_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram);
// Junkers FR10 // Junkers FR10 & FW100
void _process_FR10StatusMessage(_EMS_RxTelegram * EMS_RxTelegram); void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
/** /**
* Recognized EMS types and the functions they call to process the telegrams * Recognized EMS types and the functions they call to process the telegrams
@@ -120,6 +126,9 @@ const _EMS_Type EMS_Types[] = {
{EMS_MODEL_OTHER, EMS_TYPE_SM100Status, "SM100Status", _process_SM100Status}, {EMS_MODEL_OTHER, EMS_TYPE_SM100Status, "SM100Status", _process_SM100Status},
{EMS_MODEL_OTHER, EMS_TYPE_SM100Status2, "SM100Status2", _process_SM100Status2}, {EMS_MODEL_OTHER, EMS_TYPE_SM100Status2, "SM100Status2", _process_SM100Status2},
{EMS_MODEL_OTHER, EMS_TYPE_SM100Energy, "SM100Energy", _process_SM100Energy}, {EMS_MODEL_OTHER, EMS_TYPE_SM100Energy, "SM100Energy", _process_SM100Energy},
{EMS_MODEL_OTHER, EMS_TYPE_HPMonitor1, "HeatPumpMonitor1", _process_HPMonitor1},
{EMS_MODEL_OTHER, EMS_TYPE_HPMonitor2, "HeatPumpMonitor2", _process_HPMonitor2},
{EMS_MODEL_OTHER, EMS_TYPE_ISM1StatusMessage, "ISM1StatusMessage", _process_ISM1StatusMessage},
// RC10 // RC10
{EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime}, {EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
@@ -159,7 +168,6 @@ const _EMS_Type EMS_Types[] = {
// Easy // Easy
{EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage}, {EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
{EMS_MODEL_BOSCHEASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
// Nefit 1010, RC300, RC310 (EMS Plus) // Nefit 1010, RC300, RC310 (EMS Plus)
{EMS_MODEL_ALL, EMS_TYPE_RCPLUSStatusMessage, "RCPLUSStatusMessage", _process_RCPLUSStatusMessage}, {EMS_MODEL_ALL, EMS_TYPE_RCPLUSStatusMessage, "RCPLUSStatusMessage", _process_RCPLUSStatusMessage},
@@ -168,7 +176,7 @@ const _EMS_Type EMS_Types[] = {
{EMS_MODEL_ALL, EMS_TYPE_RCPLUSStatusMode, "RCPLUSStatusMode", _process_RCPLUSStatusMode}, {EMS_MODEL_ALL, EMS_TYPE_RCPLUSStatusMode, "RCPLUSStatusMode", _process_RCPLUSStatusMode},
// Junkers FR10 // Junkers FR10
{EMS_MODEL_ALL, EMS_TYPE_FR10StatusMessage, "FR10StatusMessage", _process_FR10StatusMessage} {EMS_MODEL_ALL, EMS_TYPE_JunkersStatusMessage, "JunkersStatusMessage", _process_JunkersStatusMessage}
}; };
@@ -199,9 +207,9 @@ const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7, 0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7, 0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7, 0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7,
0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7}; 0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
const uint8_t TX_WRITE_TIMEOUT_COUNT = 2; // 3 retries before timeout const uint8_t TX_WRITE_TIMEOUT_COUNT = 2; // 3 retries before timeout
const uint32_t EMS_BUS_TIMEOUT = 15000; // timeout in ms before recognizing the ems bus is offline (15 seconds) const uint32_t EMS_BUS_TIMEOUT = 15000; // timeout in ms before recognizing the ems bus is offline (15 seconds)
const uint32_t EMS_POLL_TIMEOUT = 5000; // timeout in ms before recognizing the ems bus is offline (5 seconds) const uint32_t EMS_POLL_TIMEOUT = 5000000; // timeout in microseconds before recognizing the ems bus is offline (5 seconds)
// init stats and counters and buffers // init stats and counters and buffers
// uses -255 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET) // uses -255 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET)
@@ -220,7 +228,7 @@ void ems_init() {
EMS_Sys_Status.emsTxDisabled = false; EMS_Sys_Status.emsTxDisabled = false;
EMS_Sys_Status.emsPollFrequency = 0; EMS_Sys_Status.emsPollFrequency = 0;
EMS_Sys_Status.txRetryCount = 0; EMS_Sys_Status.txRetryCount = 0;
EMS_Sys_Status.emsTxDelay = false; EMS_Sys_Status.emsReverse = false;
// thermostat // thermostat
EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET; EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET;
@@ -298,6 +306,8 @@ void ems_init() {
EMS_Other.SMEnergyLastHour = EMS_VALUE_SHORT_NOTSET; EMS_Other.SMEnergyLastHour = EMS_VALUE_SHORT_NOTSET;
EMS_Other.SMEnergyToday = EMS_VALUE_SHORT_NOTSET; EMS_Other.SMEnergyToday = EMS_VALUE_SHORT_NOTSET;
EMS_Other.SMEnergyTotal = EMS_VALUE_SHORT_NOTSET; EMS_Other.SMEnergyTotal = EMS_VALUE_SHORT_NOTSET;
EMS_Other.HPModulation = EMS_VALUE_INT_NOTSET;
EMS_Other.HPSpeed = EMS_VALUE_INT_NOTSET;
// calculated values // calculated values
EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off
@@ -314,6 +324,7 @@ void ems_init() {
// set other types // set other types
EMS_Other.SM = false; EMS_Other.SM = false;
EMS_Other.HP = false;
// default logging is none // default logging is none
ems_setLogging(EMS_SYS_LOGGING_DEFAULT); ems_setLogging(EMS_SYS_LOGGING_DEFAULT);
@@ -329,15 +340,6 @@ bool ems_getPoll() {
return EMS_Sys_Status.emsPollEnabled; return EMS_Sys_Status.emsPollEnabled;
} }
void ems_setTxDelay(bool b) {
EMS_Sys_Status.emsTxDelay = b;
myDebug_P(PSTR("EMS Tx delay is %s"), EMS_Sys_Status.emsTxDelay ? "enabled" : "disabled");
}
bool ems_getTxDelay() {
return EMS_Sys_Status.emsTxDelay;
}
bool ems_getEmsRefreshed() { bool ems_getEmsRefreshed() {
return EMS_Sys_Status.emsRefreshed; return EMS_Sys_Status.emsRefreshed;
} }
@@ -533,6 +535,15 @@ void _ems_sendTelegram() {
return; return;
} }
// if we're preventing all outbound traffic, quit
if (EMS_Sys_Status.emsTxDisabled) {
EMS_TxQueue.shift(); // remove from queue
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("in Listen Mode. All Tx is disabled."));
}
return;
}
// get the first in the queue, which is at the head // get the first in the queue, which is at the head
// we don't remove from the queue yet // we don't remove from the queue yet
_EMS_TxTelegram EMS_TxTelegram = EMS_TxQueue.first(); _EMS_TxTelegram EMS_TxTelegram = EMS_TxQueue.first();
@@ -544,22 +555,24 @@ void _ems_sendTelegram() {
} }
// if we're in raw mode just fire and forget // if we're in raw mode just fire and forget
// e.g. send 0B 88 02 00 20
if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_RAW) { if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_RAW) {
_EMS_RxTelegram EMS_RxTelegram; // create new Rx object if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) {
_EMS_RxTelegram EMS_RxTelegram; // create new Rx object
EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram
EMS_RxTelegram.telegram = EMS_TxTelegram.data;
EMS_RxTelegram.timestamp = millis(); // now
_debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true);
}
EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC
EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); // send the telegram to the UART Tx
EMS_RxTelegram.telegram = EMS_TxTelegram.data; EMS_TxQueue.shift(); // and remove from queue
EMS_RxTelegram.timestamp = millis(); // now
_debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true); // always show
emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); // send the telegram to the UART Tx
EMS_TxQueue.shift(); // remove from queue
return; return;
} }
// create header // create the header
EMS_TxTelegram.data[0] = EMS_ID_ME; // src EMS_TxTelegram.data[0] = (EMS_Sys_Status.emsReverse) ? EMS_ID_ME | 0x80 : EMS_ID_ME; // src
// dest // dest
if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_WRITE) { if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_WRITE) {
EMS_TxTelegram.data[1] = EMS_TxTelegram.dest; EMS_TxTelegram.data[1] = EMS_TxTelegram.dest;
@@ -651,31 +664,18 @@ void _createValidate() {
/** /**
* Entry point triggered by an interrupt in emsuart.cpp * Entry point triggered by an interrupt in emsuart.cpp
* length is size of all the telegram bytes including the CRC, excluding the BRK at the end * length is the number of all the telegram bytes up to and including the CRC at the end
* Read commands are asynchronous as they're handled by the interrupt * Read commands are asynchronous as they're handled by the interrupt
* When a telegram is processed we forcefully erase it from the stack to prevent overflow * When a telegram is processed we forcefully erase it from the stack to prevent overflow
*/ */
void ems_parseTelegram(uint8_t * telegram, uint8_t length) { void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
if ((length != 0) && (telegram[0] != 0x00)) {
_ems_readTelegram(telegram, length);
}
// clear the Rx buffer just be safe and prevent duplicates
memset(telegram, 0, EMS_MAXBUFFERSIZE);
}
/**
* the main logic that parses the telegram message
* When we receive a Poll Request we need to send any Tx packages quickly within a 200ms window
* length is total number of bytes of the telegram including the CRC byte at the end (if it exists)
*/
void _ems_readTelegram(uint8_t * telegram, uint8_t length) {
// create the Rx package // create the Rx package
static _EMS_RxTelegram EMS_RxTelegram; static _EMS_RxTelegram EMS_RxTelegram;
static uint32_t _last_emsPollFrequency = 0; static uint32_t _last_emsPollFrequency = 0;
EMS_RxTelegram.telegram = telegram;
EMS_RxTelegram.timestamp = millis(); EMS_RxTelegram.telegram = telegram;
EMS_RxTelegram.length = length; EMS_RxTelegram.timestamp = millis();
EMS_RxTelegram.length = length;
// check if we just received a single byte // check if we just received a single byte
// it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80) // it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80)
@@ -683,12 +683,13 @@ void _ems_readTelegram(uint8_t * telegram, uint8_t length) {
if (length == 1) { if (length == 1) {
uint8_t value = telegram[0]; // 1st byte of data package uint8_t value = telegram[0]; // 1st byte of data package
EMS_Sys_Status.emsPollFrequency = (EMS_RxTelegram.timestamp - _last_emsPollFrequency);
_last_emsPollFrequency = EMS_RxTelegram.timestamp;
// check first for a Poll for us // check first for a Poll for us
if (value == (EMS_ID_ME | 0x80)) { // the poll has the MSB set - seems to work on both EMS and Junkers
EMS_Sys_Status.emsTxCapable = true; if ((value & 0x7F) == EMS_ID_ME) {
EMS_Sys_Status.emsTxCapable = true;
uint32_t timenow_microsecs = micros();
EMS_Sys_Status.emsPollFrequency = (timenow_microsecs - _last_emsPollFrequency);
_last_emsPollFrequency = timenow_microsecs;
// do we have something to send thats waiting in the Tx queue? // do we have something to send thats waiting in the Tx queue?
// if so send it if the Queue is not in a wait state // if so send it if the Queue is not in a wait state
@@ -697,7 +698,7 @@ void _ems_readTelegram(uint8_t * telegram, uint8_t length) {
} else { } else {
// nothing to send so just send a poll acknowledgement back // nothing to send so just send a poll acknowledgement back
if (EMS_Sys_Status.emsPollEnabled) { if (EMS_Sys_Status.emsPollEnabled) {
emsaurt_tx_poll(); emsuart_tx_poll();
} }
} }
} else if (EMS_Sys_Status.emsTxStatus == EMS_TX_STATUS_WAIT) { } else if (EMS_Sys_Status.emsTxStatus == EMS_TX_STATUS_WAIT) {
@@ -705,14 +706,14 @@ void _ems_readTelegram(uint8_t * telegram, uint8_t length) {
if (value == EMS_TX_SUCCESS) { if (value == EMS_TX_SUCCESS) {
EMS_Sys_Status.emsTxPkgs++; EMS_Sys_Status.emsTxPkgs++;
// got a success 01. Send a validate to check the value of the last write // got a success 01. Send a validate to check the value of the last write
emsaurt_tx_poll(); // send a poll to free the EMS bus emsuart_tx_poll(); // send a poll to free the EMS bus
_createValidate(); // create a validate Tx request (if needed) _createValidate(); // create a validate Tx request (if needed)
} else if (value == EMS_TX_ERROR) { } else if (value == EMS_TX_ERROR) {
// last write failed (04), delete it from queue and dont bother to retry // last write failed (04), delete it from queue and dont bother to retry
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
myDebug_P(PSTR("** Write command failed from host")); myDebug_P(PSTR("** Write command failed from host"));
} }
emsaurt_tx_poll(); // send a poll to free the EMS bus emsuart_tx_poll(); // send a poll to free the EMS bus
_removeTxQueue(); // remove from queue _removeTxQueue(); // remove from queue
} }
} }
@@ -810,6 +811,8 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
strlcpy(output_str, "Thermostat", sizeof(output_str)); strlcpy(output_str, "Thermostat", sizeof(output_str));
} else if (src == EMS_ID_SM) { } else if (src == EMS_ID_SM) {
strlcpy(output_str, "SM", sizeof(output_str)); strlcpy(output_str, "SM", sizeof(output_str));
} else if (src == EMS_ID_HP) {
strlcpy(output_str, "HP", sizeof(output_str));
} else if (src == EMS_ID_GATEWAY) { } else if (src == EMS_ID_GATEWAY) {
strlcpy(output_str, "Gateway", sizeof(output_str)); strlcpy(output_str, "Gateway", sizeof(output_str));
} else { } else {
@@ -832,6 +835,9 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
} else if (dest == EMS_ID_SM) { } else if (dest == EMS_ID_SM) {
strlcat(output_str, "SM", sizeof(output_str)); strlcat(output_str, "SM", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s)); strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
} else if (dest == EMS_ID_HP) {
strlcat(output_str, "HP", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
} else if (dest == EMS_ID_GATEWAY) { } else if (dest == EMS_ID_GATEWAY) {
strlcat(output_str, "Gateway", sizeof(output_str)); strlcat(output_str, "Gateway", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s)); strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
@@ -896,13 +902,6 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
typeFound = true; typeFound = true;
break; break;
} }
/*
if ((EMS_Types[i].model_id == EMS_MODEL_ALL) || ((src == EMS_Boiler.device_id) || (src == EMS_Thermostat.device_id) || (src == EMS_ID_SM))) {
typeFound = true;
break;
}
*/
} }
i++; i++;
} }
@@ -949,7 +948,7 @@ void _removeTxQueue() {
void _processType(_EMS_RxTelegram * EMS_RxTelegram) { void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
uint8_t * telegram = EMS_RxTelegram->telegram; uint8_t * telegram = EMS_RxTelegram->telegram;
// if its an echo of ourselves from the master UBA, ignore // if its an echo of ourselves from the master UBA, ignore. This should never happen mind you
if (EMS_RxTelegram->src == EMS_ID_ME) { if (EMS_RxTelegram->src == EMS_ID_ME) {
// _debugPrintTelegram("echo:", EMS_RxTelegram, COLOR_WHITE); // _debugPrintTelegram("echo:", EMS_RxTelegram, COLOR_WHITE);
return; return;
@@ -987,7 +986,8 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
// if WRITE, should not happen // if WRITE, should not happen
// if VALIDATE, check the contents // if VALIDATE, check the contents
if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_READ) { if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_READ) {
if ((EMS_RxTelegram->src == EMS_TxTelegram.dest) && (EMS_RxTelegram->type == EMS_TxTelegram.type)) { // remove MSB from src/dest
if (((EMS_RxTelegram->src & 0x7F) == (EMS_TxTelegram.dest & 0x7F)) && (EMS_RxTelegram->type == EMS_TxTelegram.type)) {
// all checks out, read was successful, remove tx from queue and continue to process telegram // all checks out, read was successful, remove tx from queue and continue to process telegram
_removeTxQueue(); _removeTxQueue();
EMS_Sys_Status.emsRxPgks++; // increment counter EMS_Sys_Status.emsRxPgks++; // increment counter
@@ -1004,7 +1004,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
_removeTxQueue(); _removeTxQueue();
} else { } else {
if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) {
myDebug_P(PSTR("...Retrying read. Attempt %d/%d..."), EMS_Sys_Status.txRetryCount, TX_WRITE_TIMEOUT_COUNT); myDebug_P(PSTR("Read failed. Retrying attempt %d/%d..."), EMS_Sys_Status.txRetryCount, TX_WRITE_TIMEOUT_COUNT);
} }
} }
} }
@@ -1052,7 +1052,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
} }
} }
emsaurt_tx_poll(); // send Acknowledgement back to free the EMS bus since we have the telegram emsuart_tx_poll(); // send Acknowledgement back to free the EMS bus since we have the telegram
} }
@@ -1283,12 +1283,11 @@ void _process_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram) {
/** /**
* FR10 Junkers - type x6F01 * FR10 Junkers - type x6F01
*/ */
void _process_FR10StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->data_length == 6) { // e.g. for FR10: 90 00 FF 00 00 6F 03 01 00 BE 00 BF
// e.g. 90 00 FF 00 00 6F 03 01 00 BE 00 BF // e.g. for FW100: 90 00 FF 00 00 6F 03 02 00 D7 00 DA F3 34 00 C4
EMS_Thermostat.curr_roomTemp = _toByte(EMS_OFFSET_FR10StatusMessage_curr); // value is * 10 EMS_Thermostat.curr_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_curr); // value is * 10
EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_OFFSET_FR10StatusMessage_setpoint); // value is * 10, which is different from other EMS+ devices EMS_Thermostat.setpoint_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_setpoint); // value is * 10
}
} }
/** /**
@@ -1416,10 +1415,9 @@ void _process_SM100Status2(_EMS_RxTelegram * EMS_RxTelegram) {
/* /*
* SM100Energy - type 0x028E EMS+ for energy readings * SM100Energy - type 0x028E EMS+ for energy readings
* e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
*/ */
void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram) { void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram) {
// e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
EMS_Other.SMEnergyLastHour = _toShort(2); // last hour / 10 in Wh EMS_Other.SMEnergyLastHour = _toShort(2); // last hour / 10 in Wh
EMS_Other.SMEnergyToday = _toShort(6); // todays in Wh EMS_Other.SMEnergyToday = _toShort(6); // todays in Wh
EMS_Other.SMEnergyTotal = _toShort(10); // total / 10 in kWh EMS_Other.SMEnergyTotal = _toShort(10); // total / 10 in kWh
@@ -1428,6 +1426,36 @@ void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
} }
/*
* Type 0xE3 - HeatPump Monitor 1
*/
void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Other.HPModulation = _toByte(14); // modulation %
EMS_Other.HP = true;
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
/*
* Type 0xE5 - HeatPump Monitor 2
*/
void _process_HPMonitor2(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Other.HPSpeed = _toByte(25); // speed %
EMS_Other.HP = true;
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
/*
* Junkers ISM1 Solar Module - type 0x0003 EMS+ for energy readings
*/
void _process_ISM1StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
// e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
EMS_Other.SMcollectorTemp = _toShort(4); // Collector Temperature
EMS_Other.SMbottomTemp = _toShort(6); // Temperature Bottom of Solar Boiler
EMS_Other.SM = true;
}
/** /**
* UBASetPoint 0x1A * UBASetPoint 0x1A
*/ */
@@ -1456,7 +1484,7 @@ void _process_SetPoints(_EMS_RxTelegram * EMS_RxTelegram) {
* common for all thermostats * common for all thermostats
*/ */
void _process_RCTime(_EMS_RxTelegram * EMS_RxTelegram) { void _process_RCTime(_EMS_RxTelegram * EMS_RxTelegram) {
if ((EMS_Thermostat.model_id == EMS_MODEL_EASY) || (EMS_Thermostat.model_id == EMS_MODEL_BOSCHEASY)) { if ((EMS_Thermostat.model_id == EMS_MODEL_EASY)) {
return; // not supported return; // not supported
} }
@@ -1468,6 +1496,13 @@ void _process_RCTime(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Thermostat.year = _toByte(0); EMS_Thermostat.year = _toByte(0);
} }
/*
* Clear devices list
*/
void ems_clearDeviceList() {
Devices.clear();
}
/* /*
* add an EMS device to our list of detected devices * add an EMS device to our list of detected devices
*/ */
@@ -1501,15 +1536,16 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
return; return;
} }
uint8_t product_id = _toByte(0); uint8_t product_id = _toByte(0);
char version[10] = {0};
char version[10] = {0};
snprintf(version, sizeof(version), "%02d.%02d", _toByte(1), _toByte(2)); snprintf(version, sizeof(version), "%02d.%02d", _toByte(1), _toByte(2));
// see if its a known boiler // see if its a known boiler
int i = 0; int i = 0;
bool typeFound = false; bool typeFound = false;
while (i < _Boiler_Types_max) { while (i < _Boiler_Types_max) {
if (Boiler_Types[i].product_id == product_id) { if ((Boiler_Types[i].product_id == product_id) && ((EMS_RxTelegram->src & 0x7F) == EMS_ID_BOILER)) {
typeFound = true; // we have a matching product id. i is the index. typeFound = true; // we have a matching product id. i is the index.
break; break;
} }
@@ -1518,28 +1554,29 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
if (typeFound) { if (typeFound) {
// its a boiler // its a boiler
myDebug_P(PSTR("Boiler found. Model %s (DeviceID:0x%02X ProductID:%d Version:%s)"), myDebug_P(PSTR("Boiler found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Boiler_Types[i].model_string, EMS_ID_BOILER, product_id, version);
Boiler_Types[i].model_string,
Boiler_Types[i].device_id,
product_id,
version);
// add to list // add to list
_addDevice(product_id, Boiler_Types[i].device_id, version, Boiler_Types[i].model_string); _addDevice(product_id, EMS_ID_BOILER, version, Boiler_Types[i].model_string);
// if its a boiler set it, unless it already has been set by checking for a productID // if its a boiler set it, unless it already has been set by checking for a productID
// it will take the first one found in the list // it will take the first one found in the list
if (((EMS_Boiler.device_id == EMS_ID_NONE) || (EMS_Boiler.device_id == Boiler_Types[i].device_id)) && EMS_Boiler.product_id == EMS_ID_NONE) { if ((EMS_Boiler.device_id == EMS_ID_NONE) || ((EMS_Boiler.device_id == EMS_ID_BOILER) && EMS_Boiler.product_id == EMS_ID_NONE)) {
myDebug_P(PSTR("* Setting Boiler to model %s (DeviceID:0x%02X ProductID:%d Version:%s)"), myDebug_P(PSTR("* Setting Boiler to model %s (DeviceID:0x%02X ProductID:%d Version:%s)"),
Boiler_Types[i].model_string, Boiler_Types[i].model_string,
Boiler_Types[i].device_id, EMS_ID_BOILER,
product_id, product_id,
version); version);
EMS_Boiler.device_id = Boiler_Types[i].device_id; EMS_Boiler.device_id = EMS_ID_BOILER;
EMS_Boiler.product_id = Boiler_Types[i].product_id; EMS_Boiler.product_id = Boiler_Types[i].product_id;
strlcpy(EMS_Boiler.version, version, sizeof(EMS_Boiler.version)); strlcpy(EMS_Boiler.version, version, sizeof(EMS_Boiler.version));
// check to see if its a Junkers Heatronic3, which has a different poll'ing logic
if (EMS_Boiler.product_id == EMS_PRODUCTID_HEATRONICS) {
EMS_Sys_Status.emsReverse = true;
}
myESP.fs_saveConfig(); // save config to SPIFFS myESP.fs_saveConfig(); // save config to SPIFFS
ems_getBoilerValues(); // get Boiler values that we would usually have to wait for ems_getBoilerValues(); // get Boiler values that we would usually have to wait for
@@ -1560,7 +1597,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
if (typeFound) { if (typeFound) {
// its a known thermostat // its a known thermostat
if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) {
myDebug_P(PSTR("Thermostat found. Model %s (DeviceID:0x%02X ProductID:%d Version:%s)"), myDebug_P(PSTR("Thermostat found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"),
Thermostat_Types[i].model_string, Thermostat_Types[i].model_string,
Thermostat_Types[i].device_id, Thermostat_Types[i].device_id,
product_id, product_id,
@@ -1568,13 +1605,13 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
} }
// add to list // add to list
_addDevice(product_id, Boiler_Types[i].device_id, version, Thermostat_Types[i].model_string); _addDevice(product_id, Thermostat_Types[i].device_id, version, Thermostat_Types[i].model_string);
// if we don't have a thermostat set, use this one // if we don't have a thermostat set, use this one
if (((EMS_Thermostat.device_id == EMS_ID_NONE) || (EMS_Thermostat.model_id == EMS_MODEL_NONE) if (((EMS_Thermostat.device_id == EMS_ID_NONE) || (EMS_Thermostat.model_id == EMS_MODEL_NONE)
|| (EMS_Thermostat.device_id == Thermostat_Types[i].device_id)) || (EMS_Thermostat.device_id == Thermostat_Types[i].device_id))
&& EMS_Thermostat.product_id == EMS_ID_NONE) { && EMS_Thermostat.product_id == EMS_ID_NONE) {
myDebug_P(PSTR("* Setting Thermostat model to %s (DeviceID:0x%02X ProductID:%d Version:%s)"), myDebug_P(PSTR("* Setting Thermostat to %s (DeviceID:0x%02X ProductID:%d Version:%s)"),
Thermostat_Types[i].model_string, Thermostat_Types[i].model_string,
Thermostat_Types[i].device_id, Thermostat_Types[i].device_id,
product_id, product_id,
@@ -1605,11 +1642,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
} }
if (typeFound) { if (typeFound) {
myDebug_P(PSTR("Device found. Model %s with DeviceID 0x%02X, ProductID %d, Version %s"), myDebug_P(PSTR("Device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Other_Types[i].model_string, Other_Types[i].device_id, product_id, version);
Other_Types[i].model_string,
Other_Types[i].device_id,
product_id,
version);
// add to list // add to list
_addDevice(product_id, Other_Types[i].device_id, version, Other_Types[i].model_string); _addDevice(product_id, Other_Types[i].device_id, version, Other_Types[i].model_string);
@@ -1620,31 +1653,51 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
myDebug_P(PSTR("SM10 Solar Module support enabled.")); myDebug_P(PSTR("SM10 Solar Module support enabled."));
} }
// see if this is a HeatPump
if (Other_Types[i].device_id == EMS_ID_HP) {
EMS_Other.HP = true; // we have detected a HP
myDebug_P(PSTR("HeatPump support enabled."));
}
// fetch other values // fetch other values
ems_getOtherValues(); ems_getOtherValues();
return; return;
} else { } else {
myDebug_P(PSTR("Unrecognized device found. DeviceID 0x%02X, ProductID %d, Version %s"), EMS_RxTelegram->src, product_id, version); myDebug_P(PSTR("Unrecognized device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), EMS_RxTelegram->src, product_id, version);
// add to list // add to list
_addDevice(product_id, EMS_RxTelegram->src, version, "unknown?"); _addDevice(product_id, EMS_RxTelegram->src, version, "unknown?");
} }
} }
/*
* See if we have a Junkers Heatronic3 compatible device
* Do a read command for the version with the src having the MSB set
*/
void _ems_detectJunkers() {
char s[30] = {0};
snprintf(s, sizeof(s), "%02X %02X %02X 00 %02X", (EMS_ID_ME | 0x80), (EMS_ID_BOILER | 0x080), EMS_TYPE_Version, EMS_MAX_TELEGRAM_LENGTH);
ems_sendRawTelegram(s);
}
/* /*
* Figure out the boiler and thermostat types * Figure out the boiler and thermostat types
*/ */
void ems_discoverModels() { void ems_discoverModels() {
myDebug_P(PSTR("Starting auto discover of EMS devices...")); myDebug_P(PSTR("Starting auto discover of EMS devices..."));
// boiler // boiler...
ems_doReadCommand(EMS_TYPE_Version, EMS_Boiler.device_id); // get version details of boiler ems_doReadCommand(EMS_TYPE_Version, EMS_ID_BOILER);
_ems_detectJunkers(); // special hack for Junkers detection
// solar module // solar module...
ems_doReadCommand(EMS_TYPE_Version, EMS_ID_SM); // check if there is Solar Module available ems_doReadCommand(EMS_TYPE_Version, EMS_ID_SM); // check if there is Solar Module available
// thermostat // heatpump module...
ems_doReadCommand(EMS_TYPE_Version, EMS_ID_HP); // check if there is HeatPump Module available
// thermostat...
// if it hasn't been set, auto discover it // if it hasn't been set, auto discover it
if (EMS_Thermostat.device_id == EMS_ID_NONE) { if (EMS_Thermostat.device_id == EMS_ID_NONE) {
ems_scanDevices(); // auto-discover it ems_scanDevices(); // auto-discover it
@@ -1678,6 +1731,8 @@ void ems_printTxQueue() {
strlcpy(sType, "read", sizeof(sType)); strlcpy(sType, "read", sizeof(sType));
} else if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_VALIDATE) { } else if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_VALIDATE) {
strlcpy(sType, "validate", sizeof(sType)); strlcpy(sType, "validate", sizeof(sType));
} else if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_RAW) {
strlcpy(sType, "raw", sizeof(sType));
} else { } else {
strlcpy(sType, "?", sizeof(sType)); strlcpy(sType, "?", sizeof(sType));
} }
@@ -1733,7 +1788,7 @@ void ems_getThermostatValues() {
ems_doReadCommand(EMS_TYPE_RC35StatusMessage_HC2, type); // to get the setpoint temp ems_doReadCommand(EMS_TYPE_RC35StatusMessage_HC2, type); // to get the setpoint temp
ems_doReadCommand(EMS_TYPE_RC35Set_HC2, type); // to get the mode ems_doReadCommand(EMS_TYPE_RC35Set_HC2, type); // to get the mode
} }
} else if ((model_id == EMS_MODEL_EASY) || (model_id == EMS_MODEL_BOSCHEASY)) { } else if ((model_id == EMS_MODEL_EASY)) {
ems_doReadCommand(EMS_TYPE_EasyStatusMessage, type); ems_doReadCommand(EMS_TYPE_EasyStatusMessage, type);
} }
@@ -1756,7 +1811,7 @@ void ems_getBoilerValues() {
*/ */
void ems_getOtherValues() { void ems_getOtherValues() {
if (EMS_Other.SM) { if (EMS_Other.SM) {
ems_doReadCommand(EMS_TYPE_SM10Monitor, EMS_ID_SM); // fetch all from SM10Monitor, e.g. 0B B0 97 00 16 ems_doReadCommand(EMS_TYPE_SM10Monitor, EMS_ID_SM); // fetch all from SM10Monitor
} }
} }
@@ -1851,10 +1906,8 @@ void ems_scanDevices() {
std::list<uint8_t> Device_Ids; // create a new list std::list<uint8_t> Device_Ids; // create a new list
// copy over boilers // add boiler device_id which is always 0x08
for (_Boiler_Type bt : Boiler_Types) { Device_Ids.push_back(EMS_ID_BOILER);
Device_Ids.push_back(bt.device_id);
}
// copy over thermostats // copy over thermostats
for (_Thermostat_Type tt : Thermostat_Types) { for (_Thermostat_Type tt : Thermostat_Types) {
@@ -1875,6 +1928,9 @@ void ems_scanDevices() {
for (uint8_t device_id : Device_Ids) { for (uint8_t device_id : Device_Ids) {
ems_doReadCommand(EMS_TYPE_Version, device_id); ems_doReadCommand(EMS_TYPE_Version, device_id);
} }
// add a check for Junkers onto the queue
_ems_detectJunkers();
} }
/** /**
@@ -1889,7 +1945,7 @@ void ems_printAllDevices() {
COLOR_BOLD_ON, COLOR_BOLD_ON,
Boiler_Types[i].model_string, Boiler_Types[i].model_string,
COLOR_BOLD_OFF, COLOR_BOLD_OFF,
Boiler_Types[i].device_id, EMS_ID_BOILER,
Boiler_Types[i].product_id); Boiler_Types[i].product_id);
} }
@@ -1960,7 +2016,9 @@ void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh) {
// if we're preventing all outbound traffic, quit // if we're preventing all outbound traffic, quit
if (EMS_Sys_Status.emsTxDisabled) { if (EMS_Sys_Status.emsTxDisabled) {
myDebug_P(PSTR("in Listen Mode. All Tx is disabled.")); if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("in Listen Mode. All Tx is disabled."));
}
return; return;
} }
@@ -1979,7 +2037,7 @@ void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh) {
} }
} }
EMS_TxTelegram.action = EMS_TX_TELEGRAM_READ; // read command EMS_TxTelegram.action = EMS_TX_TELEGRAM_READ; // read command
EMS_TxTelegram.dest = dest; // set 8th bit to indicate a read EMS_TxTelegram.dest = dest; // 8th bit will be set to indicate a read
EMS_TxTelegram.offset = 0; // 0 for all data EMS_TxTelegram.offset = 0; // 0 for all data
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; // is always 6 bytes long (including CRC at end) EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; // is always 6 bytes long (including CRC at end)
EMS_TxTelegram.type = type; EMS_TxTelegram.type = type;
@@ -2309,7 +2367,6 @@ void ems_setWarmTapWaterActivated(bool activated) {
EMS_TxTelegram.dest = EMS_Boiler.device_id; EMS_TxTelegram.dest = EMS_Boiler.device_id;
EMS_TxTelegram.type = EMS_TYPE_UBAFunctionTest; EMS_TxTelegram.type = EMS_TYPE_UBAFunctionTest;
EMS_TxTelegram.offset = 0; EMS_TxTelegram.offset = 0;
EMS_TxTelegram.length = 22; // 17 bytes of data including header and CRC
EMS_TxTelegram.type_validate = EMS_TxTelegram.type; EMS_TxTelegram.type_validate = EMS_TxTelegram.type;
EMS_TxTelegram.comparisonOffset = 0; // 1st byte EMS_TxTelegram.comparisonOffset = 0; // 1st byte
@@ -2325,13 +2382,19 @@ void ems_setWarmTapWaterActivated(bool activated) {
// we use the special test mode 0x1D for this. Setting the first data to 5A puts the system into test mode and // we use the special test mode 0x1D for this. Setting the first data to 5A puts the system into test mode and
// a setting of 0x00 puts it back into normal operarting mode // a setting of 0x00 puts it back into normal operarting mode
// when in test mode we're able to mess around with the core 3-way valve settings // when in test mode we're able to mess around with the 3-way valve settings
if (!activated) { if (!activated) {
// on // on
EMS_TxTelegram.data[4] = 0x5A; // test mode on EMS_TxTelegram.data[4] = 0x5A; // test mode on
EMS_TxTelegram.data[5] = 0x00; // burner output 0% EMS_TxTelegram.data[5] = 0x00; // burner output 0%
EMS_TxTelegram.data[7] = 0x64; // boiler pump capacity 100% EMS_TxTelegram.data[7] = 0x64; // boiler pump capacity 100%
EMS_TxTelegram.data[8] = 0xFF; // 3-way valve hot water only EMS_TxTelegram.data[8] = 0xFF; // 3-way valve hot water only
EMS_TxTelegram.length = 22; // 17 bytes of data including header and CRC. We send all zeros just to be sure.
} else {
// get out of test mode
// telegram: 0B 08 1D 00 00
EMS_TxTelegram.data[4] = 0x00; // test mode off
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
} }
EMS_TxQueue.push(EMS_TxTelegram); // add to queue EMS_TxQueue.push(EMS_TxTelegram); // add to queue
@@ -2374,7 +2437,7 @@ void ems_testTelegram(uint8_t test_num) {
EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE; EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE;
} }
static uint8_t * telegram = (uint8_t *)malloc(EMS_MAX_TELEGRAM_LENGTH); // warning, memory is not free'd so use only for debugging static uint8_t * telegram = (uint8_t *)malloc(EMS_MAX_TELEGRAM_LENGTH); // warning, memory is never set free so use only for debugging
char telegram_string[200]; char telegram_string[200];
strlcpy(telegram_string, TEST_DATA[test_num - 1], sizeof(telegram_string)); strlcpy(telegram_string, TEST_DATA[test_num - 1], sizeof(telegram_string));
@@ -2404,7 +2467,7 @@ void ems_testTelegram(uint8_t test_num) {
myDebug_P(PSTR("[TEST %d] Injecting telegram %s"), test_num, TEST_DATA[test_num - 1]); myDebug_P(PSTR("[TEST %d] Injecting telegram %s"), test_num, TEST_DATA[test_num - 1]);
// go an parse it // go an parse it
_ems_readTelegram(telegram, length + 1); // include CRC in length ems_parseTelegram(telegram, length + 1); // include CRC in length
#else #else
myDebug_P(PSTR("Firmware not compiled with test data set")); myDebug_P(PSTR("Firmware not compiled with test data set"));
#endif #endif

View File

@@ -12,14 +12,17 @@
#include <Arduino.h> #include <Arduino.h>
// EMS IDs #define EMS_ID_NONE 0x00 // used as a dest in broadcast messages and empty device IDs
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty device IDs
#define EMS_PLUS_ID_NONE 0x01 // Fixed - used as a dest in broadcast messages and empty device IDs // Fixed EMS IDs
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key" #define EMS_ID_ME 0x0B // our device, hardcoded as the "Service Key"
#define EMS_ID_DEFAULT_BOILER 0x08 #define EMS_ID_BOILER 0x08 // all UBA Boilers have 0x08
#define EMS_ID_SM 0x30 // Solar Module SM10 and SM100 #define EMS_ID_SM 0x30 // Solar Module SM10 and SM100
#define EMS_ID_HP 0x38 // HeatPump
#define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway #define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway
#define EMS_PRODUCTID_HEATRONICS 95 // ProductID for a Junkers Heatronic3 device
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC #define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
// max length of a telegram, including CRC, for Rx and Tx. // max length of a telegram, including CRC, for Rx and Tx.
@@ -44,7 +47,7 @@
#define EMS_TX_TELEGRAM_QUEUE_MAX 100 // max size of Tx FIFO queue #define EMS_TX_TELEGRAM_QUEUE_MAX 100 // max size of Tx FIFO queue
// #define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE //#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE
#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE #define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE
/* EMS UART transfer status */ /* EMS UART transfer status */
@@ -94,7 +97,7 @@ typedef struct {
bool emsTxCapable; // able to send via Tx bool emsTxCapable; // able to send via Tx
bool emsTxDisabled; // true to prevent all Tx bool emsTxDisabled; // true to prevent all Tx
uint8_t txRetryCount; // # times the last Tx was re-sent uint8_t txRetryCount; // # times the last Tx was re-sent
bool emsTxDelay; // if true, slows down the Tx transmit bool emsReverse; // if true, poll logic is reversed
} _EMS_Sys_Status; } _EMS_Sys_Status;
// The Tx send package // The Tx send package
@@ -148,7 +151,6 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
typedef struct { typedef struct {
uint8_t model_id; uint8_t model_id;
uint8_t product_id; uint8_t product_id;
uint8_t device_id;
char model_string[50]; char model_string[50];
} _Boiler_Type; } _Boiler_Type;
@@ -241,7 +243,8 @@ typedef struct { // UBAParameterWW
// SM Solar Module - SM10Monitor/SM100Monitor // SM Solar Module - SM10Monitor/SM100Monitor
typedef struct { typedef struct {
bool SM; // set true if there is a SM available bool SM; // set true if there is a Solar Module available
bool HP; // set true if there is a Heat Pump available
int16_t SMcollectorTemp; // collector temp int16_t SMcollectorTemp; // collector temp
int16_t SMbottomTemp; // bottom temp int16_t SMbottomTemp; // bottom temp
uint8_t SMpumpModulation; // modulation solar pump uint8_t SMpumpModulation; // modulation solar pump
@@ -249,6 +252,8 @@ typedef struct {
int16_t SMEnergyLastHour; int16_t SMEnergyLastHour;
int16_t SMEnergyToday; int16_t SMEnergyToday;
int16_t SMEnergyTotal; int16_t SMEnergyTotal;
uint8_t HPModulation; // heatpump modulation in %
uint8_t HPSpeed; // speed 0-100 %
} _EMS_Other; } _EMS_Other;
// Thermostat data // Thermostat data
@@ -292,6 +297,14 @@ extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
void ems_init(); void ems_init();
void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh = false); void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh = false);
void ems_sendRawTelegram(char * telegram); void ems_sendRawTelegram(char * telegram);
void ems_scanDevices();
void ems_printAllDevices();
void ems_printDevices();
void ems_printTxQueue();
void ems_testTelegram(uint8_t test_num);
void ems_startupTelegrams();
bool ems_checkEMSBUSAlive();
void ems_clearDeviceList();
void ems_setThermostatTemp(float temperature, uint8_t temptype = 0); void ems_setThermostatTemp(float temperature, uint8_t temptype = 0);
void ems_setThermostatMode(uint8_t mode); void ems_setThermostatMode(uint8_t mode);
@@ -301,14 +314,14 @@ void ems_setFlowTemp(uint8_t temperature);
void ems_setWarmWaterActivated(bool activated); void ems_setWarmWaterActivated(bool activated);
void ems_setWarmTapWaterActivated(bool activated); void ems_setWarmTapWaterActivated(bool activated);
void ems_setPoll(bool b); void ems_setPoll(bool b);
void ems_setTxDelay(bool b);
void ems_setLogging(_EMS_SYS_LOGGING loglevel); void ems_setLogging(_EMS_SYS_LOGGING loglevel);
void ems_setEmsRefreshed(bool b); void ems_setEmsRefreshed(bool b);
void ems_setWarmWaterModeComfort(uint8_t comfort); void ems_setWarmWaterModeComfort(uint8_t comfort);
bool ems_checkEMSBUSAlive();
void ems_setModels(); void ems_setModels();
void ems_setTxDisabled(bool b); void ems_setTxDisabled(bool b);
char * ems_getThermostatDescription(char * buffer);
char * ems_getBoilerDescription(char * buffer);
void ems_getThermostatValues(); void ems_getThermostatValues();
void ems_getBoilerValues(); void ems_getBoilerValues();
void ems_getOtherValues(); void ems_getOtherValues();
@@ -316,7 +329,6 @@ bool ems_getPoll();
bool ems_getTxEnabled(); bool ems_getTxEnabled();
bool ems_getThermostatEnabled(); bool ems_getThermostatEnabled();
bool ems_getBoilerEnabled(); bool ems_getBoilerEnabled();
bool ems_getTxDelay();
bool ems_getBusConnected(); bool ems_getBusConnected();
_EMS_SYS_LOGGING ems_getLogging(); _EMS_SYS_LOGGING ems_getLogging();
bool ems_getEmsRefreshed(); bool ems_getEmsRefreshed();
@@ -325,17 +337,6 @@ void ems_discoverModels();
bool ems_getTxCapable(); bool ems_getTxCapable();
uint32_t ems_getPollFrequency(); uint32_t ems_getPollFrequency();
void ems_scanDevices();
void ems_printAllDevices();
void ems_printDevices();
char * ems_getThermostatDescription(char * buffer);
void ems_printTxQueue();
char * ems_getBoilerDescription(char * buffer);
void ems_testTelegram(uint8_t test_num);
void ems_startupTelegrams();
void ems_startupTelegrams();
// private functions // private functions
uint8_t _crcCalculator(uint8_t * data, uint8_t len); uint8_t _crcCalculator(uint8_t * data, uint8_t len);
void _processType(_EMS_RxTelegram * EMS_RxTelegram); void _processType(_EMS_RxTelegram * EMS_RxTelegram);
@@ -344,7 +345,6 @@ void _ems_clearTxData();
int _ems_findBoilerModel(uint8_t model_id); int _ems_findBoilerModel(uint8_t model_id);
bool _ems_setModel(uint8_t model_id); bool _ems_setModel(uint8_t model_id);
void _removeTxQueue(); void _removeTxQueue();
void _ems_readTelegram(uint8_t * telegram, uint8_t length);
// global so can referenced in other classes // global so can referenced in other classes
extern _EMS_Sys_Status EMS_Sys_Status; extern _EMS_Sys_Status EMS_Sys_Status;

View File

@@ -41,11 +41,14 @@
#define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp #define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp
// Other // Other
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor #define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor #define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
#define EMS_TYPE_SM100Status 0x0264 // SM100Status #define EMS_TYPE_SM100Status 0x0264 // SM100Status
#define EMS_TYPE_SM100Status2 0x026A // SM100Status2 #define EMS_TYPE_SM100Status2 0x026A // SM100Status2
#define EMS_TYPE_SM100Energy 0x028E // SM100Energy #define EMS_TYPE_SM100Energy 0x028E // SM100Energy
#define EMS_TYPE_HPMonitor1 0xE3 // HeatPump Monitor 1
#define EMS_TYPE_HPMonitor2 0xE5 // HeatPump Monitor 2
#define EMS_TYPE_ISM1StatusMessage 0x0003 // Solar Module Junkers ISM1 Status
/* /*
* Thermostats... * Thermostats...
@@ -107,10 +110,10 @@
#define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp #define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp
#define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode #define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode
// Junkers FR10 (EMS Plus) // Junkers FR10, FW100 (EMS Plus)
#define EMS_TYPE_FR10StatusMessage 0x6F // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps
#define EMS_OFFSET_FR10StatusMessage_setpoint 3 // setpoint temp #define EMS_OFFSET_JunkersStatusMessage_setpoint 2 // setpoint temp
#define EMS_OFFSET_FR10StatusMessage_curr 5 // current temp #define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp
// Known EMS types // Known EMS types
@@ -132,38 +135,40 @@ typedef enum {
EMS_MODEL_RC30, EMS_MODEL_RC30,
EMS_MODEL_RC35, EMS_MODEL_RC35,
EMS_MODEL_EASY, EMS_MODEL_EASY,
EMS_MODEL_BOSCHEASY,
EMS_MODEL_RC310, EMS_MODEL_RC310,
EMS_MODEL_CW100, EMS_MODEL_CW100,
EMS_MODEL_1010, EMS_MODEL_1010,
EMS_MODEL_OT, EMS_MODEL_OT,
EMS_MODEL_FW100,
EMS_MODEL_FR10, EMS_MODEL_FR10,
EMS_MODEL_FR100, EMS_MODEL_FR100,
EMS_MODEL_FR110 EMS_MODEL_FR110,
EMS_MODEL_FW120
} _EMS_MODEL_ID; } _EMS_MODEL_ID;
// EMS types for known Buderus/Bosch devices. This list will be extended when new devices are recognized. // EMS types for known devices. This list will be extended when new devices are recognized.
// format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION // The device_id is always 0x08
// format is MODEL_ID, PRODUCT ID, DESCRIPTION
const _Boiler_Type Boiler_Types[] = { const _Boiler_Type Boiler_Types[] = {
{EMS_MODEL_UBA, 72, 0x08, "MC10 Module"}, {EMS_MODEL_UBA, 72, "MC10 Module"},
{EMS_MODEL_UBA, 123, 0x08, "Buderus GB172/Nefit Trendline"}, {EMS_MODEL_UBA, 123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"},
{EMS_MODEL_UBA, 115, 0x08, "Nefit Topline Compact/Buderus GB162"}, {EMS_MODEL_UBA, 115, "Nefit Topline Compact/Buderus GB162"},
{EMS_MODEL_UBA, 203, 0x08, "Buderus Logamax U122"}, {EMS_MODEL_UBA, 203, "Buderus Logamax U122/Junkers Cerapur"},
{EMS_MODEL_UBA, 208, 0x08, "Buderus Logamax plus/GB192"}, {EMS_MODEL_UBA, 208, "Buderus Logamax plus/GB192"},
{EMS_MODEL_UBA, 64, 0x08, "Sieger BK15 Boiler/Nefit Smartline"}, {EMS_MODEL_UBA, 64, "Sieger BK15/Nefit Smartline/Buderus GB152"},
{EMS_MODEL_UBA, 95, 0x08, "Bosch Condens 2500/Junkers Heatronics3"}, {EMS_MODEL_UBA, EMS_PRODUCTID_HEATRONICS, "Bosch Condens 2500/Junkers Heatronics3"}, // Junkers
{EMS_MODEL_UBA, 122, 0x08, "Nefit Proline"}, {EMS_MODEL_UBA, 122, "Nefit Proline"},
{EMS_MODEL_UBA, 172, 0x08, "Nefit Enviline"} {EMS_MODEL_UBA, 172, "Nefit Enviline"}
}; };
// Other EMS devices which are not considered boilers or thermostats // Other EMS devices which are not considered boilers or thermostats
const _Other_Type Other_Types[] = { const _Other_Type Other_Types[] = {
{EMS_MODEL_OTHER, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id! {EMS_MODEL_OTHER, 69, 0x21, "MM10 Mixer Module"},
{EMS_MODEL_OTHER, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id! {EMS_MODEL_OTHER, 71, 0x11, "WM10 Switch Module"},
{EMS_MODEL_OTHER, 160, 0x20, "MM100 Mixing Module"}, {EMS_MODEL_OTHER, 160, 0x20, "MM100 Mixing Module"},
{EMS_MODEL_OTHER, 160, 0x21, "MM100 Mixing Module"}, {EMS_MODEL_OTHER, 160, 0x21, "MM100 Mixing Module"},
{EMS_MODEL_OTHER, 159, 0x21, "MM50 Mixing Module"}, {EMS_MODEL_OTHER, 159, 0x21, "MM50 Mixing Module"},
@@ -171,10 +176,13 @@ const _Other_Type Other_Types[] = {
{EMS_MODEL_OTHER, 190, 0x09, "BC10 Base Controller"}, {EMS_MODEL_OTHER, 190, 0x09, "BC10 Base Controller"},
{EMS_MODEL_OTHER, 114, 0x09, "BC10 Base Controller"}, {EMS_MODEL_OTHER, 114, 0x09, "BC10 Base Controller"},
{EMS_MODEL_OTHER, 125, 0x09, "BC25 Base Controller"}, {EMS_MODEL_OTHER, 125, 0x09, "BC25 Base Controller"},
{EMS_MODEL_OTHER, 152, 0x09, "Junkers Controller"},
{EMS_MODEL_OTHER, 205, 0x02, "Nefit Moduline Easy Connect"}, {EMS_MODEL_OTHER, 205, 0x02, "Nefit Moduline Easy Connect"},
{EMS_MODEL_OTHER, 73, EMS_ID_SM, "SM10 Solar Module"}, {EMS_MODEL_OTHER, 73, EMS_ID_SM, "SM10 Solar Module"},
{EMS_MODEL_OTHER, 163, EMS_ID_SM, "SM100 Solar Module"}, {EMS_MODEL_OTHER, 163, EMS_ID_SM, "SM100 Solar Module"},
{EMS_MODEL_OTHER, 171, 0x02, "EMS-OT OpenTherm converter"}, {EMS_MODEL_OTHER, 171, 0x02, "EMS-OT OpenTherm converter"},
{EMS_MODEL_OTHER, 252, EMS_ID_HP, "HeatPump Module"}, // warning, fake product id!
{EMS_MODEL_OTHER, 101, 0x30, "Junkers ISM1 Solar Controller"},
{EMS_MODEL_OTHER, 189, EMS_ID_GATEWAY, "Web Gateway KM200"} {EMS_MODEL_OTHER, 189, EMS_ID_GATEWAY, "Web Gateway KM200"}
}; };
@@ -186,7 +194,8 @@ const _Thermostat_Type Thermostat_Types[] = {
// Easy devices - not currently supporting write operations // Easy devices - not currently supporting write operations
{EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_BOSCHEASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_EASY, 203, 0x18, "Bosch EasyControl CT200", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_EASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_CW100, 157, 0x18, "CW100", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_CW100, 157, 0x18, "CW100", EMS_THERMOSTAT_WRITE_NO},
// Buderus/Nefit // Buderus/Nefit
@@ -202,9 +211,11 @@ const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_ES73, 76, 0x10, "Sieger ES73", EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_ES73, 76, 0x10, "Sieger ES73", EMS_THERMOSTAT_WRITE_YES},
// Junkers // Junkers
{EMS_MODEL_FW100, 105, 0x10, "Junkers FW100", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_FR10, 111, 0x18, "Junkers FR10", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_FR10, 111, 0x18, "Junkers FR10", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_FR100, 105, 0x18, "Junkers FR100", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_FR100, 105, 0x18, "Junkers FR100", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_FR110, 108, 0x18, "Junkers FR110", EMS_THERMOSTAT_WRITE_NO} {EMS_MODEL_FR110, 108, 0x18, "Junkers FR110", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_FW120, 192, 0x10, "Junkers FW120", EMS_THERMOSTAT_WRITE_NO}
}; };

View File

@@ -7,9 +7,6 @@
#include "emsuart.h" #include "emsuart.h"
#include "ems.h" #include "ems.h"
#include "ets_sys.h"
#include "osapi.h"
#include <Arduino.h> #include <Arduino.h>
#include <user_interface.h> #include <user_interface.h>
@@ -34,34 +31,28 @@ static void emsuart_rx_intr_handler(void * para) {
} }
// fill IRQ buffer, by emptying Rx FIFO // fill IRQ buffer, by emptying Rx FIFO
if (U0IS & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) { if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) { while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
uart_buffer[length++] = USF(EMSUART_UART); uart_buffer[length++] = USF(EMSUART_UART);
} }
// clear Rx FIFO full and Rx FIFO timeout interrupts // clear Rx FIFO full and Rx FIFO timeout interrupts
U0IC = (1 << UIFF); USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
U0IC = (1 << UITO);
} }
// BREAK detection = End of EMS data block // BREAK detection = End of EMS data block
if (USIS(EMSUART_UART) & ((1 << UIBD))) { if (USIS(EMSUART_UART) & ((1 << UIBD))) {
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them
U0IC = (1 << UIBD); // INT clear the BREAK detect interrupt USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
// copy data into transfer buffer pEMSRxBuf->length = length;
pEMSRxBuf->writePtr = length; os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package
// set the status flag stating BRK has been received and we can start a new package system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE;
// call emsuart_recvTask() at next opportunity ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
system_os_post(EMSUART_recvTaskPrio, 0, 0);
// re-enable UART interrupts
ETS_UART_INTR_ENABLE();
} }
} }
@@ -72,8 +63,30 @@ static void emsuart_rx_intr_handler(void * para) {
*/ */
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) { static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
_EMSRxBuf * pCurrent = pEMSRxBuf; _EMSRxBuf * pCurrent = pEMSRxBuf;
ems_parseTelegram((uint8_t *)pCurrent->buffer, (pCurrent->writePtr) - 1); // transmit EMS buffer, excluding the BRK uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
// validate and transmit the EMS buffer, excluding the BRK
if (length == 2) {
// it's a poll or status code, single byte
ems_parseTelegram((uint8_t *)pCurrent->buffer, 1);
} else if ((length > 4) && (pCurrent->buffer[length - 2] != 0x00)) {
// ignore double BRK at the end, possibly from the Tx loopback
// also telegrams with no data value
ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK
}
memset(pCurrent->buffer, 0x00, EMS_MAXBUFFERSIZE); // wipe memory just to be safe
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
}
/*
* flush everything left over in buffer, this clears both rx and tx FIFOs
*/
static inline void ICACHE_FLASH_ATTR emsuart_flush_fifos() {
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
} }
/* /*
@@ -100,32 +113,29 @@ void ICACHE_FLASH_ATTR emsuart_init() {
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD); USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1 USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1
// flush everything left over in buffer, this clears both rx and tx FIFOs emsuart_flush_fifos();
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
// conf1 params // conf1 params
// UCTOE = RX TimeOut enable (default is 1) // UCTOE = RX TimeOut enable (default is 1)
// UCTOT = RX TimeOut Threshold (7bit) = want this when no more data after 2 characters. (default is 2) // UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 2 characters (default is 2)
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer. (default was 127). // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127)
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf // see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
USC1(EMSUART_UART) = 0; // reset config first USC1(EMSUART_UART) = 0; // reset config first
USC1(EMSUART_UART) = (EMS_MAX_TELEGRAM_LENGTH << UCFFT) | (0x02 << UCTOT) | (1 << UCTOE); // enable interupts USC1(EMSUART_UART) = (EMS_MAX_TELEGRAM_LENGTH << UCFFT) | (0x02 << UCTOT) | (1 << UCTOE); // enable interupts
// set interrupts for triggers // set interrupts for triggers
USIC(EMSUART_UART) = 0xffff; // clear all interupts USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
USIE(EMSUART_UART) = 0; // disable all interrupts USIE(EMSUART_UART) = 0; // disable all interrupts
// enable rx break, fifo full and timeout. // enable rx break, fifo full and timeout.
// not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes // but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes
USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (1 << UITO); USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (1 << UITO);
// set up interrupt callbacks for Rx // set up interrupt callbacks for Rx
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen); system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
// disable esp debug which will go to Tx and mess up the line // disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
system_set_os_print(0); // https://github.com/espruino/Espruino/issues/655 system_set_os_print(0);
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
system_uart_swap(); system_uart_swap();
@@ -136,13 +146,10 @@ void ICACHE_FLASH_ATTR emsuart_init() {
/* /*
* stop UART0 driver * stop UART0 driver
* This is called prior to an OTA upload and also before a save to SPIFFS to prevent conflicts
*/ */
void ICACHE_FLASH_ATTR emsuart_stop() { void ICACHE_FLASH_ATTR emsuart_stop() {
ETS_UART_INTR_DISABLE(); ETS_UART_INTR_DISABLE();
//ETS_UART_INTR_ATTACH(NULL, NULL);
//system_uart_swap(); // to be sure, swap Tx/Rx back.
//detachInterrupt(digitalPinToInterrupt(D7));
//noInterrupts();
} }
/* /*
@@ -153,45 +160,77 @@ void ICACHE_FLASH_ATTR emsuart_start() {
} }
/* /*
* Send a BRK signal * set loopback mode and clear Tx/Rx FIFO
* Which is a 11-bit set of zero's (11 cycles)
*/ */
void ICACHE_FLASH_ATTR emsuart_tx_brk() { static inline void ICACHE_FLASH_ATTR emsuart_loopback(bool enable) {
// must make sure Tx FIFO is empty if (enable)
while (((USS(EMSUART_UART) >> USTXC) & 0xff) != 0) USC0(EMSUART_UART) |= (1 << UCLBE); // enable loopback
; else
USC0(EMSUART_UART) &= ~(1 << UCLBE); // disable loopback
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
// To create a 11-bit <BRK> we set TXD_BRK bit so the break signal will
// automatically be sent when the tx fifo is empty
USC0(EMSUART_UART) |= (1 << UCBRK); // set bit
delayMicroseconds(EMS_TX_BRK_WAIT); // 2070 - based on trial and error using an oscilloscope
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear bit
} }
/* /*
* Send to Tx, ending with a <BRK> * Send to Tx, ending with a <BRK>
*/ */
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
for (uint8_t i = 0; i < len; i++) { if (len == 0)
USF(EMSUART_UART) = buf[i]; return;
// check if we need to force a delay to slow down Tx /*
// https://github.com/proddy/EMS-ESP/issues/23# * based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
if (EMS_Sys_Status.emsTxDelay) { * we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
delayMicroseconds(EMS_TX_BRK_WAIT); * after sending the last char we poll the Rx status until either
* - size(Rx FIFO) == size(Tx-Telegram)
* - <BRK> is detected
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
*/
ETS_UART_INTR_DISABLE(); // disable rx interrupt
// clear Rx status register
USC0(EMSUART_UART) |= (1 << UCRXRST); // reset uart rx fifo
emsuart_flush_fifos();
// throw out the telegram...
for (uint8_t i = 0; i < len;) {
USF(EMSUART_UART) = buf[i++]; // send each Tx byte
// wait for echo from busmaster
while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) < i || (USIS(EMSUART_UART) & (1 << UIBD)))) {
delayMicroseconds(EMSUART_BIT_TIME); // burn CPU cycles...
} }
} }
emsuart_tx_brk(); // send <BRK>
// we got the whole telegram in the Rx buffer
// on Rx-BRK (bus collision), we simply enable Rx and leave it
// otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT.
// worst case, we'll see an additional Rx-BRK...
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
// no bus collision - send terminating BRK signal
emsuart_loopback(true);
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
// wait until BRK detected...
while (!(USIS(EMSUART_UART) & (1 << UIBD))) {
delayMicroseconds(EMSUART_BIT_TIME);
}
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK>
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
emsuart_loopback(false); // disable loopback mode
}
ETS_UART_INTR_ENABLE(); // receive anything from FIFO...
} }
/* /*
* Send the Poll (our own ID) to Tx as a single byte and end with a <BRK> * Send the Poll (our own ID) to Tx as a single byte and end with a <BRK>
*/ */
void ICACHE_FLASH_ATTR emsaurt_tx_poll() { void ICACHE_FLASH_ATTR emsuart_tx_poll() {
USF(EMSUART_UART) = EMS_ID_ME; static uint8_t buf[1];
emsuart_tx_brk(); // send <BRK> if (EMS_Sys_Status.emsReverse) {
buf[0] = {EMS_ID_ME | 0x80};
} else {
buf[0] = {EMS_ID_ME};
}
emsuart_tx_buffer(buf, 1);
} }

View File

@@ -13,19 +13,16 @@
#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity) #define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity)
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit #define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
#define EMS_MAXBUFFERS 10 // 4 buffers for circular filling to avoid collisions #define EMS_MAXBUFFERS 10 // buffers for circular filling to avoid collisions
#define EMS_MAXBUFFERSIZE 32 // max size of the buffer. packets are max 32 bytes #define EMS_MAXBUFFERSIZE 32 // max size of the buffer. packets are max 32 bytes to support EMS 1.0
// this is how long we drop the Tx signal to create a 11-bit Break of zeros (BRK) #define EMSUART_BIT_TIME 104 // bit time @9600 baud
// At 9600 baud, 11 bits will be 1144 microseconds
// the BRK from Boiler master is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag)
#define EMS_TX_BRK_WAIT 2070
#define EMSUART_recvTaskPrio 1 #define EMSUART_recvTaskPrio 1
#define EMSUART_recvTaskQueueLen 64 #define EMSUART_recvTaskQueueLen 64
typedef struct { typedef struct {
uint8_t writePtr; uint8_t length;
uint8_t buffer[EMS_MAXBUFFERSIZE]; uint8_t buffer[EMS_MAXBUFFERSIZE];
} _EMSRxBuf; } _EMSRxBuf;
@@ -33,5 +30,4 @@ void ICACHE_FLASH_ATTR emsuart_init();
void ICACHE_FLASH_ATTR emsuart_stop(); void ICACHE_FLASH_ATTR emsuart_stop();
void ICACHE_FLASH_ATTR emsuart_start(); void ICACHE_FLASH_ATTR emsuart_start();
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len); void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
void ICACHE_FLASH_ATTR emsaurt_tx_poll(); void ICACHE_FLASH_ATTR emsuart_tx_poll();
void ICACHE_FLASH_ATTR emsuart_tx_brk();

View File

@@ -58,6 +58,11 @@
#define SM_ENERGYTODAY "energytoday" // energy today #define SM_ENERGYTODAY "energytoday" // energy today
#define SM_ENERGYTOTAL "energytotal" // energy total #define SM_ENERGYTOTAL "energytotal" // energy total
// MQTT for HP (HeatPump)
#define TOPIC_HP_DATA "hp_data" // topic name
#define HP_PUMPMODULATION "pumpmodulation" // pump modulation
#define HP_PUMPSPEED "pumpspeed" // pump speed
// shower time // shower time
#define TOPIC_SHOWERTIME "showertime" // for sending shower time results #define TOPIC_SHOWERTIME "showertime" // for sending shower time results
#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic #define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic

View File

@@ -46,7 +46,11 @@ static const char * TEST_DATA[] = {
"10 48 F7 00 FF 01 A5 DF FF F7 7F 1F", // test 41 - gateway "10 48 F7 00 FF 01 A5 DF FF F7 7F 1F", // test 41 - gateway
"30 00 FF 09 02 64 1E", // test 42 - SM100 "30 00 FF 09 02 64 1E", // test 42 - SM100
"08 00 18 00 05 03 30 00 00 00 00 04 40 80 00 02 17 80 00 00 00 FF 30 48 00 CB 00 00 00", // test 43 - sys pressure "08 00 18 00 05 03 30 00 00 00 00 04 40 80 00 02 17 80 00 00 00 FF 30 48 00 CB 00 00 00", // test 43 - sys pressure
"90 00 FF 00 00 6F 03 01 00 BE 00 BF" // test 44 - FR10 "90 00 FF 00 00 6F 03 01 00 BE 00 BF", // test 44 - FR10
"08 00 E3 00 01 00 01 00 00 00 00 00 00 00 00 00 DF 00 64 55", // test 45 - heatpump Enviline
"08 00 E5 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A", // test 46 - heatpump Enviline
"38 10 FF 00 03 2B 00 C7 07 C3 01" // test 47 - heatpump Enviline
}; };

View File

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