esp_event_cxx.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #ifndef ESP_EVENT_CXX_H_
  7. #define ESP_EVENT_CXX_H_
  8. #ifdef __cpp_exceptions
  9. #include <functional>
  10. #include <string>
  11. #include <memory>
  12. #include <vector>
  13. #include <utility>
  14. #include <exception>
  15. #include <mutex>
  16. #include <thread>
  17. #include <atomic>
  18. #include <iostream>
  19. #include "esp_timer.h"
  20. #include "esp_err.h"
  21. #include "esp_log.h"
  22. #include "freertos/FreeRTOS.h"
  23. #include "freertos/queue.h"
  24. #include "esp_exception.hpp"
  25. #include "esp_event_api.hpp"
  26. namespace idf {
  27. namespace event {
  28. extern const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS;
  29. const std::chrono::microseconds MIN_TIMEOUT(200);
  30. class EventException : public ESPException {
  31. public:
  32. EventException(esp_err_t error) : ESPException(error) { }
  33. };
  34. /**
  35. * @brief
  36. * Thrown to signal a timeout in EventHandlerSync.
  37. */
  38. class EventTimeout : public idf::event::EventException {
  39. public:
  40. EventTimeout(esp_err_t error) : EventException(error) { }
  41. };
  42. /**
  43. * @brief
  44. * Event ID wrapper class to make C++ APIs more explicit.
  45. *
  46. * This prevents APIs from taking raw ints as event IDs which are not very expressive and may be
  47. * confused with other parameters of a function.
  48. */
  49. class ESPEventID {
  50. public:
  51. ESPEventID() : id(0) { }
  52. explicit ESPEventID(int32_t event_id) : id(event_id) { }
  53. ESPEventID(const ESPEventID &rhs) : id(rhs.id) { }
  54. inline bool operator==(const ESPEventID &rhs) const {
  55. return id == rhs.get_id();
  56. }
  57. inline ESPEventID &operator=(const ESPEventID& other) {
  58. id = other.id;
  59. return *this;
  60. }
  61. inline int32_t get_id() const {
  62. return id;
  63. }
  64. friend std::ostream& operator<<(std::ostream& os, const ESPEventID& id);
  65. private:
  66. int32_t id;
  67. };
  68. inline std::ostream& operator<<(std::ostream &os, const ESPEventID& id) {
  69. os << id.id;
  70. return os;
  71. }
  72. /*
  73. * Helper struct to bundle event base and event ID.
  74. */
  75. struct ESPEvent {
  76. ESPEvent()
  77. : base(nullptr), id() { }
  78. ESPEvent(esp_event_base_t event_base, const ESPEventID &event_id)
  79. : base(event_base), id(event_id) { }
  80. esp_event_base_t base;
  81. ESPEventID id;
  82. };
  83. /**
  84. * Thrown if event registration, i.e. \c register_event() or \c register_event_timed(), fails.
  85. */
  86. struct ESPEventRegisterException : public EventException {
  87. ESPEventRegisterException(esp_err_t err, const ESPEvent& event)
  88. : EventException(err), esp_event(event) { }
  89. const char *what() const noexcept
  90. {
  91. std::string ret_message = "Event base: " + std::string(esp_event.base)
  92. + ", Event ID: " + std::to_string(esp_event.id.get_id());
  93. return ret_message.c_str();
  94. }
  95. const ESPEvent esp_event;
  96. };
  97. inline bool operator==(const ESPEvent &lhs, const ESPEvent &rhs)
  98. {
  99. return lhs.base == rhs.base && lhs.id == rhs.id;
  100. }
  101. TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time);
  102. /**
  103. * Callback-event combination for ESPEventLoop.
  104. *
  105. * Used to bind class-based handler instances to event_handler_hook which is registered into the C-based
  106. * esp event loop.
  107. * It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event().
  108. */
  109. class ESPEventReg {
  110. public:
  111. /**
  112. * Register the event handler \c cb to handle the events defined by \c ev.
  113. *
  114. * @param cb The handler to be called.
  115. * @param ev The event for which the handler is registered.
  116. * @param api The esp event api implementation.
  117. */
  118. ESPEventReg(std::function<void(const ESPEvent &, void*)> cb,
  119. const ESPEvent& ev,
  120. std::shared_ptr<ESPEventAPI> api);
  121. /**
  122. * Unregister the event handler.
  123. */
  124. virtual ~ESPEventReg();
  125. protected:
  126. /**
  127. * This is esp_event's handler, all events registered go through this.
  128. */
  129. static void event_handler_hook(void *handler_arg,
  130. esp_event_base_t event_base,
  131. int32_t event_id,
  132. void *event_data);
  133. /**
  134. * User event handler.
  135. */
  136. std::function<void(const ESPEvent &, void*)> cb;
  137. /**
  138. * Helper function to enter the instance's scope from the generic \c event_handler_hook().
  139. */
  140. virtual void dispatch_event_handling(ESPEvent event, void *event_data);
  141. /**
  142. * Save the event here to be able to un-register from the event loop on destruction.
  143. */
  144. ESPEvent event;
  145. /**
  146. * This API handle allows different sets of APIs to be applied, e.g. default event loop API and
  147. * custom event loop API.
  148. */
  149. std::shared_ptr<ESPEventAPI> api;
  150. /**
  151. * Event handler instance from the esp event C API.
  152. */
  153. esp_event_handler_instance_t instance;
  154. };
  155. /**
  156. * Callback-event combination for ESPEventLoop with builtin timeout.
  157. *
  158. * Used to bind class-based handler instances to event_handler_hook which is registered into the C-based
  159. * esp event loop.
  160. * It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event().
  161. */
  162. class ESPEventRegTimed : public ESPEventReg {
  163. public:
  164. /**
  165. * Register the event handler \c cb to handle the events as well as a timeout callback in case the event doesn't
  166. * arrive on time.
  167. *
  168. * If the event \c ev is received before \c timeout milliseconds, then the event handler is invoked.
  169. * If no such event is received before \c timeout milliseconds, then the timeout callback is invoked.
  170. * After the timeout or the first occurance of the event, the timer will be deactivated.
  171. * The event handler registration will only be deactivated if the timeout occurs.
  172. * If event handler and timeout occur at the same time, only either the event handler or the timeout callback
  173. * will be invoked.
  174. *
  175. * @param cb The handler to be called.
  176. * @param ev The event for which the handler is registered.
  177. * @param timeout_cb The timeout callback which is called in case there is no event for \c timeout microseconds.
  178. * @param timeout The timeout in microseconds.
  179. * @param api The esp event api implementation.
  180. */
  181. ESPEventRegTimed(std::function<void(const ESPEvent &, void*)> cb,
  182. const ESPEvent& ev,
  183. std::function<void(const ESPEvent &)> timeout_cb,
  184. const std::chrono::microseconds &timeout,
  185. std::shared_ptr<ESPEventAPI> api);
  186. /**
  187. * Unregister the event handler, stop and delete the timer.
  188. */
  189. virtual ~ESPEventRegTimed();
  190. protected:
  191. /**
  192. * Helper function to hook directly into esp timer callback.
  193. */
  194. static void timer_cb_hook(void *arg);
  195. /**
  196. * Helper function to enter the instance's scope from the generic \c event_handler_hook().
  197. */
  198. void dispatch_event_handling(ESPEvent event, void *event_data) override;
  199. /**
  200. * The timer callback which will be called on timeout.
  201. */
  202. std::function<void(const ESPEvent &)> timeout_cb;
  203. /**
  204. * Timer used for event timeouts.
  205. */
  206. esp_timer_handle_t timer;
  207. /**
  208. * This mutex makes sure that a timeout and event callbacks aren't invoked both.
  209. */
  210. std::mutex timeout_mutex;
  211. };
  212. class ESPEventLoop {
  213. public:
  214. /**
  215. * Creates the ESP default event loop.
  216. *
  217. * @param api the interface to the esp_event api; this determines whether the default event loop is used
  218. * or a custom loop (or just a mock up for tests). May be nullptr, in which case it will created
  219. * here.
  220. *
  221. * @note may throw EventException
  222. */
  223. ESPEventLoop(std::shared_ptr<ESPEventAPI> api = std::make_shared<ESPEventAPIDefault>());
  224. /**
  225. * Deletes the event loop implementation (depends on \c api).
  226. */
  227. virtual ~ESPEventLoop();
  228. /**
  229. * Registers a specific handler-event combination to the event loop.
  230. *
  231. * @return a reference to the combination of handler and event which can be used to unregister
  232. * this combination again later on.
  233. *
  234. * @note registering the same event twice will result in unregistering the earlier registered handler.
  235. * @note may throw EventException, ESPEventRegisterException
  236. */
  237. std::unique_ptr<ESPEventReg> register_event(const ESPEvent &event,
  238. std::function<void(const ESPEvent &, void*)> cb);
  239. /**
  240. * Sets a timeout for event. If the specified event isn't received within timeout,
  241. * timer_cb is called.
  242. *
  243. * @note this is independent from the normal event handling. Hence, registering an event for
  244. * timeout does not interfere with a different client that has registered normally for the
  245. * same event.
  246. */
  247. std::unique_ptr<ESPEventRegTimed> register_event_timed(const ESPEvent &event,
  248. std::function<void(const ESPEvent &, void*)> cb,
  249. const std::chrono::microseconds &timeout,
  250. std::function<void(const ESPEvent &)> timer_cb);
  251. /**
  252. * Posts an event and corresponding data.
  253. *
  254. * @param event the event to post
  255. * @param event_data The event data. A copy will be made internally and a pointer to the copy will be passed to the
  256. * event handler.
  257. * @param wait_time the maximum wait time the function tries to post the event
  258. */
  259. template<typename T>
  260. void post_event_data(const ESPEvent &event,
  261. T &event_data,
  262. const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS);
  263. /**
  264. * Posts an event.
  265. *
  266. * No event data will be send. The event handler will receive a nullptr.
  267. *
  268. * @param event the event to post
  269. * @param wait_time the maximum wait time the function tries to post the event
  270. */
  271. void post_event_data(const ESPEvent &event,
  272. const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS);
  273. private:
  274. /**
  275. * This API handle allows different sets of APIs to be applied, e.g. default event loop API and
  276. * custom event loop API.
  277. */
  278. std::shared_ptr<ESPEventAPI> api;
  279. };
  280. /**
  281. * ESPEventHandlerSync builds upon ESPEventLoop to create a class which allows synchronous event handling.
  282. *
  283. * It is built around a queue which buffers received events. This queue is also used to wait synchronously (blocking)
  284. * for an event. The consequence is that once an event is registered with this class, it is guaranteed to be received
  285. * as long as the queue can handle all incoming events (see \c get_send_queue_errors()).
  286. */
  287. class ESPEventHandlerSync {
  288. public:
  289. /**
  290. * Result type for synchronous waiting.
  291. */
  292. struct EventResult {
  293. EventResult() : event(), ev_data(nullptr) { }
  294. EventResult(ESPEvent ev, void *ev_data) : event(ev), ev_data(ev_data) { }
  295. ESPEvent event;
  296. void *ev_data;
  297. };
  298. /**
  299. * Result type for synchronous waiting with timeout.
  300. */
  301. struct EventResultTimed : public EventResult {
  302. EventResultTimed(EventResult event_result, bool timeout_arg)
  303. : EventResult(event_result), timeout(timeout_arg) { }
  304. bool timeout;
  305. };
  306. /**
  307. * Sets up synchronous event handling and registers event with it.
  308. *
  309. * @param event_loop ESPEventLoop implementation to manage esp events.
  310. * @param queue_max_size The queue size of the underlying FreeRTOS queue.
  311. * The memory to store queue_max_size number of events is allocated during construction
  312. * and held until destruction!
  313. * @param queue_send_timeout The timeout for posting events to the internal queue
  314. */
  315. ESPEventHandlerSync(std::shared_ptr<ESPEventLoop> event_loop,
  316. size_t queue_max_size = 10,
  317. TickType_t queue_send_timeout = 0);
  318. /**
  319. * Unregister all formerly registered events via automatic destruction in registry.
  320. */
  321. virtual ~ESPEventHandlerSync();
  322. /**
  323. * Waits for any of the events registered before with listen_to().
  324. */
  325. EventResult wait_event();
  326. /**
  327. * Waits for an event either PLATFORM_MAX_DELAY_MS ms or timeout ms.
  328. *
  329. * @param timeout the maximum waiting time for new events if no event is pending
  330. * The timeout is restricted by the TickType_t and configTICK_RATE_HZ.
  331. * TickType_t's width determines the maximum wait time. configTICK_RATE_HZ
  332. * determines the minimum wait time.
  333. *
  334. * Throws EventTimeout in case of a timeout.
  335. */
  336. EventResultTimed wait_event_for(const std::chrono::milliseconds &timeout);
  337. /**
  338. * Register additional event to listen for.
  339. *
  340. * @note this will unregister all earlier registered events of the same event type from the event loop.
  341. */
  342. void listen_to(const ESPEvent &event);
  343. /**
  344. * Indicates whether there were errors inserting an event into the queue.
  345. * This is the case e.g. if the queue with waiting events is full already.
  346. * Use this function to adjust the queue size (\c queue_send_timeout in constructor) in your application.
  347. */
  348. size_t get_send_queue_errors() const;
  349. protected:
  350. /**
  351. * Posts an event to the internal queue.
  352. */
  353. void post_event(const EventResult &result);
  354. private:
  355. /**
  356. * Keeps track if there are any errors inserting an event into this class's event queue.
  357. */
  358. std::atomic<size_t> send_queue_errors;
  359. /**
  360. * The queue which saves events if they were received already or waits if no event was
  361. * received.
  362. */
  363. QueueHandle_t event_queue;
  364. /**
  365. * Timeout used to posting to the queue when using \c post_event(). Can be adjusted in constructor.
  366. */
  367. TickType_t queue_send_timeout;
  368. /**
  369. * The event loop used for this synchronous event handling class.
  370. */
  371. std::shared_ptr<ESPEventLoop> event_loop;
  372. /**
  373. * Keeps track of all events which are registered already for synchronous handling.
  374. *
  375. * This is necessary to keep the registration.
  376. */
  377. std::vector<std::shared_ptr<ESPEventReg> > registry;
  378. };
  379. template<typename T>
  380. void ESPEventLoop::post_event_data(const ESPEvent &event,
  381. T &event_data,
  382. const std::chrono::milliseconds &wait_time)
  383. {
  384. esp_err_t result = api->post(event.base,
  385. event.id.get_id(),
  386. &event_data,
  387. sizeof(event_data),
  388. convert_ms_to_ticks(wait_time));
  389. if (result != ESP_OK) {
  390. throw ESPException(result);
  391. }
  392. }
  393. } // namespace event
  394. } // namespace idf
  395. #endif // __cpp_exceptions
  396. #endif // ESP_EVENT_CXX_H_