This commit is contained in:
Paul
2020-05-25 17:13:05 +02:00
parent b2bb8e2b5a
commit d3953d90ca
29 changed files with 461 additions and 657 deletions

View File

@@ -41,7 +41,7 @@ Note: Version 2.0 is not backward compatible with v1.0. The File system structur
common commands available in all contexts: common commands available in all contexts:
exit exit
help help
log [level] [trace ID] log [level] [trace ID] [raw]
su su
(top level) (top level)
@@ -124,7 +124,6 @@ thermostat
### **Known issues, bugs and improvements currently working on** ### **Known issues, bugs and improvements currently working on**
``` ```
TODO when doing show, should we sort the ems devices?
TODO figure out why sometimes telnet on ESP32 (and sometimes ESP8266) has slow response times. After a manual reset it seems to fix itself. Perhaps the telnet service needs to start after the wifi is up & running. TODO figure out why sometimes telnet on ESP32 (and sometimes ESP8266) has slow response times. After a manual reset it seems to fix itself. Perhaps the telnet service needs to start after the wifi is up & running.
TODO Get the ESP32 UART code working. TODO Get the ESP32 UART code working.
TODO sometimes with tx_mode 0 there are a few CRC errors due to collision when waiting for a BRK signal. TODO sometimes with tx_mode 0 there are a few CRC errors due to collision when waiting for a BRK signal.
@@ -152,4 +151,6 @@ TODO See if it's easier to use timers instead of millis() timers, using https://
``` ```
TODO merge in the web code which has the Captive AP and better wifi reconnect logic. Use IPV6 and NTP from lwip2 TODO merge in the web code which has the Captive AP and better wifi reconnect logic. Use IPV6 and NTP from lwip2
TODO decide if I want to port over the shower one-shot cold water logic. Don't think its used. TODO decide if I want to port over the shower one-shot cold water logic. Don't think its used.
TODO when doing show in telnet, should we sort the ems devices?
``` ```

View File

@@ -21,7 +21,7 @@ extra_configs = pio_local.ini
;debug_flags = -DDEBUG_ESP_PORT=Serial -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM ;debug_flags = -DDEBUG_ESP_PORT=Serial -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM
debug_flags = debug_flags =
; -D EMSESP_DEBUG -D EMSESP_DEBUG
; -D EMSESP_SAFE_MODE ; -D EMSESP_SAFE_MODE
; -D ENABLE_CORS -D CORS_ORIGIN=\"http://localhost:3000\" ; -D ENABLE_CORS -D CORS_ORIGIN=\"http://localhost:3000\"
@@ -33,7 +33,7 @@ build_flags = -std=c++11 -Os -fno-exceptions
-D ARDUINOJSON_USE_LONG_LONG=0 -D ARDUINOJSON_USE_LONG_LONG=0
-D BEARSSL_SSL_BASIC -D BEARSSL_SSL_BASIC
-D PROGMEM_WWW -D PROGMEM_WWW
-D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=0 -D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=1
libs_core = libs_core =
ArduinoJson ArduinoJson
@@ -58,18 +58,18 @@ check_flags =
clangtidy: --checks=-*,clang-analyzer-*,performance-* clangtidy: --checks=-*,clang-analyzer-*,performance-*
; USB upload ; USB upload
upload_protocol = esptool ; upload_protocol = esptool
; example ports for OSX ; example ports for OSX
;upload_port = /dev/cu.wchusbserial14403 ;upload_port = /dev/cu.wchusbserial14403
;upload_port = /dev/cu.usbserial-1440 ;upload_port = /dev/cu.usbserial-1440
;upload_port = /dev/cu.SLAB_USBtoUART ;upload_port = /dev/cu.SLAB_USBtoUART
; OTA upload ; OTA upload
; upload_protocol = espota upload_protocol = espota
; upload_flags = upload_flags =
; --port=8266 --port=8266
; --auth=neo --auth=neo
; upload_port = ems-esp.local upload_port = ems-esp32.local
[env:esp8266] [env:esp8266]
build_type = release build_type = release

View File

@@ -46,7 +46,7 @@ uuid::log::Logger Boiler::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
DEBUG_LOG(F("Registering new Boiler with device ID 0x%02X"), device_id); LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id);
// the telegram handlers... // the telegram handlers...
register_telegram_type(0x18, F("UBAMonitorFast"), true, std::bind(&Boiler::process_UBAMonitorFast, this, _1)); register_telegram_type(0x18, F("UBAMonitorFast"), true, std::bind(&Boiler::process_UBAMonitorFast, this, _1));
@@ -91,7 +91,7 @@ void Boiler::boiler_cmd(const char * message) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
DeserializationError error = deserializeJson(doc, message); DeserializationError error = deserializeJson(doc, message);
if (error) { if (error) {
DEBUG_LOG(F("MQTT error: payload %s, error %s"), message, error.c_str()); LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
return; return;
} }
const char * command = doc["cmd"]; const char * command = doc["cmd"];
@@ -312,7 +312,7 @@ void Boiler::publish_values() {
} }
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
DEBUG_LOG(F("[DEBUG] Performing a boiler publish")); LOG_DEBUG(F("[DEBUG] Performing a boiler publish"));
#endif #endif
// if we have data, publish it // if we have data, publish it
@@ -675,31 +675,31 @@ void Boiler::process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> tele
// Set the warm water temperature 0x33 // Set the warm water temperature 0x33
void Boiler::set_warmwater_temp(const uint8_t temperature) { void Boiler::set_warmwater_temp(const uint8_t temperature) {
logger_.info(F("Setting boiler warm water temperature to %d C"), temperature); LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature);
write_command(EMS_TYPE_UBAParameterWW, 2, temperature); write_command(EMS_TYPE_UBAParameterWW, 2, temperature);
} }
// flow temp // flow temp
void Boiler::set_flow_temp(const uint8_t temperature) { void Boiler::set_flow_temp(const uint8_t temperature) {
logger_.info(F("Setting boiler flow temperature to %d C"), temperature); LOG_INFO(F("Setting boiler flow temperature to %d C"), temperature);
write_command(EMS_TYPE_UBASetPoints, 0, temperature); write_command(EMS_TYPE_UBASetPoints, 0, temperature);
} }
// 1=hot, 2=eco, 3=intelligent // 1=hot, 2=eco, 3=intelligent
void Boiler::set_warmwater_mode(const uint8_t comfort) { void Boiler::set_warmwater_mode(const uint8_t comfort) {
if (comfort == 1) { if (comfort == 1) {
logger_.info(F("Setting boiler warm water to hot")); LOG_INFO(F("Setting boiler warm water to hot"));
} else if (comfort == 2) { } else if (comfort == 2) {
logger_.info(F("Setting boiler warm water to eco")); LOG_INFO(F("Setting boiler warm water to eco"));
} else if (comfort == 3) { } else if (comfort == 3) {
logger_.info(F("Setting boiler warm water to intelligent")); LOG_INFO(F("Setting boiler warm water to intelligent"));
} }
write_command(EMS_TYPE_UBAParameterWW, 9, comfort); write_command(EMS_TYPE_UBAParameterWW, 9, comfort);
} }
// turn on/off warm water // turn on/off warm water
void Boiler::set_warmwater_activated(const bool activated) { void Boiler::set_warmwater_activated(const bool activated) {
logger_.info(F("Setting boiler warm water %s"), activated ? "on" : "off"); LOG_INFO(F("Setting boiler warm water %s"), activated ? "on" : "off");
uint8_t value; uint8_t value;
// https://github.com/proddy/EMS-ESP/issues/268 // https://github.com/proddy/EMS-ESP/issues/268
@@ -715,7 +715,7 @@ void Boiler::set_warmwater_activated(const bool activated) {
// true = on, false = off // true = on, false = off
// Note: Using the type 0x1D to put the boiler into Test mode. This may be shown on the boiler with a flashing 'T' // Note: Using the type 0x1D to put the boiler into Test mode. This may be shown on the boiler with a flashing 'T'
void Boiler::set_tapwarmwater_activated(const bool activated) { void Boiler::set_tapwarmwater_activated(const bool activated) {
logger_.info(F("Setting boiler warm tap water %s"), activated ? "on" : "off"); LOG_INFO(F("Setting boiler warm tap water %s"), activated ? "on" : "off");
uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH]; uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH];
for (uint8_t i = 0; i < sizeof(message_data); i++) { for (uint8_t i = 0; i < sizeof(message_data); i++) {
message_data[i] = 0x00; message_data[i] = 0x00;
@@ -743,14 +743,14 @@ void Boiler::set_tapwarmwater_activated(const bool activated) {
// true = on, false = off // true = on, false = off
// See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers // See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers
void Boiler::set_warmwater_onetime(const bool activated) { void Boiler::set_warmwater_onetime(const bool activated) {
logger_.info(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off"); LOG_INFO(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off");
write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02)); write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02));
} }
// Activate / De-activate circulation of warm water 0x35 // Activate / De-activate circulation of warm water 0x35
// true = on, false = off // true = on, false = off
void Boiler::set_warmwater_circulation(const bool activated) { void Boiler::set_warmwater_circulation(const bool activated) {
logger_.info(F("Setting boiler warm water circulation %s"), activated ? "on" : "off"); LOG_INFO(F("Setting boiler warm water circulation %s"), activated ? "on" : "off");
write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02)); write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02));
} }

View File

@@ -221,8 +221,9 @@ void Console::load_standard_commands(unsigned int context) {
context, context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(log)}, flash_string_vector{F_(log)},
flash_string_vector{F_(log_level_optional), F_(traceid_optional)}, flash_string_vector{F_(log_level_optional), F_(traceid_optional), F_(raw_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t watch_id;
if (!arguments.empty()) { if (!arguments.empty()) {
uuid::log::Level level; uuid::log::Level level;
@@ -233,15 +234,33 @@ void Console::load_standard_commands(unsigned int context) {
return; return;
} }
// see if we have extra argument, for trace // trace logic
uint16_t watch_id = 0; // no watch ID set if (level == uuid::log::Level::TRACE) {
if ((arguments.size() == 2) && (level == uuid::log::Level::TRACE)) { watch_id = LOG_TRACE_WATCH_NONE; // no watch ID set
watch_id = Helpers::hextoint(arguments[1].c_str()); if (arguments.size() > 1) {
shell.printfln(("Tracing only telegrams that match a device ID or telegram type of 0x%02X"), watch_id); watch_id = Helpers::hextoint(arguments[1].c_str()); // get the watch ID
}
emsesp::EMSESP::trace_watch_id(watch_id);
// see if we have a "raw"
if ((arguments.size() == 3) && (arguments[2] == read_flash_string(F_(raw)))) {
emsesp::EMSESP::trace_raw(true);
} else {
emsesp::EMSESP::trace_raw(false);
}
} }
emsesp::EMSESP::trace_watch_id(watch_id);
} }
// print out logging settings
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level())); shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level()));
watch_id = emsesp::EMSESP::trace_watch_id();
if (watch_id == LOG_TRACE_WATCH_NONE) {
shell.printfln(F("Tracing all telegrams"));
} else {
shell.printfln(F("Tracing only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
}
shell.printfln(F_(trace_raw_fmt), emsesp::EMSESP::trace_raw() ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str());
}, },
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> { [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
return uuid::log::levels_lowercase(); return uuid::log::levels_lowercase();
@@ -259,7 +278,7 @@ void Console::load_standard_commands(unsigned int context) {
flash_string_vector{F_(exit)}, flash_string_vector{F_(exit)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
// delete MAIN console stuff first to save memory // delete MAIN console stuff first to save memory
EMSESPShell::commands->remove_context_commands(context); EMSESPShell::commands->remove_context_commands(context);
shell.exit_context(); shell.exit_context();
}); });
@@ -384,6 +403,8 @@ EMSESPStreamConsole::~EMSESPStreamConsole() {
#endif #endif
ptys_[pty_] = false; ptys_[pty_] = false;
ptys_.shrink_to_fit(); ptys_.shrink_to_fit();
EMSESPShell::commands->remove_all_commands();
} }
} }
@@ -414,7 +435,7 @@ void Console::start() {
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash // note, this must be started after the network/wifi for ESP32 otherwise it'll crash
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
telnet_.start(); telnet_.start();
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second // telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
#endif #endif
} }

View File

@@ -41,8 +41,12 @@ using uuid::log::Level;
// clang-format off // clang-format off
#define DEBUG_LOG(...) if (logger_.enabled(Level::DEBUG)) {logger_.debug(__VA_ARGS__);} #define LOG_DEBUG(...) if (logger_.enabled(Level::DEBUG)) {logger_.debug(__VA_ARGS__);}
#define TRACE_LOG(...) if (logger_.enabled(Level::TRACE)) {logger_.trace(__VA_ARGS__);} #define LOG_TRACE(...) if (logger_.enabled(Level::TRACE)) {logger_.trace(__VA_ARGS__);}
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
#define LOG_NOTICE(...) logger_.notice(__VA_ARGS__)
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = string_literal; #define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = string_literal;
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name) #define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
@@ -84,6 +88,7 @@ MAKE_PSTR_WORD(debug)
MAKE_PSTR_WORD(restart) MAKE_PSTR_WORD(restart)
MAKE_PSTR_WORD(reconnect) MAKE_PSTR_WORD(reconnect)
MAKE_PSTR_WORD(format) MAKE_PSTR_WORD(format)
MAKE_PSTR_WORD(raw)
// context menus // context menus
MAKE_PSTR_WORD(mqtt) MAKE_PSTR_WORD(mqtt)
@@ -96,6 +101,8 @@ MAKE_PSTR(asterisks, "********")
MAKE_PSTR(n_mandatory, "<n>") MAKE_PSTR(n_mandatory, "<n>")
MAKE_PSTR(n_optional, "[n]") MAKE_PSTR(n_optional, "[n]")
MAKE_PSTR(traceid_optional, "[trace ID]") MAKE_PSTR(traceid_optional, "[trace ID]")
MAKE_PSTR(trace_raw_fmt, "Displaying raw bytes = %s")
MAKE_PSTR(raw_optional, "[raw]")
MAKE_PSTR(bool_mandatory, "<on | off>") MAKE_PSTR(bool_mandatory, "<on | off>")
MAKE_PSTR(typeid_mandatory, "<type ID>") MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>") MAKE_PSTR(deviceid_mandatory, "<device ID>")

