1.9.5 - Merge remote-tracking branch 'origin/dev'

This commit is contained in:
Paul
2020-04-30 10:47:55 +02:00
27 changed files with 3863 additions and 2067 deletions

View File

@@ -37,3 +37,5 @@ SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true SpaceBeforeAssignmentOperators: true
SpacesInCStyleCastParentheses: false SpacesInCStyleCastParentheses: false
SpacesInParentheses: false SpacesInParentheses: false
DerivePointerAlignment: false
SortIncludes: false

3
.gitignore vendored
View File

@@ -18,3 +18,6 @@ node_modules
# project specfic # project specfic
scripts/stackdmp.txt scripts/stackdmp.txt
firmware firmware
# firmware
*.bin

View File

@@ -1,7 +1,8 @@
os: linux os: linux
dist: bionic
language: python language: python
python: python:
- "2.7" - "3.8"
cache: cache:
directories: directories:
@@ -55,6 +56,7 @@ before_deploy:
deploy: deploy:
provider: releases provider: releases
edge: edge:
# source: wenkokke/dpl
branch: master branch: master
token: ${GITHUB_TOKEN} token: ${GITHUB_TOKEN}
file_glob: true file_glob: true

View File

@@ -5,7 +5,46 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.9.4] 2019-12-15 ## [1.9.5] 30-04-2020
### Added
- Solar Module SM200 support
- Support writing to Junkers FR100 thermostats
- Support writing to RC100, Moduline 1000/1010 thermostats
- MM10 Mixing module support (thanks @MichaelDvP)
- MM200 warm water circuits (https://github.com/proddy/EMS-ESP/pull/315)
- Support for Moduline 200 and Sieger ES72 thermostats
- First implementation of writing to generic Junker Thermostats (thanks @Neonox31)
- Added model type (Buderus, Sieger, Junkers, Nefit, Bosch, Worcester) to device names
- `set master_thermostat <product id>` to choose with thermostat is master when there are multiple on the bus
- `boiler wwonetime` command from Telnet
- `set bus_id <ID>` to support multiple EMS-ESP circuits. Default is 0x0B to mimic a service key.
- `mqtt_nestedjson` option to disable multiple data records being nested into a single JSON string
- MQTT publish messages are queued and gracefully published every second to avoid TCP blocks
- Added features to WW messages (0x33, 0x34) to improve WW monitoring. (PR#338 by @ypaindaveine)
- Added mixing log and stub for EMS type 0xAC (PR#338 by @ypaindaveine)
- Added Thermostat retrieving settings (0xA5) (validated on RC30N) with MQTT support (thanks Yves @ypaindaveine. See #352)
- Merged with PR https://github.com/proddy/EMS-ESP/pull/366 from @MichaelDvP fixing RC20 and MM50
### Fixed
- set boiler warm water temp on Junkers/Bosch HT3
- fixed detection of the Moduline 400 thermostat
- RC35 setting temperature also forces the current select temp to change, irrespective of the mode
### Changed
- improved MQTT publishing to stop network flooding. `publish_time` of -1 is no publish, 0 is automatic otherwise its a time interval
- External sensors (like Dallas DS18*) are sent as a nested MQTT topic including their unqiue identifier
- `mqttlog` console command renamed to `mqttqueue` to only show the current publish queue
- `status` payload on start-up shows the IP and Version of EMS-ESP
- `thermostat mode` takes a string like manual,auto,heat,day,night,eco,comfort,holiday,nofrost
- `thermostat temp` also takes a mode string, e.g. `thermostat temp 20 heat`
- `queue` renamed to `txqueue`
### Removed
- `autodetect scan`. Replaced with `devices scan` and `devices scan+` for deep scanning
- `mqttlog all` and showing MQTT log in the web interface - no point showing history of previous mqtt publishes in ESP's precious memory. For debugging I recommend using MQTT Explorer or another external tool.
## [1.9.4] 15-12-2019
There are breaking changes in this release. Make you sure you adjust the MQTT topics as described in the wiki. There are breaking changes in this release. Make you sure you adjust the MQTT topics as described in the wiki.

View File

@@ -15,7 +15,7 @@ EMS-ESP is a open-source system built for the Espressif ESP8266 microcontroller
## Features ## Features
* Supporting more than [50 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (EMS 1, EMS+/2.0 and Heatronics 3). * Supporting more than [50 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (EMS 1, EMS 2.0/Plus and Heatronics 3).
* A web interface for easy configuration and real-time monitoring of the EMS bus. * A web interface for easy configuration and real-time monitoring of the EMS bus.
* Telnet for advanced configuration and verbose traffic logging. * Telnet for advanced configuration and verbose traffic logging.
* Configurable MQTT, with templates for Home Assistant and Domoticz. * Configurable MQTT, with templates for Home Assistant and Domoticz.

View File

@@ -4,16 +4,23 @@
[platformio] [platformio]
default_envs = release default_envs = release
;default_envs = debug ; default_envs = debug
[common] [common]
# From https://github.com/esp8266/Arduino/blob/master/tools/sdk/ld
# eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer
# eagle.flash.4m2m.ld = same as above but with 2024 KB SPIFFS
# eagle.flash.4m.ld = same as above but with no SPIFFS storage
ldscript = eagle.flash.4m1m.ld
; custom build options are: ; custom build options are:
; -DMYESP_TIMESTAMP ; -DMYESP_TIMESTAMP
; -DTESTS ; -DTESTS
; -DCRASH ; -DCRASH
; -DFORCE_SERIAL ; -DFORCE_SERIAL
; -DMYESP_DEBUG ; -DMYESP_DEBUG
;custom_flags = -DFORCE_SERIAL -DMYESP_DEBUG ; -DSENSOR_MQTT_USEID
; custom_flags = -DFORCE_SERIAL -DMYESP_DEBUG -DEMSESP_SIMULATE
custom_flags = custom_flags =
# Available lwIP variants (macros): # Available lwIP variants (macros):
@@ -26,33 +33,28 @@ custom_flags =
# -DVTABLES_IN_FLASH # -DVTABLES_IN_FLASH
# -DNO_GLOBAL_EEPROM # -DNO_GLOBAL_EEPROM
# -DBEARSSL_SSL_BASIC # -DBEARSSL_SSL_BASIC
# general_flags = -DNO_GLOBAL_EEPROM -DVTABLES_IN_FLASH -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
general_flags = -DNO_GLOBAL_EEPROM general_flags = -DNO_GLOBAL_EEPROM
# From https://github.com/esp8266/Arduino/blob/master/tools/sdk/ld build_flags = ${common.general_flags} -std=c++11 -fno-exceptions
# eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer
# eagle.flash.4m2m.ld = same as above but with 2024 KB SPIFFS
# eagle.flash.4m.ld = same as above but with no SPIFFS storage
build_flags_4m1m = -Wl,-Teagle.flash.4m1m.ld
build_flags = ${common.general_flags} ${common.build_flags_4m1m}
[env] [env]
framework = arduino framework = arduino
;platform = espressif8266@2.2.2 ; arduino core 2.5.2
platform = espressif8266 platform = espressif8266
;platform = https://github.com/platformio/platform-espressif8266#develop board_build.ldscript = ${common.ldscript}
;platform = https://github.com/platformio/platform-espressif8266#feature/stage lib_compat_mode = strict
lib_deps = lib_deps =
https://github.com/rlogiacco/CircularBuffer https://github.com/rlogiacco/CircularBuffer
https://github.com/PaulStoffregen/OneWire https://github.com/PaulStoffregen/OneWire
https://github.com/me-no-dev/ESPAsyncWebServer https://github.com/me-no-dev/ESPAsyncWebServer
https://github.com/me-no-dev/ESPAsyncUDP https://github.com/me-no-dev/ESPAsyncUDP
uuid-common@^1.1.0 uuid-common@^1.1.0
uuid-log@^2.1.1 uuid-log@^2.1.1
uuid-syslog@^2.0.4 ; https://github.com/nomis/mcu-uuid-syslog uuid-syslog@^2.0.4 ; https://github.com/nomis/mcu-uuid-syslog
JustWifi@2.0.2 ; https://github.com/xoseperez/justwifi JustWifi@2.0.2 ; https://github.com/xoseperez/justwifi
AsyncMqttClient@0.8.2 ; https://github.com/marvinroger/async-mqtt-client AsyncMqttClient@0.8.2 ; https://github.com/marvinroger/async-mqtt-client
EEPROM_Rotate@0.9.2 ; https://github.com/xoseperez/eeprom_rotate EEPROM_Rotate@0.9.2 ; https://github.com/xoseperez/eeprom_rotate
ArduinoJson@6.13.0 ; https://github.com/bblanchon/ArduinoJson ArduinoJson
ESPAsyncTCP@1.2.2 ; https://github.com/me-no-dev/ESPAsyncTCP ESPAsyncTCP@1.2.2 ; https://github.com/me-no-dev/ESPAsyncTCP
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
@@ -112,3 +114,4 @@ build_flags = ${common.build_flags} ${common.custom_flags}
extra_scripts = extra_scripts =
pre:scripts/pre_script.py pre:scripts/pre_script.py
scripts/main_script.py scripts/main_script.py

View File

@@ -25,7 +25,7 @@ def clr(color, text):
def remove_float_support(): def remove_float_support():
flags = " ".join(env['LINKFLAGS']) flags = " ".join(env['LINKFLAGS'])
print(clr(Color.BLUE, "** LINKFLAGS = %ss" % flags)) print(clr(Color.BLUE, "LINKFLAGS = %ss" % flags))
flags = flags.replace("-u _printf_float", "") flags = flags.replace("-u _printf_float", "")
flags = flags.replace("-u _scanf_float", "") flags = flags.replace("-u _scanf_float", "")
newflags = flags.split() newflags = flags.split()

View File

@@ -7,7 +7,7 @@ Import("env")
def build_web(): def build_web():
print("** Building web...") print("** Building web...")
env.Execute( env.Execute(
"node ./tools/webfilesbuilder/node_modules/gulp/bin/gulp.js --cwd ./tools/webfilesbuilder") "node ./tools/webfilesbuilder/node_modules/gulp/bin/gulp.js --silent --cwd ./tools/webfilesbuilder")
def code_check(source, target, env): def code_check(source, target, env):
print("** Starting cppcheck...") print("** Starting cppcheck...")

View File

@@ -23,6 +23,15 @@ union system_rtcmem_t {
uint32_t value; uint32_t value;
}; };
struct mqtt_message_t {
uint16_t packetId = 0;
char * topic = nullptr;
char * payload = nullptr;
bool retain = false;
uint8_t retry_count = 0;
};
std::deque<mqtt_message_t> _mqtt_queue;
// nasty global variables that are called from internal ws functions // nasty global variables that are called from internal ws functions
static char * _general_password = nullptr; static char * _general_password = nullptr;
static bool _shouldRestart = false; static bool _shouldRestart = false;
@@ -74,10 +83,12 @@ MyESP::MyESP() {
_mqtt_heartbeat = false; _mqtt_heartbeat = false;
_mqtt_keepalive = MQTT_KEEPALIVE; _mqtt_keepalive = MQTT_KEEPALIVE;
_mqtt_qos = MQTT_QOS; _mqtt_qos = MQTT_QOS;
_mqtt_nestedjson = false;
_mqtt_retain = MQTT_RETAIN; _mqtt_retain = MQTT_RETAIN;
_mqtt_will_topic = strdup(MQTT_WILL_TOPIC); _mqtt_will_topic = strdup(MQTT_WILL_TOPIC);
_mqtt_will_online_payload = strdup(MQTT_WILL_ONLINE_PAYLOAD); _mqtt_will_online_payload = strdup(MQTT_WILL_ONLINE_PAYLOAD);
_mqtt_will_offline_payload = strdup(MQTT_WILL_OFFLINE_PAYLOAD); _mqtt_will_offline_payload = strdup(MQTT_WILL_OFFLINE_PAYLOAD);
_mqtt_publish_fails = 0; // count of number of failed MQTT topic publishes
// network // network
_network_password = nullptr; _network_password = nullptr;
@@ -110,14 +121,6 @@ MyESP::MyESP() {
// get the build time // get the build time
_buildTime = _getBuildTime(); _buildTime = _getBuildTime();
// MQTT log
for (uint8_t i = 0; i < MYESP_MQTTLOG_MAX; i++) {
MQTT_log[i].type = 0;
MQTT_log[i].timestamp = 0;
MQTT_log[i].topic = nullptr;
MQTT_log[i].payload = nullptr;
}
} }
MyESP::~MyESP() { MyESP::~MyESP() {
@@ -382,21 +385,17 @@ bool MyESP::mqttSubscribe(const char * topic) {
if (mqttClient.connected() && (strlen(topic) > 0)) { if (mqttClient.connected() && (strlen(topic) > 0)) {
char * topic_s = _mqttTopic(topic); char * topic_s = _mqttTopic(topic);
uint16_t packet_id = mqttClient.subscribe(topic_s, _mqtt_qos);
#ifdef MYESP_DEBUG #ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Subscribing to %s"), topic_s); myDebug_P(PSTR("[MQTT] Subscribing to %s"), topic_s);
#endif #endif
uint16_t packet_id = mqttClient.subscribe(topic_s, _mqtt_qos);
if (packet_id) { if (!packet_id) {
// add to mqtt log
_addMQTTLog(topic_s, "", MYESP_MQTTLOGTYPE_SUBSCRIBE); // Has an empty payload for now
return true;
} else {
myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id); myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id);
return false;
} }
} }
return false; // didn't work return true;
} }
// MQTT unsubscribe // MQTT unsubscribe
@@ -407,30 +406,218 @@ void MyESP::mqttUnsubscribe(const char * topic) {
} }
} }
// Publish using the user's custom retain flag // print MQTT log
bool MyESP::mqttPublish(const char * topic, const char * payload) { void MyESP::_printMQTTQueue() {
// use the custom MQTT retain flag myDebug_P(PSTR("MQTT publish queue:"));
return mqttPublish(topic, payload, _mqtt_retain);
}
// MQTT Publish if (_mqtt_queue.empty()) {
// returns true if all good myDebug_P(PSTR(" queue is empty!"));
bool MyESP::mqttPublish(const char * topic, const char * payload, bool retain) { myDebug_P(PSTR("")); // newline
if (mqttClient.connected() && (strlen(topic) > 0)) { return;
#ifdef MYESP_DEBUG }
myDebug_P(PSTR("[MQTT] Sending publish to %s with payload %s"), _mqttTopic(topic), payload);
#endif
uint16_t packet_id = mqttClient.publish(_mqttTopic(topic), _mqtt_qos, retain, payload);
if (packet_id) { for (mqtt_message_t it : _mqtt_queue) {
_addMQTTLog(topic, payload, MYESP_MQTTLOGTYPE_PUBLISH); // add to the log if (it.retry_count == 0) {
return true; if (it.packetId == 0) {
myDebug_P(PSTR(" topic=%s payload=%s"), it.topic, it.payload);
} else {
myDebug_P(PSTR(" topic=%s payload=%s (pid %d)"), it.topic, it.payload, it.packetId);
}
} else { } else {
myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s [error %d]"), _mqttTopic(topic), payload, packet_id); myDebug_P(PSTR(" topic=%s payload=%s (pid %d, retry #%d)"), it.topic, it.payload, it.packetId, it.retry_count);
} }
} }
return false; // failed myDebug_P(PSTR("")); // newline
}
// Publish using the user's custom retain flag
bool MyESP::mqttPublish(const char * topic, const char * payload) {
return (_mqttQueue(topic, payload, _mqtt_retain));
}
bool MyESP::mqttPublish(const char * topic, JsonDocument & payload) {
return (_mqttQueue(topic, payload, _mqtt_retain));
}
// MQTT Publish
bool MyESP::mqttPublish(const char * topic, const char * payload, bool retain) {
return (_mqttQueue(topic, payload, retain));
}
bool MyESP::mqttPublish(const char * topic, JsonDocument & payload, bool retain) {
return (_mqttQueue(topic, payload, retain));
}
// put a payload string into the queue
// can't have empty topic
// returns false if can't add to queue
bool MyESP::_mqttQueue(const char * topic, const char * payload, bool retain) {
if (!mqttClient.connected() || _mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE || !_hasValue(topic)) {
return false;
}
// create a new message
mqtt_message_t element;
element.topic = strdup(topic);
element.retain = retain;
element.packetId = 0;
element.retry_count = 0;
if (payload != NULL) {
element.payload = strdup(payload);
}
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Adding to queue: #%d [%s] %s"), _mqtt_queue.size(), element.topic, element.payload);
#endif
_mqtt_queue.push_back(element);
return true;
}
// convert json doc to a string buffer and place on queue
// can't have empty payload or topic
// returns false if can't add to queue
bool MyESP::_mqttQueue(const char * topic, JsonDocument & payload, bool retain) {
if (!mqttClient.connected() || _mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE || !_hasValue(topic)) {
return false;
}
// check for empty JSON doc - we don't like those
size_t capacity = measureJson(payload);
if (!capacity) {
return false;
}
// create a new message
mqtt_message_t element;
element.topic = strdup(topic);
element.retain = retain;
element.packetId = 0;
element.retry_count = 0;
// reserve space for buffer and serialize json into it
capacity++; // add one more to cover the EOL
element.payload = (char *)malloc(capacity);
serializeJson(payload, (char *)element.payload, capacity);
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Adding to queue: #%d [%s] %s"), _mqtt_queue.size(), element.topic, element.payload);
#endif
_mqtt_queue.push_back(element);
return true;
}
// called when an MQTT Publish ACK is received
// check if ACK matches the last Publish we sent, if not report an error
// and always remove from queue
void MyESP::_mqttOnPublish(uint16_t packetId) {
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Publish ACK for PID %d"), packetId);
#endif
// find the MQTT message in the queue and remove it
if ((_mqtt_queue.empty()) || (_mqtt_qos == 0)) {
return;
}
mqtt_message_t element = _mqtt_queue.front(); // get top of list
// if the last published failed, don't bother checking it. wait for the re-try
if (element.packetId == 0) {
return;
}
if (element.packetId == packetId) {
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Found PID %d. Removing from queue."), packetId);
#endif
} else {
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Mismatch, expecting PID %d, got %d."), element.packetId, packetId);
_mqtt_publish_fails++; // increment error count
#endif
}
_mqttRemoveLastPublish(); // always remove
}
// removes top of queue
void MyESP::_mqttRemoveLastPublish() {
mqtt_message_t element = _mqtt_queue.front(); // get top of list
free(element.topic);
if (element.payload) {
free(element.payload);
}
_mqtt_queue.pop_front();
}
// take top from queue and try and publish it
void MyESP::_mqttPublishQueue() {
if ((!mqttClient.connected()) || (_mqtt_queue.empty())) {
return;
}
mqtt_message_t element = _mqtt_queue.front(); // fetch from queue
// try and publish it
uint16_t packet_id = mqttClient.publish(_mqttTopic(element.topic), _mqtt_qos, element.retain, element.payload);
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Sent publish (attempt #%d, pid %d) [%s] [%s]"), element.retry_count, packet_id, _mqttTopic(element.topic), element.payload);
#endif
if (packet_id == 0) {
// it failed. if we retried n times, give up. remove from queue
if (element.retry_count == (MQTT_PUBLISH_MAX_RETRY - 1)) {
myDebug_P(PSTR("[MQTT] Failed to publish to %s with payload %s"), _mqttTopic(element.topic), element.payload);
_mqtt_publish_fails++; // increment failure counter
_mqttRemoveLastPublish();
} else {
_mqtt_queue[0].retry_count++;
}
return;
}
// if we have ACK set with QOS 1 or 2, leave on queue and let the ACK process remove it
// but add the packet_id so we can check it later
if (_mqtt_qos != 0) {
_mqtt_queue[0].packetId = packet_id;
#ifdef MYESP_DEBUG
myDebug_P(PSTR("[MQTT] Setting packetID %d"), packet_id);
#endif
return;
}
// delete it from queue
_mqttRemoveLastPublish();
}
// send online appended with the version information as JSON
void MyESP::_sendStartTopic() {
StaticJsonDocument<MYESP_JSON_MAXSIZE_SMALL> doc;
JsonObject payload = doc.to<JsonObject>();
payload["version"] = _app_version;
payload["IP"] = WiFi.localIP().toString();
// add time if we know it
if ((_ntp_enabled) && (NTP.tcr->abbrev != nullptr)) {
uint32_t real_time = getSystemTime();
// exclude millis() just in case
if (real_time > 10000L) {
char s[25];
// ISO 8601 describes an internationally accepted way to represent dates and times using numbers
snprintf_P(s,
25,
PSTR("%04u-%02u-%02uT%02u:%02u:%02u"),
to_year(real_time),
to_month(real_time),
to_day(real_time),
to_hour(real_time),
to_minute(real_time),
to_second(real_time)
);
payload["boottime"] = s;
}
}
mqttPublish(MQTT_TOPIC_START, doc, true); // send with retain on
} }
// MQTT onConnect - when a connect is established // MQTT onConnect - when a connect is established
@@ -440,8 +627,8 @@ void MyESP::_mqttOnConnect() {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
_mqtt_last_connection = millis(); _mqtt_last_connection = millis();
// say we're alive to the Last Will topic // say we're alive to the Last Will topic, with retain on
mqttPublish(_mqtt_will_topic, _mqtt_will_online_payload, true); // force retain on mqttPublish(_mqtt_will_topic, _mqtt_will_online_payload, true);
// subscribe to general subs // subscribe to general subs
mqttSubscribe(MQTT_TOPIC_RESTART); mqttSubscribe(MQTT_TOPIC_RESTART);
@@ -449,10 +636,14 @@ void MyESP::_mqttOnConnect() {
// subscribe to a start message and send the first publish // subscribe to a start message and send the first publish
// forcing retain to off since we only want to send this once // forcing retain to off since we only want to send this once
mqttSubscribe(MQTT_TOPIC_START); mqttSubscribe(MQTT_TOPIC_START);
mqttPublish(MQTT_TOPIC_START, MQTT_TOPIC_START_PAYLOAD, false);
// send start topic now unless NTP is enabled, otherwise wait for the time
if (!_ntp_enabled) {
_sendStartTopic();
}
// send heartbeat if enabled // send heartbeat if enabled
_heartbeatCheck(); heartbeatCheck(true);
// call custom function to handle mqtt receives // call custom function to handle mqtt receives
(_mqtt_callback_f)(MQTT_CONNECT_EVENT, nullptr, nullptr); (_mqtt_callback_f)(MQTT_CONNECT_EVENT, nullptr, nullptr);
@@ -489,7 +680,8 @@ void MyESP::_mqtt_setup() {
}); });
//mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); }); //mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); });
//mqttClient.onPublish([this](uint16_t packetId) { myDebug_P(PSTR("[MQTT] Publish ACK for PID %d"), packetId); });
mqttClient.onPublish([this](uint16_t packetId) { _mqttOnPublish(packetId); });
mqttClient.onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { mqttClient.onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
_mqttOnMessage(topic, payload, len); _mqttOnMessage(topic, payload, len);
@@ -502,8 +694,7 @@ void MyESP::_mqtt_setup() {
// last will // last will
if (_hasValue(_mqtt_will_topic)) { if (_hasValue(_mqtt_will_topic)) {
mqttClient.setWill(_mqttTopic(_mqtt_will_topic), 1, true, mqttClient.setWill(_mqttTopic(_mqtt_will_topic), 1, true, _mqtt_will_offline_payload); // retain always true
_mqtt_will_offline_payload); // retain always true
} }
// set credentials if we have them // set credentials if we have them
@@ -525,7 +716,7 @@ void MyESP::_wifi_setup() {
WiFi.setSleepMode(WIFI_NONE_SLEEP); // added to possibly fix wifi dropouts in arduino core 2.5.0 WiFi.setSleepMode(WIFI_NONE_SLEEP); // added to possibly fix wifi dropouts in arduino core 2.5.0
// ref: https://github.com/esp8266/Arduino/issues/6471 // ref: https://github.com/esp8266/Arduino/issues/6471
// ref: https://github.com/esp8266/Arduino/issues/6366 // ref: https://github.com/esp8266/Arduino/issues/6366
// high tx power causing weird behavior, slighly lowering from 20.5 to 20.0 may help stability // high tx power causing weird behavior, slightly lowering from 20.5 to 20.0 may help stability
WiFi.setOutputPower(20.0); // in DBM WiFi.setOutputPower(20.0); // in DBM
#endif #endif
@@ -715,8 +906,8 @@ void MyESP::_consoleShowHelp() {
myDebug_P(PSTR("*")); myDebug_P(PSTR("*"));
myDebug_P(PSTR("* Commands:")); myDebug_P(PSTR("* Commands:"));
myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=close telnet session")); myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=end telnet session"));
myDebug_P(PSTR("* set, system, restart, mqttlog, kick, save")); myDebug_P(PSTR("* set, system, restart, mqttqueue, kick, save"));
#ifdef CRASH #ifdef CRASH
myDebug_P(PSTR("* crash <dump | clear | test [n]>")); myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
@@ -749,16 +940,17 @@ bool MyESP::_hasValue(const char * s) {
void MyESP::_printSetCommands() { void MyESP::_printSetCommands() {
myDebug_P(PSTR("\nset commands:\n")); myDebug_P(PSTR("\nset commands:\n"));
myDebug_P(PSTR(" set erase")); myDebug_P(PSTR(" set erase"));
myDebug_P(PSTR(" set <wifi_mode <ap | client>")); myDebug_P(PSTR(" set wifi_mode <ap | client>"));
myDebug_P(PSTR(" set <wifi_ssid | wifi_password> [value]")); myDebug_P(PSTR(" set <wifi_ssid | wifi_password> [value]"));
myDebug_P(PSTR(" set mqtt_enabled <on | off>")); myDebug_P(PSTR(" set mqtt_enabled [on | off]"));
myDebug_P(PSTR(" set <mqtt_ip | mqtt_username | mqtt_password> [value]")); myDebug_P(PSTR(" set <mqtt_ip | mqtt_username | mqtt_password> [value]"));
myDebug_P(PSTR(" set mqtt_heartbeat <on | off> (every 2 mins)")); myDebug_P(PSTR(" set mqtt_heartbeat <on | off> (every 2 mins)"));
myDebug_P(PSTR(" set mqtt_base [string]")); myDebug_P(PSTR(" set mqtt_base [prefix]"));
myDebug_P(PSTR(" set mqtt_port [number]")); myDebug_P(PSTR(" set mqtt_port [n]"));
myDebug_P(PSTR(" set mqtt_qos [0-3]")); myDebug_P(PSTR(" set mqtt_qos [0-3]"));
myDebug_P(PSTR(" set mqtt_keepalive [seconds]")); myDebug_P(PSTR(" set mqtt_keepalive [seconds]"));
myDebug_P(PSTR(" set mqtt_retain [on | off]")); myDebug_P(PSTR(" set mqtt_retain [on | off]"));
myDebug_P(PSTR(" set mqtt_nestedjson [on | off]"));
myDebug_P(PSTR(" set ntp_enabled <on | off>")); myDebug_P(PSTR(" set ntp_enabled <on | off>"));
myDebug_P(PSTR(" set ntp_interval [minutes]")); myDebug_P(PSTR(" set ntp_interval [minutes]"));
myDebug_P(PSTR(" set ntp_timezone [n]")); myDebug_P(PSTR(" set ntp_timezone [n]"));
@@ -821,6 +1013,7 @@ void MyESP::_printSetCommands() {
myDebug_P(PSTR(" mqtt_retain=%s"), (_mqtt_retain) ? "on" : "off"); myDebug_P(PSTR(" mqtt_retain=%s"), (_mqtt_retain) ? "on" : "off");
myDebug_P(PSTR(" mqtt_qos=%d"), _mqtt_qos); myDebug_P(PSTR(" mqtt_qos=%d"), _mqtt_qos);
myDebug_P(PSTR(" mqtt_heartbeat=%s"), (_mqtt_heartbeat) ? "on" : "off"); myDebug_P(PSTR(" mqtt_heartbeat=%s"), (_mqtt_heartbeat) ? "on" : "off");
myDebug_P(PSTR(" mqtt_nestedjson=%s"), (_mqtt_nestedjson) ? "on" : "off");
#ifdef FORCE_SERIAL #ifdef FORCE_SERIAL
myDebug_P(PSTR(" serial=%s (this is always when compiled with -DFORCE_SERIAL)"), (_general_serial) ? "on" : "off"); myDebug_P(PSTR(" serial=%s (this is always when compiled with -DFORCE_SERIAL)"), (_general_serial) ? "on" : "off");
@@ -873,9 +1066,10 @@ char * MyESP::_telnet_readWord(bool allow_all_chars) {
// messy code but effective since we don't have too many settings // messy code but effective since we don't have too many settings
// wc is word count, number of parameters after the 'set' command // wc is word count, number of parameters after the 'set' command
bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) { bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) {
bool save_config = false; bool save_config = false;
bool save_custom_config = false; bool restart = false;
bool restart = false;
MYESP_FSACTION_t save_custom_config = MYESP_FSACTION_ERR; // default
// check for our internal commands first // check for our internal commands first
if (strcmp(setting, "erase") == 0) { if (strcmp(setting, "erase") == 0) {
@@ -924,6 +1118,8 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
restart = save_config; restart = save_config;
} else if (strcmp(setting, "mqtt_heartbeat") == 0) { } else if (strcmp(setting, "mqtt_heartbeat") == 0) {
save_config = fs_setSettingValue(&_mqtt_heartbeat, value, false); save_config = fs_setSettingValue(&_mqtt_heartbeat, value, false);
} else if (strcmp(setting, "mqtt_nestedjson") == 0) {
save_config = fs_setSettingValue(&_mqtt_nestedjson, value, false);
} else if (strcmp(setting, "ntp_enabled") == 0) { } else if (strcmp(setting, "ntp_enabled") == 0) {
save_config = fs_setSettingValue(&_ntp_enabled, value, false); save_config = fs_setSettingValue(&_ntp_enabled, value, false);
} else if (strcmp(setting, "ntp_interval") == 0) { } else if (strcmp(setting, "ntp_interval") == 0) {
@@ -938,13 +1134,14 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
// finally check for any custom commands // finally check for any custom commands
if (_fs_setlist_callback_f) { if (_fs_setlist_callback_f) {
save_custom_config = (_fs_setlist_callback_f)(MYESP_FSACTION_SET, wc, setting, value); save_custom_config = (_fs_setlist_callback_f)(MYESP_FSACTION_SET, wc, setting, value);
restart = (save_custom_config == MYESP_FSACTION_RESTART);
} }
} }
bool ok = false; bool ok = false;
// if we were able to recognize the set command, continue // if we were able to recognize the set command, continue
if ((save_config || save_custom_config)) { if ((save_config || save_custom_config != MYESP_FSACTION_ERR)) {
// check for 2 params // check for 2 params
if (value == nullptr) { if (value == nullptr) {
myDebug_P(PSTR("%s has been reset to its default value."), setting); myDebug_P(PSTR("%s has been reset to its default value."), setting);
@@ -960,7 +1157,7 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
} }
// and see if we need to also save for custom config // and see if we need to also save for custom config
if (save_custom_config) { if (save_custom_config != MYESP_FSACTION_ERR) {
ok = _fs_createCustomConfig(); ok = _fs_createCustomConfig();
} }
@@ -1042,12 +1239,12 @@ void MyESP::_telnetCommand(char * commandLine) {
} }
// print mqtt log command // print mqtt log command
if ((strcmp(ptrToCommandName, "mqttlog") == 0) && (wc == 1)) { if (strcmp(ptrToCommandName, "mqttqueue") == 0) {
_printMQTTLog(); _printMQTTQueue();
return; return;
} }
// show system stats // show system status
if ((strcmp(ptrToCommandName, "system") == 0) && (wc == 1)) { if ((strcmp(ptrToCommandName, "system") == 0) && (wc == 1)) {
showSystemStats(); showSystemStats();
return; return;
@@ -1055,12 +1252,11 @@ void MyESP::_telnetCommand(char * commandLine) {
// save everything // save everything
if ((strcmp(ptrToCommandName, "save") == 0) && (wc == 1)) { if ((strcmp(ptrToCommandName, "save") == 0) && (wc == 1)) {
_fs_writeConfig(); saveSettings();
_fs_createCustomConfig();
return; return;
} }
// show system stats // quit
if ((strcmp(ptrToCommandName, "quit") == 0) && (wc == 1)) { if ((strcmp(ptrToCommandName, "quit") == 0) && (wc == 1)) {
myDebug_P(PSTR("[TELNET] exiting telnet session")); myDebug_P(PSTR("[TELNET] exiting telnet session"));
SerialAndTelnet.disconnectClient(); SerialAndTelnet.disconnectClient();
@@ -1088,6 +1284,12 @@ void MyESP::_telnetCommand(char * commandLine) {
} }
} }
// public function so clients can save config
void MyESP::saveSettings() {
_fs_writeConfig();
_fs_createCustomConfig();
}
// returns WiFi hostname as a String object // returns WiFi hostname as a String object
String MyESP::_getESPhostname() { String MyESP::_getESPhostname() {
String hostname; String hostname;
@@ -1323,13 +1525,13 @@ void MyESP::_systemCheckLoop() {
} }
} }
// print out ESP system stats // print out ESP system status
// for battery power is ESP.getVcc() // for battery power is ESP.getVcc()
void MyESP::showSystemStats() { void MyESP::showSystemStats() {
#if defined(ESP8266) #if defined(ESP8266)
myDebug_P(PSTR("%sESP8266 System stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sESP8266 System status:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
#else #else
myDebug_P(PSTR("ESP32 System stats:")); myDebug_P(PSTR("ESP32 System status:"));
#endif #endif
myDebug_P(PSTR("")); myDebug_P(PSTR(""));
@@ -1373,13 +1575,14 @@ void MyESP::showSystemStats() {
if (isMQTTConnected()) { if (isMQTTConnected()) {
myDebug_P(PSTR(" [MQTT] is connected (heartbeat %s)"), getHeartbeat() ? "enabled" : "disabled"); myDebug_P(PSTR(" [MQTT] is connected (heartbeat %s)"), getHeartbeat() ? "enabled" : "disabled");
myDebug_P(PSTR(" [MQTT] # failed topic publishes: %d"), _mqtt_publish_fails);
} else { } else {
myDebug_P(PSTR(" [MQTT] is disconnected")); myDebug_P(PSTR(" [MQTT] is disconnected"));
} }
if (_have_ntp_time) { if ((_have_ntp_time) && (NTP.tcr->abbrev != nullptr)) {
uint32_t real_time = getSystemTime(); uint32_t real_time = getSystemTime();
myDebug_P(PSTR(" [NTP] Local Time is %02d:%02d:%02d %s (%d)"), to_hour(real_time), to_minute(real_time), to_second(real_time), NTP.tcr->abbrev, real_time); myDebug_P(PSTR(" [NTP] Local Time is %02d:%02d:%02d %s"), to_hour(real_time), to_minute(real_time), to_second(real_time), NTP.tcr->abbrev);
} }
#ifdef CRASH #ifdef CRASH
@@ -1450,43 +1653,40 @@ void MyESP::showSystemStats() {
/* /*
* Send heartbeat via MQTT with all system data * Send heartbeat via MQTT with all system data
*/ */
void MyESP::_heartbeatCheck(bool force) { void MyESP::heartbeatCheck(bool force) {
static uint32_t last_heartbeat = 0; static uint32_t last_heartbeat = 0;
if ((millis() - last_heartbeat > MYESP_HEARTBEAT_INTERVAL) || force) { if ((millis() - last_heartbeat > MYESP_HEARTBEAT_INTERVAL) || force) {
last_heartbeat = millis(); last_heartbeat = millis();
// print to log if force is set, so at bootup
if (force) {
_printHeap("[SYSTEM]");
}
#ifdef MYESP_DEBUG #ifdef MYESP_DEBUG
_printHeap("[HEARTBEAT] "); _printHeap("[HEARTBEAT]");
#endif #endif
if (!isMQTTConnected() || !(_mqtt_heartbeat)) { if (!isMQTTConnected() || !(_mqtt_heartbeat)) {
return; return;
} }
// print to log if force is set
if (force) {
_printHeap("[SYSTEM]");
}
uint32_t total_memory = _getInitialFreeHeap(); uint32_t total_memory = _getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap(); uint32_t free_memory = ESP.getFreeHeap();
uint8_t mem_available = 100 * free_memory / total_memory; // as a % uint8_t mem_available = 100 * free_memory / total_memory; // as a %
StaticJsonDocument<MYESP_JSON_MAXSIZE_SMALL> doc; const size_t capacity = JSON_OBJECT_SIZE(6);
JsonObject rootHeartbeat = doc.to<JsonObject>(); StaticJsonDocument<capacity> doc;
JsonObject rootHeartbeat = doc.to<JsonObject>();
rootHeartbeat["version"] = _app_version; rootHeartbeat["rssid"] = getWifiQuality();
rootHeartbeat["IP"] = WiFi.localIP().toString(); rootHeartbeat["load"] = getSystemLoadAverage();
rootHeartbeat["rssid"] = getWifiQuality(); rootHeartbeat["uptime"] = _getUptime();
rootHeartbeat["load"] = getSystemLoadAverage(); rootHeartbeat["freemem"] = mem_available;
rootHeartbeat["uptime"] = _getUptime(); rootHeartbeat["tcpdrops"] = _getSystemDropoutCounter();
rootHeartbeat["freemem"] = mem_available; rootHeartbeat["mqttpublishfails"] = _mqtt_publish_fails;
rootHeartbeat["MQTTdisconnects"] = _getSystemDropoutCounter();
char data[300] = {0}; mqttPublish(MQTT_TOPIC_HEARTBEAT, doc, false); // send to MQTT with retain off
serializeJson(doc, data, sizeof(data));
(void)mqttPublish(MQTT_TOPIC_HEARTBEAT, data, false); // send to MQTT with retain off
} }
} }
@@ -1499,13 +1699,13 @@ void MyESP::heartbeatPrint() {
uint32_t total_memory = _getInitialFreeHeap(); uint32_t total_memory = _getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap(); uint32_t free_memory = ESP.getFreeHeap();
myDebug("[%d] uptime:%d bytesfree:%d (%2u%%), load:%d, dropouts:%d", myDebug_P(PSTR("[%d] uptime:%d bytesfree:%d (%2u%%), load:%d, dropouts:%d"),
i++, i++,
_getUptime(), _getUptime(),
free_memory, free_memory,
100 * free_memory / total_memory, 100 * free_memory / total_memory,
getSystemLoadAverage(), getSystemLoadAverage(),
_getSystemDropoutCounter() _getSystemDropoutCounter()
); );
} }
@@ -1738,23 +1938,25 @@ bool MyESP::_fs_loadConfig() {
_general_serial = general["serial"]; _general_serial = general["serial"];
#endif #endif
JsonObject mqtt = doc["mqtt"]; JsonObject mqtt = doc["mqtt"];
_mqtt_enabled = mqtt["enabled"]; _mqtt_enabled = mqtt["enabled"];
_mqtt_heartbeat = mqtt["heartbeat"]; _mqtt_heartbeat = mqtt["heartbeat"];
_mqtt_ip = strdup(mqtt["ip"] | ""); _mqtt_ip = strdup(mqtt["ip"] | "");
_mqtt_user = strdup(mqtt["user"] | ""); _mqtt_user = strdup(mqtt["user"] | "");
_mqtt_port = mqtt["port"] | MQTT_PORT; _mqtt_port = mqtt["port"] | MQTT_PORT;
_mqtt_keepalive = mqtt["keepalive"] | MQTT_KEEPALIVE; _mqtt_keepalive = mqtt["keepalive"] | MQTT_KEEPALIVE;
_mqtt_retain = mqtt["retain"]; _mqtt_retain = mqtt["retain"];
_mqtt_qos = mqtt["qos"] | MQTT_QOS; _mqtt_qos = mqtt["qos"] | MQTT_QOS;
_mqtt_password = strdup(mqtt["password"] | ""); _mqtt_nestedjson = mqtt["nestedjson"] | true; // default to on
_mqtt_base = strdup(mqtt["base"] | MQTT_BASE_DEFAULT); _mqtt_password = strdup(mqtt["password"] | "");
_mqtt_base = strdup(mqtt["base"] | MQTT_BASE_DEFAULT);
JsonObject ntp = doc["ntp"]; JsonObject ntp = doc["ntp"];
_ntp_server = strdup(ntp["server"] | ""); _ntp_server = strdup(ntp["server"] | "");
_ntp_interval = ntp["interval"] | 60; _ntp_interval = ntp["interval"] | 60;
if (_ntp_interval < 2) if (_ntp_interval < 2) {
_ntp_interval = NTP_INTERVAL_DEFAULT; _ntp_interval = NTP_INTERVAL_DEFAULT;
}
_ntp_enabled = ntp["enabled"]; _ntp_enabled = ntp["enabled"];
_ntp_timezone = ntp["timezone"] | NTP_TIMEZONE_DEFAULT; _ntp_timezone = ntp["timezone"] | NTP_TIMEZONE_DEFAULT;
@@ -1951,17 +2153,18 @@ bool MyESP::_fs_writeConfig() {
general["log_ip"] = _general_log_ip; general["log_ip"] = _general_log_ip;
general["version"] = _app_version; general["version"] = _app_version;
JsonObject mqtt = doc.createNestedObject("mqtt"); JsonObject mqtt = doc.createNestedObject("mqtt");
mqtt["enabled"] = _mqtt_enabled; mqtt["enabled"] = _mqtt_enabled;
mqtt["heartbeat"] = _mqtt_heartbeat; mqtt["heartbeat"] = _mqtt_heartbeat;
mqtt["ip"] = _mqtt_ip; mqtt["ip"] = _mqtt_ip;
mqtt["user"] = _mqtt_user; mqtt["user"] = _mqtt_user;
mqtt["port"] = _mqtt_port; mqtt["port"] = _mqtt_port;
mqtt["qos"] = _mqtt_qos; mqtt["qos"] = _mqtt_qos;
mqtt["keepalive"] = _mqtt_keepalive; mqtt["keepalive"] = _mqtt_keepalive;
mqtt["retain"] = _mqtt_retain; mqtt["retain"] = _mqtt_retain;
mqtt["password"] = _mqtt_password; mqtt["password"] = _mqtt_password;
mqtt["base"] = _mqtt_base; mqtt["base"] = _mqtt_base;
mqtt["nestedjson"] = _mqtt_nestedjson;
JsonObject ntp = doc.createNestedObject("ntp"); JsonObject ntp = doc.createNestedObject("ntp");
ntp["server"] = _ntp_server; ntp["server"] = _ntp_server;
@@ -2063,6 +2266,11 @@ void MyESP::_calculateLoad() {
} }
} }
// returns true if nested JSON setting is enabled
bool MyESP::mqttUseNestedJson() {
return _mqtt_nestedjson;
}
// returns true is MQTT is alive // returns true is MQTT is alive
bool MyESP::isMQTTConnected() { bool MyESP::isMQTTConnected() {
return mqttClient.connected(); return mqttClient.connected();
@@ -2278,7 +2486,7 @@ void MyESP::writeLogEvent(const uint8_t type, const char * msg) {
// Handles WebSocket Events // Handles WebSocket Events
void MyESP::_onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len) { void MyESP::_onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len) {
if (type == WS_EVT_ERROR) { if (type == WS_EVT_ERROR) {
myDebug("[WEB] WebSocket[%s][%u] error(%u): %s\r\n", server->url(), client->id(), *((uint16_t *)arg), (char *)data); myDebug_P(PSTR("[WEB] WebSocket[%s][%u] error(%u): %s\r\n"), server->url(), client->id(), *((uint16_t *)arg), (char *)data);
} else if (type == WS_EVT_DATA) { } else if (type == WS_EVT_DATA) {
AwsFrameInfo * info = (AwsFrameInfo *)arg; AwsFrameInfo * info = (AwsFrameInfo *)arg;
uint64_t index = info->index; uint64_t index = info->index;
@@ -2442,13 +2650,13 @@ void MyESP::_sendStatus() {
uint32_t total_memory = _getInitialFreeHeap(); uint32_t total_memory = _getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap(); uint32_t free_memory = ESP.getFreeHeap();
DynamicJsonDocument doc(MQTT_MAX_PAYLOAD_SIZE_LARGE); DynamicJsonDocument doc(MYESP_JSON_MAXSIZE_MEDIUM);
JsonObject root = doc.to<JsonObject>(); JsonObject root = doc.to<JsonObject>();
root["command"] = "status"; root["command"] = "status";
FSInfo fsinfo; FSInfo fsinfo;
if (!SPIFFS.info(fsinfo)) { if (!SPIFFS.info(fsinfo)) {
myDebug("[SYSTEM] Error getting info on SPIFFS"); myDebug_P(PSTR("[SYSTEM] Error getting info on SPIFFS"));
} else { } else {
root["availspiffs"] = (fsinfo.totalBytes - fsinfo.usedBytes) / 1000; root["availspiffs"] = (fsinfo.totalBytes - fsinfo.usedBytes) / 1000;
root["spiffssize"] = (fsinfo.totalBytes / 1000); root["spiffssize"] = (fsinfo.totalBytes / 1000);
@@ -2486,31 +2694,7 @@ void MyESP::_sendStatus() {
sprintf(uptime, "%d day%s %d hour%s %d minute%s %d second%s", d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s"); sprintf(uptime, "%d day%s %d hour%s %d minute%s %d second%s", d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s");
root["uptime"] = uptime; root["uptime"] = uptime;
char topic_s[MQTT_MAX_TOPIC_SIZE] = {0}; char buffer[MYESP_JSON_MAXSIZE_MEDIUM];
if (_hasValue(_mqtt_base)) {
strlcpy(topic_s, _mqtt_base, sizeof(topic_s));
strlcat(topic_s, "/", sizeof(topic_s));
strlcat(topic_s, _general_hostname, sizeof(topic_s));
} else {
strlcpy(topic_s, _general_hostname, sizeof(topic_s));
}
strlcat(topic_s, "/", sizeof(topic_s));
root["mqttloghdr"] = topic_s;
// create MQTT log
JsonArray list = root.createNestedArray("mqttlog");
// only send Publish
for (uint8_t i = 0; i < MYESP_MQTTLOG_MAX; i++) {
if ((MQTT_log[i].type == 1) && (MQTT_log[i].topic != nullptr)) {
JsonObject item = list.createNestedObject();
item["topic"] = MQTT_log[i].topic;
item["payload"] = MQTT_log[i].payload;
item["time"] = MQTT_log[i].timestamp;
}
}
char buffer[MQTT_MAX_PAYLOAD_SIZE_LARGE];
size_t len = serializeJson(root, buffer); size_t len = serializeJson(root, buffer);
_ws->textAll(buffer, len); _ws->textAll(buffer, len);
@@ -2569,48 +2753,49 @@ void MyESP::_webserver_setup() {
request->send(response); request->send(response);
}); });
_webServer->on("/update", _webServer->on(
HTTP_POST, "/update",
[](AsyncWebServerRequest * request) { HTTP_POST,
AsyncWebServerResponse * response = request->beginResponse(200, "text/plain", _shouldRestart ? "OK" : "FAIL"); [](AsyncWebServerRequest * request) {
response->addHeader("Connection", "close"); AsyncWebServerResponse * response = request->beginResponse(200, "text/plain", _shouldRestart ? "OK" : "FAIL");
request->send(response); response->addHeader("Connection", "close");
}, request->send(response);
[](AsyncWebServerRequest * request, String filename, size_t index, uint8_t * data, size_t len, bool final) { },
if (!request->authenticate(MYESP_HTTP_USERNAME, _general_password)) { [](AsyncWebServerRequest * request, String filename, size_t index, uint8_t * data, size_t len, bool final) {
return; if (!request->authenticate(MYESP_HTTP_USERNAME, _general_password)) {
} return;
if (!index) { }
ETS_UART_INTR_DISABLE(); // disable all UART interrupts to be safe if (!index) {
//_writeLogEvent(MYESP_SYSLOG_INFO, "Firmware update started"); ETS_UART_INTR_DISABLE(); // disable all UART interrupts to be safe
Update.runAsync(true); //_writeLogEvent(MYESP_SYSLOG_INFO, "Firmware update started");
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
//_writeLogEvent(MYESP_SYSLOG_ERROR, "Not enough space to update"); //_writeLogEvent(MYESP_SYSLOG_ERROR, "Not enough space to update");
#ifdef MYESP_DEBUG #ifdef MYESP_DEBUG
Update.printError(Serial); Update.printError(Serial);
#endif #endif
} }
} }
if (!Update.hasError()) { if (!Update.hasError()) {
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
//_writeLogEvent(MYESP_SYSLOG_ERROR, "Writing to flash failed"); //_writeLogEvent(MYESP_SYSLOG_ERROR, "Writing to flash failed");
#ifdef MYESP_DEBUG #ifdef MYESP_DEBUG
Update.printError(Serial); Update.printError(Serial);
#endif #endif
} }
} }
if (final) { if (final) {
if (Update.end(true)) { if (Update.end(true)) {
//_writeLogEvent(MYESP_SYSLOG_INFO, "Firmware update finished"); //_writeLogEvent(MYESP_SYSLOG_INFO, "Firmware update finished");
_shouldRestart = !Update.hasError(); _shouldRestart = !Update.hasError();
} else { } else {
//_writeLogEvent(MYESP_SYSLOG_ERROR, "Firmware update failed"); //_writeLogEvent(MYESP_SYSLOG_ERROR, "Firmware update failed");
#ifdef MYESP_DEBUG #ifdef MYESP_DEBUG
Update.printError(Serial); Update.printError(Serial);
#endif #endif
} }
} }
}); });
_webServer->on("/fonts/glyphicons-halflings-regular.woff", HTTP_GET, [](AsyncWebServerRequest * request) { _webServer->on("/fonts/glyphicons-halflings-regular.woff", HTTP_GET, [](AsyncWebServerRequest * request) {
AsyncWebServerResponse * response = AsyncWebServerResponse * response =
@@ -2667,84 +2852,13 @@ void MyESP::_printHeap(const char * prefix) {
uint32_t total_memory = _getInitialFreeHeap(); uint32_t total_memory = _getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap(); uint32_t free_memory = ESP.getFreeHeap();
myDebug("%s Free Heap: %d bytes initially | %d bytes used (%2u%%) | %d bytes free (%2u%%)", myDebug_P(PSTR("%s Free Heap: %d bytes initially | %d bytes used (%2u%%) | %d bytes free (%2u%%)"),
prefix, prefix,
total_memory, total_memory,
total_memory - free_memory, total_memory - free_memory,
100 * (total_memory - free_memory) / total_memory, 100 * (total_memory - free_memory) / total_memory,
free_memory, free_memory,
100 * free_memory / total_memory); 100 * free_memory / total_memory);
}
// print MQTT log - everything that was published last per topic
void MyESP::_printMQTTLog() {
myDebug_P(PSTR("MQTT publish log:"));
uint8_t i;
for (i = 0; i < MYESP_MQTTLOG_MAX; i++) {
if ((MQTT_log[i].topic != nullptr) && (MQTT_log[i].type == MYESP_MQTTLOGTYPE_PUBLISH)) {
myDebug_P(PSTR(" Timestamp:%02d:%02d:%02d Topic:%s Payload:%s"),
to_hour(MQTT_log[i].timestamp),
to_minute(MQTT_log[i].timestamp),
to_second(MQTT_log[i].timestamp),
MQTT_log[i].topic,
MQTT_log[i].payload);
}
}
myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("MQTT subscriptions:"));
for (i = 0; i < MYESP_MQTTLOG_MAX; i++) {
if ((MQTT_log[i].topic != nullptr) && (MQTT_log[i].type == MYESP_MQTTLOGTYPE_SUBSCRIBE)) {
myDebug_P(PSTR(" Topic:%s"), MQTT_log[i].topic);
}
}
myDebug_P(PSTR("")); // newline
}
// add an MQTT log entry to our buffer
void MyESP::_addMQTTLog(const char * topic, const char * payload, const MYESP_MQTTLOGTYPE_t type) {
static uint8_t logCount = 0;
uint8_t logPointer = 0;
bool found = false;
#ifdef MYESP_DEBUG
myDebug("_addMQTTLog [#%d] %s (%d) [%s] (%d)", logCount, topic, strlen(topic), payload, strlen(payload));
#endif
// find the topic
// topics must be unique for either publish or subscribe
while ((logPointer < MYESP_MQTTLOG_MAX) && (_hasValue(MQTT_log[logPointer].topic))) {
if ((strcmp(MQTT_log[logPointer].topic, topic) == 0) && (MQTT_log[logPointer].type == type)) {
found = true;
break;
}
logPointer++;
}
// if not found add it and increment next free space pointer
if (!found) {
logPointer = logCount;
if (++logCount == MYESP_MQTTLOG_MAX) {
logCount = 0; // rotate
}
}
// delete old record
if (MQTT_log[logPointer].topic) {
free(MQTT_log[logPointer].topic);
}
if (MQTT_log[logPointer].payload) {
free(MQTT_log[logPointer].payload);
}
// and add new record
MQTT_log[logPointer].type = type;
MQTT_log[logPointer].topic = strdup(topic);
MQTT_log[logPointer].payload = strdup(payload);
MQTT_log[logPointer].timestamp = now();
} }
// send UTC time via ws // send UTC time via ws
@@ -2776,9 +2890,11 @@ void MyESP::_bootupSequence() {
// check if its booted // check if its booted
if (boot_status == MYESP_BOOTSTATUS_BOOTED) { if (boot_status == MYESP_BOOTSTATUS_BOOTED) {
if ((_ntp_enabled) && (now() > 10000) && !_have_ntp_time) { if ((_ntp_enabled) && (now() > 10000L) && !_have_ntp_time) {
_have_ntp_time = true; _have_ntp_time = true;
writeLogEvent(MYESP_SYSLOG_INFO, "System booted"); writeLogEvent(MYESP_SYSLOG_INFO, "System booted");
// send start topic
_sendStartTopic();
} }
return; return;
} }
@@ -2871,7 +2987,6 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char *
_webserver_setup(); // init web server _webserver_setup(); // init web server
_setSystemCheck(false); // reset system check _setSystemCheck(false); // reset system check
_heartbeatCheck(true); // force heartbeat, will send out message to log too
_setSystemDropoutCounter(0); // reset # TCP dropouts _setSystemDropoutCounter(0); // reset # TCP dropouts
@@ -2884,7 +2999,7 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char *
void MyESP::loop() { void MyESP::loop() {
_calculateLoad(); _calculateLoad();
_systemCheckLoop(); _systemCheckLoop();
_heartbeatCheck(); heartbeatCheck();
_bootupSequence(); // see if a reset was pressed during bootup _bootupSequence(); // see if a reset was pressed during bootup
jw.loop(); // WiFi jw.loop(); // WiFi
@@ -2897,6 +3012,14 @@ void MyESP::loop() {
_mqttConnect(); // MQTT _mqttConnect(); // MQTT
// every second check MQTT queue for publishing
static unsigned long lastMqttPoll = 0;
unsigned long currentMillis = millis();
if ((unsigned long)(currentMillis - lastMqttPoll) >= MQTT_PUBLISH_WAIT) {
_mqttPublishQueue();
lastMqttPoll = currentMillis;
}
// SysLog // SysLog
uuid::loop(); uuid::loop();
syslog.loop(); syslog.loop();
@@ -2907,7 +3030,7 @@ void MyESP::loop() {
} }
if (_formatreq) { if (_formatreq) {
myDebug("[SYSTEM] Factory reset initiated. Please wait. System will automatically restart when complete..."); myDebug_P(PSTR("[SYSTEM] Factory reset initiated. Please wait. System will automatically restart when complete..."));
SPIFFS.end(); SPIFFS.end();
_ws->enable(false); _ws->enable(false);
SPIFFS.format(); SPIFFS.format();
@@ -2917,7 +3040,7 @@ void MyESP::loop() {
if (_shouldRestart) { if (_shouldRestart) {
writeLogEvent(MYESP_SYSLOG_INFO, "System is restarting"); writeLogEvent(MYESP_SYSLOG_INFO, "System is restarting");
myDebug("[SYSTEM] Restarting..."); myDebug_P(PSTR("[SYSTEM] Restarting..."));
_deferredReset(500, CUSTOM_RESET_TERMINAL); _deferredReset(500, CUSTOM_RESET_TERMINAL);
ESP.restart(); ESP.restart();
} }

View File

@@ -9,7 +9,7 @@
#ifndef MyESP_h #ifndef MyESP_h
#define MyESP_h #define MyESP_h
#define MYESP_VERSION "1.2.22" #define MYESP_VERSION "1.2.37"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
@@ -17,6 +17,7 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <FS.h> #include <FS.h>
#include <JustWifi.h> #include <JustWifi.h>
#include <deque> // for MQTT publish queue
// SysLog // SysLog
#include <uuid/common.h> #include <uuid/common.h>
@@ -85,7 +86,6 @@ extern struct rst_info resetInfo;
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most #define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
#define MQTT_TOPIC_START "start" #define MQTT_TOPIC_START "start"
#define MQTT_TOPIC_HEARTBEAT "heartbeat" #define MQTT_TOPIC_HEARTBEAT "heartbeat"
#define MQTT_TOPIC_START_PAYLOAD "start"
#define MQTT_TOPIC_RESTART "restart" #define MQTT_TOPIC_RESTART "restart"
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload #define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload #define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
@@ -95,20 +95,18 @@ extern struct rst_info resetInfo;
#define MQTT_QOS 0 // default qos 0 #define MQTT_QOS 0 // default qos 0
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name #define MQTT_WILL_TOPIC "status" // for last will & testament topic name
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic #define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
#define MQTT_MAX_PAYLOAD_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/ #define MQTT_QUEUE_MAX_SIZE 50 // Size of the MQTT queue
#define MQTT_MAX_PAYLOAD_SIZE_LARGE 2000 // max size of a large JSON object, like for sending MQTT log #define MQTT_PUBLISH_WAIT 750 // time in ms before sending MQTT messages
#define MQTT_PUBLISH_MAX_RETRY 4 // max retries for giving up on publishing
#define MYESP_JSON_MAXSIZE_LARGE 2000 // for large Dynamic json files - https://arduinojson.org/v6/assistant/
#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files - https://arduinojson.org/v6/assistant/
#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents - https://arduinojson.org/v6/assistant/
// Internal MQTT events // Internal MQTT events
#define MQTT_CONNECT_EVENT 0 #define MQTT_CONNECT_EVENT 0
#define MQTT_DISCONNECT_EVENT 1 #define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2 #define MQTT_MESSAGE_EVENT 2
#define MYESP_JSON_MAXSIZE_LARGE 2000 // for large Dynamic json files
#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files
#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents
#define MYESP_MQTTLOG_MAX 60 // max number of log entries for MQTT publishes and subscribes
#define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on #define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on
#define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off #define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off
@@ -210,17 +208,25 @@ struct RtcmemData {
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
#define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute) #define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 min)
#define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot #define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot
#define MYESP_HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins) #define MYESP_HEARTBEAT_INTERVAL 60000 // in milliseconds, how often the MQTT heartbeat is sent (1 min)
typedef struct { typedef struct {
bool set; // is it a set command bool set; // is it a set command?
char key[50]; char key[60];
char description[100]; char description[110];
} command_t; } command_t;
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION_t; typedef enum {
MYESP_FSACTION_SET,
MYESP_FSACTION_LIST,
MYESP_FSACTION_SAVE,
MYESP_FSACTION_LOAD,
MYESP_FSACTION_ERR,
MYESP_FSACTION_OK,
MYESP_FSACTION_RESTART
} MYESP_FSACTION_t;
typedef enum { typedef enum {
MYESP_BOOTSTATUS_POWERON = 0, MYESP_BOOTSTATUS_POWERON = 0,
@@ -229,24 +235,14 @@ typedef enum {
MYESP_BOOTSTATUS_RESETNEEDED = 3 MYESP_BOOTSTATUS_RESETNEEDED = 3
} MYESP_BOOTSTATUS_t; // boot messages } MYESP_BOOTSTATUS_t; // boot messages
typedef enum { MYESP_MQTTLOGTYPE_NONE, MYESP_MQTTLOGTYPE_PUBLISH, MYESP_MQTTLOGTYPE_SUBSCRIBE } MYESP_MQTTLOGTYPE_t; typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f;
// for storing all MQTT publish messages typedef std::function<void()> ota_callback_f;
typedef struct { typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
uint8_t type; // 0=none, 1=publish, 2=subscribe typedef std::function<void(uint8_t)> telnet_callback_f;
char * topic; typedef std::function<bool(MYESP_FSACTION_t, JsonObject json)> fs_loadsave_callback_f;
char * payload; typedef std::function<MYESP_FSACTION_t(MYESP_FSACTION_t, uint8_t, const char *, const char *)> fs_setlist_callback_f;
time_t timestamp; typedef std::function<void(JsonObject root)> web_callback_f;
} _MQTT_Log_t;
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f;
typedef std::function<void()> ota_callback_f;
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION_t, JsonObject json)> fs_loadsave_callback_f;
typedef std::function<bool(MYESP_FSACTION_t, uint8_t, const char *, const char *)> fs_setlist_callback_f;
typedef std::function<void(JsonObject root)> web_callback_f;
// calculates size of an 2d array at compile time // calculates size of an 2d array at compile time
template <typename T, size_t N> template <typename T, size_t N>
@@ -285,7 +281,10 @@ class MyESP {
void mqttUnsubscribe(const char * topic); void mqttUnsubscribe(const char * topic);
bool mqttPublish(const char * topic, const char * payload); bool mqttPublish(const char * topic, const char * payload);
bool mqttPublish(const char * topic, const char * payload, bool retain); bool mqttPublish(const char * topic, const char * payload, bool retain);
bool mqttPublish(const char * topic, JsonDocument & payload);
bool mqttPublish(const char * topic, JsonDocument & payload, bool retain);
void setMQTT(mqtt_callback_f callback); void setMQTT(mqtt_callback_f callback);
bool mqttUseNestedJson();
// OTA // OTA
void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post); void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post);
@@ -302,6 +301,7 @@ class MyESP {
// FS // FS
void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true); void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true);
void saveSettings();
bool fs_saveConfig(JsonObject root); bool fs_saveConfig(JsonObject root);
bool fs_saveCustomConfig(JsonObject root); bool fs_saveCustomConfig(JsonObject root);
bool fs_setSettingValue(char ** setting, const char * value, const char * value_default); bool fs_setSettingValue(char ** setting, const char * value, const char * value_default);
@@ -329,25 +329,25 @@ class MyESP {
uint32_t getSystemLoadAverage(); uint32_t getSystemLoadAverage();
uint32_t getSystemResetReason(); uint32_t getSystemResetReason();
uint8_t getSystemBootStatus(); uint8_t getSystemBootStatus();
bool _have_ntp_time;
unsigned long getSystemTime(); unsigned long getSystemTime();
void heartbeatPrint(); void heartbeatPrint();
void heartbeatCheck(bool force = false);
private: private:
// mqtt // mqtt
void _mqttOnMessage(char * topic, char * payload, size_t len); void _mqttOnMessage(char * topic, char * payload, size_t len);
void _mqttConnect(); void _mqttOnPublish(uint16_t packetId);
void _mqtt_setup(); void _mqttConnect();
void _mqttOnConnect(); void _mqtt_setup();
void _sendStart(); void _mqttOnConnect();
char * _mqttTopic(const char * topic); void _sendStart();
char * _mqttTopic(const char * topic);
// mqtt log bool _mqttQueue(const char * topic, const char * payload, bool retain);
_MQTT_Log_t MQTT_log[MYESP_MQTTLOG_MAX]; // log for publish and subscribe messages bool _mqttQueue(const char * topic, JsonDocument & payload, bool retain);
void _printMQTTQueue();
void _printMQTTLog(); void _mqttPublishQueue();
void _addMQTTLog(const char * topic, const char * payload, const MYESP_MQTTLOGTYPE_t type); void _mqttRemoveLastPublish();
void _sendStartTopic();
AsyncMqttClient mqttClient; // the MQTT class AsyncMqttClient mqttClient; // the MQTT class
uint32_t _mqtt_reconnect_delay; uint32_t _mqtt_reconnect_delay;
mqtt_callback_f _mqtt_callback_f; mqtt_callback_f _mqtt_callback_f;
@@ -366,6 +366,8 @@ class MyESP {
uint32_t _mqtt_last_connection; uint32_t _mqtt_last_connection;
bool _mqtt_connecting; bool _mqtt_connecting;
bool _mqtt_heartbeat; bool _mqtt_heartbeat;
bool _mqtt_nestedjson;
uint16_t _mqtt_publish_fails;
// wifi // wifi
void _wifiCallback(justwifi_messages_t code, char * parameter); void _wifiCallback(justwifi_messages_t code, char * parameter);
@@ -408,20 +410,18 @@ class MyESP {
void _syslog_setup(); void _syslog_setup();
// fs and settings // fs and settings
void _fs_setup(); void _fs_setup();
bool _fs_loadConfig(); bool _fs_loadConfig();
bool _fs_loadCustomConfig(); bool _fs_loadCustomConfig();
void _fs_eraseConfig(); void _fs_eraseConfig();
bool _fs_writeConfig(); bool _fs_writeConfig();
bool _fs_createCustomConfig(); bool _fs_createCustomConfig();
bool _fs_sendConfig(); bool _fs_sendConfig();
size_t _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc); size_t _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc);
size_t _fs_validateLogFile(const char * filename); size_t _fs_validateLogFile(const char * filename);
fs_loadsave_callback_f _fs_loadsave_callback_f; fs_loadsave_callback_f _fs_loadsave_callback_f;
fs_setlist_callback_f _fs_setlist_callback_f; fs_setlist_callback_f _fs_setlist_callback_f;
void _printSetCommands();
void _printSetCommands();
// general // general
char * _general_hostname; char * _general_hostname;
@@ -444,34 +444,27 @@ class MyESP {
void _kick(); void _kick();
// reset reason and rtcmem // reset reason and rtcmem
bool _rtcmem_status; bool _rtcmem_status;
bool _rtcmemStatus(); bool _rtcmemStatus();
bool _getRtcmemStatus(); bool _getRtcmemStatus();
void _rtcmemInit();
void _rtcmemInit(); void _rtcmemSetup();
void _rtcmemSetup(); void _deferredReset(unsigned long delay, uint8_t reason);
void _deferredReset(unsigned long delay, uint8_t reason);
uint8_t _getSystemStabilityCounter(); uint8_t _getSystemStabilityCounter();
void _setSystemStabilityCounter(uint8_t counter); void _setSystemStabilityCounter(uint8_t counter);
uint8_t _getSystemDropoutCounter(); uint8_t _getSystemDropoutCounter();
void _setSystemDropoutCounter(uint8_t counter); void _setSystemDropoutCounter(uint8_t counter);
void _increaseSystemDropoutCounter(); void _increaseSystemDropoutCounter();
void _setSystemResetReason(uint8_t reason); void _setSystemResetReason(uint8_t reason);
uint8_t _getCustomResetReason(); uint8_t _getCustomResetReason();
void _setCustomResetReason(uint8_t reason); void _setCustomResetReason(uint8_t reason);
uint8_t _getSystemResetReason(); uint8_t _getSystemResetReason();
void _setSystemBootStatus(uint8_t status);
void _setSystemBootStatus(uint8_t status); bool _systemStable;
void _bootupSequence();
bool _systemStable; bool _getSystemCheck();
void _bootupSequence(); void _systemCheckLoop();
bool _getSystemCheck(); void _setSystemCheck(bool stable);
void _systemCheckLoop();
void _setSystemCheck(bool stable);
// load average (0..100) and heap ram // load average (0..100) and heap ram
void _calculateLoad(); void _calculateLoad();
@@ -479,9 +472,6 @@ class MyESP {
uint32_t _getInitialFreeHeap(); uint32_t _getInitialFreeHeap();
uint32_t _getUsedHeap(); uint32_t _getUsedHeap();
// heartbeat
void _heartbeatCheck(bool force = false);
// web // web
web_callback_f _web_callback_f; web_callback_f _web_callback_f;
const char * _http_username; const char * _http_username;
@@ -500,6 +490,7 @@ class MyESP {
uint16_t _ntp_interval; uint16_t _ntp_interval;
bool _ntp_enabled; bool _ntp_enabled;
uint8_t _ntp_timezone; uint8_t _ntp_timezone;
bool _have_ntp_time;
}; };
extern MyESP myESP; extern MyESP myESP;

View File

@@ -110,16 +110,16 @@ time_t ICACHE_FLASH_ATTR NtpClient::getNtpTime() {
time_t UnixUTCtime = (highWord << 16 | lowWord) - 2208988800UL; time_t UnixUTCtime = (highWord << 16 | lowWord) - 2208988800UL;
time_t adjustedtime = (*tz).toLocal(UnixUTCtime, &tcr); time_t adjustedtime = (*tz).toLocal(UnixUTCtime, &tcr);
myESP.myDebug("[NTP] Internet time: %02d:%02d:%02d UTC on %d/%d. Local time: %02d:%02d:%02d %s", myESP.myDebug_P(PSTR("[NTP] Internet time: %02d:%02d:%02d UTC on %d/%d. Local time: %02d:%02d:%02d %s"),
to_hour(UnixUTCtime), to_hour(UnixUTCtime),
to_minute(UnixUTCtime), to_minute(UnixUTCtime),
to_second(UnixUTCtime), to_second(UnixUTCtime),
to_day(UnixUTCtime), to_day(UnixUTCtime),
to_month(UnixUTCtime), to_month(UnixUTCtime),
to_hour(adjustedtime), to_hour(adjustedtime),
to_minute(adjustedtime), to_minute(adjustedtime),
to_second(adjustedtime), to_second(adjustedtime),
tcr->abbrev); tcr->abbrev);
setTime(adjustedtime); setTime(adjustedtime);
}); });

View File

@@ -114,7 +114,7 @@
<div class="row form-group"> <div class="row form-group">
<label class="col-xs-3">Publish Time<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" <label class="col-xs-3">Publish Time<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="How often to send MQTT topics with stats. 0 is automatic. (in seconds)"></i></label> data-content="How often to send the MQTT topics in seconds. Must be at least 1"></i></label>
<span class="col-xs-9"> <span class="col-xs-9">
<input class="form-control input-sm" placeholder="0" value="" style="display:inline;max-width:185px" <input class="form-control input-sm" placeholder="0" value="" style="display:inline;max-width:185px"
id="publish_time" type="text"> id="publish_time" type="text">
@@ -192,10 +192,8 @@
<td id="b4"></td> <td id="b4"></td>
</tr> </tr>
<tr> <tr>
<th>Boiler Temperature:</th> <th>DHW Temperature:</th>
<td id="b5"></td> <td id="b5"></td>
<th>Return Temperature:</th>
<td id="b6"></td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@@ -8,7 +8,7 @@ var custom_config = {
"listen_mode": false, "listen_mode": false,
"shower_timer": false, "shower_timer": false,
"shower_alert": false, "shower_alert": false,
"publish_time": 0, "publish_time": 10,
"tx_mode": 1 "tx_mode": 1
} }
}; };
@@ -99,7 +99,7 @@ function listCustomStats() {
var l = document.createElement("li"); var l = document.createElement("li");
var type = obj[i].type; var type = obj[i].type;
var color = ""; var color = "";
if (type === "UBAMaster") { if (type === "Boiler") {
color = "list-group-item-success"; color = "list-group-item-success";
} else if (type === "Thermostat") { } else if (type === "Thermostat") {
color = "list-group-item-info"; color = "list-group-item-info";
@@ -108,7 +108,7 @@ function listCustomStats() {
} else if (type === "Heat Pump") { } else if (type === "Heat Pump") {
color = "list-group-item-success"; color = "list-group-item-success";
} }
l.innerHTML = obj[i].model + " (Version:" + obj[i].version + " ProductID:" + obj[i].productid + " DeviceID:0x" + obj[i].deviceid + ")"; l.innerHTML = obj[i].model + " (DeviceID: 0x" + obj[i].deviceid + ", ProductID: " + obj[i].productid + ", Version: " + obj[i].version + ")";
l.className = "list-group-item " + color; l.className = "list-group-item " + color;
list.appendChild(l); list.appendChild(l);
} }
@@ -122,7 +122,6 @@ function listCustomStats() {
document.getElementById("b3").innerHTML = ajaxobj.boiler.b3 + " &#8451;"; document.getElementById("b3").innerHTML = ajaxobj.boiler.b3 + " &#8451;";
document.getElementById("b4").innerHTML = ajaxobj.boiler.b4 + " &#8451;"; document.getElementById("b4").innerHTML = ajaxobj.boiler.b4 + " &#8451;";
document.getElementById("b5").innerHTML = ajaxobj.boiler.b5 + " &#8451;"; document.getElementById("b5").innerHTML = ajaxobj.boiler.b5 + " &#8451;";
document.getElementById("b6").innerHTML = ajaxobj.boiler.b6 + " &#8451;";
} else { } else {
document.getElementById("boiler_show").style.display = "none"; document.getElementById("boiler_show").style.display = "none";
} }

View File

@@ -18,24 +18,28 @@ DS18::DS18() {
} }
DS18::~DS18() { DS18::~DS18() {
if (_wire) if (_wire) {
delete _wire; delete _wire;
}
} }
// init // init
uint8_t DS18::setup(uint8_t gpio, bool parasite) { void DS18::setup(uint8_t gpio, bool parasite) {
uint8_t count;
_gpio = gpio; _gpio = gpio;
_parasite = (parasite ? 1 : 0); _parasite = (parasite ? 1 : 0);
// OneWire // OneWire
if (_wire) if (_wire) {
delete _wire; delete _wire;
}
_wire = new OneWire(_gpio); _wire = new OneWire(_gpio);
}
// Search devices // clear list and scan for devices
count = loadDevices(); uint8_t DS18::scan() {
_devices.clear();
uint8_t count = loadDevices(); // start the search
// If no devices found check again pulling up the line // If no devices found check again pulling up the line
if (count == 0) { if (count == 0) {
@@ -48,30 +52,22 @@ uint8_t DS18::setup(uint8_t gpio, bool parasite) {
return count; return count;
} }
// scan every 2 seconds
void DS18::loop() { void DS18::loop() {
static uint32_t last = 0; // we either start a conversion or read the scratchpad
if (millis() - last < DS18_READ_INTERVAL)
return;
last = millis();
// Every second we either start a conversion or read the scratchpad
static bool conversion = true; static bool conversion = true;
if (conversion) { if (conversion) {
// Start conversion
_wire->reset(); _wire->reset();
_wire->skip(); _wire->skip();
_wire->write(DS18_CMD_START_CONVERSION, _parasite); _wire->write(DS18_CMD_START_CONVERSION, _parasite);
} else { } else {
// Read scratchpads // Read scratchpads
for (unsigned char index = 0; index < _devices.size(); index++) { for (unsigned char index = 0; index < _devices.size(); index++) {
// Read scratchpad
if (_wire->reset() == 0) { if (_wire->reset() == 0) {
// Force a CRC check error _devices[index].data[0] = _devices[index].data[0] + 1; // Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return; return;
} }
// Read each scratchpad
_wire->select(_devices[index].address); _wire->select(_devices[index].address);
_wire->write(DS18_CMD_READ_SCRATCHPAD); _wire->write(DS18_CMD_READ_SCRATCHPAD);
@@ -81,8 +77,7 @@ void DS18::loop() {
} }
if (_wire->reset() != 1) { if (_wire->reset() != 1) {
// Force a CRC check error _devices[index].data[0] = _devices[index].data[0] + 1; // Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return; return;
} }
@@ -94,7 +89,7 @@ void DS18::loop() {
} }
// return string of the device, with name and address // return string of the device, with name and address
char * DS18::getDeviceString(char * buffer, unsigned char index) { char * DS18::getDeviceType(char * buffer, unsigned char index) {
uint8_t size = 128; uint8_t size = 128;
if (index < _count) { if (index < _count) {
unsigned char chip_id = chip(index); unsigned char chip_id = chip(index);
@@ -109,25 +104,22 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) {
} else { } else {
strlcpy(buffer, "Unknown", size); strlcpy(buffer, "Unknown", size);
} }
} else {
strlcpy(buffer, "invalid", size);
}
/* return buffer;
}
// return string of the device, with name and address
char * DS18::getDeviceID(char * buffer, unsigned char index) {
uint8_t size = 128;
if (index < _count) {
uint8_t * address = _devices[index].address; uint8_t * address = _devices[index].address;
char a[30] = {0}; char a[30] = {0};
snprintf(a, snprintf(a, sizeof(a), "%02X%02X%02X%02X%02X%02X%02X%02X", address[0], address[1], address[2], address[3], address[4], address[5], address[6], address[7]);
sizeof(a),
" (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
address[0],
address[1],
address[2],
address[3],
address[4],
address[5],
address[6],
address[7],
_gpio);
strlcat(buffer, a, size); strlcpy(buffer, a, size);
*/
} else { } else {
strlcpy(buffer, "invalid", size); strlcpy(buffer, "invalid", size);
} }
@@ -153,8 +145,9 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) {
byte 8: SCRATCHPAD_CRC byte 8: SCRATCHPAD_CRC
*/ */
int16_t DS18::getRawValue(unsigned char index) { int16_t DS18::getRawValue(unsigned char index) {
if (index >= _count) if (index >= _count) {
return 0; return 0;
}
uint8_t * data = _devices[index].data; uint8_t * data = _devices[index].data;
@@ -170,23 +163,35 @@ int16_t DS18::getRawValue(unsigned char index) {
} }
} else { } else {
byte cfg = (data[4] & 0x60); byte cfg = (data[4] & 0x60);
if (cfg == 0x00) if (cfg == 0x00) {
raw = raw & ~7; // 9 bit res, 93.75 ms raw = raw & ~7; // 9 bit res, 93.75 ms
else if (cfg == 0x20) } else if (cfg == 0x20) {
raw = raw & ~3; // 10 bit res, 187.5 ms raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) } else if (cfg == 0x40) {
raw = raw & ~1; // 11 bit res, 375 ms raw = raw & ~1; // 11 bit res, 375 ms
// 12 bit res, 750 ms // 12 bit res, 750 ms
}
} }
return raw; return raw;
} }
// return real value as a float // return real value as a float, rounded to 2 decimal places
// The raw temperature data is in units of sixteenths of a degree, so the value must be divided by 16 in order to convert it to degrees.
float DS18::getValue(unsigned char index) { float DS18::getValue(unsigned char index) {
float value = (float)getRawValue(index) / 16.0; int16_t raw_value = getRawValue(index);
return value;
// check if valid
if ((raw_value == DS18_CRC_ERROR) || (raw_value == DS18_DISCONNECTED)) {
return (float)DS18_DISCONNECTED;
}
// The raw temperature data is in units of sixteenths of a degree,
// so the value must first be divided by 16 in order to convert it to degrees.
float new_value = (float)(raw_value / 16.0);
// round to 2 decimal places
// https://arduinojson.org/v6/faq/how-to-configure-the-serialization-of-floats/
return (int)(new_value * 100 + 0.5) / 100.0;
} }
// check for a supported DS chip version // check for a supported DS chip version
@@ -196,8 +201,9 @@ bool DS18::validateID(unsigned char id) {
// return the type // return the type
unsigned char DS18::chip(unsigned char index) { unsigned char DS18::chip(unsigned char index) {
if (index < _count) if (index < _count) {
return _devices[index].address[0]; return _devices[index].address[0];
}
return 0; return 0;
} }
@@ -206,6 +212,7 @@ uint8_t DS18::loadDevices() {
uint8_t address[8]; uint8_t address[8];
_wire->reset(); _wire->reset();
_wire->reset_search(); _wire->reset_search();
while (_wire->search(address)) { while (_wire->search(address)) {
// Check CRC // Check CRC
if (_wire->crc8(address, 7) == address[7]) { if (_wire->crc8(address, 7) == address[7]) {

View File

@@ -36,9 +36,11 @@ class DS18 {
DS18(); DS18();
~DS18(); ~DS18();
uint8_t setup(uint8_t gpio, bool parasite); void setup(uint8_t gpio, bool parasite);
uint8_t scan();
void loop(); void loop();
char * getDeviceString(char * s, unsigned char index); char * getDeviceType(char * s, unsigned char index);
char * getDeviceID(char * buffer, unsigned char index);
float getValue(unsigned char index); float getValue(unsigned char index);
int16_t getRawValue(unsigned char index); // raw values, needs / 16 int16_t getRawValue(unsigned char index); // raw values, needs / 16

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

433
src/ems.h
View File

@@ -12,6 +12,9 @@
#include <Arduino.h> #include <Arduino.h>
#include <list> // std::list #include <list> // std::list
// EMS bus IDs
#define EMS_BUSID_DEFAULT 0x0B // Default 0x0B (Service Key)
// EMS tx_mode types // EMS tx_mode types
#define EMS_TXMODE_DEFAULT 1 // Default (was previously known as tx_mode 2 in v1.8.x) #define EMS_TXMODE_DEFAULT 1 // Default (was previously known as tx_mode 2 in v1.8.x)
#define EMS_TXMODE_EMSPLUS 2 // EMS+ #define EMS_TXMODE_EMSPLUS 2 // EMS+
@@ -26,11 +29,12 @@
#define EMS_VALUE_BOOL_ON 0x01 // boolean true #define EMS_VALUE_BOOL_ON 0x01 // boolean true
#define EMS_VALUE_BOOL_ON2 0xFF // boolean true, EMS sometimes uses 0xFF for TRUE #define EMS_VALUE_BOOL_ON2 0xFF // boolean true, EMS sometimes uses 0xFF for TRUE
#define EMS_VALUE_BOOL_OFF 0x00 // boolean false #define EMS_VALUE_BOOL_OFF 0x00 // boolean false
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit unsigned ints/bytes
#define EMS_VALUE_SHORT_NOTSET -32768 // for 2-byte signed shorts
#define EMS_VALUE_USHORT_NOTSET 0x8000 // for 2-byte unsigned shorts
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
#define EMS_VALUE_BOOL_NOTSET 0xFE // random number that's not 0, 1 or FF #define EMS_VALUE_BOOL_NOTSET 0xFE // random number that's not 0, 1 or FF
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit unsigned ints/bytes
#define EMS_VALUE_SHORT_NOTSET -32000 // 0x8300 for 2-byte signed shorts
#define EMS_VALUE_USHORT_NOTSET 32000 // 0x7D00 (was 0x8000) for 2-byte unsigned shorts
#define EMS_VALUE_USHORT_NOTVALID 0x8000 // 0x8000 for 2-byte unsigned shorts
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
// thermostat specific // thermostat specific
#define EMS_THERMOSTAT_MAXHC 4 // max number of heating circuits #define EMS_THERMOSTAT_MAXHC 4 // max number of heating circuits
@@ -38,36 +42,40 @@
#define EMS_THERMOSTAT_WRITE_YES true #define EMS_THERMOSTAT_WRITE_YES true
#define EMS_THERMOSTAT_WRITE_NO false #define EMS_THERMOSTAT_WRITE_NO false
// mixing specific
#define EMS_MIXING_MAXHC 4 // max number of heating circuits
#define EMS_MIXING_MAXWWC 2 // max number of warm water circuits
// Device Flags // Device Flags
#define EMS_DEVICE_FLAG_NONE 0 // no flags set // They are unique to the model type (mixing, solar, thermostat etc)
#define EMS_DEVICE_FLAG_SM10 10 // solar module1 enum EMS_DEVICE_FLAG_TYPES : uint8_t {
#define EMS_DEVICE_FLAG_SM100 11 // solar module2 EMS_DEVICE_FLAG_NONE = 0,
EMS_DEVICE_FLAG_MMPLUS = 20, // mixing EMS+
// group flags specific for thermostats EMS_DEVICE_FLAG_MM10 = 21, // mixing MM10
#define EMS_DEVICE_FLAG_NO_WRITE 0x80 // top bit set if write not supported EMS_DEVICE_FLAG_SM10 = 10,
#define EMS_DEVICE_FLAG_EASY 1 EMS_DEVICE_FLAG_SM100 = 11, // for SM100 and SM200
#define EMS_DEVICE_FLAG_RC10 2 EMS_DEVICE_FLAG_EASY = 1,
#define EMS_DEVICE_FLAG_RC20 3 EMS_DEVICE_FLAG_RC10 = 2,
#define EMS_DEVICE_FLAG_RC30 4 EMS_DEVICE_FLAG_RC20 = 3,
#define EMS_DEVICE_FLAG_RC35 5 EMS_DEVICE_FLAG_RC30 = 4,
#define EMS_DEVICE_FLAG_RC300 6 EMS_DEVICE_FLAG_RC30N = 5, // newer type of RC30 with RC35 circuit
#define EMS_DEVICE_FLAG_JUNKERS 7 EMS_DEVICE_FLAG_RC35 = 6,
EMS_DEVICE_FLAG_RC100 = 7,
typedef enum { EMS_DEVICE_FLAG_RC300 = 8,
EMS_THERMOSTAT_MODE_UNKNOWN, EMS_DEVICE_FLAG_RC20N = 9,
EMS_THERMOSTAT_MODE_OFF, EMS_DEVICE_FLAG_JUNKERS1 = 31, // use 0x65 for HC
EMS_THERMOSTAT_MODE_MANUAL, EMS_DEVICE_FLAG_JUNKERS2 = 32, // use 0x79 for HC, older models
EMS_THERMOSTAT_MODE_AUTO, EMS_DEVICE_FLAG_JUNKERS = (1 << 6), // 6th bit set if its junkers HT3
EMS_THERMOSTAT_MODE_NIGHT, EMS_DEVICE_FLAG_NO_WRITE = (1 << 7) // top bit set if thermostat write not supported
EMS_THERMOSTAT_MODE_DAY };
} _EMS_THERMOSTAT_MODE;
// trigger settings to determine if hot tap water or the heating is active // trigger settings to determine if hot tap water or the heating is active
#define EMS_BOILER_BURNPOWER_TAPWATER 100 #define EMS_BOILER_BURNPOWER_TAPWATER 100
#define EMS_BOILER_SELFLOWTEMP_HEATING 30 // was 70, changed to 30 for https://github.com/proddy/EMS-ESP/issues/193 #define EMS_BOILER_SELFLOWTEMP_HEATING 20 // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
// define maximum setable tapwater temperature // define min & maximum setable tapwater temperatures
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60 #define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
#define EMS_BOILER_TAPWATER_TEMPERATURE_MIN 30
#define EMS_TX_TELEGRAM_QUEUE_MAX 50 // max size of Tx FIFO queue. Number of Tx records to send. #define EMS_TX_TELEGRAM_QUEUE_MAX 50 // max size of Tx FIFO queue. Number of Tx records to send.
@@ -106,55 +114,42 @@ typedef enum {
/* EMS logging */ /* EMS logging */
typedef enum { typedef enum {
EMS_SYS_LOGGING_NONE, // no messages EMS_SYS_LOGGING_NONE, // no messages
EMS_SYS_LOGGING_RAW, // raw data mode EMS_SYS_LOGGING_RAW, // raw data mode
EMS_SYS_LOGGING_WATCH, // watch a specific type ID EMS_SYS_LOGGING_WATCH, // watch a specific type ID
EMS_SYS_LOGGING_BASIC, // only basic read/write messages EMS_SYS_LOGGING_BASIC, // only basic read/write messages
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from thermostat EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from solar module
EMS_SYS_LOGGING_VERBOSE, // everything EMS_SYS_LOGGING_MIXINGMODULE, // only telegrams sent from mixing module
EMS_SYS_LOGGING_JABBER // lots of debug output... EMS_SYS_LOGGING_VERBOSE, // everything
EMS_SYS_LOGGING_JABBER, // lots of debug output...
EMS_SYS_LOGGING_DEVICE // watch the device ID
} _EMS_SYS_LOGGING; } _EMS_SYS_LOGGING;
// status/counters since last power on // status/counters since last power on
typedef struct { typedef struct {
_EMS_RX_STATUS emsRxStatus; _EMS_RX_STATUS emsRxStatus;
_EMS_TX_STATUS emsTxStatus; _EMS_TX_STATUS emsTxStatus;
uint16_t emsRxPgks; // # successfull received uint16_t emsRxPgks; // # successfull received
uint16_t emsTxPkgs; // # successfull sent uint16_t emsTxPkgs; // # successfull sent
uint16_t emxCrcErr; // CRC errors uint16_t emxCrcErr; // CRC errors
bool emsPollEnabled; // flag enable the response to poll messages bool emsPollEnabled; // flag enable the response to poll messages
_EMS_SYS_LOGGING emsLogging; // logging _EMS_SYS_LOGGING emsLogging; // logging
uint16_t emsLogging_typeID; // the typeID to watch uint16_t emsLogging_ID; // the type or device ID to watch
uint8_t emsRefreshedFlags; // fresh data, needs to be pushed out to MQTT uint8_t emsRefreshedFlags; // fresh data, needs to be pushed out to MQTT
bool emsBusConnected; // is there an active bus bool emsBusConnected; // is there an active bus
uint32_t emsRxTimestamp; // timestamp of last EMS message received uint32_t emsRxTimestamp; // timestamp of last EMS message received
uint32_t emsPollFrequency; // time between EMS polls uint32_t emsPollFrequency; // time between EMS polls
bool emsTxCapable; // able to send via Tx bool emsTxCapable; // able to send via Tx
bool emsTxDisabled; // true to prevent all Tx bool emsTxDisabled; // true to prevent all Tx
uint8_t txRetryCount; // # times the last Tx was re-sent uint8_t txRetryCount; // # times the last Tx was re-sent
uint8_t emsIDMask; // Buderus: 0x00, Junkers: 0x80 uint8_t emsIDMask; // Buderus: 0x00, Junkers: 0x80
uint8_t emsPollAck[1]; // acknowledge buffer for Poll uint8_t emsPollAck[1]; // acknowledge buffer for Poll
uint8_t emsTxMode; // Tx mode 1, 2 or 3 uint8_t emsTxMode; // Tx mode 1, 2 or 3
char emsDeviceMap[EMS_SYS_DEVICEMAP_LENGTH]; // contents of 0x07 telegram with bitmasks for all active EMS devices uint8_t emsbusid; // EMS bus ID, default 0x0B for Service Key
uint8_t emsMasterThermostat; // product ID for the default thermostat to use
} _EMS_Sys_Status; } _EMS_Sys_Status;
// The Tx send package
typedef struct {
_EMS_TX_TELEGRAM_ACTION action; // read, write, validate, init
uint8_t dest;
uint16_t type;
uint8_t offset;
uint8_t length; // full length of complete telegram, including CRC
uint8_t dataValue; // value to validate against
uint16_t type_validate; // type to call after a successful Write command
uint8_t comparisonValue; // value to compare against during a validate command
uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation
uint16_t comparisonPostRead; // after a successful write, do a read from this type ID
unsigned long timestamp; // when created
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
} _EMS_TxTelegram;
// The Rx receive package // The Rx receive package
typedef struct { typedef struct {
unsigned long timestamp; // timestamp from millis() unsigned long timestamp; // timestamp from millis()
@@ -170,6 +165,23 @@ typedef struct {
uint8_t emsplus_type; // FF, F7 or F9 uint8_t emsplus_type; // FF, F7 or F9
} _EMS_RxTelegram; } _EMS_RxTelegram;
// The Tx send package
typedef struct {
_EMS_TX_TELEGRAM_ACTION action; // read, write, validate, init
uint8_t dest;
uint16_t type;
uint8_t offset;
uint8_t length; // full length of complete telegram, including CRC
bool emsplus; // true if ems+/ems 2.0
uint8_t dataValue; // value to validate against
uint16_t type_validate; // type to call after a successful Write command
uint8_t comparisonValue; // value to compare against during a validate command
uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation
uint16_t comparisonPostRead; // after a successful write, do a read from this type ID
unsigned long timestamp; // when created
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
} _EMS_TxTelegram;
// default empty Tx, must match struct // default empty Tx, must match struct
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = { const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
EMS_TX_TELEGRAM_INIT, // action EMS_TX_TELEGRAM_INIT, // action
@@ -177,7 +189,8 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
EMS_ID_NONE, // type EMS_ID_NONE, // type
0, // offset 0, // offset
0, // length 0, // length
0, // data value false, // emsplus (no)
0, // dataValue
EMS_ID_NONE, // type_validate EMS_ID_NONE, // type_validate
0, // comparisonValue 0, // comparisonValue
0, // comparisonOffset 0, // comparisonOffset
@@ -193,7 +206,8 @@ typedef enum : uint8_t {
EMS_DEVICE_UPDATE_FLAG_THERMOSTAT = (1 << 1), EMS_DEVICE_UPDATE_FLAG_THERMOSTAT = (1 << 1),
EMS_DEVICE_UPDATE_FLAG_MIXING = (1 << 2), EMS_DEVICE_UPDATE_FLAG_MIXING = (1 << 2),
EMS_DEVICE_UPDATE_FLAG_SOLAR = (1 << 3), EMS_DEVICE_UPDATE_FLAG_SOLAR = (1 << 3),
EMS_DEVICE_UPDATE_FLAG_HEATPUMP = (1 << 4) EMS_DEVICE_UPDATE_FLAG_HEATPUMP = (1 << 4),
EMS_DEVICE_UPDATE_FLAG_SETTINGS = (1 << 5)
} _EMS_DEVICE_UPDATE_FLAG; } _EMS_DEVICE_UPDATE_FLAG;
typedef enum : uint8_t { typedef enum : uint8_t {
@@ -205,13 +219,36 @@ typedef enum : uint8_t {
EMS_DEVICE_TYPE_SOLAR, EMS_DEVICE_TYPE_SOLAR,
EMS_DEVICE_TYPE_HEATPUMP, EMS_DEVICE_TYPE_HEATPUMP,
EMS_DEVICE_TYPE_GATEWAY, EMS_DEVICE_TYPE_GATEWAY,
EMS_DEVICE_TYPE_OTHER,
EMS_DEVICE_TYPE_SWITCH, EMS_DEVICE_TYPE_SWITCH,
EMS_DEVICE_TYPE_CONTROLLER, EMS_DEVICE_TYPE_CONTROLLER,
EMS_DEVICE_TYPE_CONNECT, EMS_DEVICE_TYPE_CONNECT,
EMS_DEVICE_TYPE_UNKNOWN EMS_DEVICE_TYPE_UNKNOWN
} _EMS_DEVICE_TYPE; } _EMS_DEVICE_TYPE;
// to store mapping of device_ids to their string name
typedef struct {
_EMS_DEVICE_TYPE device_type;
char device_type_string[30];
} _EMS_Device_Types;
// mapping for EMS_Devices_Type
const _EMS_Device_Types EMS_Devices_Types[] = {
{EMS_DEVICE_TYPE_UNKNOWN, EMS_MODELTYPE_UNKNOWN_STRING}, // the first, at index 0 is reserved for "unknown"
{EMS_DEVICE_TYPE_NONE, "All"},
{EMS_DEVICE_TYPE_SERVICEKEY, "Me"},
{EMS_DEVICE_TYPE_BOILER, "Boiler"},
{EMS_DEVICE_TYPE_THERMOSTAT, "Thermostat"},
{EMS_DEVICE_TYPE_MIXING, "Mixing Module"},
{EMS_DEVICE_TYPE_SOLAR, "Solar Module"},
{EMS_DEVICE_TYPE_HEATPUMP, "Heat Pump"},
{EMS_DEVICE_TYPE_GATEWAY, "Gateway"},
{EMS_DEVICE_TYPE_SWITCH, "Switching Module"},
{EMS_DEVICE_TYPE_CONTROLLER, "Controller"},
{EMS_DEVICE_TYPE_CONNECT, "Connect Module"}
};
// to store all known EMS devices to date // to store all known EMS devices to date
typedef struct { typedef struct {
uint8_t product_id; uint8_t product_id;
@@ -220,13 +257,6 @@ typedef struct {
uint8_t flags; uint8_t flags;
} _EMS_Device; } _EMS_Device;
// to store mapping of device_ids to their string name
typedef struct {
uint8_t device_id;
_EMS_DEVICE_TYPE device_type;
char device_type_string[30];
} _EMS_Device_Types;
// for storing all recognised EMS devices // for storing all recognised EMS devices
typedef struct { typedef struct {
_EMS_DEVICE_TYPE device_type; // type (see above) _EMS_DEVICE_TYPE device_type; // type (see above)
@@ -248,18 +278,25 @@ typedef struct {
uint8_t product_id; uint8_t product_id;
char version[10]; char version[10];
uint8_t brand; // 0=unknown, 1=bosch, 2=junkers, 3=buderus, 4=nefit, 5=sieger, 11=worcester
// UBAParameterWW // UBAParameterWW
uint8_t wWActivated; // Warm Water activated uint8_t wWActivated; // Warm Water activated
uint8_t wWSelTemp; // Warm Water selected temperature uint8_t wWSelTemp; // Warm Water selected temperature
uint8_t wWCircPump; // Warm Water circulation pump Available uint8_t wWCircPump; // Warm Water circulation pump Available
uint8_t wWDesiredTemp; // Warm Water desired temperature uint8_t wWCircPumpMode; // Warm Water circulation pump mode (1 = 1x3min, ..., 6=6x3min, 7 continuous)
uint8_t wWComfort; // Warm water comfort or ECO mode uint8_t wWCircPumpType; // Warm Water circulation pump type (0 = charge pump, 0xff = 3-way pump)
uint8_t wWDesinfectTemp; // Warm Water desinfection temperature
uint8_t wWComfort; // Warm water comfort or ECO mode
// UBAMonitorFast // UBAMonitorFast
uint8_t selFlowTemp; // Selected flow temperature uint8_t selFlowTemp; // Selected flow temperature
uint16_t curFlowTemp; // Current flow temperature uint16_t curFlowTemp; // Current flow temperature
uint16_t wwStorageTemp1; // warm water storage temp 1
uint16_t wwStorageTemp2; // warm water storage temp 2
uint16_t retTemp; // Return temperature uint16_t retTemp; // Return temperature
uint8_t burnGas; // Gas on/off uint8_t burnGas; // Gas on/off
uint8_t wWMode; // warm water mode
uint8_t fanWork; // Fan on/off uint8_t fanWork; // Fan on/off
uint8_t ignWork; // Ignition on/off uint8_t ignWork; // Ignition on/off
uint8_t heatPmp; // Circulating pump on/off uint8_t heatPmp; // Circulating pump on/off
@@ -275,6 +312,7 @@ typedef struct {
// UBAMonitorSlow // UBAMonitorSlow
int16_t extTemp; // Outside temperature int16_t extTemp; // Outside temperature
uint16_t boilTemp; // Boiler temperature uint16_t boilTemp; // Boiler temperature
uint16_t exhaustTemp; // Exhaust temperature
uint8_t pumpMod; // Pump modulation uint8_t pumpMod; // Pump modulation
uint32_t burnStarts; // # burner starts uint32_t burnStarts; // # burner starts
uint32_t burnWorkMin; // Total burner operating time uint32_t burnWorkMin; // Total burner operating time
@@ -282,11 +320,17 @@ typedef struct {
uint16_t switchTemp; // Switch temperature uint16_t switchTemp; // Switch temperature
// UBAMonitorWWMessage // UBAMonitorWWMessage
uint16_t wWCurTmp; // Warm Water current temperature uint8_t wWSetTmp; // set temp WW (DHW)
uint32_t wWStarts; // Warm Water # starts uint16_t wWCurTmp; // Warm Water current temperature
uint32_t wWWorkM; // Warm Water # minutes uint16_t wWCurTmp2; // Warm Water current temperature storage
uint8_t wWOneTime; // Warm Water one time function on/off uint32_t wWStarts; // Warm Water # starts
uint8_t wWCurFlow; // Warm Water current flow in l/min uint32_t wWWorkM; // Warm Water # minutes
uint8_t wWOneTime; // Warm Water one time function on/off
uint8_t wWDesinfecting; // Warm Water desinfection on/off
uint8_t wWReadiness; // Warm Water readiness on/off
uint8_t wWRecharging; // Warm Water recharge on/off
uint8_t wWTemperatureOK; // Warm Water temperature ok on/off
uint8_t wWCurFlow; // Warm Water current flow in l/min
// UBATotalUptimeMessage // UBATotalUptimeMessage
uint32_t UBAuptime; // Total UBA working hours uint32_t UBAuptime; // Total UBA working hours
@@ -299,7 +343,6 @@ typedef struct {
// calculated values // calculated values
uint8_t tapwaterActive; // Hot tap water is on/off uint8_t tapwaterActive; // Hot tap water is on/off
uint8_t heatingActive; // Central heating is on/off uint8_t heatingActive; // Central heating is on/off
} _EMS_Boiler; } _EMS_Boiler;
/* /*
@@ -322,34 +365,46 @@ typedef struct {
uint16_t flowTemp; uint16_t flowTemp;
uint8_t pumpMod; uint8_t pumpMod;
uint8_t valveStatus; uint8_t valveStatus;
} _EMS_Mixing_HC; uint8_t flowSetTemp;
} _EMS_MixingModule_HC;
// Mixer data // Mixing Module per WWC
typedef struct { typedef struct {
uint8_t device_id; uint8_t wwc; // warm water circuit 1, 2
uint8_t device_flags; bool active; // true if there is data for this WWC
const char * device_desc_p; uint16_t flowTemp;
uint8_t product_id; uint8_t pumpMod;
char version[10]; uint8_t tempStatus;
bool detected; } _EMS_MixingModule_WWC;
_EMS_Mixing_HC hc[EMS_THERMOSTAT_MAXHC]; // array for the 4 heating circuits
} _EMS_Mixing;
// Solar Module - SM10/SM100/ISM1 // Mixing data
typedef struct {
uint8_t device_id;
uint8_t device_flags;
const char * device_desc_p;
uint8_t product_id;
char version[10];
_EMS_MixingModule_HC hc[EMS_MIXING_MAXHC]; // array for the 4 heating circuits
_EMS_MixingModule_WWC wwc[EMS_MIXING_MAXWWC]; // array for the 2 ww circuits
} _EMS_MixingModule;
// Solar Module - SM10/SM100/SM200/ISM1
typedef struct { typedef struct {
uint8_t device_id; // the device ID of the Solar Module uint8_t device_id; // the device ID of the Solar Module
uint8_t device_flags; // Solar Module flags uint8_t device_flags; // Solar Module flags
const char * device_desc_p; const char * device_desc_p;
uint8_t product_id; uint8_t product_id;
char version[10]; char version[10];
int16_t collectorTemp; // collector temp int16_t collectorTemp; // collector temp (TS1)
int16_t bottomTemp; // bottom temp int16_t bottomTemp; // bottom temp (TS2)
int16_t bottomTemp2; // bottom temp cylinder 2 (TS5)
uint8_t pumpModulation; // modulation solar pump uint8_t pumpModulation; // modulation solar pump
uint8_t pump; // pump active uint8_t pump; // pump active
uint8_t valveStatus; // valve status (VS2)
int16_t setpoint_maxBottomTemp; // setpoint for maximum collector temp int16_t setpoint_maxBottomTemp; // setpoint for maximum collector temp
uint16_t EnergyLastHour; uint32_t EnergyLastHour;
uint16_t EnergyToday; uint32_t EnergyToday;
uint16_t EnergyTotal; uint32_t EnergyTotal;
uint32_t pumpWorkMin; // Total solar pump operating time uint32_t pumpWorkMin; // Total solar pump operating time
} _EMS_SolarModule; } _EMS_SolarModule;
@@ -360,7 +415,7 @@ typedef struct {
int16_t setpoint_roomTemp; // current set temp int16_t setpoint_roomTemp; // current set temp
int16_t curr_roomTemp; // current room temp int16_t curr_roomTemp; // current room temp
uint8_t mode; // 0=low, 1=manual, 2=auto (or night, day on RC35s) uint8_t mode; // 0=low, 1=manual, 2=auto (or night, day on RC35s)
uint8_t day_mode; // 0=night, 1=day uint8_t mode_type; // 0=night/eco, 1=day/comfort
uint8_t summer_mode; uint8_t summer_mode;
uint8_t holiday_mode; uint8_t holiday_mode;
uint8_t daytemp; uint8_t daytemp;
@@ -372,13 +427,22 @@ typedef struct {
// Thermostat data // Thermostat data
typedef struct { typedef struct {
uint8_t device_id; // the device ID of the thermostat uint8_t device_id; // the device ID of the thermostat
uint8_t device_flags; // thermostat model flags uint8_t device_flags; // thermostat model flags
const char * device_desc_p; const char * device_desc_p;
uint8_t product_id; uint8_t product_id;
char version[10]; char version[10];
char datetime[25]; // HH:MM:SS DD/MM/YYYY char datetime[25]; // HH:MM:SS DD/MM/YYYY
bool write_supported; bool write_supported;
// Installation parameters (tested on RC30)
uint8_t ibaMainDisplay; // 00, display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
uint8_t ibaLanguage; // 01, language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
uint8_t ibaCalIntTemperature; // 02, offset int. temperature sensor, by * 0.1 Kelvin
int16_t ibaMinExtTemperature; // 05, min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 (actually a int8_t, coded as int16_t to benefit from negative value rendering)
uint8_t ibaBuildingType; // 06, building type: 0 = light, 1 = medium, 2 = heavy
uint8_t ibaClockOffset; // 12, offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
_EMS_Thermostat_HC hc[EMS_THERMOSTAT_MAXHC]; // array for the 4 heating circuits _EMS_Thermostat_HC hc[EMS_THERMOSTAT_MAXHC]; // array for the 4 heating circuits
} _EMS_Thermostat; } _EMS_Thermostat;
@@ -393,49 +457,93 @@ typedef struct {
EMS_processType_cb processType_cb; EMS_processType_cb processType_cb;
} _EMS_Type; } _EMS_Type;
typedef enum : uint8_t {
EMS_THERMOSTAT_MODE_UNKNOWN = 0,
EMS_THERMOSTAT_MODE_OFF,
EMS_THERMOSTAT_MODE_MANUAL,
EMS_THERMOSTAT_MODE_AUTO,
EMS_THERMOSTAT_MODE_HEAT, // 'heizen'
EMS_THERMOSTAT_MODE_NIGHT,
EMS_THERMOSTAT_MODE_DAY,
EMS_THERMOSTAT_MODE_ECO, // 'sparen'
EMS_THERMOSTAT_MODE_COMFORT,
EMS_THERMOSTAT_MODE_HOLIDAY,
EMS_THERMOSTAT_MODE_NOFROST
} _EMS_THERMOSTAT_MODE;
#define EMS_THERMOSTAT_MODE_UNKNOWN_STR "unknown"
#define EMS_THERMOSTAT_MODE_OFF_STR "off"
#define EMS_THERMOSTAT_MODE_MANUAL_STR "manual"
#define EMS_THERMOSTAT_MODE_AUTO_STR "auto"
#define EMS_THERMOSTAT_MODE_HEAT_STR "heat"
#define EMS_THERMOSTAT_MODE_NIGHT_STR "night"
#define EMS_THERMOSTAT_MODE_DAY_STR "day"
#define EMS_THERMOSTAT_MODE_ECO_STR "eco"
#define EMS_THERMOSTAT_MODE_COMFORT_STR "comfort"
#define EMS_THERMOSTAT_MODE_HOLIDAY_STR "holiday"
#define EMS_THERMOSTAT_MODE_NOFROST_STR "nofrost"
// function definitions // function definitions
void ems_dumpBuffer(const char * prefix, uint8_t * telegram, uint8_t length); void ems_dumpBuffer(const char * prefix, uint8_t * telegram, uint8_t length);
void ems_parseTelegram(uint8_t * telegram, uint8_t len); void ems_parseTelegram(uint8_t * telegram, uint8_t len);
void ems_init(); void ems_init();
void ems_doReadCommand(uint16_t type, uint8_t dest); void ems_doReadCommand(uint16_t type, uint8_t dest, uint8_t offset = 0);
void ems_sendRawTelegram(char * telegram); void ems_sendRawTelegram(char * telegram);
void ems_scanDevices(); void ems_printDevices();
void ems_printDevices(); uint8_t ems_printDevices_s(char * buffer, uint16_t len);
uint8_t ems_printDevices_s(char * buffer, uint16_t len); void ems_printTxQueue();
void ems_printTxQueue(); void ems_testTelegram(uint8_t test_num);
void ems_testTelegram(uint8_t test_num); void ems_startupTelegrams();
void ems_startupTelegrams(); bool ems_checkEMSBUSAlive();
bool ems_checkEMSBUSAlive();
void ems_clearDeviceList(); void ems_setSettingsLanguage(uint8_t lg);
void ems_setThermostatTemp(float temperature, uint8_t hc, uint8_t temptype = 0); void ems_setSettingsBuilding(uint8_t bg);
void ems_setThermostatMode(uint8_t mode, uint8_t hc); void ems_setSettingsDisplay(uint8_t ds);
void ems_setWarmWaterTemp(uint8_t temperature);
void ems_setFlowTemp(uint8_t temperature); void ems_setThermostatTemp(float temperature, uint8_t hc, _EMS_THERMOSTAT_MODE temptype);
void ems_setWarmWaterActivated(bool activated); void ems_setThermostatTemp(float temperature, uint8_t hc, const char * mode_s);
void ems_setWarmWaterOnetime(bool activated); void ems_setThermostatMode(_EMS_THERMOSTAT_MODE mode, uint8_t hc);
void ems_setWarmTapWaterActivated(bool activated); void ems_setThermostatMode(const char * mode_s, uint8_t hc);
void ems_setPoll(bool b); void ems_setWarmWaterTemp(uint8_t temperature);
void ems_setLogging(_EMS_SYS_LOGGING loglevel, uint16_t type_id = 0); void ems_setFlowTemp(uint8_t temperature);
void ems_setWarmWaterModeComfort(uint8_t comfort); void ems_setWarmWaterActivated(bool activated);
void ems_setModels(); void ems_setWarmWaterOnetime(bool activated);
void ems_setTxDisabled(bool b); void ems_setWarmWaterCirculation(bool activated);
void ems_setTxMode(uint8_t mode); void ems_setWarmTapWaterActivated(bool activated);
char * ems_getDeviceDescription(_EMS_DEVICE_TYPE device_type, char * buffer, bool name_only = false); void ems_setPoll(bool b);
bool ems_getDeviceTypeDescription(uint8_t device_id, char * buffer); void ems_setLogging(_EMS_SYS_LOGGING loglevel, uint16_t type_id);
void ems_getThermostatValues(); void ems_setLogging(_EMS_SYS_LOGGING loglevel, bool quiet = false);
void ems_getBoilerValues(); void ems_setWarmWaterModeComfort(uint8_t comfort);
void ems_getSolarModuleValues(); void ems_setModels();
bool ems_getPoll(); void ems_setTxDisabled(bool b);
bool ems_getTxEnabled(); void ems_setTxMode(uint8_t mode);
bool ems_getThermostatEnabled(); void ems_setEMSbusid(uint8_t id);
bool ems_getMixingDeviceEnabled(); void ems_setMasterThermostat(uint8_t product_id);
bool ems_getBoilerEnabled();
bool ems_getSolarModuleEnabled(); char * ems_getDeviceDescription(_EMS_DEVICE_TYPE device_type, char * buffer, bool name_only = false);
bool ems_getHeatPumpEnabled(); bool ems_getDeviceTypeDescription(uint8_t device_id, char * buffer);
char * ems_getDeviceTypeName(_EMS_DEVICE_TYPE device_type, char * buffer);
_EMS_THERMOSTAT_MODE ems_getThermostatMode(const char * mode_s);
void ems_getThermostatValues();
void ems_getBoilerValues();
void ems_getSettingsValues();
void ems_getSolarModuleValues();
void ems_getMixingModuleValues();
char * ems_getThermostatModeString(_EMS_THERMOSTAT_MODE mode, char * mode_str);
bool ems_getPoll();
bool ems_getTxEnabled();
bool ems_getThermostatEnabled();
bool ems_getMixingModuleEnabled();
bool ems_getBoilerEnabled();
bool ems_getSolarModuleEnabled();
bool ems_getHeatPumpEnabled();
bool ems_getBusConnected(); bool ems_getBusConnected();
_EMS_SYS_LOGGING ems_getLogging(); _EMS_SYS_LOGGING ems_getLogging();
uint8_t ems_getThermostatModel(); uint8_t ems_getThermostatFlags();
uint8_t ems_getSolarModuleModel(); uint8_t ems_getSolarModuleFlags();
void ems_discoverModels(); void ems_discoverModels();
bool ems_getTxCapable(); bool ems_getTxCapable();
uint32_t ems_getPollFrequency(); uint32_t ems_getPollFrequency();
@@ -443,6 +551,8 @@ bool ems_getTxDisabled();
void ems_Device_add_flags(unsigned int flags); void ems_Device_add_flags(unsigned int flags);
bool ems_Device_has_flags(unsigned int flags); bool ems_Device_has_flags(unsigned int flags);
void ems_Device_remove_flags(unsigned int flags); void ems_Device_remove_flags(unsigned int flags);
bool ems_isHT3();
void ems_scanDevices();
// private functions // private functions
uint8_t _crcCalculator(uint8_t * data, uint8_t len); uint8_t _crcCalculator(uint8_t * data, uint8_t len);
@@ -450,14 +560,15 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram);
void _debugPrintPackage(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color); void _debugPrintPackage(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color);
void _ems_clearTxData(); void _ems_clearTxData();
void _removeTxQueue(); void _removeTxQueue();
uint8_t _getHeatingCircuit(_EMS_RxTelegram * EMS_RxTelegram); int8_t _getHeatingCircuit(_EMS_RxTelegram * EMS_RxTelegram);
void _printMessage(_EMS_RxTelegram * EMS_RxTelegram, const int show_type = -1);
// global so can referenced in other classes // global so can referenced in other classes
extern _EMS_Sys_Status EMS_Sys_Status; extern _EMS_Sys_Status EMS_Sys_Status;
extern _EMS_Boiler EMS_Boiler; extern _EMS_Boiler EMS_Boiler;
extern _EMS_Thermostat EMS_Thermostat; extern _EMS_Thermostat EMS_Thermostat;
extern _EMS_SolarModule EMS_SolarModule; extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump; extern _EMS_HeatPump EMS_HeatPump;
extern _EMS_Mixing EMS_Mixing; extern _EMS_MixingModule EMS_MixingModule;
extern std::list<_Detected_Device> Devices; extern std::list<_Detected_Device> Devices;

View File

@@ -12,41 +12,7 @@
#include "ems.h" #include "ems.h"
// Fixed EMS Device IDs // Fixed EMS Device 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_BOILER 0x08 // all UBA Boilers have 0x08
#define EMS_ID_SM 0x30 // Solar Module SM10, SM100 and ISM1
#define EMS_ID_HP 0x38 // HeatPump
#define EMS_ID_GATEWAY 0x48 // Gateway e.g. KM200 Web Gateway
#define EMS_ID_MIXING1 0x20 // Mixing
#define EMS_ID_MIXING2 0x21 // Mixing
#define EMS_ID_SWITCH 0x11 // Switch
#define EMS_ID_CONTROLLER 0x09 // Controller
#define EMS_ID_CONNECT1 0x02 // Connect
#define EMS_ID_CONNECT2 0x50 // Connect
#define EMS_ID_THERMOSTAT1 0x10 // Thermostat
#define EMS_ID_THERMOSTAT2 0x17 // Thermostat
#define EMS_ID_THERMOSTAT3 0x18 // Thermostat
// mapping for EMS_Devices_Type
const _EMS_Device_Types EMS_Devices_Types[] = {
{EMS_ID_BOILER, EMS_DEVICE_TYPE_BOILER, "UBAMaster"},
{EMS_ID_THERMOSTAT1, EMS_DEVICE_TYPE_THERMOSTAT, "Thermostat"},
{EMS_ID_THERMOSTAT2, EMS_DEVICE_TYPE_THERMOSTAT, "Thermostat"},
{EMS_ID_THERMOSTAT3, EMS_DEVICE_TYPE_THERMOSTAT, "Thermostat"},
{EMS_ID_SM, EMS_DEVICE_TYPE_SOLAR, "Solar Module"},
{EMS_ID_HP, EMS_DEVICE_TYPE_HEATPUMP, "Heat Pump"},
{EMS_ID_GATEWAY, EMS_DEVICE_TYPE_GATEWAY, "Gateway"},
{EMS_ID_ME, EMS_DEVICE_TYPE_SERVICEKEY, "Me"},
{EMS_ID_NONE, EMS_DEVICE_TYPE_NONE, "All"},
{EMS_ID_MIXING1, EMS_DEVICE_TYPE_MIXING, "Mixing Module"},
{EMS_ID_MIXING2, EMS_DEVICE_TYPE_MIXING, "Mixing Module"},
{EMS_ID_SWITCH, EMS_DEVICE_TYPE_SWITCH, "Switching Module"},
{EMS_ID_CONTROLLER, EMS_DEVICE_TYPE_CONTROLLER, "Controller"},
{EMS_ID_CONNECT1, EMS_DEVICE_TYPE_CONNECT, "Connect"},
{EMS_ID_CONNECT2, EMS_DEVICE_TYPE_CONNECT, "Connect"}
};
/* /*
* Common Type * Common Type
@@ -61,6 +27,7 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast #define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast #define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
#define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast #define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast
#define EMS_TYPE_MC10Status 0x2A
#define EMS_TYPE_UBAParameterWW 0x33 #define EMS_TYPE_UBAParameterWW 0x33
#define EMS_TYPE_UBATotalUptimeMessage 0x14 #define EMS_TYPE_UBATotalUptimeMessage 0x14
#define EMS_TYPE_UBAFlags 0x35 #define EMS_TYPE_UBAFlags 0x35
@@ -77,6 +44,7 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature #define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated #define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
#define EMS_OFFSET_UBAParameterWW_wwOneTime 0x00 // WW OneTime loading #define EMS_OFFSET_UBAParameterWW_wwOneTime 0x00 // WW OneTime loading
#define EMS_OFFSET_UBAParameterWW_wwCirulation 1 // WW circulation
#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode #define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode
#define EMS_VALUE_UBAParameterWW_wwComfort_Hot 0x00 // the value for hot #define EMS_VALUE_UBAParameterWW_wwComfort_Hot 0x00 // the value for hot
#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco #define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco
@@ -84,19 +52,73 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp #define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp
// SM and HP Types // Installation settings
#define EMS_TYPE_IBASettingsMessage 0xA5 // installation settings
#define EMS_OFFSET_IBASettings_Display 0 // display
#define EMS_OFFSET_IBASettings_Language 1 // language
#define EMS_OFFSET_IBASettings_MinExtTemp 5 // min. ext. temperature
#define EMS_OFFSET_IBASettings_Building 6 // building
#define EMS_OFFSET_IBASettings_CalIntTemp 2 // cal. int. temperature
#define EMS_OFFSET_IBASettings_ClockOffset 12 // clock offset
#define EMS_VALUE_IBASettings_LANG_GERMAN 0
#define EMS_VALUE_IBASettings_LANG_DUTCH 1
#define EMS_VALUE_IBASettings_LANG_FRENCH 2
#define EMS_VALUE_IBASettings_LANG_ITALIAN 3
#define EMS_VALUE_IBASettings_BUILDING_LIGHT 0
#define EMS_VALUE_IBASettings_BUILDING_MEDIUM 1
#define EMS_VALUE_IBASettings_BUILDING_HEAVY 2
#define EMS_VALUE_IBASettings_DISPLAY_INTTEMP 0
#define EMS_VALUE_IBASettings_DISPLAY_INTSETPOINT 1
#define EMS_VALUE_IBASettings_DISPLAY_EXTTEMP 2
#define EMS_VALUE_IBASettings_DISPLAY_BURNERTEMP 3
#define EMS_VALUE_IBASettings_DISPLAY_WWTEMP 4
#define EMS_VALUE_IBASettings_DISPLAY_FUNCMODE 5
#define EMS_VALUE_IBASettings_DISPLAY_TIME 6
#define EMS_VALUE_IBASettings_DISPLAY_DATE 7
#define EMS_VALUE_IBASettings_DISPLAY_SMOKETEMP 9
// Mixing Modules
// MM100/MM200 (EMS Plus)
#define EMS_TYPE_MMPLUSStatusMessage_HC1 0x01D7 // mixing status HC1
#define EMS_TYPE_MMPLUSStatusMessage_HC2 0x01D8 // mixing status HC2
#define EMS_TYPE_MMPLUSStatusMessage_HC3 0x01D9 // mixing status HC3
#define EMS_TYPE_MMPLUSStatusMessage_HC4 0x01DA // mixing status HC4
#define EMS_TYPE_MMPLUSStatusMessage_WWC1 0x0231 // mixing status WWC1
#define EMS_TYPE_MMPLUSStatusMessage_WWC2 0x0232 // mixing status WWC2
#define EMS_OFFSET_MMPLUSStatusMessage_flow_temp 3 // flow temperature
#define EMS_OFFSET_MMPLUSStatusMessage_pump_mod 5 // pump modulation
#define EMS_OFFSET_MMPLUSStatusMessage_valve_status 2 // valve in percent
#define EMS_OFFSET_MMPLUSStatusMessage_WW_flow_temp 0 // flow temperature
#define EMS_OFFSET_MMPLUSStatusMessage_WW_pump_mod 2 // pump on 6, off 0
#define EMS_OFFSET_MMPLUSStatusMessage_WW_temp_status 11 // 0,1,2
// MM10
#define EMS_TYPE_MMStatusMessage 0xAB // mixing status
#define EMS_OFFSET_MMStatusMessage_flow_set 0 // flow setpoint
#define EMS_OFFSET_MMStatusMessage_flow_temp 1 // flow temperature
#define EMS_OFFSET_MMStatusMessage_pump_mod 3 // pump modulation in percent
#define EMS_OFFSET_MMStatusMessage_valve_status 4 // valve 0..255
#define EMS_TYPE_MM10ParameterMessage 0xAC // mixing parameters
// Solar Module
// Assuming here that the SM200 behaves like SM100
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor #define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor #define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
#define EMS_TYPE_SM100Status 0x0264 // SM100Status #define EMS_TYPE_SM100Status 0x0264 // SM100Status
#define EMS_TYPE_SM100Status2 0x026A // SM100Status2 #define EMS_TYPE_SM100Status2 0x026A // SM100Status2
#define EMS_TYPE_SM100Energy 0x028E // SM100Energy #define EMS_TYPE_SM100Energy 0x028E // SM100Energy
#define EMS_TYPE_HPMonitor1 0xE3 // HeatPump Monitor 1 // ISPM solar module
#define EMS_TYPE_HPMonitor2 0xE5 // HeatPump Monitor 2
#define EMS_TYPE_ISM1StatusMessage 0x0003 // Solar Module Junkers ISM1 Status #define EMS_TYPE_ISM1StatusMessage 0x0003 // Solar Module Junkers ISM1 Status
#define EMS_TYPE_ISM1Set 0x0001 // for setting values of the solar module like max boiler temp #define EMS_TYPE_ISM1Set 0x0001 // for setting values of the solar module like max boiler temp
#define EMS_OFFSET_ISM1Set_MaxBoilerTemp 6 // position of max boiler temp e.g. 50 in the following example: 90 30 FF 06 00 01 50 (CRC=2C) #define EMS_OFFSET_ISM1Set_MaxBoilerTemp 6 // position of max boiler temp e.g. 50 in the following example: 90 30 FF 06 00 01 50 (CRC=2C)
// Heat Pump
#define EMS_TYPE_HPMonitor1 0xE3 // HeatPump Monitor 1
#define EMS_TYPE_HPMonitor2 0xE5 // HeatPump Monitor 2
/* /*
* Thermostat Types * Thermostat Types
*/ */
@@ -118,6 +140,16 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_OFFSET_RC20StatusMessage_setpoint 1 // setpoint temp #define EMS_OFFSET_RC20StatusMessage_setpoint 1 // setpoint temp
#define EMS_OFFSET_RC20StatusMessage_curr 2 // current temp #define EMS_OFFSET_RC20StatusMessage_curr 2 // current temp
#define EMS_TYPE_RC20NStatusMessage 0xAE
#define EMS_OFFSET_RC20NStatusMessage_setpoint 2 // setpoint temp in AE
#define EMS_OFFSET_RC20NStatusMessage_curr 3 // current temp in AE
#define EMS_TYPE_RC20NSet 0xAD
#define EMS_OFFSET_RC20NSet_temp_day 2 // position of thermostat setpoint temperature for day time
#define EMS_OFFSET_RC20NSet_temp_night 1 // position of thermostat setpoint temperature for night time
#define EMS_OFFSET_RC20NSet_mode 3 // position mode
#define EMS_OFFSET_RC20NSet_heatingtype 0
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode #define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode #define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature #define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
@@ -142,7 +174,6 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_OFFSET_RC35StatusMessage_mode 1 // day mode, also summer on RC3's #define EMS_OFFSET_RC35StatusMessage_mode 1 // day mode, also summer on RC3's
#define EMS_OFFSET_RC35StatusMessage_mode1 0 // for holiday mode #define EMS_OFFSET_RC35StatusMessage_mode1 0 // for holiday mode
#define EMS_TYPE_RC35Set_HC1 0x3D // for setting values like temp and mode (Working mode HC1) #define EMS_TYPE_RC35Set_HC1 0x3D // for setting values like temp and mode (Working mode HC1)
#define EMS_TYPE_RC35Set_HC2 0x47 // for setting values like temp and mode (Working mode HC2) #define EMS_TYPE_RC35Set_HC2 0x47 // for setting values like temp and mode (Working mode HC2)
#define EMS_TYPE_RC35Set_HC3 0x51 // for setting values like temp and mode (Working mode HC3) #define EMS_TYPE_RC35Set_HC3 0x51 // for setting values like temp and mode (Working mode HC3)
@@ -151,9 +182,10 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode #define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
#define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time #define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
#define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time #define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time
#define EMS_OFFSET_RC35Set_temp_holiday 3 // temp during holiday 0x47 #define EMS_OFFSET_RC35Set_temp_holiday 3 // temp during holiday mode
#define EMS_OFFSET_RC35Set_heatingtype 0 // floor heating = 3 0x47 #define EMS_OFFSET_RC35Set_heatingtype 0 // e.g. floor heating = 3
#define EMS_OFFSET_RC35Set_circuitcalctemp 14 // calculated circuit temperature 0x48 #define EMS_OFFSET_RC35Set_circuitcalctemp 14 // calculated circuit temperature
#define EMS_OFFSET_RC35Set_seltemp 37 // selected temp
// Easy specific // Easy specific
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat #define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
@@ -180,21 +212,38 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
#define EMS_OFFSET_RCPLUSSet_temp_setpoint 8 // temp setpoint, when changing of templevel (in auto) value is reset and set to FF #define EMS_OFFSET_RCPLUSSet_temp_setpoint 8 // temp setpoint, when changing of templevel (in auto) value is reset and set to FF
#define EMS_OFFSET_RCPLUSSet_manual_setpoint 10 // manual setpoint #define EMS_OFFSET_RCPLUSSet_manual_setpoint 10 // manual setpoint
// Junkers FR10, FR50, FW100 (EMS Plus) // Junkers FR10, FR50, FW100, FW120 (EMS Plus)
#define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps // HC1 = 0x6F-0x72
#define EMS_TYPE_JunkersStatusMessage_HC1 0x6F
#define EMS_TYPE_JunkersStatusMessage_HC2 0x70
#define EMS_TYPE_JunkersStatusMessage_HC3 0x71
#define EMS_TYPE_JunkersStatusMessage_HC4 0x72
#define EMS_OFFSET_JunkersStatusMessage_daymode 0 // 3 = day, 2 = night #define EMS_OFFSET_JunkersStatusMessage_daymode 0 // 3 = day, 2 = night
#define EMS_OFFSET_JunkersStatusMessage_mode 1 // current mode, 1 = manual, 2 = auto #define EMS_OFFSET_JunkersStatusMessage_mode 1 // current mode, 1 = manual, 2 = auto
#define EMS_OFFSET_JunkersStatusMessage_setpoint 2 // setpoint temp #define EMS_OFFSET_JunkersStatusMessage_setpoint 2 // setpoint temp
#define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp #define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp
// MM100 (EMS Plus) // HC1-4 0x65-0x68 - EMS_DEVICE_FLAG_JUNKERS1
#define EMS_TYPE_MMPLUSStatusMessage_HC1 0x01D7 // mixer status HC1 // Junkers FR10, FR50, FW100, FW120
#define EMS_TYPE_MMPLUSStatusMessage_HC2 0x01D8 // mixer status HC2 #define EMS_TYPE_JunkersSetMessage1_HC1 0x65
#define EMS_TYPE_MMPLUSStatusMessage_HC3 0x01D9 // mixer status HC3 #define EMS_TYPE_JunkersSetMessage1_HC2 0x66
#define EMS_TYPE_MMPLUSStatusMessage_HC4 0x01DA // mixer status HC4 #define EMS_TYPE_JunkersSetMessage1_HC3 0x67
#define EMS_OFFSET_MMPLUSStatusMessage_flow_temp 3 // flow temperature #define EMS_TYPE_JunkersSetMessage1_HC4 0x68
#define EMS_OFFSET_MMPLUSStatusMessage_pump_mod 5 // pump modulation #define EMS_OFFSET_JunkersSetMessage_day_temp 0x11 // EMS offset to set temperature on thermostat for day mode
#define EMS_OFFSET_MMPLUSStatusMessage_valve_status 2 // valve in percent #define EMS_OFFSET_JunkersSetMessage_night_temp 0x10 // EMS offset to set temperature on thermostat for night mode
#define EMS_OFFSET_JunkersSetMessage_no_frost_temp 0x0F // EMS offset to set temperature on thermostat for no frost mode
#define EMS_OFFSET_JunkersSetMessage_set_mode 0x0E // EMS offset to set mode on thermostat
// HC1-4 0x79-0x7C - EMS_DEVICE_FLAG_JUNKERS2
// Junkers FR100
#define EMS_TYPE_JunkersSetMessage2_HC1 0x79
#define EMS_TYPE_JunkersSetMessage2_HC2 0x7A
#define EMS_TYPE_JunkersSetMessage2_HC3 0x7B
#define EMS_TYPE_JunkersSetMessage2_HC4 0x7C
#define EMS_OFFSET_JunkersSetMessage2_no_frost_temp 0x05
#define EMS_OFFSET_JunkersSetMessage2_eco_temp 0x06
#define EMS_OFFSET_JunkersSetMessage3_heat 0x07
/* /*
* Table of all known EMS Devices * Table of all known EMS Devices
@@ -203,7 +252,7 @@ const _EMS_Device_Types EMS_Devices_Types[] = {
static const _EMS_Device EMS_Devices[] = { static const _EMS_Device EMS_Devices[] = {
// //
// UBA Masters - typically with device_id of 0x08 // UBA Masters - must have device_id of 0x08
// //
{72, EMS_DEVICE_TYPE_BOILER, "MC10 Module", EMS_DEVICE_FLAG_NONE}, {72, EMS_DEVICE_TYPE_BOILER, "MC10 Module", EMS_DEVICE_FLAG_NONE},
{123, EMS_DEVICE_TYPE_BOILER, "Buderus GBx72/Nefit Trendline/Junkers Cerapur/Worcester Greenstar Si/27i", EMS_DEVICE_FLAG_NONE}, {123, EMS_DEVICE_TYPE_BOILER, "Buderus GBx72/Nefit Trendline/Junkers Cerapur/Worcester Greenstar Si/27i", EMS_DEVICE_FLAG_NONE},
@@ -213,7 +262,8 @@ static const _EMS_Device EMS_Devices[] = {
{208, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax plus/GB192/Bosch Condens GC9000", EMS_DEVICE_FLAG_NONE}, {208, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax plus/GB192/Bosch Condens GC9000", EMS_DEVICE_FLAG_NONE},
{64, EMS_DEVICE_TYPE_BOILER, "Sieger BK13,BK15/Nefit Smartline/Buderus GB1x2", EMS_DEVICE_FLAG_NONE}, {64, EMS_DEVICE_TYPE_BOILER, "Sieger BK13,BK15/Nefit Smartline/Buderus GB1x2", EMS_DEVICE_FLAG_NONE},
{234, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax Plus GB122", EMS_DEVICE_FLAG_NONE}, {234, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax Plus GB122", EMS_DEVICE_FLAG_NONE},
{95, EMS_DEVICE_TYPE_BOILER, "Bosch Condens 2500/Buderus Logamax GB062/Junkers Cerapur Top/Worcester Greenstar i/Generic HT3", EMS_DEVICE_FLAG_NONE}, {84, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax Plus GB022", EMS_DEVICE_FLAG_NONE},
{95, EMS_DEVICE_TYPE_BOILER, "Bosch Condens 2500/Buderus Logamax,Logomatic/Junkers Cerapur Top/Worcester Greenstar i/Generic HT3", EMS_DEVICE_FLAG_NONE},
{122, EMS_DEVICE_TYPE_BOILER, "Nefit Proline", EMS_DEVICE_FLAG_NONE}, {122, EMS_DEVICE_TYPE_BOILER, "Nefit Proline", EMS_DEVICE_FLAG_NONE},
{170, EMS_DEVICE_TYPE_BOILER, "Buderus Logano GB212", EMS_DEVICE_FLAG_NONE}, {170, EMS_DEVICE_TYPE_BOILER, "Buderus Logano GB212", EMS_DEVICE_FLAG_NONE},
{172, EMS_DEVICE_TYPE_BOILER, "Nefit Enviline", EMS_DEVICE_FLAG_NONE}, {172, EMS_DEVICE_TYPE_BOILER, "Nefit Enviline", EMS_DEVICE_FLAG_NONE},
@@ -223,19 +273,17 @@ static const _EMS_Device EMS_Devices[] = {
// //
{73, EMS_DEVICE_TYPE_SOLAR, "SM10 Solar Module", EMS_DEVICE_FLAG_SM10}, {73, EMS_DEVICE_TYPE_SOLAR, "SM10 Solar Module", EMS_DEVICE_FLAG_SM10},
{163, EMS_DEVICE_TYPE_SOLAR, "SM100 Solar Module", EMS_DEVICE_FLAG_SM100}, {163, EMS_DEVICE_TYPE_SOLAR, "SM100 Solar Module", EMS_DEVICE_FLAG_SM100},
{164, EMS_DEVICE_TYPE_SOLAR, "SM200 Solar Module", EMS_DEVICE_FLAG_SM100},
{101, EMS_DEVICE_TYPE_SOLAR, "Junkers ISM1 Solar Module", EMS_DEVICE_FLAG_SM100}, {101, EMS_DEVICE_TYPE_SOLAR, "Junkers ISM1 Solar Module", EMS_DEVICE_FLAG_SM100},
{162, EMS_DEVICE_TYPE_SOLAR, "SM50 Solar Module", EMS_DEVICE_FLAG_SM100}, {162, EMS_DEVICE_TYPE_SOLAR, "SM50 Solar Module", EMS_DEVICE_FLAG_SM100},
// //
// Mixing Devices - type 0x20 or 0x21 // Mixing Devices - type 0x20 or 0x21
// //
{160, EMS_DEVICE_TYPE_MIXING, "MM100 Mixing Module", EMS_DEVICE_FLAG_NONE}, {160, EMS_DEVICE_TYPE_MIXING, "MM100 Mixing Module", EMS_DEVICE_FLAG_MMPLUS},
{161, EMS_DEVICE_TYPE_MIXING, "MM200 Mixing Module", EMS_DEVICE_FLAG_NONE}, {161, EMS_DEVICE_TYPE_MIXING, "MM200 Mixing Module", EMS_DEVICE_FLAG_MMPLUS},
{69, EMS_DEVICE_TYPE_MIXING, "MM10 Mixer Module", EMS_DEVICE_FLAG_NONE}, {69, EMS_DEVICE_TYPE_MIXING, "MM10 Mixing Module", EMS_DEVICE_FLAG_MM10},
{159, EMS_DEVICE_TYPE_MIXING, "MM50 Mixing Module", EMS_DEVICE_FLAG_NONE}, {159, EMS_DEVICE_TYPE_MIXING, "MM50 Mixing Module", EMS_DEVICE_FLAG_MMPLUS},
{79, EMS_DEVICE_TYPE_MIXING, "MM100 Mixer Module", EMS_DEVICE_FLAG_NONE},
{80, EMS_DEVICE_TYPE_MIXING, "MM200 Mixer Module", EMS_DEVICE_FLAG_NONE},
{78, EMS_DEVICE_TYPE_MIXING, "MM400 Mixer Module", EMS_DEVICE_FLAG_NONE},
// //
// HeatPump - type 0x38 // HeatPump - type 0x38
@@ -244,25 +292,29 @@ static const _EMS_Device EMS_Devices[] = {
{200, EMS_DEVICE_TYPE_HEATPUMP, "HeatPump Module", EMS_DEVICE_FLAG_NONE}, {200, EMS_DEVICE_TYPE_HEATPUMP, "HeatPump Module", EMS_DEVICE_FLAG_NONE},
// //
// Other devices, like 0x11 for Switching, 0x09 for controllers, 0x02 for Connect, 0x48 for Gateway // Other devices like controllers and modems
// such as 0x11 for Switching, 0x09 for controllers, 0x02 for Connect, 0x48 for Gateway
// //
{71, EMS_DEVICE_TYPE_SWITCH, "WM10 Switch Module", EMS_DEVICE_FLAG_NONE}, // 0x11 {71, EMS_DEVICE_TYPE_SWITCH, "WM10 Switch Module", EMS_DEVICE_FLAG_NONE}, // 0x11
{68, EMS_DEVICE_TYPE_CONTROLLER, "BC10/RFM20 Receiver", EMS_DEVICE_FLAG_NONE}, // 0x09 {68, EMS_DEVICE_TYPE_CONTROLLER, "BC10/RFM20 Receiver", EMS_DEVICE_FLAG_NONE}, // 0x09
{218, EMS_DEVICE_TYPE_CONTROLLER, "Junkers M200/Buderus RFM200 Receiver", EMS_DEVICE_FLAG_NONE}, // 0x50 {218, EMS_DEVICE_TYPE_CONTROLLER, "Junkers M200/Buderus RFM200 Receiver", EMS_DEVICE_FLAG_NONE}, // 0x50
{190, EMS_DEVICE_TYPE_CONTROLLER, "BC10 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {190, EMS_DEVICE_TYPE_CONTROLLER, "BC10 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{114, EMS_DEVICE_TYPE_CONTROLLER, "BC10 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {114, EMS_DEVICE_TYPE_CONTROLLER, "BC10 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{125, EMS_DEVICE_TYPE_CONTROLLER, "BC25 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {125, EMS_DEVICE_TYPE_CONTROLLER, "BC25 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{169, EMS_DEVICE_TYPE_CONTROLLER, "BC40 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {169, EMS_DEVICE_TYPE_CONTROLLER, "BC40 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{152, EMS_DEVICE_TYPE_CONTROLLER, "Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {152, EMS_DEVICE_TYPE_CONTROLLER, "Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{95, EMS_DEVICE_TYPE_CONTROLLER, "HT3 Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {95, EMS_DEVICE_TYPE_CONTROLLER, "HT3 Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{230, EMS_DEVICE_TYPE_CONTROLLER, "BC Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09 {209, EMS_DEVICE_TYPE_CONTROLLER, "W-B ErP Boiler Control Panel", EMS_DEVICE_FLAG_NONE}, // 0x09
{205, EMS_DEVICE_TYPE_CONNECT, "Nefit Moduline Easy Connect", EMS_DEVICE_FLAG_NONE}, // 0x02 {230, EMS_DEVICE_TYPE_CONTROLLER, "BC Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
{206, EMS_DEVICE_TYPE_CONNECT, "Bosch Easy Connect", EMS_DEVICE_FLAG_NONE}, // 0x02 {205, EMS_DEVICE_TYPE_CONNECT, "Nefit Moduline Easy Connect", EMS_DEVICE_FLAG_NONE}, // 0x02
{171, EMS_DEVICE_TYPE_CONNECT, "EMS-OT OpenTherm converter", EMS_DEVICE_FLAG_NONE}, // 0x02 {206, EMS_DEVICE_TYPE_CONNECT, "Bosch Easy Connect", EMS_DEVICE_FLAG_NONE}, // 0x02
{189, EMS_DEVICE_TYPE_GATEWAY, "Web Gateway KM200", EMS_DEVICE_FLAG_NONE}, // 0x48 {171, EMS_DEVICE_TYPE_CONNECT, "EMS-OT OpenTherm converter", EMS_DEVICE_FLAG_NONE}, // 0x02
{189, EMS_DEVICE_TYPE_GATEWAY, "Web Gateway KM200", EMS_DEVICE_FLAG_NONE}, // 0x48
{94, EMS_DEVICE_TYPE_GATEWAY, "RC Remote Device", EMS_DEVICE_FLAG_NONE}, // 0x18
{207, EMS_DEVICE_TYPE_CONTROLLER, "Worcester Sense II/Bosch CS200 Solar Controller", EMS_DEVICE_FLAG_NONE}, // 0x10
// //
// Thermostats, typically device id of 0x10, 0x17 and 0x18 // Thermostats, typically device id of 0x10, 0x17, 0x18, 0x38 (RC100), 0x39 (Easy)
// //
// Easy devices - not currently supporting write operations // Easy devices - not currently supporting write operations
@@ -270,28 +322,30 @@ static const _EMS_Device EMS_Devices[] = {
{203, EMS_DEVICE_TYPE_THERMOSTAT, "Bosch EasyControl CT200", EMS_DEVICE_FLAG_EASY | EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {203, EMS_DEVICE_TYPE_THERMOSTAT, "Bosch EasyControl CT200", EMS_DEVICE_FLAG_EASY | EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
{157, EMS_DEVICE_TYPE_THERMOSTAT, "Buderus RC200/Bosch CW100/Junkers CW100", EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {157, EMS_DEVICE_TYPE_THERMOSTAT, "Buderus RC200/Bosch CW100/Junkers CW100", EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
// Buderus/Nefit // Buderus/Nefit specific
{79, EMS_DEVICE_TYPE_THERMOSTAT, "RC10/Moduline 100", EMS_DEVICE_FLAG_RC10}, // 0x17 {79, EMS_DEVICE_TYPE_THERMOSTAT, "RC10/Moduline 100", EMS_DEVICE_FLAG_RC10}, // 0x17
{80, EMS_DEVICE_TYPE_THERMOSTAT, "Moduline 200", EMS_DEVICE_FLAG_RC10}, // 0x17
{77, EMS_DEVICE_TYPE_THERMOSTAT, "RC20/Moduline 300", EMS_DEVICE_FLAG_RC20}, // 0x17 {77, EMS_DEVICE_TYPE_THERMOSTAT, "RC20/Moduline 300", EMS_DEVICE_FLAG_RC20}, // 0x17
{93, EMS_DEVICE_TYPE_THERMOSTAT, "RC20RF", EMS_DEVICE_FLAG_RC20}, // 0x18 {67, EMS_DEVICE_TYPE_THERMOSTAT, "RC30", EMS_DEVICE_FLAG_RC30N}, // 0x10 - based on RC35
{67, EMS_DEVICE_TYPE_THERMOSTAT, "RC30", EMS_DEVICE_FLAG_RC30}, // 0x10 {78, EMS_DEVICE_TYPE_THERMOSTAT, "Moduline 400", EMS_DEVICE_FLAG_RC30}, // 0x10
{78, EMS_DEVICE_TYPE_THERMOSTAT, "RC30/Moduline 400", EMS_DEVICE_FLAG_RC30}, // 0x10
{86, EMS_DEVICE_TYPE_THERMOSTAT, "RC35", EMS_DEVICE_FLAG_RC35}, // 0x10 {86, EMS_DEVICE_TYPE_THERMOSTAT, "RC35", EMS_DEVICE_FLAG_RC35}, // 0x10
{93, EMS_DEVICE_TYPE_THERMOSTAT, "RC20RF", EMS_DEVICE_FLAG_RC20}, // 0x19
{158, EMS_DEVICE_TYPE_THERMOSTAT, "RC300/RC310/Moduline 3000/Bosch CW400/W-B Sense II", EMS_DEVICE_FLAG_RC300}, // 0x10 {158, EMS_DEVICE_TYPE_THERMOSTAT, "RC300/RC310/Moduline 3000/Bosch CW400/W-B Sense II", EMS_DEVICE_FLAG_RC300}, // 0x10
{165, EMS_DEVICE_TYPE_THERMOSTAT, "RC100/Moduline 1010", EMS_DEVICE_FLAG_RC300 | EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {165, EMS_DEVICE_TYPE_THERMOSTAT, "RC100/Moduline 1000/1010", EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
// Sieger // Sieger
{076, EMS_DEVICE_TYPE_THERMOSTAT, "Sieger ES73", EMS_DEVICE_FLAG_RC35}, // 0x10 {76, EMS_DEVICE_TYPE_THERMOSTAT, "Sieger ES73", EMS_DEVICE_FLAG_RC35}, // 0x10
{113, EMS_DEVICE_TYPE_THERMOSTAT, "Sieger ES72/Buderus RC20", EMS_DEVICE_FLAG_RC20N}, // 0x17
// Junkers // Junkers - all 0x10
{105, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW100", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {105, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW100", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
{106, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW200", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {106, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW200", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
{107, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR100", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {107, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR100", EMS_DEVICE_FLAG_JUNKERS2}, // 0x10
{108, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR110", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {108, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR110", EMS_DEVICE_FLAG_JUNKERS2}, // 0x10
{111, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR10", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {111, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR10", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
{191, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR120", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {147, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR50", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
{192, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW120", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE}, // 0x10, cannot write {191, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR120", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
{147, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR50", EMS_DEVICE_FLAG_JUNKERS | EMS_DEVICE_FLAG_NO_WRITE} // 0x10, cannot write {192, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW120", EMS_DEVICE_FLAG_JUNKERS1} // 0x10
}; };

View File

@@ -76,11 +76,11 @@ char * _short_to_char(char * s, int16_t value, uint8_t decimals) {
return s; return s;
} }
// convert short (two bytes) to text string and prints it // convert unsigned short (two bytes) to text string and prints it
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100 // decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals) { char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals) {
// remove errors or invalid values // remove errors or invalid values
if (value == EMS_VALUE_USHORT_NOTSET) { if (value == EMS_VALUE_USHORT_NOTSET) { // 0x7D00
strlcpy(s, "?", 10); strlcpy(s, "?", 10);
return (s); return (s);
} }
@@ -149,6 +149,7 @@ void _renderUShortValue(const char * prefix, const char * postfix, uint16_t valu
// convert int (single byte) to text value and returns it // convert int (single byte) to text value and returns it
char * _int_to_char(char * s, uint8_t value, uint8_t div) { char * _int_to_char(char * s, uint8_t value, uint8_t div) {
s[0] = '\0'; // reset
if (value == EMS_VALUE_INT_NOTSET) { if (value == EMS_VALUE_INT_NOTSET) {
strlcpy(s, "?", sizeof(s)); strlcpy(s, "?", sizeof(s));
return (s); return (s);
@@ -200,7 +201,7 @@ void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, u
} }
// takes a long value at prints it to debug log and prints // takes a long value at prints it to debug log and prints
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value) { void _renderLongValue(const char * prefix, const char * postfix, uint32_t value, uint8_t div) {
static char buffer[200] = {0}; static char buffer[200] = {0};
strlcpy(buffer, " ", sizeof(buffer)); strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer)); strlcat(buffer, prefix, sizeof(buffer));
@@ -210,7 +211,13 @@ void _renderLongValue(const char * prefix, const char * postfix, uint32_t value)
strlcat(buffer, "?", sizeof(buffer)); strlcat(buffer, "?", sizeof(buffer));
} else { } else {
char s[20] = {0}; char s[20] = {0};
strlcat(buffer, ltoa(value, s, 10), sizeof(buffer)); if (div == 0) {
strlcat(buffer, ltoa(value, s, 10), sizeof(buffer));
} else {
strlcat(buffer, ltoa(value / 10, s, 10), sizeof(buffer));
strlcat(buffer, ".", sizeof(buffer));
strlcat(buffer, ltoa(value % 10, s, 10), sizeof(buffer));
}
} }
if (postfix != nullptr) { if (postfix != nullptr) {

View File

@@ -22,7 +22,7 @@ void _renderShortValue(const char * prefix, const char * postfix, int16_t va
void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals = 1); void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals = 1);
char * _int_to_char(char * s, uint8_t value, uint8_t div = 1); char * _int_to_char(char * s, uint8_t value, uint8_t div = 1);
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1); void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1);
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value); void _renderLongValue(const char * prefix, const char * postfix, uint32_t value, uint8_t div = 0);
void _renderBoolValue(const char * prefix, uint8_t value); void _renderBoolValue(const char * prefix, uint8_t value);
char * _hextoa(uint8_t value, char * buffer); char * _hextoa(uint8_t value, char * buffer);
char * _smallitoa(uint8_t value, char * buffer); char * _smallitoa(uint8_t value, char * buffer);

View File

@@ -225,26 +225,26 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
emsuart_tx_brk(); // send <BRK> emsuart_tx_brk(); // send <BRK>
} else if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_DEFAULT) { } else if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_DEFAULT) {
/* /*
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch * 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. * 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 * after sending the last char we poll the Rx status until either
* - size(Rx FIFO) == size(Tx-Telegram) * - size(Rx FIFO) == size(Tx-Telegram)
* - <BRK> is detected * - <BRK> is detected
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode. * At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
* *
* EMS-Bus error handling * EMS-Bus error handling
* 1. Busmaster stops echoing on Tx w/o permission * 1. Busmaster stops echoing on Tx w/o permission
* 2. Busmaster cancel telegram by sending a BRK * 2. Busmaster cancel telegram by sending a BRK
* *
* Case 1. is handled by a watchdog counter which is reset on each * Case 1. is handled by a watchdog counter which is reset on each
* Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus * Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus
* some smart guess for processing time on targeted EMS device. * some smart guess for processing time on targeted EMS device.
* We set EMS_Sys_Status.emsTxStatus to EMS_TX_WTD_TIMEOUT and return * We set EMS_Sys_Status.emsTxStatus to EMS_TX_WTD_TIMEOUT and return
* *
* Case 2. is handled via a BRK chk during transmission. * Case 2. is handled via a BRK chk during transmission.
* We set EMS_Sys_Status.emsTxStatus to EMS_TX_BRK_DETECT and return * We set EMS_Sys_Status.emsTxStatus to EMS_TX_BRK_DETECT and return
* *
*/ */
uint16_t wdc = EMS_TX_TO_COUNT; uint16_t wdc = EMS_TX_TO_COUNT;
ETS_UART_INTR_DISABLE(); // disable rx interrupt ETS_UART_INTR_DISABLE(); // disable rx interrupt

View File

@@ -10,8 +10,9 @@
#include "ems.h" #include "ems.h"
// TOPICS with _CMD_ are used for receiving commands from an MQTT Broker // TOPICS with *_CMD_* are used for receiving commands from an MQTT Broker
// EMS-ESP will subscribe to these topics // EMS-ESP will subscribe to these topics
#define TOPIC_GENERIC_CMD "generic_cmd" // for receiving generic system commands via MQTT #define TOPIC_GENERIC_CMD "generic_cmd" // for receiving generic system commands via MQTT
// MQTT for thermostat // MQTT for thermostat
@@ -26,38 +27,57 @@
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "daytemp" // day temp (RC35 specific) #define TOPIC_THERMOSTAT_CMD_DAYTEMP "daytemp" // day temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "nighttemp" // night temp (RC35 specific) #define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "nighttemp" // night temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "holidayttemp" // holiday temp (RC35 specific) #define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "holidayttemp" // holiday temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NOFROSTTEMP "nofrosttemp" // frost temp (Junkers specific)
#define TOPIC_THERMOSTAT_CMD_ECOTEMP "ecotemp" // eco temp (Junkers specific)
#define TOPIC_THERMOSTAT_CMD_HEATTEMP "heattemp" // heat temp (Junkers specific)
#define THERMOSTAT_CURRTEMP "currtemp" // current temperature #define THERMOSTAT_CURRTEMP "currtemp" // current temperature
#define THERMOSTAT_SELTEMP "seltemp" // selected temperature #define THERMOSTAT_SELTEMP "seltemp" // selected temperature
#define THERMOSTAT_HC "hc" // which heating circuit number #define THERMOSTAT_HC "hc" // which heating circuit number
#define THERMOSTAT_MODE "mode" // mode #define THERMOSTAT_MODE "mode" // mode
#define THERMOSTAT_MODETYPE "modetype" // mode type
#define THERMOSTAT_DAYTEMP "daytemp" // RC35 specific #define THERMOSTAT_DAYTEMP "daytemp" // RC35 specific
#define THERMOSTAT_NIGHTTEMP "nighttemp" // RC35 specific #define THERMOSTAT_NIGHTTEMP "nighttemp" // RC35 specific
#define THERMOSTAT_HOLIDAYTEMP "holidayttemp" // RC35 specific #define THERMOSTAT_HOLIDAYTEMP "holidayttemp" // RC35 specific
#define THERMOSTAT_HEATINGTYPE "heatingtype" // RC35 specific (3=floorheating) #define THERMOSTAT_HEATINGTYPE "heatingtype" // RC35 specific (3=floorheating)
#define THERMOSTAT_CIRCUITCALCTEMP "circuitcalctemp" // RC35 specific #define THERMOSTAT_CIRCUITCALCTEMP "circuitcalctemp" // RC35 specific
// mixing module
#define MIXING_HC "hc" // which heating circuit number
#define MIXING_WWC "wwc" // which warm water circuit number
// MQTT for boiler // MQTT for boiler
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT #define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running #define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on #define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
#define TOPIC_BOILER_CMD "boiler_cmd" // for receiving boiler commands via MQTT #define TOPIC_BOILER_CMD "boiler_cmd" // for receiving boiler commands via MQTT
#define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // change water on/off #define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // change water on/off
#define TOPIC_BOILER_CMD_WWONETIME "boiler_cmd_wwonetime" // warm warter one time loading #define TOPIC_BOILER_CMD_WWONETIME "boiler_cmd_wwonetime" // warm warter one time loading
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // wwtemp changes via MQTT #define TOPIC_BOILER_CMD_WWCIRCULATION "boiler_cmd_wwcirculation" // start warm warter circulation
#define TOPIC_BOILER_CMD_COMFORT "comfort" // ww comfort setting via MQTT #define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // wwtemp changes via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "flowtemp" // flowtemp value via MQTT #define TOPIC_BOILER_CMD_COMFORT "comfort" // ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "flowtemp" // flowtemp value via MQTT
// MQTT for settings
#define TOPIC_SETTINGS_DATA "settings_data" // for sending settings values to MQTT
#define TOPIC_SETTINGS_CMD "settings_cmd" // for receiving settings commands via MQTT
#define TOPIC_SETTINGS_CMD_DISPLAY "display" // change display
#define TOPIC_SETTINGS_CMD_LANGUAGE "language" // change language
#define TOPIC_SETTINGS_CMD_BUILDING "building" // change building
#define TOPIC_SETTINGS_CMD_MINEXTTEMP "minextTemp" // change min. ext. temp.
// MQTT for mixing device // MQTT for mixing device
#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT #define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT
// MQTT for SM10/SM100 Solar Module // MQTT for SM10/SM100/SM200 Solar Module
#define TOPIC_SM_DATA "sm_data" // topic name #define TOPIC_SM_DATA "sm_data" // topic name
#define SM_COLLECTORTEMP "collectortemp" // collector temp #define SM_COLLECTORTEMP "collectortemp" // collector temp
#define SM_BOTTOMTEMP "bottomtemp" // bottom temp #define SM_BOTTOMTEMP "bottomtemp" // bottom temp1
#define SM_BOTTOMTEMP2 "bottomtemp2" // bottom temp2
#define SM_PUMPMODULATION "pumpmodulation" // pump modulation #define SM_PUMPMODULATION "pumpmodulation" // pump modulation
#define SM_PUMP "pump" // pump active #define SM_PUMP "pump" // pump active
#define SM_VALVESTATUS "valvestatus" // valve status
#define SM_ENERGYLASTHOUR "energylasthour" // energy last hour #define SM_ENERGYLASTHOUR "energylasthour" // energy last hour
#define SM_ENERGYTODAY "energytoday" // energy today #define SM_ENERGYTODAY "energytoday" // energy today
#define SM_ENERGYTOTAL "energytotal" // energy total #define SM_ENERGYTOTAL "energytotal" // energy total
@@ -76,5 +96,7 @@
#define TOPIC_SHOWER_DURATION "duration" // duration of the last shower #define TOPIC_SHOWER_DURATION "duration" // duration of the last shower
// MQTT for External Sensors // MQTT for External Sensors
#define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT #define TOPIC_EXTERNAL_SENSORS "sensors" // topic for sending sensor values to MQTT
#define PAYLOAD_EXTERNAL_SENSORS "temp_%d" // for formatting the payload for each external dallas sensor #define PAYLOAD_EXTERNAL_SENSOR_NUM "sensor" // which sensor #
#define PAYLOAD_EXTERNAL_SENSOR_ID "id"
#define PAYLOAD_EXTERNAL_SENSOR_TEMP "temp"

View File

@@ -1 +1 @@
#define APP_VERSION "1.9.4" #define APP_VERSION "1.9.5"

View File

@@ -181,9 +181,9 @@
<div class="row form-group"> <div class="row form-group">
<label class="col-xs-3">Port<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" <label class="col-xs-3">Port<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Server port number"></i></label> data-content="MQTT Server port number (default 1883)"></i></label>
<span class="col-xs-9"> <span class="col-xs-9">
<input class="form-control input-sm" placeholder="1883" value="" style="display:inline;max-width:185px" <input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
id="mqttport" type="text"> id="mqttport" type="text">
</span> </span>
<br> <br>
@@ -191,9 +191,9 @@
<div class="row form-group"> <div class="row form-group">
<label class="col-xs-3">QOS<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" <label class="col-xs-3">QOS<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT QOS 0,1 or 2"></i></label> data-content="MQTT QOS 0, 1 or 2 (default 0)"></i></label>
<span class="col-xs-9"> <span class="col-xs-9">
<input class="form-control input-sm" placeholder="1" value="" style="display:inline;max-width:185px" <input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
id="mqttqos" type="text"> id="mqttqos" type="text">
</span> </span>
<br> <br>
@@ -201,9 +201,9 @@
<div class="row form-group"> <div class="row form-group">
<label class="col-xs-3">Keep Alive<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" <label class="col-xs-3">Keep Alive<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Keep Alive time in seconds"></i></label> data-content="MQTT Keep Alive time in seconds (default 60)"></i></label>
<span class="col-xs-9"> <span class="col-xs-9">
<input class="form-control input-sm" placeholder="60" value="" style="display:inline;max-width:185px" <input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
id="mqttkeepalive" type="text"> id="mqttkeepalive" type="text">
</span> </span>
<br> <br>
@@ -246,14 +246,14 @@
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT topic prefix (&lt;mqtt base&gt;/&lt;host name&gt;/)"></i></label> data-content="MQTT topic prefix (&lt;mqtt base&gt;/&lt;host name&gt;/)"></i></label>
<span class="col-xs-9"> <span class="col-xs-9">
<input class="form-control input-sm" placeholder="home" value="" style="display:inline;max-width:185px" <input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
id="mqttbase" type="text"> id="mqttbase" type="text">
</span> </span>
</div> </div>
<div class="row form-group"> <div class="row form-group">
<label class="col-xs-3">Heartbeat<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" <label class="col-xs-3">Heartbeat<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Enable or Disable an automatic MQTT topic publish with system stats"></i></label> data-content="Enable or Disable an automatic MQTT topic publish with system status"></i></label>
<div class="col-xs-9"> <div class="col-xs-9">
<form> <form>
<label class="radio-inline"> <label class="radio-inline">
@@ -508,16 +508,6 @@
<table class="table table-hover table-striped table-condensed" border=1> <table class="table table-hover table-striped table-condensed" border=1>
<caption>MQTT&nbsp;&nbsp;<div id="mqttconnected"></div>&nbsp;<div id="mqttheartbeat"></div> <caption>MQTT&nbsp;&nbsp;<div id="mqttconnected"></div>&nbsp;<div id="mqttheartbeat"></div>
</caption> </caption>
<thead>
<tr>
<th>Time</th>
<th>Topic<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
id="mqttloghdr"></i></th>
<th>Payload</th>
</tr>
</thead>
<tbody id="mqttlog"></tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@@ -391,40 +391,6 @@ function listStats() {
document.getElementById("mqttheartbeat").className = "label label-primary"; document.getElementById("mqttheartbeat").className = "label label-primary";
} }
document.getElementById("mqttloghdr").setAttribute('data-content', "Topics are prefixed with " + ajaxobj.mqttloghdr);
var mtable = document.getElementById("mqttlog");
var obj = ajaxobj.mqttlog;
var tr, td;
for (var i = 0; i < obj.length; i++) {
tr = document.createElement("tr");
td = document.createElement("td");
if (obj[i].time < 1563300000) {
td.innerHTML = "(" + obj[i].time + ")";
} else {
var vuepoch = new Date(obj[i].time * 1000);
td.innerHTML = vuepoch.getUTCFullYear() +
"-" + twoDigits(vuepoch.getUTCMonth() + 1) +
"-" + twoDigits(vuepoch.getUTCDate()) +
" " + twoDigits(vuepoch.getUTCHours()) +
":" + twoDigits(vuepoch.getUTCMinutes()) +
":" + twoDigits(vuepoch.getUTCSeconds());
}
tr.appendChild(td);
td = document.createElement("td");
td.innerHTML = obj[i].topic
tr.appendChild(td);
td = document.createElement("td");
td.innerHTML = obj[i].payload
tr.appendChild(td);
mtable.appendChild(tr);
}
} }
function getContent(contentname) { function getContent(contentname) {

View File

@@ -97,8 +97,9 @@ var custom_configfile = {
"listen_mode": false, "listen_mode": false,
"shower_timer": true, "shower_timer": true,
"shower_alert": false, "shower_alert": false,
"publish_time": 0, "publish_time": 10,
"tx_mode": 1 "tx_mode": 1,
"bus_id": 11
} }
}; };
@@ -118,17 +119,7 @@ function sendStatus() {
"systemload": 0, "systemload": 0,
"mqttconnected": true, "mqttconnected": true,
"mqttheartbeat": false, "mqttheartbeat": false,
"uptime": "0 days 0 hours 1 minute 45 seconds", "uptime": "0 days 0 hours 1 minute 45 seconds"
"mqttloghdr": "home/ems-esp/",
"mqttlog": [
{ "topic": "start", "payload": "start", "time": 1565956388 },
{ "topic": "shower_timer", "payload": "1", "time": 1565956388 },
{ "topic": "shower_alert", "payload": "0", "time": 1565956388 },
{ "topic": "boiler_data", "payload": "{\"wWComfort\":\"Hot\",\"wWSelTemp\":60,\"selFlowTemp\":5,\"selBurnPow\":0,\"curBurnPow\":0,\"pumpMod\":0,\"wWCurTmp\":48.4,\"wWCurFlow\":0,\"curFlowTemp\":49.3,\"retTemp\":49.3,\"sysPress\":1.8,\"boilTemp\":50.5,\"wWActivated\":\"on\",\"burnGas\":\"off\",\"heatPmp\":\"off\",\"fanWork\":\"off\",\"ignWork\":\"off\",\"wWCirc\":\"off\",\"wWHeat\":\"on\",\"burnStarts\":223397,\"burnWorkMin\":366019,\"heatWorkMin\":294036,\"ServiceCode\":\"0H\",\"ServiceCodeNumber\":203}", "time": 1565956463 },
{ "topic": "tapwater_active", "payload": "0", "time": 1565956408 },
{ "topic": "heating_active", "payload": "0", "time": 1565956408 },
{ "topic": "thermostat_data", "payload": "{\"thermostat_hc\":\"1\",\"thermostat_seltemp\":15,\"thermostat_currtemp\":23,\"thermostat_mode\":\"auto\"}", "time": 1565956444 }
]
}; };
wss.broadcast(stats); wss.broadcast(stats);
@@ -170,8 +161,7 @@ function sendCustomStatus() {
"b2": "off", "b2": "off",
"b3": 0, "b3": 0,
"b4": 53, "b4": 53,
"b5": 54.4, "b5": 54.4
"b6": 53.3
}, },
"sm": { "sm": {