esp_event_cxx.hpp 15 KB

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