diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 7c462295cd..ced3548b3c 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -146,7 +146,9 @@ void updateSensor(sensorType &sensor) { SSEBroadcastState(sensor.name, sensor.value, newVal); // only broadcast if state is different } sensor.value = newVal; - sensor.update.once(rand() % 20 + 10, std::bind(updateSensor, sensor)); // randomly update sensor + sensor.update.once(rand() % 20 + 10, [&]() { + updateSensor(sensor); + }); // randomly update sensor } void handleSubscribe() { diff --git a/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino index a023adfdd8..2d0673a17a 100644 --- a/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino +++ b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino @@ -13,9 +13,11 @@ public: ExampleClass(int pin, int duration) : _pin(pin), _duration(duration) { pinMode(_pin, OUTPUT); - _myTicker.attach_ms(_duration, std::bind(&ExampleClass::classBlink, this)); + _myTicker.attach_ms(_duration, + [this]() { + classBlink(); + }); } - ~ExampleClass(){}; int _pin, _duration; Ticker _myTicker; @@ -53,7 +55,7 @@ void setup() { scheduledTicker.attach_ms_scheduled(100, scheduledBlink); pinMode(LED4, OUTPUT); - parameterTicker.attach_ms(100, std::bind(parameterBlink, LED4)); + parameterTicker.attach_ms(100, parameterBlink, LED4); pinMode(LED5, OUTPUT); lambdaTicker.attach_ms(100, []() { diff --git a/libraries/Ticker/src/Ticker.cpp b/libraries/Ticker/src/Ticker.cpp index dca4435dc2..baac355ca5 100644 --- a/libraries/Ticker/src/Ticker.cpp +++ b/libraries/Ticker/src/Ticker.cpp @@ -23,49 +23,113 @@ #include "eagle_soc.h" #include "osapi.h" +#include #include "Ticker.h" -Ticker::Ticker() - : _timer(nullptr) {} +// ETSTimer is part of the instance, and we don't have any state besides +// the things required for the callback. Allow copies and moves, but +// disable any member copies and default-init + detach() instead. Ticker::~Ticker() { detach(); } -void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg) +Ticker::Ticker(const Ticker&) { - if (_timer) - { +} + +Ticker& Ticker::operator=(const Ticker&) +{ + detach(); + return *this; +} + +Ticker::Ticker(Ticker&& other) noexcept +{ + other.detach(); +} + +Ticker& Ticker::operator=(Ticker&& other) noexcept +{ + other.detach(); + detach(); + return *this; +} + +void Ticker::_attach(Ticker::Milliseconds milliseconds, bool repeat) +{ + if (_timer) { os_timer_disarm(_timer); + } else { + _timer = &_timer_internal; } - else - { - _timer = &_etsTimer; + + os_timer_setfn(_timer, + [](void* ptr) { + reinterpret_cast(ptr)->_static_callback(); + }, this); + + _repeat = repeat; + + // whenever duration excedes this limit, make timer repeatable N times + // in case it is really repeatable, it will reset itself and continue as usual + size_t total = 0; + if (milliseconds > DurationMax) { + total = 1; + while (milliseconds > DurationMax) { + total *= 2; + milliseconds /= 2; + } + _tick.reset(new callback_tick_t{ + .total = total, + .count = 0, + }); + repeat = true; } - os_timer_setfn(_timer, callback, arg); - os_timer_arm(_timer, milliseconds, repeat); + os_timer_arm(_timer, milliseconds.count(), repeat); } void Ticker::detach() { - if (!_timer) - return; - - os_timer_disarm(_timer); - _timer = nullptr; - _callback_function = nullptr; + if (_timer) { + os_timer_disarm(_timer); + _timer = nullptr; + _tick.reset(nullptr); + _callback = std::monostate{}; + } } bool Ticker::active() const { - return _timer; + return _timer != nullptr; } -void Ticker::_static_callback(void* arg) +void Ticker::_static_callback() { - Ticker* _this = reinterpret_cast(arg); - if (_this && _this->_callback_function) - _this->_callback_function(); + if (_tick) { + ++_tick->count; + if (_tick->count < _tick->total) { + return; + } + } + + std::visit([](auto&& callback) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + callback.func(callback.arg); + } else if constexpr (std::is_same_v) { + callback(); + } + }, _callback); + + if (_repeat) { + if (_tick) { + _tick->count = 0; + } + return; + } + + detach(); } diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index 791ff94567..0b61487540 100644 --- a/libraries/Ticker/src/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -19,140 +19,217 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef TICKER_H -#define TICKER_H +#pragma once +#include #include +#include +#include + +#include #include #include class Ticker { public: - Ticker(); + // Our helper type to support any callable object + // In case of a lambda with bound variable(s), it will be destroyed + // either when the timer expires or detach() is called + using callback_function_t = std::function; + + // Native SDK type, simple function with void* argument + using callback_with_arg_t = void(*)(void*); + + // Helper type to allow type coercion on function argument + // Only works with a function pointer. Argument *must not* be larger than the size of the `void*` + template + using remove_cvref_t = typename std::remove_cv_t< + typename std::remove_reference_t>; + + template > + using callback_with_typed_arg_t = void(*)(Y); + + Ticker() = default; ~Ticker(); - typedef void (*callback_with_arg_t)(void*); - typedef std::function callback_function_t; + Ticker(const Ticker&); + Ticker& operator=(const Ticker&); + + Ticker(Ticker&&) noexcept; + Ticker& operator=(Ticker&&) noexcept; // callback will be called at following loop() after ticker fires void attach_scheduled(float seconds, callback_function_t callback) { - _callback_function = [callback]() { schedule_function(callback); }; - _attach_ms(1000UL * seconds, true); + _callback = [callback]() { + schedule_function(callback); + }; + _attach(Seconds(seconds), true); } // callback will be called in SYS ctx when ticker fires void attach(float seconds, callback_function_t callback) { - _callback_function = std::move(callback); - _attach_ms(1000UL * seconds, true); + _callback = std::move(callback); + _attach(Seconds(seconds), true); } // callback will be called at following loop() after ticker fires void attach_ms_scheduled(uint32_t milliseconds, callback_function_t callback) { - _callback_function = [callback]() { schedule_function(callback); }; - _attach_ms(milliseconds, true); + _callback = [callback]() { + schedule_function(callback); + }; + _attach(Milliseconds(milliseconds), true); } // callback will be called at following yield() after ticker fires void attach_ms_scheduled_accurate(uint32_t milliseconds, callback_function_t callback) { - _callback_function = [callback]() { schedule_recurrent_function_us([callback]() { callback(); return false; }, 0); }; - _attach_ms(milliseconds, true); + _callback = [callback]() { + schedule_recurrent_function_us([callback]() { + callback(); + return false; + }, 0); + }; + _attach(Milliseconds(milliseconds), true); } // callback will be called in SYS ctx when ticker fires void attach_ms(uint32_t milliseconds, callback_function_t callback) { - _callback_function = std::move(callback); - _attach_ms(milliseconds, true); + _callback = std::move(callback); + _attach(Milliseconds(milliseconds), true); } - // callback will be called in SYS ctx when ticker fires - template - void attach(float seconds, void (*callback)(TArg), TArg arg) + // callback will still be called in SYS ctx when ticker fires + template + void attach(float seconds, Func func, Arg arg) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" - static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(1000UL * seconds, true, reinterpret_cast(callback), reinterpret_cast(arg)); -#pragma GCC diagnostic pop + _callback = make_callback_ptr(func, arg); + _attach(Seconds(seconds), true); } - // callback will be called in SYS ctx when ticker fires - template - void attach_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) + // callback will still be called in SYS ctx when ticker fires + template + void attach_ms(uint32_t milliseconds, Func func, Arg arg) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" - static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(milliseconds, true, reinterpret_cast(callback), reinterpret_cast(arg)); -#pragma GCC diagnostic pop + _callback = make_callback_ptr(func, arg); + _attach(Milliseconds(milliseconds), true); } // callback will be called at following loop() after ticker fires void once_scheduled(float seconds, callback_function_t callback) { - _callback_function = [callback]() { schedule_function(callback); }; - _attach_ms(1000UL * seconds, false); + _callback = [callback]() { schedule_function(callback); }; + _attach(Seconds(seconds), false); } // callback will be called in SYS ctx when ticker fires void once(float seconds, callback_function_t callback) { - _callback_function = std::move(callback); - _attach_ms(1000UL * seconds, false); + _callback = std::move(callback); + _attach(Seconds(seconds), false); } // callback will be called at following loop() after ticker fires void once_ms_scheduled(uint32_t milliseconds, callback_function_t callback) { - _callback_function = [callback]() { schedule_function(callback); }; - _attach_ms(milliseconds, false); + _callback = [callback]() { schedule_function(callback); }; + _attach(Milliseconds(milliseconds), false); } // callback will be called in SYS ctx when ticker fires void once_ms(uint32_t milliseconds, callback_function_t callback) { - _callback_function = std::move(callback); - _attach_ms(milliseconds, false); + _callback = std::move(callback); + _attach(Milliseconds(milliseconds), false); } // callback will be called in SYS ctx when ticker fires - template - void once(float seconds, void (*callback)(TArg), TArg arg) + template + void once(float seconds, Func func, Arg arg) { - static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(1000UL * seconds, false, reinterpret_cast(callback), reinterpret_cast(arg)); + _callback = make_callback_ptr(func, arg); + _attach(Seconds(seconds), false); } // callback will be called in SYS ctx when ticker fires - template - void once_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) + template + void once_ms(uint32_t milliseconds, Func func, Arg arg) { - static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(milliseconds, false, reinterpret_cast(callback), reinterpret_cast(arg)); + _callback = make_callback_ptr(func, arg); + _attach(Milliseconds(milliseconds), false); } + // if active(), disables currently running timer void detach(); + bool active() const; + explicit operator bool() const { + return active(); + } + protected: - static void _static_callback(void* arg); - void _attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg); - void _attach_ms(uint32_t milliseconds, bool repeat) + // internals use this as duration + using Milliseconds = std::chrono::duration>; + + // we allow a floating point as input as well + // float -> u32 has some precision issues, though + using Seconds = std::chrono::duration>; + + // NONOS SDK timer object duration cannot be longer than 6870947 (0x68D7A3) + // when that's the case, we split execution into multiple 'ticks' + static constexpr auto DurationMax = Milliseconds(6870947); + + struct callback_tick_t { - _attach_ms(milliseconds, repeat, _static_callback, this); + uint32_t total = 0; + uint32_t count = 0; + }; + + void _static_callback(); + + void _attach(Milliseconds milliseconds, bool repeat); + void _attach(Seconds seconds, bool repeat) + { + _attach(std::chrono::duration_cast(seconds), repeat); } - ETSTimer* _timer; - callback_function_t _callback_function = nullptr; + std::unique_ptr _tick; + bool _repeat = false; + + ETSTimer* _timer = nullptr; private: - ETSTimer _etsTimer; -}; + struct callback_ptr_t + { + callback_with_arg_t func; + void* arg; + }; + + // original implementation inluded type coersion of integer values that would fit into uintptr_t + // to avoid writing these in our every method, use a generic type that automatically converts it + // (XXX it is a weird hack, though, consider removing this in the future and prever void* instead) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" + template > + static callback_ptr_t make_callback_ptr(callback_with_typed_arg_t func, T arg) { + static_assert(sizeof(Y) <= sizeof(void*), ""); + return callback_ptr_t{ + .func = reinterpret_cast(func), + .arg = reinterpret_cast(arg), + }; + } +#pragma GCC diagnostic pop + using callback_data_t = std::variant< + std::monostate, + callback_ptr_t, + callback_function_t>; -#endif //TICKER_H + callback_data_t _callback; + ETSTimer _timer_internal{}; +};