mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
C++: ESP Event wrapper classes
* Provide easy interface to esp_event in C++ * Extended functionality linke synchronous waiting for events * Closes IDF-1048 * Closes IDF-232
This commit is contained in:
parent
303587103a
commit
f835bead45
10
examples/cxx/experimental/esp_event_async_cxx/CMakeLists.txt
Normal file
10
examples/cxx/experimental/esp_event_async_cxx/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an experimental c++ component.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp_event_async_cxx)
|
11
examples/cxx/experimental/esp_event_async_cxx/Makefile
Normal file
11
examples/cxx/experimental/esp_event_async_cxx/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := esp_event_async_cxx
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/cxx/experimental/experimental_cpp_component
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
41
examples/cxx/experimental/esp_event_async_cxx/README.md
Normal file
41
examples/cxx/experimental/esp_event_async_cxx/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# ESP-Event asynchronous example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
|
||||
## How to use example
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* Set serial port under Serial Flasher Options.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The object is created twice, hence the started Eventloop and finished destruction lines appear twice.
|
||||
```
|
||||
NORMAL TESTING...
|
||||
received event: test/0; data: 47
|
||||
received event: test/1
|
||||
|
||||
TIMEOUT TESTING...
|
||||
received event: test/0
|
||||
|
||||
TIMEOUT for event: test/0
|
||||
I (10419) ESP Event C++ Async: Finished example
|
||||
```
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "esp_event_async_cxx_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
@ -0,0 +1,110 @@
|
||||
/* ESP Event C++ Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <iostream>
|
||||
#include "esp_event_cxx.hpp"
|
||||
#include "esp_event.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
using namespace idf::event;
|
||||
using namespace std;
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(TEST_EVENT_BASE);
|
||||
const ESPEventID TEST_EVENT_ID_0(0);
|
||||
const ESPEventID TEST_EVENT_ID_1(1);
|
||||
|
||||
ESPEvent TEMPLATE_EVENT_0(TEST_EVENT_BASE, TEST_EVENT_ID_0);
|
||||
ESPEvent TEMPLATE_EVENT_1(TEST_EVENT_BASE, TEST_EVENT_ID_1);
|
||||
|
||||
// We use "normal" static functions here. However, passing std::function types also works with
|
||||
// ESPEventLoop::register_event() and ESPEventLoop::register_event_timed(), allowing to reference custom data.
|
||||
static void callback(const ESPEvent &event, void *data)
|
||||
{
|
||||
cout << "received event: " << event.base << "/" << event.id;
|
||||
if (data) {
|
||||
cout << "; data: " << *(static_cast<int*>(data));
|
||||
}
|
||||
cout << endl;
|
||||
};
|
||||
|
||||
static void timeout_callback(const ESPEvent &event)
|
||||
{
|
||||
cout << "TIMEOUT for event: " << event.base << "/" << event.id << endl;
|
||||
};
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
{
|
||||
cout << "Normal testing..." << endl;
|
||||
ESPEventLoop loop;
|
||||
int data = 47;
|
||||
int captured_data = 42;
|
||||
|
||||
unique_ptr<ESPEventReg> reg_1 = loop.register_event(TEMPLATE_EVENT_0,
|
||||
[captured_data](const ESPEvent &event, void *data) {
|
||||
cout << "received event: " << event.base << "/" << event.id;
|
||||
if (data) {
|
||||
cout << "; event data: " << *(static_cast<int*>(data));
|
||||
}
|
||||
cout << "; handler data: " << captured_data << endl;
|
||||
});
|
||||
unique_ptr<ESPEventReg> reg_2;
|
||||
|
||||
// Run for 4 seconds...
|
||||
for (int i = 0; i < 4; i++) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
// will be received
|
||||
loop.post_event_data(TEMPLATE_EVENT_0, data);
|
||||
break;
|
||||
case 1:
|
||||
// will NOT be received because TEST_EVENT_ID_1 hasn't been registered yet
|
||||
loop.post_event_data(TEMPLATE_EVENT_1);
|
||||
break;
|
||||
case 2:
|
||||
// register TEST_EVENT_ID_1
|
||||
reg_2 = loop.register_event(TEMPLATE_EVENT_1, callback);
|
||||
// will be received
|
||||
loop.post_event_data(TEMPLATE_EVENT_1);
|
||||
break;
|
||||
case 3:
|
||||
// unregister callback with TEST_EVENT_ID_1 again
|
||||
reg_2.reset();
|
||||
// will NOT be received
|
||||
loop.post_event_data(TEMPLATE_EVENT_1);
|
||||
break;
|
||||
|
||||
}
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
cout << endl << "Timeout testing..." << endl;
|
||||
ESPEventLoop loop;
|
||||
|
||||
// Setting timeout and sending event early enough.
|
||||
unique_ptr<ESPEventRegTimed> timed_reg = loop.register_event_timed(TEMPLATE_EVENT_0,
|
||||
callback,
|
||||
chrono::milliseconds(500),
|
||||
timeout_callback);
|
||||
loop.post_event_data(TEMPLATE_EVENT_0);
|
||||
cout << endl;
|
||||
|
||||
// Setting timeout and sending event too late.
|
||||
// Note: the old registration will be properly unregistered by resetting the unique_ptr.
|
||||
timed_reg = loop.register_event_timed(TEMPLATE_EVENT_0,
|
||||
callback,
|
||||
chrono::milliseconds(500),
|
||||
timeout_callback);
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
loop.post_event_data(TEMPLATE_EVENT_0);
|
||||
|
||||
}
|
||||
cout << "Finished example" << endl;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
# Enable C++ exceptions and set emergency pool size for exception objects
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
|
10
examples/cxx/experimental/esp_event_cxx/CMakeLists.txt
Normal file
10
examples/cxx/experimental/esp_event_cxx/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an experimental c++ component.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp_event_cxx)
|
11
examples/cxx/experimental/esp_event_cxx/Makefile
Normal file
11
examples/cxx/experimental/esp_event_cxx/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := esp_event_cxx
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/cxx/experimental/experimental_cpp_component
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
44
examples/cxx/experimental/esp_event_cxx/README.md
Normal file
44
examples/cxx/experimental/esp_event_cxx/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# ESP Event synchronous example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
|
||||
## How to use example
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* Set serial port under Serial Flasher Options.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (409) ESP Event C++: started event loop
|
||||
event base test, ID: 0; called first round
|
||||
event base test, ID: 1; called first round
|
||||
received timeout
|
||||
event base test, ID: 0; called second round
|
||||
event base test, ID: 1; called second round
|
||||
event base test, ID: 0; called second round
|
||||
event base test, ID: 1; called second round
|
||||
event base test, ID: 0; called second round
|
||||
event base test, ID: 1; called second round
|
||||
event base test, ID: 0; called second round
|
||||
event base test, ID: 1; called second round
|
||||
I (10419) ESP Event C++: Missed: 0 events
|
||||
```
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "esp_event_cxx_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
@ -0,0 +1,80 @@
|
||||
/* ESP Event C++ Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include "esp_event_cxx.hpp"
|
||||
#include "esp_event.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
using namespace idf::event;
|
||||
using namespace std;
|
||||
|
||||
#define EVENT_NUM 10
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(TEST_EVENT_BASE);
|
||||
static ESPEventID TEST_EVENT_ID_0(0);
|
||||
static ESPEventID TEST_EVENT_ID_1(1);
|
||||
|
||||
// the events we want to register
|
||||
static ESPEvent TEMPLATE_EVENT_0(TEST_EVENT_BASE, TEST_EVENT_ID_0);
|
||||
static ESPEvent TEMPLATE_EVENT_1(TEST_EVENT_BASE, TEST_EVENT_ID_1);
|
||||
|
||||
// helper function to post events, simulating an event source
|
||||
void post_events() {
|
||||
for (int i = 0; i < EVENT_NUM; i++) {
|
||||
ESP_ERROR_CHECK(esp_event_post(TEST_EVENT_BASE,
|
||||
i % 2 ? TEST_EVENT_ID_1.get_id() : TEST_EVENT_ID_0.get_id(),
|
||||
nullptr,
|
||||
0,
|
||||
portMAX_DELAY));
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESPEventHandlerSync event_handler(make_shared<ESPEventLoop>());
|
||||
event_handler.listen_to(TEMPLATE_EVENT_0);
|
||||
event_handler.listen_to(TEMPLATE_EVENT_1);
|
||||
cout << "started event loop" << endl;
|
||||
|
||||
thread th(post_events);
|
||||
|
||||
// waiting for two events to be posted via post_events()
|
||||
this_thread::sleep_for(chrono::milliseconds(1100));
|
||||
|
||||
// reading the two already received events, then running into timeout
|
||||
for (;;) {
|
||||
ESPEventHandlerSync::EventResultTimed result = event_handler.wait_event_for(std::chrono::milliseconds(500));
|
||||
|
||||
if (result.timeout) { // if timeout, then the default esp event will be sent with invalid base
|
||||
break;
|
||||
} else {
|
||||
cout << "event base " << result.event.base
|
||||
<< ", ID: " << result.event.id
|
||||
<< "; called first round" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
cout << "received timeout" << endl;
|
||||
this_thread::sleep_for(chrono::milliseconds(2000));
|
||||
|
||||
// Read the events we missed up until now and then continue reading
|
||||
for (int i = 0; i < EVENT_NUM - 2; i++) {
|
||||
ESPEventHandlerSync::EventResult result = event_handler.wait_event();
|
||||
cout << "event base " << result.event.base
|
||||
<< ", ID: " << result.event.id
|
||||
<< "; called second round" << endl;
|
||||
}
|
||||
|
||||
th.join();
|
||||
|
||||
// checking whether events were missed by the ESPEventHandlerSync class
|
||||
cout << "Missed: " << event_handler.get_send_queue_errors() << " events" << endl;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
# Enable C++ exceptions and set emergency pool size for exception objects
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
|
@ -1,3 +1,3 @@
|
||||
idf_component_register(SRCS "esp_exception.cpp" "i2c_cxx.cpp"
|
||||
idf_component_register(SRCS "esp_exception.cpp" "i2c_cxx.cpp" "esp_event_api.cpp" "esp_event_cxx.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES driver)
|
||||
REQUIRES driver esp_event)
|
||||
|
@ -0,0 +1,116 @@
|
||||
#include "esp_event.h"
|
||||
#include "esp_event_cxx.hpp"
|
||||
#include "esp_event_api.hpp"
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
|
||||
namespace idf {
|
||||
|
||||
namespace event {
|
||||
|
||||
ESPEventAPIDefault::ESPEventAPIDefault()
|
||||
{
|
||||
esp_err_t res = esp_event_loop_create_default();
|
||||
if (res != ESP_OK) {
|
||||
throw idf::event::EventException(res);
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventAPIDefault::~ESPEventAPIDefault()
|
||||
{
|
||||
esp_event_loop_delete_default();
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPIDefault::handler_register(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_t event_handler,
|
||||
void *event_handler_arg,
|
||||
esp_event_handler_instance_t *instance)
|
||||
{
|
||||
return esp_event_handler_instance_register(event_base,
|
||||
event_id,
|
||||
event_handler,
|
||||
event_handler_arg,
|
||||
instance);
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPIDefault::handler_unregister(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_instance_t instance)
|
||||
{
|
||||
return esp_event_handler_instance_unregister(event_base, event_id, instance);
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPIDefault::post(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void* event_data,
|
||||
size_t event_data_size,
|
||||
TickType_t ticks_to_wait)
|
||||
{
|
||||
return esp_event_post(event_base,
|
||||
event_id,
|
||||
event_data,
|
||||
event_data_size,
|
||||
ticks_to_wait);
|
||||
|
||||
}
|
||||
|
||||
ESPEventAPICustom::ESPEventAPICustom(const esp_event_loop_args_t &event_loop_args)
|
||||
{
|
||||
esp_err_t res = esp_event_loop_create(&event_loop_args, &event_loop);
|
||||
if (res != ESP_OK) {
|
||||
throw idf::event::EventException(res);
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventAPICustom::~ESPEventAPICustom()
|
||||
{
|
||||
esp_event_loop_delete(event_loop);
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPICustom::handler_register(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_t event_handler,
|
||||
void *event_handler_arg,
|
||||
esp_event_handler_instance_t *instance)
|
||||
{
|
||||
return esp_event_handler_instance_register_with(event_loop,
|
||||
event_base,
|
||||
event_id,
|
||||
event_handler,
|
||||
event_handler_arg,
|
||||
instance);
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPICustom::handler_unregister(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_instance_t instance)
|
||||
{
|
||||
return esp_event_handler_instance_unregister_with(event_loop, event_base, event_id, instance);
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPICustom::post(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void* event_data,
|
||||
size_t event_data_size,
|
||||
TickType_t ticks_to_wait)
|
||||
{
|
||||
return esp_event_post_to(event_loop,
|
||||
event_base,
|
||||
event_id,
|
||||
event_data,
|
||||
event_data_size,
|
||||
ticks_to_wait);
|
||||
}
|
||||
|
||||
esp_err_t ESPEventAPICustom::run(TickType_t ticks_to_run)
|
||||
{
|
||||
return esp_event_loop_run(event_loop, ticks_to_run);
|
||||
}
|
||||
|
||||
} // event
|
||||
|
||||
} // idf
|
||||
|
||||
#endif // __cpp_exceptions
|
||||
|
@ -0,0 +1,225 @@
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "esp_event_cxx.hpp"
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
|
||||
using namespace idf::event;
|
||||
using namespace std;
|
||||
|
||||
namespace idf {
|
||||
|
||||
namespace event {
|
||||
|
||||
const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS(portMAX_DELAY *portTICK_PERIOD_MS);
|
||||
|
||||
ESPEventReg::ESPEventReg(std::function<void(const ESPEvent &, void*)> cb,
|
||||
const ESPEvent& ev,
|
||||
std::shared_ptr<ESPEventAPI> api)
|
||||
: cb(cb), event(ev), api(api)
|
||||
{
|
||||
if (!cb) throw EventException(ESP_ERR_INVALID_ARG);
|
||||
if (!api) throw EventException(ESP_ERR_INVALID_ARG);
|
||||
|
||||
esp_err_t reg_result = api->handler_register(ev.base, ev.id.get_id(), event_handler_hook, this, &instance);
|
||||
if (reg_result != ESP_OK) {
|
||||
throw ESPEventRegisterException(reg_result, event);
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventReg::~ESPEventReg()
|
||||
{
|
||||
api->handler_unregister(event.base, event.id.get_id(), instance);
|
||||
}
|
||||
|
||||
void ESPEventReg::dispatch_event_handling(ESPEvent event, void *event_data)
|
||||
{
|
||||
cb(event, event_data);
|
||||
}
|
||||
|
||||
void ESPEventReg::event_handler_hook(void *handler_arg,
|
||||
esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void *event_data)
|
||||
{
|
||||
ESPEventReg *object = static_cast<ESPEventReg*>(handler_arg);
|
||||
object->dispatch_event_handling(ESPEvent(event_base, ESPEventID(event_id)), event_data);
|
||||
}
|
||||
|
||||
ESPEventRegTimed::ESPEventRegTimed(std::function<void(const ESPEvent &, void*)> cb,
|
||||
const ESPEvent& ev,
|
||||
std::function<void(const ESPEvent &)> timeout_cb,
|
||||
const std::chrono::microseconds &timeout,
|
||||
std::shared_ptr<ESPEventAPI> api)
|
||||
: ESPEventReg(cb, ev, api), timeout_cb(timeout_cb)
|
||||
{
|
||||
if (!timeout_cb || timeout < MIN_TIMEOUT) {
|
||||
throw EventException(ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
|
||||
const esp_timer_create_args_t oneshot_timer_args {
|
||||
timer_cb_hook,
|
||||
static_cast<void*>(this),
|
||||
ESP_TIMER_TASK,
|
||||
"event"
|
||||
};
|
||||
|
||||
esp_err_t res = esp_timer_create(&oneshot_timer_args, &timer);
|
||||
if (res != ESP_OK) {
|
||||
throw EventException(res);
|
||||
}
|
||||
|
||||
esp_err_t timer_result = esp_timer_start_once(timer, timeout.count());
|
||||
if (timer_result != ESP_OK) {
|
||||
esp_timer_delete(timer);
|
||||
throw EventException(timer_result);
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventRegTimed::~ESPEventRegTimed()
|
||||
{
|
||||
std::lock_guard<mutex> guard(timeout_mutex);
|
||||
esp_timer_stop(timer);
|
||||
esp_timer_delete(timer);
|
||||
// TODO: is it guaranteed that there is no pending timer callback for timer?
|
||||
}
|
||||
|
||||
void ESPEventRegTimed::dispatch_event_handling(ESPEvent event, void *event_data)
|
||||
{
|
||||
if (timeout_mutex.try_lock()) {
|
||||
esp_timer_stop(timer);
|
||||
cb(event, event_data);
|
||||
timeout_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void ESPEventRegTimed::timer_cb_hook(void *arg)
|
||||
{
|
||||
ESPEventRegTimed *object = static_cast<ESPEventRegTimed *>(arg);
|
||||
if (object->timeout_mutex.try_lock()) {
|
||||
object->timeout_cb(object->event);
|
||||
object->api->handler_unregister(object->event.base, object->event.id.get_id(), object->instance);
|
||||
object->timeout_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventLoop::ESPEventLoop(std::shared_ptr<ESPEventAPI> api) : api(api) {
|
||||
if (!api) throw EventException(ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
|
||||
ESPEventLoop::~ESPEventLoop() { }
|
||||
|
||||
unique_ptr<ESPEventReg> ESPEventLoop::register_event(const ESPEvent &event,
|
||||
function<void(const ESPEvent &, void*)> cb)
|
||||
{
|
||||
return unique_ptr<ESPEventReg>(new ESPEventReg(cb, event, api));
|
||||
}
|
||||
|
||||
std::unique_ptr<ESPEventRegTimed> ESPEventLoop::register_event_timed(const ESPEvent &event,
|
||||
std::function<void(const ESPEvent &, void*)> cb,
|
||||
const std::chrono::microseconds &timeout,
|
||||
std::function<void(const ESPEvent &)> timer_cb)
|
||||
{
|
||||
return std::unique_ptr<ESPEventRegTimed>(new ESPEventRegTimed(cb, event, timer_cb, timeout, api));
|
||||
}
|
||||
|
||||
void ESPEventLoop::post_event_data(const ESPEvent &event,
|
||||
const chrono::milliseconds &wait_time)
|
||||
{
|
||||
esp_err_t result = api->post(event.base,
|
||||
event.id.get_id(),
|
||||
nullptr,
|
||||
0,
|
||||
convert_ms_to_ticks(wait_time));
|
||||
|
||||
if (result != ESP_OK) {
|
||||
throw ESPException(result);
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventHandlerSync::ESPEventHandlerSync(std::shared_ptr<ESPEventLoop> event_loop,
|
||||
size_t queue_max_size,
|
||||
TickType_t queue_send_timeout)
|
||||
: send_queue_errors(0),
|
||||
queue_send_timeout(queue_send_timeout),
|
||||
event_loop(event_loop)
|
||||
{
|
||||
if (!event_loop) throw EventException(ESP_ERR_INVALID_ARG);
|
||||
if (queue_max_size < 1) throw EventException(ESP_ERR_INVALID_ARG);
|
||||
|
||||
event_queue = xQueueCreate(queue_max_size, sizeof(EventResult));
|
||||
if (event_queue == nullptr) {
|
||||
esp_event_loop_delete_default();
|
||||
throw EventException(ESP_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
ESPEventHandlerSync::~ESPEventHandlerSync()
|
||||
{
|
||||
vQueueDelete(event_queue);
|
||||
}
|
||||
|
||||
ESPEventHandlerSync::EventResult ESPEventHandlerSync::wait_event()
|
||||
{
|
||||
EventResult event_result;
|
||||
BaseType_t result = pdFALSE;
|
||||
while (result != pdTRUE) {
|
||||
result = xQueueReceive(event_queue, &event_result, convert_ms_to_ticks(PLATFORM_MAX_DELAY_MS));
|
||||
}
|
||||
|
||||
return event_result;
|
||||
}
|
||||
|
||||
ESPEventHandlerSync::EventResultTimed ESPEventHandlerSync::wait_event_for(const std::chrono::milliseconds &timeout)
|
||||
{
|
||||
EventResult event_result;
|
||||
BaseType_t result = xQueueReceive(event_queue, &event_result, convert_ms_to_ticks(timeout));
|
||||
|
||||
EventResultTimed event_result_timed(event_result, result != pdTRUE);
|
||||
return event_result_timed;
|
||||
}
|
||||
|
||||
void ESPEventHandlerSync::listen_to(const ESPEvent &event)
|
||||
{
|
||||
std::shared_ptr<ESPEventReg> reg = event_loop->register_event(event, [this](const ESPEvent &event, void *data) {
|
||||
EventResult result(event, data);
|
||||
post_event(result);
|
||||
});
|
||||
registry.push_back(reg);
|
||||
}
|
||||
|
||||
void ESPEventHandlerSync::post_event(const EventResult &event_result)
|
||||
{
|
||||
BaseType_t result = xQueueSendToBack(event_queue, (void *) &event_result, queue_send_timeout);
|
||||
if (result != pdTRUE) {
|
||||
++send_queue_errors;
|
||||
}
|
||||
}
|
||||
|
||||
size_t ESPEventHandlerSync::get_send_queue_errors() const
|
||||
{
|
||||
return send_queue_errors;
|
||||
}
|
||||
|
||||
TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time)
|
||||
{
|
||||
return time.count() / portTICK_PERIOD_MS;
|
||||
}
|
||||
|
||||
} // namespace event
|
||||
|
||||
} // namespace idf
|
||||
|
||||
#endif // __cpp_exceptions
|
@ -20,6 +20,10 @@ namespace idf {
|
||||
|
||||
ESPException::ESPException(esp_err_t error) : error(error) { }
|
||||
|
||||
const char *ESPException::what() const noexcept {
|
||||
return esp_err_to_name(error);
|
||||
}
|
||||
|
||||
} // namespace idf
|
||||
|
||||
#endif // __cpp_exceptions
|
||||
|
@ -0,0 +1,123 @@
|
||||
#ifndef ESP_EVENT_API_HPP_
|
||||
#define ESP_EVENT_API_HPP_
|
||||
|
||||
#include "esp_event.h"
|
||||
|
||||
namespace idf {
|
||||
|
||||
namespace event {
|
||||
|
||||
/**
|
||||
* Abstract interface for direct calls to esp_event C-API.
|
||||
* This is generally not intended to be used directly.
|
||||
* It's main purpose is to provide ESPEventLoop a unified API not dependent on whether the default event loop or a
|
||||
* custom event loop is used.
|
||||
* The interface resembles the C-API, have a look there for further documentation.
|
||||
*/
|
||||
class ESPEventAPI {
|
||||
public:
|
||||
virtual ~ESPEventAPI() { }
|
||||
|
||||
virtual esp_err_t handler_register(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_t event_handler,
|
||||
void* event_handler_arg,
|
||||
esp_event_handler_instance_t *instance) = 0;
|
||||
|
||||
virtual esp_err_t handler_unregister(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_instance_t instance) = 0;
|
||||
|
||||
virtual esp_err_t post(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void* event_data,
|
||||
size_t event_data_size,
|
||||
TickType_t ticks_to_wait) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief API version with default event loop.
|
||||
*
|
||||
* It will direct calls to the default event loop API.
|
||||
*/
|
||||
class ESPEventAPIDefault : public ESPEventAPI {
|
||||
public:
|
||||
ESPEventAPIDefault();
|
||||
virtual ~ESPEventAPIDefault();
|
||||
|
||||
/**
|
||||
* Copying would lead to deletion of event loop through destructor.
|
||||
*/
|
||||
ESPEventAPIDefault(const ESPEventAPIDefault &o) = delete;
|
||||
ESPEventAPIDefault& operator=(const ESPEventAPIDefault&) = delete;
|
||||
|
||||
esp_err_t handler_register(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_t event_handler,
|
||||
void* event_handler_arg,
|
||||
esp_event_handler_instance_t *instance) override;
|
||||
|
||||
esp_err_t handler_unregister(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_instance_t instance) override;
|
||||
|
||||
esp_err_t post(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void* event_data,
|
||||
size_t event_data_size,
|
||||
TickType_t ticks_to_wait) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief API version with custom event loop.
|
||||
*
|
||||
* It will direct calls to the custom event loop API.
|
||||
* The loop parameters are given in the constructor the same way it's done in esp_event_loop_create() in event.h.
|
||||
* This class also provides a run method in case the custom event loop was created without its own task.
|
||||
*/
|
||||
class ESPEventAPICustom : public ESPEventAPI {
|
||||
public:
|
||||
/**
|
||||
* @param event_loop_args the event loop arguments, refer to esp_event_loop_create() in event.h.
|
||||
*/
|
||||
ESPEventAPICustom(const esp_event_loop_args_t &event_loop_args);
|
||||
|
||||
virtual ~ESPEventAPICustom();
|
||||
|
||||
/**
|
||||
* Copying would lead to deletion of event loop through destructor.
|
||||
*/
|
||||
ESPEventAPICustom(const ESPEventAPICustom &o) = delete;
|
||||
ESPEventAPICustom& operator=(const ESPEventAPICustom&) = delete;
|
||||
|
||||
esp_err_t handler_register(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_t event_handler,
|
||||
void* event_handler_arg,
|
||||
esp_event_handler_instance_t *instance) override;
|
||||
|
||||
esp_err_t handler_unregister(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_instance_t instance) override;
|
||||
|
||||
esp_err_t post(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void* event_data,
|
||||
size_t event_data_size,
|
||||
TickType_t ticks_to_wait) override;
|
||||
|
||||
/**
|
||||
* Run the event loop. The behavior is the same as esp_event_loop_run in esp_event.h.
|
||||
*/
|
||||
esp_err_t run(TickType_t ticks_to_run);
|
||||
|
||||
private:
|
||||
esp_event_loop_handle_t event_loop;
|
||||
};
|
||||
|
||||
} // event
|
||||
|
||||
} // idf
|
||||
|
||||
#endif // ESP_EVENT_API_HPP_
|
||||
|
@ -0,0 +1,470 @@
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef ESP_EVENT_CXX_H_
|
||||
#define ESP_EVENT_CXX_H_
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_exception.hpp"
|
||||
#include "esp_event_api.hpp"
|
||||
|
||||
namespace idf {
|
||||
|
||||
namespace event {
|
||||
|
||||
extern const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS;
|
||||
|
||||
const std::chrono::microseconds MIN_TIMEOUT(200);
|
||||
|
||||
class EventException : public ESPException {
|
||||
public:
|
||||
EventException(esp_err_t error) : ESPException(error) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Thrown to signal a timeout in EventHandlerSync.
|
||||
*/
|
||||
class EventTimeout : public idf::event::EventException {
|
||||
public:
|
||||
EventTimeout(esp_err_t error) : EventException(error) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Event ID wrapper class to make C++ APIs more explicit.
|
||||
*
|
||||
* This prevents APIs from taking raw ints as event IDs which are not very expressive and may be
|
||||
* confused with other parameters of a function.
|
||||
*/
|
||||
class ESPEventID {
|
||||
public:
|
||||
ESPEventID() : id(0) { }
|
||||
explicit ESPEventID(int32_t event_id) : id(event_id) { }
|
||||
|
||||
inline bool operator==(const ESPEventID &rhs) const {
|
||||
return id == rhs.get_id();
|
||||
}
|
||||
|
||||
inline ESPEventID &operator=(const ESPEventID& other) {
|
||||
id = other.id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline int32_t get_id() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const ESPEventID& id);
|
||||
|
||||
private:
|
||||
int32_t id;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream &os, const ESPEventID& id) {
|
||||
os << id.id;
|
||||
return os;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper struct to bundle event base and event ID.
|
||||
*/
|
||||
struct ESPEvent {
|
||||
ESPEvent()
|
||||
: base(nullptr), id() { }
|
||||
ESPEvent(esp_event_base_t event_base, const ESPEventID &event_id)
|
||||
: base(event_base), id(event_id) { }
|
||||
|
||||
esp_event_base_t base;
|
||||
ESPEventID id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Thrown if event registration, i.e. \c register_event() or \c register_event_timed(), fails.
|
||||
*/
|
||||
struct ESPEventRegisterException : public EventException {
|
||||
ESPEventRegisterException(esp_err_t err, const ESPEvent& event)
|
||||
: EventException(err), esp_event(event) { }
|
||||
|
||||
const char *what() const noexcept
|
||||
{
|
||||
std::string ret_message = "Event base: " + std::string(esp_event.base)
|
||||
+ ", Event ID: " + std::to_string(esp_event.id.get_id());
|
||||
return ret_message.c_str();
|
||||
}
|
||||
|
||||
const ESPEvent esp_event;
|
||||
};
|
||||
|
||||
inline bool operator==(const ESPEvent &lhs, const ESPEvent &rhs)
|
||||
{
|
||||
return lhs.base == rhs.base && lhs.id == rhs.id;
|
||||
}
|
||||
|
||||
TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time);
|
||||
|
||||
/**
|
||||
* Callback-event combination for ESPEventLoop.
|
||||
*
|
||||
* Used to bind class-based handler instances to event_handler_hook which is registered into the C-based
|
||||
* esp event loop.
|
||||
* It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event().
|
||||
*/
|
||||
class ESPEventReg {
|
||||
public:
|
||||
/**
|
||||
* Register the event handler \c cb to handle the events defined by \c ev.
|
||||
*
|
||||
* @param cb The handler to be called.
|
||||
* @param ev The event for which the handler is registered.
|
||||
* @param api The esp event api implementation.
|
||||
*/
|
||||
ESPEventReg(std::function<void(const ESPEvent &, void*)> cb,
|
||||
const ESPEvent& ev,
|
||||
std::shared_ptr<ESPEventAPI> api);
|
||||
|
||||
/**
|
||||
* Unregister the event handler.
|
||||
*/
|
||||
virtual ~ESPEventReg();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* This is esp_event's handler, all events registered go through this.
|
||||
*/
|
||||
static void event_handler_hook(void *handler_arg,
|
||||
esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void *event_data);
|
||||
|
||||
/**
|
||||
* User event handler.
|
||||
*/
|
||||
std::function<void(const ESPEvent &, void*)> cb;
|
||||
|
||||
/**
|
||||
* Helper function to enter the instance's scope from the generic \c event_handler_hook().
|
||||
*/
|
||||
virtual void dispatch_event_handling(ESPEvent event, void *event_data);
|
||||
|
||||
/**
|
||||
* Save the event here to be able to un-register from the event loop on destruction.
|
||||
*/
|
||||
ESPEvent event;
|
||||
|
||||
/**
|
||||
* This API handle allows different sets of APIs to be applied, e.g. default event loop API and
|
||||
* custom event loop API.
|
||||
*/
|
||||
std::shared_ptr<ESPEventAPI> api;
|
||||
|
||||
/**
|
||||
* Event handler instance from the esp event C API.
|
||||
*/
|
||||
esp_event_handler_instance_t instance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback-event combination for ESPEventLoop with builtin timeout.
|
||||
*
|
||||
* Used to bind class-based handler instances to event_handler_hook which is registered into the C-based
|
||||
* esp event loop.
|
||||
* It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event().
|
||||
*/
|
||||
class ESPEventRegTimed : public ESPEventReg {
|
||||
public:
|
||||
/**
|
||||
* Register the event handler \c cb to handle the events as well as a timeout callback in case the event doesn't
|
||||
* arrive on time.
|
||||
*
|
||||
* If the event \c ev is received before \c timeout milliseconds, then the event handler is invoked.
|
||||
* If no such event is received before \c timeout milliseconds, then the timeout callback is invoked.
|
||||
* After the timeout or the first occurance of the event, the timer will be deactivated.
|
||||
* The event handler registration will only be deactivated if the timeout occurs.
|
||||
* If event handler and timeout occur at the same time, only either the event handler or the timeout callback
|
||||
* will be invoked.
|
||||
*
|
||||
* @param cb The handler to be called.
|
||||
* @param ev The event for which the handler is registered.
|
||||
* @param timeout_cb The timeout callback which is called in case there is no event for \c timeout microseconds.
|
||||
* @param timeout The timeout in microseconds.
|
||||
* @param api The esp event api implementation.
|
||||
*/
|
||||
ESPEventRegTimed(std::function<void(const ESPEvent &, void*)> cb,
|
||||
const ESPEvent& ev,
|
||||
std::function<void(const ESPEvent &)> timeout_cb,
|
||||
const std::chrono::microseconds &timeout,
|
||||
std::shared_ptr<ESPEventAPI> api);
|
||||
|
||||
/**
|
||||
* Unregister the event handler, stop and delete the timer.
|
||||
*/
|
||||
virtual ~ESPEventRegTimed();
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Helper function to hook directly into esp timer callback.
|
||||
*/
|
||||
static void timer_cb_hook(void *arg);
|
||||
|
||||
/**
|
||||
* Helper function to enter the instance's scope from the generic \c event_handler_hook().
|
||||
*/
|
||||
void dispatch_event_handling(ESPEvent event, void *event_data) override;
|
||||
|
||||
/**
|
||||
* The timer callback which will be called on timeout.
|
||||
*/
|
||||
std::function<void(const ESPEvent &)> timeout_cb;
|
||||
|
||||
/**
|
||||
* Timer used for event timeouts.
|
||||
*/
|
||||
esp_timer_handle_t timer;
|
||||
|
||||
/**
|
||||
* This mutex makes sure that a timeout and event callbacks aren't invoked both.
|
||||
*/
|
||||
std::mutex timeout_mutex;
|
||||
};
|
||||
|
||||
class ESPEventLoop {
|
||||
public:
|
||||
/**
|
||||
* Creates the ESP default event loop.
|
||||
*
|
||||
* @param api the interface to the esp_event api; this determines whether the default event loop is used
|
||||
* or a custom loop (or just a mock up for tests). May be nullptr, in which case it will created
|
||||
* here.
|
||||
*
|
||||
* @note may throw EventException
|
||||
*/
|
||||
ESPEventLoop(std::shared_ptr<ESPEventAPI> api = std::make_shared<ESPEventAPIDefault>());
|
||||
|
||||
/**
|
||||
* Deletes the event loop implementation (depends on \c api).
|
||||
*/
|
||||
virtual ~ESPEventLoop();
|
||||
|
||||
/**
|
||||
* Registers a specific handler-event combination to the event loop.
|
||||
*
|
||||
* @return a reference to the combination of handler and event which can be used to unregister
|
||||
* this combination again later on.
|
||||
*
|
||||
* @note registering the same event twice will result in unregistering the earlier registered handler.
|
||||
* @note may throw EventException, ESPEventRegisterException
|
||||
*/
|
||||
std::unique_ptr<ESPEventReg> register_event(const ESPEvent &event,
|
||||
std::function<void(const ESPEvent &, void*)> cb);
|
||||
|
||||
/**
|
||||
* Sets a timeout for event. If the specified event isn't received within timeout,
|
||||
* timer_cb is called.
|
||||
*
|
||||
* @note this is independent from the normal event handling. Hence, registering an event for
|
||||
* timeout does not interfere with a different client that has registered normally for the
|
||||
* same event.
|
||||
*/
|
||||
std::unique_ptr<ESPEventRegTimed> register_event_timed(const ESPEvent &event,
|
||||
std::function<void(const ESPEvent &, void*)> cb,
|
||||
const std::chrono::microseconds &timeout,
|
||||
std::function<void(const ESPEvent &)> timer_cb);
|
||||
|
||||
/**
|
||||
* Posts an event and corresponding data.
|
||||
*
|
||||
* @param event the event to post
|
||||
* @param event_data The event data. A copy will be made internally and a pointer to the copy will be passed to the
|
||||
* event handler.
|
||||
* @param wait_time the maximum wait time the function tries to post the event
|
||||
*/
|
||||
template<typename T>
|
||||
void post_event_data(const ESPEvent &event,
|
||||
T &event_data,
|
||||
const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS);
|
||||
|
||||
/**
|
||||
* Posts an event.
|
||||
*
|
||||
* No event data will be send. The event handler will receive a nullptr.
|
||||
*
|
||||
* @param event the event to post
|
||||
* @param wait_time the maximum wait time the function tries to post the event
|
||||
*/
|
||||
void post_event_data(const ESPEvent &event,
|
||||
const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS);
|
||||
|
||||
private:
|
||||
/**
|
||||
* This API handle allows different sets of APIs to be applied, e.g. default event loop API and
|
||||
* custom event loop API.
|
||||
*/
|
||||
std::shared_ptr<ESPEventAPI> api;
|
||||
};
|
||||
|
||||
/**
|
||||
* ESPEventHandlerSync builds upon ESPEventLoop to create a class which allows synchronous event handling.
|
||||
*
|
||||
* It is built around a queue which buffers received events. This queue is also used to wait synchronously (blocking)
|
||||
* for an event. The consequence is that once an event is registered with this class, it is guaranteed to be received
|
||||
* as long as the queue can handle all incoming events (see \c get_send_queue_errors()).
|
||||
*/
|
||||
class ESPEventHandlerSync {
|
||||
public:
|
||||
/**
|
||||
* Result type for synchronous waiting.
|
||||
*/
|
||||
struct EventResult {
|
||||
EventResult() : event(), ev_data(nullptr) { }
|
||||
EventResult(ESPEvent ev, void *ev_data) : event(ev), ev_data(ev_data) { }
|
||||
ESPEvent event;
|
||||
void *ev_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Result type for synchronous waiting with timeout.
|
||||
*/
|
||||
struct EventResultTimed : public EventResult {
|
||||
EventResultTimed(EventResult event_result, bool timeout_arg)
|
||||
: EventResult(event_result), timeout(timeout_arg) { }
|
||||
bool timeout;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up synchronous event handling and registers event with it.
|
||||
*
|
||||
* @param event_loop ESPEventLoop implementation to manage esp events.
|
||||
* @param queue_max_size The queue size of the underlying FreeRTOS queue.
|
||||
* The memory to store queue_max_size number of events is allocated during construction
|
||||
* and held until destruction!
|
||||
* @param queue_send_timeout The timeout for posting events to the internal queue
|
||||
*/
|
||||
ESPEventHandlerSync(std::shared_ptr<ESPEventLoop> event_loop,
|
||||
size_t queue_max_size = 10,
|
||||
TickType_t queue_send_timeout = 0);
|
||||
|
||||
/**
|
||||
* Unregister all formerly registered events via automatic destruction in registry.
|
||||
*/
|
||||
virtual ~ESPEventHandlerSync();
|
||||
|
||||
/**
|
||||
* Waits for any of the events registered before with listen_to().
|
||||
*/
|
||||
EventResult wait_event();
|
||||
|
||||
/**
|
||||
* Waits for an event either PLATFORM_MAX_DELAY_MS ms or timeout ms.
|
||||
*
|
||||
* @param timeout the maximum waiting time for new events if no event is pending
|
||||
* The timeout is restricted by the TickType_t and configTICK_RATE_HZ.
|
||||
* TickType_t's width determines the maximum wait time. configTICK_RATE_HZ
|
||||
* determines the minimum wait time.
|
||||
*
|
||||
* Throws EventTimeout in case of a timeout.
|
||||
*/
|
||||
EventResultTimed wait_event_for(const std::chrono::milliseconds &timeout);
|
||||
|
||||
/**
|
||||
* Register additional event to listen for.
|
||||
*
|
||||
* @note this will unregister all earlier registered events of the same event type from the event loop.
|
||||
*/
|
||||
void listen_to(const ESPEvent &event);
|
||||
|
||||
/**
|
||||
* Indicates whether there were errors inserting an event into the queue.
|
||||
* This is the case e.g. if the queue with waiting events is full already.
|
||||
* Use this function to adjust the queue size (\c queue_send_timeout in constructor) in your application.
|
||||
*/
|
||||
size_t get_send_queue_errors() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Posts an event to the internal queue.
|
||||
*/
|
||||
void post_event(const EventResult &result);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Keeps track if there are any errors inserting an event into this class's event queue.
|
||||
*/
|
||||
std::atomic<size_t> send_queue_errors;
|
||||
|
||||
/**
|
||||
* The queue which saves events if they were received already or waits if no event was
|
||||
* received.
|
||||
*/
|
||||
QueueHandle_t event_queue;
|
||||
|
||||
/**
|
||||
* Timeout used to posting to the queue when using \c post_event(). Can be adjusted in constructor.
|
||||
*/
|
||||
TickType_t queue_send_timeout;
|
||||
|
||||
/**
|
||||
* The event loop used for this synchronous event handling class.
|
||||
*/
|
||||
std::shared_ptr<ESPEventLoop> event_loop;
|
||||
|
||||
/**
|
||||
* Keeps track of all events which are registered already for synchronous handling.
|
||||
*
|
||||
* This is necessary to keep the registration.
|
||||
*/
|
||||
std::vector<std::shared_ptr<ESPEventReg> > registry;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void ESPEventLoop::post_event_data(const ESPEvent &event,
|
||||
T &event_data,
|
||||
const std::chrono::milliseconds &wait_time)
|
||||
{
|
||||
esp_err_t result = api->post(event.base,
|
||||
event.id.get_id(),
|
||||
&event_data,
|
||||
sizeof(event_data),
|
||||
convert_ms_to_ticks(wait_time));
|
||||
|
||||
if (result != ESP_OK) {
|
||||
throw ESPException(result);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace event
|
||||
|
||||
} // namespace idf
|
||||
|
||||
#endif // __cpp_exceptions
|
||||
|
||||
#endif // ESP_EVENT_CXX_H_
|
@ -22,13 +22,33 @@
|
||||
namespace idf {
|
||||
|
||||
/**
|
||||
* General exception class for exceptions on the ESP chips.
|
||||
* @brief
|
||||
* General exception class for all C++ exceptions in IDF.
|
||||
*
|
||||
* All throwing code in IDF should use either this exception directly or a sub-classes.
|
||||
* An error from the underlying IDF function is mandatory. The idea is to wrap the orignal IDF error code to keep
|
||||
* the error scheme partially compatible. If an exception occurs in a higher level C++ code not directly wrapping
|
||||
* IDF functions, an appropriate error code reflecting the cause must be chosen or newly created.
|
||||
*/
|
||||
struct ESPException : public std::exception {
|
||||
class ESPException : public std::exception {
|
||||
public:
|
||||
/**
|
||||
* @param error Error from underlying IDF functions.
|
||||
*/
|
||||
ESPException(esp_err_t error);
|
||||
|
||||
esp_err_t error;
|
||||
virtual ~ESPException() { }
|
||||
|
||||
/**
|
||||
* @return A textual representation of the contained error. This method only wraps \c esp_err_to_name.
|
||||
*/
|
||||
virtual const char *what() const noexcept;
|
||||
|
||||
/**
|
||||
* Error from underlying IDF functions. If an exception occurs in a higher level C++ code not directly wrapping
|
||||
* IDF functions, an appropriate error code reflecting the cause must be chosen or newly created.
|
||||
*/
|
||||
const esp_err_t error;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <stdio.h>
|
||||
#include <cstring>
|
||||
#include "unity.h"
|
||||
|
||||
#include "unity_cxx.hpp"
|
||||
@ -48,5 +49,14 @@ TEST_CASE("CHECK_THROW throws", "[cxx exception][leaks=" LEAKS "]")
|
||||
TEST_THROW(CHECK_THROW(error), ESPException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPException has working what() method", "[cxx exception][leaks=" LEAKS "]")
|
||||
{
|
||||
try {
|
||||
throw ESPException(ESP_FAIL);
|
||||
} catch (ESPException &e) {
|
||||
TEST_ASSERT(strcmp(esp_err_to_name(ESP_FAIL), e.what()) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __cpp_exceptions
|
||||
|
||||
|
@ -0,0 +1,762 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/param.h>
|
||||
#include <memory>
|
||||
#include "unity.h"
|
||||
#include "unity_cxx.hpp"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_event_cxx.hpp"
|
||||
#include "esp_event_api.hpp"
|
||||
|
||||
#include "esp_exception.hpp"
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
|
||||
#ifdef CONFIG_ESP_TIMER_PROFILING
|
||||
#define WITH_PROFILING 1
|
||||
#endif
|
||||
|
||||
using namespace idf::event;
|
||||
using namespace std;
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(TEST_EVENT_BASE_0);
|
||||
ESP_EVENT_DEFINE_BASE(TEST_EVENT_BASE_1);
|
||||
static ESPEventID TEST_EVENT_ID_0(0);
|
||||
static ESPEventID TEST_EVENT_ID_1(1);
|
||||
|
||||
#define TAG "Event CXX Test"
|
||||
|
||||
ESPEvent TEMPLATE_EVENT_0(TEST_EVENT_BASE_0, TEST_EVENT_ID_0);
|
||||
ESPEvent TEMPLATE_EVENT_1(TEST_EVENT_BASE_0, TEST_EVENT_ID_1);
|
||||
|
||||
/**
|
||||
* Mock which only returns a certain error message.
|
||||
*/
|
||||
class ESPEventMock : public ESPEventAPIDefault {
|
||||
public:
|
||||
esp_err_t next_error;
|
||||
|
||||
esp_err_t handler_register(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_t event_handler,
|
||||
void* event_handler_arg,
|
||||
esp_event_handler_instance_t *instance) override {
|
||||
return next_error;
|
||||
}
|
||||
|
||||
esp_err_t handler_unregister(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
esp_event_handler_instance_t instance) override {
|
||||
return next_error;
|
||||
}
|
||||
|
||||
esp_err_t post(esp_event_base_t event_base,
|
||||
int32_t event_id,
|
||||
void* event_data,
|
||||
size_t event_data_size,
|
||||
TickType_t ticks_to_wait) override {
|
||||
return next_error;
|
||||
}
|
||||
};
|
||||
|
||||
/* The initial logging "initializing test" is to ensure mutex allocation is not counted against memory not being freed
|
||||
* during teardown.
|
||||
* esp_event_loop_delete_default() tries to mitigate side effects of failed tests where objects
|
||||
* with automatic storage duration weren't destructed.
|
||||
*
|
||||
* TODO: The final "testing mem..." is to prevent memory leaks which occur for yet unknown reasons
|
||||
*/
|
||||
struct EventFixture {
|
||||
EventFixture() : free_mem_before(0) {
|
||||
ESP_LOGI(TAG, "initializing test");
|
||||
|
||||
esp_event_loop_delete_default();
|
||||
|
||||
free_mem_before = heap_caps_get_free_size(MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
|
||||
virtual ~EventFixture()
|
||||
{
|
||||
ESP_LOGI(TAG, "de-initializing test...");
|
||||
}
|
||||
|
||||
size_t free_mem_before;
|
||||
};
|
||||
|
||||
struct EventLoopFix : public EventFixture {
|
||||
EventLoopFix()
|
||||
: EventFixture(),
|
||||
api(new ESPEventAPIDefault()),
|
||||
event_loop(api),
|
||||
ev0_called(false),
|
||||
ev1_called(false),
|
||||
timeout(false),
|
||||
ev0(),
|
||||
ev1()
|
||||
{
|
||||
handler0 = [this](const ESPEvent& ev, const void* data) {
|
||||
ev0 = ev;
|
||||
ev0_called = true;
|
||||
};
|
||||
|
||||
handler1 = [this](const ESPEvent& ev, const void* data) {
|
||||
ev1 = ev;
|
||||
ev1_called = true;
|
||||
};
|
||||
|
||||
timer_cb = [this](const ESPEvent& ev) {
|
||||
timeout_event = ev;
|
||||
timeout = true;
|
||||
};
|
||||
}
|
||||
|
||||
std::function<void(const ESPEvent &, const void* data)> handler0;
|
||||
|
||||
std::function<void(const ESPEvent &, const void* data)> handler1;
|
||||
|
||||
std::function<void(const ESPEvent &)> timer_cb;
|
||||
|
||||
std::shared_ptr<ESPEventAPI> api;
|
||||
ESPEventLoop event_loop;
|
||||
bool ev0_called;
|
||||
bool ev1_called;
|
||||
bool timeout;
|
||||
ESPEvent ev0;
|
||||
ESPEvent ev1;
|
||||
ESPEvent timeout_event;
|
||||
};
|
||||
|
||||
void send_default_event(ESPEventID event_id = TEST_EVENT_ID_0) {
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_event_post(TEST_EVENT_BASE_0,
|
||||
event_id.get_id(),
|
||||
nullptr,
|
||||
0,
|
||||
portMAX_DELAY));
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventAPIDefault deinitialization without failure", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
std::shared_ptr<ESPEventAPI> api(new ESPEventAPIDefault());
|
||||
esp_event_loop_delete_default();
|
||||
|
||||
// destructor of ESPEventAPI needs to run without failure
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventReg cb nullptr", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
std::shared_ptr<ESPEventAPI> api(new ESPEventAPIDefault());
|
||||
ESPEventLoop event_loop(api);
|
||||
TEST_THROW(ESPEventReg reg(nullptr, TEMPLATE_EVENT_0, api), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventReg api nullptr", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
function<void(const ESPEvent &, const void *)> cb = [](const ESPEvent &event, const void *data) {};
|
||||
shared_ptr<ESPEventAPI> api(new ESPEventAPIDefault());
|
||||
ESPEventLoop event_loop(api);
|
||||
TEST_THROW(ESPEventReg reg(cb, TEMPLATE_EVENT_0, nullptr), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventReg event api not initialized", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
std::shared_ptr<ESPEventMock> api(new ESPEventMock());
|
||||
api->next_error = ESP_ERR_INVALID_STATE;
|
||||
TEST_THROW(ESPEventReg cb([](const ESPEvent &, const void* data) { }, TEMPLATE_EVENT_0, api),
|
||||
ESPEventRegisterException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventReg event register failure no loop initialized", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
// registering will fail because default event loop isn't initialized
|
||||
std::shared_ptr<ESPEventAPI> api(new ESPEventAPIDefault());
|
||||
esp_event_loop_delete_default();
|
||||
TEST_THROW(ESPEventReg cb([](const ESPEvent &, const void* data) { }, TEMPLATE_EVENT_0, api),
|
||||
ESPEventRegisterException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventReg initialization failure", "[cxx event]")
|
||||
{
|
||||
ESPEvent event;
|
||||
EventFixture f;
|
||||
std::shared_ptr<ESPEventAPI> api = std::make_shared<ESPEventAPIDefault>();
|
||||
|
||||
TEST_THROW(ESPEventReg([&](const ESPEvent &ev, const void*) { event = ev; }, ESPEvent(), api),
|
||||
ESPEventRegisterException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventReg registration success", "[cxx event]")
|
||||
{
|
||||
ESPEvent event;
|
||||
EventFixture f;
|
||||
std::shared_ptr<ESPEventAPI> api = std::make_shared<ESPEventAPIDefault>();
|
||||
ESPEventLoop loop(api);
|
||||
|
||||
ESPEventReg registration([&event](const ESPEvent &ev, const void *) { event = ev; }, TEMPLATE_EVENT_0, api);
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(event == TEMPLATE_EVENT_0);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoopCB event passes data", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
int data_sent = 47;
|
||||
int data_received = 0;
|
||||
|
||||
ESPEvent event;
|
||||
|
||||
ESPEventReg cb([&event, &data_received](const ESPEvent & ev, const void* data) {
|
||||
event = ev;
|
||||
data_received = *((int*) data);
|
||||
}, TEMPLATE_EVENT_0, fix.api);
|
||||
|
||||
fix.event_loop.post_event_data(ESPEvent(TEST_EVENT_BASE_0, TEST_EVENT_ID_0), data_sent);
|
||||
|
||||
TEST_ASSERT(TEMPLATE_EVENT_0 == event);
|
||||
TEST_ASSERT(data_sent == data_received);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop Create event loop failure", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
esp_event_loop_create_default();
|
||||
|
||||
TEST_THROW(ESPEventLoop event_loop, EventException);
|
||||
|
||||
// just in case
|
||||
esp_event_loop_delete_default();
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop registration invalid event callback", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
std::function<void(const ESPEvent &, const void *)> event_cb;
|
||||
TEST_THROW(event_loop.register_event(TEMPLATE_EVENT_0, event_cb), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop timed registration invalid event callback", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
std::function<void(const ESPEvent &, const void *)> event_cb;
|
||||
std::function<void(const ESPEvent &)> timer_cb = [](const ESPEvent &ev) { };
|
||||
TEST_THROW(event_loop.register_event_timed(TEMPLATE_EVENT_0, event_cb, std::chrono::microseconds(10), timer_cb),
|
||||
EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop timed registration invalid timeout callback", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
std::function<void(const ESPEvent &, const void *)> event_cb = [](const ESPEvent &ev, const void *data) { };
|
||||
std::function<void(const ESPEvent &)> timer_cb;
|
||||
TEST_THROW(event_loop.register_event_timed(TEMPLATE_EVENT_0, event_cb, std::chrono::microseconds(10), timer_cb),
|
||||
EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop make sure timeout is off after register exception", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEvent timeout_event;
|
||||
bool timeout = false;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
std::function<void(const ESPEvent &, const void *)> event_cb = [&](const ESPEvent &ev, const void *data) {
|
||||
timeout_event = ev;
|
||||
};
|
||||
std::function<void(const ESPEvent &)> timer_cb = [&](const ESPEvent& ev) {
|
||||
timeout_event = ev;
|
||||
timeout = true;
|
||||
};
|
||||
esp_event_loop_delete_default();
|
||||
|
||||
// Below ~35 microseconds the timer expires too fast for esp_timer_stop() to prevent it from being called.
|
||||
TEST_THROW(event_loop.register_event_timed(TEMPLATE_EVENT_0, event_cb, std::chrono::microseconds(40), timer_cb),
|
||||
ESPEventRegisterException);
|
||||
|
||||
TEST_ASSERT_EQUAL(false, timeout);
|
||||
TEST_ASSERT(timeout_event == ESPEvent());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop Delete event loop failure - no error", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
|
||||
esp_event_loop_delete_default();
|
||||
|
||||
// destructor of ESPEventLoop needs to run without failure
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop post nullptr event without registrations", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
ESPEvent event(TEST_EVENT_BASE_0, TEST_EVENT_ID_0);
|
||||
void *ptr = nullptr;
|
||||
event_loop.post_event_data(event, ptr);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop post int event without registrations", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
ESPEventLoop event_loop;
|
||||
ESPEvent event(TEST_EVENT_BASE_0, TEST_EVENT_ID_0);
|
||||
int fourtyseven = 47;
|
||||
event_loop.post_event_data(event, fourtyseven);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop can create, use and delete ESPEventLoop", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
bool tested = false;
|
||||
|
||||
std::function<void(const ESPEvent &, const void* data)> cb = [&tested](const ESPEvent& event, const void* data) {
|
||||
tested = true;
|
||||
};
|
||||
|
||||
ESPEventReg registration(fix.handler0, TEMPLATE_EVENT_0, fix.api);
|
||||
|
||||
void *ptr = nullptr;
|
||||
fix.event_loop.post_event_data(ESPEvent(TEST_EVENT_BASE_0, TEST_EVENT_ID_0), ptr);
|
||||
|
||||
TEST_ASSERT_EQUAL(true, fix.ev0_called);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop Register, receive, unregister ESPEvent", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
std::unique_ptr<ESPEventReg> registration(new ESPEventReg(fix.handler0, TEMPLATE_EVENT_0, fix.api));
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
|
||||
registration.reset();
|
||||
fix.ev0 = ESPEvent();
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == ESPEvent());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop register multiple ESPEvents, same cb", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventReg registration0(fix.handler0, TEMPLATE_EVENT_0, fix.api);
|
||||
ESPEventReg registration1(fix.handler1, TEMPLATE_EVENT_1, fix.api);
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
fix.ev0 = ESPEvent();
|
||||
|
||||
send_default_event(TEST_EVENT_ID_1);
|
||||
|
||||
TEST_ASSERT(fix.ev1 == TEMPLATE_EVENT_1);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop register multiple ESPEvents, multiple cbs", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventReg registration0(fix.handler0, TEMPLATE_EVENT_0, fix.api);
|
||||
ESPEventReg registration1(fix.handler1, TEMPLATE_EVENT_1, fix.api);
|
||||
|
||||
send_default_event();
|
||||
send_default_event(TEST_EVENT_ID_1);
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT(fix.ev1 == TEMPLATE_EVENT_1);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop register to all events of one event base", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEvent any_id_event(ESP_EVENT_ANY_BASE, ESPEventID(ESP_EVENT_ANY_ID));
|
||||
ESPEventReg registration(fix.handler0, any_id_event, fix.api);
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
|
||||
send_default_event(TEST_EVENT_ID_1);
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_1);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop register to all ESP events", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEvent any_event(ESP_EVENT_ANY_BASE, ESPEventID(ESP_EVENT_ANY_ID));
|
||||
ESPEventReg registration(fix.handler0, any_event, fix.api);
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
|
||||
send_default_event(TEST_EVENT_ID_1);
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_1);
|
||||
|
||||
void *ptr = nullptr;
|
||||
fix.event_loop.post_event_data(ESPEvent(TEST_EVENT_BASE_1, TEST_EVENT_ID_0), ptr);
|
||||
|
||||
// check reception of event with different base
|
||||
TEST_ASSERT_EQUAL(TEST_EVENT_BASE_1, fix.ev0.base);
|
||||
TEST_ASSERT_EQUAL(TEST_EVENT_ID_0.get_id(), fix.ev0.id.get_id());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop direct register, receive, unregister ESPEvent", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
std::unique_ptr<ESPEventReg> registration = fix.event_loop.register_event(TEMPLATE_EVENT_0, fix.handler0);
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
|
||||
registration.reset();
|
||||
fix.ev0 = ESPEvent();
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == ESPEvent());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop set timeout invalid timeout", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
const std::chrono::microseconds INVALID_US(MIN_TIMEOUT - chrono::microseconds(1));
|
||||
|
||||
TEST_THROW(ESPEventRegTimed(fix.handler0, TEMPLATE_EVENT_0, fix.timer_cb, INVALID_US, fix.api),
|
||||
EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop lonely timeout", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventRegTimed timed_reg(fix.handler0, TEMPLATE_EVENT_0, fix.timer_cb, MIN_TIMEOUT, fix.api);
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
TEST_ASSERT_EQUAL(true, fix.timeout);
|
||||
TEST_ASSERT_EQUAL(false, fix.ev0_called);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop timeout unregisters from loop", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventRegTimed timed_reg(fix.handler0, TEMPLATE_EVENT_0, fix.timer_cb, MIN_TIMEOUT, fix.api);
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
send_default_event(TEST_EVENT_ID_0);
|
||||
|
||||
TEST_ASSERT_EQUAL(true, fix.timeout);
|
||||
TEST_ASSERT_EQUAL(false, fix.ev0_called);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop no timeout", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventRegTimed timed_reg(fix.handler0, TEMPLATE_EVENT_0, fix.timer_cb, std::chrono::microseconds(500000), fix.api);
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT_EQUAL(false, fix.timeout);
|
||||
TEST_ASSERT_EQUAL(true, fix.ev0_called);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers an event via both set_timeout() and register_event().
|
||||
* Result: both handlers will be invoked, the timeout callback won't be called.
|
||||
*/
|
||||
TEST_CASE("ESPEventLoop register timeout and event - no timeout", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventReg reg(fix.handler0, TEMPLATE_EVENT_0, fix.api);
|
||||
ESPEventRegTimed timed_reg(fix.handler1, TEMPLATE_EVENT_0, fix.timer_cb, std::chrono::microseconds(500000), fix.api);
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT(fix.ev1 == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT_EQUAL(false, fix.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an event via both set_timeout() and register_event().
|
||||
* Result: both handlers will be invoked, the timeout callback won't be called.
|
||||
*/
|
||||
TEST_CASE("ESPEventLoop direct register timeout and event - no timeout", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
unique_ptr<ESPEventReg> reg = fix.event_loop.register_event(TEMPLATE_EVENT_0, fix.handler0);
|
||||
unique_ptr<ESPEventRegTimed> timed_reg = fix.event_loop.register_event_timed(TEMPLATE_EVENT_0,
|
||||
fix.handler1,
|
||||
std::chrono::microseconds(500000),
|
||||
fix.timer_cb);
|
||||
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT(fix.ev1 == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT_EQUAL(false, fix.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an event via both set_timeout() and register_event().
|
||||
* Result: both handlers will be invoked, the timeout callback won't be called.
|
||||
*/
|
||||
TEST_CASE("ESPEventLoop register timeout and event - timeout", "[cxx event]")
|
||||
{
|
||||
EventLoopFix fix;
|
||||
|
||||
ESPEventReg reg(fix.handler0, TEMPLATE_EVENT_0, fix.api);
|
||||
ESPEventRegTimed timed_reg(fix.handler1, TEMPLATE_EVENT_0, fix.timer_cb, MIN_TIMEOUT, fix.api);
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(fix.ev0 == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT_EQUAL(false, fix.ev1_called);
|
||||
TEST_ASSERT(fix.timeout_event == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT_EQUAL(true, fix.timeout);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventLoop custom loop register, receive, unregister ESPEvent", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEvent event;
|
||||
esp_event_loop_args_t loop_args;
|
||||
loop_args.queue_size = 32;
|
||||
loop_args.task_name = "sys_evt";
|
||||
loop_args.task_stack_size = 2304;
|
||||
loop_args.task_priority = 20;
|
||||
loop_args.task_core_id = 0;
|
||||
|
||||
std::shared_ptr<ESPEventAPICustom> api(new ESPEventAPICustom(loop_args));
|
||||
|
||||
ESPEventLoop event_loop(api);
|
||||
std::function<void(const ESPEvent &, const void* data)> cb = [&event](const ESPEvent& ev, const void* data) {
|
||||
event = ev;
|
||||
};
|
||||
shared_ptr<ESPEventReg> registration = event_loop.register_event(TEMPLATE_EVENT_0, cb);
|
||||
|
||||
void *ptr = nullptr;
|
||||
event_loop.post_event_data(ESPEvent(TEST_EVENT_BASE_0, TEST_EVENT_ID_0), ptr);
|
||||
|
||||
ESP_ERROR_CHECK(api->run(1));
|
||||
|
||||
TEST_ASSERT(event == TEMPLATE_EVENT_0);
|
||||
|
||||
registration.reset();
|
||||
event = ESPEvent();
|
||||
|
||||
event_loop.post_event_data(ESPEvent(TEST_EVENT_BASE_0, TEST_EVENT_ID_0), ptr);
|
||||
|
||||
ESP_ERROR_CHECK(api->run(1));
|
||||
|
||||
TEST_ASSERT(event == ESPEvent());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync simple construction and destruction", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync simple event wait", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
|
||||
send_default_event();
|
||||
|
||||
ESPEventHandlerSync::EventResult result = handler.wait_event();
|
||||
|
||||
TEST_ASSERT_EQUAL(TEMPLATE_EVENT_0.base, result.event.base);
|
||||
TEST_ASSERT_EQUAL(TEMPLATE_EVENT_0.id.get_id(), result.event.id.get_id());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync wait_for(0) succeed", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
|
||||
send_default_event();
|
||||
|
||||
ESPEventHandlerSync::EventResult result = handler.wait_event_for(chrono::milliseconds(0));
|
||||
TEST_ASSERT(TEMPLATE_EVENT_0 == result.event);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync start waiting after events arrived", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
|
||||
send_default_event();
|
||||
send_default_event();
|
||||
|
||||
TEST_ASSERT(handler.wait_event().event == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT(handler.wait_event().event == TEMPLATE_EVENT_0);
|
||||
}
|
||||
|
||||
// helper function to post events, simulating an event source
|
||||
void post_events(int event_num) {
|
||||
for (int i = 0; i < event_num; i++) {
|
||||
ESP_ERROR_CHECK(esp_event_post(TEST_EVENT_BASE_0,
|
||||
TEST_EVENT_ID_0.get_id(),
|
||||
nullptr,
|
||||
0,
|
||||
portMAX_DELAY));
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync simultaneous event handling", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
// Create handler with queue size 1
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
thread th(post_events, 3);
|
||||
|
||||
// no for-loop for better feedback (line numbers)
|
||||
TEST_ASSERT(handler.wait_event().event == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT(handler.wait_event().event == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT(handler.wait_event().event == TEMPLATE_EVENT_0);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, handler.get_send_queue_errors());
|
||||
th.join();
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync wait_for(0) timeout", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
|
||||
ESPEventHandlerSync::EventResultTimed result = handler.wait_event_for(chrono::milliseconds(0));
|
||||
TEST_ASSERT_EQUAL(true, result.timeout);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync register default event fails", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
TEST_THROW(handler.listen_to(ESPEvent()), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync null pointer", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
TEST_THROW(ESPEventHandlerSync handler(nullptr), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync empty shared_ptr", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
shared_ptr<ESPEventLoop> event_loop;
|
||||
TEST_THROW(ESPEventHandlerSync handler(event_loop), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync queue size 0", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
|
||||
TEST_THROW(ESPEventHandlerSync handler(make_shared<ESPEventLoop>(), 0), EventException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync receive after timeout", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>());
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
|
||||
TEST_ASSERT_EQUAL(true, handler.wait_event_for(chrono::milliseconds(0)).timeout);
|
||||
|
||||
send_default_event();
|
||||
|
||||
ESPEvent event = handler.wait_event().event;
|
||||
TEST_ASSERT(TEMPLATE_EVENT_0 == event);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventHandlerSync send too many events", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
// Create handler with queue size 1
|
||||
ESPEventHandlerSync handler(make_shared<ESPEventLoop>(), 1);
|
||||
handler.listen_to(TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT_EQUAL(0, handler.get_send_queue_errors());
|
||||
|
||||
send_default_event();
|
||||
send_default_event();
|
||||
TEST_ASSERT(handler.wait_event().event == TEMPLATE_EVENT_0);
|
||||
TEST_ASSERT_EQUAL(true, handler.wait_event_for(chrono::milliseconds(10)).timeout);
|
||||
TEST_ASSERT_EQUAL(1, handler.get_send_queue_errors());
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventAPIDefault initialization failure", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
esp_event_loop_create_default();
|
||||
TEST_THROW(std::shared_ptr<ESPEventAPI> api(new ESPEventAPIDefault()), EventException);
|
||||
esp_event_loop_delete_default();
|
||||
}
|
||||
|
||||
TEST_CASE("ESPEventAPICustom no mem", "[cxx event]")
|
||||
{
|
||||
EventFixture f;
|
||||
esp_event_loop_args_t loop_args;
|
||||
loop_args.queue_size = 1000000;
|
||||
loop_args.task_name = "custom_evt";
|
||||
loop_args.task_stack_size = 2304;
|
||||
loop_args.task_priority = 20;
|
||||
loop_args.task_core_id = 0;
|
||||
|
||||
esp_event_loop_create_default();
|
||||
|
||||
TEST_THROW(std::shared_ptr<ESPEventAPI> api(new ESPEventAPICustom(loop_args)), EventException);
|
||||
|
||||
esp_event_loop_delete_default();
|
||||
}
|
||||
|
||||
#endif // __cpp_exceptions
|
||||
|
@ -1,9 +1,8 @@
|
||||
#ifndef UNITY_CXX_H_
|
||||
#define UNITY_CXX_H_
|
||||
#pragma once
|
||||
|
||||
#include "unity.h"
|
||||
|
||||
#define STR(x) #x
|
||||
#define CXX_UNITY_TYPE_TO_STR(x) #x
|
||||
|
||||
/**
|
||||
* Very simple helper macro to catch exceptions.
|
||||
@ -24,12 +23,10 @@
|
||||
} catch ( std::exception &e) { \
|
||||
caught_different = true; \
|
||||
} \
|
||||
TEST_ASSERT_FALSE_MESSAGE(caught_different, "ERROR: Expected " STR(exception_) \
|
||||
TEST_ASSERT_FALSE_MESSAGE(caught_different, "ERROR: Expected " CXX_UNITY_TYPE_TO_STR(exception_) \
|
||||
", but caught different exception."); \
|
||||
TEST_ASSERT_TRUE_MESSAGE(caught, "ERROR: Expected " STR(exception_) \
|
||||
TEST_ASSERT_TRUE_MESSAGE(caught, "ERROR: Expected " CXX_UNITY_TYPE_TO_STR(exception_) \
|
||||
", but no exception thrown."); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
|
||||
#endif // UNITY_CXX_H_
|
||||
|
@ -43,7 +43,7 @@ Current temperature: 24.875
|
||||
|
||||
If something went wrong:
|
||||
```
|
||||
I2C Exception with error: -1
|
||||
I2C Exception with error: ESP_FAIL (-1)
|
||||
Coulnd't read sensor!
|
||||
```
|
||||
|
||||
|
@ -38,7 +38,8 @@ extern "C" void app_main(void)
|
||||
|
||||
cout << "Current temperature: " << calc_temp(data[0], data[1]) << endl;
|
||||
} catch (const I2CException &e) {
|
||||
cout << "I2C Exception with error: " << e.error << endl;
|
||||
cout << "I2C Exception with error: " << e.what();
|
||||
cout << " (" << e.error<< ")" << endl;
|
||||
cout << "Coulnd't read sensor!" << endl;
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +429,7 @@ component_ut_test_001:
|
||||
|
||||
UT_001:
|
||||
extends: .unit_test_template
|
||||
parallel: 43
|
||||
parallel: 44
|
||||
tags:
|
||||
- ESP32_IDF
|
||||
- UT_T1_1
|
||||
@ -644,7 +644,7 @@ UT_045:
|
||||
- ESP32_IDF
|
||||
- UT_SDIO
|
||||
- psram
|
||||
|
||||
|
||||
UT_046:
|
||||
extends: .unit_test_template
|
||||
tags:
|
||||
|
@ -2,7 +2,7 @@
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/"
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component"
|
||||
"$ENV{IDF_PATH}/examples/peripherals/rmt/ir_protocols/components")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
Loading…
x
Reference in New Issue
Block a user