Merge pull request #840 from proddy/dev

added version upgrade/downgrade #832
This commit is contained in:
Proddy
2022-12-26 15:07:55 +01:00
committed by GitHub
35 changed files with 980 additions and 166 deletions

View File

@@ -1,7 +1,7 @@
# #
# GNUMakefile for EMS-ESP # GNUMakefile for EMS-ESP
# (c) 2020 Paul Derbyshire
# #
NUMJOBS=${NUMJOBS:-" -j4 "} NUMJOBS=${NUMJOBS:-" -j4 "}
MAKEFLAGS+="j " MAKEFLAGS+="j "
#---------------------------------------------------------------------- #----------------------------------------------------------------------
@@ -17,23 +17,28 @@ MAKEFLAGS+="j "
#TARGET := $(notdir $(CURDIR)) #TARGET := $(notdir $(CURDIR))
TARGET := emsesp TARGET := emsesp
BUILD := build 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 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/* src/devices 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 := LIBRARIES :=
CPPCHECK = cppcheck CPPCHECK = cppcheck
# CHECKFLAGS = -q --force --std=c++17
CHECKFLAGS = -q --force --std=c++11 CHECKFLAGS = -q --force --std=c++11
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Languages Standard # Languages Standard
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# C_STANDARD := -std=c17
# CXX_STANDARD := -std=c++17
C_STANDARD := -std=c11 C_STANDARD := -std=c11
CXX_STANDARD := -std=c++11 CXX_STANDARD := -std=c++11
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Defined Symbols # 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 # Sources & Files
@@ -66,7 +71,7 @@ CXX := /usr/bin/g++
# CXXFLAGS C++ Compiler Flags # CXXFLAGS C++ Compiler Flags
# LDFLAGS Linker Flags # LDFLAGS Linker Flags
#---------------------------------------------------------------------- #----------------------------------------------------------------------
CPPFLAGS += $(DEFINES) $(INCLUDE) CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
CPPFLAGS += -ggdb CPPFLAGS += -ggdb
CPPFLAGS += -g3 CPPFLAGS += -g3
CPPFLAGS += -Os CPPFLAGS += -Os
@@ -114,6 +119,8 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
# Targets # Targets
#---------------------------------------------------------------------- #----------------------------------------------------------------------
.PHONY: all .PHONY: all
.SILENT: $(OUTPUT)
all: $(OUTPUT) all: $(OUTPUT)
$(OUTPUT): $(OBJS) $(OUTPUT): $(OBJS)

View File

@@ -41,7 +41,6 @@ class FSPersistence {
Serial.println(); Serial.println();
#endif #endif
#endif #endif
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close(); settingsFile.close();
return; return;
@@ -96,6 +95,7 @@ class FSPersistence {
// debug added by Proddy // debug added by Proddy
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
#if defined(EMSESP_USE_SERIAL) #if defined(EMSESP_USE_SERIAL)
Serial.println();
Serial.printf("Writing to file: %s: ", _filePath); Serial.printf("Writing to file: %s: ", _filePath);
serializeJson(jsonDocument, Serial); serializeJson(jsonDocument, Serial);
Serial.println(); Serial.println();

22
lib/semver/LICENSE Normal file
View File

@@ -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.

7
lib/semver/README.md Normal file
View File

@@ -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

View File

@@ -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 <algorithm>
#include <functional>
#include <map>
#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<Id_type, Id_type>;
using Prerel_id_comparator = function<int(const string &, const string &)>;
const map<Prerel_type_pair, Prerel_id_comparator> 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

View File

@@ -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 <functional>
#include <map>
#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<void(const string &, const char)>;
using State_transition_hook = function<void(string &)>;
/// 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<const char, Parser_state, State_transition_hook>;
using Transitions = vector<Transition>;
using State = tuple<Transitions, string &, Validator>;
using State_machine = std::map<Parser_state, State>;
// Ranges of characters allowed in prerelease and build identifiers.
const vector<pair<char, char>> 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

53
lib/semver/semver200.h Normal file
View File

@@ -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<Semver200_parser, Semver200_comparator> {
public:
Semver200_version()
: Basic_version{Semver200_parser(), Semver200_comparator()} {
}
Semver200_version(const std::string & v)
: Basic_version{v, Semver200_parser(), Semver200_comparator()} {
}
};
} // namespace version

149
lib/semver/version.h Normal file
View File

@@ -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 <string>
#include <vector>
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<std::string, Id_type>;
/// Container for all prerelease identifiers for a given version string.
using Prerelease_identifiers = std::vector<Prerelease_identifier>;
/// 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<Build_identifier>;
/// 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 <typename Parser, typename Comparator>
class Basic_version;
/// Test if left-hand version operand is of lower precedence than the right-hand version.
template <typename Parser, typename Comparator>
bool operator<(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
/// Test if left-hand version operand if of equal precedence as the right-hand version.
template <typename Parser, typename Comparator>
bool operator==(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
/// Test if left-hand version and right-hand version are of different precedence.
template <typename Parser, typename Comparator>
bool operator!=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
/// Test if left-hand version operand is of higher precedence than the right-hand version.
template <typename Parser, typename Comparator>
bool operator>(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
/// Test if left-hand version operand is of higher or equal precedence as the right-hand version.
template <typename Parser, typename Comparator>
bool operator>=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
/// Test if left-hand version operand is of lower or equal precedence as the right-hand version.
template <typename Parser, typename Comparator>
bool operator<=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
/// 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 <typename Parser, typename Comparator>
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"

132
lib/semver/version.inl Normal file
View File

@@ -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 <typename T, typename F>
std::string & splice(std::string & ss, const std::vector<T> & 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 <typename Parser, typename Comparator>
Basic_version<Parser, Comparator>::Basic_version(Parser p, Comparator c)
: parser_(p)
, comparator_(c)
, ver_(parser_.parse("0.0.0")) {
}
template <typename Parser, typename Comparator>
Basic_version<Parser, Comparator>::Basic_version(const std::string & v, Parser p, Comparator c)
: parser_(p)
, comparator_(c)
, ver_(parser_.parse(v)) {
}
template <typename Parser, typename Comparator>
Basic_version<Parser, Comparator>::Basic_version(const Version_data & v, Parser p, Comparator c)
: parser_(p)
, comparator_(c)
, ver_(v) {
}
template <typename Parser, typename Comparator>
Basic_version<Parser, Comparator>::Basic_version(const Basic_version<Parser, Comparator> &) = default;
template <typename Parser, typename Comparator>
Basic_version<Parser, Comparator> & Basic_version<Parser, Comparator>::operator=(const Basic_version<Parser, Comparator> &) = default;
template <typename Parser, typename Comparator>
int Basic_version<Parser, Comparator>::major() const {
return ver_.major;
}
template <typename Parser, typename Comparator>
int Basic_version<Parser, Comparator>::minor() const {
return ver_.minor;
}
template <typename Parser, typename Comparator>
int Basic_version<Parser, Comparator>::patch() const {
return ver_.patch;
}
template <typename Parser, typename Comparator>
const std::string Basic_version<Parser, Comparator>::prerelease() const {
std::string ss;
return splice(ss, ver_.prerelease_ids, ".", [](const Prerelease_identifier & id) { return id.first; });
}
template <typename Parser, typename Comparator>
const std::string Basic_version<Parser, Comparator>::build() const {
std::string ss;
return splice(ss, ver_.build_ids, ".", [](const std::string & id) { return id; });
}
template <typename Parser, typename Comparator>
bool operator<(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
return l.comparator_.compare(l.ver_, r.ver_) == -1;
}
template <typename Parser, typename Comparator>
bool operator==(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
return l.comparator_.compare(l.ver_, r.ver_) == 0;
}
template <typename Parser, typename Comparator>
inline bool operator!=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
return !(l == r);
}
template <typename Parser, typename Comparator>
inline bool operator>(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
return r < l;
}
template <typename Parser, typename Comparator>
inline bool operator>=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
return !(l < r);
}
template <typename Parser, typename Comparator>
inline bool operator<=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
return !(l > r);
}
} // namespace version

View File

@@ -26,43 +26,9 @@
class DummySettings { class DummySettings {
public: public:
std::string version{"poerp"}; // SYSTEM
String locale = "en"; bool bandwidth20 = false;
uint8_t tx_mode = 1; bool nosleep = false;
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;
// MQTT // MQTT
uint16_t publish_time = 10; uint16_t publish_time = 10;
@@ -107,13 +73,6 @@ class DummySettings {
bool enableCORS = false; bool enableCORS = false;
String CORSOrigin = "*"; 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, JsonObject & root){};
static void read(DummySettings & settings){}; static void read(DummySettings & settings){};

View File

@@ -260,7 +260,7 @@ const UPLOAD_FILE_ENDPOINT = REST_ENDPOINT_ROOT + 'uploadFile'
const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn' const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn'
const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken' const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken'
const system_status = { const system_status = {
emsesp_version: '3.5.0demo', emsesp_version: '3.5.0-demo',
esp_platform: 'ESP32', esp_platform: 'ESP32',
max_alloc_heap: 89, max_alloc_heap: 89,
psram_size: 0, psram_size: 0,

View File

@@ -2,11 +2,12 @@
[common] [common]
; custom build flags ; 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 -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\"
; my_build_flags = -DEMSESP_DEBUG_SENSOR ; my_build_flags = -DEMSESP_DEBUG_SENSOR
; my_build_flags = -DEMSESP_DEBUG -DEMSESP_USE_SERIAL ; 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 -DCORE_DEBUG_LEVEL=5 ; 5=verbose, 4=debug, 3=info
my_build_flags = -DEMSESP_DEBUG -DEMSESP_DEBUG_LIMITED -DEMSESP_USE_SERIAL
[env:esp32_4M] [env:esp32_4M]
; if using OTA enter your details below ; if using OTA enter your details below

View File

@@ -11,12 +11,14 @@ extra_configs =
[common] [common]
core_build_flags = core_build_flags =
-Wall ; -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
-D NDEBUG -D NDEBUG
-D ARDUINO_ARCH_ESP32=1 -D ARDUINO_ARCH_ESP32=1
-D ESP32=1 -D ESP32=1
; -Os
; core_unbuild_flags = -std=gnu++11
core_unbuild_flags = core_unbuild_flags =
; my_build_flags is set in pio_local.ini ; my_build_flags is set in pio_local.ini
@@ -42,7 +44,6 @@ monitor_raw = yes
upload_speed = 921600 upload_speed = 921600
build_type = release build_type = release
lib_ldf_mode = chain+ lib_ldf_mode = chain+
debug_build_flags = -Os # optimize for size
check_tool = cppcheck, clangtidy check_tool = cppcheck, clangtidy
check_severity = high, medium check_severity = high, medium
@@ -57,6 +58,7 @@ extra_scripts = scripts/rename_fw.py
board = esp32dev board = esp32dev
platform = espressif32 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_flags = ${common.build_flags}
build_unflags = ${common.unbuild_flags} build_unflags = ${common.unbuild_flags}

104
scripts/app-tls-size.py Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
# 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<type>\w+)\s+(?P<offset>\w+)\s+(?P<virtaddr>\w+)\s+(?P<physaddr>\w+)\s+(?P<filesiz>\w+)\s+(?P<memsiz>\w+)\s+(?P<ndx>\w+)\s+")
Symbol = collections.namedtuple("Symbol", ["value", "size", "line"])
RE_ELF_SYMBOL = re.compile(r"^(?P<before_value>\s*(?P<num>\w+):\s+)(?P<value>\w+)(?P<after_value>\s+(?P<size>\w+)\s+(?P<type>\w+)\s+(?P<bind>\w+)\s+(?P<visibility>\w+)\s+(?P<ndx>\w+)\s+(?P<name>\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)

View File

@@ -59,12 +59,12 @@ void EMSESPShell::stopped() {
// this is one of the first functions called when the shell is started // this is one of the first functions called when the shell is started
void EMSESPShell::display_banner() { void EMSESPShell::display_banner() {
println(); println();
printfln("┌──────────────────────────────────────┐"); printfln("┌────────────────────────────────────────────");
printfln("│ %sEMS-ESP version %-10s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); 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("│ %s%shttps://github.com/emsesp/EMS-ESP32%s ", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET);
printfln("│ │"); printfln(" ");
printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); printfln("│ type %shelp%s to show available commands ", COLOR_UNDERLINE, COLOR_RESET);
printfln("└──────────────────────────────────────┘"); printfln("└────────────────────────────────────────────");
println(); println();
// set console name // set console name

View File

@@ -21,14 +21,14 @@
// GENERAL SETTINGS // GENERAL SETTINGS
#ifndef EMSESP_STANDALONE #ifndef EMSESP_DEFAULT_LOCALE
#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
#define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English #define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English
#endif #endif
#ifndef EMSESP_DEFAULT_VERSION
#define EMSESP_DEFAULT_VERSION ""
#endif
#ifndef EMSESP_DEFAULT_TX_MODE #ifndef EMSESP_DEFAULT_TX_MODE
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0 #define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#endif #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 #endif

View File

@@ -1297,6 +1297,21 @@ void EMSESP::start() {
} }
#endif #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) 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 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... webSettingsService.begin(); // load EMS-ESP Application settings...
// do any system upgrades // 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."); LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart(); 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 webCustomizationService.begin(); // load the customizations
// start telnet service if it's enabled // start telnet service if it's enabled

View File

@@ -192,6 +192,7 @@ void Mqtt::loop() {
// print MQTT log and other stuff to console // print MQTT log and other stuff to console
void Mqtt::show_mqtt(uuid::console::Shell & shell) { void Mqtt::show_mqtt(uuid::console::Shell & shell) {
shell.printfln("MQTT is %s", connected() ? F_(connected) : F_(disconnected)); 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.printfln("MQTT publish errors: %lu", mqtt_publish_fails_);
shell.println(); shell.println();
@@ -669,12 +670,12 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
if (operation == Operation::PUBLISH) { if (operation == Operation::PUBLISH) {
if (message->payload.empty()) { 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 { } 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 { } 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 #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 // 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) { if (Mqtt::entity_format() == 2) {
// prefix base name to each uniq_id and use the shortname // 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); 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 // entity_format is 0
// old v3.4 style // old v3.4 style
// take en_name and replace all spaces and lowercase it // 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)); strlcpy(uniq_s, en_name, sizeof(uniq_s));
Helpers::replace_char(uniq_s, ' ', '_'); Helpers::replace_char(uniq_s, ' ', '_');
if (EMSdevice::tag_to_string(tag).empty()) { if (EMSdevice::tag_to_string(tag).empty()) {

View File

@@ -19,6 +19,8 @@
#include "system.h" #include "system.h"
#include "emsesp.h" // for send_raw_telegram() command #include "emsesp.h" // for send_raw_telegram() command
#include <semver200.h>
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
#include "test/test.h" #include "test/test.h"
#endif #endif
@@ -218,7 +220,7 @@ void System::wifi_reconnect() {
void System::format(uuid::console::Shell & shell) { void System::format(uuid::console::Shell & shell) {
auto msg = ("Formatting file system. This will reset all settings to their defaults"); auto msg = ("Formatting file system. This will reset all settings to their defaults");
shell.logger().warning(msg); shell.logger().warning(msg);
shell.flush(); // shell.flush();
EMSuart::stop(); EMSuart::stop();
@@ -1013,45 +1015,74 @@ bool System::check_restore() {
} }
// handle upgrades from previous versions // 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 // returns true if we need a reboot
bool System::check_upgrade() { bool System::check_upgrade(bool factory_settings) {
std::string old_version; 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) { // see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022
if (version_.empty()) { missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5));
LOG_DEBUG("No version, presuming fresh install. Setting to %s", EMSESP_APP_VERSION); if (missing_version) {
old_version = EMSESP_APP_VERSION; #ifdef EMSESP_DEBUG
} else { LOG_DEBUG("No version information found (%s)", settingsVersion.c_str());
LOG_DEBUG("Going from version %s to %s", version_, EMSESP_APP_VERSION); #endif
old_version = version_; 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) { version::Semver200_version settings_version(settingsVersion);
return false; // no upgrades or reboot needed. we're on the latest
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 version::Semver200_version this_version(EMSESP_APP_VERSION);
// get major version
// get minor version
// get patch version (ignore alphanumerics)
// compare versions
bool reboot_required = false; 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; 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["pbutton gpio"] = settings.pbutton_gpio;
node["led gpio"] = settings.led_gpio; node["led gpio"] = settings.led_gpio;
} }
node["hide led"] = settings.hide_led; node["hide led"] = settings.hide_led;
node["notoken api"] = settings.notoken_api; node["notoken api"] = settings.notoken_api;
node["readonly mode"] = settings.readonly_mode; node["readonly mode"] = settings.readonly_mode;
node["fahrenheit"] = settings.fahrenheit; node["fahrenheit"] = settings.fahrenheit;
node["dallas parasite"] = settings.dallas_parasite; node["dallas parasite"] = settings.dallas_parasite;
node["bool format"] = settings.bool_format; node["bool format"] = settings.bool_format;
node["bool dashboard"] = settings.bool_dashboard; node["bool dashboard"] = settings.bool_dashboard;
node["enum format"] = settings.enum_format; node["enum format"] = settings.enum_format;
node["analog enabled"] = settings.analog_enabled; node["analog enabled"] = settings.analog_enabled;
node["telnet enabled"] = settings.telnet_enabled; node["telnet enabled"] = settings.telnet_enabled;
node["max web log buffer"] = settings.weblog_buffer; node["web log buffer"] = settings.weblog_buffer;
node["web log buffered"] = EMSESP::webLogService.num_log_messages();
}); });
// Devices - show EMS devices if we have any // Devices - show EMS devices if we have any

View File

@@ -74,7 +74,7 @@ class System {
void reload_settings(); void reload_settings();
void wifi_tweak(); void wifi_tweak();
void syslog_init(); void syslog_init();
bool check_upgrade(); bool check_upgrade(bool factory_settings);
bool check_restore(); bool check_restore();
bool heartbeat_json(JsonObject & output); bool heartbeat_json(JsonObject & output);
void send_heartbeat(); void send_heartbeat();
@@ -273,7 +273,7 @@ class System {
// EMS-ESP settings // EMS-ESP settings
// copies from WebSettings class in WebSettingsService.h and loaded with reload_settings() // copies from WebSettings class in WebSettingsService.h and loaded with reload_settings()
std::string hostname_ = FACTORY_WIFI_HOSTNAME; std::string hostname_;
String locale_; String locale_;
bool hide_led_; bool hide_led_;
uint8_t led_gpio_; uint8_t led_gpio_;
@@ -295,7 +295,7 @@ class System {
uint8_t bool_format_; uint8_t bool_format_;
uint8_t enum_format_; uint8_t enum_format_;
bool readonly_mode_; bool readonly_mode_;
std::string version_; String version_;
// ethernet // ethernet
uint8_t phy_type_; uint8_t phy_type_;

View File

@@ -29,6 +29,7 @@ bool Test::run_test(const char * command, int8_t id) {
return false; return false;
} }
if (strcmp(command, "general") == 0) { if (strcmp(command, "general") == 0) {
EMSESP::logger().info("Testing general. Adding a Boiler and Thermostat"); 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; return true;
} }
#ifndef EMSESP_DEBUG_LIMITED
if (strcmp(command, "2thermostats") == 0) { if (strcmp(command, "2thermostats") == 0) {
EMSESP::logger().info("Testing with multiple thermostats..."); EMSESP::logger().info("Testing with multiple thermostats...");
@@ -216,6 +219,8 @@ bool Test::run_test(const char * command, int8_t id) {
return true; return true;
} }
#endif
return false; 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 EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
std::string command(20, '\0'); std::string command(20, '\0');
#ifndef EMSESP_DEBUG_LIMITED
if ((cmd.empty()) || (cmd == "default")) { if ((cmd.empty()) || (cmd == "default")) {
command = EMSESP_DEBUG_DEFAULT; command = EMSESP_DEBUG_DEFAULT;
} else { } else {
@@ -1548,6 +1556,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
#pragma GCC diagnostic pop #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 // simulates a telegram in the Rx queue, but without the CRC which is added automatically

View File

@@ -1,11 +1 @@
#define EMSESP_APP_VERSION "3.5.0b13" #define EMSESP_APP_VERSION "3.5.0-dev.14"
#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

View File

@@ -37,6 +37,7 @@ WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, Securit
} }
void WebSettings::read(WebSettings & settings, JsonObject & root) { void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["version"] = settings.version;
root["locale"] = settings.locale; root["locale"] = settings.locale;
root["tx_mode"] = settings.tx_mode; root["tx_mode"] = settings.tx_mode;
root["ems_bus_id"] = settings.ems_bus_id; 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) { StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
// load the version of the settings // load the version of the settings
// will be picked up in System::check_upgrade() // 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 // load default GPIO configuration based on board profile
std::vector<int8_t> data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode std::vector<int8_t> data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode

View File

@@ -29,41 +29,41 @@ namespace emsesp {
class WebSettings { class WebSettings {
public: public:
std::string version; String version;
String locale; String locale;
uint8_t tx_mode; uint8_t tx_mode;
uint8_t ems_bus_id; uint8_t ems_bus_id;
bool shower_timer; bool shower_timer;
bool shower_alert; bool shower_alert;
uint8_t shower_alert_trigger; uint8_t shower_alert_trigger;
uint8_t shower_alert_coldshot; uint8_t shower_alert_coldshot;
bool syslog_enabled; bool syslog_enabled;
int8_t syslog_level; // uuid::log::Level int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval; uint32_t syslog_mark_interval;
String syslog_host; String syslog_host;
uint16_t syslog_port; uint16_t syslog_port;
bool trace_raw; bool trace_raw;
uint8_t rx_gpio; uint8_t rx_gpio;
uint8_t tx_gpio; uint8_t tx_gpio;
uint8_t dallas_gpio; uint8_t dallas_gpio;
bool dallas_parasite; bool dallas_parasite;
uint8_t led_gpio; uint8_t led_gpio;
bool hide_led; bool hide_led;
bool low_clock; bool low_clock;
bool telnet_enabled; bool telnet_enabled;
bool notoken_api; bool notoken_api;
bool readonly_mode; bool readonly_mode;
bool analog_enabled; bool analog_enabled;
uint8_t pbutton_gpio; uint8_t pbutton_gpio;
uint8_t solar_maxflow; uint8_t solar_maxflow;
String board_profile; String board_profile;
uint8_t bool_format; uint8_t bool_format;
uint8_t bool_dashboard; uint8_t bool_dashboard;
uint8_t enum_format; uint8_t enum_format;
int8_t weblog_level; int8_t weblog_level;
uint8_t weblog_buffer; uint8_t weblog_buffer;
bool weblog_compact; bool weblog_compact;
bool fahrenheit; bool fahrenheit;
uint8_t phy_type; uint8_t phy_type;
int8_t eth_power; // -1 means disabled int8_t eth_power; // -1 means disabled