diff --git a/code/espurna/board.cpp b/code/espurna/board.cpp index 7e0d3ebf..4d10ccc6 100644 --- a/code/espurna/board.cpp +++ b/code/espurna/board.cpp @@ -394,12 +394,14 @@ void boardSetup() { return; } - DEBUG_MSG_P(PSTR("[MAIN] %s %s built %s\n"), getAppName(), getVersion(), buildTime().c_str()); + DEBUG_MSG_P(PSTR("[MAIN] %s %s built %s\n"), + getAppName(), getVersion(), buildTime().c_str()); DEBUG_MSG_P(PSTR("[MAIN] %s\n"), getAppAuthor()); DEBUG_MSG_P(PSTR("[MAIN] %s\n"), getAppWebsite()); - DEBUG_MSG_P(PSTR("[MAIN] CPU chip ID: %s\n"), getFullChipId().c_str()); - DEBUG_MSG_P(PSTR("[MAIN] SDK: %s\n"), ESP.getSdkVersion()); - DEBUG_MSG_P(PSTR("[MAIN] Arduino Core: %s\n"), getCoreVersion().c_str()); + DEBUG_MSG_P(PSTR("[MAIN] CPU chip ID: %s frequency: %hhuMHz\n"), + getFullChipId().c_str(), system_get_cpu_freq()); + DEBUG_MSG_P(PSTR("[MAIN] SDK: %s Arduino Core: %s\n"), + ESP.getSdkVersion(), getCoreVersion().c_str()); DEBUG_MSG_P(PSTR("[MAIN] Support: %s\n"), getEspurnaModules()); #if SENSOR_SUPPORT DEBUG_MSG_P(PSTR("[MAIN] Sensors: %s\n"), getEspurnaSensors()); diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index a8a76718..b21b7388 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -218,7 +218,7 @@ #endif #ifndef SYSTEM_CHECK_TIME -#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis +#define SYSTEM_CHECK_TIME 60 // The system is considered stable after these many seconds #endif #ifndef SYSTEM_CHECK_MAX @@ -375,7 +375,7 @@ //------------------------------------------------------------------------------ #ifndef LOADAVG_INTERVAL -#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) +#define LOADAVG_INTERVAL 30 // Interval between calculating load average (in seconds) #endif //------------------------------------------------------------------------------ diff --git a/code/espurna/crash.cpp b/code/espurna/crash.cpp index 7f50c912..5a95c0c8 100644 --- a/code/espurna/crash.cpp +++ b/code/espurna/crash.cpp @@ -240,7 +240,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack } // Do not record crash data when doing a normal reboot or when crash trace was disabled - if (checkNeedsReset()) { + if (pendingDeferredReset()) { return; } @@ -297,7 +297,7 @@ void crashDump(Print& print) { } void crashResetReason(Print& print) { - auto reason = customResetReason(); + const auto reason = customResetReason(); bool custom { CustomResetReason::None != reason }; print.printf_P(PSTR("last reset reason: %s\n"), custom ? customResetReasonToPayload(reason).c_str() diff --git a/code/espurna/debug.cpp b/code/espurna/debug.cpp index a4448b76..47f02b1d 100644 --- a/code/espurna/debug.cpp +++ b/code/espurna/debug.cpp @@ -544,9 +544,9 @@ bool status(espurna::heartbeat::Mask mask) { } if (mask & espurna::heartbeat::Report::Freeheap) { - auto stats = systemHeapStats(); - debugSend(PSTR("[MAIN] Heap: %5u / %5u bytes available (%5u contiguous)\n"), - stats.available, systemInitialFreeHeap(), stats.usable); + const auto stats = systemHeapStats(); + debugSend(PSTR("[MAIN] Heap: initial %5lu available %5lu contiguous %5hu\n"), + systemInitialFreeHeap(), stats.available, stats.usable); } if ((mask & espurna::heartbeat::Report::Vcc) && (ADC_MODE_VALUE == ADC_VCC)) { @@ -554,8 +554,8 @@ bool status(espurna::heartbeat::Mask mask) { } #if NTP_SUPPORT - if ((mask & espurna::heartbeat::Report::Datetime) && (ntpSynced())) { - debugSend(PSTR("[MAIN] Time: %s\n"), ntpDateTime().c_str()); + if ((mask & espurna::heartbeat::Report::Datetime) && ntpSynced()) { + debugSend(PSTR("[MAIN] Datetime: %s\n"), ntpDateTime().c_str()); } #endif @@ -586,10 +586,8 @@ void configure() { #endif #if DEBUG_LOG_BUFFER_SUPPORT - { - if (settings::buffer()) { - debug::buffer::enable(settings::bufferSize()); - } + if (settings::buffer()) { + debug::buffer::enable(settings::bufferSize()); } #endif @@ -674,9 +672,8 @@ void debugSetup() { return; } - ctx.output.printf_P(PSTR("Buffer size: %u / %u bytes\n"), - debug::buffer::size(), - debug::buffer::capacity()); + ctx.output.printf_P(PSTR("buffer size: %u / %u bytes\n"), + debug::buffer::size(), debug::buffer::capacity()); debug::buffer::dump(ctx.output); terminalOK(ctx); }); diff --git a/code/espurna/led.cpp b/code/espurna/led.cpp index 6fcb3329..73ace7ac 100644 --- a/code/espurna/led.cpp +++ b/code/espurna/led.cpp @@ -37,13 +37,13 @@ namespace { // but anything else is experiencing overflow mechanics struct Delay { - static constexpr auto ClockCyclesMax = espurna::duration::ClockCycles(espurna::duration::ClockCycles::max()); - static constexpr auto MillisecondsMax = std::chrono::duration_cast(ClockCyclesMax); - - using Duration = espurna::duration::ClockCycles; - using Source = espurna::duration::ClockCyclesSource; + using Source = espurna::time::CpuClock; + using Duration = Source::duration; using TimePoint = Source::time_point; + static constexpr auto ClockCyclesMax = Duration(Duration::max()); + static constexpr auto MillisecondsMax = std::chrono::duration_cast(ClockCyclesMax); + using Repeats = unsigned char; static constexpr Repeats RepeatsMin { std::numeric_limits::min() }; static constexpr Repeats RepeatsMax { std::numeric_limits::max() }; @@ -567,24 +567,15 @@ void migrate(int version) { // For network-based modes, indefinitely cycle ON <-> OFF // (TODO: template params containing structs like duration need -std=c++2a) -template -struct StaticDelay { - static constexpr auto MillisecondsOn = espurna::duration::Milliseconds(On); - static constexpr auto MillisecondsOff = espurna::duration::Milliseconds(Off); - - static_assert(MillisecondsOn <= Delay::MillisecondsMax, ""); - static_assert(MillisecondsOff <= Delay::MillisecondsMax, ""); - - static constexpr auto DurationOn = std::chrono::duration_cast(MillisecondsOn); - static constexpr auto DurationOff = std::chrono::duration_cast(MillisecondsOff); - - constexpr operator Delay() const { - return Delay{DurationOn, DurationOff, Delay::RepeatsMin}; - } -}; - #define LED_STATIC_DELAY(NAME, ON, OFF)\ - constexpr Delay NAME = StaticDelay{} + static constexpr auto NAME ## MillisecondsOn = espurna::duration::Milliseconds(ON);\ + static constexpr auto NAME ## MillisecondsOff = espurna::duration::Milliseconds(OFF);\ + static_assert(NAME ## MillisecondsOn < Delay::MillisecondsMax, "");\ + static_assert(NAME ## MillisecondsOff < Delay::MillisecondsMax, "");\ + static constexpr Delay NAME = Delay {\ + std::chrono::duration_cast(NAME ## MillisecondsOn),\ + std::chrono::duration_cast(NAME ## MillisecondsOff),\ + Delay::RepeatsMin } LED_STATIC_DELAY(NetworkConnected, 100, 4900); LED_STATIC_DELAY(NetworkConnectedInverse, 4900, 100); @@ -794,10 +785,12 @@ void pattern(Led& led, Pattern&& other) { } void run(Led& led, const Delay& delay) { - static auto clock_last = espurna::duration::ClockCyclesSource::now(); + using TimeSource = espurna::time::CpuClock; + + static auto clock_last = TimeSource::now(); static auto delay_for = delay.on(); - const auto clock_current = espurna::duration::ClockCyclesSource::now(); + const auto clock_current = TimeSource::now(); if (clock_current - clock_last >= delay_for) { delay_for = led.toggle() ? delay.on() : delay.off(); clock_last = clock_current; diff --git a/code/espurna/main.cpp b/code/espurna/main.cpp index df0d268b..193c11be 100644 --- a/code/espurna/main.cpp +++ b/code/espurna/main.cpp @@ -27,57 +27,66 @@ along with this program. If not, see . // GENERAL CALLBACKS // ----------------------------------------------------------------------------- +namespace espurna { namespace { +namespace main { +namespace build { -std::vector _loop_callbacks; -std::vector _reload_callbacks; - -bool _reload_config { false }; -espurna::duration::Milliseconds _loop_delay { 0 }; - +// XXX: some SYS tasks require more time than yield(), as they won't +// be scheduled if user task is always doing something +// no particular need to delay too much though, besides attempting to reduce +// the power consuption of the board constexpr espurna::duration::Milliseconds LoopDelayMin { 10 }; constexpr espurna::duration::Milliseconds LoopDelayMax { 300 }; -} // namespace - -void espurnaRegisterLoop(LoopCallback callback) { - _loop_callbacks.push_back(callback); +constexpr espurna::duration::Milliseconds loopDelay() { + return espurna::duration::Milliseconds { LOOP_DELAY_TIME }; } -void espurnaRegisterReload(LoopCallback callback) { - _reload_callbacks.push_back(callback); +} // namespace build + +namespace settings { + +espurna::duration::Milliseconds loopDelay() { + return std::clamp(getSetting("loopDelay", build::loopDelay()), build::LoopDelayMin, build::LoopDelayMax); } -void espurnaReload() { - _reload_config = true; +} // namespace settings + +namespace internal { + +std::vector loop_callbacks; +espurna::duration::Milliseconds loop_delay { build::LoopDelayMin }; + +std::vector reload_callbacks; +bool reload_flag { false }; + +} // namespace internal + +bool reload() { + if (internal::reload_flag) { + internal::reload_flag = false; + return true; + } + + return false; } -espurna::duration::Milliseconds espurnaLoopDelay() { - return _loop_delay; -} +void loop() { + // Reload config before running any callbacks + if (reload()) { + for (const auto& callback : internal::reload_callbacks) { + callback(); + } + } -void espurnaLoopDelay(espurna::duration::Milliseconds value) { - _loop_delay = value; -} - -namespace { - -constexpr espurna::duration::Milliseconds _loopDelay() { - return espurna::duration::Milliseconds{LOOP_DELAY_TIME}; -} - -void _espurnaReload() { - for (const auto& callback : _reload_callbacks) { + for (const auto& callback : internal::loop_callbacks) { callback(); } + + espurna::time::delay(internal::loop_delay); } -} // namespace - -// ----------------------------------------------------------------------------- -// BOOTING -// ----------------------------------------------------------------------------- - void setup() { // ------------------------------------------------------------------------- // Basic modules, will always run @@ -285,21 +294,38 @@ void setup() { migrate(); // Set up delay() after loop callbacks are finished - // Note: should be after settingsSetup() - _loop_delay = std::clamp(getSetting("loopDelay", _loopDelay()), LoopDelayMin, LoopDelayMax); + // Notice that this requires settings storage to be available and must be **after** settingsSetup()! + internal::loop_delay = settings::loopDelay(); +} +} // namespace main +} // namespace +} // namespace espurna + +void espurnaRegisterLoop(LoopCallback callback) { + espurna::main::internal::loop_callbacks.push_back(callback); +} + +void espurnaRegisterReload(LoopCallback callback) { + espurna::main::internal::reload_callbacks.push_back(callback); +} + +void espurnaReload() { + espurna::main::internal::reload_flag = true; +} + +espurna::duration::Milliseconds espurnaLoopDelay() { + return espurna::main::internal::loop_delay; +} + +void espurnaLoopDelay(espurna::duration::Milliseconds value) { + espurna::main::internal::loop_delay = value; +} + +void setup() { + espurna::main::setup(); } void loop() { - // Reload config before running any callbacks - if (_reload_config) { - _espurnaReload(); - _reload_config = false; - } - - for (auto* callback : _loop_callbacks) { - callback(); - } - - espurna::duration::delay(_loop_delay); + espurna::main::loop(); } diff --git a/code/espurna/ntp.cpp b/code/espurna/ntp.cpp index 06c89e6d..726c4af5 100644 --- a/code/espurna/ntp.cpp +++ b/code/espurna/ntp.cpp @@ -229,7 +229,7 @@ void _ntpReport() { return; } - auto info = ntpInfo(); + const auto info = ntpInfo(); DEBUG_MSG_P(PSTR("[NTP] Server : %s\n"), _ntp_server.c_str()); DEBUG_MSG_P(PSTR("[NTP] Sync Time : %s (UTC)\n"), info.sync.c_str()); DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), info.utc.c_str()); diff --git a/code/espurna/sensor.cpp b/code/espurna/sensor.cpp index 65fa7384..1238e346 100644 --- a/code/espurna/sensor.cpp +++ b/code/espurna/sensor.cpp @@ -3363,10 +3363,11 @@ void sensorSetup() { void sensorLoop() { - // If there are still some un-initialized sensors after setup() - static espurna::duration::Seconds last_init { 0 }; + // Continiously repeat initialization if there are still some un-initialized sensors after setup() + using TimeSource = espurna::time::CoreClock; + static TimeSource::time_point last_init { 0 }; - auto timestamp = espurna::duration::seconds(); + auto timestamp = TimeSource::now(); if (!_sensors_ready && (timestamp - last_init > _sensor_init_interval)) { last_init = timestamp; _sensorInit(); @@ -3380,7 +3381,7 @@ void sensorLoop() { _sensorTick(); // But, the actual reading needs to happen at the specified interval - static espurna::duration::Seconds last_update { 0 }; + static TimeSource::time_point last_update { 0 }; static int report_count { 0 }; if (timestamp - last_update > _sensor_read_interval) { diff --git a/code/espurna/sensors/PZEM004TSensor.h b/code/espurna/sensors/PZEM004TSensor.h index bf414810..f0a06ba4 100644 --- a/code/espurna/sensors/PZEM004TSensor.h +++ b/code/espurna/sensors/PZEM004TSensor.h @@ -59,11 +59,12 @@ class PZEM004TSensor : public BaseEmonSensor { private: // Track instances returned by 'make()' in a singly linked list // Compared to stdlib's forward_list, head and tail are reversed + static PZEM004TSensor* _current_instance; static PZEM004TSensor* _head_instance; PZEM004TSensor* _next_instance; - static PZEM004TSensor* _current_instance; - static espurna::duration::Milliseconds _last_read; + using TimeSource = espurna::time::CoreClock; + static TimeSource::time_point _last_read; template static void foreach(T&& callback) { @@ -99,7 +100,7 @@ public: return 1 == PZEM004T_USE_SOFT; } - static constexpr espurna::duration::Milliseconds ReadInterval { PZEM004T_READ_INTERVAL }; + static constexpr TimeSource::duration ReadInterval { PZEM004T_READ_INTERVAL }; static constexpr size_t DevicesMax { PZEM004T_DEVICES_MAX }; static IPAddress defaultAddress(size_t device) { @@ -431,7 +432,7 @@ public: // Current approach is to spread our reads of mutliple instances, // instead of doing them in the same time slot. - if (espurna::duration::millis() - _last_read < ReadInterval) { + if (TimeSource::now() - _last_read < ReadInterval) { return; } @@ -443,7 +444,7 @@ public: yield(); } - _last_read = espurna::duration::millis(); + _last_read = TimeSource::now(); _current_instance = (_current_instance->_next_instance) ? _current_instance->_next_instance : _head_instance; @@ -554,8 +555,9 @@ void PZEM004TSensor::registerTerminalCommands() { #endif } -PZEM004TSensor* PZEM004TSensor::_head_instance { nullptr }; +PZEM004TSensor::TimeSource::time_point PZEM004TSensor::_last_read { PZEM004TSensor::TimeSource::now() - ReadInterval }; + PZEM004TSensor* PZEM004TSensor::_current_instance { nullptr }; -espurna::duration::Milliseconds PZEM004TSensor::_last_read { espurna::duration::millis() - ReadInterval }; +PZEM004TSensor* PZEM004TSensor::_head_instance { nullptr }; PZEM004TSensor::Ports PZEM004TSensor::_ports{}; diff --git a/code/espurna/system.cpp b/code/espurna/system.cpp index 0d3d9d71..0a6f9b87 100644 --- a/code/espurna/system.cpp +++ b/code/espurna/system.cpp @@ -15,15 +15,22 @@ Copyright (C) 2019 by Xose Pérez #include "ntp.h" #include +#include #include #include +extern "C" { +#include "user_interface.h" +extern struct rst_info resetInfo; +} + #include "libs/TypeChecks.h" // ----------------------------------------------------------------------------- // This method is called by the SDK early on boot to know where to connect the ADC - +// Notice that current Core versions automatically de-mangle the function name for historical reasons +// (meaning, it is already used as `_Z14__get_adc_modev` and there's no need for `extern "C"`) int __get_adc_mode() { return (int) (ADC_MODE_VALUE); } @@ -60,160 +67,56 @@ espurna::heartbeat::Mode convert(const String& value) { template <> espurna::duration::Seconds convert(const String& value) { - return espurna::duration::Seconds(convert(value)); + return espurna::duration::Seconds(convert(value)); } template <> espurna::duration::Milliseconds convert(const String& value) { - return espurna::duration::Milliseconds(convert(value)); + return espurna::duration::Milliseconds(convert(value)); } } // namespace internal } // namespace settings -String systemHeartbeatModeToPayload(espurna::heartbeat::Mode mode) { - const __FlashStringHelper* ptr { nullptr }; - switch (mode) { - case espurna::heartbeat::Mode::None: - ptr = F("none"); - break; - case espurna::heartbeat::Mode::Once: - ptr = F("once"); - break; - case espurna::heartbeat::Mode::Repeat: - ptr = F("repeat"); - break; - } - - return String(ptr); -} - // ----------------------------------------------------------------------------- -unsigned long systemFreeStack() { +namespace espurna { +namespace { +namespace memory { + +// returns 'total stack size' minus 'un-painted area' +// needs re-painting step, as this never decreases +unsigned long freeStack() { return ESP.getFreeContStack(); } -HeapStats systemHeapStats() { +HeapStats heapStats() { HeapStats stats; ESP.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct); return stats; } -void systemHeapStats(HeapStats& stats) { - stats = systemHeapStats(); +void heapStats(HeapStats& stats) { + stats = heapStats(); } -unsigned long systemFreeHeap() { +unsigned long freeHeap() { return ESP.getFreeHeap(); } -unsigned long systemInitialFreeHeap() { - static unsigned long value { 0ul }; - if (!value) { - value = systemFreeHeap(); - } +decltype(freeHeap()) initialFreeHeap() { + static const auto value = ([]() { + return freeHeap(); + })(); return value; } -//-------------------------------------------------------------------------------- +} // namespace memory -union system_rtcmem_t { - struct { - uint8_t stability_counter; - uint8_t reset_reason; - uint16_t _reserved_; - } packed; - uint32_t value; -}; +namespace boot { -uint8_t systemStabilityCounter() { - system_rtcmem_t data; - data.value = Rtcmem->sys; - return data.packed.stability_counter; -} - -void systemStabilityCounter(uint8_t count) { - system_rtcmem_t data; - data.value = Rtcmem->sys; - data.packed.stability_counter = count; - Rtcmem->sys = data.value; -} - -CustomResetReason _systemRtcmemResetReason() { - system_rtcmem_t data; - data.value = Rtcmem->sys; - return static_cast(data.packed.reset_reason); -} - -void _systemRtcmemResetReason(CustomResetReason reason) { - system_rtcmem_t data; - data.value = Rtcmem->sys; - data.packed.reset_reason = static_cast(reason); - Rtcmem->sys = data.value; -} - -#if SYSTEM_CHECK_ENABLED - -// Call this method on boot with start=true to increase the crash counter -// Call it again once the system is stable to decrease the counter -// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable -// setting _systemOK = false; -// -// An unstable system will only have serial access, WiFi in AP mode and OTA - -constexpr unsigned char _systemCheckMin() { - return 0u; -} - -constexpr unsigned char _systemCheckMax() { - return SYSTEM_CHECK_MAX; -} - -constexpr unsigned long _systemCheckTime() { - return SYSTEM_CHECK_TIME; -} - -static_assert(_systemCheckMax() > 0, ""); - -Ticker _system_stable_timer; -bool _system_stable { true }; - -void _systemStabilityInit() { - auto count = rtcmemStatus() ? systemStabilityCounter() : 1u; - - _system_stable = (count < _systemCheckMax()); - DEBUG_MSG_P(PSTR("[MAIN] System %s\n"), _system_stable ? "OK" : "UNSTABLE"); - - _system_stable_timer.once_ms_scheduled(_systemCheckTime(), []() { - DEBUG_MSG_P(PSTR("[MAIN] System stability counter %hhu / %hhu\n"), - _systemCheckMin(), _systemCheckMax()); - systemStabilityCounter(_systemCheckMin()); - }); - - auto next = count + 1u; - count = next > _systemCheckMax() - ? count - : next; - - systemStabilityCounter(count); -} - -bool systemCheck() { - return _system_stable; -} - -#endif - -// ----------------------------------------------------------------------------- -// Reset -// ----------------------------------------------------------------------------- - -Ticker _defer_reset; -auto _reset_reason = CustomResetReason::None; - -String customResetReasonToPayload(CustomResetReason reason) { +String serialize(CustomResetReason reason) { const __FlashStringHelper* ptr { nullptr }; switch (reason) { case CustomResetReason::None: @@ -254,75 +157,243 @@ String customResetReasonToPayload(CustomResetReason reason) { return String(ptr); } +// The ESPLive has an ADC MUX which needs to be configured. +// Default CT input (pin B, solder jumper B) +void hardware() { +#if defined(MANCAVEMADE_ESPLIVE) + pinMode(16, OUTPUT); + digitalWrite(16, HIGH); +#endif +} + +// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable +// When it that mode, system will only have minimal set of services available +struct Data { + Data() = delete; + explicit Data(volatile uint32_t* ptr) : + _ptr(ptr) + {} + + explicit operator bool() const { + return rtcmemStatus(); + } + + uint8_t counter() const { + return read().counter; + } + + void counter(uint8_t input) { + auto value = read(); + value.counter = input; + write(value); + } + + CustomResetReason reason() const { + return static_cast(read().reason); + } + + void reason(CustomResetReason input) { + auto value = read(); + value.reason = static_cast(input); + write(value); + } + + uint32_t value() const { + return *_ptr; + } + +private: + struct alignas(uint32_t) Raw { + uint8_t counter; + uint8_t reason; + uint8_t _stub1; + uint8_t _stub2; + }; + + static_assert(sizeof(Raw) == sizeof(uint32_t), ""); + static_assert(alignof(Raw) == alignof(uint32_t), ""); + + void write(Raw raw) { + uint32_t out{}; + std::memcpy(&out, &raw, sizeof(out)); + *_ptr = out; + } + + Raw read() const { + uint32_t value = *_ptr; + + Raw out{}; + std::memcpy(&out, &value, sizeof(out)); + + return out; + } + + volatile uint32_t* _ptr; +}; + +namespace internal { + +Data persistent_data { &Rtcmem->sys }; + +Ticker timer; +bool flag { true }; + +} // namespace internal + +#if SYSTEM_CHECK_ENABLED +namespace stability { +namespace build { + +constexpr uint8_t ChecksMin { 0 }; +constexpr uint8_t ChecksMax { SYSTEM_CHECK_MAX }; +static_assert(ChecksMax > 0, ""); + +constexpr espurna::duration::Seconds CheckTime { SYSTEM_CHECK_TIME }; +static_assert(CheckTime > espurna::duration::Seconds::min(), ""); + +} // namespace build + +void init() { + // on cold boot / rst, bumps count to 2 so we don't end up + // spamming crash recorder in case something goes wrong + auto count = static_cast(internal::persistent_data) + ? internal::persistent_data.counter() : 1u; + + internal::flag = (count < build::ChecksMax); + internal::timer.once_scheduled(build::CheckTime.count(), []() { + DEBUG_MSG_P(PSTR("[MAIN] Resetting stability counter\n")); + internal::persistent_data.counter(build::ChecksMin); + }); + + const auto next = count + 1u; + internal::persistent_data.counter((next > build::ChecksMax) ? count : next); +} + +bool check() { + return internal::flag; +} + +} // namespace stability +#endif + // system_get_rst_info() result is cached by the Core init for internal use -uint32_t systemResetReason() { +uint32_t system_reason() { return resetInfo.reason; } -void customResetReason(CustomResetReason reason) { - _reset_reason = reason; - _systemRtcmemResetReason(reason); -} +// prunes custom reason after accessing it once +CustomResetReason custom_reason() { + static const CustomResetReason reason = ([]() { + const auto out = static_cast(internal::persistent_data) + ? internal::persistent_data.reason() + : CustomResetReason::None; + internal::persistent_data.reason(CustomResetReason::None); + return out; + })(); -CustomResetReason customResetReason() { - bool once { true }; - static auto reason = CustomResetReason::None; - if (once) { - once = false; - if (rtcmemStatus()) { - reason = _systemRtcmemResetReason(); - } - customResetReason(CustomResetReason::None); - } return reason; } -void reset() { - ESP.restart(); +void custom_reason(CustomResetReason reason) { + internal::persistent_data.reason(reason); } -bool eraseSDKConfig() { - return ESP.eraseConfig(); -} - -void deferredReset(unsigned long delay, CustomResetReason reason) { - _defer_reset.once_ms(delay, customResetReason, reason); -} - -void factoryReset() { - DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n")); - resetSettings(); - deferredReset(100, CustomResetReason::Factory); -} - -bool checkNeedsReset() { - return _reset_reason != CustomResetReason::None; -} +} // namespace boot // ----------------------------------------------------------------------------- -// Calculated load average as a percentage +// Calculated load average of the loop() as a percentage (notice that this may not be accurate) +namespace load_average { +namespace build { -unsigned char _load_average { 0u }; +constexpr size_t ValueMin { 0 }; +constexpr size_t ValueMax { 100 }; -unsigned char systemLoadAverage() { - return _load_average; +static constexpr espurna::duration::Seconds Interval { LOADAVG_INTERVAL }; +static_assert(Interval <= espurna::duration::Seconds(90), ""); + +} // namespace build + +using Type = unsigned long; + +struct Counter { + using TimeSource = espurna::time::SystemClock; + TimeSource::time_point last; + Type count; + Type value; + Type max; +}; + +namespace internal { + +Type load_average { 0 }; + +} // namespace internal + +Type value() { + return internal::load_average; } +void loop() { + using TimeSource = Counter::TimeSource; + static Counter counter { + .last = TimeSource::now(), + .count = 0, + .value = 0, + .max = 1 + }; + + ++counter.count; + + const auto timestamp = TimeSource::now(); + if (timestamp - counter.last < build::Interval) { + return; + } + + counter.last = timestamp; + counter.value = counter.count; + counter.count = 0; + counter.max = std::max(counter.max, counter.value); + + internal::load_average = build::ValueMax - (build::ValueMax * counter.value / counter.max); +} + +} // namespace load_average +} // namespace + // ----------------------------------------------------------------------------- -namespace espurna { namespace heartbeat { +namespace { -constexpr Mode defaultMode() { +String serialize(espurna::heartbeat::Mode mode) { + const __FlashStringHelper* ptr { nullptr }; + switch (mode) { + case Mode::None: + ptr = F("none"); + break; + case Mode::Once: + ptr = F("once"); + break; + case Mode::Repeat: + ptr = F("repeat"); + break; + } + + return String(ptr); +} + +namespace build { + +constexpr Mode mode() { return HEARTBEAT_MODE; } -constexpr espurna::duration::Seconds defaultInterval() { - return espurna::duration::Seconds(HEARTBEAT_INTERVAL); +constexpr espurna::duration::Seconds interval() { + return espurna::duration::Seconds { HEARTBEAT_INTERVAL }; } -constexpr Mask defaultValue() { +constexpr Mask value() { return (Report::Status * (HEARTBEAT_REPORT_STATUS)) | (Report::Ssid * (HEARTBEAT_REPORT_SSID)) | (Report::Ip * (HEARTBEAT_REPORT_IP)) @@ -346,42 +417,45 @@ constexpr Mask defaultValue() { | (Report::Bssid * (HEARTBEAT_REPORT_BSSID)); } -Mask currentValue() { +} // namespace build + +namespace settings { + +Mode mode() { + return getSetting("hbMode", build::mode()); +} + +espurna::duration::Seconds interval() { + return getSetting("hbInterval", build::interval()); +} + +Mask value() { // because we start shifting from 1, we could use the // first bit as a flag to enable all of the messages - auto value = getSetting("hbReport", defaultValue()); - if (value == 1) { + static constexpr Mask MaskAll { 1 }; + + auto value = getSetting("hbReport", build::value()); + if (value == MaskAll) { value = std::numeric_limits::max(); } return value; } -Mode currentMode() { - return getSetting("hbMode", defaultMode()); -} - -espurna::duration::Seconds currentInterval() { - return getSetting("hbInterval", defaultInterval()); -} - -espurna::duration::Milliseconds currentIntervalMs() { - return espurna::duration::Milliseconds(currentInterval()); -} - -Ticker timer; +} // namespace settings struct CallbackRunner { + using TimeSource = espurna::time::CoreClock; Callback callback; Mode mode; - espurna::duration::Milliseconds interval; - espurna::duration::Milliseconds last; + TimeSource::duration interval; + TimeSource::time_point last; }; -std::vector runners; - namespace internal { +Ticker timer; +std::vector runners; bool scheduled { false }; } // namespace internal @@ -399,81 +473,25 @@ bool scheduled() { return false; } -} // namespace heartbeat -} // namespace espurna +void run() { + static constexpr duration::Milliseconds BeatMin { duration::Seconds(1) }; + static constexpr duration::Milliseconds BeatMax { BeatMin * 10 }; -void _systemHeartbeat(); + auto next = duration::Milliseconds(settings::interval()); -void systemStopHeartbeat(espurna::heartbeat::Callback callback) { - using namespace espurna::heartbeat; - auto found = std::remove_if(runners.begin(), runners.end(), - [&](const CallbackRunner& runner) { - return callback == runner.callback; - }); - runners.erase(found, runners.end()); -} + auto ts = CallbackRunner::TimeSource::now(); + if (internal::runners.size()) { + auto mask = settings::value(); -void systemHeartbeat(espurna::heartbeat::Callback callback, espurna::heartbeat::Mode mode, espurna::duration::Seconds interval) { - if (mode == espurna::heartbeat::Mode::None) { - return; - } - - auto msec = espurna::duration::Milliseconds(interval); - if (!msec.count()) { - return; - } - - auto offset = espurna::duration::Milliseconds(millis() - 1ul); - espurna::heartbeat::runners.push_back({ - callback, mode, - msec, - offset - msec - }); - - espurna::heartbeat::timer.detach(); - espurna::heartbeat::schedule(); -} - -void systemHeartbeat(espurna::heartbeat::Callback callback, espurna::heartbeat::Mode mode) { - systemHeartbeat(callback, mode, espurna::heartbeat::currentInterval()); -} - -void systemHeartbeat(espurna::heartbeat::Callback callback) { - systemHeartbeat(callback, espurna::heartbeat::currentMode(), espurna::heartbeat::currentInterval()); -} - -espurna::duration::Seconds systemHeartbeatInterval() { - espurna::duration::Milliseconds result(0ul); - for (auto& runner : espurna::heartbeat::runners) { - result = espurna::duration::Milliseconds(result.count() - ? std::min(result, runner.interval) : runner.interval); - } - - return std::chrono::duration_cast(result); -} - -void _systemHeartbeat() { - using namespace espurna::heartbeat; - using namespace espurna::duration; - - constexpr Milliseconds BeatMin { Seconds(1) }; - constexpr Milliseconds BeatMax { BeatMin * 10 }; - - auto next = Milliseconds(currentInterval()); - - auto ts = espurna::duration::millis(); - if (runners.size()) { - auto mask = currentValue(); - - auto it = runners.begin(); - auto end = runners.end(); + auto it = internal::runners.begin(); + auto end = internal::runners.end(); while (it != end) { auto diff = ts - (*it).last; if (diff > (*it).interval) { auto result = (*it).callback(mask); if (result && ((*it).mode == Mode::Once)) { - it = runners.erase(it); - end = runners.end(); + it = internal::runners.erase(it); + end = internal::runners.end(); continue; } @@ -495,118 +513,343 @@ void _systemHeartbeat() { next = BeatMin; } - timer.once_ms(next.count(), espurna::heartbeat::schedule); + internal::timer.once_ms(next.count(), schedule); } -void systemScheduleHeartbeat() { - auto ts = espurna::duration::Milliseconds(millis()); - for (auto& runner : espurna::heartbeat::runners) { - runner.last = ts - runner.interval - espurna::duration::Milliseconds(1ul); +void stop(Callback callback) { + auto found = std::remove_if(internal::runners.begin(), internal::runners.end(), + [&](const CallbackRunner& runner) { + return callback == runner.callback; + }); + internal::runners.erase(found, internal::runners.end()); +} + +void push(Callback callback, Mode mode, duration::Seconds interval) { + if (mode == Mode::None) { + return; } - espurna::heartbeat::schedule(); + + auto msec = duration::Milliseconds(interval); + if ((mode != Mode::Once) && !msec.count()) { + return; + } + + using TimeSource = CallbackRunner::TimeSource; + auto offset = TimeSource::now() - TimeSource::duration(1); + internal::runners.push_back({ + callback, mode, + msec, + offset - msec + }); + + internal::timer.detach(); + schedule(); } -void _systemUpdateLoadAverage() { - static unsigned long last_loadcheck = 0; - static unsigned long load_counter_temp = 0; - load_counter_temp++; +void pushOnce(Callback callback) { + push(callback, Mode::Once, espurna::duration::Seconds::min()); +} - if (millis() - last_loadcheck > LOADAVG_INTERVAL) { - static unsigned long load_counter = 0; - static unsigned long load_counter_max = 1; +duration::Seconds interval() { + using TimeSource = CallbackRunner::TimeSource; + TimeSource::duration result { settings::interval() }; - load_counter = load_counter_temp; - load_counter_temp = 0; - if (load_counter > load_counter_max) { - load_counter_max = load_counter; + for (auto& runner : internal::runners) { + result = std::min(result, runner.interval); + } + + return std::chrono::duration_cast(result); +} + +void reschedule() { + using TimeSource = CallbackRunner::TimeSource; + static constexpr TimeSource::duration Offset { 1 }; + + const auto ts = TimeSource::now(); + for (auto& runner : internal::runners) { + runner.last = ts - runner.interval - Offset; + } + + schedule(); +} + +void loop() { + if (scheduled()) { + run(); + } +} + +void init() { +#if DEBUG_SUPPORT + pushOnce([](Mask) { + const auto mode = settings::mode(); + if (mode != Mode::None) { + DEBUG_MSG_P(PSTR("[MAIN] Heartbeat \"%s\", every %u (seconds)\n"), + serialize(mode).c_str(), settings::interval().count()); + } else { + DEBUG_MSG_P(PSTR("[MAIN] Heartbeat disabled\n")); } - _load_average = 100u - (100u * load_counter / load_counter_max); - last_loadcheck = millis(); - } + return true; + }); +#if SYSTEM_CHECK_ENABLED + pushOnce([](Mask) { + if (!espurna::boot::stability::check()) { + DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n")); + } + return true; + }); +#endif +#endif + schedule(); } +} // namespace +} // namespace heartbeat + +namespace { + #if WEB_SUPPORT +namespace web { -uint8_t _systemHeartbeatModeToId(espurna::heartbeat::Mode mode) { - return static_cast(mode); +void onConnected(JsonObject& root) { + root["hbReport"] = heartbeat::settings::value(); + root["hbInterval"] = heartbeat::settings::interval().count(); + root["hbMode"] = static_cast(heartbeat::settings::mode()); } -bool _systemWebSocketOnKeyCheck(const char * key, JsonVariant& value) { +bool onKeyCheck(const char * key, JsonVariant& value) { if (strncmp(key, "sys", 3) == 0) return true; if (strncmp(key, "hb", 2) == 0) return true; return false; } -void _systemWebSocketOnConnected(JsonObject& root) { - root["hbReport"] = espurna::heartbeat::currentValue(); - root["hbInterval"] = getSetting("hbInterval", espurna::heartbeat::defaultInterval()).count(); - root["hbMode"] = _systemHeartbeatModeToId(getSetting("hbMode", espurna::heartbeat::defaultMode())); +} // namespace web +#endif + +// Allow to schedule a reset at the next loop +// Store reset reason both here and in for the next boot +namespace internal { + +Ticker reset_timer; +auto reset_reason = CustomResetReason::None; + +void reset(CustomResetReason reason) { + ::espurna::boot::custom_reason(reason); + reset_reason = reason; } +} // namespace internal + +// raw reboot call, effectively: +// ``` +// system_restart(); +// esp_suspend(); +// ``` +// triggered in SYS, might not always result in a clean reboot b/c of expected suspend +// triggered in CONT *should* end up never returning back and loop might now be needed +// (but, try to force swdt reboot in case it somehow happens) +[[noreturn]] void reset() { + ESP.restart(); + for (;;) { + delay(100); + } +} + +// 'simple' reboot call with software controlled time +// always needs a reason, so it can be displayed in logs and / or trigger some actions on boot +void pending_reset_loop() { + if (internal::reset_reason != CustomResetReason::None) { + reset(); + } +} + +void deferredReset(duration::Milliseconds delay, CustomResetReason reason) { + DEBUG_MSG_P(PSTR("[MAIN] Requested reset: %s\n"), + espurna::boot::serialize(reason).c_str()); + internal::reset_timer.once_ms(delay.count(), internal::reset, reason); +} + +// SDK reserves last 16KiB on the flash for it's own means +// Notice that it *may* also be required to soft-crash the board, +// so it does not end up restoring the configuration cached in RAM +// ref. https://github.com/esp8266/Arduino/issues/1494 +bool eraseSDKConfig() { + return ESP.eraseConfig(); +} + +void forceEraseSDKConfig() { + eraseSDKConfig(); + *((int*) 0) = 0; +} + +// Accumulates only when called, make sure to do so periodically +// Even in 32bit range, seconds would take a lot of time to overflow +duration::Seconds uptime() { + using TimeSource = espurna::time::SystemClock; + return std::chrono::duration_cast( + TimeSource::now().time_since_epoch()); +} + +} // namespace +} // namespace espurna + +// ----------------------------------------------------------------------------- + +namespace espurna { +namespace heartbeat { + +// system defaults, r/n used when providing module-specific settings + +espurna::duration::Milliseconds currentIntervalMs() { + return espurna::heartbeat::settings::interval(); +} + +espurna::duration::Seconds currentInterval() { + return espurna::heartbeat::settings::interval(); +} + +Mask currentValue() { + return espurna::heartbeat::settings::value(); +} + +Mode currentMode() { + return espurna::heartbeat::settings::mode(); +} + +} // namespace heartbeat +} // namespace espurna + +unsigned long systemFreeStack() { + return espurna::memory::freeStack(); +} + +HeapStats systemHeapStats() { + return espurna::memory::heapStats(); +} + +void systemHeapStats(HeapStats& stats) { + espurna::memory::heapStats(stats); +} + +unsigned long systemFreeHeap() { + return espurna::memory::freeHeap(); +} + +unsigned long systemInitialFreeHeap() { + return espurna::memory::initialFreeHeap(); +} + +unsigned long systemLoadAverage() { + return espurna::load_average::value(); +} + +void reset() { + espurna::reset(); +} + +bool eraseSDKConfig() { + return espurna::eraseSDKConfig(); +} + +void forceEraseSDKConfig() { + espurna::forceEraseSDKConfig(); +} + +void deferredReset(unsigned long delay, CustomResetReason reason) { + espurna::deferredReset(espurna::duration::Milliseconds(delay), reason); +} + +void factoryReset() { + static constexpr espurna::duration::Milliseconds Time { 100 }; + resetSettings(); + espurna::deferredReset(Time, CustomResetReason::Factory); +} + +bool pendingDeferredReset() { + return espurna::internal::reset_reason != CustomResetReason::None; +} + +uint32_t systemResetReason() { + return espurna::boot::system_reason(); +} + +CustomResetReason customResetReason() { + return espurna::boot::custom_reason(); +} + +void customResetReason(CustomResetReason reason) { + espurna::boot::custom_reason(reason); +} + +String customResetReasonToPayload(CustomResetReason reason) { + return espurna::boot::serialize(reason); +} + +#if SYSTEM_CHECK_ENABLED +uint8_t systemStabilityCounter() { + return espurna::boot::internal::persistent_data.counter(); +} + +void systemStabilityCounter(uint8_t count) { + espurna::boot::internal::persistent_data.counter(count); +} + +bool systemCheck() { + return espurna::boot::stability::check(); +} #endif +void systemStopHeartbeat(espurna::heartbeat::Callback callback) { + espurna::heartbeat::stop(callback); +} + +void systemHeartbeat(espurna::heartbeat::Callback callback, espurna::heartbeat::Mode mode, espurna::duration::Seconds interval) { + espurna::heartbeat::push(callback, mode, interval); +} + +void systemHeartbeat(espurna::heartbeat::Callback callback, espurna::heartbeat::Mode mode) { + espurna::heartbeat::push(callback, mode, + espurna::heartbeat::settings::interval()); +} + +void systemHeartbeat(espurna::heartbeat::Callback callback) { + espurna::heartbeat::push(callback, + espurna::heartbeat::settings::mode(), + espurna::heartbeat::settings::interval()); +} + +espurna::duration::Seconds systemHeartbeatInterval() { + return espurna::heartbeat::interval(); +} + +void systemScheduleHeartbeat() { + espurna::heartbeat::reschedule(); +} + void systemLoop() { - if (checkNeedsReset()) { - reset(); - return; - } - - if (espurna::heartbeat::scheduled()) { - _systemHeartbeat(); - } - - _systemUpdateLoadAverage(); -} - -void _systemSetupSpecificHardware() { -#if defined(MANCAVEMADE_ESPLIVE) - // The ESPLive has an ADC MUX which needs to be configured. - // Default CT input (pin B, solder jumper B) - pinMode(16, OUTPUT); - digitalWrite(16, HIGH); -#endif + espurna::pending_reset_loop(); + espurna::load_average::loop(); + espurna::heartbeat::loop(); } espurna::duration::Seconds systemUptime() { - static espurna::duration::Milliseconds last { espurna::duration::millis() }; - static espurna::duration::Type overflows { 0 }; - - auto timestamp = espurna::duration::millis(); - if (timestamp < last) { - ++overflows; - } - - last = timestamp; - - static constexpr espurna::duration::Milliseconds MillisecondsMax { - espurna::duration::Milliseconds::max() }; - static constexpr espurna::duration::Seconds OverflowSeconds { - std::chrono::duration_cast(MillisecondsMax) }; - - return (overflows * OverflowSeconds) - + std::chrono::duration_cast(last); + return espurna::uptime(); } void systemSetup() { + espurna::boot::hardware(); + espurna::boot::custom_reason(); - #if SPIFFS_SUPPORT - SPIFFS.begin(); - #endif +#if SYSTEM_CHECK_ENABLED + espurna::boot::stability::init(); +#endif - #if SYSTEM_CHECK_ENABLED - _systemStabilityInit(); - #endif - - #if WEB_SUPPORT - wsRegister() - .onConnected(_systemWebSocketOnConnected) - .onKeyCheck(_systemWebSocketOnKeyCheck); - #endif - - _systemSetupSpecificHardware(); +#if WEB_SUPPORT + wsRegister() + .onConnected(espurna::web::onConnected) + .onKeyCheck(espurna::web::onKeyCheck); +#endif espurnaRegisterLoop(systemLoop); - - espurna::heartbeat::schedule(); - + espurna::heartbeat::init(); } diff --git a/code/espurna/system.h b/code/espurna/system.h index 78712b1b..1d655a7c 100644 --- a/code/espurna/system.h +++ b/code/espurna/system.h @@ -13,11 +13,6 @@ Copyright (C) 2019 by Xose Pérez #include #include -extern "C" { -#include "user_interface.h" -extern struct rst_info resetInfo; -} - struct HeapStats { uint32_t available; uint16_t usable; @@ -41,51 +36,109 @@ enum class CustomResetReason : uint8_t { namespace espurna { namespace duration { -using Type = uint32_t; - -using Seconds = std::chrono::duration; -using Milliseconds = std::chrono::duration; -using ClockCycles = std::chrono::duration>; - -inline Milliseconds millis() { - return Milliseconds(::millis()); -} - -inline Seconds seconds() { - return Seconds(std::chrono::duration_cast(millis())); -} - -inline void delay(Milliseconds value) { - ::delay(value.count()); -} - -// TODO: also implement a software source based on boot time in msec / usec? -// Current NONOS esp8266 gcc + newlib do not implement clock_getttime for REALTIME and MONOTONIC types, -// everything (system_clock, steady_clock, high_resolution_clock) goes through gettimeofday() -// RTOS port *does* include monotonic clock through the systick counter, which seems to be implement'able -// here as well through the use of os_timer delay and a certain fixed tick (e.g. default CONFIG_FREERTOS_HZ, set to 1000) - // TODO: cpu frequency value might not always be true at build-time, detect at boot instead? // (also notice the discrepancy when OTA'ing between different values, as CPU *may* keep the old value) +using ClockCycles = std::chrono::duration>; -struct ClockCyclesSource { - using rep = espurna::duration::Type; +// Only micros are 64bit, millis stored as 32bit to match what is actually returned & used by Core functions +using Microseconds = std::chrono::duration; +using Milliseconds = std::chrono::duration; + +// Our own type, since a lot of things want this as a type of measurement +// (and it can be seamlessly converted from millis) +using Seconds = std::chrono::duration; + +} // namespace duration + +namespace time { + +struct CpuClock { using duration = espurna::duration::ClockCycles; + using rep = duration::rep; using period = duration::period; - using time_point = std::chrono::time_point; + using time_point = std::chrono::time_point; static constexpr bool is_steady { true }; // `"rsr %0, ccount\n" : "=a" (out) :: "memory"` on xtensa // or "soc_get_ccount()" with esp8266-idf // or "cpu_hal_get_cycle_count()" with esp-idf - // (and notably, every one of them is 32bit as the Tick) + // (and notably, every one of them is 32bit) static time_point now() noexcept { - return time_point(duration(esp_get_cycle_count())); + return time_point(duration(::esp_get_cycle_count())); } }; -} // namespace duration +inline CpuClock::time_point ccount() { + return CpuClock::now(); +} + +// chrono's system_clock and steady_clock are implemented in the libstdc++ +// at the time of writing this, `steady_clock::now()` *is* `system_clock::now()` +// (aka `std::time(nullptr)` aka `clock_gettime(CLOCK_REALTIME, ...)`) +struct SystemClock { + using duration = espurna::duration::Microseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady { true }; + + static time_point now() noexcept { + return time_point(duration(::micros64())); + } +}; + +// on esp8266 this is a sntp timeshift'ed timestamp plus `micros64()` +// resulting value is available from either +// - `_gettimeofday_r(nullptr, &timeval_struct, nullptr);`, as both seconds and microseconds +// - `std::time(...)` just as seconds +// +// notice that on boot it should be equal to the build timestamp when NTP_SUPPORT=1 +// (also, only works correctly with Cores >= 3, otherwise there are two different sources) +struct RealtimeClock { + using duration = std::chrono::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady { false }; + + static time_point now() noexcept { + return time_point(duration(::std::time(nullptr))); + } +}; + +// common 'Arduino Core' clock, fallback to 32bit and `millis()` to utilize certain math quirks +// ref. +// - https://github.com/esp8266/Arduino/issues/3078 +// - https://github.com/esp8266/Arduino/pull/4264 +struct CoreClock { + using duration = espurna::duration::Milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady { true }; + + static time_point now() noexcept { + return time_point(duration(::millis())); + } +}; + +inline SystemClock::time_point micros() { + return SystemClock::now(); +} + +inline CoreClock::time_point millis() { + return CoreClock::now(); +} + +inline void delay(CoreClock::duration value) { + ::delay(value.count()); +} + +} // namespace time namespace heartbeat { @@ -172,6 +225,7 @@ unsigned long systemFreeHeap(); unsigned long systemInitialFreeHeap(); bool eraseSDKConfig(); +void forceEraseSDKConfig(); void factoryReset(); uint32_t systemResetReason(); @@ -180,14 +234,14 @@ void systemStabilityCounter(uint8_t count); bool systemCheck(); -void customResetReason(CustomResetReason reason); +void customResetReason(CustomResetReason); CustomResetReason customResetReason(); -String customResetReasonToPayload(CustomResetReason reason); +String customResetReasonToPayload(CustomResetReason); void deferredReset(unsigned long delay, CustomResetReason reason); -bool checkNeedsReset(); +bool pendingDeferredReset(); -unsigned char systemLoadAverage(); +unsigned long systemLoadAverage(); espurna::duration::Seconds systemHeartbeatInterval(); void systemScheduleHeartbeat(); diff --git a/code/espurna/terminal.cpp b/code/espurna/terminal.cpp index aed3b0f5..d6e9bce8 100644 --- a/code/espurna/terminal.cpp +++ b/code/espurna/terminal.cpp @@ -353,8 +353,7 @@ void _terminalInitCommands() { terminalRegisterCommand(F("ERASE.CONFIG"), [](const terminal::CommandContext&) { terminalOK(); customResetReason(CustomResetReason::Terminal); - eraseSDKConfig(); - *((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494 + forceEraseSDKConfig(); }); terminalRegisterCommand(F("ADC"), [](const terminal::CommandContext& ctx) { @@ -406,11 +405,9 @@ void _terminalInitCommands() { }); terminalRegisterCommand(F("HEAP"), [](const terminal::CommandContext& ctx) { - static auto initial = systemInitialFreeHeap(); - - auto stats = systemHeapStats(); - ctx.output.printf_P(PSTR("initial: %u, available: %u, fragmentation: %hhu%%\n"), - initial, stats.available, stats.frag_pct); + const auto stats = systemHeapStats(); + ctx.output.printf_P(PSTR("initial: %lu available: %lu contiguous: %hu\n"), + systemInitialFreeHeap(), stats.available, stats.usable); terminalOK(ctx); }); @@ -422,12 +419,8 @@ void _terminalInitCommands() { }); terminalRegisterCommand(F("INFO"), [](const terminal::CommandContext& ctx) { - if (!systemCheck()) { - ctx.output.print(F("\n\n!!! device is in safe mode !!!\n\n")); - } - ctx.output.printf_P(PSTR("%s %s built %s\n"), getAppName(), getVersion(), buildTime().c_str()); - ctx.output.printf_P(PSTR("mcu: esp8266 chipid: %s\n"), getFullChipId().c_str()); + ctx.output.printf_P(PSTR("mcu: esp8266 chipid: %s freq: %hhumhz\n"), getFullChipId().c_str(), system_get_cpu_freq()); ctx.output.printf_P(PSTR("sdk: %s core: %s\n"), ESP.getSdkVersion(), getCoreVersion().c_str()); ctx.output.printf_P(PSTR("md5: %s\n"), ESP.getSketchMD5().c_str()); @@ -435,7 +428,10 @@ void _terminalInitCommands() { #if SENSOR_SUPPORT ctx.output.printf_P(PSTR("sensors: %s\n"), getEspurnaSensors()); #endif - +#if SYSTEM_CHECK_ENABLED + ctx.output.printf_P(PSTR("system: %s boot counter: %u\n"), + systemCheck() ? PSTR("OK") : PSTR("UNSTABLE"), systemStabilityCounter()); +#endif #if DEBUG_SUPPORT crashResetReason(ctx.output); #endif @@ -461,7 +457,6 @@ void _terminalInitCommands() { // app is at a normal location, [0...size), but... since it is offset by the free space, make sure it is aligned // to the sector size (...and it is expected from the getFreeSketchSpace, as the app will align to use the fixed // sector address for OTA writes). - layouts.add("sdk", 4 * SPI_FLASH_SEC_SIZE); layouts.add("eeprom", eepromSpace()); @@ -470,7 +465,6 @@ void _terminalInitCommands() { // OTA is allowed to use all but one eeprom sectors that, leaving the last one // for the settings snapshot during the update - layouts.add("ota", ota_size); layouts.add("app", app_size); diff --git a/code/espurna/utils.cpp b/code/espurna/utils.cpp index ddd31dae..ca03e74b 100644 --- a/code/espurna/utils.cpp +++ b/code/espurna/utils.cpp @@ -122,31 +122,10 @@ const char* getManufacturer() { return manufacturer; } -String buildTime() { -#if NTP_SUPPORT - constexpr const time_t ts = __UNIX_TIMESTAMP__; - tm timestruct; - gmtime_r(&ts, ×truct); - return ntpDateTime(×truct); -#else - char buffer[20]; - snprintf_P( - buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), - __TIME_YEAR__, __TIME_MONTH__, __TIME_DAY__, - __TIME_HOUR__, __TIME_MINUTE__, __TIME_SECOND__ - ); - return String(buffer); -#endif -} - -#if NTP_SUPPORT - -String getUptime() { - auto seconds = systemUptime(); - - time_t uptime = static_cast(seconds.count()); +String prettyDuration(espurna::duration::Seconds seconds) { + time_t timestamp = static_cast(seconds.count()); tm spec; - gmtime_r(&uptime, &spec); + gmtime_r(×tamp, &spec); char buffer[64]; sprintf_P(buffer, PSTR("%02dy %02dd %02dh %02dm %02ds"), @@ -156,13 +135,30 @@ String getUptime() { return String(buffer); } -#else - String getUptime() { +#if NTP_SUPPORT + return prettyDuration(systemUptime()); +#else return String(systemUptime().count(), 10); +#endif } -#endif // NTP_SUPPORT +String buildTime() { +#if NTP_SUPPORT + constexpr const time_t ts = __UNIX_TIMESTAMP__; + tm timestruct; + gmtime_r(&ts, ×truct); + return ntpDateTime(×truct); +#else + char buffer[32]; + snprintf_P( + buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), + __TIME_YEAR__, __TIME_MONTH__, __TIME_DAY__, + __TIME_HOUR__, __TIME_MINUTE__, __TIME_SECOND__ + ); + return String(buffer); +#endif +} // ----------------------------------------------------------------------------- // SSL diff --git a/code/espurna/utils.h b/code/espurna/utils.h index 621e9327..41f012db 100644 --- a/code/espurna/utils.h +++ b/code/espurna/utils.h @@ -12,9 +12,6 @@ Copyright (C) 2017-2019 by Xose Pérez #include "system.h" -extern "C" uint32_t _SPIFFS_start; -extern "C" uint32_t _SPIFFS_end; - void setDefaultHostname(); void setBoardName(); @@ -36,6 +33,7 @@ String getBoardName(); String buildTime(); bool haveRelaysOrSensors(); +String prettyDuration(espurna::duration::Seconds); String getUptime(); void infoHeapStats(const char* name, const HeapStats& stats);