View File

@@ -160,7 +160,7 @@ void EMSdevice::show_values(uuid::console::Shell & shell) {
// for each telegram that has the fetch value set (true) do a read request // for each telegram that has the fetch value set (true) do a read request
void EMSdevice::fetch_values() { void EMSdevice::fetch_values() {
DEBUG_LOG(F("Fetching values for device ID 0x%02X"), device_id()); LOG_DEBUG(F("Fetching values for device ID 0x%02X"), device_id());
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.fetch_) { if (tf.fetch_) {
@@ -188,7 +188,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
} }
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) { void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) {
DEBUG_LOG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_); LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
Mqtt::subscribe(this->device_id_, topic, f); Mqtt::subscribe(this->device_id_, topic, f);
} }
@@ -230,7 +230,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegr
bool EMSdevice::process_telegram(std::shared_ptr<const Telegram> telegram) { bool EMSdevice::process_telegram(std::shared_ptr<const Telegram> telegram) {
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram->type_id) { if (tf.telegram_type_id_ == telegram->type_id) {
DEBUG_LOG(F("Processing %s..."), uuid::read_flash_string(tf.telegram_type_name_).c_str()); LOG_DEBUG(F("Processing %s..."), uuid::read_flash_string(tf.telegram_type_name_).c_str());
tf.process_function_(telegram); tf.process_function_(telegram);
return true; return true;
} }

View File

@@ -56,7 +56,8 @@ Shower EMSESP::shower_; // Shower logic
// static/common variables // static/common variables
uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found
uint16_t EMSESP::trace_watch_id_ = 0; // for when log is TRACE uint16_t EMSESP::trace_watch_id_ = LOG_TRACE_WATCH_NONE; // for when log is TRACE. 0 means no trace set
bool EMSESP::trace_raw_ = false; // not showing raw when in trace logging
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower() bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
bool EMSESP::ems_read_only_; bool EMSESP::ems_read_only_;
uint32_t EMSESP::last_fetch_ = 0; uint32_t EMSESP::last_fetch_ = 0;
@@ -332,7 +333,7 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP) // if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
// when the version info is received, it will automagically add the device // when the version info is received, it will automagically add the device
if ((device_id != ems_bus_id) && !(EMSESP::device_exists(device_id))) { if ((device_id != ems_bus_id) && !(EMSESP::device_exists(device_id))) {
logger_.info(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id); LOG_INFO(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id);
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id); send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
} }
} }
@@ -391,9 +392,11 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
// We also check for common telgram types, like the Version(0x02) // We also check for common telgram types, like the Version(0x02)
// returns false if there are none found // returns false if there are none found
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) { bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// print to console if logging is TRACE if ((logger_.enabled(Level::TRACE)) && !trace_raw()) {
if ((trace_watch_id_ == 0) || (telegram->src == trace_watch_id_) || (telegram->dest == trace_watch_id_) || (telegram->type_id == trace_watch_id_)) { if ((trace_watch_id_ == LOG_TRACE_WATCH_NONE) || (telegram->src == trace_watch_id_) || (telegram->dest == trace_watch_id_)
TRACE_LOG(pretty_telegram(telegram).c_str()); || (telegram->type_id == trace_watch_id_)) {
LOG_TRACE(pretty_telegram(telegram).c_str());
}
} }
// only process broadcast telegrams or ones sent to us on request // only process broadcast telegrams or ones sent to us on request
@@ -431,7 +434,7 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
} }
if (!found) { if (!found) {
DEBUG_LOG(F("No telegram type handler found for type ID 0x%02X (src 0x%02X, dest 0x%02X)"), telegram->type_id, telegram->src, telegram->dest); LOG_DEBUG(F("No telegram type handler found for type ID 0x%02X (src 0x%02X, dest 0x%02X)"), telegram->type_id, telegram->src, telegram->dest);
} }
return found; return found;
@@ -504,7 +507,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice) { if (emsdevice) {
if (emsdevice->is_device_id(device_id)) { if (emsdevice->is_device_id(device_id)) {
DEBUG_LOG(F("Updating details for already existing device with ID 0x%02X"), device_id); LOG_DEBUG(F("Updating details for already existing device with ID 0x%02X"), device_id);
emsdevice->product_id(product_id); emsdevice->product_id(product_id);
emsdevice->version(version); emsdevice->version(version);
emsdevice->brand(brand); emsdevice->brand(brand);
@@ -535,10 +538,10 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
// if we don't recognize the product ID report it, but don't add it. // if we don't recognize the product ID report it, but don't add it.
if (!found) { if (!found) {
logger_.notice(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id); LOG_NOTICE(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id);
return false; // not found return false; // not found
} else { } else {
DEBUG_LOG(F("Adding new device with device ID 0x%02X with product ID %d"), device_id, product_id); LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d"), device_id, product_id);
// go and fetch its data, including asking for the version // go and fetch its data, including asking for the version
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id); send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
fetch_device_values(device_id); fetch_device_values(device_id);
@@ -578,12 +581,12 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
EMSbus::tx_waiting(false); // reset Tx wait state EMSbus::tx_waiting(false); // reset Tx wait state
if (length == 1) { if (length == 1) {
if (first_value == TxService::TX_WRITE_SUCCESS) { if (first_value == TxService::TX_WRITE_SUCCESS) {
DEBUG_LOG(F("Last Tx write successful. Sending read request.")); LOG_DEBUG(F("Last Tx write successful. Sending read request."));
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
txservice_.send_poll(); // close the bus txservice_.send_poll(); // close the bus
txservice_.post_send_query(); // send type_id to last destination txservice_.post_send_query(); // send type_id to last destination
} else if (first_value == TxService::TX_WRITE_FAIL) { } else if (first_value == TxService::TX_WRITE_FAIL) {
DEBUG_LOG(F("Last Tx write rejected by host")); LOG_DEBUG(F("Last Tx write rejected by host"));
txservice_.send_poll(); // close the bus txservice_.send_poll(); // close the bus
} else { } else {
// ignore it, it's probably a poll and we can wait for the next one // ignore it, it's probably a poll and we can wait for the next one
@@ -595,7 +598,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
uint8_t src = data[0]; uint8_t src = data[0];
uint8_t dest = data[1]; uint8_t dest = data[1];
if (txservice_.is_last_tx(src, dest)) { if (txservice_.is_last_tx(src, dest)) {
DEBUG_LOG(F("Last Tx read successful")); LOG_DEBUG(F("Last Tx read successful"));
txservice_.increment_telegram_read_count(); txservice_.increment_telegram_read_count();
txservice_.send_poll(); txservice_.send_poll();
} else { } else {
@@ -603,9 +606,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
// So re-send the last Tx and increment retry count // So re-send the last Tx and increment retry count
uint8_t retries = txservice_.retry_tx(); // returns 0 if exceeded count uint8_t retries = txservice_.retry_tx(); // returns 0 if exceeded count
if (retries) { if (retries) {
DEBUG_LOG(F("Last Tx read failed. Retrying #%d..."), retries); LOG_DEBUG(F("Last Tx read failed. Retrying #%d..."), retries);
} else { } else {
DEBUG_LOG(F("Last Tx read failed. Giving up")); LOG_DEBUG(F("Last Tx read failed. Giving up"));
} }
} }
} }
@@ -634,7 +637,7 @@ void EMSESP::send_raw_telegram(const char * data) {
// sets the ems read only flag preventing any Tx from going out // sets the ems read only flag preventing any Tx from going out
void EMSESP::set_ems_read_only() { void EMSESP::set_ems_read_only() {
ems_read_only_ = Settings().ems_read_only(); ems_read_only_ = Settings().ems_read_only();
DEBUG_LOG(F("Setting EMS read-only mode to %s"), ems_read_only_ ? F("on") : F("off")); LOG_DEBUG(F("Setting EMS read-only mode to %s"), ems_read_only_ ? F("on") : F("off"));
} }
// console commands to add // console commands to add
@@ -786,7 +789,7 @@ void EMSESP::console_commands(Shell & shell, unsigned int context) {
// kick off the party, start all the services // kick off the party, start all the services
void EMSESP::start() { void EMSESP::start() {
// Load our libary of known devices // Load our library of known devices
device_library_ = { device_library_ = {
#include "device_library.h" #include "device_library.h"
}; };
@@ -798,6 +801,7 @@ void EMSESP::start() {
rxservice_.start(); rxservice_.start();
txservice_.start(); txservice_.start();
shower_.start(); shower_.start();
mqtt_.start();
set_ems_read_only(); // see if we have Tx disabled and set the flag set_ems_read_only(); // see if we have Tx disabled and set the flag
} }

View File

@@ -45,6 +45,8 @@
#include "boiler.h" #include "boiler.h"
#include "shower.h" #include "shower.h"
#define LOG_TRACE_WATCH_NONE 0 // no watch set
namespace emsesp { namespace emsesp {
class Shower; // forward declaration for compiler class Shower; // forward declaration for compiler
@@ -79,8 +81,6 @@ class EMSESP {
static uint8_t actual_master_thermostat(); static uint8_t actual_master_thermostat();
static void actual_master_thermostat(const uint8_t device_id); static void actual_master_thermostat(const uint8_t device_id);
static void trace_watch_id(uint16_t);
static void show_values(uuid::console::Shell & shell); static void show_values(uuid::console::Shell & shell);
static void show_devices(uuid::console::Shell & shell); static void show_devices(uuid::console::Shell & shell);
static void show_emsbus(uuid::console::Shell & shell); static void show_emsbus(uuid::console::Shell & shell);
@@ -93,10 +93,20 @@ class EMSESP {
return sensors_.devices(); return sensors_.devices();
} }
static void trace_watch_id(uint16_t id);
static uint16_t trace_watch_id() { static uint16_t trace_watch_id() {
return trace_watch_id_; return trace_watch_id_;
} }
static void trace_raw(bool trace_raw) {
trace_raw_ = trace_raw;
}
static bool trace_raw() {
return trace_raw_;
}
static bool tap_water_active() { static bool tap_water_active() {
return tap_water_active_; return tap_water_active_;
} }
@@ -151,6 +161,7 @@ class EMSESP {
static uint8_t actual_master_thermostat_; static uint8_t actual_master_thermostat_;
static uint16_t trace_watch_id_; static uint16_t trace_watch_id_;
static bool trace_raw_;
static bool tap_water_active_; static bool tap_water_active_;
static bool ems_read_only_; static bool ems_read_only_;
}; };

View File

@@ -35,7 +35,7 @@ uuid::log::Logger Heatpump::logger_{F_(logger_name), uuid::log::Facility::CONSOL
Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
DEBUG_LOG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id); LOG_DEBUG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id);
// telegram handlers // telegram handlers
register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1)); register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1));

View File

@@ -265,7 +265,8 @@ std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
} }
// takes a hex string and convert it to a 32bit number (max 8 hex digits) // takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
// works with only positive numbers
uint32_t Helpers::hextoint(const char * hex) { uint32_t Helpers::hextoint(const char * hex) {
uint32_t val = 0; uint32_t val = 0;
while (*hex) { while (*hex) {

View File

@@ -26,7 +26,7 @@ uuid::log::Logger Mixing::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}
Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
DEBUG_LOG(F("Registering new Mixing module with device ID 0x%02X"), device_id); LOG_DEBUG(F("Registering new Mixing module with device ID 0x%02X"), device_id);
// telegram handlers 0x20 - 0x27 for HC // telegram handlers 0x20 - 0x27 for HC
register_telegram_type(0x02D7, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1)); register_telegram_type(0x02D7, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1));
@@ -105,7 +105,7 @@ void Mixing::publish_values() {
} }
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
DEBUG_LOG(F("[DEBUG] Performing a mixing module publish")); LOG_DEBUG(F("[DEBUG] Performing a mixing module publish"));
#endif #endif
char topic[30]; char topic[30];

View File

@@ -93,11 +93,11 @@ void Mqtt::reconnect() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
mqttClient_.disconnect(); mqttClient_.disconnect();
#endif #endif
DEBUG_LOG(F("Reconnecting...")); LOG_DEBUG(F("Reconnecting..."));
} }
// MQTT setup // MQTT setup
void Mqtt::start() { void Mqtt::setup() {
// exit if already initialized // exit if already initialized
if (mqtt_start_) { if (mqtt_start_) {
return; return;
@@ -145,7 +145,7 @@ void Mqtt::start() {
mqtt_last_connection_ = millis(); mqtt_last_connection_ = millis();
mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN;
DEBUG_LOG(F("Configuring MQTT service...")); LOG_DEBUG(F("Configuring MQTT service..."));
} }
// MQTT init callbacks // MQTT init callbacks
@@ -161,19 +161,19 @@ void Mqtt::init() {
mqttClient_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { mqttClient_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
logger_.err(F("Disconnected from server")); LOG_DEBUG(F("Disconnected from server"));
} }
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
logger_.err(F("Server identifier Rejected")); LOG_ERROR(F("Server identifier Rejected"));
} }
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
logger_.err(F("Server unavailable")); LOG_ERROR(F("Server unavailable"));
} }
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
logger_.err(F("Malformed credentials")); LOG_ERROR(F("Malformed credentials"));
} }
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
logger_.err(F("Not authorized")); LOG_ERROR(F("Not authorized"));
} }
// Reset reconnection delay // Reset reconnection delay
@@ -182,7 +182,7 @@ void Mqtt::init() {
mqtt_start_ = false; // will force a new start() mqtt_start_ = false; // will force a new start()
}); });
// mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { DEBUG_LOG(F("Subscribe ACK for PID %d"), packetId); }); // mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { LOG_DEBUG(F("Subscribe ACK for PID %d"), packetId); });
mqttClient_.onPublish([this](uint16_t packetId) { on_publish(packetId); }); mqttClient_.onPublish([this](uint16_t packetId) { on_publish(packetId); });
@@ -271,8 +271,8 @@ void Mqtt::loop() {
} }
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
start(); setup();
logger_.info(F("Connecting to MQTT server...")); LOG_INFO(F("Connecting to the MQTT server..."));
mqttClient_.connect(); // Connect to the MQTT broker mqttClient_.connect(); // Connect to the MQTT broker
#endif #endif
} }
@@ -338,7 +338,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
strlcpy(message, payload, len + 1); strlcpy(message, payload, len + 1);
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
DEBUG_LOG(F("Received %s => %s (length %d)"), topic, message, len); LOG_DEBUG(F("Received %s => %s (length %d)"), topic, message, len);
#endif #endif
// strip out everything until the last / // strip out everything until the last /
@@ -357,7 +357,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
} }
// if we got here we didn't find a topic match // if we got here we didn't find a topic match
DEBUG_LOG(F("No responding handler found for topic %s"), topic); LOG_DEBUG(F("No responding handler found for topic %s"), topic);
} }
// print all the topics related to a specific device_id // print all the topics related to a specific device_id
@@ -382,7 +382,7 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic
// and always remove from queue // and always remove from queue
void Mqtt::on_publish(uint16_t packetId) { void Mqtt::on_publish(uint16_t packetId) {
// find the MQTT message in the queue and remove it // find the MQTT message in the queue and remove it
if ((mqtt_messages_.empty()) || (mqtt_qos_ == 0)) { if (mqtt_messages_.empty()) {
return; return;
} }
@@ -394,9 +394,9 @@ void Mqtt::on_publish(uint16_t packetId) {
} }
if (mqtt_message.packet_id_ == packetId) { if (mqtt_message.packet_id_ == packetId) {
DEBUG_LOG(F("Acknowledged PID %d. Removing from queue"), packetId); LOG_DEBUG(F("Acknowledged PID %d. Removing from queue"), packetId);
} else { } else {
DEBUG_LOG(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId); LOG_DEBUG(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId);
mqtt_publish_fails_++; // increment error count mqtt_publish_fails_++; // increment error count
} }
@@ -419,31 +419,26 @@ char * Mqtt::make_topic(char * result, const std::string & topic) {
return result; return result;
} }
void Mqtt::start() {
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
send_start_topic();
send_heartbeat(); // send heartbeat if enabled
}
// send online appended with the version information as JSON // send online appended with the version information as JSON
void Mqtt::send_start_topic() { void Mqtt::send_start_topic() {
StaticJsonDocument<90> doc; StaticJsonDocument<90> doc;
doc["event"] = "start"; doc["event"] = "start";
doc["version"] = Settings().app_version(); doc["version"] = Settings().app_version();
#ifndef EMSESP_STANDALONE
doc["IP"] = WiFi.localIP().toString();
#endif
publish("info", doc, false); // send with retain off publish("info", doc, false); // send with retain off
} }
// MQTT onConnect - when a connect is established // MQTT onConnect - when a connect is established
void Mqtt::on_connect() { void Mqtt::on_connect() {
DEBUG_LOG(F("MQTT connected"));
mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN;
mqtt_last_connection_ = millis(); mqtt_last_connection_ = millis();
mqtt_connecting_ = false; mqtt_connecting_ = false;
LOG_INFO(F("MQTT connected"));
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
send_start_topic();
send_heartbeat(); // send heartbeat if enabled
} }
// send periodic MQTT message with system information // send periodic MQTT message with system information
@@ -462,38 +457,6 @@ void Mqtt::send_heartbeat() {
publish("heartbeat", doc, false); // send to MQTT with retain off publish("heartbeat", doc, false); // send to MQTT with retain off
} }
// add MQTT message to queue, payload is a JSON doc.
// NOTE this only prints first 255 chars
void Mqtt::queue_publish_message(const std::string & topic, const JsonDocument & payload, const bool retain) {
// can't have bogus topics, but empty payloads are ok
if (topic.empty()) {
return;
}
/*
// check for empty JSON doc - we don't like those
size_t capacity = measureJson(payload);
if (capacity <= 3) {
// DEBUG_LOG(("Empty JSON payload for topic %s. Skipping"), topic);
return;
}
*/
std::string payload_text;
serializeJson(payload, payload_text);
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, topic, payload_text, retain);
// DEBUG_LOG(F("Adding JSON publish message created with topic %s, message %s"), topic, payload_text.c_str());
// if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
mqtt_messages_.pop_front();
}
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
}
// add MQTT message to queue, payload is a string // add MQTT message to queue, payload is a string
void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) { void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
// can't have bogus topics, but empty payloads are ok // can't have bogus topics, but empty payloads are ok
@@ -518,7 +481,7 @@ void Mqtt::queue_subscribe_message(const std::string & topic) {
} }
auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false); auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false);
DEBUG_LOG(F("Adding a subscription for %s"), topic.c_str()); LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
// if the queue is full, make room but removing the last one // if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= maximum_mqtt_messages_) { if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
@@ -528,24 +491,27 @@ void Mqtt::queue_subscribe_message(const std::string & topic) {
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
} }
// Publish using the user's custom retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload) {
publish(topic, payload, mqtt_retain_);
}
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) {
publish(topic, payload, mqtt_retain_);
}
// MQTT Publish, using a specific retain flag // MQTT Publish, using a specific retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload, bool retain) { void Mqtt::publish(const std::string & topic, const std::string & payload, bool retain) {
queue_publish_message(topic, payload, retain); queue_publish_message(topic, payload, retain);
} }
// Publish using the user's custom retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload) {
publish(topic, payload, mqtt_retain_);
}
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) {
publish(topic, payload, mqtt_retain_);
}
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) { void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
queue_publish_message(topic, payload, retain); // convert json to string
std::string payload_text;
serializeJson(payload, payload_text);
queue_publish_message(topic, payload_text, retain);
} }
// for booleans, which get converted to string values 1 and 0
void Mqtt::publish(const std::string & topic, const bool value) { void Mqtt::publish(const std::string & topic, const bool value) {
queue_publish_message(topic, value ? "1" : "0", mqtt_retain_); queue_publish_message(topic, value ? "1" : "0", mqtt_retain_);
} }
@@ -576,7 +542,7 @@ void Mqtt::process_queue() {
// append the hostname and base to the topic, unless we're doing native HA which has a different format // append the hostname and base to the topic, unless we're doing native HA which has a different format
char full_topic[MQTT_TOPIC_MAX_SIZE]; char full_topic[MQTT_TOPIC_MAX_SIZE];
// if the topic starts with "homeassistant" we leave it untouched, otherwise append ho st and base // if the topic starts with "homeassistant" we leave it untouched, otherwise append host and base
if (strncmp(message->topic.c_str(), "homeassistant/", 13) == 0) { if (strncmp(message->topic.c_str(), "homeassistant/", 13) == 0) {
strcpy(full_topic, message->topic.c_str()); strcpy(full_topic, message->topic.c_str());
} else { } else {
@@ -585,14 +551,14 @@ void Mqtt::process_queue() {
// if we're subscribing... // if we're subscribing...
if (message->operation == Operation::SUBSCRIBE) { if (message->operation == Operation::SUBSCRIBE) {
DEBUG_LOG(F("Subscribing to topic: %s"), full_topic); LOG_DEBUG(F("Subscribing to topic: %s"), full_topic);
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint16_t packet_id = mqttClient_.subscribe(full_topic, mqtt_qos_); uint16_t packet_id = mqttClient_.subscribe(full_topic, mqtt_qos_);
#else #else
uint16_t packet_id = 1; uint16_t packet_id = 1;
#endif #endif
if (!packet_id) { if (!packet_id) {
DEBUG_LOG(F("Error subscribing to %s, error %d"), full_topic, packet_id); LOG_DEBUG(F("Error subscribing to %s, error %d"), full_topic, packet_id);
} }
mqtt_messages_.pop_front(); // remove the message from the queue mqtt_messages_.pop_front(); // remove the message from the queue
@@ -606,25 +572,26 @@ void Mqtt::process_queue() {
return; return;
} }
// else try and publish it // else try and publish it
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str()); // uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str());
uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
#else #else
uint16_t packet_id = 1; uint16_t packet_id = 1;
#endif #endif
DEBUG_LOG(F("Publishing topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id); LOG_DEBUG(F("Publishing topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id);
if (packet_id == 0) { if (packet_id == 0) {
// it failed. if we retried n times, give up. remove from queue // it failed. if we retried n times, give up. remove from queue
if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) { if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) {
logger_.err(F("Failed to publish to %s after %d attempts"), full_topic, mqtt_message.retry_count_ + 1); LOG_ERROR(F("Failed to publish to %s after %d attempts"), full_topic, mqtt_message.retry_count_ + 1);
mqtt_publish_fails_++; // increment failure counter mqtt_publish_fails_++; // increment failure counter
mqtt_messages_.pop_front(); // delete mqtt_messages_.pop_front(); // delete
return; return;
} else { } else {
mqtt_messages_.front().retry_count_++; mqtt_messages_.front().retry_count_++;
// logger_.err(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1); LOG_DEBUG(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1);
DEBUG_LOG(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1);
return; // leave on queue for next time so it gets republished return; // leave on queue for next time so it gets republished
} }
} }
@@ -633,7 +600,7 @@ void Mqtt::process_queue() {
// but add the packet_id so we can check it later // but add the packet_id so we can check it later
if (mqtt_qos_ != 0) { if (mqtt_qos_ != 0) {
mqtt_messages_.front().packet_id_ = packet_id; mqtt_messages_.front().packet_id_ = packet_id;
DEBUG_LOG(F("Setting packetID for ACK to %d"), packet_id); LOG_DEBUG(F("Setting packetID for ACK to %d"), packet_id);
return; return;
} }

View File

@@ -63,6 +63,7 @@ struct MqttMessage {
class Mqtt { class Mqtt {
public: public:
void loop(); void loop();
void start();
void send_heartbeat(); void send_heartbeat();
enum Operation { PUBLISH, SUBSCRIBE }; enum Operation { PUBLISH, SUBSCRIBE };
@@ -109,32 +110,28 @@ class Mqtt {
}; };
static std::deque<QueuedMqttMessage> mqtt_messages_; static std::deque<QueuedMqttMessage> mqtt_messages_;
void start();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
static AsyncMqttClient mqttClient_; static AsyncMqttClient mqttClient_;
#endif #endif
void flush_message_queue(); void flush_message_queue();
void setup();
static constexpr size_t MAX_MQTT_MESSAGES = 50; static constexpr size_t MAX_MQTT_MESSAGES = 50;
static size_t maximum_mqtt_messages_; static size_t maximum_mqtt_messages_;
static uint16_t mqtt_message_id_; static uint16_t mqtt_message_id_;
static bool mqtt_retain_;
static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50; static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50;
static constexpr uint32_t MQTT_PUBLISH_WAIT = 750; // delay between sending publishes, although it should be asynchronous! static constexpr uint32_t MQTT_PUBLISH_WAIT = 750; // delay between sending publishes, to account for large payloads
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
static constexpr uint8_t MQTT_KEEP_ALIVE = 60; static constexpr uint8_t MQTT_KEEP_ALIVE = 60; // 60 seconds. This could also be less, like 30 seconds
static constexpr uint32_t MQTT_RECONNECT_DELAY_MIN = 2000; // Try to reconnect in 2 seconds upon disconnection static constexpr uint32_t MQTT_RECONNECT_DELAY_MIN = 2000; // Try to reconnect in 2 seconds upon disconnection
static constexpr uint32_t MQTT_RECONNECT_DELAY_STEP = 3000; // Increase the reconnect delay in 3 seconds after each failed attempt static constexpr uint32_t MQTT_RECONNECT_DELAY_STEP = 3000; // Increase the reconnect delay in 3 seconds after each failed attempt
static constexpr uint32_t MQTT_RECONNECT_DELAY_MAX = 120000; // Set reconnect time to 2 minutes at most static constexpr uint32_t MQTT_RECONNECT_DELAY_MAX = 120000; // Set reconnect time to 2 minutes at most
static constexpr uint32_t MQTT_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min) static constexpr uint32_t MQTT_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static bool mqtt_retain_;
static void queue_publish_message(const std::string & topic, const JsonDocument & payload, const bool retain);
static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain); static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
static void queue_subscribe_message(const std::string & topic); static void queue_subscribe_message(const std::string & topic);
void on_publish(uint16_t packetId); void on_publish(uint16_t packetId);

View File

@@ -36,6 +36,7 @@ void Network::start() {
WiFi.persistent(false); WiFi.persistent(false);
WiFi.disconnect(true); WiFi.disconnect(true);
WiFi.setAutoReconnect(false); WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_STA);
#endif #endif
#if defined(ESP8266) #if defined(ESP8266)
@@ -71,7 +72,7 @@ void Network::sta_mode_start(WiFiEvent_t event, WiFiEventInfo_t info) {
#if defined(ESP8266) #if defined(ESP8266)
void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) { void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) {
logger_.info(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"), LOG_INFO(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
event.ssid.c_str(), event.ssid.c_str(),
event.bssid[0], event.bssid[0],
event.bssid[1], event.bssid[1],
@@ -86,7 +87,7 @@ void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) {
} }
#elif defined(ESP32) #elif defined(ESP32)
void Network::sta_mode_connected(WiFiEvent_t event, WiFiEventInfo_t info) { void Network::sta_mode_connected(WiFiEvent_t event, WiFiEventInfo_t info) {
logger_.info(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"), LOG_INFO(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
info.connected.ssid, info.connected.ssid,
info.sta_connected.mac[0], info.sta_connected.mac[0],
info.sta_connected.mac[1], info.sta_connected.mac[1],
@@ -107,10 +108,10 @@ void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & eve
if (event.reason == 201) { if (event.reason == 201) {
if (++disconnect_count_ == 3) { if (++disconnect_count_ == 3) {
if (System::safe_mode()) { if (System::safe_mode()) {
logger_.err(F("Failed to connect to WiFi %s after %d attempts"), event.ssid.c_str(), disconnect_count_ - 1); LOG_ERROR(F("Failed to connect to WiFi %s after %d attempts"), event.ssid.c_str(), disconnect_count_ - 1);
disconnect_count_ = 0; disconnect_count_ = 0;
} else { } else {
logger_.err(F("Failed to connect to WiFi. Rebooting into Safe mode")); LOG_ERROR(F("Failed to connect to WiFi. Rebooting into Safe mode"));
System::restart(true); // set safe mode and restart System::restart(true); // set safe mode and restart
} }
} }
@@ -118,20 +119,20 @@ void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & eve
} }
#elif defined(ESP32) #elif defined(ESP32)
void Network::sta_mode_disconnected(WiFiEvent_t event, WiFiEventInfo_t info) { void Network::sta_mode_disconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
// logger_.err(F("Failed to connect to WiFi %s, reason code %d"), info.disconnected.ssid, info.disconnected.reason); // LOG_ERROR(F("Failed to connect to WiFi %s, reason code %d"), info.disconnected.ssid, info.disconnected.reason);
} }
#endif #endif
#if defined(ESP8266) #if defined(ESP8266)
void Network::sta_mode_got_ip(const WiFiEventStationModeGotIP & event) { void Network::sta_mode_got_ip(const WiFiEventStationModeGotIP & event) {
logger_.info(F("Obtained IPv4 address %s/%s and gateway %s"), LOG_INFO(F("Obtained IPv4 address %s/%s and gateway %s"),
uuid::printable_to_string(event.ip).c_str(), uuid::printable_to_string(event.ip).c_str(),
uuid::printable_to_string(event.mask).c_str(), uuid::printable_to_string(event.mask).c_str(),
uuid::printable_to_string(event.gw).c_str()); uuid::printable_to_string(event.gw).c_str());
} }
#elif defined(ESP32) #elif defined(ESP32)
void Network::sta_mode_got_ip(WiFiEvent_t event, WiFiEventInfo_t info) { void Network::sta_mode_got_ip(WiFiEvent_t event, WiFiEventInfo_t info) {
logger_.info(F("Obtained IPv4 address %s/%s and gateway %s"), LOG_INFO(F("Obtained IPv4 address %s/%s and gateway %s"),
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.ip.addr)).c_str(), uuid::printable_to_string(IPAddress(info.got_ip.ip_info.ip.addr)).c_str(),
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.netmask.addr)).c_str(), uuid::printable_to_string(IPAddress(info.got_ip.ip_info.netmask.addr)).c_str(),
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.gw.addr)).c_str()); uuid::printable_to_string(IPAddress(info.got_ip.ip_info.gw.addr)).c_str());
@@ -202,21 +203,21 @@ void Network::ota_setup() {
ota_->setPassword(settings.admin_password().c_str()); ota_->setPassword(settings.admin_password().c_str());
ota_->onStart([this]() { ota_->onStart([this]() {
DEBUG_LOG(F("OTA starting (send type %d)..."), ota_->getCommand()); LOG_DEBUG(F("OTA starting (send type %d)..."), ota_->getCommand());
// turn off stuff to stop interference // turn off stuff to stop interference
EMSuart::stop(); // UART stop EMSuart::stop(); // UART stop
in_ota_ = true; // set flag so all other services stop in_ota_ = true; // set flag so all other services stop
}); });
ota_->onEnd([this]() { DEBUG_LOG(F("OTA done, automatically restarting")); }); ota_->onEnd([this]() { LOG_DEBUG(F("OTA done, automatically restarting")); });
ota_->onProgress([this](unsigned int progress, unsigned int total) { ota_->onProgress([this](unsigned int progress, unsigned int total) {
/* /*
static unsigned int _progOld; static unsigned int _progOld;
unsigned int _prog = (progress / (total / 100)); unsigned int _prog = (progress / (total / 100));
if (_prog != _progOld) { if (_prog != _progOld) {
DEBUG_LOG(F("[OTA] Progress: %u%%"), _prog); LOG_DEBUG(F("[OTA] Progress: %u%%"), _prog);
_progOld = _prog; _progOld = _prog;
} }
*/ */
@@ -224,23 +225,23 @@ void Network::ota_setup() {
ota_->onError([this](ota_error_t error) { ota_->onError([this](ota_error_t error) {
if (error == OTA_AUTH_ERROR) { if (error == OTA_AUTH_ERROR) {
logger_.err(F("[OTA] Auth Failed")); LOG_ERROR(F("[OTA] Auth Failed"));
} else if (error == OTA_BEGIN_ERROR) { } else if (error == OTA_BEGIN_ERROR) {
logger_.err(F("[OTA] Begin Failed")); LOG_ERROR(F("[OTA] Begin Failed"));
} else if (error == OTA_CONNECT_ERROR) { } else if (error == OTA_CONNECT_ERROR) {
logger_.err(F("[OTA] Connect Failed")); LOG_ERROR(F("[OTA] Connect Failed"));
} else if (error == OTA_RECEIVE_ERROR) { } else if (error == OTA_RECEIVE_ERROR) {
logger_.err(F("[OTA] Receive Failed")); LOG_ERROR(F("[OTA] Receive Failed"));
} else if (error == OTA_END_ERROR) { } else if (error == OTA_END_ERROR) {
logger_.err(F("[OTA] End Failed")); LOG_ERROR(F("[OTA] End Failed"));
} else { } else {
logger_.err(F("[OTA] Error %d"), error); LOG_ERROR(F("[OTA] Error %d"), error);
}; };
}); });
// start ota service // start ota service
ota_->begin(); ota_->begin();
logger_.info(F("Listening to firmware updates on %s.local:%u"), ota_->getHostname().c_str(), OTA_PORT); LOG_INFO(F("Listening to firmware updates on %s.local:%u"), ota_->getHostname().c_str(), OTA_PORT);
#endif #endif
} }

View File

@@ -30,6 +30,23 @@ void Sensors::start() {
// copy over values from MQTT so we don't keep on quering the filesystem // copy over values from MQTT so we don't keep on quering the filesystem
mqtt_format_ = Settings().mqtt_format(); mqtt_format_ = Settings().mqtt_format();
// if we're using HA MQTT Discovery, send out the config
// currently we just do this for a single sensor (sensor1)
if (mqtt_format_ == Settings::MQTT_format::HA) {
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
doc["dev_cla"] = "temperature";
doc["name"] = "ems-esp-sensor1";
doc["uniq_id"] = "ems-esp-sensor1";
doc["~"] = "homeassistant/sensor/ems-esp/external";
doc["stat_t"] = "~/state";
doc["unit_of_meas"] = "°C";
doc["val_tpl"] = "{{value_json.sensor1.temp}}";
Mqtt::publish("homeassistant/sensor/ems-esp/external/config", doc, true); // publish the config payload with retain flag
}
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
bus_.begin(SENSOR_GPIO); bus_.begin(SENSOR_GPIO);
#endif #endif
@@ -39,7 +56,7 @@ void Sensors::loop() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (state_ == State::IDLE) { if (state_ == State::IDLE) {
if (millis() - last_activity_ >= READ_INTERVAL_MS) { if (millis() - last_activity_ >= READ_INTERVAL_MS) {
// DEBUG_LOG(F("Read sensor temperature")); // uncomment for debug // LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
if (bus_.reset()) { if (bus_.reset()) {
bus_.skip(); bus_.skip();
bus_.write(CMD_CONVERT_TEMP); bus_.write(CMD_CONVERT_TEMP);
@@ -47,28 +64,28 @@ void Sensors::loop() {
state_ = State::READING; state_ = State::READING;
} else { } else {
// no sensors found // no sensors found
// logger_.err(F("Bus reset failed")); // uncomment for debug // LOG_ERROR(F("Bus reset failed")); // uncomment for debug
devices_.clear(); // remove all know devices incase we have a disconnect devices_.clear(); // remove all know devices incase we have a disconnect
} }
last_activity_ = millis(); last_activity_ = millis();
} }
} else if (state_ == State::READING) { } else if (state_ == State::READING) {
if (temperature_convert_complete()) { if (temperature_convert_complete()) {
// DEBUG_LOG(F("Scanning for sensors")); // uncomment for debug // LOG_DEBUG(F("Scanning for sensors")); // uncomment for debug
bus_.reset_search(); bus_.reset_search();
found_.clear(); found_.clear();
state_ = State::SCANNING; state_ = State::SCANNING;
last_activity_ = millis(); last_activity_ = millis();
} else if (millis() - last_activity_ > READ_TIMEOUT_MS) { } else if (millis() - last_activity_ > READ_TIMEOUT_MS) {
logger_.err(F("Sensor read timeout")); LOG_ERROR(F("Sensor read timeout"));
state_ = State::IDLE; state_ = State::IDLE;
last_activity_ = millis(); last_activity_ = millis();
} }
} else if (state_ == State::SCANNING) { } else if (state_ == State::SCANNING) {
if (millis() - last_activity_ > SCAN_TIMEOUT_MS) { if (millis() - last_activity_ > SCAN_TIMEOUT_MS) {
logger_.err(F("Sensor scan timeout")); LOG_ERROR(F("Sensor scan timeout"));
state_ = State::IDLE; state_ = State::IDLE;
last_activity_ = millis(); last_activity_ = millis();
} else { } else {
@@ -89,24 +106,24 @@ void Sensors::loop() {
/* /*
// comment out for debugging // comment out for debugging
char result[10]; char result[10];
DEBUG_LOG(F("Temp of %s = %s"), LOG_DEBUG(F("Temp of %s = %s"),
found_.back().to_string().c_str(), found_.back().to_string().c_str(),
Helpers::render_value(result, found_.back().temperature_c_, 2)); Helpers::render_value(result, found_.back().temperature_c_, 2));
*/ */
break; break;
default: default:
logger_.err(F("Unknown sensor %s"), Device(addr).to_string().c_str()); LOG_ERROR(F("Unknown sensor %s"), Device(addr).to_string().c_str());
break; break;
} }
} else { } else {
logger_.err(F("Invalid sensor %s"), Device(addr).to_string().c_str()); LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str());
} }
} else { } else {
bus_.depower(); bus_.depower();
devices_ = std::move(found_); devices_ = std::move(found_);
found_.clear(); found_.clear();
// DEBUG_LOG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug // LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
state_ = State::IDLE; state_ = State::IDLE;
last_activity_ = millis(); last_activity_ = millis();
} }
@@ -129,7 +146,7 @@ bool Sensors::temperature_convert_complete() {
float Sensors::get_temperature_c(const uint8_t addr[]) { float Sensors::get_temperature_c(const uint8_t addr[]) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (!bus_.reset()) { if (!bus_.reset()) {
logger_.err(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str()); LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
return NAN; return NAN;
} }
@@ -140,22 +157,22 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN); bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
if (!bus_.reset()) { if (!bus_.reset()) {
logger_.err(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str()); LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str());
return NAN; return NAN;
} }
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) { if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
logger_.warning(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"), LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"),
scratchpad[0], scratchpad[0],
scratchpad[1], scratchpad[1],
scratchpad[2], scratchpad[2],
scratchpad[3], scratchpad[3],
scratchpad[4], scratchpad[4],
scratchpad[5], scratchpad[5],
scratchpad[6], scratchpad[6],
scratchpad[7], scratchpad[7],
scratchpad[8], scratchpad[8],
Device(addr).to_string().c_str()); Device(addr).to_string().c_str());
return NAN; return NAN;
} }
@@ -227,7 +244,7 @@ void Sensors::publish_values() {
// if we're not using nested JSON, send each sensor out seperately // if we're not using nested JSON, send each sensor out seperately
// sensor1, sensor2 etc... // sensor1, sensor2 etc...
// e.g. sensor_1 = {"temp":20.2} // e.g. sensor_1 = {"temp":20.2}
if (mqtt_format_ != Settings::MQTT_format::NESTED) { if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
StaticJsonDocument<100> doc; StaticJsonDocument<100> doc;
for (const auto & device : devices_) { for (const auto & device : devices_) {
char s[5]; char s[5];
@@ -242,13 +259,14 @@ void Sensors::publish_values() {
} }
// group all sensors together - https://github.com/proddy/EMS-ESP/issues/327 // group all sensors together - https://github.com/proddy/EMS-ESP/issues/327
// https://arduinojson.org/v6/assistant/ // This is used for both NESTED and HA modes
// sensors = { // sensors = {
// "sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, // "sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
// "sensor2":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, // "sensor2":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
// "sensor3":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, // "sensor3":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
// "sensor4":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"} // "sensor4":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}
// } // }
// const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices); // const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices);
DynamicJsonDocument doc(100 * num_devices); DynamicJsonDocument doc(100 * num_devices);
@@ -263,7 +281,11 @@ void Sensors::publish_values() {
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2); dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
} }
Mqtt::publish("sensors", doc); if (mqtt_format_ == Settings::MQTT_format::HA) {
Mqtt::publish("homeassistant/sensor/ems-esp/external/state", doc);
} else {
Mqtt::publish("sensors", doc);
}
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -63,7 +63,11 @@ class Sensors {
const std::vector<Device> devices() const; const std::vector<Device> devices() const;
private: private:
#if defined(ESP8266)
static constexpr uint8_t SENSOR_GPIO = 14; // D5 static constexpr uint8_t SENSOR_GPIO = 14; // D5
#elif defined(ESP32)
static constexpr uint8_t SENSOR_GPIO = 18; // Wemos D1-32 for compatibility D5
#endif
enum class State { IDLE, READING, SCANNING }; enum class State { IDLE, READING, SCANNING };

View File

@@ -139,10 +139,10 @@ Settings::Settings() {
if (EMSESP_FS.begin(true)) { if (EMSESP_FS.begin(true)) {
#endif #endif
#endif #endif
logger_.info(F("Mounted filesystem")); LOG_INFO(F("Mounted filesystem"));
mounted_ = true; mounted_ = true;
} else { } else {
logger_.alert(F("Unable to mount filesystem")); LOG_ERROR(F("Unable to mount filesystem"));
unavailable_ = true; unavailable_ = true;
} }
} }
@@ -154,7 +154,7 @@ Settings::Settings() {
} }
if (!loaded_) { if (!loaded_) {
logger_.err(F("Failed to load settings. Using defaults")); LOG_ERROR(F("Failed to load settings. Using defaults"));
read_settings(ArduinoJson::StaticJsonDocument<0>()); read_settings(ArduinoJson::StaticJsonDocument<0>());
loaded_ = true; loaded_ = true;
} }
@@ -169,7 +169,7 @@ void Settings::commit() {
EMSuart::stop(); // temporary suspend UART because is can cause interference on the UART EMSuart::stop(); // temporary suspend UART because is can cause interference on the UART
logger_.debug(F("Saving settings")); LOG_DEBUG(F("Saving settings"));
if (write_settings(filename)) { if (write_settings(filename)) {
if (read_settings(filename, false)) { if (read_settings(filename, false)) {
write_settings(backup_filename); write_settings(backup_filename);
@@ -190,21 +190,21 @@ bool Settings::read_settings(const std::string & filename, bool load) {
auto error = ArduinoJson::deserializeMsgPack(doc, file); auto error = ArduinoJson::deserializeMsgPack(doc, file);
if (error) { if (error) {
logger_.err(F("Failed to parse settings file %s: %s"), filename.c_str(), error.c_str()); LOG_ERROR(F("Failed to parse settings file %s: %s"), filename.c_str(), error.c_str());
return false; return false;
} else { } else {
if (load) { if (load) {
logger_.info(F("Loading settings from file %s"), filename.c_str()); LOG_INFO(F("Loading settings from file %s"), filename.c_str());
read_settings(doc); read_settings(doc);
} }
return true; return true;
} }
} else { } else {
logger_.err(F("Settings file %s does not exist"), filename.c_str()); LOG_ERROR(F("Settings file %s does not exist"), filename.c_str());
return false; return false;
} }
#else #else
logger_.info(F("Loading settings from file %s (%d)"), filename.c_str(), load); LOG_INFO(F("Loading settings from file %s (%d)"), filename.c_str(), load);
return false; return false;
#endif #endif
} }
@@ -221,17 +221,17 @@ bool Settings::write_settings(const std::string & filename) {
ArduinoJson::serializeMsgPack(doc, file); ArduinoJson::serializeMsgPack(doc, file);
if (file.getWriteError()) { if (file.getWriteError()) {
logger_.alert(F("Failed to write settings file %s: %u"), filename.c_str(), file.getWriteError()); LOG_ERROR(F("Failed to write settings file %s: %u"), filename.c_str(), file.getWriteError());
return false; return false;
} else { } else {
return true; return true;
} }
} else { } else {
logger_.alert(F("Unable to open settings file %s for writing"), filename.c_str()); LOG_ERROR(F("Unable to open settings file %s for writing"), filename.c_str());
return false; return false;
} }
#else #else
logger_.debug(F("Write settings file %s"), filename.c_str()); LOG_DEBUG(F("Write settings file %s"), filename.c_str());
return false; return false;
#endif #endif
} }

View File

@@ -49,15 +49,15 @@
#define EMSESP_DEFAULT_MQTT_ENABLED true #define EMSESP_DEFAULT_MQTT_ENABLED true
#define EMSESP_DEFAULT_MQTT_BASE "home" #define EMSESP_DEFAULT_MQTT_BASE "home"
#define EMSESP_DEFAULT_MQTT_PORT 1883 #define EMSESP_DEFAULT_MQTT_PORT 1883
#define EMSESP_DEFAULT_MQTT_QOS 0 #define EMSESP_DEFAULT_MQTT_QOS 1
#define EMSESP_DEFAULT_MQTT_RETAIN false #define EMSESP_DEFAULT_MQTT_RETAIN false
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested #define EMSESP_DEFAULT_MQTT_FORMAT 2 // 2=nested
#define EMSESP_DEFAULT_MQTT_HEARTBEAT true #define EMSESP_DEFAULT_MQTT_HEARTBEAT true
#define EMSESP_DEFAULT_EMS_READ_ONLY false #define EMSESP_DEFAULT_EMS_READ_ONLY false
#define EMSESP_DEFAULT_SHOWER_TIMER false #define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false #define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_SYSLOG_INTERVAL 0 #define EMSESP_DEFAULT_SYSLOG_INTERVAL 0
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set #define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // 0=not set
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 10 #define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 10

View File

@@ -55,7 +55,7 @@ void Shower::loop() {
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) { if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
shower_on_ = true; shower_on_ = true;
Mqtt::publish("shower_active", (bool)true); Mqtt::publish("shower_active", (bool)true);
DEBUG_LOG(F("[Shower] hot water still running, starting shower timer")); LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
} }
// check if the shower has been on too long // check if the shower has been on too long
else if ((((time_now - timer_start_) > SHOWER_MAX_DURATION) && !doing_cold_shot_) && shower_alert_) { else if ((((time_now - timer_start_) > SHOWER_MAX_DURATION) && !doing_cold_shot_) && shower_alert_) {
@@ -76,7 +76,7 @@ void Shower::loop() {
duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME); duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME);
if (duration_ > SHOWER_MIN_DURATION) { if (duration_ > SHOWER_MIN_DURATION) {
Mqtt::publish("shower_active", (bool)false); Mqtt::publish("shower_active", (bool)false);
DEBUG_LOG(F("[Shower] finished with duration %d"), duration_); LOG_DEBUG(F("[Shower] finished with duration %d"), duration_);
publish_values(); publish_values();
} }
} }
@@ -94,7 +94,7 @@ void Shower::loop() {
// turn back on the hot water for the shower // turn back on the hot water for the shower
void Shower::shower_alert_stop() { void Shower::shower_alert_stop() {
if (doing_cold_shot_) { if (doing_cold_shot_) {
DEBUG_LOG(F("Shower Alert stopped")); LOG_DEBUG(F("Shower Alert stopped"));
// Boiler::set_tapwarmwater_activated(true); // Boiler::set_tapwarmwater_activated(true);
doing_cold_shot_ = false; doing_cold_shot_ = false;
// showerColdShotStopTimer.detach(); // disable the timer // showerColdShotStopTimer.detach(); // disable the timer
@@ -104,7 +104,7 @@ void Shower::shower_alert_stop() {
// turn off hot water to send a shot of cold // turn off hot water to send a shot of cold
void Shower::shower_alert_start() { void Shower::shower_alert_start() {
if (shower_alert_) { if (shower_alert_) {
DEBUG_LOG(F("Shower Alert started!")); LOG_DEBUG(F("Shower Alert started!"));
// Boiler::set_tapwarmwater_activated(false); // Boiler::set_tapwarmwater_activated(false);
doing_cold_shot_ = true; doing_cold_shot_ = true;
// start the timer for n seconds which will reset the water back to hot // start the timer for n seconds which will reset the water back to hot

View File

@@ -29,7 +29,7 @@ uuid::log::Logger Solar::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
DEBUG_LOG(F("Registering new Solar module with device ID 0x%02X"), device_id); LOG_DEBUG(F("Registering new Solar module with device ID 0x%02X"), device_id);
// telegram handlers // telegram handlers
register_telegram_type(0x0097, F("SM10Monitor"), true, std::bind(&Solar::process_SM10Monitor, this, _1)); register_telegram_type(0x0097, F("SM10Monitor"), true, std::bind(&Solar::process_SM10Monitor, this, _1));
@@ -108,7 +108,7 @@ void Solar::publish_values() {
} }
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
DEBUG_LOG(F("[DEBUG] Performing a solar module publish")); LOG_DEBUG(F("[DEBUG] Performing a solar module publish"));
#endif #endif
Mqtt::publish("sm_data", doc); Mqtt::publish("sm_data", doc);

View File

@@ -60,7 +60,7 @@ void System::mqtt_commands(const char * message) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
DeserializationError error = deserializeJson(doc, message); DeserializationError error = deserializeJson(doc, message);
if (error) { if (error) {
DEBUG_LOG(F("MQTT error: payload %s, error %s"), message, error.c_str()); LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
return; return;
} }
const char * command = doc["cmd"]; const char * command = doc["cmd"];
@@ -91,9 +91,9 @@ void System::restart(bool mode) {
// check for safe mode // check for safe mode
if (mode) { if (mode) {
logger_.notice("Restarting system in safe mode..."); LOG_NOTICE("Restarting system in safe mode...");
} else { } else {
logger_.notice("Restarting system..."); LOG_NOTICE("Restarting system...");
} }
Shell::loop_all(); Shell::loop_all();
@@ -140,9 +140,11 @@ void System::start() {
settings.app_version(EMSESP_APP_VERSION); settings.app_version(EMSESP_APP_VERSION);
settings.commit(); settings.commit();
logger_.info(F("System booted (EMS-ESP version %s)"), settings.app_version().c_str()); LOG_INFO(F("System booted (EMS-ESP version %s)"), settings.app_version().c_str());
pinMode(LED_GPIO, OUTPUT); // LED pin if (LED_GPIO) {
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 is disabled
}
// register MQTT system commands // register MQTT system commands
Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, _1)); Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, _1));
@@ -218,19 +220,11 @@ void System::loop() {
syslog_.loop(); syslog_.loop();
#endif #endif
led_monitor(); // check status and report back using the LED if (LED_GPIO) {
led_monitor(); // check status and report back using the LED
}
system_check(); // check system health system_check(); // check system health
/*
#ifdef EMSESP_DEBUG
static uint32_t last_debug_ = 0;
if (millis() - last_debug_ >= 5000) {
last_debug_ = millis();
show_mem("loop");
}
#endif
*/
} }
void System::show_mem(const char * text) { void System::show_mem(const char * text) {
@@ -239,7 +233,7 @@ void System::show_mem(const char * text) {
#else #else
uint32_t mem = 1000; uint32_t mem = 1000;
#endif #endif
logger_.notice(F("{%s} Free mem: %ld (%d%%)"), text, mem, (100 * mem / heap_start_)); LOG_NOTICE(F("{%s} Free mem: %ld (%d%%)"), text, mem, (100 * mem / heap_start_));
} }
// sets rate of led flash // sets rate of led flash
@@ -272,7 +266,9 @@ void System::system_check() {
// if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed // if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed
if (!system_healthy_) { if (!system_healthy_) {
system_healthy_ = true; system_healthy_ = true;
digitalWrite(LED_GPIO, LED_ON); // LED on, for ever if (LED_GPIO) {
digitalWrite(LED_GPIO, LED_ON); // LED on, for ever
}
} }
} }
} }

View File

@@ -60,7 +60,7 @@ class System {
static void restart(bool safe_mode); static void restart(bool safe_mode);
static void restart() { static void restart() {
restart(false); // no safe mode restart(false); // default, don't boot into safe mode
} }
static void show_mem(const char * text); static void show_mem(const char * text);
@@ -83,8 +83,8 @@ class System {
static constexpr uint8_t LED_GPIO = 2; static constexpr uint8_t LED_GPIO = 2;
static constexpr uint8_t LED_ON = LOW; static constexpr uint8_t LED_ON = LOW;
#elif defined(ESP32) #elif defined(ESP32)
static constexpr uint8_t LED_GPIO = 5; // on Wemos D32 static constexpr uint8_t LED_GPIO = 5; // 5 on Lolin D32, 2 on Wemos D1-32 mini. Use 0 for off.
static constexpr uint8_t LED_ON = LOW; static constexpr uint8_t LED_ON = LOW; // LOW on Lolin D32, HIGH on Wemos D1-32 mini
#else #else
static constexpr uint8_t LED_GPIO = 0; static constexpr uint8_t LED_GPIO = 0;
static constexpr uint8_t LED_ON = 0; static constexpr uint8_t LED_ON = 0;
@@ -106,6 +106,7 @@ class System {
static int reset_counter_; static int reset_counter_;
static EMSuart emsuart_; static EMSuart emsuart_;
#if defined(ESP8266) #if defined(ESP8266)
static RTCVars state_; static RTCVars state_;
#endif #endif

View File

@@ -202,7 +202,7 @@ void RxService::flush_rx_queue() {
// start and initialize the Rx incoming buffer. Not currently used. // start and initialize the Rx incoming buffer. Not currently used.
void RxService::start() { void RxService::start() {
// DEBUG_LOG(F("RxStart")); // LOG_DEBUG(F("RxStart"));
} }
// Rx loop, run as many times as you can // Rx loop, run as many times as you can
@@ -240,7 +240,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
// validate the CRC // validate the CRC
uint8_t crc = calculate_crc(data, length - 1); uint8_t crc = calculate_crc(data, length - 1);
if (data[length - 1] != crc) { if (data[length - 1] != crc) {
TRACE_LOG(F("Rx: %s %s(BAD, CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET); LOG_TRACE(F("Rx: %s %s(BAD, CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
increment_telegram_error_count(); increment_telegram_error_count();
return; return;
} }
@@ -253,9 +253,9 @@ void RxService::add(uint8_t * data, uint8_t length) {
ems_mask(data[0]); ems_mask(data[0]);
} }
// if we're in read only mode, just dump out to console // if we're in "trace" and "raw" print out actual telegram
if (EMSESP::ems_read_only()) { if (logger_.enabled(Level::TRACE) && EMSESP::trace_raw()) {
TRACE_LOG(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str()); LOG_TRACE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
} }
// src, dest and offset are always in fixed positions // src, dest and offset are always in fixed positions
@@ -307,7 +307,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
} }
// add to queue, with timestamp // add to queue, with timestamp
DEBUG_LOG(F("New Rx [#%d] telegram added, length %d"), rx_telegram_id_, message_length); LOG_DEBUG(F("New Rx [#%d] telegram, length %d"), rx_telegram_id_, message_length);
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram));
} }
@@ -325,7 +325,7 @@ void TxService::flush_tx_queue() {
// start and initialize Tx // start and initialize Tx
void TxService::start() { void TxService::start() {
// DEBUG_LOG(F("TxStart()")); // LOG_DEBUG(F("TxStart()"));
// grab the bus ID // grab the bus ID
Settings settings; Settings settings;
@@ -431,11 +431,12 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
length++; // add one since we want to now include the CRC length++; // add one since we want to now include the CRC
DEBUG_LOG(F("Sending %s Tx [#%d], telegram: %s"), LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"),
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"), (telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
tx_telegram.id_, tx_telegram.id_,
telegram->to_string(telegram_raw, length).c_str()); telegram->to_string(telegram_raw, length).c_str());
// if we're watching an ID, then always show
if ((logger_.enabled(Level::TRACE)) if ((logger_.enabled(Level::TRACE))
&& ((telegram->src == EMSESP::trace_watch_id()) || (telegram->dest == EMSESP::trace_watch_id()) || (telegram->type_id == EMSESP::trace_watch_id()))) { && ((telegram->src == EMSESP::trace_watch_id()) || (telegram->dest == EMSESP::trace_watch_id()) || (telegram->type_id == EMSESP::trace_watch_id()))) {
logger_.trace(F("Sending %s Tx [#%d], telegram: %s"), logger_.trace(F("Sending %s Tx [#%d], telegram: %s"),
@@ -448,7 +449,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length); EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length);
if (status != EMS_TX_STATUS_OK) { if (status != EMS_TX_STATUS_OK) {
logger_.err(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK")); LOG_ERROR(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK"));
} }
tx_waiting(true); // tx now in a wait state tx_waiting(true); // tx now in a wait state
@@ -465,12 +466,12 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
} }
telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC
DEBUG_LOG(F("Sending Raw telegram: %s (length=%d)"), Helpers::data_to_hex(telegram_raw, length).c_str(), length); LOG_DEBUG(F("Sending Raw telegram: %s (length=%d)"), Helpers::data_to_hex(telegram_raw, length).c_str(), length);
// send the telegram to the UART Tx // send the telegram to the UART Tx
EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length); EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length);
if (status != EMS_TX_STATUS_OK) { if (status != EMS_TX_STATUS_OK) {
logger_.err(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK")); LOG_ERROR(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK"));
} }
} }
@@ -478,7 +479,7 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
// given some details like the destination, type, offset and message block // given some details like the destination, type, offset and message block
void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length) { void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length) {
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length); auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
DEBUG_LOG(F("New Tx [#%d] telegram added, length %d"), tx_telegram_id_, message_length); LOG_DEBUG(F("New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
// if the queue is full, make room but removing the last one // if the queue is full, make room but removing the last one
if (tx_telegrams_.size() >= maximum_tx_telegrams_) { if (tx_telegrams_.size() >= maximum_tx_telegrams_) {
@@ -493,7 +494,7 @@ void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t
void TxService::add(uint8_t * data, const uint8_t length) { void TxService::add(uint8_t * data, const uint8_t length) {
uint8_t message_length = length - 4; uint8_t message_length = length - 4;
if (!message_length) { if (!message_length) {
logger_.err(F("Bad Tx telegram, too short (message length is %d)"), message_length); LOG_ERROR(F("Bad Tx telegram, too short (message length is %d)"), message_length);
return; return;
} }
@@ -511,13 +512,13 @@ void TxService::add(uint8_t * data, const uint8_t length) {
tx_telegrams_.pop_front(); tx_telegrams_.pop_front();
} }
DEBUG_LOG(F("New Tx [#%d] telegram added, length %d"), tx_telegram_id_, message_length); LOG_DEBUG(F("New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram)); tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram));
} }
// send a Tx telegram to request data from an EMS device // send a Tx telegram to request data from an EMS device
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset) { void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset) {
DEBUG_LOG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id); LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1); add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1);
@@ -582,7 +583,7 @@ uint8_t TxService::retry_tx() {
// and incoming Rx dest must be us (our ems_bus_id) // and incoming Rx dest must be us (our ems_bus_id)
// for both src and dest we strip the MSB 8th bit // for both src and dest we strip the MSB 8th bit
bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const { bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
// DEBUG_LOG(F("Comparing %02X=%02X , %02X,%02X"), (telegram_last_[1] & 0x7F), (src & 0x7F), (dest & 0x7F), ems_bus_id()); // LOG_DEBUG(F("Comparing %02X=%02X , %02X,%02X"), (telegram_last_[1] & 0x7F), (src & 0x7F), (dest & 0x7F), ems_bus_id());
return (((telegram_last_[1] & 0x7F) == (src & 0x7F)) && ((dest & 0x7F) == ems_bus_id())); return (((telegram_last_[1] & 0x7F) == (src & 0x7F)) && ((dest & 0x7F) == ems_bus_id()));
} }
@@ -590,7 +591,7 @@ bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
void TxService::post_send_query() { void TxService::post_send_query() {
if (telegram_last_post_send_query_) { if (telegram_last_post_send_query_) {
uint8_t dest = (telegram_last_[1] & 0x7F); uint8_t dest = (telegram_last_[1] & 0x7F);
DEBUG_LOG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest); LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest);
read_request(telegram_last_post_send_query_, dest, 0); // no offset read_request(telegram_last_post_send_query_, dest, 0); // no offset
} }

View File

@@ -130,10 +130,10 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17))) if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17)))
|| (master_thermostat == device_id)) { || (master_thermostat == device_id)) {
EMSESP::actual_master_thermostat(device_id); EMSESP::actual_master_thermostat(device_id);
DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X (as the master)"), device_id); LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as the master)"), device_id);
init_mqtt(); init_mqtt();
} else { } else {
DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X"), device_id); LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X"), device_id);
} }
} }
@@ -145,39 +145,45 @@ void Thermostat::init_mqtt() {
// for each of the heating circuits // for each of the heating circuits
if (mqtt_format_ == Settings::MQTT_format::HA) { if (mqtt_format_ == Settings::MQTT_format::HA) {
for (uint8_t hc = 0; hc < monitor_typeids.size(); hc++) { for (uint8_t hc = 0; hc < monitor_typeids.size(); hc++) {
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/config"), hc + 1);
// Mqtt::publish(topic.c_str()); // empty payload, this remove any previous config sent to HA
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
std::string payload(100, '\0');
snprintf_P(&payload[0], payload.capacity() + 1, PSTR("thermostat_hc%d"), hc + 1);
doc["name"] = payload; // "name": "thermostat_hc1" std::string hc_text(10, '\0');
doc["unique_id"] = payload; // "unique_id": "thermostat_hc1" snprintf_P(&hc_text[0], hc_text.capacity() + 1, PSTR("hc%d"), hc + 1);
doc["name"] = hc_text;
doc["uniq_id"] = hc_text;
snprintf_P(&payload[0], payload.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat"), hc + 1); doc["~"] = "homeassistant/climate/ems-esp";
doc["~"] = payload; // "homeassistant/climate/hc1/thermostat" doc["mode_cmd_t"] = "~/cmd_mode";
doc["mode_stat_t"] = "~/state";
doc["temp_cmd_t"] = "~/cmd_temp";
doc["temp_stat_t"] = "~/state";
doc["curr_temp_t"] = "~/state";
doc["mode_cmd_t"] = "~/cmd_mode"; std::string mode_str(30, '\0');
doc["mode_stat_t"] = "~/state"; snprintf_P(&mode_str[0], 30, PSTR("{{value_json.hc%d.mode}}"), hc + 1);
doc["mode_stat_tpl"] = "{{value_json.mode}}"; doc["mode_stat_tpl"] = mode_str;
doc["temp_cmd_t"] = "~/cmd_temp";
doc["temp_stat_t"] = "~/state"; std::string seltemp_str(30, '\0');
doc["temp_stat_tpl"] = "{{value_json.seltemp}}"; snprintf_P(&seltemp_str[0], 30, PSTR("{{value_json.hc%d.seltemp}}"), hc + 1);
doc["curr_temp_t"] = "~/state"; doc["temp_stat_tpl"] = seltemp_str;
doc["curr_temp_tpl"] = "{{value_json.currtemp}}";
doc["min_temp"] = "5"; std::string currtemp_str(30, '\0');
doc["max_temp"] = "40"; snprintf_P(&currtemp_str[0], 30, PSTR("{{value_json.hc%d.currtemp}}"), hc + 1);
doc["temp_step"] = "0.5"; doc["curr_temp_tpl"] = currtemp_str;
doc["min_temp"] = "5";
doc["max_temp"] = "40";
doc["temp_step"] = "0.5";
JsonArray modes = doc.createNestedArray("modes"); JsonArray modes = doc.createNestedArray("modes");
modes.add("off"); modes.add("off");
modes.add("heat"); modes.add("heat");
modes.add("auto"); modes.add("auto");
Mqtt::publish(topic.c_str(), doc, true); // publish the config payload with retain flag std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc + 1);
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
Mqtt::publish(topic, doc, true); // publish the config payload with retain flag
// subscribe to the temp and mode commands // subscribe to the temp and mode commands
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/cmd_temp"), hc + 1); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/cmd_temp"), hc + 1);
@@ -212,7 +218,7 @@ void Thermostat::thermostat_cmd(const char * message) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
DeserializationError error = deserializeJson(doc, message); DeserializationError error = deserializeJson(doc, message);
if (error) { if (error) {
DEBUG_LOG(F("MQTT error: payload %s, error %s"), message, error.c_str()); LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
return; return;
} }
@@ -342,7 +348,7 @@ void Thermostat::publish_values() {
return; return;
} }
DEBUG_LOG(F("Performing a thermostat publish (device ID 0x%02X)"), device_id()); LOG_DEBUG(F("Performing a thermostat publish (device ID 0x%02X)"), device_id());
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits
bool has_data = false; bool has_data = false;
@@ -371,8 +377,8 @@ void Thermostat::publish_values() {
} }
has_data = true; has_data = true;
// if the MQTT format is 'nested' then create the parent object hc<n> // if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
if (mqtt_format_ == Settings::MQTT_format::NESTED) { if (mqtt_format_ != Settings::MQTT_format::SINGLE) {
// create nested json for each HC // create nested json for each HC
char hc_name[10]; // hc{1-4} char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10); strlcpy(hc_name, "hc", 10);
@@ -459,9 +465,7 @@ void Thermostat::publish_values() {
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags)); dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
} }
// if format is single, send immediately and quit
// if format is single, send immediately
// if its HA send it to the special topic
if (mqtt_format_ == Settings::MQTT_format::SINGLE) { if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
char topic[30]; char topic[30];
char s[3]; // for formatting strings char s[3]; // for formatting strings
@@ -469,17 +473,18 @@ void Thermostat::publish_values() {
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc); Mqtt::publish(topic, doc);
return; return;
} else if (mqtt_format_ == Settings::MQTT_format::HA) {
std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/state"), hc->hc_num());
Mqtt::publish(topic.c_str(), doc);
return;
} }
} }
if (!has_data) {
return; // nothing to send
}
// if we're using nested json, send all in one go // if we're using nested json, send all in one go
if ((mqtt_format_ == Settings::MQTT_format::NESTED) && has_data) { if (mqtt_format_ == Settings::MQTT_format::NESTED) {
Mqtt::publish("thermostat_data", doc); Mqtt::publish("thermostat_data", doc);
} else if (mqtt_format_ == Settings::MQTT_format::HA) {
Mqtt::publish("homeassistant/climate/ems-esp/state", doc);
} }
} }
@@ -998,19 +1003,19 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
// 0xA5 - Set the display settings // 0xA5 - Set the display settings
void Thermostat::set_settings_display(const uint8_t ds) { void Thermostat::set_settings_display(const uint8_t ds) {
logger_.info(F("Setting display to %d"), ds); LOG_INFO(F("Setting display to %d"), ds);
write_command(EMS_TYPE_IBASettings, 0, ds); write_command(EMS_TYPE_IBASettings, 0, ds);
} }
// 0xA5 - Set the building settings // 0xA5 - Set the building settings
void Thermostat::set_settings_building(const uint8_t bg) { void Thermostat::set_settings_building(const uint8_t bg) {
logger_.info(F("Setting building to %d"), bg); LOG_INFO(F("Setting building to %d"), bg);
write_command(EMS_TYPE_IBASettings, 6, bg); write_command(EMS_TYPE_IBASettings, 6, bg);
} }
// 0xA5 Set the language settings // 0xA5 Set the language settings
void Thermostat::set_settings_language(const uint8_t lg) { void Thermostat::set_settings_language(const uint8_t lg) {
logger_.info(F("Setting building to %d"), lg); LOG_INFO(F("Setting building to %d"), lg);
write_command(EMS_TYPE_IBASettings, 1, lg); write_command(EMS_TYPE_IBASettings, 1, lg);
} }
@@ -1037,21 +1042,21 @@ void Thermostat::set_mode(const std::string & mode, const uint8_t hc_num) {
} else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
set_mode(HeatingCircuit::Mode::COMFORT, hc_num); set_mode(HeatingCircuit::Mode::COMFORT, hc_num);
} else { } else {
logger_.warning(F("Invalid mode %s. Cannot set"), mode.c_str()); LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
} }
} }
// Set the thermostat working mode // Set the thermostat working mode
void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) { void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
if (can_write()) { if (can_write()) {
logger_.warning(F("Write not supported for this model Thermostat")); LOG_WARNING(F("Write not supported for this model Thermostat"));
return; return;
} }
// get hc based on number // get hc based on number
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
logger_.warning(F("set mode: Heating Circuit %d not found or activated"), hc_num); LOG_WARNING(F("set mode: Heating Circuit %d not found or activated"), hc_num);
return; return;
} }
@@ -1127,7 +1132,7 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
break; break;
} }
logger_.info(F("Setting thermostat mode to %s for heating circuit %d"), mode_tostring(mode).c_str(), hc->hc_num()); LOG_INFO(F("Setting thermostat mode to %s for heating circuit %d"), mode_tostring(mode).c_str(), hc->hc_num());
// add the write command to the Tx queue // add the write command to the Tx queue
// post validate is the corresponding monitor or set type IDs as they can differ per model // post validate is the corresponding monitor or set type IDs as they can differ per model
@@ -1138,14 +1143,14 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
// Set the temperature of the thermostat // Set the temperature of the thermostat
void Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) { void Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) {
if (can_write()) { if (can_write()) {
logger_.warning(F("Write not supported for this model Thermostat")); LOG_WARNING(F("Write not supported for this model Thermostat"));
return; return;
} }
// get hc based on number // get hc based on number
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
logger_.warning(F("set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
return; return;
} }
@@ -1244,10 +1249,10 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
// if we know what to send and to where, go and do it // if we know what to send and to where, go and do it
if (offset != -1) { if (offset != -1) {
char s[10]; char s[10];
logger_.info(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"), LOG_INFO(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"),
Helpers::render_value(s, temperature, 2), Helpers::render_value(s, temperature, 2),
hc->hc_num(), hc->hc_num(),
mode_tostring(mode).c_str()); mode_tostring(mode).c_str());
// add the write command to the Tx queue // add the write command to the Tx queue
// value is *2 // value is *2

View File

@@ -16,7 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* ESP32 UART port by Arwed Richert @ArwedL */ /*
* ESP32 UART port by @ArwedL and improved by @MichaelDvP. See https://github.com/proddy/EMS-ESP/issues/380
*/
#if defined(ESP32) #if defined(ESP32)
@@ -26,19 +28,20 @@
namespace emsesp { namespace emsesp {
static RingbufHandle_t buf_handle = NULL; static intr_handle_t uart_handle;
static QueueHandle_t uart_queue = NULL; static RingbufHandle_t buf_handle = NULL;
static volatile uint8_t sending = 0; // If a telegram is send we ++, in receiving we ignore if != 0 and -- static uint8_t rxbuf[UART_FIFO_LEN];
uint8_t tx_mode_ = EMS_TXMODE_DEFAULT; static uint8_t rxlen;
// Main interrupt handler /*
void EMSuart::emsuart_parseTask(void * param) { * Task to handle the incoming data
for (;;) { */
void EMSuart::emsuart_recvTask(void * param) {
while (1) {
size_t item_size; size_t item_size;
uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(1000)); uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, portMAX_DELAY);
uint8_t telegramSize = item_size; // can't be more than EMS_MAXBUFFERSIZE size (checked in sending task) uint8_t telegramSize = item_size;
// Did we had a timeout?
if (telegram) { if (telegram) {
EMSESP::incoming_telegram(telegram, telegramSize); EMSESP::incoming_telegram(telegram, telegramSize);
vRingbufferReturnItem(buf_handle, (void *)telegram); vRingbufferReturnItem(buf_handle, (void *)telegram);
@@ -47,113 +50,90 @@ void EMSuart::emsuart_parseTask(void * param) {
} }
/* /*
* system task triggered on BRK interrupt * UART interrupt, on break read the fifo and put the whole telegram to ringbuffer
* incoming received messages are always asynchronous
* The full buffer is sent to the ems_parseTelegram() function in ems.cpp.
*/ */
void EMSuart::emsuart_recvTask(void * param) { static void IRAM_ATTR uart_intr_handle(void * arg) {
uart_event_t event; if (EMS_UART.int_st.brk_det) {
for (;;) { uint8_t rx_fifo_len = EMS_UART.status.rxfifo_cnt;
if (xQueueReceive(uart_queue, (void *)&event, (portTickType)portMAX_DELAY)) { for (rxlen = 0; rxlen < rx_fifo_len; rxlen++) {
// We are only interested in UART_BREAK event and ignore all others rxbuf[rxlen] = EMS_UART.fifo.rw_byte; // read all bytes into buffer
if (event.type == UART_BREAK) {
// If we didn't send it previously it is a newly received telegram
if (sending == 0) {
// Read all bytes which are already in the buffer
uint8_t data[EMSUART_RXBUFSIZE];
int length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(EMSUART_UART, (size_t *)&length));
if (length > EMSUART_RXBUFSIZE) // pedantic check - should never be the case because the receive buffer is of size EMSUART_RXBUFSIZE
{
length = EMSUART_RXBUFSIZE;
}
length = uart_read_bytes(EMSUART_UART, data, length, 0); // 0 --> we don't want to wait
// we will have runtime performance penalty with impact because of parseTelegram
// --> send the data to a 2nd task --> use a ringbuffer to exchange data between both tasks --> no need for pEMSRxBuf or alike
// this 2nd tasks processes the message with parseTelegram --> our receiving task is still responsiveness
// already do some validty checking here before informing the 2nd task
if ((length == 2) || ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1))) {
xRingbufferSend(buf_handle, data, length - 1, 0); // we are not waiting if ringbuffer is full --> just loose telegrams pdMS_TO_TICKS(1000)
}
} else {
--sending;
}
}
} }
if ((rxlen == 2) || ((rxlen > 4) && (rxlen <= EMS_MAXBUFFERSIZE))) {
int baseType = 0;
xRingbufferSendFromISR(buf_handle, rxbuf, rxlen - 1, &baseType);
}
EMS_UART.int_clr.brk_det = 1; // clear flag
EMS_UART.conf0.txd_brk = 0; // if it was break from sending, clear bit
} }
} }
/* /*
* init UART0 driver * init UART driver
*/ */
void EMSuart::start(uint8_t tx_mode) { void EMSuart::start(uint8_t tx_mode) {
uart_config_t uart_config = {
tx_mode_ = tx_mode; .baud_rate = EMSUART_BAUD,
.data_bits = UART_DATA_8_BITS,
// Configure UART parameters .parity = UART_PARITY_DISABLE,
// clang-format off .stop_bits = UART_STOP_BITS_1,
uart_config_t uart_config = { .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.baud_rate = EMSUART_BAUD, };
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
//.rx_flow_ctrl_thresh = 0, // UART HW RTS threshold should not be important
};
// clang-format on
ESP_ERROR_CHECK(uart_param_config(EMSUART_UART, &uart_config)); ESP_ERROR_CHECK(uart_param_config(EMSUART_UART, &uart_config));
// Set UART pins(TX: IO16 (UART2 default), RX: IO17 (UART2 default), RTS: IO18, CTS: IO19)
ESP_ERROR_CHECK(uart_set_pin(EMSUART_UART, EMSUART_TXPIN, EMSUART_RXPIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); ESP_ERROR_CHECK(uart_set_pin(EMSUART_UART, EMSUART_TXPIN, EMSUART_RXPIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
//EMS_UART.conf1.rxfifo_full_thrhd = 127; // enough to hold the incoming telegram, should never reached
// Setup UART buffered IO with event queue //EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit
// const int uart_buffer_size = (1024 * 2); EMS_UART.int_ena.val = 0; // disable all intr.
// QueueHandle_t uart_queue; EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
// Install UART driver using an event queue here EMS_UART.int_ena.brk_det = 1; // activate only break
// ESP_ERROR_CHECK(uart_driver_install(UART2, uart_buffer_size, buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT);
// uart_buffer_size, 10, &uart_queue, 0)); ESP_ERROR_CHECK(uart_isr_register(EMSUART_UART, uart_intr_handle, NULL, ESP_INTR_FLAG_IRAM, &uart_handle));
// do not use user defined interrupt handlers --> we are waiting for break event xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, 12, NULL);
// Decision was against written buffered I/O (seems to be fit better to ESP8266 approach), Queue is needed for receiving the break event, queue size wasn't clear
// 1 should be enough - also 10 should be fine
ESP_ERROR_CHECK(uart_driver_install(EMSUART_UART, EMSUART_RXBUFSIZE, 0, 10, &uart_queue, 0));
// Create a ringbuffer for communication between recvTask and parseTask (as max message size is ~32 bytes) 512 bytes has capacity to hold more than 16 telegrams
buf_handle = xRingbufferCreate(512, RINGBUF_TYPE_NOSPLIT);
// start recvTaskQueue stacksize choosen any value 4069 bytes, Priority choosen 2 above normal to be able to fastly react on received signals
// xTaskCreate parameters taken from https://github.com/espressif/esp-idf/blob/ce2a99dc23533f40369e4ab0d17ffd40f4b0dd72/examples/peripherals/uart/uart_events/main/uart_events_example_main.c
xTaskCreate(emsuart_recvTask, "EMS_recv", 2048, NULL, 12, NULL);
// create 2 tasks because assumption is that parseTelegram needs some time (even on ESP32) which would block receiving task
xTaskCreate(emsuart_parseTask, "EMS_parse", 8000, NULL, 2, NULL);
} }
/*
* Stop, disables interrupt
*/
void EMSuart::stop() {
EMS_UART.int_ena.val = 0; // disable all intr.
};
/*
* Restart Interrupt
*/
void EMSuart::restart() {
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
EMS_UART.int_ena.brk_det = 1; // activate only break
};
/* /*
* Sends a 1-byte poll, ending with a <BRK> * Sends a 1-byte poll, ending with a <BRK>
* It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking
*/ */
void EMSuart::send_poll(uint8_t data) { void EMSuart::send_poll(uint8_t data) {
char buf[1]; EMS_UART.conf0.txd_brk = 0; // just to make sure the bit is cleared
buf[0] = data; EMS_UART.fifo.rw_byte = data;
uart_write_bytes_with_break(EMSUART_UART, (const char *)buf, 1, EMSUART_BREAKBITS); EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit
EMS_UART.conf0.txd_brk = 1; // sending ends in a break
} }
/* /*
* Send data to Tx line, ending with a <BRK> * Send data to Tx line, ending with a <BRK>
* buf contains the CRC and len is #bytes including the CRC * buf contains the CRC and len is #bytes including the CRC
* returns code, 0=success, 1=brk error, 2=watchdog timeout * returns code, 1=success
*/ */
EMSUART_STATUS EMSuart::transmit(uint8_t * buf, uint8_t len) { EMSUART_STATUS EMSuart::transmit(uint8_t * buf, uint8_t len) {
if (len == 0) { if (len > 0) {
return EMS_TX_STATUS_OK; // nothing to send if (EMS_UART.status.txfifo_cnt > 0) { // fifo not empty
return EMS_TX_WTD_TIMEOUT;
}
EMS_UART.conf0.txd_brk = 0; // just to make sure the bit is cleared
for (uint8_t i = 0; i < len; i++) {
EMS_UART.fifo.rw_byte = buf[i];
}
//uart_tx_chars(EMSUART_UART, (const char *)buf, len);
EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit
EMS_UART.conf0.txd_brk = 1; // sending ends in a break
} }
if (len && buf) {
++sending;
uart_write_bytes_with_break(EMSUART_UART, (const char *)buf, len, EMSUART_BREAKBITS);
}
return EMS_TX_STATUS_OK; return EMS_TX_STATUS_OK;
} }

View File

@@ -27,19 +27,15 @@
#include "freertos/queue.h" #include "freertos/queue.h"
#include <driver/uart.h> #include <driver/uart.h>
#define EMS_MAXBUFFERSIZE 34 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs #define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
#define EMSUART_UART UART_NUM_2 // UART 0 --> Changed to 2 for ESP32 // To do: Adapt #define EMSUART_UART UART_NUM_2 // on the ESP32 we're using UART2
#define EMSUART_RXPIN 17 // To do: Adapt seems to be IO17 for ESP32 UART2 RX pin #define EMS_UART UART2 // for intr setting
#define EMSUART_TXPIN 16 // To do: Adapt seems to be IO16 for ESP32 UART2 TX pin
//#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no parity, 1 stopbit)
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit #define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
#define EMSUART_RXBUFSIZE (2 * UART_FIFO_LEN)
#define EMSUART_BREAKBITS 11 // 11 bits break signal
#define EMS_TXMODE_DEFAULT 1 // customize the GPIO pins for RX and TX here
#define EMS_TXMODE_EMSPLUS 2 #define EMSUART_RXPIN 17 // 17 is UART2 RX. Use 23 for D7 on a Wemos D1-32 mini for backwards compatabilty
#define EMS_TXMODE_HT3 3 #define EMSUART_TXPIN 16 // 16 is UART2 TX. Use 5 for D8 on a Wemos D1-32 mini for backwards compatabilty
namespace emsesp { namespace emsesp {
@@ -56,14 +52,12 @@ class EMSuart {
static void start(uint8_t tx_mode); static void start(uint8_t tx_mode);
static void send_poll(uint8_t data); static void send_poll(uint8_t data);
static void stop();
static void restart();
static EMSUART_STATUS transmit(uint8_t * buf, uint8_t len); static EMSUART_STATUS transmit(uint8_t * buf, uint8_t len);
static void stop(){}; // not used with ESP32
static void restart(){}; // not used with ESP32
private: private:
static void emsuart_recvTask(void * param); static void emsuart_recvTask(void * param);
static void emsuart_parseTask(void * param);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -24,52 +24,32 @@
namespace emsesp { namespace emsesp {
os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
EMSuart::EMSRxBuf_t * pEMSRxBuf; EMSuart::EMSRxBuf_t * pEMSRxBuf;
EMSuart::EMSRxBuf_t * paEMSRxBuf[EMS_MAXBUFFERS]; EMSuart::EMSRxBuf_t * paEMSRxBuf[EMS_MAXBUFFERS];
uint8_t emsRxBufIdx = 0; uint8_t emsRxBufIdx = 0;
uint8_t phantomBreak = 0;
uint8_t tx_mode_ = EMS_TXMODE_DEFAULT;
//
// Main interrupt handler // Main interrupt handler
// Important: must not use ICACHE_FLASH_ATTR // Important: must not use ICACHE_FLASH_ATTR
//
void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) { void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
static uint8_t length = 0; static uint8_t length = 0;
static bool rx_idle_ = true; static uint8_t uart_buffer[128];
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE + 2];
// is a new buffer? if so init the thing for a new telegram
if (rx_idle_) {
rx_idle_ = false; // status set to busy
length = 0;
}
// fill IRQ buffer, by emptying Rx FIFO
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
uint8_t rx = USF(EMSUART_UART);
if (length < EMS_MAXBUFFERSIZE)
uart_buffer[length++] = rx;
}
// clear Rx FIFO full and Rx FIFO timeout interrupts
USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
}
// BREAK detection = End of EMS data block // BREAK detection = End of EMS data block
if (USIS(EMSUART_UART) & ((1 << UIBD))) { if (USIS(EMSUART_UART) & ((1 << UIBD))) {
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them uint8_t rxlen = (USS(EMSUART_UART) & 0xFF); // length of buffer
for (length = 0; length < rxlen; length++) {
uart_buffer[length] = USF(EMSUART_UART);
}
USIE(EMSUART_UART) = 0; // disable all interrupts and clear them
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset <BRK> from sending
if (length < EMS_MAXBUFFERSIZE) { // only a valid telegram
pEMSRxBuf->length = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
}
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break
pEMSRxBuf->length = (length > EMS_MAXBUFFERSIZE) ? EMS_MAXBUFFERSIZE : length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
rx_idle_ = true; // check set the status flag stating BRK has been received and we can start a new package
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
} }
} }
@@ -84,40 +64,23 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_recvTask(os_event_t * events) {
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
pCurrent->length = 0; pCurrent->length = 0;
if (phantomBreak) {
phantomBreak = 0;
length--; // remove phantom break from Rx buffer
}
// it's a poll or status code, single byte and ok to send on, then quit // it's a poll or status code, single byte and ok to send on, then quit
if (length == 2) { if (length == 2) {
EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, 1); EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, 1);
return; return;
} }
// ignore double BRK at the end, possibly from the Tx loopback
// also telegrams with no data value // also telegrams with no data value
// then transmit EMS buffer, excluding the BRK // then transmit EMS buffer, excluding the BRK, length is checked by irq
if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) { if (length > 4) {
EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, length - 1); EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, length - 1);
} }
} }
/*
* flush everything left over in buffer, this clears both rx and tx FIFOs
*/
void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() {
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
}
/* /*
* init UART0 driver * init UART0 driver
*/ */
void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
tx_mode_ = tx_mode;
// allocate and preset EMS Receive buffers // allocate and preset EMS Receive buffers
for (int i = 0; i < EMS_MAXBUFFERS; i++) { for (int i = 0; i < EMS_MAXBUFFERS; i++) {
EMSRxBuf_t * p = (EMSRxBuf_t *)malloc(sizeof(EMSRxBuf_t)); EMSRxBuf_t * p = (EMSRxBuf_t *)malloc(sizeof(EMSRxBuf_t));
@@ -125,9 +88,6 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
} }
pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer
ETS_UART_INTR_DISABLE();
ETS_UART_INTR_ATTACH(nullptr, nullptr);
// pin settings // pin settings
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
@@ -136,43 +96,21 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
// set 9600, 8 bits, no parity check, 1 stop bit // set 9600, 8 bits, no parity check, 1 stop bit
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD); USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1 USC0(EMSUART_UART) = EMSUART_CONFIG;
emsuart_flush_fifos(); USC0(EMSUART_UART) |= ((1 << UCRXRST) | (1 << UCTXRST)); // set bits
USC0(EMSUART_UART) &= ~((1 << UCRXRST) | (1 << UCTXRST)); // clear bits
// conf1 params // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be more than 32)
// UCTOE = RX TimeOut enable (default is 1) USC1(EMSUART_UART) = (0x7F << UCFFT); // rx buffer full
// UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2) USIE(EMSUART_UART) = 0; // disable all interrupts
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
//
// change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
// Otherwise, we're only noticed by UCTOT or RxBRK!
USC1(EMSUART_UART) = 0; // reset config first
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (0 << UCTOE); // enable interupts
// set interrupts for triggers
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
USIE(EMSUART_UART) = 0; // disable all interrupts
// enable rx break, fifo full and timeout.
// but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes
// change: we don't care about Rx Timeout - it may lead to wrong readouts
USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (0 << UITO);
// set up interrupt callbacks for Rx
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
// disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
system_set_os_print(0);
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
system_uart_swap();
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen); // set up interrupt callbacks for Rx
system_set_os_print(0); // disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
system_uart_swap(); // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr); ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
ETS_UART_INTR_ENABLE(); USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break interrupt
// logger_.info(F("UART service for Rx/Tx started"));
} }
/* /*
@@ -180,170 +118,38 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
* This is called prior to an OTA upload and also before a save to the filesystem to prevent conflicts * This is called prior to an OTA upload and also before a save to the filesystem to prevent conflicts
*/ */
void ICACHE_FLASH_ATTR EMSuart::stop() { void ICACHE_FLASH_ATTR EMSuart::stop() {
ETS_UART_INTR_DISABLE(); USIE(EMSUART_UART) = 0; // disable interrup
} }
/* /*
* re-start UART0 driver * re-start UART0 driver
*/ */
void ICACHE_FLASH_ATTR EMSuart::restart() { void ICACHE_FLASH_ATTR EMSuart::restart() {
ETS_UART_INTR_ENABLE(); USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break
} }
/*
* Send a BRK signal
* Which is a 11-bit set of zero's (11 cycles)
*/
void ICACHE_FLASH_ATTR EMSuart::tx_brk() {
uint32_t tmp;
// must make sure Tx FIFO is empty
while (((USS(EMSUART_UART) >> USTXC) & 0xFF))
;
tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
// To create a 11-bit <BRK> we set TXD_BRK bit so the break signal will
// automatically be sent when the tx fifo is empty
tmp = (1 << UCBRK);
USC0(EMSUART_UART) |= (tmp); // set bit
if (tx_mode_ == EMS_TX_WTD_TIMEOUT) { // EMS+ mode
delayMicroseconds(EMSUART_TX_BRK_WAIT);
} else if (tx_mode_ == EMS_TXMODE_HT3) { // junkers mode
delayMicroseconds(EMSUART_TX_WAIT_BRK - EMSUART_TX_LAG); // 1144 (11 Bits)
}
USC0(EMSUART_UART) &= ~(tmp); // clear bit
}
/*
* Sends a 1-byte poll, ending with a <BRK>
* It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking
*/
void EMSuart::send_poll(uint8_t data) { void EMSuart::send_poll(uint8_t data) {
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK> bit
USF(EMSUART_UART) = data; USF(EMSUART_UART) = data;
delayMicroseconds(EMSUART_TX_BRK_WAIT); USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
tx_brk(); // send <BRK>
} }
/* /*
* Send data to Tx line, ending with a <BRK> * Send data to Tx line, ending with a <BRK>
* buf contains the CRC and len is #bytes including the CRC * buf contains the CRC and len is #bytes including the CRC
* returns code, 0=success, 1=brk error, 2=watchdog timeout
*/ */
EMSUART_STATUS ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { EMSUART_STATUS ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
if (len == 0) { if (len) {
return EMS_TX_STATUS_OK; // nothing to send USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK> bit
}
// EMS+ https://github.com/proddy/EMS-ESP/issues/23#
if (tx_mode_ == EMS_TXMODE_EMSPLUS) { // With extra tx delay for EMS+
for (uint8_t i = 0; i < len; i++) { for (uint8_t i = 0; i < len; i++) {
USF(EMSUART_UART) = buf[i]; USF(EMSUART_UART) = buf[i];
delayMicroseconds(EMSUART_TX_BRK_WAIT); // 2070
} }
tx_brk(); // send <BRK> USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
return EMS_TX_STATUS_OK;
} }
return EMS_TX_STATUS_OK;
// Junkers logic by @philrich
if (tx_mode_ == EMS_TXMODE_HT3) {
for (uint8_t i = 0; i < len; i++) {
USF(EMSUART_UART) = buf[i];
// just to be safe wait for tx fifo empty (still needed?)
while (((USS(EMSUART_UART) >> USTXC) & 0xff))
;
// wait until bits are sent on wire
delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP); // 1760
}
tx_brk(); // send <BRK>
return EMS_TX_STATUS_OK;
}
/*
* Logic for tx_mode of 0 (EMS_TXMODE_DEFAULT)
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
*
* Logic:
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
* after sending the last char we poll the Rx status until either
* - size(Rx FIFO) == size(Tx-Telegram)
* - <BRK> is detected
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
*
* EMS-Bus error handling
* 1. Busmaster stops echoing on Tx w/o permission
* 2. Busmaster cancel telegram by sending a BRK
*
* Case 1. is handled by a watchdog counter which is reset on each
* Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus
* some smart guess for processing time on targeted EMS device.
* We set Status to EMS_TX_WTD_TIMEOUT and return
*
* Case 2. is handled via a BRK chk during transmission.
* We set Status to EMS_TX_BRK_DETECT and return
*
*/
EMSUART_STATUS result = EMS_TX_STATUS_OK;
// disable rx interrupt
// clear Rx status register, resetting the Rx FIFO and flush it
ETS_UART_INTR_DISABLE();
USC0(EMSUART_UART) |= (1 << UCRXRST);
emsuart_flush_fifos();
// send the bytes along the serial line
for (uint8_t i = 0; i < len; i++) {
uint16_t wdc = EMS_TX_TO_COUNT; // 1760
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
USF(EMSUART_UART) = buf[i]; // send each Tx byte
// wait for echo from the busmaster
while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) {
delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles...
if (--wdc == 0) {
ETS_UART_INTR_ENABLE();
return EMS_TX_WTD_TIMEOUT;
}
if (USIR(EMSUART_UART) & (1 << UIBD)) {
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
ETS_UART_INTR_ENABLE();
return EMS_TX_BRK_DETECT;
}
}
}
// we got the whole telegram in the Rx buffer
// on Rx-BRK (bus collision), we simply enable Rx and leave it
// otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT.
// worst case, we'll see an additional Rx-BRK...
if (result == EMS_TX_STATUS_OK) {
// neither bus collision nor timeout - send terminating BRK signal
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
// no bus collision - send terminating BRK signal
USC0(EMSUART_UART) |= (1 << UCLBE) | (1 << UCBRK); // enable loopback & set <BRK>
// wait until BRK detected...
while (!(USIR(EMSUART_UART) & (1 << UIBD))) {
delayMicroseconds(EMSUART_BIT_TIME);
}
USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK>
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
phantomBreak = 1;
}
}
ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
return result; // send the Tx status back
} }
} // namespace emsesp } // namespace emsesp
#endif #endif

