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 <concepts>
26#include <esp_event.h>
27#include <functional>
28#include <type_traits>
29#include <utility>
30
58#define IDFXX_EVENT_DEFINE_BASE(name, id_enum) \
59 inline constexpr char _##name##_base[] = #name; \
60 inline constexpr ::idfxx::event_base<id_enum> name{_##name##_base}; \
61 constexpr ::idfxx::event_base<id_enum> idfxx_get_event_base(id_enum*) { \
62 return name; \
63 }
64
65namespace idfxx {
66
67template<typename IdEnum>
68class event_base;
69
89template<typename IdEnum>
91public:
93 using id_type = IdEnum;
94
118 : _base(base) {}
119
124 [[nodiscard]] constexpr esp_event_base_t idf_base() const noexcept { return _base; }
125
126private:
127 esp_event_base_t _base;
128};
129
145template<typename T>
146concept receivable_event_data = std::is_trivially_copyable_v<T> || requires(const void* data) {
147 { T::from_opaque(data) } -> std::same_as<T>;
148};
149
163template<typename T>
164concept event_data = receivable_event_data<T> && std::is_trivially_copyable_v<T>;
165
193template<typename IdEnum, typename DataType = void>
194 requires(std::is_void_v<DataType> || receivable_event_data<DataType>)
195struct event {
196 static_assert(sizeof(IdEnum) <= sizeof(int32_t), "event ID enum must fit in int32_t");
197
200};
201
203
210template<typename IdEnum>
212 return idfxx_get_event_base(static_cast<IdEnum*>(nullptr));
213}
214
222template<typename T>
223T from_opaque_data(const void* data) {
224 if constexpr (requires {
225 { T::from_opaque(data) } -> std::same_as<T>;
226 }) {
227 return T::from_opaque(data);
228 } else {
229 return *static_cast<const T*>(data);
230 }
231}
232
234
236template<typename DataType>
238 using type = std::move_only_function<void(const DataType&) const>;
239};
240
241template<>
243 using type = std::move_only_function<void() const>;
244};
246
258
270
272
301public:
302 class listener_handle;
304
305 // =========================================================================
306 // System (default) loop lifecycle
307 // =========================================================================
308
309#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
316 inline static void create_system() { unwrap(try_create_system()); }
317
324 inline static void destroy_system() { unwrap(try_destroy_system()); }
325#endif
326
333
340
348
349 // =========================================================================
350 // Event loop creation (with dedicated task)
351 // =========================================================================
352
356 struct task_config {
358 std::string_view name;
360 size_t stack_size = 2048;
362 task_priority priority = 5;
367 std::optional<core_id> core_affinity = std::nullopt;
368 };
369
370#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
382 [[nodiscard]] explicit event_loop(task_config task, size_t queue_size = 32);
383#endif
384
395
396 // =========================================================================
397 // Listener registration (typed event)
398 // =========================================================================
399
400#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
427 template<typename IdEnum, typename DataType>
429
444 template<typename IdEnum>
445 listener_handle listener_add(event_base<IdEnum> base, opaque_event_handler<std::type_identity_t<IdEnum>> callback);
446#endif
447
460 template<typename IdEnum, typename DataType>
462 try_listener_add(event<IdEnum, DataType> event, event_handler<DataType> callback);
463
476 template<typename IdEnum>
478 try_listener_add(event_base<IdEnum> base, opaque_event_handler<std::type_identity_t<IdEnum>> callback);
479
480 // =========================================================================
481 // Listener removal
482 // =========================================================================
483
484#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
492 void listener_remove(listener_handle handle);
493#endif
494
502
503 // =========================================================================
504 // Event posting
505 // =========================================================================
506
507#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
521 template<typename IdEnum>
523 unwrap(try_post(evt));
524 }
525
538 template<typename IdEnum, typename Rep, typename Period>
539 void post(event<IdEnum> evt, const std::chrono::duration<Rep, Period>& timeout) {
540 unwrap(try_post(evt, timeout));
541 }
542
558 template<typename IdEnum, typename DataType>
559 requires event_data<DataType>
561 unwrap(try_post(evt, data));
562 }
563
578 template<typename IdEnum, typename DataType, typename Rep, typename Period>
579 requires event_data<DataType>
580 void post(event<IdEnum, DataType> evt, const DataType& data, const std::chrono::duration<Rep, Period>& timeout) {
581 unwrap(try_post(evt, data, timeout));
582 }
583#endif
584
592 template<typename IdEnum>
594 return _post(event_base_lookup<IdEnum>().idf_base(), static_cast<int32_t>(evt.id), nullptr, 0, portMAX_DELAY);
595 }
596
607 template<typename IdEnum, typename Rep, typename Period>
608 [[nodiscard]] result<void> try_post(event<IdEnum> evt, const std::chrono::duration<Rep, Period>& timeout) {
609 return _post(
610 event_base_lookup<IdEnum>().idf_base(), static_cast<int32_t>(evt.id), nullptr, 0, chrono::ticks(timeout)
611 );
612 }
613
625 template<typename IdEnum, typename DataType>
626 requires event_data<DataType>
628 return _post(
629 event_base_lookup<IdEnum>().idf_base(), static_cast<int32_t>(evt.id), &data, sizeof(data), portMAX_DELAY
630 );
631 }
632
647 template<typename IdEnum, typename DataType, typename Rep, typename Period>
648 requires event_data<DataType>
650 try_post(event<IdEnum, DataType> evt, const DataType& data, const std::chrono::duration<Rep, Period>& timeout) {
651 return _post(
652 event_base_lookup<IdEnum>().idf_base(),
653 static_cast<int32_t>(evt.id),
654 &data,
655 sizeof(data),
657 );
658 }
659
664 [[nodiscard]] esp_event_loop_handle_t idf_handle() const { return _handle; }
665
667
668 event_loop(const event_loop&) = delete;
669 event_loop& operator=(const event_loop&) = delete;
670
672 event_loop(event_loop&& other) noexcept;
673
675 event_loop& operator=(event_loop&& other) noexcept;
676
677protected:
678 event_loop() = default;
679
685 explicit event_loop(esp_event_loop_handle_t handle, bool system = false)
686 : _handle(handle)
687 , _system(system) {}
688
689private:
690 static result<listener_handle> register_listener(
693 int32_t id,
694 std::move_only_function<void(esp_event_base_t, int32_t, void*) const> callback
695 );
696
697 template<typename IdEnum>
698 static result<listener_handle> register_listener(
701 int32_t id,
703 );
704
705 static result<void> unregister_listener(
709 int32_t id
710 );
711
712 void _delete() noexcept;
713 result<void> _post(esp_event_base_t base, int32_t id, const void* data, size_t size, TickType_t ticks);
714
715 esp_event_loop_handle_t _handle = nullptr;
716 bool _system = false;
717};
718
730 friend class event_loop;
732
733public:
735 listener_handle() = default;
736
737private:
742 int32_t id
743 )
744 : _loop(loop)
745 , _instance(instance)
746 , _base(base)
747 , _id(id) {}
748
749 esp_event_loop_handle_t _loop = nullptr;
750 esp_event_handler_instance_t _instance = nullptr;
751 esp_event_base_t _base = nullptr;
752 int32_t _id = 0;
753};
754
762public:
767
773 : _handle(handle) {}
774
779 : _handle(std::exchange(other._handle, listener_handle{})) {}
780
785 if (this != &other) {
786 reset();
787 _handle = std::exchange(other._handle, listener_handle{});
788 }
789 return *this;
790 }
791
794
799
805 auto h = _handle;
806 _handle = listener_handle{};
807 return h;
808 }
809
814
819 [[nodiscard]] explicit operator bool() const noexcept { return _handle._instance != nullptr; }
820
821private:
822 listener_handle _handle;
823};
824
849public:
850#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
862#endif
863
873
874#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
875 // =========================================================================
876 // Manual dispatch (for loops without dedicated task)
877 // =========================================================================
878
890 template<typename Rep, typename Period>
891 void run(const std::chrono::duration<Rep, Period>& duration) {
892 unwrap(try_run(duration));
893 }
894#endif
895
906 template<typename Rep, typename Period>
907 [[nodiscard]] result<void> try_run(const std::chrono::duration<Rep, Period>& duration) {
908 if (idf_handle() == nullptr) {
909 return error(errc::invalid_state);
910 }
911 return wrap(esp_event_loop_run(idf_handle(), chrono::ticks(duration)));
912 }
913
918
919private:
921
923 : event_loop(handle) {}
924};
925
926// =============================================================================
927// event_loop template implementations
928// =============================================================================
929
930#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
931template<typename IdEnum, typename DataType>
935
936template<typename IdEnum>
939 return unwrap(try_listener_add(base, std::move(callback)));
940}
941
943 unwrap(try_listener_remove(handle));
944}
945#endif
946
947template<typename IdEnum>
948result<event_loop::listener_handle> event_loop::register_listener(
951 int32_t id,
953) {
954 return register_listener(loop, base, id, [cb = std::move(callback)](esp_event_base_t b, int32_t i, void* data) {
955 cb(event_base<IdEnum>{b}, static_cast<IdEnum>(i), data);
956 });
957}
958
959template<typename IdEnum, typename DataType>
962 if (!_system && _handle == nullptr) {
964 }
965 if constexpr (std::is_void_v<DataType>) {
966 return register_listener(
967 _handle,
968 event_base_lookup<IdEnum>().idf_base(),
969 static_cast<int32_t>(event.id),
970 [cb = std::move(callback)](esp_event_base_t, int32_t, void*) { cb(); }
971 );
972 } else {
973 return register_listener(
974 _handle,
975 event_base_lookup<IdEnum>().idf_base(),
976 static_cast<int32_t>(event.id),
977 [cb = std::move(callback)](esp_event_base_t, int32_t, void* data) { cb(from_opaque_data<DataType>(data)); }
978 );
979 }
980}
981
982template<typename IdEnum>
985 if (!_system && _handle == nullptr) {
987 }
988 return register_listener<IdEnum>(_handle, base.idf_base(), ESP_EVENT_ANY_ID, std::move(callback));
989}
990
// end of idfxx_event
992
993} // namespace idfxx
Typed event base template.
Definition event.hpp:90
constexpr esp_event_base_t idf_base() const noexcept
Returns the underlying ESP-IDF event base.
Definition event.hpp:124
constexpr event_base(esp_event_base_t base) noexcept
Constructs from an ESP-IDF event base pointer.
Definition event.hpp:117
IdEnum id_type
The enum type for event IDs.
Definition event.hpp:93
Handle to a registered event listener.
Definition event.hpp:729
listener_handle()=default
Default constructor creates an invalid handle.
RAII handle for event listener registration.
Definition event.hpp:761
unique_listener_handle() noexcept=default
Constructs an empty handle.
unique_listener_handle & operator=(unique_listener_handle &&other) noexcept
Move assignment.
Definition event.hpp:784
listener_handle release() noexcept
Releases ownership without removing the listener.
Definition event.hpp:804
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:778
void reset() noexcept
Removes the listener and resets to empty.
Base class for event loops.
Definition event.hpp:300
void post(event< IdEnum > evt)
Posts an event without data, waiting indefinitely.
Definition event.hpp:522
static result< event_loop > make(task_config task, size_t queue_size=32)
Creates an event loop with a dedicated dispatch task.
result< void > try_listener_remove(listener_handle handle)
Removes a listener by handle.
event_loop(task_config task, size_t queue_size=32)
Creates an event loop with a dedicated dispatch task.
void post(event< IdEnum, DataType > evt, const DataType &data, const std::chrono::duration< Rep, Period > &timeout)
Posts an event with data, with a timeout.
Definition event.hpp:580
result< void > try_post(event< IdEnum, DataType > evt, const DataType &data, const std::chrono::duration< Rep, Period > &timeout)
Posts an event with data, with a timeout.
Definition event.hpp:650
static void destroy_system()
Destroys the system (default) event loop.
Definition event.hpp:324
result< void > try_post(event< IdEnum > evt)
Posts an event without data, waiting indefinitely.
Definition event.hpp:593
esp_event_loop_handle_t idf_handle() const
Returns the underlying ESP-IDF event loop handle.
Definition event.hpp:664
static void create_system()
Creates the system (default) event loop.
Definition event.hpp:316
event_loop & operator=(const event_loop &)=delete
event_loop(event_loop &&other) noexcept
Move constructor.
static event_loop & system()
Returns a reference to the system (default) event loop.
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
result< void > try_post(event< IdEnum > evt, const std::chrono::duration< Rep, Period > &timeout)
Posts an event without data, with a timeout.
Definition event.hpp:608
void post(event< IdEnum, DataType > evt, const DataType &data)
Posts an event with data, waiting indefinitely.
Definition event.hpp:560
result< void > try_post(event< IdEnum, DataType > evt, const DataType &data)
Posts an event with data, waiting indefinitely.
Definition event.hpp:627
event_loop & operator=(event_loop &&other) noexcept
Move assignment.
event_loop()=default
event_loop(esp_event_loop_handle_t handle, bool system=false)
Constructs an event_loop with the given handle.
Definition event.hpp:685
void post(event< IdEnum > evt, const std::chrono::duration< Rep, Period > &timeout)
Posts an event without data, with a timeout.
Definition event.hpp:539
Type-safe wrapper for FreeRTOS task priority values.
Definition cpu.hpp:84
Task lifecycle management.
Definition task.hpp:47
User-created event loop with manual dispatch.
Definition event.hpp:848
result< void > try_run(const std::chrono::duration< Rep, Period > &duration)
Dispatches pending events.
Definition event.hpp:907
user_event_loop & operator=(const user_event_loop &)=delete
static result< user_event_loop > make(size_t queue_size=32)
Creates a user event loop without a dedicated task.
user_event_loop(size_t queue_size)
Creates a user event loop without a dedicated task.
user_event_loop(user_event_loop &&) noexcept=default
user_event_loop(const user_event_loop &)=delete
void run(const std::chrono::duration< Rep, Period > &duration)
Dispatches pending events.
Definition event.hpp:891
Concept for types that can be both received and posted as event data.
Definition event.hpp:164
Concept for types that can be received as event data.
Definition event.hpp:146
constexpr TickType_t ticks(const std::chrono::duration< Rep, Period > &d)
Converts a std::chrono duration to TickType_t ticks.
Definition chrono.hpp:33
result< listener_handle > try_listener_add(event< IdEnum, DataType > event, event_handler< DataType > callback)
Registers a type-safe listener for a specific event.
Definition event.hpp:961
void listener_remove(listener_handle handle)
Removes a listener by handle.
Definition event.hpp:942
std::move_only_function< void(event_base< IdEnum > base, IdEnum id, void *event_data) const > opaque_event_handler
Callback type for wildcard event listeners.
Definition event.hpp:269
typename event_handler_traits< DataType >::type event_handler
Handler type for typed event listeners.
Definition event.hpp:257
listener_handle listener_add(event< IdEnum, DataType > event, event_handler< DataType > callback)
Registers a type-safe listener for a specific event.
Definition event.hpp:932
@ base
Base (factory) MAC address.
constexpr std::unexpected< std::error_code > error(E e) noexcept
Creates an unexpected error from an error code enum.
Definition error.hpp:187
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:307
@ invalid_state
Invalid state.
@ timeout
Operation timed out.
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:120
result< void > wrap(esp_err_t e)
Wraps an esp_err_t into a result<void>.
Definition error.hpp:286
Configuration for a dedicated event dispatch task.
Definition event.hpp:356
std::string_view name
Name of the task.
Definition event.hpp:358
A typed event that pairs an event ID with its data type.
Definition event.hpp:195
IdEnum id
The event ID.
Definition event.hpp:199