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:
Jakob Hasse 2020-03-31 18:52:37 +08:00
parent 303587103a
commit f835bead45
28 changed files with 2088 additions and 17 deletions

View 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)

View 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

View 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
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "esp_event_async_cxx_example.cpp"
INCLUDE_DIRS ".")

View File

@ -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.
#

View File

@ -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;
}

View File

@ -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

View 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)

View 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

View 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
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "esp_event_cxx_example.cpp"
INCLUDE_DIRS ".")

View File

@ -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.
#

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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_

View File

@ -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;
};
/**

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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!
```

View File

@ -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;
}
}

View File

@ -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:

View File

@ -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)