idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
event.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
21#include <idfxx/chrono>
22#include <idfxx/cpu>
23#include <idfxx/error>
24
25#include <esp_event.h>
26#include <functional>
27#include <memory>
28#include <type_traits>
29
50#define IDFXX_EVENT_DEFINE_BASE(name, id_enum) \
51 inline constexpr char _##name##_base[] = #name; \
52 inline constexpr ::idfxx::event_base<id_enum> name { \
53 _##name##_base \
54 }
55
56namespace idfxx {
57
58template<typename IdEnum>
59class event_base;
60
61template<typename IdEnum>
62struct event_type;
63
83template<typename IdEnum>
85public:
87 using id_type = IdEnum;
88
111 constexpr event_base(esp_event_base_t base) noexcept
112 : _base(base) {}
113
118 [[nodiscard]] constexpr esp_event_base_t idf_base() const noexcept { return _base; }
119
130 [[nodiscard]] constexpr event_type<IdEnum> operator()(IdEnum id) const;
131
132private:
133 esp_event_base_t _base;
134};
135
147template<typename IdEnum>
152 IdEnum id;
153
158 [[nodiscard]] constexpr esp_event_base_t idf_base() const { return base.idf_base(); }
159
164 [[nodiscard]] constexpr int32_t idf_id() const { return static_cast<int32_t>(id); }
165};
166
167// CTAD guide
168template<typename IdEnum>
170
171// Implement operator() after event_type is defined
172template<typename IdEnum>
174 return {*this, id};
175}
176
185template<typename IdEnum>
186using event_callback = std::move_only_function<void(event_base<IdEnum> base, IdEnum id, void* event_data) const>;
187
188class user_event_loop;
189
212public:
213 class listener_handle;
215
216 // =========================================================================
217 // System (default) loop lifecycle
218 // =========================================================================
219
220#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
227 inline static void create_system() { unwrap(try_create_system()); }
228
235 inline static void destroy_system() { unwrap(try_destroy_system()); }
236#endif
237
243 [[nodiscard]] static result<void> try_create_system();
244
250 [[nodiscard]] static result<void> try_destroy_system();
251
258 [[nodiscard]] static event_loop& system();
259
260 // =========================================================================
261 // User event loop creation
262 // =========================================================================
263
267 struct task_config {
269 std::string_view name;
271 size_t stack_size = 2048;
273 unsigned int priority = 5;
278 std::optional<core_id> core_affinity = std::nullopt;
279 };
280
281#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
292 static std::unique_ptr<user_event_loop> make_user(size_t queue_size = 32);
293
305 static std::unique_ptr<event_loop> make_user(task_config task, size_t queue_size = 32);
306#endif
307
316 [[nodiscard]] static result<std::unique_ptr<user_event_loop>> try_make_user(size_t queue_size = 32);
317
327 [[nodiscard]] static result<std::unique_ptr<event_loop>> try_make_user(task_config task, size_t queue_size = 32);
328
329 // =========================================================================
330 // Listener registration (base + specific id)
331 // =========================================================================
332
333#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
345 template<typename IdEnum>
347 listener_add(event_base<IdEnum> base, IdEnum id, event_callback<std::type_identity_t<IdEnum>> callback);
348
359 template<typename IdEnum>
360 listener_handle listener_add(event_type<IdEnum> event, event_callback<std::type_identity_t<IdEnum>> callback);
361
372 template<typename IdEnum>
373 listener_handle listener_add(event_base<IdEnum> base, event_callback<std::type_identity_t<IdEnum>> callback);
374#endif
375
385 template<typename IdEnum>
386 [[nodiscard]] result<listener_handle>
387 try_listener_add(event_base<IdEnum> base, IdEnum id, event_callback<std::type_identity_t<IdEnum>> callback);
388
397 template<typename IdEnum>
398 [[nodiscard]] result<listener_handle>
399 try_listener_add(event_type<IdEnum> event, event_callback<std::type_identity_t<IdEnum>> callback);
400
409 template<typename IdEnum>
410 [[nodiscard]] result<listener_handle>
411 try_listener_add(event_base<IdEnum> base, event_callback<std::type_identity_t<IdEnum>> callback);
412
413 // =========================================================================
414 // Listener removal
415 // =========================================================================
416
417#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
425 void listener_remove(listener_handle handle);
426#endif
427
435
436 // =========================================================================
437 // Event posting
438 // =========================================================================
439
440#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
452 template<typename IdEnum>
453 void post(event_base<IdEnum> base, IdEnum id, const void* data = nullptr, size_t size = 0) {
454 unwrap(try_post(base, id, data, size));
455 }
456
471 template<typename IdEnum, typename Rep, typename Period>
472 void post(
474 IdEnum id,
475 const void* data,
476 size_t size,
477 const std::chrono::duration<Rep, Period>& timeout
478 ) {
479 unwrap(try_post(base, id, data, size, timeout));
480 }
481
492 template<typename IdEnum>
493 void post(event_type<IdEnum> event, const void* data = nullptr, size_t size = 0) {
494 unwrap(try_post(event, data, size));
495 }
496
510 template<typename IdEnum, typename Rep, typename Period>
511 void
512 post(event_type<IdEnum> event, const void* data, size_t size, const std::chrono::duration<Rep, Period>& timeout) {
513 unwrap(try_post(event, data, size, timeout));
514 }
515#endif
516
527 template<typename IdEnum>
528 [[nodiscard]] result<void>
529 try_post(event_base<IdEnum> base, IdEnum id, const void* data = nullptr, size_t size = 0) {
530 if (_handle == nullptr) {
531 return wrap(esp_event_post(base.idf_base(), static_cast<int32_t>(id), data, size, portMAX_DELAY));
532 }
533 return wrap(esp_event_post_to(_handle, base.idf_base(), static_cast<int32_t>(id), data, size, portMAX_DELAY));
534 }
535
549 template<typename IdEnum, typename Rep, typename Period>
550 [[nodiscard]] result<void> try_post(
552 IdEnum id,
553 const void* data,
554 size_t size,
555 const std::chrono::duration<Rep, Period>& timeout
556 ) {
557 auto ticks = chrono::ticks(timeout);
558 if (_handle == nullptr) {
559 return wrap(esp_event_post(base.idf_base(), static_cast<int32_t>(id), data, size, ticks));
560 }
561 return wrap(esp_event_post_to(_handle, base.idf_base(), static_cast<int32_t>(id), data, size, ticks));
562 }
563
573 template<typename IdEnum>
574 [[nodiscard]] result<void> try_post(event_type<IdEnum> event, const void* data = nullptr, size_t size = 0) {
575 return try_post(event.base, event.id, data, size);
576 }
577
590 template<typename IdEnum, typename Rep, typename Period>
591 [[nodiscard]] result<void> try_post(
592 event_type<IdEnum> event,
593 const void* data,
594 size_t size,
595 const std::chrono::duration<Rep, Period>& timeout
596 ) {
597 return try_post(event.base, event.id, data, size, timeout);
598 }
599
604 [[nodiscard]] esp_event_loop_handle_t idf_handle() const { return _handle; }
605
609 virtual ~event_loop();
610
611 event_loop(const event_loop&) = delete;
612 event_loop& operator=(const event_loop&) = delete;
615
616protected:
621 explicit event_loop(esp_event_loop_handle_t handle)
622 : _handle(handle) {}
623
624private:
625 static result<listener_handle> register_listener(
626 esp_event_loop_handle_t loop,
627 esp_event_base_t base,
628 int32_t id,
629 std::move_only_function<void(esp_event_base_t, int32_t, void*) const> callback
630 );
631
632 template<typename IdEnum>
634 register_listener(esp_event_loop_handle_t loop, esp_event_base_t base, int32_t id, event_callback<IdEnum> callback);
635
636 static result<void> unregister_listener(
637 esp_event_loop_handle_t loop,
638 esp_event_handler_instance_t instance,
639 esp_event_base_t base,
640 int32_t id
641 );
642
643 esp_event_loop_handle_t _handle;
644};
645
657 friend class event_loop;
659
660public:
662 listener_handle() = default;
663
664private:
666 esp_event_loop_handle_t loop,
667 esp_event_handler_instance_t instance,
668 esp_event_base_t base,
669 int32_t id
670 )
671 : _loop(loop)
672 , _instance(instance)
673 , _base(base)
674 , _id(id) {}
675
676 esp_event_loop_handle_t _loop = nullptr;
677 esp_event_handler_instance_t _instance = nullptr;
678 esp_event_base_t _base = nullptr;
679 int32_t _id = 0;
680};
681
689public:
693 unique_listener_handle() noexcept = default;
694
699 explicit unique_listener_handle(listener_handle handle) noexcept
700 : _handle(handle) {}
701
706 : _handle(other._handle) {
707 other._handle = listener_handle{};
708 }
709
714 if (this != &other) {
715 reset();
716 _handle = other._handle;
717 other._handle = listener_handle{};
718 }
719 return *this;
720 }
721
724
729
734 [[nodiscard]] listener_handle release() noexcept {
735 auto h = _handle;
736 _handle = listener_handle{};
737 return h;
738 }
739
743 void reset() noexcept;
744
749 [[nodiscard]] explicit operator bool() const noexcept { return _handle._instance != nullptr; }
750
751private:
752 listener_handle _handle;
753};
754
779 friend class event_loop;
780
781public:
782#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
783 // =========================================================================
784 // Manual dispatch (for loops without dedicated task)
785 // =========================================================================
786
798 template<typename Rep, typename Period>
799 void run(const std::chrono::duration<Rep, Period>& duration) {
800 unwrap(try_run(duration));
801 }
802#endif
803
814 template<typename Rep, typename Period>
815 [[nodiscard]] result<void> try_run(const std::chrono::duration<Rep, Period>& duration) {
816 return wrap(esp_event_loop_run(idf_handle(), chrono::ticks(duration)));
817 }
818
823
824private:
825 explicit user_event_loop(esp_event_loop_handle_t handle)
826 : event_loop(handle) {}
827};
828
829// =============================================================================
830// event_loop template implementations
831// =============================================================================
832
833#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
834inline std::unique_ptr<user_event_loop> event_loop::make_user(size_t queue_size) {
835 auto result = try_make_user(queue_size);
836 return std::move(result).transform_error([](auto ec) -> std::error_code { throw std::system_error(ec); }).value();
837}
838
839inline std::unique_ptr<event_loop> event_loop::make_user(task_config task, size_t queue_size) {
840 auto result = try_make_user(std::move(task), queue_size);
841 return std::move(result).transform_error([](auto ec) -> std::error_code { throw std::system_error(ec); }).value();
842}
843
844template<typename IdEnum>
846event_loop::listener_add(event_base<IdEnum> base, IdEnum id, event_callback<std::type_identity_t<IdEnum>> callback) {
847 return unwrap(try_listener_add(base, id, std::move(callback)));
848}
849
850template<typename IdEnum>
852event_loop::listener_add(event_base<IdEnum> base, event_callback<std::type_identity_t<IdEnum>> callback) {
853 return unwrap(try_listener_add(base, std::move(callback)));
854}
855
856template<typename IdEnum>
858event_loop::listener_add(event_type<IdEnum> event, event_callback<std::type_identity_t<IdEnum>> callback) {
859 return unwrap(try_listener_add(event, std::move(callback)));
860}
861
865#endif
866
867template<typename IdEnum>
868result<event_loop::listener_handle> event_loop::register_listener(
869 esp_event_loop_handle_t loop,
870 esp_event_base_t base,
871 int32_t id,
873) {
874 return register_listener(loop, base, id, [cb = std::move(callback)](esp_event_base_t b, int32_t i, void* data) {
875 cb(event_base<IdEnum>{b}, static_cast<IdEnum>(i), data);
876 });
877}
878
879template<typename IdEnum>
882 IdEnum id,
883 event_callback<std::type_identity_t<IdEnum>> callback
884) {
885 return register_listener<IdEnum>(_handle, base.idf_base(), static_cast<int32_t>(id), std::move(callback));
886}
887
888template<typename IdEnum>
890event_loop::try_listener_add(event_base<IdEnum> base, event_callback<std::type_identity_t<IdEnum>> callback) {
891 return register_listener<IdEnum>(_handle, base.idf_base(), ESP_EVENT_ANY_ID, std::move(callback));
892}
893
894template<typename IdEnum>
896event_loop::try_listener_add(event_type<IdEnum> event, event_callback<std::type_identity_t<IdEnum>> callback) {
897 return try_listener_add(event.base, event.id, std::move(callback));
898}
899
// end of idfxx_event
901
902} // namespace idfxx
Typed event base template.
Definition event.hpp:84
constexpr esp_event_base_t idf_base() const noexcept
Returns the underlying ESP-IDF event base.
Definition event.hpp:118
constexpr event_base(esp_event_base_t base) noexcept
Constructs from an ESP-IDF event base pointer.
Definition event.hpp:111
IdEnum id_type
The enum type for event IDs.
Definition event.hpp:87
Handle to a registered event listener.
Definition event.hpp:656
listener_handle()=default
Default constructor creates an invalid handle.
RAII handle for event listener registration.
Definition event.hpp:688
unique_listener_handle() noexcept=default
Constructs an empty handle.
unique_listener_handle & operator=(unique_listener_handle &&other) noexcept
Move assignment.
Definition event.hpp:713
listener_handle release() noexcept
Releases ownership without removing the listener.
Definition event.hpp:734
unique_listener_handle(const unique_listener_handle &)=delete
unique_listener_handle & operator=(const unique_listener_handle &)=delete
unique_listener_handle(unique_listener_handle &&other) noexcept
Move constructor.
Definition event.hpp:705
void reset() noexcept
Removes the listener and resets to empty.
Base class for event loops.
Definition event.hpp:211
event_loop(event_loop &&)=delete
void post(event_base< IdEnum > base, IdEnum id, const void *data, size_t size, const std::chrono::duration< Rep, Period > &timeout)
Posts a typed event with a timeout.
Definition event.hpp:472
result< void > try_listener_remove(listener_handle handle)
Removes a listener by handle.
result< void > try_post(event_base< IdEnum > base, IdEnum id, const void *data, size_t size, const std::chrono::duration< Rep, Period > &timeout)
Posts a typed event with a timeout.
Definition event.hpp:550
void post(event_type< IdEnum > event, const void *data=nullptr, size_t size=0)
Posts a typed event via event_type, waiting indefinitely.
Definition event.hpp:493
static void destroy_system()
Destroys the system (default) event loop.
Definition event.hpp:235
void post(event_type< IdEnum > event, const void *data, size_t size, const std::chrono::duration< Rep, Period > &timeout)
Posts a typed event via event_type with a timeout.
Definition event.hpp:512
esp_event_loop_handle_t idf_handle() const
Returns the underlying ESP-IDF event loop handle.
Definition event.hpp:604
static void create_system()
Creates the system (default) event loop.
Definition event.hpp:227
event_loop & operator=(const event_loop &)=delete
result< void > try_post(event_base< IdEnum > base, IdEnum id, const void *data=nullptr, size_t size=0)
Posts a typed event, waiting indefinitely.
Definition event.hpp:529
static result< std::unique_ptr< event_loop > > try_make_user(task_config task, size_t queue_size=32)
Creates a user event loop with a dedicated dispatch task.
static event_loop & system()
Returns a reference to the system (default) event loop.
event_loop(esp_event_loop_handle_t handle)
Constructs an event_loop with the given handle.
Definition event.hpp:621
void post(event_base< IdEnum > base, IdEnum id, const void *data=nullptr, size_t size=0)
Posts a typed event, waiting indefinitely.
Definition event.hpp:453
result< void > try_post(event_type< IdEnum > event, const void *data, size_t size, const std::chrono::duration< Rep, Period > &timeout)
Posts a typed event via event_type with a timeout.
Definition event.hpp:591
static result< void > try_destroy_system()
Destroys the system (default) event loop.
static result< void > try_create_system()
Creates the system (default) event loop.
event_loop(const event_loop &)=delete
event_loop & operator=(event_loop &&)=delete
result< void > try_post(event_type< IdEnum > event, const void *data=nullptr, size_t size=0)
Posts a typed event via event_type, waiting indefinitely.
Definition event.hpp:574
static result< std::unique_ptr< user_event_loop > > try_make_user(size_t queue_size=32)
Creates a user event loop without a dedicated task.
virtual ~event_loop()
Virtual destructor.
Task lifecycle management.
Definition task.hpp:47
User-created event loop with manual dispatch.
Definition event.hpp:778
user_event_loop & operator=(user_event_loop &&)=delete
result< void > try_run(const std::chrono::duration< Rep, Period > &duration)
Dispatches pending events.
Definition event.hpp:815
user_event_loop(user_event_loop &&)=delete
user_event_loop & operator=(const user_event_loop &)=delete
user_event_loop(const user_event_loop &)=delete
void run(const std::chrono::duration< Rep, Period > &duration)
Dispatches pending events.
Definition event.hpp:799
constexpr TickType_t ticks(const std::chrono::duration< Rep, Period > &d)
Converts a std::chrono duration to TickType_t ticks.
Definition chrono.hpp:33
std::move_only_function< void(event_base< IdEnum > base, IdEnum id, void *event_data) const > event_callback
Callback type for event listeners.
Definition event.hpp:186
void listener_remove(listener_handle handle)
Removes a listener by handle.
Definition event.hpp:862
listener_handle listener_add(event_base< IdEnum > base, IdEnum id, event_callback< std::type_identity_t< IdEnum > > callback)
Registers a typed listener for a specific event.
Definition event.hpp:846
static std::unique_ptr< user_event_loop > make_user(size_t queue_size=32)
Creates a user event loop without a dedicated task.
Definition event.hpp:834
constexpr event_type< IdEnum > operator()(IdEnum id) const
Creates an event_type combining this base with a specific ID.
Definition event.hpp:173
result< listener_handle > try_listener_add(event_base< IdEnum > base, IdEnum id, event_callback< std::type_identity_t< IdEnum > > callback)
Registers a typed listener for a specific event.
Definition event.hpp:880
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:237
@ timeout
Operation timed out.
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:118
result< void > wrap(esp_err_t e)
Wraps an esp_err_t into a result<void>.
Definition error.hpp:216
Configuration for a dedicated event dispatch task.
Definition event.hpp:267
unsigned int priority
Priority for the task.
Definition event.hpp:273
size_t stack_size
Stack size for the task.
Definition event.hpp:271
std::string_view name
Name of the task.
Definition event.hpp:269
std::optional< core_id > core_affinity
Core affinity for the task.
Definition event.hpp:278
Combines an event base with a specific event ID.
Definition event.hpp:148
constexpr esp_event_base_t idf_base() const
Returns the underlying ESP-IDF event base.
Definition event.hpp:158
IdEnum id
The event ID.
Definition event.hpp:152
constexpr int32_t idf_id() const
Returns the event ID as an int32_t.
Definition event.hpp:164
event_base< IdEnum > base
The event base.
Definition event.hpp:150