From cb228d8c8b58e3c583d7f84d3c5b2218e51fe3bf Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sun, 1 Nov 2020 14:22:57 +0100 Subject: [PATCH 01/10] shell line edit: cursor, pos1, end, del, F1.. --- CHANGELOG_LATEST.md | 1 + lib/uuid-console/src/shell.cpp | 177 +++++++++++++++++----------- lib/uuid-console/src/uuid/console.h | 4 +- 3 files changed, 111 insertions(+), 71 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 14fab2652..77d09e2a2 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -1,6 +1,7 @@ # Changelog ### Added +- function keys in editor: cursor, del, pos1, end. F1=help, F2=show, F10=report ### Fixed diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index ddc03d82a..e68af84c8 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -65,8 +65,8 @@ void Shell::start() { #endif line_buffer_.reserve(maximum_command_line_length_); - oldline_.reserve(maximum_command_line_length_); - oldline_.clear(); + line_old_.reserve(maximum_command_line_length_); + line_old_.clear(); display_banner(); display_prompt(); shells_.insert(shared_from_this()); @@ -148,8 +148,7 @@ void Shell::loop_normal() { // Interrupt (^C) line_buffer_.clear(); println(); - prompt_displayed_ = false; - display_prompt(); + cursor_ = 0; break; case '\x04': @@ -160,51 +159,38 @@ void Shell::loop_normal() { break; case '\x08': - case '\x7F': // Backspace (^H) - // Delete (^?) - if (!line_buffer_.empty()) { - erase_characters(1); - line_buffer_.pop_back(); + case '\x7F': + // Del/Backspace (^?) + if (line_buffer_.length() > cursor_) { + line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1); } break; case '\x09': // Tab (^I) process_completion(); + cursor_ = 0; break; case '\x0A': // Line feed (^J) if (previous_ != '\x0D') { - if (!line_buffer_.empty()) { - oldline_ = line_buffer_; - } process_command(); } break; - case '\x0C': - // New page (^L) - erase_current_line(); - prompt_displayed_ = false; - display_prompt(); - break; - case '\x0D': - if (!line_buffer_.empty()) { - oldline_ = line_buffer_; - } // Carriage return (^M) process_command(); break; + case '\x0C': + // New page (^L) case '\x15': // Delete line (^U) - erase_current_line(); - prompt_displayed_ = false; line_buffer_.clear(); - display_prompt(); + cursor_ = 0; break; case '\x17': @@ -212,51 +198,91 @@ void Shell::loop_normal() { delete_buffer_word(true); break; + case '\033': + // esc + esc_ = 0x80; + break; + default: - if (c >= '\x20' && c <= '\x7E') { + if (esc_) { + if (c == '[') { + // start of sequence + } else if (c >= '0' && (c <= '9')) { + // numbers + esc_ = (esc_ & 0x7F) * 10 + c - '0'; + } else if (c == 'A') { + // cursor up + line_buffer_ = line_old_; + cursor_ = 0; + esc_ = 0; + } else if (c == 'B') { + // cursor down + line_buffer_.clear(); + cursor_ = 0; + esc_ = 0; + } else if (c == 'C') { + // cursor right + if (cursor_) { + cursor_--; + } + esc_ = 0; + } else if (c == 'D') { + // cursor left + if (cursor_ < line_buffer_.length()) { + cursor_++; + } + esc_ = 0; + } else if (c == '~') { + // function keys with number + if ((esc_ == 3) && cursor_) { + // del + cursor_--; + line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1); + } else if (esc_ == 4) { + // end + cursor_ = 0; + } else if (esc_ == 1) { + // pos1 + cursor_ = line_buffer_.length(); + } else if (esc_ == 11) { + // F1 and F10 + line_buffer_ = "help"; + process_command(); + } else if (esc_ == 12) { + // F2 + line_buffer_ = "show"; + process_command(); + } else if (esc_ == 20) { + // F9 + line_buffer_ = "send telegram \"0B \""; + cursor_ = 1; + } else if (esc_ == 21) { + // F10 + line_buffer_ = "call system report"; + process_command(); + } + esc_ = 0; + } else { + // all other chars end sequence + esc_ = 0; + } + } else if (c >= '\x20' && c <= '\x7E') { // ASCII text if (line_buffer_.length() < maximum_command_line_length_) { - line_buffer_.push_back(c); - write((uint8_t)c); - } - // cursor up, get last command - if ((c == 'A') && (previous_ == '[')) { - erase_current_line(); - prompt_displayed_ = false; - line_buffer_ = oldline_; - display_prompt(); - } - // cursor back, delete cursor chars - if ((c == 'D') && (previous_ == '[')) { - line_buffer_.pop_back(); - line_buffer_.pop_back(); - // alternative work as backspace - // if (line_buffer_.length() > 0) { - // line_buffer_.pop_back(); - // } - erase_current_line(); - prompt_displayed_ = false; - display_prompt(); - } - // cursor forward, only delete cursor chars - if ((c == 'C') && (previous_ == '[')) { - line_buffer_.pop_back(); - line_buffer_.pop_back(); - erase_current_line(); - prompt_displayed_ = false; - display_prompt(); - } - // cursor down(B): Delete line - if ((c == 'B') && (previous_ == '[')) { - erase_current_line(); - prompt_displayed_ = false; - line_buffer_.clear(); - display_prompt(); + line_buffer_.insert(line_buffer_.length() - cursor_, 1, c); } } break; } + // common for all, display the complete line + erase_current_line(); + prompt_displayed_ = false; + display_prompt(); + if (cursor_) { + printf(F("\033[%dD"), cursor_); + } + previous_ = c; // This is a hack to let TelnetStream know that command @@ -428,16 +454,24 @@ void Shell::delete_buffer_word(bool display) { if (pos == std::string::npos) { line_buffer_.clear(); - if (display) { - erase_current_line(); - prompt_displayed_ = false; - display_prompt(); - } + cursor_ = 0; } else { if (display) { - erase_characters(line_buffer_.length() - pos); + size_t pos1 = 0; + pos = 0; + while (pos1 < line_buffer_.length() - cursor_) { + pos = pos1; + pos1 = line_buffer_.find(' ', pos + 1); + } + line_buffer_.erase(pos, pos1 - pos); + if (line_buffer_.find(' ') == 0) { + line_buffer_.erase(0, 1); + } + cursor_ = line_buffer_.length() - pos; + } else { + line_buffer_.resize(pos); + cursor_ = 0; } - line_buffer_.resize(pos); } } @@ -453,7 +487,6 @@ void Shell::maximum_command_line_length(size_t length) { void Shell::process_command() { CommandLine command_line{line_buffer_}; - line_buffer_.clear(); println(); prompt_displayed_ = false; @@ -467,8 +500,12 @@ void Shell::process_command() { } else { println(F("No commands configured")); } + line_old_ = line_buffer_; } + cursor_ = 0; + line_buffer_.clear(); + if (running()) { display_prompt(); } diff --git a/lib/uuid-console/src/uuid/console.h b/lib/uuid-console/src/uuid/console.h index 5b65acb5f..6918020cb 100644 --- a/lib/uuid-console/src/uuid/console.h +++ b/lib/uuid-console/src/uuid/console.h @@ -903,9 +903,11 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand std::list log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */ size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */ std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */ + std::string line_old_; /*!< old Command line buffer.*/ size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */ unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */ - std::string oldline_; /*!< old Command line buffer.*/ + uint8_t cursor_ = 0; /*!< cursor position from end of line */ + uint8_t esc_ = 0; /*!< esc sequence running */ Mode mode_ = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */ std::unique_ptr mode_data_ = nullptr; /*!< Data associated with the current execution mode. @since 0.1.0 */ bool stopped_ = false; /*!< Indicates that the shell has been stopped. @since 0.1.0 */ From de0e291b786e29ebdd18db270dd59c137ced1012 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sun, 1 Nov 2020 15:35:49 +0100 Subject: [PATCH 02/10] F-commands to flash and display them --- lib/uuid-console/src/shell.cpp | 19 ++++++++++++------- lib/uuid-console/src/uuid/console.h | 1 + src/emsesp.cpp | 2 +- src/system.cpp | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index e68af84c8..d57c004f0 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -133,6 +133,14 @@ void Shell::loop_one() { } } +void Shell::set_command_str(const __FlashStringHelper * str) { + line_buffer_ = read_flash_string(str); + erase_current_line(); + prompt_displayed_ = false; + display_prompt(); + process_command(); +} + void Shell::loop_normal() { const int input = read_one_char(); @@ -246,20 +254,17 @@ void Shell::loop_normal() { cursor_ = line_buffer_.length(); } else if (esc_ == 11) { // F1 and F10 - line_buffer_ = "help"; - process_command(); + set_command_str(F("help")); } else if (esc_ == 12) { // F2 - line_buffer_ = "show"; - process_command(); + set_command_str(F("show")); } else if (esc_ == 20) { // F9 - line_buffer_ = "send telegram \"0B \""; + line_buffer_ = read_flash_string(F("send telegram \"0B \"")); cursor_ = 1; } else if (esc_ == 21) { // F10 - line_buffer_ = "call system report"; - process_command(); + set_command_str(F("call system report")); } esc_ = 0; } else { diff --git a/lib/uuid-console/src/uuid/console.h b/lib/uuid-console/src/uuid/console.h index 6918020cb..c1b1027d5 100644 --- a/lib/uuid-console/src/uuid/console.h +++ b/lib/uuid-console/src/uuid/console.h @@ -892,6 +892,7 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @since 0.1.0 */ size_t vprintf(const __FlashStringHelper * format, va_list ap); + void set_command_str(const __FlashStringHelper * str); static const uuid::log::Logger logger_; /*!< uuid::log::Logger instance for shells. @since 0.1.0 */ static std::set> shells_; /*!< Registered running shells to be executed. @since 0.1.0 */ diff --git a/src/emsesp.cpp b/src/emsesp.cpp index f439ce1a1..91b30df71 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -917,7 +917,7 @@ void EMSESP::start() { emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem - LOG_INFO("EMS Device library loaded with %d records", device_library_.size()); + LOG_INFO(F("EMS Device library loaded with %d records"), device_library_.size()); #if defined(EMSESP_STANDALONE) mqtt_.on_connect(); // simulate an MQTT connection diff --git a/src/system.cpp b/src/system.cpp index dff320365..2a858b7c2 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -61,7 +61,7 @@ bool System::command_send(const char * value, const int8_t id) { // restart EMS-ESP void System::restart() { - LOG_NOTICE("Restarting system..."); + LOG_NOTICE(F("Restarting system...")); Shell::loop_all(); delay(1000); // wait a second #if defined(ESP8266) @@ -73,7 +73,7 @@ void System::restart() { // saves all settings void System::wifi_reconnect() { - LOG_NOTICE("The wifi will reconnect..."); + LOG_NOTICE(F("The wifi will reconnect...")); Shell::loop_all(); delay(1000); // wait a second EMSESP::webSettingsService.save(); // local settings From a15efa4f53b4c0a4c4583dd2505ad13cb7438ec3 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Mon, 2 Nov 2020 11:34:56 +0100 Subject: [PATCH 03/10] move some strings to flash --- src/devices/boiler.cpp | 2 +- src/devices/thermostat.cpp | 26 +++++++++++++------------- src/devices/thermostat.h | 2 +- src/helpers.cpp | 16 +++++++++------- src/helpers.h | 4 ++-- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index af8037124..3cb34e0c1 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -1202,7 +1202,7 @@ bool Boiler::set_pump_delay(const char * value, const int8_t id) { // on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent bool Boiler::set_warmwater_mode(const char * value, const int8_t id) { uint8_t set; - if (!Helpers::value2enum(value, set, {"hot", "eco", "intelligent"})) { + if (!Helpers::value2enum(value, set, {F("hot"), F("eco"), F("intelligent")})) { LOG_WARNING(F("Set boiler warm water mode: Invalid value")); return false; } diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 804a82090..184da790c 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -419,7 +419,7 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) { // Floordry if (Helpers::hasValue(floordrystatus_) && Helpers::hasValue(floordrytemp_) && (floordrytemp_ > 0)) { char s[10]; - rootThermostat["floordry"] = Helpers::render_enum(s, {"off", "start", "heat", "hold", "cool", "end"}, floordrystatus_); + rootThermostat["floordry"] = Helpers::render_enum(s, {F("off"), F("start"), F("heat"), F("hold"), F("cool"), F("end")}, floordrystatus_); rootThermostat["floordrytemp"] = floordrytemp_; } @@ -447,9 +447,9 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) { if (Helpers::hasValue(ibaBuildingType_)) { char s[10]; if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { - rootThermostat["building"] = Helpers::render_enum(s, {"light", "medium", "heavy"}, ibaBuildingType_ - 1); + rootThermostat["building"] = Helpers::render_enum(s, {F("light"), F("medium"), F("heavy")}, ibaBuildingType_ - 1); } else { - rootThermostat["building"] = Helpers::render_enum(s, {"light", "medium", "heavy"}, ibaBuildingType_); + rootThermostat["building"] = Helpers::render_enum(s, {F("light"), F("medium"), F("heavy")}, ibaBuildingType_); } } @@ -457,9 +457,9 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) { if (Helpers::hasValue(wwMode_)) { char s[10]; if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { - rootThermostat["wwmode"] = Helpers::render_enum(s, {"off", "low", "high", "auto", "own_prog"}, wwMode_); + rootThermostat["wwmode"] = Helpers::render_enum(s, {F("off"), F("low"), F("high"), F("auto"), F("own_prog")}, wwMode_); } else { - rootThermostat["wwmode"] = Helpers::render_enum(s, {"off", "on", "auto"}, wwMode_); + rootThermostat["wwmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwMode_); } } @@ -476,7 +476,7 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) { // Warm Water circulation mode if (Helpers::hasValue(wwCircMode_)) { char s[7]; - rootThermostat["wwcircmode"] = Helpers::render_enum(s, {"off", "on", "auto"}, wwCircMode_); + rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwCircMode_); } return (rootThermostat.size()); @@ -608,7 +608,7 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost // Summer mode if (Helpers::hasValue(hc->summer_setmode)) { char s[7]; - dataThermostat["summermode"] = Helpers::render_enum(s, {"summer", "auto", "winter"}, hc->summer_setmode); + dataThermostat["summermode"] = Helpers::render_enum(s, {F("summer"), F("auto"), F("winter")}, hc->summer_setmode); } // mode - always force showing this when in HA so not to break HA's climate component @@ -1514,7 +1514,7 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) { // 0xA5 - Set the building settings bool Thermostat::set_building(const char * value, const int8_t id) { uint8_t bd = 0; - if (!Helpers::value2enum(value, bd, {"light", "medium", "heavy"})) { + if (!Helpers::value2enum(value, bd, {F("light"), F("medium"), F("heavy")})) { LOG_WARNING(F("Set building: Invalid value")); return false; } @@ -1533,7 +1533,7 @@ bool Thermostat::set_building(const char * value, const int8_t id) { // 0xA5 Set the language settings bool Thermostat::set_language(const char * value, const int8_t id) { uint8_t lg = 0; - if (!Helpers::value2enum(value, lg, {"german", "dutch", "french", "italian"})) { + if (!Helpers::value2enum(value, lg, {F("german"), F("dutch"), F("french"), F("italian")})) { LOG_WARNING(F("Set language: Invalid value")); return false; } @@ -1586,14 +1586,14 @@ bool Thermostat::set_roominfluence(const char * value, const int8_t id) { bool Thermostat::set_wwmode(const char * value, const int8_t id) { uint8_t set = 0xFF; if ((this->model() == EMS_DEVICE_FLAG_RC300) || (this->model() == EMS_DEVICE_FLAG_RC100)) { - if (!Helpers::value2enum(value, set, {"off", "low", "high", "auto", "own"})) { + if (!Helpers::value2enum(value, set, {F("off"), F("low"), F("high"), F("auto"), F("own")})) { LOG_WARNING(F("Set warm water mode: Invalid mode")); return false; } LOG_INFO(F("Setting warm water mode to %s"), value); write_command(0x02F5, 2, set, 0x02F5); } else { - if (!Helpers::value2enum(value, set, {"off", "on", "auto"})) { + if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) { LOG_WARNING(F("Set warm water mode: Invalid mode")); return false; } @@ -1631,7 +1631,7 @@ bool Thermostat::set_wwtemplow(const char * value, const int8_t id) { // sets the thermostat ww circulation working mode, where mode is a string bool Thermostat::set_wwcircmode(const char * value, const int8_t id) { uint8_t set = 0xFF; - if (!Helpers::value2enum(value, set, {"off", "on", "auto"})) { + if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) { LOG_WARNING(F("Set warm water circulation mode: Invalid mode")); return false; } @@ -1909,7 +1909,7 @@ bool Thermostat::set_summermode(const char * value, const int8_t id) { return false; } uint8_t set = 0xFF; - if (!Helpers::value2enum(value, set, {"summer", "auto", "winter"})) { + if (!Helpers::value2enum(value, set, {F("summer"), F("auto"), F("winter")})) { LOG_WARNING(F("Setting summer mode: Invalid mode")); return false; } diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index 486e49daa..8e949ca69 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -133,7 +133,7 @@ class Thermostat : public EMSdevice { std::vector summer_typeids; std::string datetime_; // date and time stamp - std::string errorCode_; // code as string i.e. "A22(816)" + std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)" bool changed_ = false; bool ha_registered_ = false; diff --git a/src/helpers.cpp b/src/helpers.cpp index 2dfe27324..328b69d71 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -136,19 +136,20 @@ char * Helpers::render_boolean(char * result, bool value) { } // depending on format render a number or a string -char * Helpers::render_enum(char * result, const std::vector & value, const uint8_t no) { +char * Helpers::render_enum(char * result, const std::vector value, const uint8_t no) { if (no >= value.size()) { return nullptr; // out of bounds } + std::string str = uuid::read_flash_string(value[no]); if (bool_format() == BOOL_FORMAT_ONOFF) { - strcpy(result, value[no].c_str()); + strcpy(result, str.c_str()); } else if (bool_format() == BOOL_FORMAT_TRUEFALSE) { - if (no == 0 && value[0] == "off") { + if (no == 0 && uuid::read_flash_string(value[0]) == "off") { strlcpy(result, "false", 7); - } else if (no == 1 && value[1] == "on") { + } else if (no == 1 && uuid::read_flash_string(value[1]) == "on") { strlcpy(result, "true", 6); } else { - strcpy(result, value[no].c_str()); + strcpy(result, str.c_str()); } } else { itoa(result, no); @@ -462,13 +463,14 @@ bool Helpers::value2bool(const char * v, bool & value) { } // checks to see if a string is member of a vector and return the index, also allow true/false for on/off -bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector & strs) { +bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector strs) { if ((v == nullptr) || (strlen(v) == 0)) { return false; } std::string str = toLower(v); for (value = 0; value < strs.size(); value++) { - if ((strs[value] == "off" && str == "false") || (strs[value] == "on" && str == "true") || (str == strs[value]) || (v[0] == '0' + value)) { + std::string str1 = uuid::read_flash_string(strs[value]); + if ((str1 == "off" && str == "false") || (str1 == "on" && str == "true") || (str == str1) || (v[0] == '0' + value)) { return true; } } diff --git a/src/helpers.h b/src/helpers.h index 80e4ec171..ce4cdd584 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -39,7 +39,7 @@ class Helpers { static char * render_value(char * result, const int16_t value, const uint8_t format); static char * render_value(char * result, const char * value, uint8_t format); static char * render_boolean(char * result, bool value); - static char * render_enum(char * result, const std::vector & value, const uint8_t no); + static char * render_enum(char * result, const std::vector value, const uint8_t no); static char * hextoa(char * result, const uint8_t value); static std::string data_to_hex(const uint8_t * data, const uint8_t length); @@ -62,7 +62,7 @@ class Helpers { static bool value2float(const char * v, float & value); static bool value2bool(const char * v, bool & value); static bool value2string(const char * v, std::string & value); - static bool value2enum(const char * v, uint8_t & value, const std::vector & strs); + static bool value2enum(const char * v, uint8_t & value, const std::vector strs); static void bool_format(uint8_t bool_format) { bool_format_ = bool_format; From df4f12cb961cb5c581200ccdd4abdd8107af5129 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Mon, 2 Nov 2020 18:52:24 +0100 Subject: [PATCH 04/10] add Linux telnet keys --- lib/uuid-console/src/shell.cpp | 22 +++++++++++++++++++--- src/helpers.cpp | 10 +++------- src/helpers.h | 1 + 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index d57c004f0..7156bddfd 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -213,7 +213,7 @@ void Shell::loop_normal() { default: if (esc_) { - if (c == '[') { + if ((c == '[') || (c == 'O')) { // start of sequence } else if (c >= '0' && (c <= '9')) { // numbers @@ -240,6 +240,22 @@ void Shell::loop_normal() { cursor_++; } esc_ = 0; + } else if (c == 'H') { + // Home + cursor_ = line_buffer_.length(); + esc_ = 0; + } else if (c == 'F') { + // End + cursor_ = 0; + esc_ = 0; + } else if (c == 'P') { + // F1 + set_command_str(F("help")); + esc_ = 0; + } else if (c == 'Q') { + // F2 + set_command_str(F("show")); + esc_ = 0; } else if (c == '~') { // function keys with number if ((esc_ == 3) && cursor_) { @@ -262,8 +278,8 @@ void Shell::loop_normal() { // F9 line_buffer_ = read_flash_string(F("send telegram \"0B \"")); cursor_ = 1; - } else if (esc_ == 21) { - // F10 + } else if (esc_ == 15) { + // F5 set_command_str(F("call system report")); } esc_ = 0; diff --git a/src/helpers.cpp b/src/helpers.cpp index 328b69d71..c63831402 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -140,18 +140,14 @@ char * Helpers::render_enum(char * result, const std::vector= value.size()) { return nullptr; // out of bounds } - std::string str = uuid::read_flash_string(value[no]); - if (bool_format() == BOOL_FORMAT_ONOFF) { - strcpy(result, str.c_str()); - } else if (bool_format() == BOOL_FORMAT_TRUEFALSE) { + strcpy(result, uuid::read_flash_string(value[no]).c_str()); + if (bool_format() == BOOL_FORMAT_TRUEFALSE) { if (no == 0 && uuid::read_flash_string(value[0]) == "off") { strlcpy(result, "false", 7); } else if (no == 1 && uuid::read_flash_string(value[1]) == "on") { strlcpy(result, "true", 6); - } else { - strcpy(result, str.c_str()); } - } else { + } else if (bool_format() == BOOL_FORMAT_NUMBERS) { itoa(result, no); } return result; diff --git a/src/helpers.h b/src/helpers.h index ce4cdd584..8573d5797 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -26,6 +26,7 @@ #define BOOL_FORMAT_ONOFF 1 #define BOOL_FORMAT_TRUEFALSE 2 +#define BOOL_FORMAT_NUMBERS 3 namespace emsesp { From 824c40f8310e6da9076e6477b4e7b0fc763768ed Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 2 Nov 2020 21:32:47 +0100 Subject: [PATCH 05/10] remove mqtt qos text --- interface/src/mqtt/MqttSettingsForm.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/mqtt/MqttSettingsForm.tsx b/interface/src/mqtt/MqttSettingsForm.tsx index 21ab9192c..4dcf9728b 100644 --- a/interface/src/mqtt/MqttSettingsForm.tsx +++ b/interface/src/mqtt/MqttSettingsForm.tsx @@ -124,9 +124,9 @@ class MqttSettingsForm extends React.Component { variant="outlined" onChange={handleValueChange('mqtt_qos')} margin="normal"> - 0 - At most once - 1 - At least once - 2 - Exactly once + 0 + 1 + 2 Date: Mon, 2 Nov 2020 22:50:50 +0100 Subject: [PATCH 06/10] new icons --- interface/src/project/ProjectMenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/project/ProjectMenu.tsx b/interface/src/project/ProjectMenu.tsx index 03a292792..7adc934bd 100644 --- a/interface/src/project/ProjectMenu.tsx +++ b/interface/src/project/ProjectMenu.tsx @@ -3,8 +3,8 @@ import { Link, withRouter, RouteComponentProps } from "react-router-dom"; import { List, ListItem, ListItemIcon, ListItemText } from "@material-ui/core"; -import SettingsIcon from '@material-ui/icons/Settings'; -import SettingsRemoteIcon from "@material-ui/icons/SettingsRemote"; +import TuneIcon from '@material-ui/icons/Tune'; +import DashboardIcon from "@material-ui/icons/Dashboard"; import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; @@ -18,13 +18,13 @@ class ProjectMenu extends Component { - + - + From 737f450173803c4164bf994ce097e7a2159ea36a Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 2 Nov 2020 23:14:55 +0100 Subject: [PATCH 07/10] minor attempts to reduce mem frag --- src/mqtt.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 2bd54347e..bc137d35c 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -486,7 +486,7 @@ void Mqtt::on_connect() { // homeassistant/sensor/ems-esp/status/config // all the values from the heartbeat payload will be added as attributes to the entity state void Mqtt::ha_status() { - StaticJsonDocument doc; + StaticJsonDocument doc; doc["name"] = F("EMS-ESP status"); doc["uniq_id"] = F("status"); @@ -697,7 +697,9 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons return; } - StaticJsonDocument doc; + return; // TODO + + StaticJsonDocument doc; doc["name"] = name; doc["uniq_id"] = entity; @@ -729,9 +731,9 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity); // convert json to string and publish immediately with retain forced to true - std::string payload_text; + char payload_text[300]; serializeJson(doc, payload_text); // convert json to string - uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text.c_str(), payload_text.size()); + uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text); #if defined(EMSESP_STANDALONE) LOG_DEBUG(F("Publishing topic %s"), topic); #else @@ -802,7 +804,7 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix, } new_name[0] = toupper(new_name[0]); // capitalize first letter - DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM); + StaticJsonDocument doc; doc["name"] = new_name; doc["uniq_id"] = uniq; if (uom != nullptr) { @@ -818,10 +820,11 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix, ids.add(ha_device); // convert json to string and publish immediately with retain forced to true - doc.shrinkToFit(); - std::string payload_text; + // std::string payload_text; + char payload_text[300]; serializeJson(doc, payload_text); // convert json to string - uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text.c_str(), payload_text.size()); + + uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text); if (!packet_id) { LOG_ERROR(F("Failed to publish topic %s"), topic); } else { @@ -832,7 +835,7 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix, #endif } - delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp + // delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp } } // namespace emsesp From 5e697e194f95731eb67177f490aec74fa9c4a3b4 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 3 Nov 2020 09:43:09 +0100 Subject: [PATCH 08/10] fix debug log in standalone --- src/mqtt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index bc137d35c..0d9d828e7 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -829,7 +829,7 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix, LOG_ERROR(F("Failed to publish topic %s"), topic); } else { #if defined(EMSESP_STANDALONE) - LOG_DEBUG(F("Publishing topic=%s, payload=%s"), topic, payload_text.c_str()); + LOG_DEBUG(F("Publishing topic=%s, payload=%s"), topic, payload_text); #else LOG_DEBUG(F("Publishing topic %s"), topic); #endif From 4f877bfb1a891e341013df3bc9914defb5d6a1eb Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 3 Nov 2020 17:43:57 +0100 Subject: [PATCH 09/10] removed old scripts, so mem optimizations --- CHANGELOG_LATEST.md | 3 +- scripts/analyze_stackdmp.py | 26 --- scripts/build.sh | 169 ---------------- scripts/clean_fw.py | 14 -- scripts/decoder.py | 307 ----------------------------- scripts/decoder_linux.py | 307 ----------------------------- scripts/memanalyzer.py | 383 ------------------------------------ src/version.h | 2 +- 8 files changed, 3 insertions(+), 1208 deletions(-) delete mode 100755 scripts/analyze_stackdmp.py delete mode 100755 scripts/build.sh delete mode 100755 scripts/clean_fw.py delete mode 100755 scripts/decoder.py delete mode 100755 scripts/decoder_linux.py delete mode 100755 scripts/memanalyzer.py diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 77d09e2a2..168f8f142 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -6,7 +6,8 @@ ### Fixed ### Changed +- optimized MQTT for HA to reduce mem fragmentation issues ### Removed - +- old scripts diff --git a/scripts/analyze_stackdmp.py b/scripts/analyze_stackdmp.py deleted file mode 100755 index 9fc8c2a23..000000000 --- a/scripts/analyze_stackdmp.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -from subprocess import call -import os - -# example stackdmp.txt would contain text like below copied & pasted from a 'crash dump' command: - -# >>>stack>>> -# 3fffff20: 3fff32f0 00000003 3fff3028 402101b2 -# 3fffff30: 3fffdad0 3fff3280 0000000d 402148aa -# 3fffff40: 3fffdad0 3fff3280 3fff326c 3fff32f0 -# 3fffff50: 0000000d 3fff326c 3fff3028 402103bd -# 3fffff60: 0000000d 3fff34cc 40211de4 3fff34cc -# 3fffff70: 3fff3028 3fff14c4 3fff301c 3fff34cc -# 3fffff80: 3fffdad0 3fff14c4 3fff3028 40210493 -# 3fffff90: 3fffdad0 00000000 3fff14c4 4020a738 -# 3fffffa0: 3fffdad0 00000000 3fff349c 40211e90 -# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01 -# <</dev/null 2>&1 || return 1 - command git rev-parse >/dev/null 2>&1 || return 1 - - return 0 -} - -stat_bytes() { - filesize=`du -k "$1" | cut -f1;` - echo 'size:' $filesize 'bytes' -} - -# Available environments -list_envs() { - grep env: platformio.ini | sed 's/\[env:\(.*\)\]/\1/g' -} - -print_available() { - echo "--------------------------------------------------------------" - echo "Available environments:" - for environment in $available; do - echo "-> $environment" - done -} - -print_environments() { - echo "--------------------------------------------------------------" - echo "Current environments:" - for environment in $environments; do - echo "-> $environment" - done -} - -set_default_environments() { - # Hook to build in parallel when using travis - if [[ "${TRAVIS_BUILD_STAGE_NAME}" = "Release" ]] && ${par_build}; then - environments=$(echo ${available} | \ - awk -v par_thread=${par_thread} -v par_total_threads=${par_total_threads} \ - '{ for (i = 1; i <= NF; i++) if (++j % par_total_threads == par_thread ) print $i; }') - return - fi - - # Only build travis target - if [[ "${TRAVIS_BUILD_STAGE_NAME}" = "Test" ]]; then - environments=$travis - return - fi - - # Fallback to all available environments - environments=$available -} - -build_environments() { - echo "--------------------------------------------------------------" - echo "Building firmware images..." - # don't move to firmware folder until Travis fixed (see https://github.com/travis-ci/dpl/issues/846#issuecomment-547157406) - # mkdir -p $destination - - for environment in $environments; do - echo "* EMS-ESP-$version-$environment.bin" - platformio run --silent --environment $environment || exit 1 - stat_bytes .pio/build/$environment/firmware.bin - # mv .pio/build/$environment/firmware.bin $destination/EMS-ESP-$version-$environment.bin - # mv .pio/build/$environment/firmware.bin EMS-ESP-$version-$environment.bin - mv .pio/build/$environment/firmware.bin EMS-ESP-dev-$environment.bin - done - echo "--------------------------------------------------------------" -} - - -####### MAIN - -destination=firmware -version_file=./src/version.h -version=$(grep -E '^#define EMSESP_APP_VERSION' $version_file | awk '{print $3}' | sed 's/"//g') - -if ${TRAVIS:-false}; then - git_revision=${TRAVIS_COMMIT::7} - git_tag=${TRAVIS_TAG} -elif is_git; then - git_revision=$(git rev-parse --short HEAD) - git_tag=$(git tag --contains HEAD) -else - git_revision=unknown - git_tag= -fi - -echo $git_tag - -if [[ -n $git_tag ]]; then - new_version=${version/-*} - sed -i -e "s@$version@$new_version@" $version_file - version=$new_version - trap "git checkout -- $version_file" EXIT -fi - -par_build=false -par_thread=${BUILDER_THREAD:-0} -par_total_threads=${BUILDER_TOTAL_THREADS:-4} -if [ ${par_thread} -ne ${par_thread} -o \ - ${par_total_threads} -ne ${par_total_threads} ]; then - echo "Parallel threads should be a number." - exit -fi -if [ ${par_thread} -ge ${par_total_threads} ]; then - echo "Current thread is greater than total threads. Doesn't make sense" - exit -fi - -# travis platformio target is used for nightly Test -travis=$(list_envs | grep travis | sort) - -# get all taregts, excluding travis and debug -available=$(list_envs | grep -Ev -- 'travis|debug|release' | sort) - -export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS}" - -# get command line Parameters -# l prints environments -# 2 does parallel builds -# d uses next arg as destination folder -while getopts "lpd:" opt; do - case $opt in - l) - print_available - exit - ;; - p) - par_build=true - ;; - d) - destination=$OPTARG - ;; - esac -done - -shift $((OPTIND-1)) - -# Welcome message -echo "--------------------------------------------------------------" -echo "EMS-ESP FIRMWARE BUILDER" -echo "Building for version ${version}" ${git_revision:+($git_revision)} - -# Environments to build -environments=$@ - -if [ $# -eq 0 ]; then - set_default_environments -fi - -if ${CI:-false}; then - print_environments -fi - -# for debugging -echo "* git_revision = $git_revision" -echo "* git_tag = $git_tag" -echo "* TRAVIS_COMMIT = $TRAVIS_COMMIT" -echo "* TRAVIS_TAG = $TRAVIS_TAG" -echo "* TRAVIS_BRANCH = $TRAVIS_BRANCH" -echo "* TRAVIS_BUILD_STAGE_NAME = $TRAVIS_BUILD_STAGE_NAME" - -build_environments - diff --git a/scripts/clean_fw.py b/scripts/clean_fw.py deleted file mode 100755 index 140f23ee4..000000000 --- a/scripts/clean_fw.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -from subprocess import call -import os -Import("env") - -def clean(source, target, env): - print("\n** Starting clean...") - call(["pio", "run", "-t", "erase"]) - call(["esptool.py", "-p COM6", "write_flash 0x00000", os.getcwd()+"../firmware/*.bin"]) - print("\n** Finished clean.") - -# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota) -env.AddPreAction("buildprog", clean) - diff --git a/scripts/decoder.py b/scripts/decoder.py deleted file mode 100755 index a86ea655c..000000000 --- a/scripts/decoder.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env python3 - -"""ESP Exception Decoder - -github: https://github.com/janLo/EspArduinoExceptionDecoder -license: GPL v3 -author: Jan Losinski -""" - -import argparse -import re -import subprocess -from collections import namedtuple - -import sys - -import os - -EXCEPTIONS = [ - "Illegal instruction", - "SYSCALL instruction", - "InstructionFetchError: Processor internal physical address or data error during instruction fetch", - "LoadStoreError: Processor internal physical address or data error during load or store", - "Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register", - "Alloca: MOVSP instruction, if caller's registers are not in the register file", - "IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero", - "reserved", - "Privileged: Attempt to execute a privileged operation when CRING ? 0", - "LoadStoreAlignmentCause: Load or store to an unaligned address", - "reserved", - "reserved", - "InstrPIFDataError: PIF data error during instruction fetch", - "LoadStorePIFDataError: Synchronous PIF data error during LoadStore access", - "InstrPIFAddrError: PIF address error during instruction fetch", - "LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access", - "InstTLBMiss: Error during Instruction TLB refill", - "InstTLBMultiHit: Multiple instruction TLB entries matched", - "InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING", - "reserved", - "InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch", - "reserved", - "reserved", - "reserved", - "LoadStoreTLBMiss: Error during TLB refill for a load or store", - "LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store", - "LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING", - "reserved", - "LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads", - "StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores" -] - -PLATFORMS = { - "ESP8266": "lx106", - "ESP32": "esp32" -} - -EXCEPTION_REGEX = re.compile("^Exception \\((?P[0-9]*)\\):$") -COUNTER_REGEX = re.compile('^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) ' - 'excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$') -CTX_REGEX = re.compile("^ctx: (?P.+)$") -POINTER_REGEX = re.compile('^sp: (?P[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$') -STACK_BEGIN = '>>>stack>>>' -STACK_END = '<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$') - -StackLine = namedtuple("StackLine", ["offset", "content"]) - - -class ExceptionDataParser(object): - def __init__(self): - self.exception = None - - self.epc1 = None - self.epc2 = None - self.epc3 = None - self.excvaddr = None - self.depc = None - - self.ctx = None - - self.sp = None - self.end = None - self.offset = None - - self.stack = [] - - def _parse_exception(self, line): - match = EXCEPTION_REGEX.match(line) - if match is not None: - self.exception = int(match.group('exc')) - return self._parse_counters - return self._parse_exception - - def _parse_counters(self, line): - match = COUNTER_REGEX.match(line) - if match is not None: - self.epc1 = match.group("epc1") - self.epc2 = match.group("epc2") - self.epc3 = match.group("epc3") - self.excvaddr = match.group("excvaddr") - self.depc = match.group("depc") - return self._parse_ctx - return self._parse_counters - - def _parse_ctx(self, line): - match = CTX_REGEX.match(line) - if match is not None: - self.ctx = match.group("ctx") - return self._parse_pointers - return self._parse_ctx - - def _parse_pointers(self, line): - match = POINTER_REGEX.match(line) - if match is not None: - self.sp = match.group("sp") - self.end = match.group("end") - self.offset = match.group("offset") - return self._parse_stack_begin - return self._parse_pointers - - def _parse_stack_begin(self, line): - if line == STACK_BEGIN: - return self._parse_stack_line - return self._parse_stack_begin - - def _parse_stack_line(self, line): - if line != STACK_END: - match = STACK_REGEX.match(line) - if match is not None: - self.stack.append(StackLine(offset=match.group("off"), - content=(match.group("c1"), match.group("c2"), match.group("c3"), - match.group("c4")))) - return self._parse_stack_line - return None - - def parse_file(self, file, stack_only=False): - func = self._parse_exception - if stack_only: - func = self._parse_stack_begin - - for line in file: - func = func(line.strip()) - if func is None: - break - - if func is not None: - print("ERROR: Parser not complete!") - sys.exit(1) - - -class AddressResolver(object): - def __init__(self, tool_path, elf_path): - self._tool = tool_path - self._elf = elf_path - self._address_map = {} - - def _lookup(self, addresses): - cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None] - - if sys.version_info[0] < 3: - output = subprocess.check_output(cmd) - else: - output = subprocess.check_output(cmd, encoding="utf-8") - - line_regex = re.compile("^(?P[0-9a-fx]+): (?P.+)$") - - last = None - for line in output.splitlines(): - line = line.strip() - match = line_regex.match(line) - - if match is None: - if last is not None and line.startswith('(inlined by)'): - line = line [12:].strip() - self._address_map[last] += ("\n \-> inlined by: " + line) - continue - - if match.group("result") == '?? ??:0': - continue - - self._address_map[match.group("addr")] = match.group("result") - last = match.group("addr") - - def fill(self, parser): - addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset] - for line in parser.stack: - addresses.extend(line.content) - - self._lookup(addresses) - - def _sanitize_addr(self, addr): - if addr.startswith("0x"): - addr = addr[2:] - - fill = "0" * (8 - len(addr)) - return "0x" + fill + addr - - def resolve_addr(self, addr): - out = self._sanitize_addr(addr) - - if out in self._address_map: - out += ": " + self._address_map[out] - - return out - - def resolve_stack_addr(self, addr, full=True): - addr = self._sanitize_addr(addr) - if addr in self._address_map: - return addr + ": " + self._address_map[addr] - - if full: - return "[DATA (0x" + addr + ")]" - - return None - - -def print_addr(name, value, resolver): - print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value))) - - -def print_stack_full(lines, resolver): - print("stack:") - for line in lines: - print(line.offset + ":") - for content in line.content: - print(" " + resolver.resolve_stack_addr(content)) - - -def print_stack(lines, resolver): - print("stack:") - for line in lines: - for content in line.content: - out = resolver.resolve_stack_addr(content, full=False) - if out is None: - continue - print(out) - - -def print_result(parser, resolver, full=True, stack_only=False): - if not stack_only: - print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception])) - - print("") - print_addr("epc1", parser.epc1, resolver) - print_addr("epc2", parser.epc2, resolver) - print_addr("epc3", parser.epc3, resolver) - print_addr("excvaddr", parser.excvaddr, resolver) - print_addr("depc", parser.depc, resolver) - - print("") - print("ctx: " + parser.ctx) - - print("") - print_addr("sp", parser.sp, resolver) - print_addr("end", parser.end, resolver) - print_addr("offset", parser.offset, resolver) - - print("") - if full: - print_stack_full(parser.stack, resolver) - else: - print_stack(parser.stack, resolver) - - -def parse_args(): - parser = argparse.ArgumentParser(description="decode ESP Stacktraces.") - - parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(), - default="ESP8266") - parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain", - default="~/.platformio/packages/toolchain-xtensa/") - parser.add_argument("-e", "--elf", help="path to elf file", required=True) - parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true") - parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true") - parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-") - - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - - if args.file == "-": - file = sys.stdin - else: - if not os.path.exists(args.file): - print("ERROR: file " + args.file + " not found") - sys.exit(1) - file = open(args.file, "r") - - addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)), - "bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line.exe") - if not os.path.exists(addr2line): - print("ERROR: addr2line not found (" + addr2line + ")") - - elf_file = os.path.abspath(os.path.expanduser(args.elf)) - if not os.path.exists(elf_file): - print("ERROR: elf file not found (" + elf_file + ")") - - parser = ExceptionDataParser() - resolver = AddressResolver(addr2line, elf_file) - - parser.parse_file(file, args.stack_only) - resolver.fill(parser) - - print_result(parser, resolver, args.full, args.stack_only) diff --git a/scripts/decoder_linux.py b/scripts/decoder_linux.py deleted file mode 100755 index 710d4b640..000000000 --- a/scripts/decoder_linux.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env python3 - -"""ESP Exception Decoder - -github: https://github.com/janLo/EspArduinoExceptionDecoder -license: GPL v3 -author: Jan Losinski -""" - -import argparse -import re -import subprocess -from collections import namedtuple - -import sys - -import os - -EXCEPTIONS = [ - "Illegal instruction", - "SYSCALL instruction", - "InstructionFetchError: Processor internal physical address or data error during instruction fetch", - "LoadStoreError: Processor internal physical address or data error during load or store", - "Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register", - "Alloca: MOVSP instruction, if caller's registers are not in the register file", - "IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero", - "reserved", - "Privileged: Attempt to execute a privileged operation when CRING ? 0", - "LoadStoreAlignmentCause: Load or store to an unaligned address", - "reserved", - "reserved", - "InstrPIFDataError: PIF data error during instruction fetch", - "LoadStorePIFDataError: Synchronous PIF data error during LoadStore access", - "InstrPIFAddrError: PIF address error during instruction fetch", - "LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access", - "InstTLBMiss: Error during Instruction TLB refill", - "InstTLBMultiHit: Multiple instruction TLB entries matched", - "InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING", - "reserved", - "InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch", - "reserved", - "reserved", - "reserved", - "LoadStoreTLBMiss: Error during TLB refill for a load or store", - "LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store", - "LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING", - "reserved", - "LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads", - "StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores" -] - -PLATFORMS = { - "ESP8266": "lx106", - "ESP32": "esp32" -} - -EXCEPTION_REGEX = re.compile("^Exception \\((?P[0-9]*)\\):$") -COUNTER_REGEX = re.compile('^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) ' - 'excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$') -CTX_REGEX = re.compile("^ctx: (?P.+)$") -POINTER_REGEX = re.compile('^sp: (?P[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$') -STACK_BEGIN = '>>>stack>>>' -STACK_END = '<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$') - -StackLine = namedtuple("StackLine", ["offset", "content"]) - - -class ExceptionDataParser(object): - def __init__(self): - self.exception = None - - self.epc1 = None - self.epc2 = None - self.epc3 = None - self.excvaddr = None - self.depc = None - - self.ctx = None - - self.sp = None - self.end = None - self.offset = None - - self.stack = [] - - def _parse_exception(self, line): - match = EXCEPTION_REGEX.match(line) - if match is not None: - self.exception = int(match.group('exc')) - return self._parse_counters - return self._parse_exception - - def _parse_counters(self, line): - match = COUNTER_REGEX.match(line) - if match is not None: - self.epc1 = match.group("epc1") - self.epc2 = match.group("epc2") - self.epc3 = match.group("epc3") - self.excvaddr = match.group("excvaddr") - self.depc = match.group("depc") - return self._parse_ctx - return self._parse_counters - - def _parse_ctx(self, line): - match = CTX_REGEX.match(line) - if match is not None: - self.ctx = match.group("ctx") - return self._parse_pointers - return self._parse_ctx - - def _parse_pointers(self, line): - match = POINTER_REGEX.match(line) - if match is not None: - self.sp = match.group("sp") - self.end = match.group("end") - self.offset = match.group("offset") - return self._parse_stack_begin - return self._parse_pointers - - def _parse_stack_begin(self, line): - if line == STACK_BEGIN: - return self._parse_stack_line - return self._parse_stack_begin - - def _parse_stack_line(self, line): - if line != STACK_END: - match = STACK_REGEX.match(line) - if match is not None: - self.stack.append(StackLine(offset=match.group("off"), - content=(match.group("c1"), match.group("c2"), match.group("c3"), - match.group("c4")))) - return self._parse_stack_line - return None - - def parse_file(self, file, stack_only=False): - func = self._parse_exception - if stack_only: - func = self._parse_stack_begin - - for line in file: - func = func(line.strip()) - if func is None: - break - - if func is not None: - print("ERROR: Parser not complete!") - sys.exit(1) - - -class AddressResolver(object): - def __init__(self, tool_path, elf_path): - self._tool = tool_path - self._elf = elf_path - self._address_map = {} - - def _lookup(self, addresses): - cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None] - - if sys.version_info[0] < 3: - output = subprocess.check_output(cmd) - else: - output = subprocess.check_output(cmd, encoding="utf-8") - - line_regex = re.compile("^(?P[0-9a-fx]+): (?P.+)$") - - last = None - for line in output.splitlines(): - line = line.strip() - match = line_regex.match(line) - - if match is None: - if last is not None and line.startswith('(inlined by)'): - line = line [12:].strip() - self._address_map[last] += ("\n \-> inlined by: " + line) - continue - - if match.group("result") == '?? ??:0': - continue - - self._address_map[match.group("addr")] = match.group("result") - last = match.group("addr") - - def fill(self, parser): - addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset] - for line in parser.stack: - addresses.extend(line.content) - - self._lookup(addresses) - - def _sanitize_addr(self, addr): - if addr.startswith("0x"): - addr = addr[2:] - - fill = "0" * (8 - len(addr)) - return "0x" + fill + addr - - def resolve_addr(self, addr): - out = self._sanitize_addr(addr) - - if out in self._address_map: - out += ": " + self._address_map[out] - - return out - - def resolve_stack_addr(self, addr, full=True): - addr = self._sanitize_addr(addr) - if addr in self._address_map: - return addr + ": " + self._address_map[addr] - - if full: - return "[DATA (0x" + addr + ")]" - - return None - - -def print_addr(name, value, resolver): - print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value))) - - -def print_stack_full(lines, resolver): - print("stack:") - for line in lines: - print(line.offset + ":") - for content in line.content: - print(" " + resolver.resolve_stack_addr(content)) - - -def print_stack(lines, resolver): - print("stack:") - for line in lines: - for content in line.content: - out = resolver.resolve_stack_addr(content, full=False) - if out is None: - continue - print(out) - - -def print_result(parser, resolver, full=True, stack_only=False): - if not stack_only: - print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception])) - - print("") - print_addr("epc1", parser.epc1, resolver) - print_addr("epc2", parser.epc2, resolver) - print_addr("epc3", parser.epc3, resolver) - print_addr("excvaddr", parser.excvaddr, resolver) - print_addr("depc", parser.depc, resolver) - - print("") - print("ctx: " + parser.ctx) - - print("") - print_addr("sp", parser.sp, resolver) - print_addr("end", parser.end, resolver) - print_addr("offset", parser.offset, resolver) - - print("") - if full: - print_stack_full(parser.stack, resolver) - else: - print_stack(parser.stack, resolver) - - -def parse_args(): - parser = argparse.ArgumentParser(description="decode ESP Stacktraces.") - - parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(), - default="ESP8266") - parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain", - default="~/.platformio/packages/toolchain-xtensa/") - parser.add_argument("-e", "--elf", help="path to elf file", required=True) - parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true") - parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true") - parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-") - - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - - if args.file == "-": - file = sys.stdin - else: - if not os.path.exists(args.file): - print("ERROR: file " + args.file + " not found") - sys.exit(1) - file = open(args.file, "r") - - addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)), - "bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line") - if not os.path.exists(addr2line): - print("ERROR: addr2line not found (" + addr2line + ")") - - elf_file = os.path.abspath(os.path.expanduser(args.elf)) - if not os.path.exists(elf_file): - print("ERROR: elf file not found (" + elf_file + ")") - - parser = ExceptionDataParser() - resolver = AddressResolver(addr2line, elf_file) - - parser.parse_file(file, args.stack_only) - resolver.fill(parser) - - print_result(parser, resolver, args.full, args.stack_only) diff --git a/scripts/memanalyzer.py b/scripts/memanalyzer.py deleted file mode 100755 index 160c4c9bb..000000000 --- a/scripts/memanalyzer.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env python3 -# pylint: disable=C0301,C0114,C0116,W0511 -# coding=utf-8 -# ------------------------------------------------------------------------------- -# based on ESPurna module memory analyser by xose.perez@gmail.com -# -# Rewritten for python-3 and changed to use "size" instead of "objdump" -# Based on https://github.com/esp8266/Arduino/pull/6525 -# by Maxim Prokhorov -# -# Based on: -# https://github.com/letscontrolit/ESPEasy/blob/mega/memanalyzer.py -# by psy0rz -# https://raw.githubusercontent.com/SmingHub/Sming/develop/tools/memanalyzer.py -# by Slavey Karadzhov -# https://github.com/Sermus/ESP8266_memory_analyzer -# by Andrey Filimonov -# -# ------------------------------------------------------------------------------- -# -# When using Windows with non-default installation at the C:\.platformio, -# you would need to specify toolchain path manually. For example: -# -# $ py -3 scripts\memanalyzer.py --toolchain-prefix C:\.platformio\packages\toolchain-xtensa\bin\xtensa-lx106-elf- -# -# You could also change the path to platformio binary in a similar fashion: -# $ py -3 scripts\memanalyzer.py --platformio-prefix C:\Users\Max\platformio-penv\Scripts\ -# -# ------------------------------------------------------------------------------- - -import argparse -import os -import re -import subprocess -import sys -from collections import OrderedDict -from subprocess import getstatusoutput - -__version__ = (0, 3) - -# ------------------------------------------------------------------------------- - -TOTAL_IRAM = 32786 -TOTAL_DRAM = 81920 - -DEFAULT_ENV = "esp12e" -TOOLCHAIN_PREFIX = "~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-" -PLATFORMIO_PREFIX = "" -SECTIONS = OrderedDict( - [ - (".data", "Initialized Data (RAM)"), - (".rodata", "ReadOnly Data (RAM)"), - (".bss", "Uninitialized Data (RAM)"), - (".text", "Cached Code (IRAM)"), - (".irom0.text", "Uncached Code (SPI)"), - ] -) -DESCRIPTION = "Memory Analyzer v{}".format( - ".".join(str(x) for x in __version__) -) - - -# ------------------------------------------------------------------------------- - - -def size_binary_path(prefix): - return "{}size".format(os.path.expanduser(prefix)) - - -def file_size(file): - try: - return os.stat(file).st_size - except OSError: - return 0 - - -def analyse_memory(size, elf_file): - proc = subprocess.Popen( - [size, "-A", elf_file], stdout=subprocess.PIPE, universal_newlines=True - ) - lines = proc.stdout.readlines() - - values = {} - - for line in lines: - words = line.split() - for name in SECTIONS.keys(): - if line.startswith(name): - value = values.setdefault(name, 0) - value += int(words[1]) - values[name] = value - break - - return values - - -def run(prefix, env, modules, debug): - flags = " ".join("-D{}_SUPPORT={:d}".format(k, v) for k, v in modules.items()) - - os_env = os.environ.copy() - os_env["PLATFORMIO_SRC_BUILD_FLAGS"] = flags - os_env["PLATFORMIO_BUILD_CACHE_DIR"] = "test/pio_cache" - - if debug: - print("Selected flags: {}".format(flags)) - - command = [os.path.join(prefix, "platformio"), "run"] - if not debug: - command.append("--silent") - command.extend(["--environment", env]) - - output = None if debug else subprocess.DEVNULL - - - try: - subprocess.check_call( - command, shell=False, env=os_env, stdout=output, stderr=output - ) - except subprocess.CalledProcessError: - print(" - Command failed: {}".format(command)) - print(" - Selected flags: {}".format(flags)) - sys.exit(1) - - -def get_available_modules(): - modules = [] - for line in open("lib/framework/services.h"): - match = re.search(r"(\w*)_SUPPORT", line) - if match: - modules.append((match.group(1), 0)) - modules.sort(key=lambda item: item[0]) - - return OrderedDict(modules) - - -def parse_commandline_args(): - parser = argparse.ArgumentParser( - description=DESCRIPTION, formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - parser.add_argument( - "-e", "--environment", help="platformio environment to use", default=DEFAULT_ENV - ) - parser.add_argument( - "--toolchain-prefix", - help="where to find the xtensa toolchain binaries", - default=TOOLCHAIN_PREFIX, - ) - parser.add_argument( - "--platformio-prefix", - help="where to find the platformio executable", - default=PLATFORMIO_PREFIX, - ) - parser.add_argument( - "-c", - "--core", - help="use core as base configuration instead of default", - action="store_true", - default=False, - ) - parser.add_argument( - "-l", - "--list", - help="list available modules", - action="store_true", - default=False, - ) - parser.add_argument("-d", "--debug", action="store_true", default=False) - parser.add_argument( - "modules", nargs="*", help="Modules to test (use ALL to test them all)" - ) - - return parser.parse_args() - - -def size_binary_exists(args): - - status, _ = getstatusoutput(size_binary_path(args.toolchain_prefix)) - if status != 1: - print("size not found, please check that the --toolchain-prefix is correct") - sys.exit(1) - - -def get_modules(args): - - # Load list of all modules - available_modules = get_available_modules() - if args.list: - print("List of available modules:\n") - for module in available_modules: - print("* " + module) - print() - sys.exit(0) - - modules = [] - if args.modules: - if "ALL" in args.modules: - modules.extend(available_modules.keys()) - else: - modules.extend(args.modules) - modules.sort() - - # Check test modules exist - for module in modules: - if module not in available_modules: - print("Module {} not found".format(module)) - sys.exit(2) - - # Either use all of modules or specified subset - if args.core: - modules = available_modules - else: - modules = OrderedDict((x, 0) for x in modules) - - configuration = "CORE" if args.core else "DEFAULT" - - return configuration, modules - - -# ------------------------------------------------------------------------------- - - -class Analyser: - """Run platformio and print info about the resulting binary.""" - - OUTPUT_FORMAT = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}" - DELIMETERS = OUTPUT_FORMAT.format( - "-" * 20, "-" * 15, "-" * 15, "-" * 15, "-" * 15, "-" * 15, "-" * 15, "-" * 15 - ) - FIRMWARE_FORMAT = ".pio/build/{env}/firmware.{suffix}" - - class _Enable: - def __init__(self, analyser, module=None): - self.analyser = analyser - self.module = module - - def __enter__(self): - if not self.module: - for name in self.analyser.modules: - self.analyser.modules[name] = 1 - else: - self.analyser.modules[self.module] = 1 - return self.analyser - - def __exit__(self, *args, **kwargs): - if not self.module: - for name in self.analyser.modules: - self.analyser.modules[name] = 0 - else: - self.analyser.modules[self.module] = 0 - - analyser = None - module = None - - def __init__(self, args, modules): - self._debug = args.debug - self._platformio_prefix = args.platformio_prefix - self._toolchain_prefix = args.toolchain_prefix - self._environment = args.environment - self.modules = modules - self.baseline = None - - def enable(self, module=None): - return self._Enable(self, module) - - def print(self, *args): - print(self.OUTPUT_FORMAT.format(*args)) - - def print_delimiters(self): - print(self.DELIMETERS) - - def begin(self, name): - self.print( - "Module", - "Cache IRAM", - "Init RAM", - "R.O. RAM", - "Uninit RAM", - "Available RAM", - "Flash ROM", - "Binary size", - ) - self.print( - "", - ".text + .text1", - ".data", - ".rodata", - ".bss", - "heap + stack", - ".irom0.text", - "", - ) - self.print_delimiters() - self.baseline = self.run() - self.print_values(name, self.baseline) - - def print_values(self, header, values): - self.print( - header, - values[".text"], - values[".data"], - values[".rodata"], - values[".bss"], - values["free"], - values[".irom0.text"], - values["size"], - ) - - def print_compare(self, header, values): - self.print( - header, - values[".text"] - self.baseline[".text"], - values[".data"] - self.baseline[".data"], - values[".rodata"] - self.baseline[".rodata"], - values[".bss"] - self.baseline[".bss"], - values["free"] - self.baseline["free"], - values[".irom0.text"] - self.baseline[".irom0.text"], - values["size"] - self.baseline["size"], - ) - - def run(self): - run(self._platformio_prefix, self._environment, self.modules, self._debug) - - elf_path = self.FIRMWARE_FORMAT.format(env=self._environment, suffix="elf") - bin_path = self.FIRMWARE_FORMAT.format(env=self._environment, suffix="bin") - - values = analyse_memory( - size_binary_path(self._toolchain_prefix), elf_path - ) - - free = 80 * 1024 - values[".data"] - values[".rodata"] - values[".bss"] - free = free + (16 - free % 16) - values["free"] = free - - values["size"] = file_size(bin_path) - - return values - - -def main(args): - - # Check xtensa-lx106-elf-size is in the path - size_binary_exists(args) - - # Which modules to test? - configuration, modules = get_modules(args) - - # print_values init message - print('Selected environment "{}"'.format(args.environment), end="") - if modules: - print(" with modules: {}".format(" ".join(modules.keys()))) - else: - print() - - print() - print("Analyzing {} configuration".format(configuration)) - print() - - # Build the core without any modules to get base memory usage - analyser = Analyser(args, modules) - analyser.begin(configuration) - - # Test each module separately - results = {} - for module in analyser.modules: - with analyser.enable(module): - results[module] = analyser.run() - analyser.print_compare(module, results[module]) - - # Test all modules - if analyser.modules: - - with analyser.enable(): - total = analyser.run() - - analyser.print_delimiters() - if len(analyser.modules) > 1: - analyser.print_compare("ALL MODULES", total) - - analyser.print_values("TOTAL", total) - - -if __name__ == "__main__": - main(parse_commandline_args()) diff --git a/src/version.h b/src/version.h index 96602d1d1..4616c4e4d 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.1.1b0" +#define EMSESP_APP_VERSION "2.1.1b1" From f112887658b8adac708f183e8048b716dd91d2f6 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Thu, 5 Nov 2020 07:55:26 +0100 Subject: [PATCH 10/10] fix #595 mixer IPM pumpstatus --- CHANGELOG_LATEST.md | 1 + src/devices/mixer.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 168f8f142..229cc4451 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -4,6 +4,7 @@ - function keys in editor: cursor, del, pos1, end. F1=help, F2=show, F10=report ### Fixed +- mixer IPM pumpstatus ### Changed - optimized MQTT for HA to reduce mem fragmentation issues diff --git a/src/devices/mixer.cpp b/src/devices/mixer.cpp index 1ff6900cb..43354768d 100644 --- a/src/devices/mixer.cpp +++ b/src/devices/mixer.cpp @@ -224,7 +224,7 @@ bool Mixer::export_values(JsonObject & json) { // returns false if empty bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) { // check if there is data for the mixer unit - if (!Helpers::hasValue(status_)) { + if (this->type() == Type::NONE) { return 0; } @@ -249,7 +249,7 @@ bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) { json_hc["flowSetTemp"] = flowSetTemp_; } if (Helpers::hasValue(pumpStatus_)) { - char s[5]; + char s[7]; json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL); } if (Helpers::hasValue(status_)) { @@ -274,7 +274,7 @@ bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) { json_hc["wwTemp"] = (float)flowTemp_ / 10; } if (Helpers::hasValue(pumpStatus_)) { - char s[5]; + char s[7]; json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL); } if (Helpers::hasValue(status_)) { @@ -316,7 +316,7 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr telegram) { // check if circuit is active, 0-off, 1-unmixed, 2-mixed uint8_t ismixed = 0; - changed_ |= telegram->read_value(ismixed, 0); + telegram->read_value(ismixed, 0); if (ismixed == 0) { return; }