diff --git a/code/espurna/led.cpp b/code/espurna/led.cpp index 539217ce..3f07ae65 100644 --- a/code/espurna/led.cpp +++ b/code/espurna/led.cpp @@ -773,6 +773,12 @@ bool status(size_t id, bool value) { return status(internal::leds[id], value); } +void turn_off() { + for (auto& led : internal::leds) { + status(led, false); + } +} + [[gnu::unused]] void pattern(Led& led, Pattern&& other) { led.pattern(std::move(other)); @@ -1076,6 +1082,9 @@ void setup() { terminal::setup(); #endif + systemBeforeSleep(turn_off); + systemAfterSleep(schedule); + ::espurnaRegisterLoop(loop); ::espurnaRegisterReload(configure); diff --git a/code/espurna/light.cpp b/code/espurna/light.cpp index b18a4d64..e1a8b80b 100644 --- a/code/espurna/light.cpp +++ b/code/espurna/light.cpp @@ -3484,6 +3484,30 @@ void _lightConfigure() { } } +void _lightSleepSetup() { + systemBeforeSleep( + []() { + size_t id = 0; + for (auto& channel : _light_channels) { + _lightProviderHandleValue(id, 0); + ++id; + + channel.value = 0; + } + + _lightProviderHandleState(false); + _lightProviderHandleUpdate(); + espurna::time::blockingDelay( + espurna::duration::Milliseconds{ 100 }); + }); + + systemAfterSleep( + []() { + _lightUpdate(false); + _light_state_changed = true; + }); +} + void _lightBoot() { const size_t Channels { _light_channels.size() }; if (Channels) { @@ -3684,6 +3708,8 @@ void lightSetup() { _lightInitCommands(); #endif + _lightSleepSetup(); + espurnaRegisterReload(_lightConfigure); espurnaRegisterLoop([]() { _lightSequenceCheck(); diff --git a/code/espurna/sensor.cpp b/code/espurna/sensor.cpp index 7bad6899..c8abb00f 100644 --- a/code/espurna/sensor.cpp +++ b/code/espurna/sensor.cpp @@ -297,9 +297,9 @@ void forEachError(T&& callback) { } struct ReadValue { - double raw; - double processed; - double filtered; + double raw; // as the sensor returns it + double processed; // after applying units and decimals + double filtered; // after applying filters, units and decimals }; enum class Filter : int { @@ -1908,10 +1908,21 @@ Value safe_value_reported(size_t index) { } // namespace magnitude +using TimeSource = espurna::time::CoreClock; + +enum class State { + None, + Initial, + Idle, + Resume, + Ready, + Reading, +}; + namespace internal { std::vector sensors; -bool ready { false }; +size_t read_count; bool real_time { build::realTimeValues() }; size_t report_every { build::reportEvery() }; @@ -3780,8 +3791,38 @@ void setup() { // Sensor initialization // ----------------------------------------------------------------------------- -void init() { - internal::ready = true; +namespace internal { + +State state; + +TimeSource::time_point last_init; +TimeSource::time_point last_reading; + +} // namespace internal + +void suspend() { + for (auto& sensor : internal::sensors) { + sensor->suspend(); + } +} + +void resume() { + internal::last_init = TimeSource::now(); + internal::last_reading = TimeSource::now(); + internal::read_count = 1; + + magnitude::forEachInstance( + [](sensor::Magnitude& instance) { + instance.filter->reset(); + }); + + for (auto& sensor : internal::sensors) { + sensor->resume(); + } +} + +bool init() { + bool out { true }; for (auto sensor : internal::sensors) { // Do not process an already initialized sensor @@ -3800,7 +3841,7 @@ void init() { DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %s (%hhu)\n"), sensor::error(error).c_str(), error); } - internal::ready = false; + out = false; break; } @@ -3818,43 +3859,73 @@ void init() { } } - if (internal::ready) { + if (out) { + internal::state = State::Ready; DEBUG_MSG_P(PSTR("[SENSOR] Finished initialization for %zu sensor(s) and %zu magnitude(s)\n"), sensor::count(), magnitude::count()); } + return out; } -void loop() { - // Continiously repeat initialization if there are still some un-initialized sensors after setup() - using TimeSource = espurna::time::CoreClock; - static auto last_init = TimeSource::now(); +bool try_init() { + const auto timestamp = TimeSource::now(); + if (timestamp - internal::last_init > initInterval()) { + internal::last_init = timestamp; + return init(); + } + + return false; +} - auto timestamp = TimeSource::now(); - if (!internal::ready && (timestamp - last_init > initInterval())) { - last_init = timestamp; - sensor::init(); +bool ready_to_read() { + const auto timestamp = TimeSource::now(); + if (timestamp - internal::last_reading > readInterval()) { + internal::last_reading = timestamp; + internal::read_count = (internal::read_count + 1) % reportEvery(); + return true; } - if (!magnitude::internal::magnitudes.size()) { + return false; +} + +bool ready_to_report() { + return internal::read_count == 0; +} + +void loop() { + // TODO: allow to do nothing + if (internal::state == State::Idle) { return; } - static auto last_update = TimeSource::now(); - static size_t report_count { 0 }; + // Continiously repeat initialization if there are still some un-initialized sensors after setup() + if (internal::state == State::None) { + internal::state = State::Initial; + } - sensor::tick(); + // General initialization, generate magnitudes from available sensors + if (internal::state == State::Initial) { + if (try_init()) { + internal::state = State::Ready; + } + } - if (timestamp - last_update > readInterval()) { - last_update = timestamp; - report_count = (report_count + 1) % reportEvery(); + // If magnitudes were initialized and we are ready, prepare to read sensor data + if (internal::state == State::Ready) { + if (magnitude::internal::magnitudes.size() != 0) { + internal::state = State::Reading; + } + } - sensor::ReadValue value { - .raw = 0.0, // as the sensor returns it - .processed = 0.0, // after applying units and decimals - .filtered = 0.0 // after applying filters, units and decimals - }; + if (internal::state != State::Reading) { + return; + } + + // Tick hook, called every loop() + sensor::tick(); + if (ready_to_read()) { // Pre-read hook, called every reading sensor::pre(); @@ -3863,6 +3934,8 @@ void loop() { const bool relay_off = (relayCount() == 1) && (relayStatus(0) == 0); #endif + auto value = sensor::ReadValue{}; + for (size_t index = 0; index < magnitude::count(); ++index) { auto& magnitude = magnitude::get(index); if (!magnitude.sensor->status()) { @@ -3913,7 +3986,7 @@ void loop() { // ------------------------------------------------------------------- // Initial status or after report counter overflows - bool report { 0 == report_count }; + bool report { ready_to_report() }; // In case magnitude was configured with ${name}MaxDelta, override report check // when the value change is greater than the delta @@ -4104,6 +4177,9 @@ void setup() { terminal::setup(); #endif + systemBeforeSleep(sensor::suspend); + systemAfterSleep(sensor::resume); + espurnaRegisterLoop(sensor::loop); espurnaRegisterReload(sensor::configure); } diff --git a/code/espurna/sensors/BaseSensor.h b/code/espurna/sensors/BaseSensor.h index ba11fc9e..76ce805e 100644 --- a/code/espurna/sensors/BaseSensor.h +++ b/code/espurna/sensors/BaseSensor.h @@ -137,6 +137,13 @@ public: virtual void begin() { } + // Suspend / resume sensor operation + virtual void suspend() { + } + + virtual void resume() { + } + // Loop-like method, call it in your main loop virtual void tick() { } diff --git a/code/espurna/system.cpp b/code/espurna/system.cpp index 158027da..a341fd04 100644 --- a/code/espurna/system.cpp +++ b/code/espurna/system.cpp @@ -143,6 +143,13 @@ String serialize(espurna::duration::ClockCycles value) { namespace sleep { namespace { +namespace internal { + +std::forward_list before; +std::forward_list after; + +} // namespace internal + constexpr auto DeepSleepWakeupPin = uint8_t{ 16 }; namespace build { @@ -268,6 +275,9 @@ private: FpmLightSleep::~FpmLightSleep() { if (_ok) { wifi_fpm_close(); + for (auto callback : internal::after) { + callback(); + } } wifi_fpm_auto_sleep_set_in_null_mode(1); @@ -293,6 +303,10 @@ FpmLightSleep::FpmLightSleep() { wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); wifi_fpm_open(); + for (auto callback : internal::before) { + callback(); + } + _ok = true; } @@ -396,6 +410,10 @@ bool deep_sleep(sleep::Microseconds time) { system_deep_sleep_set_option(RF_DEFAULT); if (system_deep_sleep(time.count())) { + for (auto callback : internal::before) { + callback(); + } + yield(); return true; } @@ -403,6 +421,14 @@ bool deep_sleep(sleep::Microseconds time) { return false; } +void before(SleepCallback callback) { + internal::before.push_front(callback); +} + +void after(SleepCallback callback) { + internal::after.push_front(callback); +} + // Force WiFi RF peripheral to power down when NULL opmode is selected void init() { wifi_fpm_auto_sleep_set_in_null_mode(1); @@ -1627,6 +1653,14 @@ bool wakeupModemForcedSleep() { return espurna::sleep::forced_wakeup(); } +void systemBeforeSleep(SleepCallback callback) { + espurna::sleep::before(callback); +} + +void systemAfterSleep(SleepCallback callback) { + espurna::sleep::after(callback); +} + bool instantLightSleep() { return espurna::sleep::forced_light_sleep(); } diff --git a/code/espurna/system.h b/code/espurna/system.h index 95ad13f9..9ffc4f87 100644 --- a/code/espurna/system.h +++ b/code/espurna/system.h @@ -412,6 +412,10 @@ 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);