Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

450 lines
12 KiB

/*
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();