diff --git a/Makefile b/Makefile index 6f449c25c..636cc1d99 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # GNUMakefile for EMS-ESP -# (c) 2020 Paul Derbyshire # + NUMJOBS=${NUMJOBS:-" -j4 "} MAKEFLAGS+="j " #---------------------------------------------------------------------- @@ -17,23 +17,28 @@ MAKEFLAGS+="j " #TARGET := $(notdir $(CURDIR)) TARGET := emsesp BUILD := build -SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton -INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/* src/devices +SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver +INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices LIBRARIES := CPPCHECK = cppcheck +# CHECKFLAGS = -q --force --std=c++17 CHECKFLAGS = -q --force --std=c++11 #---------------------------------------------------------------------- # Languages Standard #---------------------------------------------------------------------- +# C_STANDARD := -std=c17 +# CXX_STANDARD := -std=c++17 C_STANDARD := -std=c11 CXX_STANDARD := -std=c++11 #---------------------------------------------------------------------- # Defined Symbols #---------------------------------------------------------------------- -DEFINES += -DFACTORY_WIFI_HOSTNAME=\"ems-esp\" -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\" +DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL + +DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.5.0b11\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" #---------------------------------------------------------------------- # Sources & Files @@ -66,7 +71,7 @@ CXX := /usr/bin/g++ # CXXFLAGS C++ Compiler Flags # LDFLAGS Linker Flags #---------------------------------------------------------------------- -CPPFLAGS += $(DEFINES) $(INCLUDE) +CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE) CPPFLAGS += -ggdb CPPFLAGS += -g3 CPPFLAGS += -Os @@ -114,6 +119,8 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@ # Targets #---------------------------------------------------------------------- .PHONY: all +.SILENT: $(OUTPUT) + all: $(OUTPUT) $(OUTPUT): $(OBJS) @@ -147,4 +154,4 @@ help: @echo available targets: all run clean @echo $(OUTPUT) --include $(DEPS) +-include $(DEPS) \ No newline at end of file diff --git a/lib/Adafruit_NeoPixel-master/Adafruit_NeoPixel.cpp b/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp similarity index 100% rename from lib/Adafruit_NeoPixel-master/Adafruit_NeoPixel.cpp rename to lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp diff --git a/lib/Adafruit_NeoPixel-master/Adafruit_NeoPixel.h b/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h similarity index 100% rename from lib/Adafruit_NeoPixel-master/Adafruit_NeoPixel.h rename to lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h diff --git a/lib/Adafruit_NeoPixel-master/CONTRIBUTING.md b/lib/Adafruit_NeoPixel/CONTRIBUTING.md similarity index 100% rename from lib/Adafruit_NeoPixel-master/CONTRIBUTING.md rename to lib/Adafruit_NeoPixel/CONTRIBUTING.md diff --git a/lib/Adafruit_NeoPixel-master/COPYING b/lib/Adafruit_NeoPixel/COPYING similarity index 100% rename from lib/Adafruit_NeoPixel-master/COPYING rename to lib/Adafruit_NeoPixel/COPYING diff --git a/lib/Adafruit_NeoPixel-master/README.md b/lib/Adafruit_NeoPixel/README.md similarity index 100% rename from lib/Adafruit_NeoPixel-master/README.md rename to lib/Adafruit_NeoPixel/README.md diff --git a/lib/Adafruit_NeoPixel-master/esp.c b/lib/Adafruit_NeoPixel/esp.c similarity index 100% rename from lib/Adafruit_NeoPixel-master/esp.c rename to lib/Adafruit_NeoPixel/esp.c diff --git a/lib/Adafruit_NeoPixel-master/esp8266.c b/lib/Adafruit_NeoPixel/esp8266.c similarity index 100% rename from lib/Adafruit_NeoPixel-master/esp8266.c rename to lib/Adafruit_NeoPixel/esp8266.c diff --git a/lib/Adafruit_NeoPixel-master/kendyte_k210.c b/lib/Adafruit_NeoPixel/kendyte_k210.c similarity index 100% rename from lib/Adafruit_NeoPixel-master/kendyte_k210.c rename to lib/Adafruit_NeoPixel/kendyte_k210.c diff --git a/lib/Adafruit_NeoPixel-master/keywords.txt b/lib/Adafruit_NeoPixel/keywords.txt similarity index 100% rename from lib/Adafruit_NeoPixel-master/keywords.txt rename to lib/Adafruit_NeoPixel/keywords.txt diff --git a/lib/Adafruit_NeoPixel-master/library.properties b/lib/Adafruit_NeoPixel/library.properties similarity index 100% rename from lib/Adafruit_NeoPixel-master/library.properties rename to lib/Adafruit_NeoPixel/library.properties diff --git a/lib/Adafruit_NeoPixel-master/rp2040_pio.h b/lib/Adafruit_NeoPixel/rp2040_pio.h similarity index 100% rename from lib/Adafruit_NeoPixel-master/rp2040_pio.h rename to lib/Adafruit_NeoPixel/rp2040_pio.h diff --git a/lib/framework/FSPersistence.h b/lib/framework/FSPersistence.h index fb77e99ad..0b66de2f2 100644 --- a/lib/framework/FSPersistence.h +++ b/lib/framework/FSPersistence.h @@ -41,7 +41,6 @@ class FSPersistence { Serial.println(); #endif #endif - _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); settingsFile.close(); return; @@ -96,6 +95,7 @@ class FSPersistence { // debug added by Proddy #if defined(EMSESP_DEBUG) #if defined(EMSESP_USE_SERIAL) + Serial.println(); Serial.printf("Writing to file: %s: ", _filePath); serializeJson(jsonDocument, Serial); Serial.println(); diff --git a/lib/semver/LICENSE b/lib/semver/LICENSE new file mode 100644 index 000000000..53234651b --- /dev/null +++ b/lib/semver/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Marko Živanović + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lib/semver/README.md b/lib/semver/README.md new file mode 100644 index 000000000..82c86d7d3 --- /dev/null +++ b/lib/semver/README.md @@ -0,0 +1,7 @@ +# About + +This project is MIT-licensed, C++14 implementation of [semantic versioning](http://semver.org) parser and comparator with support for modifying parsed version strings. Semantic versioning 2.0.0 specification is supported out-of-the-box and the code should be flexible-enough to support future revisions or other similar versioning schemes. + +Copyright (c) 2015 Marko Zivanovic + +Based on https://github.com/zmarko/semver diff --git a/lib/semver/Semver200_comparator.cpp b/lib/semver/Semver200_comparator.cpp new file mode 100644 index 000000000..1430bd14b --- /dev/null +++ b/lib/semver/Semver200_comparator.cpp @@ -0,0 +1,121 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include "semver200.h" + +using namespace std; + +namespace version { + +namespace { + +// Compare normal version identifiers. +int compare_normal(const Version_data & l, const Version_data & r) { + if (l.major > r.major) + return 1; + if (l.major < r.major) + return -1; + if (l.minor > r.minor) + return 1; + if (l.minor < r.minor) + return -1; + if (l.patch > r.patch) + return 1; + if (l.patch < r.patch) + return -1; + return 0; +} + +// Compare alphanumeric prerelease identifiers. +inline int cmp_alnum_prerel_ids(const string & l, const string & r) { + auto cmp = l.compare(r); + if (cmp == 0) { + return cmp; + } else { + return cmp > 0 ? 1 : -1; + } +} + +// Compare numeric prerelease identifiers. +inline int cmp_num_prerel_ids(const string & l, const string & r) { + long long li = stoll(l); + long long ri = stoll(r); + if (li == ri) + return 0; + return li > ri ? 1 : -1; +} + +using Prerel_type_pair = pair; +using Prerel_id_comparator = function; +const map comparators = {{{Id_type::alnum, Id_type::alnum}, cmp_alnum_prerel_ids}, + {{Id_type::alnum, Id_type::num}, [](const string &, const string &) { return 1; }}, + {{Id_type::num, Id_type::alnum}, [](const string &, const string &) { return -1; }}, + {{Id_type::num, Id_type::num}, cmp_num_prerel_ids}}; + +// Compare prerelease identifiers based on their types. +inline int compare_prerel_identifiers(const Prerelease_identifier & l, const Prerelease_identifier & r) { + auto cmp = comparators.at({l.second, r.second}); + return cmp(l.first, r.first); +} + +inline int cmp_rel_prerel(const Prerelease_identifiers & l, const Prerelease_identifiers & r) { + if (l.empty() && !r.empty()) + return 1; + if (r.empty() && !l.empty()) + return -1; + return 0; +} +} // namespace + +int Semver200_comparator::compare(const Version_data & l, const Version_data & r) const { + // Compare normal version components. + int cmp = compare_normal(l, r); + if (cmp != 0) + return cmp; + + // Compare if one version is release and the other prerelease - release is always higher. + cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids); + if (cmp != 0) + return cmp; + + // Compare prerelease by looking at each identifier: numeric ones are compared as numbers, + // alphanum as ASCII strings. + auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size()); + for (size_t i = 0; i < shorter; i++) { + cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]); + if (cmp != 0) + return cmp; + } + + // Prerelease identifiers are the same, to the length of the shorter version string; + // if they are the same length, then versions are equal, otherwise, longer one wins. + if (l.prerelease_ids.size() == r.prerelease_ids.size()) + return 0; + return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1; +} + +} // namespace version diff --git a/lib/semver/Semver200_parser.cpp b/lib/semver/Semver200_parser.cpp new file mode 100644 index 000000000..77bcbd5a7 --- /dev/null +++ b/lib/semver/Semver200_parser.cpp @@ -0,0 +1,194 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include "semver200.h" + +#include "../../src/emsesp_stub.hpp" // for logging + +#ifdef _MSC_VER +// disable symbol name too long warning +#pragma warning(disable : 4503) +#endif + +using namespace std; + +namespace version { + +namespace { +enum class Parser_state { major, minor, patch, prerelease, build }; + +using Validator = function; +using State_transition_hook = function; +/// State transition is described by a character that triggers it, a state to transition to and +/// optional hook to be invoked on transition. +using Transition = tuple; +using Transitions = vector; +using State = tuple; +using State_machine = std::map; + +// Ranges of characters allowed in prerelease and build identifiers. +const vector> allowed_prerel_id_chars = {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {'-', '-'}}; + +inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) { + return make_tuple(c, p, pth); +} + +inline void Parse_error(const std::string & s) { + emsesp::EMSESP::logger().err("parse error: %s", s.c_str()); +} + +/// Advance parser state machine by a single step. +/** + Perform single step of parser state machine: if character matches one from transition tables - + trigger transition to next state; otherwise, validate if current token is in legal state + (throw Parse_error if not) and then add character to current token; State transition includes + preparing various vars for next state and invoking state transition hook (if specified) which is + where whole tokens are validated. + */ +inline void process_char(const char c, Parser_state & cstate, Parser_state & pstate, const Transitions & transitions, string & target, Validator validate) { + for (const auto & transition : transitions) { + if (c == get<0>(transition)) { + if (get<2>(transition)) + get<2>(transition)(target); + pstate = cstate; + cstate = get<1>(transition); + return; + } + } + validate(target, c); + target.push_back(c); +} + +/// Validate normal (major, minor, patch) version components. +inline void normal_version_validator(const string & tgt, const char c) { + if (c < '0' || c > '9') + Parse_error("invalid character encountered: " + string(1, c)); + if (tgt.compare(0, 1, "0") == 0) + Parse_error("leading 0 not allowed"); +} + +/// Validate that prerelease and build version identifiers are comprised of allowed chars only. +inline void prerelease_version_validator(const string &, const char c) { + bool res = false; + for (const auto & r : allowed_prerel_id_chars) { + res |= (c >= r.first && c <= r.second); + } + if (!res) + Parse_error("invalid character encountered: " + string(1, c)); +} + +inline bool is_identifier_numeric(const string & id) { + return id.find_first_not_of("0123456789") == string::npos; +} + +inline bool check_for_leading_0(const string & str) { + return str.length() > 1 && str[0] == '0'; +} + +/// Validate every individual prerelease identifier, determine it's type and add it to collection. +void prerelease_hook_impl(string & id, Prerelease_identifiers & prerelease) { + if (id.empty()) + Parse_error("version identifier cannot be empty"); + Id_type t = Id_type::alnum; + if (is_identifier_numeric(id)) { + t = Id_type::num; + if (check_for_leading_0(id)) { + Parse_error("numeric identifiers cannot have leading 0"); + } + } + prerelease.push_back(Prerelease_identifier(id, t)); + id.clear(); +} + +/// Validate every individual build identifier and add it to collection. +void build_hook_impl(string & id, Parser_state & pstate, Build_identifiers & build, std::string & prerelease_id, Prerelease_identifiers & prerelease) { + // process last token left from parsing prerelease data + if (pstate == Parser_state::prerelease) + prerelease_hook_impl(prerelease_id, prerelease); + if (id.empty()) + Parse_error("version identifier cannot be empty"); + build.push_back(id); + id.clear(); +} + +} // namespace + +/// Parse semver 2.0.0-compatible string to Version_data structure. +/** + Version text parser is implemented as a state machine. In each step one successive character from version + string is consumed and is either added to current token or triggers state transition. Hooks can be + injected into state transitions for validation/customization purposes. + */ +Version_data Semver200_parser::parse(const string & s) const { + string major; + string minor; + string patch; + string prerelease_id; + string build_id; + Prerelease_identifiers prerelease; + Build_identifiers build; + Parser_state cstate{Parser_state::major}; + Parser_state pstate; + + auto prerelease_hook = [&](string & id) { prerelease_hook_impl(id, prerelease); }; + + auto build_hook = [&](string & id) { build_hook_impl(id, pstate, build, prerelease_id, prerelease); }; + + // State transition tables + auto major_trans = {mkx('.', Parser_state::minor, {})}; + auto minor_trans = {mkx('.', Parser_state::patch, {})}; + auto patch_trans = {mkx('-', Parser_state::prerelease, {}), mkx('+', Parser_state::build, {})}; + auto prerelease_trans = {// When identifier separator (.) is found, stay in the same state but invoke hook + // in order to process each individual identifier separately. + mkx('.', Parser_state::prerelease, prerelease_hook), + mkx('+', Parser_state::build, {})}; + auto build_trans = {// Same stay-in-the-same-state-but-invoke-hook trick from above. + mkx('.', Parser_state::build, build_hook)}; + + State_machine state_machine = {{Parser_state::major, State{major_trans, major, normal_version_validator}}, + {Parser_state::minor, State{minor_trans, minor, normal_version_validator}}, + {Parser_state::patch, State{patch_trans, patch, normal_version_validator}}, + {Parser_state::prerelease, State{prerelease_trans, prerelease_id, prerelease_version_validator}}, + {Parser_state::build, State{build_trans, build_id, prerelease_version_validator}}}; + + // Main loop. + for (const auto & c : s) { + auto state = state_machine.at(cstate); + process_char(c, cstate, pstate, get<0>(state), get<1>(state), get<2>(state)); + } + + // Trigger appropriate hooks in order to process last token, because no state transition was + // triggered for it. + if (cstate == Parser_state::prerelease) { + prerelease_hook(prerelease_id); + } else if (cstate == Parser_state::build) { + build_hook(build_id); + } + + return Version_data{stoi(major), stoi(minor), stoi(patch), prerelease, build}; +} + +} // namespace version diff --git a/lib/semver/semver200.h b/lib/semver/semver200.h new file mode 100644 index 000000000..5c19f07ff --- /dev/null +++ b/lib/semver/semver200.h @@ -0,0 +1,53 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include "version.h" + +namespace version { + +/// Parse string into Version_data structure according to semantic versioning 2.0.0 rules. +struct Semver200_parser { + Version_data parse(const std::string &) const; +}; + +/// Compare Version_data to another using semantic versioning 2.0.0 rules. +struct Semver200_comparator { + int compare(const Version_data &, const Version_data &) const; +}; + +/// Concrete version class that binds all semver 2.0.0 functionality together. +class Semver200_version : public Basic_version { + public: + Semver200_version() + : Basic_version{Semver200_parser(), Semver200_comparator()} { + } + + Semver200_version(const std::string & v) + : Basic_version{v, Semver200_parser(), Semver200_comparator()} { + } +}; + +} // namespace version \ No newline at end of file diff --git a/lib/semver/version.h b/lib/semver/version.h new file mode 100644 index 000000000..517d6f435 --- /dev/null +++ b/lib/semver/version.h @@ -0,0 +1,149 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include +#include + +namespace version { + +/// Type of prerelease identifier: alphanumeric or numeric. +/** + Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while + numeric identifiers are compared as numbers. + */ +enum class Id_type { + alnum, ///< Identifier is alphanumerical + num ///< Identifier is numeric +}; + +/// Container for prerelease identifier value and it's type. +/** + Prerelease version string consist of an optional series of dot-separated identifiers. + These identifiers can be either numerical or alphanumerical. + This structure describes one such identifier. + */ +using Prerelease_identifier = std::pair; + +/// Container for all prerelease identifiers for a given version string. +using Prerelease_identifiers = std::vector; + +/// Build identifier is arbitrary string with no special meaning with regards to version precedence. +using Build_identifier = std::string; + +/// Container for all build identifiers of a given version string. +using Build_identifiers = std::vector; + +/// Description of version broken into parts, as per semantic versioning specification. +struct Version_data { + Version_data(const int M, const int m, const int p, const Prerelease_identifiers & pr, const Build_identifiers & b) + : major{M} + , minor{m} + , patch{p} + , prerelease_ids{pr} + , build_ids{b} { + } + + int major; ///< Major version, change only on incompatible API modifications. + int minor; ///< Minor version, change on backwards-compatible API modifications. + int patch; ///< Patch version, change only on bugfixes. + + /// Optional series of prerelease identifiers. + Prerelease_identifiers prerelease_ids; + + /// Optional series of build identifiers. + Build_identifiers build_ids; +}; + +// Forward declaration required for operators' template declarations. +template +class Basic_version; + +/// Test if left-hand version operand is of lower precedence than the right-hand version. +template +bool operator<(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand if of equal precedence as the right-hand version. +template +bool operator==(const Basic_version &, const Basic_version &); + +/// Test if left-hand version and right-hand version are of different precedence. +template +bool operator!=(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand is of higher precedence than the right-hand version. +template +bool operator>(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand is of higher or equal precedence as the right-hand version. +template +bool operator>=(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand is of lower or equal precedence as the right-hand version. +template +bool operator<=(const Basic_version &, const Basic_version &); + +/// Base class for various version parsing, precedence ordering and data manipulation schemes. +/** + Basic_version class describes general version object without prescribing parsing, + validation, comparison and modification rules. These rules are implemented by supplied Parser, Comparator + and Modifier objects. + */ +template +class Basic_version { + public: + /// Construct Basic_version object using Parser object to parse default ("0.0.0") version string, Comparator for comparison and Modifier for modification. + Basic_version(Parser, Comparator); + + /// Construct Basic_version object using Parser to parse supplied version string, Comparator for comparison and Modifier for modification. + Basic_version(const std::string &, Parser, Comparator); + + /// Construct Basic_version object using supplied Version_data, Parser, Comparator and Modifier objects. + Basic_version(const Version_data &, Parser, Comparator); + + /// Construct Basic_version by copying data from another one. + Basic_version(const Basic_version &); + + /// Copy version data from another Basic_version to this one. + Basic_version & operator=(const Basic_version &); + + int major() const; ///< Get major version. + int minor() const; ///< Get minor version. + int patch() const; ///< Get patch version. + const std::string prerelease() const; ///< Get prerelease version string. + const std::string build() const; ///< Get build version string. + + friend bool operator< <>(const Basic_version &, const Basic_version &); + friend bool operator==<>(const Basic_version &, const Basic_version &); + + private: + Parser parser_; + Comparator comparator_; + Version_data ver_; +}; + +} // namespace version + +#include "version.inl" diff --git a/lib/semver/version.inl b/lib/semver/version.inl new file mode 100644 index 000000000..8de18af61 --- /dev/null +++ b/lib/semver/version.inl @@ -0,0 +1,132 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include "version.h" + +namespace version { + +namespace { + +/// Utility function to splice all vector elements to output stream, using designated separator +/// between elements and function object for getting values from vector elements. +template +std::string & splice(std::string & ss, const std::vector & vec, const std::string & sep, F read) { + if (!vec.empty()) { + for (auto it = vec.cbegin(); it < vec.cend() - 1; ++it) { + ss += read(*it) + sep; + } + ss += read(*vec.crbegin()); + } + return ss; +} + +} // namespace + +template +Basic_version::Basic_version(Parser p, Comparator c) + : parser_(p) + , comparator_(c) + , ver_(parser_.parse("0.0.0")) { +} + +template +Basic_version::Basic_version(const std::string & v, Parser p, Comparator c) + : parser_(p) + , comparator_(c) + , ver_(parser_.parse(v)) { +} + +template +Basic_version::Basic_version(const Version_data & v, Parser p, Comparator c) + : parser_(p) + , comparator_(c) + , ver_(v) { +} + +template +Basic_version::Basic_version(const Basic_version &) = default; + +template +Basic_version & Basic_version::operator=(const Basic_version &) = default; + +template +int Basic_version::major() const { + return ver_.major; +} + +template +int Basic_version::minor() const { + return ver_.minor; +} + +template +int Basic_version::patch() const { + return ver_.patch; +} + +template +const std::string Basic_version::prerelease() const { + std::string ss; + return splice(ss, ver_.prerelease_ids, ".", [](const Prerelease_identifier & id) { return id.first; }); +} + +template +const std::string Basic_version::build() const { + std::string ss; + return splice(ss, ver_.build_ids, ".", [](const std::string & id) { return id; }); +} + +template +bool operator<(const Basic_version & l, const Basic_version & r) { + return l.comparator_.compare(l.ver_, r.ver_) == -1; +} + +template +bool operator==(const Basic_version & l, const Basic_version & r) { + return l.comparator_.compare(l.ver_, r.ver_) == 0; +} + +template +inline bool operator!=(const Basic_version & l, const Basic_version & r) { + return !(l == r); +} + +template +inline bool operator>(const Basic_version & l, const Basic_version & r) { + return r < l; +} + +template +inline bool operator>=(const Basic_version & l, const Basic_version & r) { + return !(l < r); +} + +template +inline bool operator<=(const Basic_version & l, const Basic_version & r) { + return !(l > r); +} + +} // namespace version diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index e469cc380..7bc563104 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -26,43 +26,9 @@ class DummySettings { public: - std::string version{"poerp"}; - String locale = "en"; - uint8_t tx_mode = 1; - uint8_t ems_bus_id = 0x0B; - bool syslog_enabled = false; - int8_t syslog_level = 3; // uuid::log::Level - uint32_t syslog_mark_interval = 0; - String syslog_host = "192.168.1.4"; - uint16_t syslog_port = 514; - bool shower_timer = true; - bool shower_alert = false; - uint8_t shower_alert_coldshot = 10; - uint8_t shower_alert_trigger = 7; - bool hide_led = false; - bool notoken_api = false; - bool readonly_mode = false; - uint8_t bool_format = 1; // using "on" and "off" - uint8_t bool_dashboard = 1; - uint8_t enum_format = 1; - bool nosleep = false; - bool fahrenheit = false; - bool bandwidth20 = false; - bool telnet_enabled = false; - String board_profile = "CUSTOM"; - bool trace_raw = false; - bool analog_enabled = true; // analog is enabled - int8_t weblog_level = 1; - uint8_t weblog_buffer = 50; - bool weblog_compact = true; - uint8_t rx_gpio = 0; - uint8_t tx_gpio = 0; - uint8_t dallas_gpio = 16; // to ensure its enabled - bool dallas_parasite = false; - uint8_t led_gpio = 0; - bool low_clock = false; - uint8_t pbutton_gpio = false; - uint8_t solar_maxflow = 30; + // SYSTEM + bool bandwidth20 = false; + bool nosleep = false; // MQTT uint16_t publish_time = 10; @@ -107,13 +73,6 @@ class DummySettings { bool enableCORS = false; String CORSOrigin = "*"; - uint8_t phy_type = 0; - uint8_t eth_power = 0; // 0 means -1 - uint8_t eth_phy_addr = 0; - uint8_t eth_clock_mode = 0; - -#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 - static void read(DummySettings & settings, JsonObject & root){}; static void read(DummySettings & settings){}; @@ -188,4 +147,4 @@ class JsonUtils { } }; -#endif +#endif \ No newline at end of file diff --git a/mock-api/server.js b/mock-api/server.js index f94b8290e..fc6e7b7ce 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -260,7 +260,7 @@ const UPLOAD_FILE_ENDPOINT = REST_ENDPOINT_ROOT + 'uploadFile' const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn' const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken' const system_status = { - emsesp_version: '3.5.0demo', + emsesp_version: '3.5.0-demo', esp_platform: 'ESP32', max_alloc_heap: 89, psram_size: 0, diff --git a/pio_local.ini_example b/pio_local.ini_example index 6172ddb2c..4a173ecae 100644 --- a/pio_local.ini_example +++ b/pio_local.ini_example @@ -2,11 +2,12 @@ [common] ; custom build flags -; options are: EMSESP_DEBUG, EMSESP_UART_DEBUG, EMSESP_DEBUG_SENSOR, EMSESP_WIFI_TWEAK, EMSESP_DEFAULT_BOARD_PROFILE +; options are: EMSESP_DEBUG, EMSESP_UART_DEBUG, EMSESP_DEBUG_SENSOR, EMSESP_WIFI_TWEAK, EMSESP_DEFAULT_BOARD_PROFILE EMSESP_DEBUG_LIMITED ; my_build_flags = -DEMSESP_DEBUG -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\" ; my_build_flags = -DEMSESP_DEBUG_SENSOR ; my_build_flags = -DEMSESP_DEBUG -DEMSESP_USE_SERIAL ; my_build_flags = -DEMSESP_DEBUG -DCORE_DEBUG_LEVEL=5 ; 5=verbose, 4=debug, 3=info +my_build_flags = -DEMSESP_DEBUG -DEMSESP_DEBUG_LIMITED -DEMSESP_USE_SERIAL [env:esp32_4M] ; if using OTA enter your details below diff --git a/platformio.ini b/platformio.ini index 137262c87..987ca1252 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,13 +11,15 @@ extra_configs = [common] core_build_flags = - -Wall + ; -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -D CORE_DEBUG_LEVEL=0 -D NDEBUG -D ARDUINO_ARCH_ESP32=1 -D ESP32=1 + ; -Os -core_unbuild_flags = +; core_unbuild_flags = -std=gnu++11 +core_unbuild_flags = ; my_build_flags is set in pio_local.ini my_build_flags = @@ -42,7 +44,6 @@ monitor_raw = yes upload_speed = 921600 build_type = release lib_ldf_mode = chain+ -debug_build_flags = -Os # optimize for size check_tool = cppcheck, clangtidy check_severity = high, medium @@ -56,7 +57,8 @@ check_flags = extra_scripts = scripts/rename_fw.py board = esp32dev platform = espressif32 -board_build.partitions = esp32_partition_4M.csv +board_build.partitions = esp32_partition_4M.csv +board_build.filesystem = littlefs build_flags = ${common.build_flags} build_unflags = ${common.unbuild_flags} diff --git a/scripts/app-tls-size.py b/scripts/app-tls-size.py new file mode 100644 index 000000000..214e4a433 --- /dev/null +++ b/scripts/app-tls-size.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# app-tls-size - Calculate size of Thread-Local Storage +# Copyright 2022 Simon Arlott + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# PlatformIO usage: +# +# [env:...] +# extra_scripts = post:app-tls-size.py + +import argparse +import collections +import re +import subprocess +import sys + +RE_ELF_SECTION = re.compile(r"^\s*(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+") +Symbol = collections.namedtuple("Symbol", ["value", "size", "line"]) +RE_ELF_SYMBOL = re.compile(r"^(?P\s*(?P\w+):\s+)(?P\w+)(?P\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+))") + +def print_tls_size(fw_elf): + tls_offset = None + width = 8 + + lines = subprocess.run(["readelf", "-W", "--program-headers", fw_elf], + check=True, universal_newlines=True, stdout=subprocess.PIPE + ).stdout.strip().split("\n") + + for line in lines: + match = RE_ELF_SECTION.match(line) + if match: + if tls_offset is None and match["type"] == "TLS": + tls_offset = int(match["virtaddr"], 16) + + header = True + lines = subprocess.run(["readelf", "-W", "--syms", "--dyn-syms", fw_elf], + check=True, universal_newlines=True, stdout=subprocess.PIPE + ).stdout.strip().split("\n") + syms = set() + + for line in lines: + match = RE_ELF_SYMBOL.match(line) + if match: + header = False + + if match["type"] == "TLS": + syms.add(Symbol(int(match["value"], 16), int(match["size"]), line)) + width = len(match['value']) + elif tls_offset is not None and (match["type"] == "NOTYPE" and match["bind"] == "GLOBAL" + and match["visibility"] == "DEFAULT" + and match["name"] in set(["_thread_local_start", "_thread_local_end"]) + ): + value = int(match["value"], 16) - tls_offset + line = ("{1}{2:0{0}x}{3}").format(len(match['value']), + match["before_value"], value, match["after_value"]) + syms.add(Symbol(value, int(match["size"]), line)) + + elif header: + print(line) + + if syms: + syms = list(syms) + syms.sort() + size = (syms[-1].value + syms[-1].size) - syms[0].value + else: + size = 0 + + value = syms[0].value + for sym in syms: + if sym.value > value: + print("\t{1:0{0}x} {2:5d} TLS UNKNOWN".format(width, value, sym.value - value)) + print(sym.line) + value = sym.value + sym.size + + print() + print(f"Total Thread-Local Storage size: {size} bytes") + +def after_fw_elf(source, target, env): + fw_elf = str(target[0]) + print_tls_size(fw_elf) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Calculate size of Thread-Local Storage") + parser.add_argument("fw_elf", metavar="ELF", type=str, help="Firmware ELF filename") + + args = parser.parse_args() + print_tls_size(**vars(args)) +elif __name__ == "SCons.Script": + Import("env") + + env.AddPostAction("${BUILD_DIR}/${PROGNAME}.elf", after_fw_elf) + \ No newline at end of file diff --git a/src/console.cpp b/src/console.cpp index b9a3eaa46..a9486facf 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -59,12 +59,12 @@ void EMSESPShell::stopped() { // this is one of the first functions called when the shell is started void EMSESPShell::display_banner() { println(); - printfln("┌──────────────────────────────────────┐"); - printfln("│ %sEMS-ESP version %-10s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); - printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); - printfln("│ │"); - printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); - printfln("└──────────────────────────────────────┘"); + printfln("┌────────────────────────────────────────────┐"); + printfln("│ %sEMS-ESP version %-10s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); + printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); + printfln("│ │"); + printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); + printfln("└────────────────────────────────────────────┘"); println(); // set console name diff --git a/src/default_settings.h b/src/default_settings.h index d372efa38..230ae9c7f 100644 --- a/src/default_settings.h +++ b/src/default_settings.h @@ -21,14 +21,14 @@ // GENERAL SETTINGS -#ifndef EMSESP_STANDALONE -#define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English -#else -// this is for debugging different languages in standalone version -// #define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_DE // German +#ifndef EMSESP_DEFAULT_LOCALE #define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English #endif +#ifndef EMSESP_DEFAULT_VERSION +#define EMSESP_DEFAULT_VERSION "" +#endif + #ifndef EMSESP_DEFAULT_TX_MODE #define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0 #endif @@ -228,5 +228,14 @@ enum { }; +#if CONFIG_IDF_TARGET_ESP32C3 +#define EMSESP_PLATFORM "ESP32-C3"; +#elif CONFIG_IDF_TARGET_ESP32S2 +#define EMSESP_PLATFORM "ESP32-S2"; +#elif CONFIG_IDF_TARGET_ESP32 || EMSESP_STANDALONE +#define EMSESP_PLATFORM "ESP32"; +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif #endif diff --git a/src/emsesp.cpp b/src/emsesp.cpp index fb55c17e9..d4cc7a02f 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1297,6 +1297,21 @@ void EMSESP::start() { } #endif +// do a quick scan of the filesystem to see if we have a /config folder +// so we know if this is a new install or not +#ifndef EMSESP_STANDALONE + File root = LittleFS.open("/config"); + bool factory_settings = !root; + if (!root) { +#ifdef EMSESP_DEBUG + Serial.println("No config found, assuming factory settings"); +#endif + } + root.close(); +#else + bool factory_settings = false; +#endif + esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc) webLogService.begin(); // start web log service. now we can start capturing logs to the web log @@ -1315,12 +1330,13 @@ void EMSESP::start() { webSettingsService.begin(); // load EMS-ESP Application settings... // do any system upgrades - if (system_.check_upgrade()) { + if (system_.check_upgrade(factory_settings)) { LOG_WARNING("System needs a restart to apply new settings. Please wait."); system_.system_restart(); }; - system_.reload_settings(); // ... and store some of the settings locally + system_.reload_settings(); // ... and store some of the settings locally + webCustomizationService.begin(); // load the customizations // start telnet service if it's enabled diff --git a/src/mqtt.cpp b/src/mqtt.cpp index c034f7a74..76a87da0e 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -192,6 +192,7 @@ void Mqtt::loop() { // print MQTT log and other stuff to console void Mqtt::show_mqtt(uuid::console::Shell & shell) { shell.printfln("MQTT is %s", connected() ? F_(connected) : F_(disconnected)); + shell.printfln("MQTT Entity ID format is %d", entity_format_); shell.printfln("MQTT publish errors: %lu", mqtt_publish_fails_); shell.println(); @@ -669,12 +670,12 @@ std::shared_ptr Mqtt::queue_message(const uint8_t operation, #if defined(EMSESP_DEBUG) if (operation == Operation::PUBLISH) { if (message->payload.empty()) { - LOG_INFO("[DEBUG] Adding to queue: (Publish) topic='%s' empty payload", message->topic.c_str()); + LOG_INFO("[DEBUG] Adding to queue: (publish) topic='%s' empty payload", message->topic.c_str()); } else { - LOG_INFO("[DEBUG] Adding to queue: (Publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str()); + LOG_INFO("[DEBUG] Adding to queue: (publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str()); } } else { - LOG_INFO("[DEBUG] Adding to queue: (Subscribe) topic='%s'", message->topic.c_str()); + LOG_INFO("[DEBUG] Adding to queue: (subscribe) topic='%s'", message->topic.c_str()); } #endif @@ -986,7 +987,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev } // build unique identifier also used as object_id and becomes the Entity ID in HA - char uniq_id[70]; + char uniq_id[80]; if (Mqtt::entity_format() == 2) { // prefix base name to each uniq_id and use the shortname snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", mqtt_basename_.c_str(), device_name, entity_with_tag); @@ -997,7 +998,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // entity_format is 0 // old v3.4 style // take en_name and replace all spaces and lowercase it - char uniq_s[40]; + char uniq_s[60]; strlcpy(uniq_s, en_name, sizeof(uniq_s)); Helpers::replace_char(uniq_s, ' ', '_'); if (EMSdevice::tag_to_string(tag).empty()) { diff --git a/src/system.cpp b/src/system.cpp index ce7509f22..e48ca2618 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -19,6 +19,8 @@ #include "system.h" #include "emsesp.h" // for send_raw_telegram() command +#include + #if defined(EMSESP_DEBUG) #include "test/test.h" #endif @@ -218,7 +220,7 @@ void System::wifi_reconnect() { void System::format(uuid::console::Shell & shell) { auto msg = ("Formatting file system. This will reset all settings to their defaults"); shell.logger().warning(msg); - shell.flush(); + // shell.flush(); EMSuart::stop(); @@ -1013,45 +1015,74 @@ bool System::check_restore() { } // handle upgrades from previous versions +// this function will not be called on a clean install, with no settings files yet created // returns true if we need a reboot -bool System::check_upgrade() { - std::string old_version; +bool System::check_upgrade(bool factory_settings) { + bool missing_version = true; + std::string settingsVersion{EMSESP_APP_VERSION}; // default setting version - // TODO fix + if (!factory_settings) { + // fetch current version from settings file + EMSESP::webSettingsService.read([&](WebSettings & settings) { settingsVersion = settings.version.c_str(); }); - if (version_ != EMSESP_APP_VERSION) { - if (version_.empty()) { - LOG_DEBUG("No version, presuming fresh install. Setting to %s", EMSESP_APP_VERSION); - old_version = EMSESP_APP_VERSION; - } else { - LOG_DEBUG("Going from version %s to %s", version_, EMSESP_APP_VERSION); - old_version = version_; + // see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022 + missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5)); + if (missing_version) { +#ifdef EMSESP_DEBUG + LOG_DEBUG("No version information found (%s)", settingsVersion.c_str()); +#endif + settingsVersion = "3.4.4"; // this was the last stable version } - // save the new version - version_ = EMSESP_APP_VERSION; - EMSESP::webSettingsService.update( - [&](WebSettings & settings) { - settings.version = EMSESP_APP_VERSION; - return StateUpdateResult::CHANGED; - }, - "local"); } - if (old_version == EMSESP_APP_VERSION) { - return false; // no upgrades or reboot needed. we're on the latest + version::Semver200_version settings_version(settingsVersion); + + if (!missing_version) { + LOG_INFO("Current version from settings is %d.%d.%d-%s", + settings_version.major(), + settings_version.minor(), + settings_version.patch(), + settings_version.prerelease().c_str()); } - LOG_DEBUG("Doing upgrade..."); // TODO remove + // always save the new version to the settings + EMSESP::webSettingsService.update( + [&](WebSettings & settings) { + settings.version = EMSESP_APP_VERSION; + return StateUpdateResult::CHANGED; + }, + "local"); + if (factory_settings) { + return false; // fresh install, do nothing + } - // check variations between versions - // get major version - - // get minor version - - // get patch version (ignore alphanumerics) + version::Semver200_version this_version(EMSESP_APP_VERSION); + // compare versions bool reboot_required = false; + if (this_version > settings_version) { + LOG_NOTICE("Upgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); + + // if we're coming from 3.4.4 or 3.5.0b14 then we need to apply new settings + if (missing_version) { +#ifdef EMSESP_DEBUG + LOG_DEBUG("Setting MQTT ID Entity to v3.4 format"); +#endif + EMSESP::esp8266React.getMqttSettingsService()->update( + [&](MqttSettings & mqttSettings) { + mqttSettings.entity_format = 0; // use old Entity ID format from v3.4 + return StateUpdateResult::CHANGED; + }, + "local"); + } + + } else if (this_version < settings_version) { + LOG_NOTICE("Downgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); + } else { + // same version, do nothing + return false; + } return reboot_required; } @@ -1299,18 +1330,17 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp node["pbutton gpio"] = settings.pbutton_gpio; node["led gpio"] = settings.led_gpio; } - node["hide led"] = settings.hide_led; - node["notoken api"] = settings.notoken_api; - node["readonly mode"] = settings.readonly_mode; - node["fahrenheit"] = settings.fahrenheit; - node["dallas parasite"] = settings.dallas_parasite; - node["bool format"] = settings.bool_format; - node["bool dashboard"] = settings.bool_dashboard; - node["enum format"] = settings.enum_format; - node["analog enabled"] = settings.analog_enabled; - node["telnet enabled"] = settings.telnet_enabled; - node["max web log buffer"] = settings.weblog_buffer; - node["web log buffered"] = EMSESP::webLogService.num_log_messages(); + node["hide led"] = settings.hide_led; + node["notoken api"] = settings.notoken_api; + node["readonly mode"] = settings.readonly_mode; + node["fahrenheit"] = settings.fahrenheit; + node["dallas parasite"] = settings.dallas_parasite; + node["bool format"] = settings.bool_format; + node["bool dashboard"] = settings.bool_dashboard; + node["enum format"] = settings.enum_format; + node["analog enabled"] = settings.analog_enabled; + node["telnet enabled"] = settings.telnet_enabled; + node["web log buffer"] = settings.weblog_buffer; }); // Devices - show EMS devices if we have any diff --git a/src/system.h b/src/system.h index 789de9018..5b98edd1f 100644 --- a/src/system.h +++ b/src/system.h @@ -74,7 +74,7 @@ class System { void reload_settings(); void wifi_tweak(); void syslog_init(); - bool check_upgrade(); + bool check_upgrade(bool factory_settings); bool check_restore(); bool heartbeat_json(JsonObject & output); void send_heartbeat(); @@ -273,7 +273,7 @@ class System { // EMS-ESP settings // copies from WebSettings class in WebSettingsService.h and loaded with reload_settings() - std::string hostname_ = FACTORY_WIFI_HOSTNAME; + std::string hostname_; String locale_; bool hide_led_; uint8_t led_gpio_; @@ -295,7 +295,7 @@ class System { uint8_t bool_format_; uint8_t enum_format_; bool readonly_mode_; - std::string version_; + String version_; // ethernet uint8_t phy_type_; diff --git a/src/test/test.cpp b/src/test/test.cpp index d4ab3fe8e..1e6452263 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -29,6 +29,7 @@ bool Test::run_test(const char * command, int8_t id) { return false; } + if (strcmp(command, "general") == 0) { EMSESP::logger().info("Testing general. Adding a Boiler and Thermostat"); @@ -53,6 +54,8 @@ bool Test::run_test(const char * command, int8_t id) { return true; } +#ifndef EMSESP_DEBUG_LIMITED + if (strcmp(command, "2thermostats") == 0) { EMSESP::logger().info("Testing with multiple thermostats..."); @@ -216,6 +219,8 @@ bool Test::run_test(const char * command, int8_t id) { return true; } +#endif + return false; } @@ -231,6 +236,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw std::string command(20, '\0'); + +#ifndef EMSESP_DEBUG_LIMITED + if ((cmd.empty()) || (cmd == "default")) { command = EMSESP_DEBUG_DEFAULT; } else { @@ -1548,6 +1556,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const #pragma GCC diagnostic pop } + +#endif + + if (command == "limited") { + shell.printfln("Run a limited memory test..."); + + run_test("general"); + } } // simulates a telegram in the Rx queue, but without the CRC which is added automatically diff --git a/src/version.h b/src/version.h index e37ede30a..68e089c4f 100644 --- a/src/version.h +++ b/src/version.h @@ -1,11 +1 @@ -#define EMSESP_APP_VERSION "3.5.0b13" - -#if CONFIG_IDF_TARGET_ESP32C3 -#define EMSESP_PLATFORM "ESP32-C3"; -#elif CONFIG_IDF_TARGET_ESP32S2 -#define EMSESP_PLATFORM "ESP32-S2"; -#elif CONFIG_IDF_TARGET_ESP32 || EMSESP_STANDALONE -#define EMSESP_PLATFORM "ESP32"; -#else -#error Target CONFIG_IDF_TARGET is not supported -#endif +#define EMSESP_APP_VERSION "3.5.0-dev.14" diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 5923a0a97..e9c49d434 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -37,6 +37,7 @@ WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, Securit } void WebSettings::read(WebSettings & settings, JsonObject & root) { + root["version"] = settings.version; root["locale"] = settings.locale; root["tx_mode"] = settings.tx_mode; root["ems_bus_id"] = settings.ems_bus_id; @@ -82,7 +83,7 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) { StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) { // load the version of the settings // will be picked up in System::check_upgrade() - settings.version = root["version"] || ""; + settings.version = root["version"] | EMSESP_DEFAULT_VERSION; // load default GPIO configuration based on board profile std::vector data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index 2addbb4cb..5a08207bc 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -29,41 +29,41 @@ namespace emsesp { class WebSettings { public: - std::string version; - String locale; - uint8_t tx_mode; - uint8_t ems_bus_id; - bool shower_timer; - bool shower_alert; - uint8_t shower_alert_trigger; - uint8_t shower_alert_coldshot; - bool syslog_enabled; - int8_t syslog_level; // uuid::log::Level - uint32_t syslog_mark_interval; - String syslog_host; - uint16_t syslog_port; - bool trace_raw; - uint8_t rx_gpio; - uint8_t tx_gpio; - uint8_t dallas_gpio; - bool dallas_parasite; - uint8_t led_gpio; - bool hide_led; - bool low_clock; - bool telnet_enabled; - bool notoken_api; - bool readonly_mode; - bool analog_enabled; - uint8_t pbutton_gpio; - uint8_t solar_maxflow; - String board_profile; - uint8_t bool_format; - uint8_t bool_dashboard; - uint8_t enum_format; - int8_t weblog_level; - uint8_t weblog_buffer; - bool weblog_compact; - bool fahrenheit; + String version; + String locale; + uint8_t tx_mode; + uint8_t ems_bus_id; + bool shower_timer; + bool shower_alert; + uint8_t shower_alert_trigger; + uint8_t shower_alert_coldshot; + bool syslog_enabled; + int8_t syslog_level; // uuid::log::Level + uint32_t syslog_mark_interval; + String syslog_host; + uint16_t syslog_port; + bool trace_raw; + uint8_t rx_gpio; + uint8_t tx_gpio; + uint8_t dallas_gpio; + bool dallas_parasite; + uint8_t led_gpio; + bool hide_led; + bool low_clock; + bool telnet_enabled; + bool notoken_api; + bool readonly_mode; + bool analog_enabled; + uint8_t pbutton_gpio; + uint8_t solar_maxflow; + String board_profile; + uint8_t bool_format; + uint8_t bool_dashboard; + uint8_t enum_format; + int8_t weblog_level; + uint8_t weblog_buffer; + bool weblog_compact; + bool fahrenheit; uint8_t phy_type; int8_t eth_power; // -1 means disabled