- /*
-
- SYSTEM MODULE
-
- Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
-
- */
-
- #pragma once
-
- #include "settings.h"
- #include "types.h"
-
- #include <chrono>
- #include <cstdint>
- #include <limits>
-
- #include <user_interface.h>
-
- struct HeapStats {
- uint32_t available;
- uint32_t usable;
- uint8_t fragmentation;
- };
-
- enum class CustomResetReason : uint8_t {
- None,
- Button, // button event action
- Factory, // requested factory reset
- Hardware, // driver event
- Mqtt,
- Ota, // successful ota
- Rpc, // rpc (api) calls
- Rule, // rpn rule operator action
- Scheduler, // scheduled reset
- Terminal, // terminal command action
- Web, // webui action
- Stability, // stable counter action
- };
-
- namespace espurna {
- namespace sleep {
-
- // Both LIGHT and DEEP sleep accept microseconds as input
- // Effective limit is ~31bit - 1 in size
- using Microseconds = std::chrono::duration<uint32_t, std::micro>;
-
- constexpr auto FpmSleepMin = Microseconds{ 1000 };
- constexpr auto FpmSleepIndefinite = Microseconds{ 0xFFFFFFF };
-
- } // namespace sleep
-
- namespace system {
-
- struct RandomDevice {
- using result_type = uint32_t;
-
- static constexpr result_type min() {
- return std::numeric_limits<result_type>::min();
- }
-
- static constexpr result_type max() {
- return std::numeric_limits<result_type>::max();
- }
-
- uint32_t operator()() const;
- };
-
- } // namespace system
-
- namespace duration {
-
- // 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<uint32_t, std::ratio<1, F_CPU>>;
-
- namespace critical {
-
- using Microseconds = std::chrono::duration<uint16_t, std::micro>;
-
- } // namespace critical
- } // namespace duration
-
- namespace time {
- namespace critical {
-
- // Wait for the specified amount of time *without* using SDK or Core timers.
- // Supposedly, should be the same as a simple do-while loop.
- inline void delay(duration::critical::Microseconds) __attribute__((always_inline));
- inline void delay(duration::critical::Microseconds duration) {
- ::ets_delay_us(duration.count());
- }
-
- } // namespace critical
-
- struct CpuClock {
- using duration = espurna::duration::ClockCycles;
- using rep = duration::rep;
- using period = duration::period;
- using time_point = std::chrono::time_point<CpuClock, duration>;
-
- 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)
- static time_point now() noexcept {
- return time_point(duration(::esp_get_cycle_count()));
- }
- };
-
- 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, ...)`)
- //
- // notice that the `micros()` by itself relies on `system_get_time()` which uses 32bit
- // storage (...or slightly less that that) and will overflow at around 72 minute mark.
- struct SystemClock {
- using duration = espurna::duration::Microseconds;
- using rep = duration::rep;
- using period = duration::period;
- using time_point = std::chrono::time_point<SystemClock, duration>;
-
- 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<int64_t>;
- using rep = duration::rep;
- using period = duration::period;
- using time_point = std::chrono::time_point<RealtimeClock, duration>;
-
- 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<CoreClock, duration>;
-
- static constexpr bool is_steady { true };
-
- static time_point now() noexcept {
- return time_point(duration(::millis()));
- }
- };
-
- // Simple 'proxies' for most common operations
-
- inline SystemClock::time_point micros() {
- return SystemClock::now();
- }
-
- inline CoreClock::time_point millis() {
- return CoreClock::now();
- }
-
- // Attempt to sleep for N milliseconds, but this is allowed to be woken up at any point by the SDK
- inline void delay(CoreClock::duration value) {
- ::delay(value.count());
- }
-
- bool tryDelay(CoreClock::time_point start, CoreClock::duration timeout, CoreClock::duration interval);
-
- template <typename T>
- bool blockingDelay(CoreClock::duration timeout, CoreClock::duration interval, T&& blocked) {
- auto result = blocked();
-
- if (result) {
- const auto start = CoreClock::now();
- for (;;) {
- if (tryDelay(start, timeout, interval)) {
- break;
- }
-
- result = blocked();
- if (!result) {
- break;
- }
- }
- }
-
- return result;
- }
-
- // Local implementation of 'delay' that will make sure that we wait for the specified
- // time, even after being woken up. Allows to service Core tasks that are scheduled
- // in-between context switches, where the interval controls the minimum sleep time.
- bool blockingDelay(CoreClock::duration timeout, CoreClock::duration interval);
- bool blockingDelay(CoreClock::duration timeout);
-
- } // namespace time
-
- namespace timer {
-
- struct SystemTimer {
- using TimeSource = time::CoreClock;
- using Duration = TimeSource::duration;
-
- static constexpr Duration DurationMin = Duration(5);
-
- SystemTimer();
- ~SystemTimer() {
- stop();
- }
-
- SystemTimer(const SystemTimer&) = delete;
- SystemTimer& operator=(const SystemTimer&) = delete;
-
- SystemTimer(SystemTimer&&) = default;
- SystemTimer& operator=(SystemTimer&&) = default;
-
- explicit operator bool() const {
- return _armed != nullptr;
- }
-
- void once(Duration duration, Callback callback) {
- start(duration, std::move(callback), false);
- }
-
- void repeat(Duration duration, Callback callback) {
- start(duration, std::move(callback), true);
- }
-
- void schedule_once(Duration, Callback);
- void stop();
-
- private:
- // limit is per https://www.espressif.com/sites/default/files/documentation/2c-esp8266_non_os_sdk_api_reference_en.pdf
- // > 3.1.1 os_timer_arm
- // > with `system_timer_reinit()`, the timer value allowed ranges from 100 to 0x0x689D0.
- // > otherwise, the timer value allowed ranges from 5 to 0x68D7A3.
- // with current implementation we use division by 2 until we reach value less than this one
- static constexpr Duration DurationMax = Duration(6870947);
-
- void reset();
- void start(Duration, Callback, bool repeat);
- void callback();
-
- struct Tick {
- size_t total;
- size_t count;
- };
-
- Callback _callback;
-
- os_timer_t* _armed { nullptr };
- bool _repeat { false };
-
- std::unique_ptr<Tick> _tick;
- std::unique_ptr<os_timer_t> _timer;
- };
-
- } // namespace timer
-
- namespace heartbeat {
-
- using Mask = int32_t;
- using Callback = bool(*)(Mask);
-
- enum class Mode {
- None,
- Once,
- Repeat
- };
-
- enum class Report : Mask {
- Status = 1 << 1,
- Ssid = 1 << 2,
- Ip = 1 << 3,
- Mac = 1 << 4,
- Rssi = 1 << 5,
- Uptime = 1 << 6,
- Datetime = 1 << 7,
- Freeheap = 1 << 8,
- Vcc = 1 << 9,
- Relay = 1 << 10,
- Light = 1 << 11,
- Hostname = 1 << 12,
- App = 1 << 13,
- Version = 1 << 14,
- Board = 1 << 15,
- Loadavg = 1 << 16,
- Interval = 1 << 17,
- Description = 1 << 18,
- Range = 1 << 19,
- RemoteTemp = 1 << 20,
- Bssid = 1 << 21
- };
-
- constexpr Mask operator*(Report lhs, Mask rhs) {
- return static_cast<Mask>(lhs) * rhs;
- }
-
- constexpr Mask operator*(Mask lhs, Report rhs) {
- return lhs * static_cast<Mask>(rhs);
- }
-
- constexpr Mask operator|(Report lhs, Report rhs) {
- return static_cast<Mask>(lhs) | static_cast<Mask>(rhs);
- }
-
- constexpr Mask operator|(Report lhs, Mask rhs) {
- return static_cast<Mask>(lhs) | rhs;
- }
-
- constexpr Mask operator|(Mask lhs, Report rhs) {
- return lhs | static_cast<Mask>(rhs);
- }
-
- constexpr Mask operator&(Report lhs, Mask rhs) {
- return static_cast<Mask>(lhs) & rhs;
- }
-
- constexpr Mask operator&(Mask lhs, Report rhs) {
- return lhs & static_cast<Mask>(rhs);
- }
-
- constexpr Mask operator&(Report lhs, Report rhs) {
- return static_cast<Mask>(lhs) & static_cast<Mask>(rhs);
- }
-
- espurna::duration::Seconds currentInterval();
- espurna::duration::Milliseconds currentIntervalMs();
-
- Mask currentValue();
- Mode currentMode();
-
- } // namespace heartbeat
-
- namespace sleep {
-
- enum class Interrupt {
- Low,
- High,
- };
-
- } // namespace sleep
-
- namespace settings {
- namespace internal {
-
- template <>
- heartbeat::Mode convert(const String&);
-
- String serialize(heartbeat::Mode);
- String serialize(duration::Seconds);
- String serialize(duration::Milliseconds);
- String serialize(duration::ClockCycles);
-
- } // namespace internal
- } // namespace settings
- } // namespace espurna
-
- uint32_t randomNumber(uint32_t minimum, uint32_t maximum);
- uint32_t randomNumber();
-
- unsigned long systemFreeStack();
-
- HeapStats systemHeapStats();
-
- size_t systemFreeHeap();
- size_t systemInitialFreeHeap();
-
- bool eraseSDKConfig();
- void forceEraseSDKConfig();
- void factoryReset();
-
- uint32_t systemResetReason();
- uint8_t systemStabilityCounter();
- void systemStabilityCounter(uint8_t count);
-
- void systemForceStable();
- void systemForceUnstable();
- bool systemCheck();
-
- void customResetReason(CustomResetReason);
- CustomResetReason customResetReason();
- String customResetReasonToPayload(CustomResetReason);
-
- void deferredReset(espurna::duration::Milliseconds, CustomResetReason);
- void prepareReset(CustomResetReason);
- bool pendingDeferredReset();
-
- bool wakeupModemForcedSleep();
- bool prepareModemForcedSleep();
-
- using SleepCallback = void (*)();
- void systemBeforeSleep(SleepCallback);
- void systemAfterSleep(SleepCallback);
-
- bool instantLightSleep();
- bool instantLightSleep(espurna::sleep::Microseconds);
- bool instantLightSleep(uint8_t pin, espurna::sleep::Interrupt);
-
- bool instantDeepSleep(espurna::sleep::Microseconds);
-
- unsigned long systemLoadAverage();
-
- espurna::duration::Seconds systemHeartbeatInterval();
- void systemScheduleHeartbeat();
-
- void systemStopHeartbeat(espurna::heartbeat::Callback);
- void systemHeartbeat(espurna::heartbeat::Callback, espurna::heartbeat::Mode, espurna::duration::Seconds interval);
- void systemHeartbeat(espurna::heartbeat::Callback, espurna::heartbeat::Mode);
- void systemHeartbeat(espurna::heartbeat::Callback);
- bool systemHeartbeat();
-
- espurna::duration::Seconds systemUptime();
-
- espurna::StringView systemDevice();
- espurna::StringView systemIdentifier();
-
- espurna::StringView systemChipId();
- espurna::StringView systemShortChipId();
-
- espurna::StringView systemDefaultPassword();
-
- String systemPassword();
- bool systemPasswordEquals(espurna::StringView);
-
- String systemHostname();
- String systemDescription();
-
- void systemSetup();
|