Browse Source

system: fixup timers and loop callbacks

* globally accessible system timer class; help out with our internal
  scheduling by always using strongly typed duration and dynamic
  time adjustment for durations longer than system limits
  (see `os_timer_t` documentation)
* our own class for scheduled callbacks which are either choosing a
  simple function pointer or std function wrapper, depending on the type
  passed into the constructor. specifically for scheduled functions,
  this allows us to filter globally scheduled functions and push them to
  the front of the queue when necessary to ensure certain order of calls
* replace Ticker instances with SystemTimer
* allow types {h, cpp} in unit tests
pull/2552/head
Maxim Prokhorov 1 year ago
parent
commit
510d68d079
29 changed files with 954 additions and 292 deletions
  1. +1
    -1
      code/espurna/debug.cpp
  2. +3
    -0
      code/espurna/espurna.h
  3. +7
    -7
      code/espurna/homeassistant.cpp
  4. +87
    -26
      code/espurna/light.cpp
  5. +1
    -2
      code/espurna/light.h
  6. +82
    -27
      code/espurna/main.cpp
  7. +7
    -7
      code/espurna/mqtt.cpp
  8. +18
    -25
      code/espurna/ntp.cpp
  9. +1
    -1
      code/espurna/ota_asynctcp.cpp
  10. +19
    -9
      code/espurna/ota_httpupdate.cpp
  11. +46
    -116
      code/espurna/relay.cpp
  12. +6
    -4
      code/espurna/rpc.cpp
  13. +1
    -1
      code/espurna/rpnrules.cpp
  14. +100
    -16
      code/espurna/system.cpp
  15. +62
    -0
      code/espurna/system.h
  16. +2
    -2
      code/espurna/terminal.cpp
  17. +117
    -0
      code/espurna/types.cpp
  18. +141
    -1
      code/espurna/types.h
  19. +1
    -3
      code/espurna/web_asyncwebprint.ipp
  20. +18
    -16
      code/espurna/wifi.cpp
  21. +1
    -3
      code/espurna/wifi.h
  22. +1
    -5
      code/espurna/ws.cpp
  23. +1
    -1
      code/espurna/ws.h
  24. +3
    -2
      code/test/unit/CMakeLists.txt
  25. +1
    -1
      code/test/unit/src/settings/settings.cpp
  26. +2
    -9
      code/test/unit/src/terminal/terminal.cpp
  27. +6
    -6
      code/test/unit/src/tuya/tuya.cpp
  28. +218
    -0
      code/test/unit/src/types/types.cpp
  29. +1
    -1
      code/test/unit/src/url/url.cpp

+ 1
- 1
code/espurna/debug.cpp View File

@ -191,7 +191,7 @@ void enable() {
void delayedEnable() { void delayedEnable() {
disable(); disable();
schedule_function(enable);
::espurnaRegisterOnce(enable);
} }
void send(const char* message, size_t len, Timestamp); void send(const char* message, size_t len, Timestamp);


+ 3
- 0
code/espurna/espurna.h View File

@ -62,6 +62,9 @@ void espurnaReload();
using LoopCallback = void (*)(); using LoopCallback = void (*)();
void espurnaRegisterLoop(LoopCallback); void espurnaRegisterLoop(LoopCallback);
void espurnaRegisterOnce(espurna::Callback);
void espurnaRegisterOnceUnique(espurna::Callback::Type);
espurna::duration::Milliseconds espurnaLoopDelay(); espurna::duration::Milliseconds espurnaLoopDelay();
void espurnaLoopDelay(espurna::duration::Milliseconds); void espurnaLoopDelay(espurna::duration::Milliseconds);


+ 7
- 7
code/espurna/homeassistant.cpp View File

@ -905,12 +905,12 @@ enum class State {
}; };
State state { State::Initial }; State state { State::Initial };
Ticker timer;
timer::SystemTimer timer;
void send(TaskPtr ptr, FlagPtr flag_ptr); void send(TaskPtr ptr, FlagPtr flag_ptr);
void stop(bool done) { void stop(bool done) {
timer.detach();
timer.stop();
if (done) { if (done) {
DEBUG_MSG_P(PSTR("[HA] Stopping discovery\n")); DEBUG_MSG_P(PSTR("[HA] Stopping discovery\n"));
state = State::Sent; state = State::Sent;
@ -921,8 +921,8 @@ void stop(bool done) {
} }
void schedule(espurna::duration::Milliseconds wait, TaskPtr ptr, FlagPtr flag_ptr) { void schedule(espurna::duration::Milliseconds wait, TaskPtr ptr, FlagPtr flag_ptr) {
internal::timer.once_ms_scheduled(
wait.count(),
timer.schedule_once(
wait,
[ptr, flag_ptr]() { [ptr, flag_ptr]() {
send(ptr, flag_ptr); send(ptr, flag_ptr);
}); });
@ -989,7 +989,7 @@ void send(TaskPtr ptr, FlagPtr flag_ptr) {
} // namespace internal } // namespace internal
void publishDiscovery() { void publishDiscovery() {
if (!mqttConnected() || internal::timer.active() || (internal::state != internal::State::Pending)) {
if (!mqttConnected() || internal::timer || (internal::state != internal::State::Pending)) {
return; return;
} }
@ -1031,7 +1031,7 @@ void mqttCallback(unsigned int type, const char* topic, char* payload) {
if (internal::state == internal::State::Sent) { if (internal::state == internal::State::Sent) {
internal::state = internal::State::Pending; internal::state = internal::State::Pending;
} }
internal::timer.detach();
internal::timer.stop();
return; return;
} }
@ -1039,7 +1039,7 @@ void mqttCallback(unsigned int type, const char* topic, char* payload) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
::mqttSubscribe(MQTT_TOPIC_LIGHT_JSON); ::mqttSubscribe(MQTT_TOPIC_LIGHT_JSON);
#endif #endif
::schedule_function(publishDiscovery);
::espurnaRegisterOnce(publishDiscovery);
return; return;
} }


+ 87
- 26
code/espurna/light.cpp View File

@ -18,8 +18,6 @@ Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com
#include "rtcmem.h" #include "rtcmem.h"
#include "ws.h" #include "ws.h"
#include <Ticker.h>
#include <Schedule.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <array> #include <array>
@ -638,13 +636,52 @@ void _lightUpdateMapping(T& channels) {
} }
} }
template <typename T>
struct LightTimerValue {
using Timer = espurna::timer::SystemTimer;
using Duration = Timer::Duration;
LightTimerValue() = delete;
constexpr LightTimerValue(T defaultValue) :
_defaultValue(defaultValue)
{}
explicit operator bool() const {
return _value != _defaultValue;
}
void wait_set(Duration duration, T value) {
_timer.once(
duration,
[&]() {
_value = value;
});
}
void reset() {
_value = _defaultValue;
}
T get() {
const auto value = _value;
reset();
return value;
}
private:
T _defaultValue;
T _value;
espurna::timer::SystemTimer _timer;
};
auto _light_save_delay = espurna::light::build::saveDelay(); auto _light_save_delay = espurna::light::build::saveDelay();
bool _light_save { espurna::light::build::save() }; bool _light_save { espurna::light::build::save() };
Ticker _light_save_ticker;
LightTimerValue<bool> _light_save_timer(false);
auto _light_report_delay = espurna::light::build::reportDelay(); auto _light_report_delay = espurna::light::build::reportDelay();
Ticker _light_report_ticker;
std::forward_list<LightReportListener> _light_report; std::forward_list<LightReportListener> _light_report;
LightTimerValue<int> _light_report_timer(0);
bool _light_has_controls = false; bool _light_has_controls = false;
bool _light_has_cold_white = false; bool _light_has_cold_white = false;
@ -1675,11 +1712,6 @@ private:
}; };
struct LightSequenceHandler { struct LightSequenceHandler {
LightSequenceHandler& operator=(const LightSequenceCallbacks& callbacks) {
_callbacks = callbacks;
return *this;
}
LightSequenceHandler& operator=(LightSequenceCallbacks&& callbacks) { LightSequenceHandler& operator=(LightSequenceCallbacks&& callbacks) {
_callbacks = std::move(callbacks); _callbacks = std::move(callbacks);
return *this; return *this;
@ -1701,12 +1733,37 @@ private:
LightSequenceCallbacks _callbacks; LightSequenceCallbacks _callbacks;
}; };
struct LightProviderHandler {
using Timer = espurna::timer::SystemTimer;
using Duration = Timer::Duration;
LightProviderHandler() = default;
explicit operator bool() const {
return _ready;
}
void stop() {
_ready = false;
_timer.stop();
}
void start(Duration duration) {
_timer.once(duration,
[&]() {
_ready = true;
});
}
private:
Timer _timer;
bool _ready { false };
};
LightUpdateHandler _light_update; LightUpdateHandler _light_update;
bool _light_provider_update = false;
LightProviderHandler _light_provider_update;
LightSequenceHandler _light_sequence; LightSequenceHandler _light_sequence;
Ticker _light_transition_ticker;
std::unique_ptr<LightTransitionHandler> _light_transition; std::unique_ptr<LightTransitionHandler> _light_transition;
auto _light_transition_time = espurna::light::build::transitionTime(); auto _light_transition_time = espurna::light::build::transitionTime();
@ -1805,7 +1862,7 @@ void _lightProviderUpdate() {
} }
if (!_light_transition) { if (!_light_transition) {
_light_provider_update = false;
_light_provider_update.stop();
return; return;
} }
@ -1820,13 +1877,11 @@ void _lightProviderUpdate() {
_light_transition.reset(nullptr); _light_transition.reset(nullptr);
} }
_light_provider_update = false;
_light_provider_update.stop();
} }
void _lightProviderSchedule(espurna::duration::Milliseconds next) { void _lightProviderSchedule(espurna::duration::Milliseconds next) {
_light_transition_ticker.once_ms(next.count(), []() {
_light_provider_update = true;
});
_light_provider_update.start(next);
} }
} // namespace } // namespace
@ -2894,6 +2949,17 @@ void _lightSequenceCheck() {
} }
} }
void _lightPostLoop() {
if (_light_report_timer) {
_lightReport(_light_report_timer.get());
}
if (_light_save_timer) {
_light_save_timer.reset();
_lightSaveSettings();
}
}
void _lightUpdate() { void _lightUpdate() {
if (!_light_update) { if (!_light_update) {
return; return;
@ -2917,18 +2983,12 @@ void _lightUpdate() {
// Send current state to all available 'report' targets // Send current state to all available 'report' targets
// (make sure to delay the report, in case lightUpdate is called repeatedly) // (make sure to delay the report, in case lightUpdate is called repeatedly)
_light_report_ticker.once_ms(
_light_report_delay.count(),
[report]() {
_lightReport(report);
});
_light_report_timer.wait_set(_light_report_delay, report);
// Always save to RTCMEM, optionally preserve the state in the settings storage // Always save to RTCMEM, optionally preserve the state in the settings storage
_lightSaveRtcmem(); _lightSaveRtcmem();
if (save) { if (save) {
_light_save_ticker.once_ms(
_light_save_delay.count(),
_lightSaveSettings);
_light_save_timer.wait_set(_light_save_delay, true);
} }
}); });
} }
@ -3337,7 +3397,7 @@ bool lightAdd() {
_light_channels.emplace_back(LightChannel()); _light_channels.emplace_back(LightChannel());
if (State::Scheduled != state) { if (State::Scheduled != state) {
state = State::Scheduled; state = State::Scheduled;
schedule_function([]() {
espurnaRegisterOnceUnique([]() {
_lightBoot(); _lightBoot();
state = State::Done; state = State::Done;
}); });
@ -3482,6 +3542,7 @@ void lightSetup() {
_lightSequenceCheck(); _lightSequenceCheck();
_lightUpdate(); _lightUpdate();
_lightProviderUpdate(); _lightProviderUpdate();
_lightPostLoop();
}); });
} }


