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
lib/readme.txt
.travis.yml
;stackdmp.txt
;*.jar
scripts/stackdmp.txt

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/),
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
- 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
- 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

View File

@@ -1,7 +1,23 @@
# 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"
#ifdef CRASH
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
MyESP::MyESP() {
@@ -33,6 +43,7 @@ MyESP::MyESP() {
_helpProjectCmds_count = 0;
_use_serial = false;
_heartbeat = false;
_mqtt_host = NULL;
_mqtt_password = NULL;
_mqtt_username = NULL;
@@ -57,6 +68,9 @@ MyESP::MyESP() {
_ota_post_callback = NULL;
_suspendOutput = false;
_rtcmem_status = false;
_systemStable = true;
}
MyESP::~MyESP() {
@@ -124,6 +138,26 @@ bool MyESP::getUseSerial() {
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
void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
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
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);
} else {
myDebug_P(PSTR("Using serial port output"));
}
// 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() {
myDebug_P(PSTR("[OTA] Start"));
#ifdef CRASH
// If we are not specifically reserving the sectors we are using as
// EEPROM in the memory layout then any OTA upgrade will overwrite
// all but the last one.
@@ -375,7 +407,6 @@ void MyESP::_OTACallback() {
// See onError callback below.
EEPROMr.rotate(false);
EEPROMr.commit();
#endif
if (_ota_pre_callback) {
(_ota_pre_callback)(); // call custom function
@@ -392,7 +423,11 @@ void MyESP::_ota_setup() {
ArduinoOTA.setHostname(_app_hostname);
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) {
static unsigned int _progOld;
unsigned int _prog = (progress / (total / 100));
@@ -414,10 +449,8 @@ void MyESP::_ota_setup() {
else if (error == OTA_END_ERROR)
myDebug_P(PSTR("[OTA] End Failed"));
#ifdef CRASH
// There's been an error, reenable rotation
EEPROMr.rotate(true);
#endif
});
}
@@ -431,10 +464,8 @@ void MyESP::setBoottime(const char * boottime) {
// eeprom
void MyESP::_eeprom_setup() {
#ifdef CRASH
EEPROMr.size(4);
EEPROMr.begin(SPI_FLASH_SEC_SIZE);
#endif
}
// Set callback of sketch function to process project messages
@@ -450,17 +481,17 @@ void MyESP::_telnetConnected() {
_consoleShowHelp(); // Show the initial message
// show crash dump if just restarted after a fatal crash
#ifdef CRASH
uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time != 0) && (crash_time != 0xFFFFFFFF)) {
crashDump();
/*
// clear crash data
crash_time = 0xFFFFFFFF;
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
*/
}
#endif
// call callback
if (_telnet_callback) {
@@ -497,16 +528,18 @@ void MyESP::_consoleShowHelp() {
} else {
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("* 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("* Commands:"));
myDebug_P(PSTR("* ?=help, CTRL-D/quit=exit telnet session"));
myDebug_P(PSTR("* set, system, reboot"));
#ifdef CRASH
myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
#endif
myDebug_P(PSTR("* crash <dump | clear>"));
// print custom commands if available. Taken from progmem
if (_telnetcommand_callback) {
@@ -590,6 +623,7 @@ void MyESP::_printSetCommands() {
myDebug_P(PSTR("")); // newline
myDebug_P(PSTR(" serial=%s"), (_use_serial) ? "on" : "off");
myDebug_P(PSTR(" heartbeat=%s"), (_heartbeat) ? "on" : "off");
// print any custom settings
(_fs_settings_callback)(MYESP_FSACTION_LIST, 0, NULL, NULL);
@@ -600,6 +634,7 @@ void MyESP::_printSetCommands() {
// reset / restart
void MyESP::resetESP() {
myDebug_P(PSTR("* Reboot ESP..."));
_deferredReset(100, CUSTOM_RESET_TERMINAL);
end();
#if defined(ARDUINO_ARCH_ESP32)
ESP.restart();
@@ -638,7 +673,7 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
}
ok = true;
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) {
if (_wifi_password)
free(_wifi_password);
@@ -648,7 +683,7 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
}
ok = true;
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) {
if (_mqtt_host)
@@ -691,6 +726,23 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
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 {
// finally check for any custom commands
ok = (_fs_settings_callback)(MYESP_FSACTION_SET, wc, setting, value);
@@ -791,21 +843,18 @@ void MyESP::_telnetCommand(char * commandLine) {
}
// crash command
#ifdef CRASH
if ((strcmp(ptrToCommandName, "crash") == 0) && (wc >= 2)) {
// crash command
if ((strcmp(ptrToCommandName, "crash") == 0) && (wc == 2)) {
char * cmd = _telnet_readWord(false);
if (strcmp(cmd, "dump") == 0) {
crashDump();
} else if (strcmp(cmd, "clear") == 0) {
crashClear();
} else if ((strcmp(cmd, "test") == 0) && (wc == 3)) {
char * value = _telnet_readWord(false);
crashTest(atoi(value));
} else {
myDebug_P(PSTR("Error. Usage: crash <dump | clear>"));
}
return; // don't call custom command line callback
}
#endif
// call callback function
(_telnetcommand_callback)(wc, commandLine);
@@ -852,17 +901,145 @@ String MyESP::_buildTime() {
// returns system uptime in seconds - copied for espurna. see (c)
unsigned long MyESP::_getUptime() {
static unsigned long last_uptime = 0;
static unsigned char uptime_overflows = 0;
static uint32_t last_uptime = 0;
static uint8_t uptime_overflows = 0;
if (millis() < last_uptime)
if (millis() < last_uptime) {
++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;
}
// 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
// for battery power is ESP.getVcc()
void MyESP::showSystemStats() {
@@ -881,16 +1058,21 @@ void MyESP::showSystemStats() {
}
// uptime
uint32_t t = _getUptime(); // seconds
uint32_t t = _getUptime(); // seconds
uint32_t d = t / 86400L;
uint32_t h = (t / 3600L) % 60;
uint32_t h = ((t % 86400L) / 3600L) % 60;
uint32_t rem = t % 3600L;
uint8_t m = 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());
if (!getSystemCheck()) {
myDebug_P(PSTR(" [SYSTEM] Device is in SAFE MODE"));
}
if (isAPmode()) {
myDebug_P(PSTR(" [WIFI] Device is in AP mode with SSID %s"), jw.getAPSSID().c_str());
} else {
@@ -901,10 +1083,8 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [WIFI] WiFi MAC: %s"), WiFi.macAddress().c_str());
#ifdef CRASH
char output_str[80] = {0};
char buffer[16] = {0};
/* Crash info */
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));
strlcat(output_str, itoa(EEPROMr.size(), buffer, 10), sizeof(output_str));
@@ -914,7 +1094,6 @@ void MyESP::showSystemStats() {
strlcat(output_str, " ", sizeof(output_str));
}
myDebug(output_str);
#endif
#ifdef 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 mode: %d"), ESP.getBootMode());
//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
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] Max OTA size: %d"), (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
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(""));
}
/*
* 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
void MyESP::_telnetHandle() {
SerialAndTelnet.handle();
@@ -1187,7 +1429,7 @@ void MyESP::_fs_eraseConfig() {
"automatically restart when finished."));
if (SPIFFS.format()) {
delay(1000); // wait 1 seconds
delay(1000); // wait 1 second
resetESP();
}
}
@@ -1242,6 +1484,8 @@ bool MyESP::_fs_loadConfig() {
_use_serial = (bool)json["use_serial"];
_heartbeat = (bool)json["heartbeat"];
// callback for loading custom settings
// 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);
@@ -1270,6 +1514,7 @@ bool MyESP::fs_saveConfig() {
json["mqtt_username"] = _mqtt_username;
json["mqtt_password"] = _mqtt_password;
json["use_serial"] = _use_serial;
json["heartbeat"] = _heartbeat;
// callback for saving custom settings
(void)(_fs_callback)(MYESP_FSACTION_SAVE, json);
@@ -1321,19 +1566,19 @@ void MyESP::_fs_setup() {
// _fs_printConfig(); // enable for debugging
}
uint16_t MyESP::getSystemLoadAverage() {
uint32_t MyESP::getSystemLoadAverage() {
return _load_average;
}
// calculate load average
void MyESP::_calculateLoad() {
static unsigned long last_loadcheck = 0;
static unsigned long load_counter_temp = 0;
static uint32_t last_loadcheck = 0;
static uint32_t load_counter_temp = 0;
load_counter_temp++;
if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
static unsigned long load_counter = 0;
static unsigned long load_counter_max = 1;
static uint32_t load_counter = 0;
static uint32_t load_counter_max = 1;
load_counter = load_counter_temp;
load_counter_temp = 0;
@@ -1385,7 +1630,6 @@ int MyESP::getWifiQuality() {
return 2 * (dBm + 100);
}
#ifdef CRASH
/**
* Save crash information in EEPROM
* 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();
}
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
*/
@@ -1473,18 +1677,71 @@ void MyESP::crashClear() {
/**
* Print out crash information that has been previously saved in EEPROM
* Copied from https://github.com/krzychb/EspSaveCrash
*/
void MyESP::crashDump() {
uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
myDebug_P(PSTR("[CRASH] No crash info"));
myDebug_P(PSTR("[CRASH] No crash data captured."));
return;
}
myDebug_P(PSTR("[CRASH] Latest crash was at %lu ms after boot"), crash_time);
myDebug_P(PSTR("[CRASH] Reason of restart: %u"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
myDebug_P(PSTR("[CRASH] Exception cause: %u"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
uint32_t t = crash_time / 1000; // convert to seconds
uint32_t d = t / 86400L;
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;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
@@ -1519,17 +1776,8 @@ void MyESP::crashDump() {
SerialAndTelnet.println();
}
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
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_version = strdup(app_version);
_telnet_setup(); // Telnet setup, does first to set Serial
_eeprom_setup(); // set up eeprom for storing crash data
getInitialFreeHeap(); // get initial free mem
_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
_wifi_setup(); // WIFI setup
_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() {
_calculateLoad();
_systemCheckLoop();
_heartbeatCheck();
_telnetHandle();
jw.loop(); // WiFi
/*
// do nothing else until we've got a wifi connection
if (WiFi.getMode() & WIFI_AP) {
return;
}
*/
jw.loop(); // WiFi
ArduinoOTA.handle(); // OTA
_mqttConnect(); // MQTT

View File

@@ -9,7 +9,7 @@
#ifndef MyEMS_h
#define MyEMS_h
#define MYESP_VERSION "1.1.11"
#define MYESP_VERSION "1.1.16"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
@@ -19,12 +19,12 @@
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#ifdef CRASH
#include <EEPROM_Rotate.h>
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)
//#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_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_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_HEARTBEAT "heartbeat"
#define MQTT_TOPIC_START_PAYLOAD "start"
#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_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
#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_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 {
bool set; // is it a set command
char key[50];
@@ -193,14 +231,18 @@ class MyESP {
void crashInfo();
// general
void end();
void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version);
void setBoottime(const char * boottime);
void resetESP();
uint16_t getSystemLoadAverage();
int getWifiQuality();
void showSystemStats();
void end();
void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version);
void setBoottime(const char * boottime);
void resetESP();
int getWifiQuality();
void showSystemStats();
bool getHeartbeat();
// rtcmem and reset reason
bool rtcmemStatus();
uint32_t getSystemResetReason();
private:
// mqtt
@@ -226,6 +268,7 @@ class MyESP {
char * _mqtt_topic;
unsigned long _mqtt_last_connection;
bool _mqtt_connecting;
bool _rtcmem_status;
// wifi
DNSServer dnsServer; // For Access Point (AP) support
@@ -280,12 +323,41 @@ class MyESP {
char * _boottime;
bool _suspendOutput;
bool _use_serial;
bool _heartbeat;
unsigned long _getUptime();
String _buildTime();
// load average (0..100)
void _calculateLoad();
unsigned short int _load_average;
// reset reason and rtcmem
bool _rtcmemStatus();
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;

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]
; add here your board, e.g. nodemcuv2, d1_mini, d1_mini_pro
env_default = d1_mini
default_envs = release
;default_envs = debug
[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
; for debug use these...
; build_flags = -g -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DCRASH -DTESTS
wifi_settings =
; 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"'
arduino_core_2_3_0 = espressif8266@1.5.0
arduino_core_2_4_0 = espressif8266@1.6.0
arduino_core_2_4_1 = espressif8266@1.7.3
arduino_core_2_4_2 = espressif8266@1.8.0
arduino_core_2_5_0 = espressif8266@2.0.4
arduino_core_2_5_1 = espressif8266@2.1.1
arduino_core_2_5_2 = espressif8266@2.2.1
arduino_core_latest = espressif8266
[env]
board = d1_mini
; board = nodemcuv2
; board = d1_mini_pro
framework = arduino
platform = ${common.arduino_core_latest}
lib_deps =
CRC32
CircularBuffer
@@ -22,17 +34,23 @@ lib_deps =
ArduinoJson
OneWire
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
monitor_speed = 115200
; for OTA comment out these sections
; uncomment next 2 lines for OTA
;upload_protocol = espota
;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
# <<<stack<<<
# two ways of analyzing the dumps
# 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'])
call(['python', 'scripts/decoder.py ', '-s', '-e', os.getcwd()+"/.pio/build/debug/firmware_d1_mini.elf", 'scripts/stackdmp.txt'])

View File

@@ -1,11 +1,34 @@
>>>stack>>>
3fffff30: 00000020 3fff4dcc 0000000d 40216e4e
3fffff40: 00000020 3fff4dcc 3fff4db8 3fff4e3c
3fffff50: 0000000d 3fff4db8 3fff4b64 4021280d
3fffff60: 0000000d 3fff5094 3ffe8560 3fff5038
3fffff70: 3fff4b64 3fff316c 3fff4b54 3fff5038
3fffff80: 3fffdad0 3fff316c 3fff4b64 402128df
3fffff90: 3fffdad0 00000000 3fff316c 4020bc20
3fffffa0: 3fffdad0 00000000 3fff5008 402143d4
3fffffb0: feefeffe feefeffe 3ffe8560 40100b01
3ffffdc0: 3fff472c 00000001 3ffec811 40224020
3ffffdd0: 3fff472c 00000019 3fff8acd 0000001b
3ffffde0: 3fff4a5c 0000072a 0000072a 402113c8
3ffffdf0: 3ffec4b5 3fff44d8 3fff8ab4 402117f0
3ffffe00: 3fff8ab4 3fff44d8 3fff472c 4022d20a
3ffffe10: 00000000 40209737 3fff472c 4022d1f8
3ffffe20: 3ffec4b5 3fff44d8 3fff8ab4 00000003
3ffffe30: 00000002 3fff4884 3fff44d8 40246fb8
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<<<

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, "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, "tx_delay <on | off>", "turn on if Tx not working on newer boilers"},
{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"},
#ifdef TESTS
{false, "test <n>", "insert a test telegram on to the EMS bus"},
#endif
{false, "publish", "publish all values to MQTT"},
{false, "refresh", "fetch values from the EMS devices"},
{false, "devices", "list all supported and detected EMS devices and types IDs"},
@@ -338,11 +342,13 @@ void showInfo() {
if (ems_getBusConnected()) {
myDebug_P(PSTR(" Bus is connected"));
myDebug_P(PSTR(" Rx: Poll=%d ms, # Rx telegrams read=%d, # CRC errors=%d"), ems_getPollFrequency(), EMS_Sys_Status.emsRxPgks, EMS_Sys_Status.emxCrcErr);
myDebug_P(PSTR(" Rx: # successful read requests=%d, # CRC errors=%d"), EMS_Sys_Status.emsRxPgks, EMS_Sys_Status.emxCrcErr);
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 {
myDebug_P(PSTR(" Tx: no signal"));
}
@@ -447,13 +453,21 @@ void showInfo() {
if (EMS_Other.SM) {
myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
_renderShortValue(" Collector temperature", "C", EMS_Other.SMcollectorTemp);
_renderShortValue(" Bottom temperature", "C", EMS_Other.SMbottomTemp);
_renderIntValue(" Pump modulation", "%", EMS_Other.SMpumpModulation);
_renderBoolValue(" Pump active", EMS_Other.SMpump);
_renderShortValue(" Energy Last Hour", "Wh", EMS_Other.SMEnergyLastHour, 1); // *10
_renderShortValue(" Energy Today", "Wh", EMS_Other.SMEnergyToday, 0);
_renderShortValue(" Energy Total", "kWH", EMS_Other.SMEnergyTotal, 1); // *10
_renderShortValue("Collector temperature", "C", EMS_Other.SMcollectorTemp);
_renderShortValue("Bottom temperature", "C", EMS_Other.SMbottomTemp);
_renderIntValue("Pump modulation", "%", EMS_Other.SMpumpModulation);
_renderBoolValue("Pump active", EMS_Other.SMpump);
_renderShortValue("Energy Last Hour", "Wh", EMS_Other.SMEnergyLastHour, 1); // *10
_renderShortValue("Energy Today", "Wh", EMS_Other.SMEnergyToday, 0);
_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
@@ -461,50 +475,55 @@ void showInfo() {
myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
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("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // *100
} else if (ems_getThermostatModel() == EMS_MODEL_FR10) {
// Junkers are *10
_renderIntValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 10); // *10
_renderIntValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // *10
} else {
}
else if ((ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100)) {
// Temperatures are *10
_renderShortValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 1); // *10
_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
if (EMS_Thermostat.setpoint_roomTemp <= 0) {
EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_INT_NOTSET;
}
if (EMS_Thermostat.curr_roomTemp <= 0) {
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("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
_renderIntValue("Day temperature", "C", EMS_Thermostat.daytemp, 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
}
// Render Day/Night/Holiday Temperature
if ((EMS_Thermostat.holidaytemp > 0) && (EMSESP_Status.heating_circuit == 2)) { // only if we are on a RC35 we show more info
_renderIntValue("Day temperature", "C", EMS_Thermostat.daytemp, 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"),
EMS_Thermostat.hour,
EMS_Thermostat.minute,
EMS_Thermostat.second,
EMS_Thermostat.day,
EMS_Thermostat.month,
EMS_Thermostat.year + 2000);
// Render Thermostat Date & Time
myDebug_P(PSTR(" Thermostat time is %02d:%02d:%02d %d/%d/%d"),
EMS_Thermostat.hour,
EMS_Thermostat.minute,
EMS_Thermostat.second,
EMS_Thermostat.day,
EMS_Thermostat.month,
EMS_Thermostat.year + 2000);
if (EMS_Thermostat.mode == 0) {
myDebug_P(PSTR(" Mode is set to low"));
} else if (EMS_Thermostat.mode == 1) {
myDebug_P(PSTR(" Mode is set to manual"));
} else if (EMS_Thermostat.mode == 2) {
myDebug_P(PSTR(" Mode is set to auto"));
} else {
myDebug_P(PSTR(" Mode is set to ?"));
}
// Render Termostat Mode
if (EMS_Thermostat.mode == 0) {
myDebug_P(PSTR(" Mode is set to low"));
} else if (EMS_Thermostat.mode == 1) {
myDebug_P(PSTR(" Mode is set to manual"));
} else if (EMS_Thermostat.mode == 2) {
myDebug_P(PSTR(" Mode is set to auto"));
} 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);
// different logic depending on thermostat types
if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) {
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 ((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)
@@ -749,6 +762,7 @@ void publishValues(bool force) {
}
// handle the other values separately
// For SM10 and SM100 Solar Modules
if (EMS_Other.SM) {
// build new json object
@@ -794,6 +808,27 @@ void publishValues(bool force) {
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
@@ -931,6 +966,7 @@ void startDeviceScan() {
regularUpdatesTimer.detach();
publishSensorValuesTimer.detach();
scanDevices_count = 1; // starts at 1
ems_clearDeviceList(); // empty the current list
ems_setLogging(EMS_SYS_LOGGING_NONE);
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);
@@ -975,7 +1011,7 @@ void runUnitTest(uint8_t test_num) {
publishValuesTimer.detach();
systemCheckTimer.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);
}
@@ -1020,9 +1056,6 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
// shower_alert
EMSESP_Status.shower_alert = json["shower_alert"];
// tx delay
ems_setTxDelay(json["tx_delay"]);
// publish_wait
if (!(EMSESP_Status.publish_wait = json["publish_wait"])) {
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["publish_wait"] = EMSESP_Status.publish_wait;
json["heating_circuit"] = EMSESP_Status.heating_circuit;
json["tx_delay"] = ems_getTxDelay();
return true;
}
@@ -1090,7 +1122,7 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
EMSESP_Status.listen_mode = false;
ok = true;
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 {
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) {
@@ -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_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off");
myDebug_P(PSTR(" publish_wait=%d"), EMSESP_Status.publish_wait);
myDebug_P(PSTR(" tx_delay=%s"), ems_getTxDelay() ? "on" : "off");
}
return ok;
@@ -1456,7 +1475,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
if ((hc >= 1) && (hc <= 2)) {
EMSESP_Status.heating_circuit = 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
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 {
emsuart_init();
myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
@@ -1659,11 +1677,7 @@ void setup() {
// set up myESP for Wifi, MQTT, MDNS and Telnet
myESP.setTelnet(project_cmds, ArraySize(project_cmds), TelnetCommandCallback, TelnetCallback); // set up Telnet commands
#ifdef WIFI_SSID
myESP.setWIFI(WIFI_SSID, WIFI_PASSWORD, WIFICallback);
#else
myESP.setWIFI(NULL, NULL, WIFICallback); // pull the wifi settings from the SPIFFS stored settings
#endif
myESP.setWIFI(NULL, NULL, WIFICallback);
// MQTT host, username and password taken from the SPIFFS settings
myESP.setMQTT(

View File

@@ -50,7 +50,6 @@ void _process_UBAMonitorWWMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_UBAParameterWW(_EMS_RxTelegram * EMS_RxTelegram);
void _process_UBATotalUptimeMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_UBAParametersMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_SetPoints(_EMS_RxTelegram * EMS_RxTelegram);
// SM10
@@ -62,6 +61,13 @@ void _process_SM100Status(_EMS_RxTelegram * EMS_RxTelegram);
void _process_SM100Status2(_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
void _process_RCTime(_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_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram);
// Junkers FR10
void _process_FR10StatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
// Junkers FR10 & FW100
void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
/**
* 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_SM100Status2, "SM100Status2", _process_SM100Status2},
{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
{EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
@@ -159,7 +168,6 @@ const _EMS_Type EMS_Types[] = {
// Easy
{EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
{EMS_MODEL_BOSCHEASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
// Nefit 1010, RC300, RC310 (EMS Plus)
{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},
// 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,
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 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 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_POLL_TIMEOUT = 5000000; // timeout in microseconds before recognizing the ems bus is offline (5 seconds)
// 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)
@@ -220,7 +228,7 @@ void ems_init() {
EMS_Sys_Status.emsTxDisabled = false;
EMS_Sys_Status.emsPollFrequency = 0;
EMS_Sys_Status.txRetryCount = 0;
EMS_Sys_Status.emsTxDelay = false;
EMS_Sys_Status.emsReverse = false;
// thermostat
EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET;
@@ -298,6 +306,8 @@ void ems_init() {
EMS_Other.SMEnergyLastHour = EMS_VALUE_SHORT_NOTSET;
EMS_Other.SMEnergyToday = 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
EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off
@@ -314,6 +324,7 @@ void ems_init() {
// set other types
EMS_Other.SM = false;
EMS_Other.HP = false;
// default logging is none
ems_setLogging(EMS_SYS_LOGGING_DEFAULT);
@@ -329,15 +340,6 @@ bool ems_getPoll() {
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() {
return EMS_Sys_Status.emsRefreshed;
}
@@ -533,6 +535,15 @@ void _ems_sendTelegram() {
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
// we don't remove from the queue yet
_EMS_TxTelegram EMS_TxTelegram = EMS_TxQueue.first();
@@ -544,22 +555,24 @@ void _ems_sendTelegram() {
}
// 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) {
_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_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); // always show
emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); // send the telegram to the UART Tx
EMS_TxQueue.shift(); // remove from queue
emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); // send the telegram to the UART Tx
EMS_TxQueue.shift(); // and remove from queue
return;
}
// create header
EMS_TxTelegram.data[0] = EMS_ID_ME; // src
// create the header
EMS_TxTelegram.data[0] = (EMS_Sys_Status.emsReverse) ? EMS_ID_ME | 0x80 : EMS_ID_ME; // src
// dest
if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_WRITE) {
EMS_TxTelegram.data[1] = EMS_TxTelegram.dest;
@@ -651,31 +664,18 @@ void _createValidate() {
/**
* 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
* When a telegram is processed we forcefully erase it from the stack to prevent overflow
*/
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
static _EMS_RxTelegram EMS_RxTelegram;
static uint32_t _last_emsPollFrequency = 0;
EMS_RxTelegram.telegram = telegram;
EMS_RxTelegram.timestamp = millis();
EMS_RxTelegram.length = length;
EMS_RxTelegram.telegram = telegram;
EMS_RxTelegram.timestamp = millis();
EMS_RxTelegram.length = length;
// 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)
@@ -683,12 +683,13 @@ void _ems_readTelegram(uint8_t * telegram, uint8_t length) {
if (length == 1) {
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
if (value == (EMS_ID_ME | 0x80)) {
EMS_Sys_Status.emsTxCapable = true;
// the poll has the MSB set - seems to work on both EMS and Junkers
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?
// 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 {
// nothing to send so just send a poll acknowledgement back
if (EMS_Sys_Status.emsPollEnabled) {
emsaurt_tx_poll();
emsuart_tx_poll();
}
}
} 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) {
EMS_Sys_Status.emsTxPkgs++;
// 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)
} else if (value == EMS_TX_ERROR) {
// last write failed (04), delete it from queue and dont bother to retry
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
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
}
}
@@ -810,6 +811,8 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
strlcpy(output_str, "Thermostat", sizeof(output_str));
} else if (src == EMS_ID_SM) {
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) {
strlcpy(output_str, "Gateway", sizeof(output_str));
} else {
@@ -832,6 +835,9 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
} else if (dest == EMS_ID_SM) {
strlcat(output_str, "SM", sizeof(output_str));
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) {
strlcat(output_str, "Gateway", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
@@ -896,13 +902,6 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
typeFound = true;
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++;
}
@@ -949,7 +948,7 @@ void _removeTxQueue() {
void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
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) {
// _debugPrintTelegram("echo:", EMS_RxTelegram, COLOR_WHITE);
return;
@@ -987,7 +986,8 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
// if WRITE, should not happen
// if VALIDATE, check the contents
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
_removeTxQueue();
EMS_Sys_Status.emsRxPgks++; // increment counter
@@ -1004,7 +1004,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
_removeTxQueue();
} else {
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
*/
void _process_FR10StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->data_length == 6) {
// e.g. 90 00 FF 00 00 6F 03 01 00 BE 00 BF
EMS_Thermostat.curr_roomTemp = _toByte(EMS_OFFSET_FR10StatusMessage_curr); // value is * 10
EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_OFFSET_FR10StatusMessage_setpoint); // value is * 10, which is different from other EMS+ devices
}
void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
// e.g. for FR10: 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 = _toShort(EMS_OFFSET_JunkersStatusMessage_curr); // value is * 10
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
* 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) {
// 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.SMEnergyToday = _toShort(6); // todays in Wh
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
}
/*
* 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
*/
@@ -1456,7 +1484,7 @@ void _process_SetPoints(_EMS_RxTelegram * EMS_RxTelegram) {
* common for all thermostats
*/
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
}
@@ -1468,6 +1496,13 @@ void _process_RCTime(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Thermostat.year = _toByte(0);
}
/*
* Clear devices list
*/
void ems_clearDeviceList() {
Devices.clear();
}
/*
* add an EMS device to our list of detected devices
*/
@@ -1501,15 +1536,16 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
return;
}
uint8_t product_id = _toByte(0);
char version[10] = {0};
uint8_t product_id = _toByte(0);
char version[10] = {0};
snprintf(version, sizeof(version), "%02d.%02d", _toByte(1), _toByte(2));
// see if its a known boiler
int i = 0;
bool typeFound = false;
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.
break;
}
@@ -1518,28 +1554,29 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
if (typeFound) {
// its a boiler
myDebug_P(PSTR("Boiler found. Model %s (DeviceID:0x%02X ProductID:%d Version:%s)"),
Boiler_Types[i].model_string,
Boiler_Types[i].device_id,
product_id,
version);
myDebug_P(PSTR("Boiler found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Boiler_Types[i].model_string, EMS_ID_BOILER, product_id, version);
// 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
// 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)"),
Boiler_Types[i].model_string,
Boiler_Types[i].device_id,
EMS_ID_BOILER,
product_id,
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;
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
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) {
// its a known thermostat
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].device_id,
product_id,
@@ -1568,13 +1605,13 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
}
// 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 (((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.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].device_id,
product_id,
@@ -1605,11 +1642,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
}
if (typeFound) {
myDebug_P(PSTR("Device found. Model %s with DeviceID 0x%02X, ProductID %d, Version %s"),
Other_Types[i].model_string,
Other_Types[i].device_id,
product_id,
version);
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);
// add to list
_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."));
}
// 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
ems_getOtherValues();
return;
} 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
_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
*/
void ems_discoverModels() {
myDebug_P(PSTR("Starting auto discover of EMS devices..."));
// boiler
ems_doReadCommand(EMS_TYPE_Version, EMS_Boiler.device_id); // get version details of boiler
// 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
// 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 (EMS_Thermostat.device_id == EMS_ID_NONE) {
ems_scanDevices(); // auto-discover it
@@ -1678,6 +1731,8 @@ void ems_printTxQueue() {
strlcpy(sType, "read", sizeof(sType));
} else if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_VALIDATE) {
strlcpy(sType, "validate", sizeof(sType));
} else if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_RAW) {
strlcpy(sType, "raw", sizeof(sType));
} else {
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_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);
}
@@ -1756,7 +1811,7 @@ void ems_getBoilerValues() {
*/
void ems_getOtherValues() {
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
// copy over boilers
for (_Boiler_Type bt : Boiler_Types) {
Device_Ids.push_back(bt.device_id);
}
// add boiler device_id which is always 0x08
Device_Ids.push_back(EMS_ID_BOILER);
// copy over thermostats
for (_Thermostat_Type tt : Thermostat_Types) {
@@ -1875,6 +1928,9 @@ void ems_scanDevices() {
for (uint8_t device_id : Device_Ids) {
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,
Boiler_Types[i].model_string,
COLOR_BOLD_OFF,
Boiler_Types[i].device_id,
EMS_ID_BOILER,
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 (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;
}
@@ -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.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.length = EMS_MIN_TELEGRAM_LENGTH; // is always 6 bytes long (including CRC at end)
EMS_TxTelegram.type = type;
@@ -2309,7 +2367,6 @@ void ems_setWarmTapWaterActivated(bool activated) {
EMS_TxTelegram.dest = EMS_Boiler.device_id;
EMS_TxTelegram.type = EMS_TYPE_UBAFunctionTest;
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.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
// 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) {
// on
EMS_TxTelegram.data[4] = 0x5A; // test mode on
EMS_TxTelegram.data[5] = 0x00; // burner output 0%
EMS_TxTelegram.data[7] = 0x64; // boiler pump capacity 100%
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
@@ -2374,7 +2437,7 @@ void ems_testTelegram(uint8_t test_num) {
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];
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]);
// go an parse it
_ems_readTelegram(telegram, length + 1); // include CRC in length
ems_parseTelegram(telegram, length + 1); // include CRC in length
#else
myDebug_P(PSTR("Firmware not compiled with test data set"));
#endif

View File

@@ -12,14 +12,17 @@
#include <Arduino.h>
// EMS 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
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key"
#define EMS_ID_DEFAULT_BOILER 0x08
#define EMS_ID_NONE 0x00 // used as a dest in broadcast messages and empty device IDs
// Fixed EMS IDs
#define EMS_ID_ME 0x0B // our device, hardcoded as the "Service Key"
#define EMS_ID_BOILER 0x08 // all UBA Boilers have 0x08
#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_PRODUCTID_HEATRONICS 95 // ProductID for a Junkers Heatronic3 device
#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.
@@ -44,7 +47,7 @@
#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
/* EMS UART transfer status */
@@ -94,7 +97,7 @@ typedef struct {
bool emsTxCapable; // able to send via Tx
bool emsTxDisabled; // true to prevent all Tx
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;
// The Tx send package
@@ -148,7 +151,6 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
typedef struct {
uint8_t model_id;
uint8_t product_id;
uint8_t device_id;
char model_string[50];
} _Boiler_Type;
@@ -241,7 +243,8 @@ typedef struct { // UBAParameterWW
// SM Solar Module - SM10Monitor/SM100Monitor
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 SMbottomTemp; // bottom temp
uint8_t SMpumpModulation; // modulation solar pump
@@ -249,6 +252,8 @@ typedef struct {
int16_t SMEnergyLastHour;
int16_t SMEnergyToday;
int16_t SMEnergyTotal;
uint8_t HPModulation; // heatpump modulation in %
uint8_t HPSpeed; // speed 0-100 %
} _EMS_Other;
// Thermostat data
@@ -292,6 +297,14 @@ extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
void ems_init();
void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh = false);
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_setThermostatMode(uint8_t mode);
@@ -301,14 +314,14 @@ void ems_setFlowTemp(uint8_t temperature);
void ems_setWarmWaterActivated(bool activated);
void ems_setWarmTapWaterActivated(bool activated);
void ems_setPoll(bool b);
void ems_setTxDelay(bool b);
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
void ems_setEmsRefreshed(bool b);
void ems_setWarmWaterModeComfort(uint8_t comfort);
bool ems_checkEMSBUSAlive();
void ems_setModels();
void ems_setTxDisabled(bool b);
char * ems_getThermostatDescription(char * buffer);
char * ems_getBoilerDescription(char * buffer);
void ems_getThermostatValues();
void ems_getBoilerValues();
void ems_getOtherValues();
@@ -316,7 +329,6 @@ bool ems_getPoll();
bool ems_getTxEnabled();
bool ems_getThermostatEnabled();
bool ems_getBoilerEnabled();
bool ems_getTxDelay();
bool ems_getBusConnected();
_EMS_SYS_LOGGING ems_getLogging();
bool ems_getEmsRefreshed();
@@ -325,17 +337,6 @@ void ems_discoverModels();
bool ems_getTxCapable();
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
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
void _processType(_EMS_RxTelegram * EMS_RxTelegram);
@@ -344,7 +345,6 @@ void _ems_clearTxData();
int _ems_findBoilerModel(uint8_t model_id);
bool _ems_setModel(uint8_t model_id);
void _removeTxQueue();
void _ems_readTelegram(uint8_t * telegram, uint8_t length);
// global so can referenced in other classes
extern _EMS_Sys_Status EMS_Sys_Status;

View File

@@ -41,11 +41,14 @@
#define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp
// Other
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
#define EMS_TYPE_SM100Status 0x0264 // SM100Status
#define EMS_TYPE_SM100Status2 0x026A // SM100Status2
#define EMS_TYPE_SM100Energy 0x028E // SM100Energy
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
#define EMS_TYPE_SM100Status 0x0264 // SM100Status
#define EMS_TYPE_SM100Status2 0x026A // SM100Status2
#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...
@@ -107,10 +110,10 @@
#define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp
#define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode
// Junkers FR10 (EMS Plus)
#define EMS_TYPE_FR10StatusMessage 0x6F // is an automatic thermostat broadcast giving us temps
#define EMS_OFFSET_FR10StatusMessage_setpoint 3 // setpoint temp
#define EMS_OFFSET_FR10StatusMessage_curr 5 // current temp
// Junkers FR10, FW100 (EMS Plus)
#define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps
#define EMS_OFFSET_JunkersStatusMessage_setpoint 2 // setpoint temp
#define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp
// Known EMS types
@@ -132,38 +135,40 @@ typedef enum {
EMS_MODEL_RC30,
EMS_MODEL_RC35,
EMS_MODEL_EASY,
EMS_MODEL_BOSCHEASY,
EMS_MODEL_RC310,
EMS_MODEL_CW100,
EMS_MODEL_1010,
EMS_MODEL_OT,
EMS_MODEL_FW100,
EMS_MODEL_FR10,
EMS_MODEL_FR100,
EMS_MODEL_FR110
EMS_MODEL_FR110,
EMS_MODEL_FW120
} _EMS_MODEL_ID;
// EMS types for known Buderus/Bosch devices. This list will be extended when new devices are recognized.
// format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION
// EMS types for known devices. This list will be extended when new devices are recognized.
// The device_id is always 0x08
// format is MODEL_ID, PRODUCT ID, DESCRIPTION
const _Boiler_Type Boiler_Types[] = {
{EMS_MODEL_UBA, 72, 0x08, "MC10 Module"},
{EMS_MODEL_UBA, 123, 0x08, "Buderus GB172/Nefit Trendline"},
{EMS_MODEL_UBA, 115, 0x08, "Nefit Topline Compact/Buderus GB162"},
{EMS_MODEL_UBA, 203, 0x08, "Buderus Logamax U122"},
{EMS_MODEL_UBA, 208, 0x08, "Buderus Logamax plus/GB192"},
{EMS_MODEL_UBA, 64, 0x08, "Sieger BK15 Boiler/Nefit Smartline"},
{EMS_MODEL_UBA, 95, 0x08, "Bosch Condens 2500/Junkers Heatronics3"},
{EMS_MODEL_UBA, 122, 0x08, "Nefit Proline"},
{EMS_MODEL_UBA, 172, 0x08, "Nefit Enviline"}
{EMS_MODEL_UBA, 72, "MC10 Module"},
{EMS_MODEL_UBA, 123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"},
{EMS_MODEL_UBA, 115, "Nefit Topline Compact/Buderus GB162"},
{EMS_MODEL_UBA, 203, "Buderus Logamax U122/Junkers Cerapur"},
{EMS_MODEL_UBA, 208, "Buderus Logamax plus/GB192"},
{EMS_MODEL_UBA, 64, "Sieger BK15/Nefit Smartline/Buderus GB152"},
{EMS_MODEL_UBA, EMS_PRODUCTID_HEATRONICS, "Bosch Condens 2500/Junkers Heatronics3"}, // Junkers
{EMS_MODEL_UBA, 122, "Nefit Proline"},
{EMS_MODEL_UBA, 172, "Nefit Enviline"}
};
// Other EMS devices which are not considered boilers or thermostats
const _Other_Type Other_Types[] = {
{EMS_MODEL_OTHER, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id!
{EMS_MODEL_OTHER, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id!
{EMS_MODEL_OTHER, 69, 0x21, "MM10 Mixer Module"},
{EMS_MODEL_OTHER, 71, 0x11, "WM10 Switch Module"},
{EMS_MODEL_OTHER, 160, 0x20, "MM100 Mixing Module"},
{EMS_MODEL_OTHER, 160, 0x21, "MM100 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, 114, 0x09, "BC10 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, 73, EMS_ID_SM, "SM10 Solar Module"},
{EMS_MODEL_OTHER, 163, EMS_ID_SM, "SM100 Solar Module"},
{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"}
};
@@ -186,7 +194,8 @@ const _Thermostat_Type Thermostat_Types[] = {
// Easy devices - not currently supporting write operations
{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},
// Buderus/Nefit
@@ -202,9 +211,11 @@ const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_ES73, 76, 0x10, "Sieger ES73", EMS_THERMOSTAT_WRITE_YES},
// 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_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 "ems.h"
#include "ets_sys.h"
#include "osapi.h"
#include <Arduino.h>
#include <user_interface.h>
@@ -34,34 +31,28 @@ static void emsuart_rx_intr_handler(void * para) {
}
// 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) {
uart_buffer[length++] = USF(EMSUART_UART);
}
// clear Rx FIFO full and Rx FIFO timeout interrupts
U0IC = (1 << UIFF);
U0IC = (1 << UITO);
USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
}
// BREAK detection = End of EMS data block
if (USIS(EMSUART_UART) & ((1 << UIBD))) {
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->writePtr = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length);
pEMSRxBuf->length = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end
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
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE;
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
// call emsuart_recvTask() at next opportunity
system_os_post(EMSUART_recvTaskPrio, 0, 0);
// re-enable UART interrupts
ETS_UART_INTR_ENABLE();
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
}
}
@@ -72,8 +63,30 @@ static void emsuart_rx_intr_handler(void * para) {
*/
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
_EMSRxBuf * pCurrent = pEMSRxBuf;
ems_parseTelegram((uint8_t *)pCurrent->buffer, (pCurrent->writePtr) - 1); // transmit EMS buffer, excluding the BRK
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
// 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);
USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1
// flush everything left over in buffer, this clears both rx and tx FIFOs
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
emsuart_flush_fifos();
// conf1 params
// UCTOE = RX TimeOut enable (default is 1)
// UCTOT = RX TimeOut Threshold (7bit) = 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).
// 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)
// 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
// 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
// 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);
// set up interrupt callbacks for Rx
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
// disable esp debug which will go to Tx and mess up the line
system_set_os_print(0); // https://github.com/espruino/Espruino/issues/655
// 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);
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
system_uart_swap();
@@ -136,13 +146,10 @@ void ICACHE_FLASH_ATTR emsuart_init() {
/*
* 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() {
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
* Which is a 11-bit set of zero's (11 cycles)
* set loopback mode and clear Tx/Rx FIFO
*/
void ICACHE_FLASH_ATTR emsuart_tx_brk() {
// must make sure Tx FIFO is empty
while (((USS(EMSUART_UART) >> USTXC) & 0xff) != 0)
;
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
static inline void ICACHE_FLASH_ATTR emsuart_loopback(bool enable) {
if (enable)
USC0(EMSUART_UART) |= (1 << UCLBE); // enable loopback
else
USC0(EMSUART_UART) &= ~(1 << UCLBE); // disable loopback
}
/*
* Send to Tx, ending with a <BRK>
*/
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
for (uint8_t i = 0; i < len; i++) {
USF(EMSUART_UART) = buf[i];
if (len == 0)
return;
// check if we need to force a delay to slow down Tx
// https://github.com/proddy/EMS-ESP/issues/23#
if (EMS_Sys_Status.emsTxDelay) {
delayMicroseconds(EMS_TX_BRK_WAIT);
/*
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
* 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>
*/
void ICACHE_FLASH_ATTR emsaurt_tx_poll() {
USF(EMSUART_UART) = EMS_ID_ME;
emsuart_tx_brk(); // send <BRK>
void ICACHE_FLASH_ATTR emsuart_tx_poll() {
static uint8_t buf[1];
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_BAUD 9600 // uart baud rate for the EMS circuit
#define EMS_MAXBUFFERS 10 // 4 buffers for circular filling to avoid collisions
#define EMS_MAXBUFFERSIZE 32 // max size of the buffer. packets are max 32 bytes
#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 to support EMS 1.0
// this is how long we drop the Tx signal to create a 11-bit Break of zeros (BRK)
// 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_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_recvTaskPrio 1
#define EMSUART_recvTaskQueueLen 64
typedef struct {
uint8_t writePtr;
uint8_t length;
uint8_t buffer[EMS_MAXBUFFERSIZE];
} _EMSRxBuf;
@@ -33,5 +30,4 @@ void ICACHE_FLASH_ATTR emsuart_init();
void ICACHE_FLASH_ATTR emsuart_stop();
void ICACHE_FLASH_ATTR emsuart_start();
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_brk();
void ICACHE_FLASH_ATTR emsuart_tx_poll();

View File

@@ -58,6 +58,11 @@
#define SM_ENERGYTODAY "energytoday" // energy today
#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
#define TOPIC_SHOWERTIME "showertime" // for sending shower time results
#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
"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
"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
#define APP_NAME "EMS-ESP"
#define APP_VERSION "1.7.1"
#define APP_VERSION "1.8.0"
#define APP_HOSTNAME "ems-esp"