View File

@@ -28,19 +28,7 @@
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit #define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
#define EMS_MAXBUFFERS 3 // buffers for circular filling to avoid collisions #define EMS_MAXBUFFERS 3 // buffers for circular filling to avoid collisions
#define EMS_MAXBUFFERSIZE 34 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs #define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
#define EMSUART_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_TX_BRK_WAIT 2070 // the BRK from Boiler master is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag)
#define EMSUART_TX_WAIT_BYTE (EMSUART_BIT_TIME * 10) // Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit)
#define EMSUART_TX_WAIT_BRK (EMSUART_BIT_TIME * 11) // Time to send a BRK Signal (11 Bit)
#define EMSUART_TX_WAIT_GAP (EMSUART_BIT_TIME * 7) // Gap between to Bytes
#define EMSUART_TX_LAG 8
#define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8)
#define EMS_TX_TO_CHARS (2 + 20)
#define EMS_TX_TO_COUNT ((EMS_TX_TO_CHARS)*10 * 8)
#define EMSUART_recvTaskPrio 1 // 0, 1 or 2. 0 being the lowest #define EMSUART_recvTaskPrio 1 // 0, 1 or 2. 0 being the lowest
#define EMSUART_recvTaskQueueLen 10 // number of queued'd Rx triggers #define EMSUART_recvTaskQueueLen 10 // number of queued'd Rx triggers
@@ -74,11 +62,8 @@ class EMSuart {
} EMSRxBuf_t; } EMSRxBuf_t;
private: private:
static void ICACHE_RAM_ATTR emsuart_rx_intr_handler(void * para); static void ICACHE_RAM_ATTR emsuart_rx_intr_handler(void * para);
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events); static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events);
static void ICACHE_FLASH_ATTR emsuart_flush_fifos();
static void ICACHE_FLASH_ATTR tx_brk();
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0a6" #define EMSESP_APP_VERSION "2.0.0a7"