+ 1
- 2
code/espurna/light.h View File

@ -197,8 +197,7 @@ void lightTransition(LightTransition);
// Light internals are forced to be sequential. In case some actions need to happen // Light internals are forced to be sequential. In case some actions need to happen
// right after transition / channel state / state changes, it will call these functions // right after transition / channel state / state changes, it will call these functions
using LightSequenceCallback = std::function<void()>;
using LightSequenceCallbacks = std::forward_list<LightSequenceCallback>;
using LightSequenceCallbacks = std::forward_list<espurna::Callback>;
void lightSequence(LightSequenceCallbacks); void lightSequence(LightSequenceCallbacks);
void lightUpdateSequence(LightTransition); void lightUpdateSequence(LightTransition);


+ 82
- 27
code/espurna/main.cpp View File

@ -23,6 +23,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "espurna.h" #include "espurna.h"
#include "main.h" #include "main.h"
#include <algorithm>
#include <utility>
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// GENERAL CALLBACKS // GENERAL CALLBACKS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -47,24 +50,35 @@ constexpr espurna::duration::Milliseconds loopDelay() {
} // namespace build } // namespace build
namespace settings { namespace settings {
namespace keys {
alignas(4) static constexpr char LoopDelay[] PROGMEM = "loopDelay";
} // namespace keys
espurna::duration::Milliseconds loopDelay() { espurna::duration::Milliseconds loopDelay() {
return std::clamp(getSetting("loopDelay", build::loopDelay()), build::LoopDelayMin, build::LoopDelayMax);
return std::clamp(getSetting(keys::LoopDelay, build::loopDelay()), build::LoopDelayMin, build::LoopDelayMax);
} }
} // namespace settings } // namespace settings
namespace internal { namespace internal {
std::vector<LoopCallback> reload_callbacks;
bool reload_flag { false };
std::vector<LoopCallback> loop_callbacks; std::vector<LoopCallback> loop_callbacks;
espurna::duration::Milliseconds loop_delay { build::LoopDelayMin }; espurna::duration::Milliseconds loop_delay { build::LoopDelayMin };
std::vector<LoopCallback> reload_callbacks;
bool reload_flag { false };
std::forward_list<Callback> once_callbacks;
} // namespace internal } // namespace internal
bool reload() {
void flag_reload() {
internal::reload_flag = true;
}
bool check_reload() {
if (internal::reload_flag) { if (internal::reload_flag) {
internal::reload_flag = false; internal::reload_flag = false;
return true; return true;
@ -73,18 +87,68 @@ bool reload() {
return false; return false;
} }
void push_reload(ReloadCallback callback) {
internal::reload_callbacks.push_back(callback);
}
void push_loop(LoopCallback callback) {
internal::loop_callbacks.push_back(callback);
}
duration::Milliseconds loop_delay() {
return internal::loop_delay;
}
void loop_delay(duration::Milliseconds value) {
internal::loop_delay = value;
}
void push_once(Callback callback) {
internal::once_callbacks.push_front(std::move(callback));
}
void push_once_unique(Callback::Type callback) {
auto& callbacks = internal::once_callbacks;
auto it = std::find_if(
callbacks.begin(),
callbacks.end(),
[&](const Callback& other) {
return other == callback;
});
if ((it != callbacks.begin()) && (it != callbacks.end())) {
std::swap(*callbacks.begin(), *it);
return;
}
push_once(Callback(callback));
}
void loop() { void loop() {
// Reload config before running any callbacks // Reload config before running any callbacks
if (reload()) {
if (check_reload()) {
for (const auto& callback : internal::reload_callbacks) { for (const auto& callback : internal::reload_callbacks) {
callback(); callback();
} }
} }
// Loop callbacks, registered some time in setup()
// Notice that everything is in order of registration
for (const auto& callback : internal::loop_callbacks) { for (const auto& callback : internal::loop_callbacks) {
callback(); callback();
} }
// One-time callbacks, registered some time during runtime
// Notice that callback container is LIFO, most recently added
// callback is called first. Copy to allow container modifications.
decltype(internal::once_callbacks) once_callbacks;
once_callbacks.swap(internal::once_callbacks);
for (const auto& callback : once_callbacks) {
callback();
}
espurna::time::delay(internal::loop_delay); espurna::time::delay(internal::loop_delay);
} }
@ -313,43 +377,34 @@ void setup() {
} // namespace main } // namespace main
} // namespace } // namespace
} // namespace espurna
bool StringView::equals(StringView other) const {
if (other._len == _len) {
if (inFlash(_ptr) && inFlash(other._ptr)) {
return _ptr == other._ptr;
} else if (inFlash(_ptr)) {
return memcmp_P(other._ptr, _ptr, _len) == 0;
} else if (inFlash(other._ptr)) {
return memcmp_P(_ptr, other._ptr, _len) == 0;
}
return __builtin_memcmp(_ptr, other._ptr, _len) == 0;
}
return false;
void espurnaRegisterOnce(espurna::Callback callback) {
espurna::main::push_once(std::move(callback));
} }
} // namespace espurna
void espurnaRegisterLoop(LoopCallback callback) {
espurna::main::internal::loop_callbacks.push_back(callback);
void espurnaRegisterOnceUnique(espurna::Callback::Type ptr) {
espurna::main::push_once_unique(ptr);
} }
void espurnaRegisterReload(LoopCallback callback) { void espurnaRegisterReload(LoopCallback callback) {
espurna::main::internal::reload_callbacks.push_back(callback);
espurna::main::push_reload(callback);
}
void espurnaRegisterLoop(LoopCallback callback) {
espurna::main::push_loop(callback);
} }
void espurnaReload() { void espurnaReload() {
espurna::main::internal::reload_flag = true;
espurna::main::flag_reload();
} }
espurna::duration::Milliseconds espurnaLoopDelay() { espurna::duration::Milliseconds espurnaLoopDelay() {
return espurna::main::internal::loop_delay;
return espurna::main::loop_delay();
} }
void espurnaLoopDelay(espurna::duration::Milliseconds value) { void espurnaLoopDelay(espurna::duration::Milliseconds value) {
espurna::main::internal::loop_delay = value;
espurna::main::loop_delay(value);
} }
void setup() { void setup() {


+ 7
- 7
code/espurna/mqtt.cpp View File

@ -13,7 +13,6 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot
#include <forward_list> #include <forward_list>
#include <utility> #include <utility>
#include <Ticker.h>
#include "system.h" #include "system.h"
#include "mdns.h" #include "mdns.h"
@ -537,7 +536,7 @@ private:
size_t _mqtt_json_payload_count { 0ul }; size_t _mqtt_json_payload_count { 0ul };
std::forward_list<MqttPayload> _mqtt_json_payload; std::forward_list<MqttPayload> _mqtt_json_payload;
Ticker _mqtt_json_payload_flush;
espurna::timer::SystemTimer _mqtt_json_payload_flush;
} // namespace } // namespace
@ -795,16 +794,16 @@ void _mqttConfigure() {
#if MDNS_SERVER_SUPPORT #if MDNS_SERVER_SUPPORT
constexpr unsigned long MqttMdnsDiscoveryInterval { 15000 };
Ticker _mqtt_mdns_discovery;
constexpr auto MqttMdnsDiscoveryInterval = espurna::duration::Seconds(15);
espurna::timer::SystemTimer _mqtt_mdns_discovery;
void _mqttMdnsStop() { void _mqttMdnsStop() {
_mqtt_mdns_discovery.detach();
_mqtt_mdns_discovery.stop();
} }
void _mqttMdnsDiscovery(); void _mqttMdnsDiscovery();
void _mqttMdnsSchedule() { void _mqttMdnsSchedule() {
_mqtt_mdns_discovery.once_ms_scheduled(MqttMdnsDiscoveryInterval, _mqttMdnsDiscovery);
_mqtt_mdns_discovery.once(MqttMdnsDiscoveryInterval, _mqttMdnsDiscovery);
} }
void _mqttMdnsDiscovery() { void _mqttMdnsDiscovery() {
@ -1369,7 +1368,8 @@ uint16_t mqttSendRaw(const char * topic, const char * message) {
bool mqttSend(const char * topic, const char * message, bool force, bool retain) { bool mqttSend(const char * topic, const char * message, bool force, bool retain) {
if (!force && _mqtt_use_json) { if (!force && _mqtt_use_json) {
mqttEnqueue(topic, message); mqttEnqueue(topic, message);
_mqtt_json_payload_flush.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
_mqtt_json_payload_flush.once(
espurna::duration::Milliseconds(MQTT_USE_JSON_DELAY), mqttFlush);
return true; return true;
} }


+ 18
- 25
code/espurna/ntp.cpp View File

@ -17,7 +17,6 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#include <Arduino.h> #include <Arduino.h>
#include <coredecls.h> #include <coredecls.h>
#include <Ticker.h>
#include <ctime> #include <ctime>
#include <errno.h> #include <errno.h>
@ -592,7 +591,7 @@ void report() {
} }
const auto info = makeInfo(); const auto info = makeInfo();
DEBUG_MSG_P(PSTR("[NTP] Server %s\n"), internal::server.c_str());
DEBUG_MSG_P(PSTR("[NTP] Server %s\n"), ntp::internal::server.c_str());
DEBUG_MSG_P(PSTR("[NTP] Last Sync %s (UTC)\n"), info.sync.c_str()); DEBUG_MSG_P(PSTR("[NTP] Last Sync %s (UTC)\n"), info.sync.c_str());
DEBUG_MSG_P(PSTR("[NTP] UTC Time %s\n"), info.utc.c_str()); DEBUG_MSG_P(PSTR("[NTP] UTC Time %s\n"), info.utc.c_str());
@ -602,11 +601,17 @@ void report() {
} }
} }
void schedule_now() {
::espurnaRegisterOnceUnique(report);
}
} // namespace debug } // namespace debug
#endif #endif
namespace tick { namespace tick {
// Never allow delays less than a second, or greater than a minute
// (ref. Non-OS 3.1.1 os_timer_arm, actual minimal value is 5ms)
static constexpr espurna::duration::Seconds OffsetMin { 1 }; static constexpr espurna::duration::Seconds OffsetMin { 1 };
static constexpr espurna::duration::Seconds OffsetMax { 60 }; static constexpr espurna::duration::Seconds OffsetMax { 60 };
@ -615,7 +620,7 @@ using Callbacks = std::forward_list<NtpTickCallback>;
namespace internal { namespace internal {
Callbacks callbacks; Callbacks callbacks;
Ticker timer;
timer::SystemTimer timer;
} // namespace internal } // namespace internal
@ -660,27 +665,15 @@ void callback() {
schedule(OffsetMax - espurna::duration::Seconds(local_tm.tm_sec)); schedule(OffsetMax - espurna::duration::Seconds(local_tm.tm_sec));
} }
void schedule(espurna::duration::Seconds offset) {
static bool scheduled { false };
// Never allow delays less than a second, or greater than a minute
// (ref. Non-OS 3.1.1 os_timer_arm, actual minimal value is 5ms)
if (!scheduled) {
scheduled = true;
internal::timer.once_scheduled(
std::clamp(offset, OffsetMin, OffsetMax).count(),
[]() {
scheduled = false;
callback();
});
}
void schedule_now() {
::espurnaRegisterOnceUnique(callback);
} }
void init() {
static bool initialized { false };
if (!initialized) {
schedule_function(callback);
initialized = true;
void schedule(espurna::duration::Seconds offset) {
if (!internal::timer) {
internal::timer.once(
std::clamp(offset, OffsetMin, OffsetMax),
schedule_now);
} }
} }
@ -688,13 +681,13 @@ void init() {
void onSystemTimeSynced() { void onSystemTimeSynced() {
internal::status.update(::time(nullptr)); internal::status.update(::time(nullptr));
tick::init();
tick::schedule_now();
#if WEB_SUPPORT #if WEB_SUPPORT
wsPost(web::onData); wsPost(web::onData);
#endif #endif
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
schedule_function(debug::report);
debug::schedule_now();
#endif #endif
} }
@ -801,7 +794,7 @@ void onStationModeGotIP(WiFiEventStationModeGotIP) {
DEBUG_MSG_P(PSTR("[NTP] Updating `ntpDhcp` to ignore the DHCP values\n")); DEBUG_MSG_P(PSTR("[NTP] Updating `ntpDhcp` to ignore the DHCP values\n"));
settings::dhcp(false); settings::dhcp(false);
sntp_servermode_dhcp(0); sntp_servermode_dhcp(0);
schedule_function(configure);
::espurnaRegisterOnce(configure);
return; return;
} }


+ 1
- 1
code/espurna/ota_asynctcp.cpp View File

@ -104,7 +104,7 @@ void disconnect() {
void onDisconnect(void* arg, AsyncClient*) { void onDisconnect(void* arg, AsyncClient*) {
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
otaFinalize(reinterpret_cast<BasicHttpClient*>(arg)->size, CustomResetReason::Ota, true); otaFinalize(reinterpret_cast<BasicHttpClient*>(arg)->size, CustomResetReason::Ota, true);
schedule_function(internal::disconnect);
espurnaRegisterOnce(internal::disconnect);
} }
void onTimeout(void*, AsyncClient* client, uint32_t) { void onTimeout(void*, AsyncClient* client, uint32_t) {


+ 19
- 9
code/espurna/ota_httpupdate.cpp View File

@ -126,6 +126,12 @@ void clientFromHttps(const String& url) {
#endif // SECURE_CLIENT_BEARSSL #endif // SECURE_CLIENT_BEARSSL
namespace internal {
String url;
} // namespace internal
void clientFromUrl(const String& url) { void clientFromUrl(const String& url) {
if (url.startsWith("http://")) { if (url.startsWith("http://")) {
clientFromHttp(url); clientFromHttp(url);
@ -141,6 +147,17 @@ void clientFromUrl(const String& url) {
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
} }
void clientFromInternalUrl() {
const auto url = std::move(internal::url);
clientFromUrl(url);
}
[[gnu::unused]]
void clientQueueUrl(String url) {
internal::url = std::move(url);
espurnaRegisterOnceUnique(clientFromInternalUrl);
}
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
alignas(4) static constexpr char OtaCommand[] PROGMEM = "OTA"; alignas(4) static constexpr char OtaCommand[] PROGMEM = "OTA";
@ -173,16 +190,9 @@ void mqttCallback(unsigned int type, const char* topic, char* payload) {
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
const String t = mqttMagnitude(topic); const String t = mqttMagnitude(topic);
static bool busy { false };
if (!busy && t.equals(MQTT_TOPIC_OTA)) {
if (!internal::url.length() && t.equals(MQTT_TOPIC_OTA)) {
DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload); DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload);
const String url(payload);
schedule_function([url]() {
clientFromUrl(url);
busy = false;
});
clientQueueUrl(payload);
} }
return; return;


+ 46
- 116
code/espurna/relay.cpp View File

@ -363,15 +363,7 @@ Mode mode(size_t index) {
namespace { namespace {
struct Timer { struct Timer {
// 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.
static constexpr auto DurationMin = Duration { 5 };
static constexpr auto DurationMax = Duration { espurna::duration::Hours { 1 } };
using TimeSource = espurna::time::CoreClock;
using Duration = timer::SystemTimer::Duration;
Timer() = delete; Timer() = delete;
Timer(const Timer&) = delete; Timer(const Timer&) = delete;
@ -384,14 +376,14 @@ struct Timer {
{} {}
~Timer() { ~Timer() {
stop();
_timer.stop();
} }
Timer& operator=(const Timer&) = delete; Timer& operator=(const Timer&) = delete;
Timer& operator=(Timer&&) = delete; Timer& operator=(Timer&&) = delete;
explicit operator bool() const { explicit operator bool() const {
return _armed;
return static_cast<bool>(_timer);
} }
bool operator==(const Timer& other) const { bool operator==(const Timer& other) const {
@ -420,55 +412,29 @@ struct Timer {
} }
void stop() { void stop() {
if (_armed) {
os_timer_disarm(&_timer);
_timer = os_timer_t{};
_armed = false;
}
_timer.stop();
} }
void start() { void start() {
stop();
auto delay = std::clamp(_duration, DurationMin, DurationMax);
os_timer_setfn(&_timer, timerCallback, this);
os_timer_arm(&_timer, delay.count(), 0);
_start = TimeSource::now();
_armed = true;
const auto id = _id;
const auto status = _status;
_timer.once(
(_duration.count() > 0)
? _duration
: timer::SystemTimer::DurationMin,
[id, status]() {
relayStatus(id, status);
});
} }
private: private:
void check() {
auto elapsed = TimeSource::now() - _start;
if (elapsed <= _duration) {
auto left = std::clamp(_duration - elapsed, DurationMin, DurationMax);
if (left != DurationMin) {
os_timer_arm(&_timer, left.count(), 0);
return;
}
}
relayStatus(_id, _status);
stop();
}
static void timerCallback(void* arg) {
reinterpret_cast<Timer*>(arg)->check();
}
Duration _duration; Duration _duration;
size_t _id; size_t _id;
bool _status; bool _status;
TimeSource::time_point _start;
bool _armed { false };
os_timer_t _timer {};
timer::SystemTimer _timer;
}; };
constexpr Duration Timer::DurationMin;
constexpr Duration Timer::DurationMax;
namespace internal { namespace internal {
std::forward_list<Timer> timers; std::forward_list<Timer> timers;
@ -1087,7 +1053,8 @@ public:
namespace { namespace {
struct RelaySaveTimer { struct RelaySaveTimer {
using Duration = espurna::duration::Milliseconds;
using Timer = espurna::timer::SystemTimer;
using Duration = Timer::Duration;
RelaySaveTimer() = default; RelaySaveTimer() = default;
@ -1098,33 +1065,28 @@ struct RelaySaveTimer {
RelaySaveTimer& operator=(RelaySaveTimer&&) = delete; RelaySaveTimer& operator=(RelaySaveTimer&&) = delete;
~RelaySaveTimer() { ~RelaySaveTimer() {
stop();
_timer.stop();
} }
void schedule(Duration duration) { void schedule(Duration duration) {
stop();
os_timer_setfn(&_timer, timerCallback, this);
os_timer_arm(&_timer, duration.count(), 0);
_armed = true;
_timer.once(
duration,
[&]() {
_ready = true;
});
} }
void stop() { void stop() {
if (_armed) {
os_timer_disarm(&_timer);
_timer = os_timer_t{};
_done = false;
_armed = false;
_persist = false;
}
_timer.stop();
_persist = false;
} }
template <typename T> template <typename T>
void process(T&& callback) { void process(T&& callback) {
if (_done) {
if (_ready) {
callback(_persist); callback(_persist);
_persist = false; _persist = false;
_done = false;
_ready = false;
} }
} }
@ -1135,23 +1097,14 @@ struct RelaySaveTimer {
} }
private: private:
void done() {
_done = true;
_armed = false;
}
static void timerCallback(void* arg) {
reinterpret_cast<RelaySaveTimer*>(arg)->done();
}
bool _persist { false }; bool _persist { false };
bool _done { false };
bool _armed { false };
os_timer_t _timer;
bool _ready { false };
Timer _timer;
}; };
struct RelaySyncTimer { struct RelaySyncTimer {
using Duration = espurna::duration::Milliseconds;
using Timer = espurna::timer::SystemTimer;
using Duration = Timer::Duration;
using Callback = void(*)(); using Callback = void(*)();
RelaySyncTimer() = default; RelaySyncTimer() = default;
@ -1167,43 +1120,30 @@ struct RelaySyncTimer {
} }
void schedule(Duration duration, Callback callback) { void schedule(Duration duration, Callback callback) {
stop();
os_timer_setfn(&_timer, timerCallback, this);
os_timer_arm(&_timer, duration.count(), 0);
_timer.once(
duration,
[&]() {
_ready = true;
});
_callback = callback; _callback = callback;
_armed = true;
} }
void stop() { void stop() {
if (_armed) {
os_timer_disarm(&_timer);
_timer = os_timer_t{};
_done = false;
_armed = false;
}
_timer.stop();
_ready = false;
} }
void process() { void process() {
if (_done) {
if (_ready) {
_callback(); _callback();
_done = false;
_ready = false;
} }
} }
private: private:
void done() {
_done = true;
}
static void timerCallback(void* arg) {
reinterpret_cast<RelaySyncTimer*>(arg)->done();
}
Callback _callback { nullptr }; Callback _callback { nullptr };
bool _done { false };
bool _armed { false };
os_timer_t _timer;
bool _ready { false };
Timer _timer;
}; };
using Relays = std::vector<Relay>; using Relays = std::vector<Relay>;
@ -1382,13 +1322,7 @@ public:
} }
void change(bool) override { void change(bool) override {
static bool scheduled { false };
if (!scheduled) {
schedule_function([]() {
flush();
scheduled = false;
});
}
espurnaRegisterOnceUnique(flush);
} }
size_t relayId() const { size_t relayId() const {
@ -3023,15 +2957,11 @@ void relaySetup() {
bool relayAdd(RelayProviderBasePtr&& provider) { bool relayAdd(RelayProviderBasePtr&& provider) {
if (provider && provider->setup()) { if (provider && provider->setup()) {
static bool scheduled { false };
_relays.emplace_back(std::move(provider)); _relays.emplace_back(std::move(provider));
if (!scheduled) {
schedule_function([]() {
_relayConfigure();
_relayBootAll();
scheduled = false;
});
}
espurnaRegisterOnceUnique([]() {
_relayConfigure();
_relayBootAll();
});
return true; return true;
} }


+ 6
- 4
code/espurna/rpc.cpp View File

@ -6,21 +6,23 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/ */
#include "espurna.h"
#include "rpc.h" #include "rpc.h"
#include <Schedule.h>
#include <cstring> #include <cstring>
#include "system.h" #include "system.h"
#include "utils.h" #include "utils.h"
static void rpcPrepareReset() {
prepareReset(CustomResetReason::Rpc);
}
bool rpcHandleAction(const String& action) { bool rpcHandleAction(const String& action) {
bool result = false; bool result = false;
if (action.equals("reboot")) { if (action.equals("reboot")) {
result = true; result = true;
schedule_function([]() {
prepareReset(CustomResetReason::Rpc);
});
espurnaRegisterOnce(rpcPrepareReset);
} else if (action.equals("heartbeat")) { } else if (action.equals("heartbeat")) {
result = true; result = true;
systemScheduleHeartbeat(); systemScheduleHeartbeat();


+ 1
- 1
code/espurna/rpnrules.cpp View File

@ -1093,7 +1093,7 @@ namespace system {
void sleep(uint64_t duration, RFMode mode); void sleep(uint64_t duration, RFMode mode);
void scheduleSleep(uint64_t duration, RFMode mode) { void scheduleSleep(uint64_t duration, RFMode mode) {
schedule_function([duration, mode]() {
espurnaRegisterOnce([duration, mode]() {
sleep(duration, mode); sleep(duration, mode);
}); });
} }


+ 100
- 16
code/espurna/system.cpp View File

@ -8,8 +8,6 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "espurna.h" #include "espurna.h"
#include <Ticker.h>
#include "rtcmem.h" #include "rtcmem.h"
#include "ws.h" #include "ws.h"
#include "ntp.h" #include "ntp.h"
@ -327,6 +325,87 @@ void blockingDelay(CoreClock::duration timeout) {
} // namespace time } // namespace time
namespace timer {
constexpr SystemTimer::Duration SystemTimer::DurationMin;
constexpr SystemTimer::Duration SystemTimer::DurationMax;
SystemTimer::SystemTimer() = default;
void SystemTimer::start(Duration duration, Callback callback, bool repeat) {
stop();
if (!duration.count()) {
return;
}
if (!_timer) {
_timer.reset(new os_timer_t{});
}
_callback = std::move(callback);
_armed = _timer.get();
_repeat = repeat;
os_timer_setfn(_timer.get(),
[](void* arg) {
reinterpret_cast<SystemTimer*>(arg)->callback();
},
this);
size_t total = 0;
if (duration > DurationMax) {
total = 1;
while (duration > DurationMax) {
total *= 2;
duration /= 2;
}
_tick.reset(new Tick{
.total = total,
.count = 0,
});
repeat = true;
}
os_timer_arm(_armed, duration.count(), repeat);
}
void SystemTimer::stop() {
if (_armed) {
os_timer_disarm(_armed);
_armed = nullptr;
_callback = nullptr;
_tick = nullptr;
}
}
void SystemTimer::callback() {
if (_tick) {
++_tick->count;
if (_tick->count < _tick->total) {
return;
}
}
_callback();
if (_repeat) {
if (_tick) {
_tick->count = 0;
}
return;
}
stop();
}
void SystemTimer::schedule_once(Duration duration, Callback callback) {
once(duration, [callback = std::move(callback)]() {
espurnaRegisterOnce(callback);
});
}
} // namespace timer
namespace { namespace {
namespace memory { namespace memory {
@ -514,7 +593,7 @@ namespace internal {
Data persistent_data { &Rtcmem->sys }; Data persistent_data { &Rtcmem->sys };
Ticker timer;
timer::SystemTimer timer;
bool flag { true }; bool flag { true };
} // namespace internal } // namespace internal
@ -574,6 +653,11 @@ uint8_t counter() {
: build::ChecksMin; : build::ChecksMin;
} }
void reset() {
DEBUG_MSG_P(PSTR("[MAIN] Resetting stability counter\n"));
internal::persistent_data.counter(build::ChecksMin);
}
void init() { void init() {
const auto count = counter(); const auto count = counter();
@ -601,10 +685,7 @@ void init() {
internal::persistent_data.counter(next); internal::persistent_data.counter(next);
internal::flag = (count < build::ChecksMax); 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);
});
internal::timer.once(build::CheckTime, reset);
} }
bool check() { bool check() {
@ -830,7 +911,7 @@ struct CallbackRunner {
namespace internal { namespace internal {
Ticker timer;
timer::SystemTimer timer;
std::vector<CallbackRunner> runners; std::vector<CallbackRunner> runners;
bool scheduled { false }; bool scheduled { false };
@ -890,11 +971,12 @@ void run() {
next = BeatMin; next = BeatMin;
} }
internal::timer.once_ms(next.count(), schedule);
internal::timer.once(next, schedule);
} }
void stop(Callback callback) { void stop(Callback callback) {
auto found = std::remove_if(internal::runners.begin(), internal::runners.end(),
auto found = std::remove_if(
internal::runners.begin(), internal::runners.end(),
[&](const CallbackRunner& runner) { [&](const CallbackRunner& runner) {
return callback == runner.callback; return callback == runner.callback;
}); });
@ -918,7 +1000,7 @@ void push(Callback callback, Mode mode, duration::Seconds interval) {
offset - msec offset - msec
}); });
internal::timer.detach();
internal::timer.stop();
schedule(); schedule();
} }
@ -972,7 +1054,7 @@ void init() {
pushOnce([](Mask) { pushOnce([](Mask) {
if (!espurna::boot::stability::check()) { if (!espurna::boot::stability::check()) {
DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n")); DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
} else if (espurna::boot::internal::timer.active()) {
} else if (espurna::boot::internal::timer) {
DEBUG_MSG_P(PSTR("[MAIN] Pending stability counter reset...\n")); DEBUG_MSG_P(PSTR("[MAIN] Pending stability counter reset...\n"));
} }
return true; return true;
@ -1035,7 +1117,7 @@ void init() {
// Store reset reason both here and in for the next boot // Store reset reason both here and in for the next boot
namespace internal { namespace internal {
Ticker reset_timer;
timer::SystemTimer reset_timer;
auto reset_reason = CustomResetReason::None; auto reset_reason = CustomResetReason::None;
void reset(CustomResetReason reason) { void reset(CustomResetReason reason) {
@ -1070,9 +1152,11 @@ static constexpr espurna::duration::Milliseconds ShortDelayForReset { 500 };
void deferredReset(duration::Milliseconds delay, CustomResetReason reason) { void deferredReset(duration::Milliseconds delay, CustomResetReason reason) {
DEBUG_MSG_P(PSTR("[MAIN] Requested reset: %s\n"), DEBUG_MSG_P(PSTR("[MAIN] Requested reset: %s\n"),
espurna::boot::serialize(reason).c_str()); espurna::boot::serialize(reason).c_str());
internal::reset_timer.once_ms(delay.count(), [reason]() {
internal::reset(reason);
});
internal::reset_timer.once(
delay,
[reason]() {
internal::reset(reason);
});
} }
// SDK reserves last 16KiB on the flash for it's own means // SDK reserves last 16KiB on the flash for it's own means


+ 62
- 0
code/espurna/system.h View File

@ -14,6 +14,8 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
#include <user_interface.h>
struct HeapStats { struct HeapStats {
uint32_t available; uint32_t available;
uint32_t usable; uint32_t usable;
@ -199,6 +201,66 @@ void blockingDelay(CoreClock::duration timeout);
} // namespace time } // 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 start(Duration, Callback, bool repeat);
void callback();
struct Tick {
size_t total;
size_t count;
};
Callback _callback { nullptr };
os_timer_t* _armed { nullptr };
bool _repeat { false };
std::unique_ptr<Tick> _tick;
std::unique_ptr<os_timer_t> _timer;
};
} // namespace timer
namespace heartbeat { namespace heartbeat {
using Mask = int32_t; using Mask = int32_t;


+ 2
- 2
code/espurna/terminal.cpp View File

@ -486,7 +486,7 @@ void setup() {
auto cmd = std::make_shared<String>(std::move(line)); auto cmd = std::make_shared<String>(std::move(line));
schedule_function([cmd]() {
espurnaRegisterOnce([cmd]() {
PrintString out(TCP_MSS); PrintString out(TCP_MSS);
api_find_and_call(*cmd, out); api_find_and_call(*cmd, out);
@ -621,7 +621,7 @@ void onAction(uint32_t client_id, const char* action, JsonObject& data) {
return; return;
} }
schedule_function([cmd, client_id]() {
espurnaRegisterOnce([cmd, client_id]() {
PrintLine<Output> out(client_id); PrintLine<Output> out(client_id);
api_find_and_call(*cmd, out); api_find_and_call(*cmd, out);
}); });


+ 117
- 0
code/espurna/types.cpp View File

@ -0,0 +1,117 @@
/*
Part of the SYSTEM MODULE
Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include "types.h"
namespace espurna {
void Callback::swap(Callback& other) noexcept {
if (_type == other._type) {
switch (_type) {
case StorageType::Empty:
break;
case StorageType::Simple:
std::swap(_storage.simple, other._storage.simple);
break;
case StorageType::Wrapper:
std::swap(_storage.wrapper, other._storage.wrapper);
break;
}
return;
}
auto moved = std::move(*this);
*this = std::move(other);
other = std::move(moved);
}
void Callback::operator()() const {
switch (_type) {
case StorageType::Empty:
break;
case StorageType::Simple:
(*_storage.simple)();
break;
case StorageType::Wrapper:
_storage.wrapper();
break;
}
}
void Callback::copy(const Callback& other) {
_type = other._type;
switch (other._type) {
case StorageType::Empty:
break;
case StorageType::Simple:
_storage.simple = other._storage.simple;
break;
case StorageType::Wrapper:
new (&_storage.wrapper) WrapperType(
other._storage.wrapper);
break;
}
}
void Callback::move(Callback& other) noexcept {
_type = other._type;
switch (other._type) {
case StorageType::Empty:
break;
case StorageType::Simple:
_storage.simple = other._storage.simple;
break;
case StorageType::Wrapper:
new (&_storage.wrapper) WrapperType(
std::move(other._storage.wrapper));
break;
}
other._storage.simple = nullptr;
other._type = StorageType::Empty;
}
void Callback::reset() {
switch (_type) {
case StorageType::Empty:
case StorageType::Simple:
break;
case StorageType::Wrapper:
_storage.wrapper.~WrapperType();
break;
}
_storage.simple = nullptr;
_type = StorageType::Empty;
}
Callback& Callback::operator=(Callback&& other) noexcept {
reset();
move(other);
return *this;
}
bool StringView::equals(StringView other) const {
if (other._len == _len) {
if (inFlash(_ptr) && inFlash(other._ptr)) {
return _ptr == other._ptr;
} else if (inFlash(_ptr)) {
return memcmp_P(other._ptr, _ptr, _len) == 0;
} else if (inFlash(other._ptr)) {
return memcmp_P(_ptr, other._ptr, _len) == 0;
}
return __builtin_memcmp(_ptr, other._ptr, _len) == 0;
}
return false;
}
} // namespace espurna

+ 141
- 1
code/espurna/types.h View File

@ -10,11 +10,146 @@ Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com
#include <Arduino.h> #include <Arduino.h>
#include <sys/pgmspace.h> #include <sys/pgmspace.h>
#include <memory>
// missing in our original header // missing in our original header
extern "C" int memcmp_P(const void*, const void*, size_t); extern "C" int memcmp_P(const void*, const void*, size_t);
namespace espurna { namespace espurna {
// base class for loop / oneshot / generic callbacks that do not need arguments
// *not expected* to be used instead of std function at all times.
// main purpose of this special class is to circumvent the need for rtti in
// our gcc stl implementation and retrieve the 'target function' pointer
// (*should* be different in gcc 11 / 12 though, target() became constexpr)
struct Callback {
using Type = void (*)();
using WrapperType = std::function<void()>;
Callback() = default;
Callback(const Callback& other) :
_storage(nullptr),
_type(other._type)
{
copy(other);
}
Callback& operator=(const Callback& other) {
reset();
copy(other);
return *this;
}
Callback(const Callback&&) = delete;
Callback(Callback&& other) noexcept :
_storage(nullptr),
_type(other._type)
{
move(other);
}
Callback& operator=(Callback&& other) noexcept;
template <typename T>
using remove_cvref = typename std::remove_cv<std::remove_reference<T>>::type;
template <typename T>
using is_callback = std::is_same<remove_cvref<T>, Callback>;
template <typename T>
using is_type = std::is_same<T, Type>;
template <typename T>
using type_convertible = std::is_convertible<T, Type>;
template <typename T>
using wrapper_convertible = std::is_convertible<T, WrapperType>;
// when T *can* be converted into Callback::Type
// usually, function pointer *or* lambda without capture list
template <typename T,
typename = typename std::enable_if<
is_type<T>::value
|| type_convertible<T>::value>::type>
constexpr Callback(T callback) noexcept :
_storage(Type(callback)),
_type(StorageType::Simple)
{}
// anything else convertible into std function
template <typename T,
typename = typename std::enable_if<
!is_callback<T>::value>::type,
typename = typename std::enable_if<
wrapper_convertible<T>::value>::type,
typename = typename std::enable_if<
!type_convertible<T>::value>::type>
Callback(T callback) :
_storage(WrapperType(std::move(callback))),
_type(StorageType::Wrapper)
{
static_assert(!is_callback<T>::value, "");
}
~Callback() {
reset();
}
bool isEmpty() const {
return (_type == StorageType::Empty);
}
bool isSimple() const {
return (_type == StorageType::Simple);
}
bool isWrapped() const {
return (_type == StorageType::Wrapper);
}
bool operator==(Type callback) const {
return isSimple() && (_storage.simple == callback);
}
void swap(Callback&) noexcept;
void operator()() const;
private:
union Storage {
WrapperType wrapper;
Type simple;
~Storage() {
}
explicit Storage(WrapperType callback) :
wrapper(std::move(callback))
{}
constexpr explicit Storage(Type callback) :
simple(callback)
{}
constexpr explicit Storage(std::nullptr_t) :
simple(nullptr)
{}
};
enum class StorageType {
Empty,
Simple,
Wrapper,
};
void copy(const Callback&);
void move(Callback&) noexcept;
void reset();
Storage _storage { nullptr };
StorageType _type { StorageType::Empty };
};
// aka `std::source_location` // aka `std::source_location`
struct SourceLocation { struct SourceLocation {
int line; int line;
@ -73,7 +208,6 @@ private:
bool& _handle; bool& _handle;
}; };
struct StringView { struct StringView {
StringView() = delete; StringView() = delete;
~StringView() = default; ~StringView() = default;
@ -164,6 +298,11 @@ struct StringView {
bool equals(StringView other) const; bool equals(StringView other) const;
private: private:
#if defined(HOST_MOCK)
constexpr static bool inFlash(const char*) {
return false;
}
#else
static bool inFlash(const char* ptr) { static bool inFlash(const char* ptr) {
// common comparison would use >=0x40000000 // common comparison would use >=0x40000000
// instead, slightly reduce the footprint by // instead, slightly reduce the footprint by
@ -171,6 +310,7 @@ private:
static constexpr uintptr_t Mask { 1 << 30 }; static constexpr uintptr_t Mask { 1 << 30 };
return (reinterpret_cast<uintptr_t>(ptr) & Mask) > 0; return (reinterpret_cast<uintptr_t>(ptr) & Mask) > 0;
} }
#endif
const char* _ptr; const char* _ptr;
size_t _len; size_t _len;


+ 1
- 3
code/espurna/web_asyncwebprint.ipp View File

@ -11,8 +11,6 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "web.h" #include "web.h"
#include "libs/TypeChecks.h" #include "libs/TypeChecks.h"
#include <Schedule.h>
#if WEB_SUPPORT #if WEB_SUPPORT
namespace asyncwebprint { namespace asyncwebprint {
@ -48,7 +46,7 @@ void AsyncWebPrint::scheduleFromRequest(AsyncWebPrintConfig config, AsyncWebServ
}); });
// attach another capture to the scheduled function, so we execute as soon as we exit next loop() // attach another capture to the scheduled function, so we execute as soon as we exit next loop()
schedule_function([callback, print]() {
espurnaRegisterOnce([callback, print]() {
if (State::None != print->getState()) return; if (State::None != print->getState()) return;
callback(*print.get()); callback(*print.get());
print->flush(); print->flush();


+ 18
- 16
code/espurna/wifi.cpp View File

@ -1107,7 +1107,7 @@ espurna::duration::Milliseconds interval() {
namespace internal { namespace internal {
Ticker timer;
timer::SystemTimer timer;
bool wait { false }; bool wait { false };
} // namespace internal } // namespace internal
@ -1141,13 +1141,15 @@ bool wait() {
} }
void stop() { void stop() {
internal::timer.detach();
internal::timer.stop();
}
void reset() {
internal::wait = false;
} }
void start(espurna::duration::Milliseconds next) { void start(espurna::duration::Milliseconds next) {
internal::timer.attach_ms(next.count(), []() {
internal::wait = false;
});
internal::timer.repeat(next, reset);
} }
} // namespace garp } // namespace garp
@ -1503,7 +1505,7 @@ wifi::Networks preparedNetworks;
bool connected { false }; bool connected { false };
bool wait { false }; bool wait { false };
Ticker timer;
timer::SystemTimer timer;
bool persist { false }; bool persist { false };
using TaskPtr = std::unique_ptr<Task>; using TaskPtr = std::unique_ptr<Task>;
@ -1521,7 +1523,7 @@ bool persist() {
void stop() { void stop() {
internal::task.reset(); internal::task.reset();
internal::timer.detach();
internal::timer.stop();
} }
bool start(String&& hostname) { bool start(String&& hostname) {
@ -1530,7 +1532,7 @@ bool start(String&& hostname) {
std::move(hostname), std::move(hostname),
std::move(internal::preparedNetworks), std::move(internal::preparedNetworks),
build::ConnectionRetries); build::ConnectionRetries);
internal::timer.detach();
internal::timer.stop();
return true; return true;
} }
@ -1539,7 +1541,7 @@ bool start(String&& hostname) {
} }
void schedule(espurna::duration::Milliseconds next, internal::ActionPtr ptr) { void schedule(espurna::duration::Milliseconds next, internal::ActionPtr ptr) {
internal::timer.once_ms(next.count(), ptr);
internal::timer.once(next, ptr);
DEBUG_MSG_P(PSTR("[WIFI] Next connection attempt in %u (ms)\n"), next.count()); DEBUG_MSG_P(PSTR("[WIFI] Next connection attempt in %u (ms)\n"), next.count());
} }
@ -1725,7 +1727,7 @@ namespace internal {
int8_t threshold { build::threshold() }; int8_t threshold { build::threshold() };
int8_t counter { build::Checks }; int8_t counter { build::Checks };
Ticker timer;
timer::SystemTimer timer;
void task() { void task() {
if (!wifi::sta::connected()) { if (!wifi::sta::connected()) {
@ -1749,12 +1751,12 @@ void task() {
void start() { void start() {
counter = build::Checks; counter = build::Checks;
timer.attach_ms(build::Interval.count(), task);
timer.repeat(build::Interval, task);
} }
void stop() { void stop() {
counter = build::Checks; counter = build::Checks;
timer.detach();
timer.stop();
} }
} // namespace internal } // namespace internal
@ -2091,7 +2093,7 @@ namespace internal {
auto timeout = build::Timeout; auto timeout = build::Timeout;
bool enabled { false }; bool enabled { false };
Ticker timer;
timer::SystemTimer timer;
} // namespace internal } // namespace internal
@ -2108,13 +2110,13 @@ bool enabled() {
} }
void remove() { void remove() {
internal::timer.detach();
internal::timer.stop();
} }
void check(); void check();
void schedule() { void schedule() {
internal::timer.once_ms(internal::timeout.count(), check);
internal::timer.once(internal::timeout, check);
} }
void check() { void check() {
@ -2342,7 +2344,7 @@ void wifi(::terminal::CommandContext&& ctx) {
alignas(4) static constexpr char Reset[] PROGMEM = "WIFI.RESET"; alignas(4) static constexpr char Reset[] PROGMEM = "WIFI.RESET";
void reset(::terminal::CommandContext&& ctx) { void reset(::terminal::CommandContext&& ctx) {
wifiDisconnect();
wifi::sta::disconnect();
wifi::settings::configure(); wifi::settings::configure();
terminalOK(ctx); terminalOK(ctx);
} }


+ 1
- 3
code/espurna/wifi.h View File

@ -21,7 +21,6 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
// esp8266 re-defines enum values from tcp header... include them first // esp8266 re-defines enum values from tcp header... include them first
#define LWIP_INTERNAL #define LWIP_INTERNAL
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <Ticker.h>
#undef LWIP_INTERNAL #undef LWIP_INTERNAL
extern "C" { extern "C" {
@ -94,8 +93,7 @@ void wifiStartAp();
void wifiToggleAp(); void wifiToggleAp();
void wifiToggleSta(); void wifiToggleSta();
// Disconnects STA intefrace
// (and will immediatly trigger a reconnection)
// Disconnects STA intefrace, will trigger reconnection
void wifiDisconnect(); void wifiDisconnect();
// Toggle WiFi modem // Toggle WiFi modem


+ 1
- 5
code/espurna/ws.cpp View File

@ -563,11 +563,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
} }
if (strcmp(action, "reconnect") == 0) { if (strcmp(action, "reconnect") == 0) {
static Ticker timer;
timer.once_ms_scheduled(100, []() {
wifiDisconnect();
yield();
});
wifiDisconnect();
return; return;
} }


+ 1
- 1
code/espurna/ws.h View File

@ -20,7 +20,7 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#include "ws_utils.h" #include "ws_utils.h"
// Generalized WS lifetime callbacks. // Generalized WS lifetime callbacks.
// Each callback is kept as std::function, thus we can use complex objects, and not just basic function pointers.
// Callback could either be a lambda or a plain function pointer
// //
// Connection start: // Connection start:
// - on_visible will be the very first message sent, callback data will be grouped together // - on_visible will be the very first message sent, callback data will be grouped together


+ 3
- 2
code/test/unit/CMakeLists.txt View File

@ -149,10 +149,11 @@ target_link_libraries(esp8266 PUBLIC common)
add_library(terminal STATIC add_library(terminal STATIC
${ESPURNA_PATH}/code/espurna/terminal_commands.cpp ${ESPURNA_PATH}/code/espurna/terminal_commands.cpp
${ESPURNA_PATH}/code/espurna/terminal_parsing.cpp ${ESPURNA_PATH}/code/espurna/terminal_parsing.cpp
${ESPURNA_PATH}/code/espurna/types.cpp
) )
target_link_libraries(terminal PUBLIC esp8266) target_link_libraries(terminal PUBLIC esp8266)
target_include_directories(terminal PUBLIC target_include_directories(terminal PUBLIC
${ESPURNA_PATH}/code/espurna/
${ESPURNA_PATH}/code/
) )
target_compile_options(terminal PUBLIC target_compile_options(terminal PUBLIC
${COMMON_FLAGS} ${COMMON_FLAGS}
@ -180,4 +181,4 @@ function(build_tests)
endforeach() endforeach()
endfunction() endfunction()
build_tests(basic settings terminal tuya url)
build_tests(basic settings terminal tuya types url)

+ 1
- 1
code/test/unit/src/settings/settings.cpp View File

@ -7,7 +7,7 @@
#pragma GCC diagnostic warning "-Wpointer-arith" #pragma GCC diagnostic warning "-Wpointer-arith"
#pragma GCC diagnostic warning "-Wstrict-overflow=5" #pragma GCC diagnostic warning "-Wstrict-overflow=5"
#include <settings_embedis.h>
#include <espurna/settings_embedis.h>
#include <array> #include <array>
#include <algorithm> #include <algorithm>


+ 2
- 9
code/test/unit/src/terminal/terminal.cpp View File

@ -2,17 +2,10 @@
#include <Arduino.h> #include <Arduino.h>
#include <StreamString.h> #include <StreamString.h>
#include <libs/PrintString.h>
#include <terminal_commands.h>
#include <espurna/libs/PrintString.h>
#include <espurna/terminal_commands.h>
namespace espurna { namespace espurna {
// no special cases for flash strings
bool StringView::equals(espurna::StringView other) const {
return _ptr == other._ptr
|| (_len == other._len && (0 == __builtin_memcmp(_ptr, other._ptr, _len)));
}
namespace terminal { namespace terminal {
namespace test { namespace test {
namespace { namespace {


+ 6
- 6
code/test/unit/src/tuya/tuya.cpp View File

@ -9,12 +9,12 @@
#include <type_traits> #include <type_traits>
#include <queue> #include <queue>
#include "libs/TypeChecks.h"
#include "tuya_types.h"
#include "tuya_util.h"
#include "tuya_transport.h"
#include "tuya_protocol.h"
#include "tuya_dataframe.h"
#include <espurna/libs/TypeChecks.h>
#include <espurna/tuya_types.h>
#include <espurna/tuya_util.h>
#include <espurna/tuya_transport.h>
#include <espurna/tuya_protocol.h>
#include <espurna/tuya_dataframe.h>
using namespace tuya; using namespace tuya;


+ 218
- 0
code/test/unit/src/types/types.cpp View File

@ -0,0 +1,218 @@
#include <unity.h>
#include <Arduino.h>
#include <espurna/types.h>
namespace espurna {
namespace test {
namespace {
void test_view() {
StringView view{"123456789"};
TEST_ASSERT_EQUAL(9, view.length());
TEST_ASSERT(view.c_str() != nullptr);
const char expected[] = "123456789";
TEST_ASSERT_EQUAL(__builtin_strlen(expected), view.length());
TEST_ASSERT_EQUAL_CHAR_ARRAY(
expected, view.begin(), view.length());
}
void test_view_convert() {
const String origin("12345");
StringView view(origin);
TEST_ASSERT(origin.begin() == view.begin());
TEST_ASSERT(origin.end() == view.end());
TEST_ASSERT_EQUAL(origin.length(), view.length());
auto copy = view.toString();
TEST_ASSERT(view.equals(copy));
TEST_ASSERT(origin.begin() != copy.begin());
TEST_ASSERT_EQUAL(origin.length(), copy.length());
TEST_ASSERT_EQUAL_CHAR_ARRAY(
origin.begin(), copy.begin(), copy.length());
StringView copy_view(copy);
TEST_ASSERT_EQUAL(view.length(), copy_view.length());
TEST_ASSERT(view.equals(copy_view));
}
void test_callback_empty() {
Callback callback;
TEST_ASSERT(callback.isEmpty());
}
void test_callback_lambda() {
static int once = 0;
Callback callback([]() {
++once;
});
TEST_ASSERT(callback.isSimple());
callback();
TEST_ASSERT_EQUAL(1, once);
}
int external { 0 };
void increment_external() {
++external;
++external;
}
void test_callback_simple() {
TEST_ASSERT_EQUAL(0, external);
Callback callback(increment_external);
TEST_ASSERT(callback.isSimple());
callback();
TEST_ASSERT_EQUAL(2, external);
}
void test_callback_capture() {
int value = 0;
std::vector<Callback> callbacks;
callbacks.resize(3, [&]() {
++value;
});
TEST_ASSERT_EQUAL(3, callbacks.size());
TEST_ASSERT(callbacks[0].isWrapped());
TEST_ASSERT(callbacks[1].isWrapped());
TEST_ASSERT(callbacks[2].isWrapped());
for (const auto& callback : callbacks) {
callback();
}
TEST_ASSERT_EQUAL(3, value);
}
void test_callback_capture_copy() {
int value = 0;
Callback original([&]() {
++value;
++value;
++value;
++value;
});
TEST_ASSERT(original.isWrapped());
auto copy = original;
TEST_ASSERT(original.isWrapped());
TEST_ASSERT(copy.isWrapped());
original();
TEST_ASSERT_EQUAL(4, value);
copy();
TEST_ASSERT_EQUAL(8, value);
}
void test_callback_capture_move() {
int value = 0;
Callback original([&]() {
++value;
++value;
++value;
++value;
});
TEST_ASSERT(original.isWrapped());
Callback moved(std::move(original));
TEST_ASSERT(original.isEmpty());
TEST_ASSERT(moved.isWrapped());
original();
TEST_ASSERT_EQUAL(0, value);
moved();
TEST_ASSERT_EQUAL(4, value);
}
void test_callback_assign() {
int value = 0;
Callback original([&]() {
value += 10;
});
TEST_ASSERT(original.isWrapped());
Callback copy;
TEST_ASSERT(copy.isEmpty());
copy = original;
TEST_ASSERT(copy.isWrapped());
original();
TEST_ASSERT_EQUAL(10, value);
copy();
TEST_ASSERT_EQUAL(20, value);
Callback moved;
TEST_ASSERT(moved.isEmpty());
moved = std::move(original);
TEST_ASSERT(original.isEmpty());
TEST_ASSERT(moved.isWrapped());
original();
TEST_ASSERT_EQUAL(20, value);
copy();
TEST_ASSERT_EQUAL(30, value);
moved();
TEST_ASSERT_EQUAL(40, value);
}
void test_callback_swap() {
int output = 0;
Callback good([&]() {
output = 5;
});
Callback bad([&]() {
output = 10;
});
good.swap(bad);
TEST_ASSERT_EQUAL(0, output);
bad();
TEST_ASSERT_EQUAL(5, output);
good();
TEST_ASSERT_EQUAL(10, output);
}
} // namespace
} // namespace test
} // namespace espurna
int main(int, char**) {
UNITY_BEGIN();
using namespace espurna::test;
RUN_TEST(test_view);
RUN_TEST(test_view_convert);
RUN_TEST(test_callback_empty);
RUN_TEST(test_callback_simple);
RUN_TEST(test_callback_lambda);
RUN_TEST(test_callback_capture);
RUN_TEST(test_callback_capture_copy);
RUN_TEST(test_callback_capture_move);
RUN_TEST(test_callback_assign);
RUN_TEST(test_callback_swap);
return UNITY_END();
}

+ 1
- 1
code/test/unit/src/url/url.cpp View File

@ -1,7 +1,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <unity.h> #include <unity.h>
#include "libs/URL.h"
#include <espurna/libs/URL.h>
void test_parse() { void test_parse() {
URL url("http://api.thingspeak.com/update"); URL url("http://api.thingspeak.com/update");


Loading…
Cancel
Save