1.8.0
3
.gitignore
vendored
@@ -6,5 +6,4 @@
|
||||
platformio.ini
|
||||
lib/readme.txt
|
||||
.travis.yml
|
||||
;stackdmp.txt
|
||||
;*.jar
|
||||
scripts/stackdmp.txt
|
||||
|
||||
10
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
22
README.md
@@ -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:**
|
||||
|
||||

|
||||
|
||||
**Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) circuit:**
|
||||
|
||||
|  |  |  |
|
||||
| - | - | - |
|
||||
|
||||
**Example of the EMS-ESP's telnet console:**
|
||||
|
||||
|  |  |
|
||||
| - | - |
|
||||
|
||||
BIN
doc/ems gateway/ems-board-white.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
doc/ems gateway/ems-kit-2.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
doc/ems gateway/on-boiler.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 189 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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'])
|
||||
@@ -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<<<
|
||||
162
src/ems-esp.cpp
@@ -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(
|
||||
|
||||
287
src/ems.cpp
@@ -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
|
||||
|
||||
48
src/ems.h
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
};
|
||||
|
||||
159
src/emsuart.cpp
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||