From d3953d90ca371e6057c7ec7d13ceb8d955d8330c Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 25 May 2020 17:13:05 +0200 Subject: [PATCH] a7 --- README.md | 5 +- platformio.ini | 16 +-- src/boiler.cpp | 24 ++-- src/console.cpp | 39 +++-- src/console.h | 11 +- src/emsdevice.cpp | 6 +- src/emsesp.cpp | 36 ++--- src/emsesp.h | 15 +- src/heatpump.cpp | 2 +- src/helpers.cpp | 3 +- src/mixing.cpp | 4 +- src/mqtt.cpp | 129 +++++++---------- src/mqtt.h | 15 +- src/network.cpp | 35 ++--- src/sensors.cpp | 72 ++++++---- src/sensors.h | 4 + src/settings.cpp | 22 +-- src/settings.h | 6 +- src/shower.cpp | 8 +- src/solar.cpp | 4 +- src/system.cpp | 32 ++--- src/system.h | 7 +- src/telegram.cpp | 35 ++--- src/thermostat.cpp | 107 +++++++------- src/uart/emsuart_esp32.cpp | 168 ++++++++++------------ src/uart/emsuart_esp32.h | 22 ++- src/uart/emsuart_esp8266.cpp | 270 +++++------------------------------ src/uart/emsuart_esp8266.h | 19 +-- src/version.h | 2 +- 29 files changed, 461 insertions(+), 657 deletions(-) diff --git a/README.md b/README.md index 093e50d34..9f7c63664 100644 --- a/README.md +++ b/README.md @@ -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: exit help - log [level] [trace ID] + log [level] [trace ID] [raw] su (top level) @@ -124,7 +124,6 @@ thermostat ### **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 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. @@ -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 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? + ``` diff --git a/platformio.ini b/platformio.ini index 78616f747..8468e8390 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 = - ; -D EMSESP_DEBUG + -D EMSESP_DEBUG ; -D EMSESP_SAFE_MODE ; -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 BEARSSL_SSL_BASIC -D PROGMEM_WWW - -D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=0 + -D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=1 libs_core = ArduinoJson @@ -58,18 +58,18 @@ check_flags = clangtidy: --checks=-*,clang-analyzer-*,performance-* ; USB upload -upload_protocol = esptool +; upload_protocol = esptool ; example ports for OSX ;upload_port = /dev/cu.wchusbserial14403 ;upload_port = /dev/cu.usbserial-1440 ;upload_port = /dev/cu.SLAB_USBtoUART ; OTA upload -; upload_protocol = espota -; upload_flags = -; --port=8266 -; --auth=neo -; upload_port = ems-esp.local +upload_protocol = espota +upload_flags = + --port=8266 + --auth=neo +upload_port = ems-esp32.local [env:esp8266] build_type = release diff --git a/src/boiler.cpp b/src/boiler.cpp index d708dd5ed..c9b6bca9e 100644 --- a/src/boiler.cpp +++ b/src/boiler.cpp @@ -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) : 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... 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 doc; DeserializationError error = deserializeJson(doc, message); 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; } const char * command = doc["cmd"]; @@ -312,7 +312,7 @@ void Boiler::publish_values() { } #ifdef EMSESP_DEBUG - DEBUG_LOG(F("[DEBUG] Performing a boiler publish")); + LOG_DEBUG(F("[DEBUG] Performing a boiler publish")); #endif // if we have data, publish it @@ -675,31 +675,31 @@ void Boiler::process_UBAMaintenanceSettings(std::shared_ptr tele // Set the warm water temperature 0x33 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); } // flow temp 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); } // 1=hot, 2=eco, 3=intelligent void Boiler::set_warmwater_mode(const uint8_t comfort) { 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) { - logger_.info(F("Setting boiler warm water to eco")); + LOG_INFO(F("Setting boiler warm water to eco")); } 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); } // turn on/off warm water 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; // https://github.com/proddy/EMS-ESP/issues/268 @@ -715,7 +715,7 @@ void Boiler::set_warmwater_activated(const bool activated) { // 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' 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]; for (uint8_t i = 0; i < sizeof(message_data); i++) { message_data[i] = 0x00; @@ -743,14 +743,14 @@ void Boiler::set_tapwarmwater_activated(const bool activated) { // true = on, false = off // See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers 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)); } // Activate / De-activate circulation of warm water 0x35 // true = on, false = off 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)); } diff --git a/src/console.cpp b/src/console.cpp index a8f15d588..1f57b045e 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -221,8 +221,9 @@ void Console::load_standard_commands(unsigned int context) { context, CommandFlags::USER, 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 & arguments) { + uint16_t watch_id; if (!arguments.empty()) { uuid::log::Level level; @@ -233,15 +234,33 @@ void Console::load_standard_commands(unsigned int context) { return; } - // see if we have extra argument, for trace - uint16_t watch_id = 0; // no watch ID set - if ((arguments.size() == 2) && (level == uuid::log::Level::TRACE)) { - watch_id = Helpers::hextoint(arguments[1].c_str()); - shell.printfln(("Tracing only telegrams that match a device ID or telegram type of 0x%02X"), watch_id); + // trace logic + if (level == uuid::log::Level::TRACE) { + watch_id = LOG_TRACE_WATCH_NONE; // no watch ID set + if (arguments.size() > 1) { + 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())); + 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 & arguments __attribute__((unused))) -> std::vector { return uuid::log::levels_lowercase(); @@ -259,7 +278,7 @@ void Console::load_standard_commands(unsigned int context) { flash_string_vector{F_(exit)}, [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { // delete MAIN console stuff first to save memory - EMSESPShell::commands->remove_context_commands(context); + EMSESPShell::commands->remove_context_commands(context); shell.exit_context(); }); @@ -384,6 +403,8 @@ EMSESPStreamConsole::~EMSESPStreamConsole() { #endif ptys_[pty_] = false; 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 #ifndef EMSESP_STANDALONE 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 } diff --git a/src/console.h b/src/console.h index 4b0488784..049c4d51b 100644 --- a/src/console.h +++ b/src/console.h @@ -41,8 +41,12 @@ using uuid::log::Level; // clang-format off -#define DEBUG_LOG(...) if (logger_.enabled(Level::DEBUG)) {logger_.debug(__VA_ARGS__);} -#define TRACE_LOG(...) if (logger_.enabled(Level::TRACE)) {logger_.trace(__VA_ARGS__);} +#define LOG_DEBUG(...) if (logger_.enabled(Level::DEBUG)) {logger_.debug(__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_WORD(string_name) MAKE_PSTR(string_name, #string_name) @@ -84,6 +88,7 @@ MAKE_PSTR_WORD(debug) MAKE_PSTR_WORD(restart) MAKE_PSTR_WORD(reconnect) MAKE_PSTR_WORD(format) +MAKE_PSTR_WORD(raw) // context menus MAKE_PSTR_WORD(mqtt) @@ -96,6 +101,8 @@ MAKE_PSTR(asterisks, "********") MAKE_PSTR(n_mandatory, "") MAKE_PSTR(n_optional, "[n]") MAKE_PSTR(traceid_optional, "[trace ID]") +MAKE_PSTR(trace_raw_fmt, "Displaying raw bytes = %s") +MAKE_PSTR(raw_optional, "[raw]") MAKE_PSTR(bool_mandatory, "") MAKE_PSTR(typeid_mandatory, "") MAKE_PSTR(deviceid_mandatory, "") diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 5726f30bb..a588b9441 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -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 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_) { 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) { - 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); } @@ -230,7 +230,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr telegr bool EMSdevice::process_telegram(std::shared_ptr telegram) { for (const auto & tf : telegram_functions_) { 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); return true; } diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 70289c5d4..3fbeb4844 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -56,7 +56,8 @@ Shower EMSESP::shower_; // Shower logic // static/common variables 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::ems_read_only_; uint32_t EMSESP::last_fetch_ = 0; @@ -332,7 +333,7 @@ void EMSESP::process_UBADevices(std::shared_ptr telegram) { // 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 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); } } @@ -391,9 +392,11 @@ void EMSESP::process_version(std::shared_ptr telegram) { // We also check for common telgram types, like the Version(0x02) // returns false if there are none found bool EMSESP::process_telegram(std::shared_ptr telegram) { - // print to console if logging is TRACE - if ((trace_watch_id_ == 0) || (telegram->src == trace_watch_id_) || (telegram->dest == trace_watch_id_) || (telegram->type_id == trace_watch_id_)) { - TRACE_LOG(pretty_telegram(telegram).c_str()); + if ((logger_.enabled(Level::TRACE)) && !trace_raw()) { + if ((trace_watch_id_ == LOG_TRACE_WATCH_NONE) || (telegram->src == trace_watch_id_) || (telegram->dest == trace_watch_id_) + || (telegram->type_id == trace_watch_id_)) { + LOG_TRACE(pretty_telegram(telegram).c_str()); + } } // only process broadcast telegrams or ones sent to us on request @@ -431,7 +434,7 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { } 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; @@ -504,7 +507,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: for (const auto & emsdevice : emsdevices) { if (emsdevice) { 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->version(version); 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 (!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 } 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 send_read_request(EMSdevice::EMS_TYPE_VERSION, 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 if (length == 1) { 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_.send_poll(); // close the bus txservice_.post_send_query(); // send type_id to last destination } 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 } else { // 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 dest = data[1]; 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_.send_poll(); } 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 uint8_t retries = txservice_.retry_tx(); // returns 0 if exceeded count if (retries) { - DEBUG_LOG(F("Last Tx read failed. Retrying #%d..."), retries); + LOG_DEBUG(F("Last Tx read failed. Retrying #%d..."), retries); } 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 void EMSESP::set_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 @@ -786,7 +789,7 @@ void EMSESP::console_commands(Shell & shell, unsigned int context) { // kick off the party, start all the services void EMSESP::start() { - // Load our libary of known devices + // Load our library of known devices device_library_ = { #include "device_library.h" }; @@ -798,6 +801,7 @@ void EMSESP::start() { rxservice_.start(); txservice_.start(); shower_.start(); + mqtt_.start(); set_ems_read_only(); // see if we have Tx disabled and set the flag } diff --git a/src/emsesp.h b/src/emsesp.h index 949747c4a..c66698e35 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -45,6 +45,8 @@ #include "boiler.h" #include "shower.h" +#define LOG_TRACE_WATCH_NONE 0 // no watch set + namespace emsesp { class Shower; // forward declaration for compiler @@ -79,8 +81,6 @@ class EMSESP { static uint8_t actual_master_thermostat(); 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_devices(uuid::console::Shell & shell); static void show_emsbus(uuid::console::Shell & shell); @@ -93,10 +93,20 @@ class EMSESP { return sensors_.devices(); } + static void trace_watch_id(uint16_t id); + static uint16_t 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() { return tap_water_active_; } @@ -151,6 +161,7 @@ class EMSESP { static uint8_t actual_master_thermostat_; static uint16_t trace_watch_id_; + static bool trace_raw_; static bool tap_water_active_; static bool ems_read_only_; }; diff --git a/src/heatpump.cpp b/src/heatpump.cpp index ab46b23e0..1c52442cc 100644 --- a/src/heatpump.cpp +++ b/src/heatpump.cpp @@ -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) : 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 register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1)); diff --git a/src/helpers.cpp b/src/helpers.cpp index 2f1e8efce..0b4ef1d3a 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -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 val = 0; while (*hex) { diff --git a/src/mixing.cpp b/src/mixing.cpp index 488406976..9b301aabb 100644 --- a/src/mixing.cpp +++ b/src/mixing.cpp @@ -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) : 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 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 - DEBUG_LOG(F("[DEBUG] Performing a mixing module publish")); + LOG_DEBUG(F("[DEBUG] Performing a mixing module publish")); #endif char topic[30]; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 10fdc9ab9..55197fa24 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -93,11 +93,11 @@ void Mqtt::reconnect() { #ifndef EMSESP_STANDALONE mqttClient_.disconnect(); #endif - DEBUG_LOG(F("Reconnecting...")); + LOG_DEBUG(F("Reconnecting...")); } // MQTT setup -void Mqtt::start() { +void Mqtt::setup() { // exit if already initialized if (mqtt_start_) { return; @@ -145,7 +145,7 @@ void Mqtt::start() { mqtt_last_connection_ = millis(); mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; - DEBUG_LOG(F("Configuring MQTT service...")); + LOG_DEBUG(F("Configuring MQTT service...")); } // MQTT init callbacks @@ -161,19 +161,19 @@ void Mqtt::init() { mqttClient_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { - logger_.err(F("Disconnected from server")); + LOG_DEBUG(F("Disconnected from server")); } if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { - logger_.err(F("Server identifier Rejected")); + LOG_ERROR(F("Server identifier Rejected")); } if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { - logger_.err(F("Server unavailable")); + LOG_ERROR(F("Server unavailable")); } if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { - logger_.err(F("Malformed credentials")); + LOG_ERROR(F("Malformed credentials")); } if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { - logger_.err(F("Not authorized")); + LOG_ERROR(F("Not authorized")); } // Reset reconnection delay @@ -182,7 +182,7 @@ void Mqtt::init() { 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); }); @@ -271,8 +271,8 @@ void Mqtt::loop() { } #ifndef EMSESP_STANDALONE - start(); - logger_.info(F("Connecting to MQTT server...")); + setup(); + LOG_INFO(F("Connecting to the MQTT server...")); mqttClient_.connect(); // Connect to the MQTT broker #endif } @@ -338,7 +338,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) { strlcpy(message, payload, len + 1); #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 // 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 - 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 @@ -382,7 +382,7 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic // and always remove from queue void Mqtt::on_publish(uint16_t packetId) { // find the MQTT message in the queue and remove it - if ((mqtt_messages_.empty()) || (mqtt_qos_ == 0)) { + if (mqtt_messages_.empty()) { return; } @@ -394,9 +394,9 @@ void Mqtt::on_publish(uint16_t 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 { - 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 } @@ -419,31 +419,26 @@ char * Mqtt::make_topic(char * result, const std::string & topic) { 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 void Mqtt::send_start_topic() { StaticJsonDocument<90> doc; doc["event"] = "start"; doc["version"] = Settings().app_version(); -#ifndef EMSESP_STANDALONE - doc["IP"] = WiFi.localIP().toString(); -#endif - publish("info", doc, false); // send with retain off } // MQTT onConnect - when a connect is established void Mqtt::on_connect() { - DEBUG_LOG(F("MQTT connected")); - mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; mqtt_last_connection_ = millis(); mqtt_connecting_ = false; - - 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 + LOG_INFO(F("MQTT connected")); } // 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 } -// 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(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 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 @@ -518,7 +481,7 @@ void Mqtt::queue_subscribe_message(const std::string & topic) { } auto message = std::make_shared(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 (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)); } +// 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 void Mqtt::publish(const std::string & topic, const std::string & payload, bool 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) { - 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) { 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 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) { strcpy(full_topic, message->topic.c_str()); } else { @@ -585,14 +551,14 @@ void Mqtt::process_queue() { // if we're subscribing... 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 uint16_t packet_id = mqttClient_.subscribe(full_topic, mqtt_qos_); #else uint16_t packet_id = 1; #endif 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 @@ -606,25 +572,26 @@ void Mqtt::process_queue() { return; } -// else try and publish it + // else try and publish it + #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 uint16_t packet_id = 1; #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) { // it failed. if we retried n times, give up. remove from queue 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_messages_.pop_front(); // delete return; } else { mqtt_messages_.front().retry_count_++; - // logger_.err(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); + LOG_DEBUG(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 } } @@ -633,7 +600,7 @@ void Mqtt::process_queue() { // but add the packet_id so we can check it later if (mqtt_qos_ != 0) { 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; } diff --git a/src/mqtt.h b/src/mqtt.h index 6cc9e5ed0..fb44028dc 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -63,6 +63,7 @@ struct MqttMessage { class Mqtt { public: void loop(); + void start(); void send_heartbeat(); enum Operation { PUBLISH, SUBSCRIBE }; @@ -109,32 +110,28 @@ class Mqtt { }; static std::deque mqtt_messages_; - void start(); - #ifndef EMSESP_STANDALONE static AsyncMqttClient mqttClient_; #endif void flush_message_queue(); + void setup(); static constexpr size_t MAX_MQTT_MESSAGES = 50; static size_t maximum_mqtt_messages_; static uint16_t mqtt_message_id_; + static bool mqtt_retain_; 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 uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing - static constexpr uint8_t MQTT_KEEP_ALIVE = 60; + 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_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_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_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_subscribe_message(const std::string & topic); void on_publish(uint16_t packetId); diff --git a/src/network.cpp b/src/network.cpp index 6a9b1cb2b..7d11fcbf6 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -36,6 +36,7 @@ void Network::start() { WiFi.persistent(false); WiFi.disconnect(true); WiFi.setAutoReconnect(false); + WiFi.mode(WIFI_STA); #endif #if defined(ESP8266) @@ -71,7 +72,7 @@ void Network::sta_mode_start(WiFiEvent_t event, WiFiEventInfo_t info) { #if defined(ESP8266) 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.bssid[0], event.bssid[1], @@ -86,7 +87,7 @@ void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) { } #elif defined(ESP32) 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.sta_connected.mac[0], info.sta_connected.mac[1], @@ -107,10 +108,10 @@ void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & eve if (event.reason == 201) { if (++disconnect_count_ == 3) { 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; } 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 } } @@ -118,20 +119,20 @@ void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & eve } #elif defined(ESP32) 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 #if defined(ESP8266) 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.mask).c_str(), uuid::printable_to_string(event.gw).c_str()); } #elif defined(ESP32) 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.netmask.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_->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 EMSuart::stop(); // UART 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) { /* static unsigned int _progOld; unsigned int _prog = (progress / (total / 100)); if (_prog != _progOld) { - DEBUG_LOG(F("[OTA] Progress: %u%%"), _prog); + LOG_DEBUG(F("[OTA] Progress: %u%%"), _prog); _progOld = _prog; } */ @@ -224,23 +225,23 @@ void Network::ota_setup() { ota_->onError([this](ota_error_t error) { if (error == OTA_AUTH_ERROR) { - logger_.err(F("[OTA] Auth Failed")); + LOG_ERROR(F("[OTA] Auth Failed")); } else if (error == OTA_BEGIN_ERROR) { - logger_.err(F("[OTA] Begin Failed")); + LOG_ERROR(F("[OTA] Begin Failed")); } else if (error == OTA_CONNECT_ERROR) { - logger_.err(F("[OTA] Connect Failed")); + LOG_ERROR(F("[OTA] Connect Failed")); } else if (error == OTA_RECEIVE_ERROR) { - logger_.err(F("[OTA] Receive Failed")); + LOG_ERROR(F("[OTA] Receive Failed")); } else if (error == OTA_END_ERROR) { - logger_.err(F("[OTA] End Failed")); + LOG_ERROR(F("[OTA] End Failed")); } else { - logger_.err(F("[OTA] Error %d"), error); + LOG_ERROR(F("[OTA] Error %d"), error); }; }); // start ota service 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 } diff --git a/src/sensors.cpp b/src/sensors.cpp index a7dd020c3..339af7eed 100644 --- a/src/sensors.cpp +++ b/src/sensors.cpp @@ -30,6 +30,23 @@ void Sensors::start() { // copy over values from MQTT so we don't keep on quering the filesystem 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 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 bus_.begin(SENSOR_GPIO); #endif @@ -39,7 +56,7 @@ void Sensors::loop() { #ifndef EMSESP_STANDALONE if (state_ == State::IDLE) { 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()) { bus_.skip(); bus_.write(CMD_CONVERT_TEMP); @@ -47,28 +64,28 @@ void Sensors::loop() { state_ = State::READING; } else { // 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 } last_activity_ = millis(); } } else if (state_ == State::READING) { 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(); found_.clear(); state_ = State::SCANNING; last_activity_ = millis(); } else if (millis() - last_activity_ > READ_TIMEOUT_MS) { - logger_.err(F("Sensor read timeout")); + LOG_ERROR(F("Sensor read timeout")); state_ = State::IDLE; last_activity_ = millis(); } } else if (state_ == State::SCANNING) { if (millis() - last_activity_ > SCAN_TIMEOUT_MS) { - logger_.err(F("Sensor scan timeout")); + LOG_ERROR(F("Sensor scan timeout")); state_ = State::IDLE; last_activity_ = millis(); } else { @@ -89,24 +106,24 @@ void Sensors::loop() { /* // comment out for debugging char result[10]; - DEBUG_LOG(F("Temp of %s = %s"), + LOG_DEBUG(F("Temp of %s = %s"), found_.back().to_string().c_str(), Helpers::render_value(result, found_.back().temperature_c_, 2)); */ break; 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; } } 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 { bus_.depower(); devices_ = std::move(found_); 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; last_activity_ = millis(); } @@ -129,7 +146,7 @@ bool Sensors::temperature_convert_complete() { float Sensors::get_temperature_c(const uint8_t addr[]) { #ifndef EMSESP_STANDALONE 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; } @@ -140,22 +157,22 @@ float Sensors::get_temperature_c(const uint8_t addr[]) { bus_.read_bytes(scratchpad, SCRATCHPAD_LEN); 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; } 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"), - scratchpad[0], - scratchpad[1], - scratchpad[2], - scratchpad[3], - scratchpad[4], - scratchpad[5], - scratchpad[6], - scratchpad[7], - scratchpad[8], - Device(addr).to_string().c_str()); + LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"), + scratchpad[0], + scratchpad[1], + scratchpad[2], + scratchpad[3], + scratchpad[4], + scratchpad[5], + scratchpad[6], + scratchpad[7], + scratchpad[8], + Device(addr).to_string().c_str()); return NAN; } @@ -227,7 +244,7 @@ void Sensors::publish_values() { // if we're not using nested JSON, send each sensor out seperately // sensor1, sensor2 etc... // e.g. sensor_1 = {"temp":20.2} - if (mqtt_format_ != Settings::MQTT_format::NESTED) { + if (mqtt_format_ == Settings::MQTT_format::SINGLE) { StaticJsonDocument<100> doc; for (const auto & device : devices_) { char s[5]; @@ -242,13 +259,14 @@ void Sensors::publish_values() { } // 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 = { // "sensor1":{"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"}, // "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); DynamicJsonDocument doc(100 * num_devices); @@ -263,7 +281,11 @@ void Sensors::publish_values() { 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 diff --git a/src/sensors.h b/src/sensors.h index a3b530037..f8553f334 100644 --- a/src/sensors.h +++ b/src/sensors.h @@ -63,7 +63,11 @@ class Sensors { const std::vector devices() const; private: +#if defined(ESP8266) 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 }; diff --git a/src/settings.cpp b/src/settings.cpp index 26213847d..7a5035eb6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -139,10 +139,10 @@ Settings::Settings() { if (EMSESP_FS.begin(true)) { #endif #endif - logger_.info(F("Mounted filesystem")); + LOG_INFO(F("Mounted filesystem")); mounted_ = true; } else { - logger_.alert(F("Unable to mount filesystem")); + LOG_ERROR(F("Unable to mount filesystem")); unavailable_ = true; } } @@ -154,7 +154,7 @@ Settings::Settings() { } 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>()); loaded_ = true; } @@ -169,7 +169,7 @@ void Settings::commit() { 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 (read_settings(filename, false)) { write_settings(backup_filename); @@ -190,21 +190,21 @@ bool Settings::read_settings(const std::string & filename, bool load) { auto error = ArduinoJson::deserializeMsgPack(doc, file); 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; } else { 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); } return true; } } 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; } #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; #endif } @@ -221,17 +221,17 @@ bool Settings::write_settings(const std::string & filename) { ArduinoJson::serializeMsgPack(doc, file); 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; } else { return true; } } 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; } #else - logger_.debug(F("Write settings file %s"), filename.c_str()); + LOG_DEBUG(F("Write settings file %s"), filename.c_str()); return false; #endif } diff --git a/src/settings.h b/src/settings.h index 64fc36619..a8ccd07cb 100644 --- a/src/settings.h +++ b/src/settings.h @@ -49,15 +49,15 @@ #define EMSESP_DEFAULT_MQTT_ENABLED true #define EMSESP_DEFAULT_MQTT_BASE "home" #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_FORMAT 2 // nested +#define EMSESP_DEFAULT_MQTT_FORMAT 2 // 2=nested #define EMSESP_DEFAULT_MQTT_HEARTBEAT true #define EMSESP_DEFAULT_EMS_READ_ONLY false #define EMSESP_DEFAULT_SHOWER_TIMER false #define EMSESP_DEFAULT_SHOWER_ALERT false #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 #define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 10 diff --git a/src/shower.cpp b/src/shower.cpp index 6e8dc0a82..6a539f820 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -55,7 +55,7 @@ void Shower::loop() { if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) { shower_on_ = 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 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); if (duration_ > SHOWER_MIN_DURATION) { 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(); } } @@ -94,7 +94,7 @@ void Shower::loop() { // turn back on the hot water for the shower void Shower::shower_alert_stop() { if (doing_cold_shot_) { - DEBUG_LOG(F("Shower Alert stopped")); + LOG_DEBUG(F("Shower Alert stopped")); // Boiler::set_tapwarmwater_activated(true); doing_cold_shot_ = false; // showerColdShotStopTimer.detach(); // disable the timer @@ -104,7 +104,7 @@ void Shower::shower_alert_stop() { // turn off hot water to send a shot of cold void Shower::shower_alert_start() { if (shower_alert_) { - DEBUG_LOG(F("Shower Alert started!")); + LOG_DEBUG(F("Shower Alert started!")); // Boiler::set_tapwarmwater_activated(false); doing_cold_shot_ = true; // start the timer for n seconds which will reset the water back to hot diff --git a/src/solar.cpp b/src/solar.cpp index 25289e8c0..254e17539 100644 --- a/src/solar.cpp +++ b/src/solar.cpp @@ -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) : 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 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 - DEBUG_LOG(F("[DEBUG] Performing a solar module publish")); + LOG_DEBUG(F("[DEBUG] Performing a solar module publish")); #endif Mqtt::publish("sm_data", doc); diff --git a/src/system.cpp b/src/system.cpp index 3b8c6411a..f8c0d1ea5 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -60,7 +60,7 @@ void System::mqtt_commands(const char * message) { StaticJsonDocument doc; DeserializationError error = deserializeJson(doc, message); 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; } const char * command = doc["cmd"]; @@ -91,9 +91,9 @@ void System::restart(bool mode) { // check for safe mode if (mode) { - logger_.notice("Restarting system in safe mode..."); + LOG_NOTICE("Restarting system in safe mode..."); } else { - logger_.notice("Restarting system..."); + LOG_NOTICE("Restarting system..."); } Shell::loop_all(); @@ -140,9 +140,11 @@ void System::start() { settings.app_version(EMSESP_APP_VERSION); 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 Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, _1)); @@ -218,19 +220,11 @@ void System::loop() { syslog_.loop(); #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 - - /* -#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) { @@ -239,7 +233,7 @@ void System::show_mem(const char * text) { #else uint32_t mem = 1000; #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 @@ -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 (!system_healthy_) { system_healthy_ = true; - digitalWrite(LED_GPIO, LED_ON); // LED on, for ever + if (LED_GPIO) { + digitalWrite(LED_GPIO, LED_ON); // LED on, for ever + } } } } diff --git a/src/system.h b/src/system.h index b940ede13..c56d4791c 100644 --- a/src/system.h +++ b/src/system.h @@ -60,7 +60,7 @@ class System { static void restart(bool safe_mode); static void restart() { - restart(false); // no safe mode + restart(false); // default, don't boot into safe mode } 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_ON = LOW; #elif defined(ESP32) - static constexpr uint8_t LED_GPIO = 5; // on Wemos D32 - static constexpr uint8_t LED_ON = LOW; + 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; // LOW on Lolin D32, HIGH on Wemos D1-32 mini #else static constexpr uint8_t LED_GPIO = 0; static constexpr uint8_t LED_ON = 0; @@ -106,6 +106,7 @@ class System { static int reset_counter_; static EMSuart emsuart_; + #if defined(ESP8266) static RTCVars state_; #endif diff --git a/src/telegram.cpp b/src/telegram.cpp index ea7443bcb..64bd3946f 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -202,7 +202,7 @@ void RxService::flush_rx_queue() { // start and initialize the Rx incoming buffer. Not currently used. void RxService::start() { - // DEBUG_LOG(F("RxStart")); + // LOG_DEBUG(F("RxStart")); } // 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 uint8_t crc = calculate_crc(data, length - 1); 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(); return; } @@ -253,9 +253,9 @@ void RxService::add(uint8_t * data, uint8_t length) { ems_mask(data[0]); } - // if we're in read only mode, just dump out to console - if (EMSESP::ems_read_only()) { - TRACE_LOG(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str()); + // if we're in "trace" and "raw" print out actual telegram + if (logger_.enabled(Level::TRACE) && EMSESP::trace_raw()) { + LOG_TRACE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str()); } // 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 - 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)); } @@ -325,7 +325,7 @@ void TxService::flush_tx_queue() { // start and initialize Tx void TxService::start() { - // DEBUG_LOG(F("TxStart()")); + // LOG_DEBUG(F("TxStart()")); // grab the bus ID 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 - 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"), tx_telegram.id_, telegram->to_string(telegram_raw, length).c_str()); + // if we're watching an ID, then always show if ((logger_.enabled(Level::TRACE)) && ((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"), @@ -448,7 +449,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) { EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length); 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 @@ -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 - 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 EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length); 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 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(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 (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) { uint8_t message_length = length - 4; 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; } @@ -511,13 +512,13 @@ void TxService::add(uint8_t * data, const uint8_t length) { 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)); } // 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) { - 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 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) // 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 { - // 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())); } @@ -590,7 +591,7 @@ bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const { void TxService::post_send_query() { if (telegram_last_post_send_query_) { 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 } diff --git a/src/thermostat.cpp b/src/thermostat.cpp index ebcad9efa..12d903938 100644 --- a/src/thermostat.cpp +++ b/src/thermostat.cpp @@ -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))) || (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(); } 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 if (mqtt_format_ == Settings::MQTT_format::HA) { 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 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" - doc["unique_id"] = payload; // "unique_id": "thermostat_hc1" + std::string hc_text(10, '\0'); + 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["~"] = payload; // "homeassistant/climate/hc1/thermostat" + doc["~"] = "homeassistant/climate/ems-esp"; + 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"; - doc["mode_stat_t"] = "~/state"; - doc["mode_stat_tpl"] = "{{value_json.mode}}"; - doc["temp_cmd_t"] = "~/cmd_temp"; - doc["temp_stat_t"] = "~/state"; - doc["temp_stat_tpl"] = "{{value_json.seltemp}}"; - doc["curr_temp_t"] = "~/state"; - doc["curr_temp_tpl"] = "{{value_json.currtemp}}"; - doc["min_temp"] = "5"; - doc["max_temp"] = "40"; - doc["temp_step"] = "0.5"; + std::string mode_str(30, '\0'); + snprintf_P(&mode_str[0], 30, PSTR("{{value_json.hc%d.mode}}"), hc + 1); + doc["mode_stat_tpl"] = mode_str; + + std::string seltemp_str(30, '\0'); + snprintf_P(&seltemp_str[0], 30, PSTR("{{value_json.hc%d.seltemp}}"), hc + 1); + doc["temp_stat_tpl"] = seltemp_str; + + std::string currtemp_str(30, '\0'); + snprintf_P(&currtemp_str[0], 30, PSTR("{{value_json.hc%d.currtemp}}"), hc + 1); + doc["curr_temp_tpl"] = currtemp_str; + + doc["min_temp"] = "5"; + doc["max_temp"] = "40"; + doc["temp_step"] = "0.5"; JsonArray modes = doc.createNestedArray("modes"); modes.add("off"); modes.add("heat"); 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 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 doc; DeserializationError error = deserializeJson(doc, message); 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; } @@ -342,7 +348,7 @@ void Thermostat::publish_values() { 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 bool has_data = false; @@ -371,8 +377,8 @@ void Thermostat::publish_values() { } has_data = true; - // if the MQTT format is 'nested' then create the parent object hc - if (mqtt_format_ == Settings::MQTT_format::NESTED) { + // if the MQTT format is 'nested' or 'ha' then create the parent object hc + if (mqtt_format_ != Settings::MQTT_format::SINGLE) { // create nested json for each HC char hc_name[10]; // hc{1-4} strlcpy(hc_name, "hc", 10); @@ -459,9 +465,7 @@ void Thermostat::publish_values() { dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags)); } - - // if format is single, send immediately - // if its HA send it to the special topic + // if format is single, send immediately and quit if (mqtt_format_ == Settings::MQTT_format::SINGLE) { char topic[30]; 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 Mqtt::publish(topic, doc); 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 ((mqtt_format_ == Settings::MQTT_format::NESTED) && has_data) { + if (mqtt_format_ == Settings::MQTT_format::NESTED) { 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 telegram) { // 0xA5 - Set the display settings 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); } // 0xA5 - Set the building settings 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); } // 0xA5 Set the language settings 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); } @@ -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) { set_mode(HeatingCircuit::Mode::COMFORT, hc_num); } 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 void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) { if (can_write()) { - logger_.warning(F("Write not supported for this model Thermostat")); + LOG_WARNING(F("Write not supported for this model Thermostat")); return; } // get hc based on number std::shared_ptr hc = heating_circuit(hc_num); 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; } @@ -1127,7 +1132,7 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) { 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 // 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 void Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) { if (can_write()) { - logger_.warning(F("Write not supported for this model Thermostat")); + LOG_WARNING(F("Write not supported for this model Thermostat")); return; } // get hc based on number std::shared_ptr hc = heating_circuit(hc_num); 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; } @@ -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 (offset != -1) { char s[10]; - logger_.info(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"), - Helpers::render_value(s, temperature, 2), - hc->hc_num(), - mode_tostring(mode).c_str()); + LOG_INFO(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"), + Helpers::render_value(s, temperature, 2), + hc->hc_num(), + mode_tostring(mode).c_str()); // add the write command to the Tx queue // value is *2 diff --git a/src/uart/emsuart_esp32.cpp b/src/uart/emsuart_esp32.cpp index 260bdb7ae..45d5b66b2 100644 --- a/src/uart/emsuart_esp32.cpp +++ b/src/uart/emsuart_esp32.cpp @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/* 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) @@ -26,19 +28,20 @@ namespace emsesp { -static RingbufHandle_t buf_handle = NULL; -static QueueHandle_t uart_queue = NULL; -static volatile uint8_t sending = 0; // If a telegram is send we ++, in receiving we ignore if != 0 and -- -uint8_t tx_mode_ = EMS_TXMODE_DEFAULT; +static intr_handle_t uart_handle; +static RingbufHandle_t buf_handle = NULL; +static uint8_t rxbuf[UART_FIFO_LEN]; +static uint8_t rxlen; -// Main interrupt handler -void EMSuart::emsuart_parseTask(void * param) { - for (;;) { +/* +* Task to handle the incoming data +*/ +void EMSuart::emsuart_recvTask(void * param) { + while (1) { size_t item_size; - uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(1000)); - uint8_t telegramSize = item_size; // can't be more than EMS_MAXBUFFERSIZE size (checked in sending task) + uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, portMAX_DELAY); + uint8_t telegramSize = item_size; - // Did we had a timeout? if (telegram) { EMSESP::incoming_telegram(telegram, telegramSize); vRingbufferReturnItem(buf_handle, (void *)telegram); @@ -47,113 +50,90 @@ void EMSuart::emsuart_parseTask(void * param) { } /* - * system task triggered on BRK interrupt - * incoming received messages are always asynchronous - * The full buffer is sent to the ems_parseTelegram() function in ems.cpp. + * UART interrupt, on break read the fifo and put the whole telegram to ringbuffer */ -void EMSuart::emsuart_recvTask(void * param) { - uart_event_t event; - for (;;) { - if (xQueueReceive(uart_queue, (void *)&event, (portTickType)portMAX_DELAY)) { - // We are only interested in UART_BREAK event and ignore all others - 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; - } - } +static void IRAM_ATTR uart_intr_handle(void * arg) { + if (EMS_UART.int_st.brk_det) { + uint8_t rx_fifo_len = EMS_UART.status.rxfifo_cnt; + for (rxlen = 0; rxlen < rx_fifo_len; rxlen++) { + rxbuf[rxlen] = EMS_UART.fifo.rw_byte; // read all bytes into buffer } + 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) { - - tx_mode_ = tx_mode; - - // Configure UART parameters - // clang-format off - uart_config_t uart_config = { - .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 - + uart_config_t uart_config = { + .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, + }; + 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)); - - // Setup UART buffered IO with event queue - // const int uart_buffer_size = (1024 * 2); - // QueueHandle_t uart_queue; - // Install UART driver using an event queue here - // ESP_ERROR_CHECK(uart_driver_install(UART2, uart_buffer_size, - // uart_buffer_size, 10, &uart_queue, 0)); - // do not use user defined interrupt handlers --> we are waiting for break event - // 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); + //EMS_UART.conf1.rxfifo_full_thrhd = 127; // enough to hold the incoming telegram, should never reached + //EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit + EMS_UART.int_ena.val = 0; // disable all intr. + EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags + EMS_UART.int_ena.brk_det = 1; // activate only break + buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT); + ESP_ERROR_CHECK(uart_isr_register(EMSUART_UART, uart_intr_handle, NULL, ESP_INTR_FLAG_IRAM, &uart_handle)); + xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, 12, 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 - * 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) { - char buf[1]; - buf[0] = data; - uart_write_bytes_with_break(EMSUART_UART, (const char *)buf, 1, EMSUART_BREAKBITS); + EMS_UART.conf0.txd_brk = 0; // just to make sure the bit is cleared + EMS_UART.fifo.rw_byte = data; + 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 * 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) { - if (len == 0) { - return EMS_TX_STATUS_OK; // nothing to send + if (len > 0) { + 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; } diff --git a/src/uart/emsuart_esp32.h b/src/uart/emsuart_esp32.h index 974e0283a..d021f3e66 100644 --- a/src/uart/emsuart_esp32.h +++ b/src/uart/emsuart_esp32.h @@ -27,19 +27,15 @@ #include "freertos/queue.h" #include -#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_RXPIN 17 // To do: Adapt seems to be IO17 for ESP32 UART2 RX pin -#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_UART UART_NUM_2 // on the ESP32 we're using UART2 +#define EMS_UART UART2 // for intr setting #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 -#define EMS_TXMODE_EMSPLUS 2 -#define EMS_TXMODE_HT3 3 +// customize the GPIO pins for RX and TX here +#define EMSUART_RXPIN 17 // 17 is UART2 RX. Use 23 for D7 on a Wemos D1-32 mini for backwards compatabilty +#define EMSUART_TXPIN 16 // 16 is UART2 TX. Use 5 for D8 on a Wemos D1-32 mini for backwards compatabilty namespace emsesp { @@ -56,14 +52,12 @@ class EMSuart { static void start(uint8_t tx_mode); static void send_poll(uint8_t data); + static void stop(); + static void restart(); 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: static void emsuart_recvTask(void * param); - static void emsuart_parseTask(void * param); }; } // namespace emsesp diff --git a/src/uart/emsuart_esp8266.cpp b/src/uart/emsuart_esp8266.cpp index 597b82fad..6a738b59e 100644 --- a/src/uart/emsuart_esp8266.cpp +++ b/src/uart/emsuart_esp8266.cpp @@ -24,52 +24,32 @@ 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 * paEMSRxBuf[EMS_MAXBUFFERS]; -uint8_t emsRxBufIdx = 0; -uint8_t phantomBreak = 0; -uint8_t tx_mode_ = EMS_TXMODE_DEFAULT; +uint8_t emsRxBufIdx = 0; -// // Main interrupt handler // Important: must not use ICACHE_FLASH_ATTR -// void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) { - static uint8_t length = 0; - static bool rx_idle_ = true; - 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); - } + static uint8_t length = 0; + static uint8_t uart_buffer[128]; // BREAK detection = End of EMS data block 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 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 - - 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 + USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break } } @@ -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 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 if (length == 2) { EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, 1); return; } - // ignore double BRK at the end, possibly from the Tx loopback // also telegrams with no data value - // then transmit EMS buffer, excluding the BRK - if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) { + // then transmit EMS buffer, excluding the BRK, length is checked by irq + if (length > 4) { 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 */ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { - tx_mode_ = tx_mode; - // allocate and preset EMS Receive buffers for (int i = 0; i < EMS_MAXBUFFERS; i++) { 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 - ETS_UART_INTR_DISABLE(); - ETS_UART_INTR_ATTACH(nullptr, nullptr); - // pin settings PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); 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 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 - // UCTOE = RX TimeOut enable (default is 1) - // UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2) - // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) - // see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf - // - // 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(); + // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be more than 32) + USC1(EMSUART_UART) = (0x7F << UCFFT); // rx buffer full + USIE(EMSUART_UART) = 0; // disable all interrupts + USIC(EMSUART_UART) = 0xFFFF; // clear all interupts + 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_ENABLE(); - - // logger_.info(F("UART service for Rx/Tx started")); + USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break interrupt } /* @@ -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 */ void ICACHE_FLASH_ATTR EMSuart::stop() { - ETS_UART_INTR_DISABLE(); + USIE(EMSUART_UART) = 0; // disable interrup } /* * re-start UART0 driver */ 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 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 - * 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) { + USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear bit USF(EMSUART_UART) = data; - delayMicroseconds(EMSUART_TX_BRK_WAIT); - tx_brk(); // send + USC0(EMSUART_UART) |= (1 << UCBRK); // send at the end } /* * Send data to Tx line, ending with a * 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) { - if (len == 0) { - return EMS_TX_STATUS_OK; // nothing to send - } - - // EMS+ https://github.com/proddy/EMS-ESP/issues/23# - if (tx_mode_ == EMS_TXMODE_EMSPLUS) { // With extra tx delay for EMS+ + if (len) { + USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear bit for (uint8_t i = 0; i < len; i++) { USF(EMSUART_UART) = buf[i]; - delayMicroseconds(EMSUART_TX_BRK_WAIT); // 2070 } - tx_brk(); // send - return EMS_TX_STATUS_OK; + USC0(EMSUART_UART) |= (1 << UCBRK); // send at the end } - - // 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 - 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) - * - 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 - - // wait until BRK detected... - while (!(USIR(EMSUART_UART) & (1 << UIBD))) { - delayMicroseconds(EMSUART_BIT_TIME); - } - - USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear - 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 + return EMS_TX_STATUS_OK; } + } // namespace emsesp #endif diff --git a/src/uart/emsuart_esp8266.h b/src/uart/emsuart_esp8266.h index 082300e4a..b7bf94d99 100644 --- a/src/uart/emsuart_esp8266.h +++ b/src/uart/emsuart_esp8266.h @@ -28,19 +28,7 @@ #define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit #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 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 EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs #define EMSUART_recvTaskPrio 1 // 0, 1 or 2. 0 being the lowest #define EMSUART_recvTaskQueueLen 10 // number of queued'd Rx triggers @@ -74,11 +62,8 @@ class EMSuart { } EMSRxBuf_t; 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_flush_fifos(); - static void ICACHE_FLASH_ATTR tx_brk(); }; } // namespace emsesp diff --git a/src/version.h b/src/version.h index db2055ff8..ab2be6267 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0a6" +#define EMSESP_APP_VERSION "2.0.0a7"