Merge branch 'feature/remove-cxx-experimental' into 'master'

cxx: removed cxx experimental components

See merge request espressif/esp-idf!20411
This commit is contained in:
Zim Kalinowski 2023-01-25 21:29:32 +08:00
commit 50ad61cbee
121 changed files with 9 additions and 9367 deletions

View File

@ -40,7 +40,6 @@
.patterns-build_components: &patterns-build_components
- "components/**/*"
- "examples/cxx/experimental/experimental_cpp_component/*"
.patterns-downloadable-tools: &patterns-downloadable-tools
- "tools/idf_tools.py"

View File

@ -6,41 +6,6 @@ examples/cxx/exceptions:
temporary: true
reason: lack of runners
examples/cxx/experimental/esp_modem_cxx:
enable:
- if: IDF_TARGET in ["esp32", "esp32s2"]
temporary: true
reason: the other targets are not tested yet
examples/cxx/experimental/esp_mqtt_cxx/ssl:
disable:
- if: IDF_TARGET == "esp32c6" or IDF_TARGET == "esp32h2"
temporary: true
reason: target esp32c6 is not supported yet
examples/cxx/experimental/esp_mqtt_cxx/tcp:
disable:
- if: IDF_TARGET == "esp32c6" or IDF_TARGET == "esp32h2"
temporary: true
reason: target esp32c6 is not supported yet
examples/cxx/experimental/experimental_cpp_component/host_test:
enable:
- if: IDF_TARGET == "linux"
reason: only test on linux
examples/cxx/experimental/simple_i2c_rw_example:
disable:
- if: IDF_TARGET in ["esp32c2", "esp32c6", "esp32h2"]
temporary: true
reason: target(s) not supported yet
examples/cxx/experimental/simple_spi_rw_example:
disable:
- if: IDF_TARGET in ["esp32c2", "esp32c6", "esp32h2"]
temporary: true
reason: target(s) not supported yet
examples/cxx/pthread:
disable_test:
- if: IDF_TARGET not in ["esp32", "esp32c3"]

View File

@ -1,10 +0,0 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# 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.16)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(blink_cxx)

View File

@ -1,60 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# Example: Blink C++ example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of the `GPIO_Output` C++ class in ESP-IDF.
In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option.
This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling.
This is necessary for the C++ APIs.
## How to use example
### Hardware Required
Any ESP32 family development board.
Connect an LED to the corresponding pin (default is pin 4). If the board has a normal LED already, you can use the pin number to which that one is connected.
Development boards with an RGB LED that only has one data line like the ESP32-C3-DevKitC-02 and ESP32-C3-DevKitM-1 will not work. In this case, please connect an external normal LED to the chosen pin.
### Configure the project
```
idf.py menuconfig
```
### Build and Flash
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port.)
(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 (339) cpu_start: Starting scheduler.
I (343) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
```

View File

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

View File

@ -1,39 +0,0 @@
/* Blink 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 <cstdlib>
#include <thread>
#include "esp_log.h"
#include "gpio_cxx.hpp"
using namespace idf;
using namespace std;
extern "C" void app_main(void)
{
/* The functions of GPIO_Output throws exceptions in case of parameter errors or if there are underlying driver
errors. */
try {
/* This line may throw an exception if the pin number is invalid.
* Alternatively to 4, choose another output-capable pin. */
GPIO_Output gpio(GPIONum(4));
while (true) {
printf("LED ON\n");
gpio.set_high();
this_thread::sleep_for(std::chrono::seconds(1));
printf("LED OFF\n");
gpio.set_low();
this_thread::sleep_for(std::chrono::seconds(1));
}
} catch (GPIOException &e) {
printf("GPIO exception occurred: %s\n", esp_err_to_name(e.error));
printf("stopping.\n");
}
}

View File

@ -1,3 +0,0 @@
# 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,10 +0,0 @@
# 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.16)
# (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

@ -1,44 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# 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

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

View File

@ -1,111 +0,0 @@
/* 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

@ -1,3 +0,0 @@
# 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,10 +0,0 @@
# 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.16)
# (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

@ -1,47 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# 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

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

View File

@ -1,80 +0,0 @@
/* 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

@ -1,3 +0,0 @@
# 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,23 +0,0 @@
idf_build_get_property(target IDF_TARGET)
idf_component_register(SRCS "esp_mqtt_cxx.cpp"
INCLUDE_DIRS "include"
)
if(TEST_BUILD)
message(STATUS "Test build")
idf_component_get_property(mqtt_dir mqtt COMPONENT_DIR)
idf_component_get_property(experimental_cpp_component_dir experimental_cpp_component COMPONENT_DIR)
idf_component_get_property(esp_common_dir esp_common COMPONENT_DIR)
idf_component_get_property(esp_event_dir esp_event COMPONENT_DIR)
target_include_directories(${COMPONENT_LIB} PUBLIC ${mqtt_dir}/esp-mqtt/include
${esp_event_dir}/include
${experimental_cpp_component_dir}/include
${esp_common_dir}/include)
else()
idf_component_get_property(mqtt_lib mqtt COMPONENT_LIB)
idf_component_get_property(log_lib log COMPONENT_LIB)
idf_component_get_property(experimental_cpp_component_lib experimental_cpp_component COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} PUBLIC ${log_lib} ${mqtt_lib} ${experimental_cpp_component_lib})
endif()

View File

@ -1,293 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string>
#include <algorithm>
#include <stdexcept>
#include <inttypes.h>
#include "mqtt_client.h"
#include "esp_log.h"
#include "esp_mqtt.hpp"
#include "esp_mqtt_client_config.hpp"
namespace {
// Helper for static assert.
template<class T>
constexpr bool always_false = false;
template<class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
};
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
using namespace idf::mqtt;
/*
* This function is responsible for fill in the configurations for the broker related data
* of mqtt_client_config_t
*/
void config_broker(esp_mqtt_client_config_t &mqtt_client_cfg, BrokerConfiguration const &broker)
{
std::visit(overloaded{
[&mqtt_client_cfg](Host const & host)
{
mqtt_client_cfg.broker.address.hostname = host.address.c_str();
mqtt_client_cfg.broker.address.path = host.path.c_str();
mqtt_client_cfg.broker.address.transport = host.transport;
},
[&mqtt_client_cfg](URI const & uri)
{
mqtt_client_cfg.broker.address.uri = uri.address.c_str();
},
[]([[maybe_unused ]]auto & unknown)
{
static_assert(always_false<decltype(unknown)>, "Missing type handler for variant handler");
}
},
broker.address.address);
std::visit(overloaded{
[]([[maybe_unused]]Insecure const & insecure) {},
[&mqtt_client_cfg](GlobalCAStore const & use_global_store)
{
mqtt_client_cfg.broker.verification.use_global_ca_store = true;
},
[&mqtt_client_cfg](CryptographicInformation const & certificates)
{
std::visit(overloaded{
[&mqtt_client_cfg](PEM const & pem)
{
mqtt_client_cfg.broker.verification.certificate= pem.data;
}, [&mqtt_client_cfg](DER const & der)
{
mqtt_client_cfg.broker.verification.certificate = der.data;
mqtt_client_cfg.broker.verification.certificate_len = der.len;
}}, certificates);
},
[]([[maybe_unused]] PSK const & psk) {},
[]([[maybe_unused]] auto & unknown)
{
static_assert(always_false<decltype(unknown)>, "Missing type handler for variant handler");
}
},
broker.security);
mqtt_client_cfg.broker.address.port = broker.address.port;
}
/*
* This function is responsible for fill in the configurations for the client credentials related data
* of mqtt_client_config_t
*/
void config_client_credentials(esp_mqtt_client_config_t &mqtt_client_cfg, ClientCredentials const &credentials)
{
mqtt_client_cfg.credentials.client_id = credentials.client_id.has_value() ? credentials.client_id.value().c_str() : nullptr ;
mqtt_client_cfg.credentials.username = credentials.username.has_value() ? credentials.username.value().c_str() : nullptr ;
std::visit(overloaded{
[&mqtt_client_cfg](Password const & password)
{
mqtt_client_cfg.credentials.authentication.password = password.data.c_str();
},
[](ClientCertificate const & certificate) {},
[](SecureElement const & enable_secure_element) {},
[]([[maybe_unused ]]auto & unknown)
{
static_assert(always_false<decltype(unknown)>, "Missing type handler for variant handler");
}
}, credentials.authentication);
}
esp_mqtt_client_config_t make_config(BrokerConfiguration const &broker, ClientCredentials const &credentials, Configuration const &config)
{
esp_mqtt_client_config_t mqtt_client_cfg{};
config_broker(mqtt_client_cfg, broker);
config_client_credentials(mqtt_client_cfg, credentials);
return mqtt_client_cfg;
}
}
namespace idf::mqtt {
Client::Client(BrokerConfiguration const &broker, ClientCredentials const &credentials, Configuration const &config): Client(make_config(broker, credentials, config)) {}
Client::Client(esp_mqtt_client_config_t const &config) : handler(esp_mqtt_client_init(&config))
{
if (handler == nullptr) {
throw MQTTException(ESP_FAIL);
};
CHECK_THROW_SPECIFIC(esp_mqtt_client_register_event(handler.get(), MQTT_EVENT_ANY, mqtt_event_handler, this), mqtt::MQTTException);
CHECK_THROW_SPECIFIC(esp_mqtt_client_start(handler.get()), mqtt::MQTTException);
}
void Client::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) noexcept
{
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32, base, event_id);
auto *event = static_cast<esp_mqtt_event_t *>(event_data);
auto &client = *static_cast<Client *>(handler_args);
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
client.on_connected(event);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
client.on_disconnected(event);
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
client.on_subscribed(event);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
client.on_unsubscribed(event);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
client.on_published(event);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
client.on_data(event);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
client.on_error(event);
break;
case MQTT_EVENT_BEFORE_CONNECT:
ESP_LOGI(TAG, "MQTT_EVENT_BEFORE_CONNECT");
client.on_before_connect(event);
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}
void Client::on_error(esp_mqtt_event_handle_t const event)
{
auto log_error_if_nonzero = [](const char *message, int error_code) {
if (error_code != 0) {
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
}
};
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
}
}
void Client::on_disconnected(esp_mqtt_event_handle_t const event)
{
}
void Client::on_subscribed(esp_mqtt_event_handle_t const event)
{
printf("Subscribed to %.*s\r\n", event->topic_len, event->topic);
}
void Client::on_unsubscribed(esp_mqtt_event_handle_t const event)
{
}
void Client::on_published(esp_mqtt_event_handle_t const event)
{
}
void Client::on_before_connect(esp_mqtt_event_handle_t const event)
{
}
void Client::on_connected(esp_mqtt_event_handle_t const event)
{
}
void Client::on_data(esp_mqtt_event_handle_t const event)
{
}
std::optional<MessageID> Client::subscribe(std::string const &topic, QoS qos)
{
auto res = esp_mqtt_client_subscribe(handler.get(), topic.c_str(),
static_cast<int>(qos));
if (res < 0) {
return std::nullopt;
}
return MessageID{res};
}
bool is_valid(std::string::const_iterator first, std::string::const_iterator last)
{
if (first == last) {
return false;
}
auto number = std::find(first, last, '#');
if (number != last) {
if (std::next(number) != last) {
return false;
}
if (*std::prev(number) != '/' && number != first) {
return false;
}
}
auto plus = std::find(first, last, '+');
if (plus != last) {
if (*(std::prev(plus)) != '/' && plus != first) {
return false;
}
if (std::next(plus) != last && *(std::next(plus)) != '/') {
return false;
}
}
return true;
}
Filter::Filter(std::string user_filter) : filter(std::move(user_filter))
{
if (!is_valid(filter.begin(), filter.end())) {
throw std::domain_error("Forbidden Filter string");
}
}
[[nodiscard]] bool Filter::match(std::string::const_iterator topic_begin, std::string::const_iterator topic_end) const noexcept
{
auto filter_begin = filter.begin();
auto filter_end = filter.end();
for (auto mismatch = std::mismatch(filter_begin, filter_end, topic_begin);
mismatch.first != filter.end() and mismatch.second != topic_end;
mismatch = std::mismatch(filter_begin, filter_end, topic_begin)) {
if (*mismatch.first != '#' and * mismatch.first != '+') {
return false;
}
if (*mismatch.first == '#') {
return true;
}
if (*mismatch.first == '+') {
filter_begin = advance(mismatch.first, filter_end);
topic_begin = advance(mismatch.second, topic_end);
if (filter_begin == filter_end and topic_begin != topic_end) {
return false;
}
}
}
return true;
}
const std::string &Filter::get()
{
return filter;
}
[[nodiscard]] bool Filter::match(char const *const first, int size) const noexcept
{
auto it = static_cast<std::string::const_iterator>(first);
return match(it, it + size);
}
std::string::const_iterator Filter::advance(std::string::const_iterator first, std::string::const_iterator last) const
{
constexpr auto separator = '/';
return std::find(first, last, separator);
}
}

View File

@ -1,229 +0,0 @@
// Copyright 2021 Espressif Systems (Shanghai) CO 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.
#pragma once
#include <string_view>
#ifndef __cpp_exceptions
#error MQTT class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
#endif
#include <optional>
#include <variant>
#include <utility>
#include <memory>
#include <string>
#include "esp_exception.hpp"
#include "esp_mqtt_client_config.hpp"
#include "mqtt_client.h"
namespace idf::mqtt {
constexpr auto *TAG = "mqtt_client_cpp";
struct MQTTException : ESPException {
using ESPException::ESPException;
};
/**
* @brief QoS for publish and subscribe
*
* Sets the QoS as:
* AtMostOnce : Best effort delivery of messages. Message loss can occur.
* AtLeastOnce : Guaranteed delivery of messages. Duplicates can occur.
* ExactlyOnce : Guaranteed delivery of messages exactly once.
*
*/
enum class QoS { AtMostOnce = 0, AtLeastOnce = 1, ExactlyOnce = 2 };
/**
* @brief Sets if a message must be retained.
*
* Retained messages are delivered to future subscribers that match the topic name.
*
*/
enum class Retain : bool { NotRetained = false, Retained = true };
/**
* @brief Message class template to publish.
*
*/
template <typename T> struct Message {
T data; /*!< Data for publish. Should be a contiguous type*/
QoS qos = QoS::AtLeastOnce; /*!< QoS for the message*/
Retain retain = Retain::NotRetained; /*!< Retention mark for the message.*/
};
/**
* @brief Message type that holds std::string for data
*
*/
using StringMessage = Message<std::string>;
[[nodiscard]] bool filter_is_valid(std::string::const_iterator first, std::string::const_iterator last);
/**
* @brief Filter for mqtt topic subscription.
* @throws std::domain_error if the filter is invalid.
*
* Topic filter.
*
*/
class Filter {
public:
explicit Filter(std::string user_filter);
/**
* @brief Get the filter string used.
*
*/
const std::string &get();
/**
* @brief Checks the filter string against a topic name.
*
* @param first Iterator to the beginning of the sequence.
* @param last Iterator to the end of the sequence.
*
* @return true if the topic name match the filter
*/
[[nodiscard]] bool match(std::string::const_iterator first,
std::string::const_iterator last) const noexcept;
/**
* @brief Checks the filter string against a topic name.
*
* @param topic topic name
*
* @return true if the topic name match the filter
*/
[[nodiscard]] bool match(const std::string &topic) const noexcept;
/**
* @brief Checks the filter string against a topic name.
*
* @param first Char array with topic name.
* @param last Size of given topic name.
*
* @return true if the topic name match the filter
*/
[[nodiscard]] bool match(const char *const begin, int size) const noexcept;
private:
/**
* @brief Advance the topic to the next level.
*
* An mqtt topic ends with a /. This function is used to iterate in topic levels.
*
* @return Iterator to the start of the topic.
*/
[[nodiscard]] std::string::const_iterator advance(std::string::const_iterator begin, std::string::const_iterator end) const;
std::string filter;
};
/**
* @brief Message identifier to track delivery.
*
*/
enum class MessageID : int {};
/**
* @brief Base class for MQTT client
*
* Should be inherited to provide event handlers.
*/
class Client {
public:
Client(const BrokerConfiguration &broker,const ClientCredentials &credentials,const Configuration &config);
Client(const esp_mqtt_client_config_t &config);
/**
* @brief Subscribe to topic
*
* @param filter
* @param qos QoS subscription, defaulted as QoS::AtLeastOnce
*
* @return Optional MessageID. In case of failure std::nullopt is returned.
*/
std::optional<MessageID> subscribe(const std::string &filter, QoS qos = QoS::AtLeastOnce);
/**
* @brief publish message to topic
*
* @tparam Container Type for data container. Must be a contiguous memory.
* @param topic Topic name
* @param message Message struct containing data, qos and retain configuration.
*
* @return Optional MessageID. In case of failure std::nullopt is returned.
*/
template <class Container> std::optional<MessageID> publish(const std::string &topic, const Message<Container>& message)
{
return publish(topic, std::begin(message.data), std::end(message.data), message.qos, message.retain);
}
/**
* @brief publish message to topic
*
* @tparam InputIt Input data iterator type.
* @param topic Topic name
* @param first, last Iterator pair of data to publish
* @param message Message struct containing data, qos and retain configuration.
*
* @return Optional MessageID. In case of failure std::nullopt is returned.
*/
template <class InputIt>
std::optional<MessageID> publish(const std::string &topic, InputIt first, InputIt last, QoS qos = QoS::AtLeastOnce, Retain retain = Retain::NotRetained)
{
auto size = std::distance(first, last);
auto res = esp_mqtt_client_publish(handler.get(), topic.c_str(), &(*first), size, static_cast<int>(qos),
static_cast<int>(retain));
if (res < 0) {
return std::nullopt;
}
return MessageID{res};
}
virtual ~Client() = default;
protected:
struct MqttClientDeleter {
void operator()(esp_mqtt_client *client_handler)
{
esp_mqtt_client_destroy(client_handler);
}
};
using ClientHandler = std::unique_ptr<esp_mqtt_client, MqttClientDeleter>;
ClientHandler handler;
private:
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id,
void *event_data) noexcept;
void init(const esp_mqtt_client_config_t &config);
virtual void on_error(const esp_mqtt_event_handle_t event);
virtual void on_disconnected(const esp_mqtt_event_handle_t event);
virtual void on_subscribed(const esp_mqtt_event_handle_t event);
virtual void on_unsubscribed(const esp_mqtt_event_handle_t event);
virtual void on_published(const esp_mqtt_event_handle_t event);
virtual void on_before_connect(const esp_mqtt_event_handle_t event);
virtual void on_connected(const esp_mqtt_event_handle_t event) = 0;
virtual void on_data(const esp_mqtt_event_handle_t event) = 0;
};
} // namespace idf::mqtt

View File

@ -1,221 +0,0 @@
// Copyright 2021 Espressif Systems (Shanghai) CO 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.
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include "mqtt_client.h"
namespace idf::mqtt {
/**
* @brief Broker addresss
*
* Use this to set the broker without parsing the URI string.
*
*/
struct Host {
std::string address; /*!< Host name*/
std::string path; /*!< Route path of the broker in host*/
esp_mqtt_transport_t transport; /*!< Transport scheme to use. */
};
/**
* @brief Broker addresss URI
*
* Use this to set the broker address using the URI.
*
*/
struct URI {
std::string address; /*!< Broker adddress URI*/
};
/**
* @brief Broker addresss.
*
*/
struct BrokerAddress {
std::variant<Host, URI> address; /*!< Address, defined by URI or Host struct */
uint32_t port = 0; /*!< Port used, defaults to 0 to select common port for the scheme used */
};
/**
* @brief PEM formated data
*
* Store certificates, keys and cryptographic data.
*
*/
struct PEM {
const char *data;
};
/**
* @brief DER formated data
*
* Store certificates, keys and cryptographic data.
*
*/
struct DER {
const char *data;
size_t len;
};
/**
* @brief Holds cryptography related information
*
* Hold PEM or DER formated cryptographic data.
*
*/
using CryptographicInformation = std::variant<PEM, DER>;
/**
* @brief Do not verify broker certificate.
*
* To be used when doing MQTT over TLS connection but not verify broker's certificates.
*
*/
struct Insecure {};
/**
* @brief Use global CA store
*
* To be used when client should use the Global CA Store to get trusted certificates for the broker.
*
*/
struct GlobalCAStore {};
/**
* @brief Use a pre shared key for broker authentication.
*
* To be used when client should use a PSK to authenticate the broker.
*
*/
struct PSK {
const struct psk_key_hint *hint_key;/* Pointer to PSK struct defined in esp_tls.h to enable PSK authentication */
};
/**
* @brief Authentication method for Broker
*
* Selects the method for authentication based on the type it holds.
*
*/
using BrokerAuthentication = std::variant<Insecure, GlobalCAStore, CryptographicInformation, PSK>;
/**
* @brief Password related data.
*
*/
struct Password {
std::string data;
};
/**
* @brief Data to authenticate client with certificates.
*
*/
struct ClientCertificate {
CryptographicInformation certificate; /*!< Certificate in PEM or DER format.*/
CryptographicInformation key; /*!< Key data in PEM or DER format.*/
std::optional<Password> key_password = std::nullopt; /*!< Optional password for key */
};
/**
* @brief Used to select usage of Secure Element
*
* Enables the usage of the secure element present in ESP32-WROOM-32SE.
*
*/
struct SecureElement {};
/**
* @brief Used to select usage of Digital Signature Peripheral.
*
* Enables the usage of the Digital Signature hardware accelerator.
*
*/
struct DigitalSignatureData {
void *ds_data; /* carrier of handle for digital signature parameters */
};
using AuthenticationFactor = std::variant<Password, ClientCertificate, SecureElement>;
struct BrokerConfiguration {
BrokerAddress address;
BrokerAuthentication security;
};
struct ClientCredentials {
std::optional<std::string> username; // MQTT username
AuthenticationFactor authentication;
std::vector<std::string> alpn_protos; /*!< List of supported application protocols to be used for ALPN */
/* default is ``ESP32_%CHIPID%`` where %CHIPID% are last 3 bytes of MAC address in hex format */
std::optional<std::string > client_id = std::nullopt;
};
struct Event {
mqtt_event_callback_t event_handle; /*!< handle for MQTT events as a callback in legacy mode */
esp_event_loop_handle_t event_loop_handle; /*!< handle for MQTT event loop library */
};
struct LastWill {
const char *lwt_topic; /*!< LWT (Last Will and Testament) message topic (NULL by default) */
const char *lwt_msg; /*!< LWT message (NULL by default) */
int lwt_qos; /*!< LWT message qos */
int lwt_retain; /*!< LWT retained message flag */
int lwt_msg_len; /*!< LWT message length */
};
struct Session {
LastWill last_will;
int disable_clean_session; /*!< mqtt clean session, default clean_session is true */
int keepalive; /*!< mqtt keepalive, default is 120 seconds */
bool disable_keepalive; /*!< Set disable_keepalive=true to turn off keep-alive mechanism, false by default (keepalive is active by default). Note: setting the config value `keepalive` to `0` doesn't disable keepalive feature, but uses a default keepalive period */
esp_mqtt_protocol_ver_t protocol_ver; /*!< MQTT protocol version used for connection, defaults to value from menuconfig*/
};
struct Task {
int task_prio; /*!< MQTT task priority, default is 5, can be changed in ``make menuconfig`` */
int task_stack; /*!< MQTT task stack size, default is 6144 bytes, can be changed in ``make menuconfig`` */
};
struct Connection {
esp_mqtt_transport_t transport; /*!< overrides URI transport */
int reconnect_timeout_ms; /*!< Reconnect to the broker after this value in miliseconds if auto reconnect is not disabled (defaults to 10s) */
int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */
int refresh_connection_after_ms; /*!< Refresh connection after this value (in milliseconds) */
bool disable_auto_reconnect; /*!< this mqtt client will reconnect to server (when errors/disconnect). Set disable_auto_reconnect=true to disable */
};
struct Configuration {
Event event;
Task task;
Session session;
Connection connection;
void *user_context; /*!< pass user context to this option, then can receive that context in ``event->user_context`` */
int buffer_size; /*!< size of MQTT send/receive buffer, default is 1024 (only receive buffer size if ``out_buffer_size`` defined) */
int out_buffer_size; /*!< size of MQTT output buffer. If not defined, both output and input buffers have the same size defined as ``buffer_size`` */
};
} // idf::mqtt

View File

@ -1,14 +0,0 @@
# The following four 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.16)
# (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common
$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component
$ENV{IDF_PATH}/examples/cxx/experimental/esp_mqtt_cxx/components)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mqtt_ssl_cxx)
target_add_binary_data(mqtt_ssl_cxx.elf "main/mqtt_eclipseprojects_io.pem" TEXT)

View File

@ -1,2 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |

View File

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

View File

@ -1,9 +0,0 @@
menu "Example Configuration"
config BROKER_URI
string "Broker URL"
default "mqtts://mqtt.eclipse.org:8883"
help
URL of the broker to connect to
endmenu

View File

@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

View File

@ -1,30 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----

View File

@ -1,86 +0,0 @@
/* C++ MQTT (over TCP) 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 <cstdint>
#include <string>
#include <inttypes.h>
#include "esp_mqtt_client_config.hpp"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_log.h"
#include "esp_mqtt.hpp"
namespace {
constexpr auto *TAG = "MQTT_EXAMPLE";
extern const char mqtt_eclipse_org_pem_start[] asm("_binary_mqtt_eclipseprojects_io_pem_start");
extern const char mqtt_eclipse_org_pem_end[] asm("_binary_mqtt_eclipseprojects_io_pem_end");
class MyClient final : public idf::mqtt::Client {
public:
using idf::mqtt::Client::Client;
private:
void on_connected(esp_mqtt_event_handle_t const event) override
{
using idf::mqtt::QoS;
subscribe(messages.get());
subscribe(sent_load.get(), QoS::AtMostOnce);
}
void on_data(esp_mqtt_event_handle_t const event) override
{
if (messages.match(event->topic, event->topic_len)) {
ESP_LOGI(TAG, "Received in the messages topic");
}
}
idf::mqtt::Filter messages{std::string{"$SYS/broker/messages/received"}};
idf::mqtt::Filter sent_load{std::string{"$SYS/broker/load/+/sent"}};
};
}
namespace mqtt = idf::mqtt;
extern "C" void app_main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
mqtt::BrokerConfiguration broker{
.address = {mqtt::URI{std::string{CONFIG_BROKER_URI}}},
.security = mqtt::CryptographicInformation{mqtt::PEM{mqtt_eclipse_org_pem_start}}
};
idf::mqtt::ClientCredentials credentials{};
idf::mqtt::Configuration config{};
MyClient client{broker, credentials, config};
while (true) {
constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS;
vTaskDelay( xDelay );
}
}

View File

@ -1,3 +0,0 @@
# 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,12 +0,0 @@
# The following four 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.16)
# (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common
$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component
$ENV{IDF_PATH}/examples/cxx/experimental/esp_mqtt_cxx/components)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mqtt_tcp_cxx)

View File

@ -1,2 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |

View File

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

View File

@ -1,9 +0,0 @@
menu "Example Configuration"
config BROKER_URL
string "Broker URL"
default "mqtt://mqtt.eclipse.org"
help
URL of the broker to connect to
endmenu

View File

@ -1,83 +0,0 @@
/* C++ MQTT (over TCP) 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 <inttypes.h>
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_log.h"
#include "esp_mqtt.hpp"
#include "esp_mqtt_client_config.hpp"
namespace mqtt = idf::mqtt;
namespace {
constexpr auto *TAG = "MQTT_EXAMPLE";
class MyClient final : public mqtt::Client {
public:
using mqtt::Client::Client;
private:
void on_connected(esp_mqtt_event_handle_t const event) override
{
using mqtt::QoS;
subscribe(messages.get());
subscribe(sent_load.get(), QoS::AtMostOnce);
}
void on_data(esp_mqtt_event_handle_t const event) override
{
if (messages.match(event->topic, event->topic_len)) {
ESP_LOGI(TAG, "Received in the messages topic");
}
}
mqtt::Filter messages{"$SYS/broker/messages/received"};
mqtt::Filter sent_load{"$SYS/broker/load/+/sent"};
};
}
extern "C" void app_main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
mqtt::BrokerConfiguration broker{
.address = {mqtt::URI{std::string{CONFIG_BROKER_URL}}},
.security = mqtt::Insecure{}
};
mqtt::ClientCredentials credentials{};
mqtt::Configuration config{};
MyClient client{broker, credentials, config};
while (true) {
constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS;
vTaskDelay(xDelay);
}
}

View File

@ -1,3 +0,0 @@
# 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,8 +0,0 @@
# The following 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.16)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp_timer_cxx)

View File

@ -1,51 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# Example: ESPTimer C++ class
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of the ESPTimer c++ class in ESP-IDF.
In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option.
This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling.
This is necessary for the C++ APIs.
## How to use example
### Hardware Required
Any ESP32 family development board.
### Configure the project
```
idf.py menuconfig
```
### Build and Flash
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port.)
(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
```
Setting up timer to trigger in 500ms
timeout
Setting up timer to periodically every 200ms
periodic timeout
periodic timeout
periodic timeout
periodic timeout
periodic timeout
```

View File

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

View File

@ -1,38 +0,0 @@
/* ESP Timer 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 <chrono>
#include "esp_timer_cxx.hpp"
#include "esp_exception.hpp"
using namespace std;
using namespace idf;
using namespace idf::esp_timer;
extern "C" void app_main(void)
{
try {
cout << "Setting up timer to trigger in 500ms" << endl;
ESPTimer timer([]() { cout << "timeout" << endl; });
timer.start(chrono::microseconds(200 * 1000));
this_thread::sleep_for(std::chrono::milliseconds(550));
cout << "Setting up timer to trigger periodically every 200ms" << endl;
ESPTimer timer2([]() { cout << "periodic timeout" << endl; });
timer2.start_periodic(chrono::microseconds(200 * 1000));
this_thread::sleep_for(std::chrono::milliseconds(1050));
} catch (const ESPException &e) {
cout << "Exception with error: " << e.error << endl;
}
}

View File

@ -1,3 +0,0 @@
# 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,17 +0,0 @@
idf_build_get_property(target IDF_TARGET)
set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp" "i2c_cxx.cpp" "spi_cxx.cpp" "spi_host_cxx.cpp")
set(requires "esp_timer" "driver")
if(NOT ${target} STREQUAL "linux")
list(APPEND srcs
"esp_event_api.cpp"
"esp_event_cxx.cpp")
list(APPEND requires "esp_event")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "private_include"
PRIV_REQUIRES freertos
REQUIRES ${requires})

View File

@ -1,20 +0,0 @@
# Experimental C++ Component
*Warning:* This component is subject to change without notice. Don't consider it as a stable API.
It proposes future C++ interfaces of IDF components.
# Usage/Build
To use and build this component, add it as an extra component in your project's cmake file:
```cmake
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component)
```
# Tests
To build the tests, first add them to the unit test's CMakeLists.txt:
```cmake
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
```
Then go to the unit test app's directory and run:
```bash
idf.py -T experimental_cpp_component build
```

View File

@ -1,115 +0,0 @@
#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

@ -1,226 +0,0 @@
// 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",
false // skip_unhandled_events
};
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

@ -1,29 +0,0 @@
// Copyright 2020 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.
#ifdef __cpp_exceptions
#include "esp_exception.hpp"
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

@ -1,60 +0,0 @@
// Copyright 2020 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.
#ifdef __cpp_exceptions
#include <functional>
#include "esp_timer_cxx.hpp"
#include "esp_exception.hpp"
using namespace std;
namespace idf {
namespace esp_timer {
ESPTimer::ESPTimer(function<void()> timeout_cb, const string &timer_name)
: timeout_cb(timeout_cb), name(timer_name)
{
if (timeout_cb == nullptr) {
throw ESPException(ESP_ERR_INVALID_ARG);
}
esp_timer_create_args_t timer_args = {};
timer_args.callback = esp_timer_cb;
timer_args.arg = this;
timer_args.dispatch_method = ESP_TIMER_TASK;
timer_args.name = name.c_str();
CHECK_THROW(esp_timer_create(&timer_args, &timer_handle));
}
ESPTimer::~ESPTimer()
{
// Ignore potential ESP_ERR_INVALID_STATE here to not throw exception.
esp_timer_stop(timer_handle);
esp_timer_delete(timer_handle);
}
void ESPTimer::esp_timer_cb(void *arg)
{
ESPTimer *timer = static_cast<ESPTimer*>(arg);
timer->timeout_cb();
}
} // esp_timer
} // idf
#endif // __cpp_exceptions

View File

@ -1,214 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include <array>
#include "driver/gpio.h"
#include "gpio_cxx.hpp"
namespace idf {
#define GPIO_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), GPIOException)
namespace {
#if CONFIG_IDF_TARGET_LINUX
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
#elif CONFIG_IDF_TARGET_ESP32
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
#elif CONFIG_IDF_TARGET_ESP32S2
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
#elif CONFIG_IDF_TARGET_ESP32S3
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
#elif CONFIG_IDF_TARGET_ESP32C3
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#elif CONFIG_IDF_TARGET_ESP32C2
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#elif CONFIG_IDF_TARGET_ESP32C6
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#elif CONFIG_IDF_TARGET_ESP32H2
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#else
#error "No GPIOs defined for the current target"
#endif
gpio_num_t gpio_to_driver_type(const GPIONum &gpio_num)
{
return static_cast<gpio_num_t>(gpio_num.get_num());
}
}
GPIOException::GPIOException(esp_err_t error) : ESPException(error) { }
esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept
{
if (pin_num >= GPIO_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
for (auto num: INVALID_GPIOS)
{
if (pin_num == num) {
return ESP_ERR_INVALID_ARG;
}
}
return ESP_OK;
}
esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept
{
if (strength >= GPIO_DRIVE_CAP_MAX) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
GPIOPullMode GPIOPullMode::FLOATING()
{
return GPIOPullMode(GPIO_FLOATING);
}
GPIOPullMode GPIOPullMode::PULLUP()
{
return GPIOPullMode(GPIO_PULLUP_ONLY);
}
GPIOPullMode GPIOPullMode::PULLDOWN()
{
return GPIOPullMode(GPIO_PULLDOWN_ONLY);
}
GPIOWakeupIntrType GPIOWakeupIntrType::LOW_LEVEL()
{
return GPIOWakeupIntrType(GPIO_INTR_LOW_LEVEL);
}
GPIOWakeupIntrType GPIOWakeupIntrType::HIGH_LEVEL()
{
return GPIOWakeupIntrType(GPIO_INTR_HIGH_LEVEL);
}
GPIODriveStrength GPIODriveStrength::DEFAULT()
{
return MEDIUM();
}
GPIODriveStrength GPIODriveStrength::WEAK()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_0);
}
GPIODriveStrength GPIODriveStrength::LESS_WEAK()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_1);
}
GPIODriveStrength GPIODriveStrength::MEDIUM()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_2);
}
GPIODriveStrength GPIODriveStrength::STRONGEST()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_3);
}
GPIOBase::GPIOBase(GPIONum num) : gpio_num(num)
{
GPIO_CHECK_THROW(gpio_reset_pin(gpio_to_driver_type(gpio_num)));
}
void GPIOBase::hold_en()
{
GPIO_CHECK_THROW(gpio_hold_en(gpio_to_driver_type(gpio_num)));
}
void GPIOBase::hold_dis()
{
GPIO_CHECK_THROW(gpio_hold_dis(gpio_to_driver_type(gpio_num)));
}
void GPIOBase::set_drive_strength(GPIODriveStrength strength)
{
GPIO_CHECK_THROW(gpio_set_drive_capability(gpio_to_driver_type(gpio_num),
static_cast<gpio_drive_cap_t>(strength.get_strength())));
}
GPIO_Output::GPIO_Output(GPIONum num) : GPIOBase(num)
{
GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_OUTPUT));
}
void GPIO_Output::set_high()
{
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 1));
}
void GPIO_Output::set_low()
{
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 0));
}
GPIODriveStrength GPIOBase::get_drive_strength()
{
gpio_drive_cap_t strength;
GPIO_CHECK_THROW(gpio_get_drive_capability(gpio_to_driver_type(gpio_num), &strength));
return GPIODriveStrength(static_cast<uint32_t>(strength));
}
GPIOInput::GPIOInput(GPIONum num) : GPIOBase(num)
{
GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_INPUT));
}
GPIOLevel GPIOInput::get_level() const noexcept
{
int level = gpio_get_level(gpio_to_driver_type(gpio_num));
if (level) {
return GPIOLevel::HIGH;
} else {
return GPIOLevel::LOW;
}
}
void GPIOInput::set_pull_mode(GPIOPullMode mode)
{
GPIO_CHECK_THROW(gpio_set_pull_mode(gpio_to_driver_type(gpio_num),
static_cast<gpio_pull_mode_t>(mode.get_pull_mode())));
}
void GPIOInput::wakeup_enable(GPIOWakeupIntrType interrupt_type)
{
GPIO_CHECK_THROW(gpio_wakeup_enable(gpio_to_driver_type(gpio_num),
static_cast<gpio_int_type_t>(interrupt_type.get_level())));
}
void GPIOInput::wakeup_disable()
{
GPIO_CHECK_THROW(gpio_wakeup_disable(gpio_to_driver_type(gpio_num)));
}
GPIO_OpenDrain::GPIO_OpenDrain(GPIONum num) : GPIOInput(num)
{
GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_INPUT_OUTPUT_OD));
}
void GPIO_OpenDrain::set_floating()
{
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 1));
}
void GPIO_OpenDrain::set_low()
{
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 0));
}
}
#endif

View File

@ -1,10 +0,0 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_esp_timer_cxx_host)

View File

@ -1,36 +0,0 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# C++ ESPTimer test on Linux target
This unit test tests basic functionality of the `ESPTimer` class. The test does not use mocks. Instead, it runs the whole implementation of the component on the Linux host. The test framework is CATCH.
## Requirements
* A Linux system
* The usual IDF requirements for Linux system, as described in the [Getting Started Guides](../../../../../../docs/en/get-started/index.rst).
* The host's gcc/g++
This application has been tested on Ubuntu 20.04 with `gcc` version *9.3.0*.
## Build
First, make sure that the target is set to Linux. Run `idf.py --preview set-target linux` if you are not sure. Then do a normal IDF build: `idf.py build`.
## Run
IDF monitor doesn't work yet for Linux. You have to run the app manually:
```bash
build/test_esp_timer_cxx_host.elf
```
## Example Output
Ideally, all tests pass, which is indicated by "All tests passed" in the last line:
```bash
$ build/test_esp_timer_cxx_host.elf
===============================================================================
All tests passed (9 assertions in 11 test cases)
```

View File

@ -1,5 +0,0 @@
idf_component_register(SRCS "esp_timer_test.cpp"
INCLUDE_DIRS
"."
$ENV{IDF_PATH}/tools/catch
REQUIRES cmock esp_timer experimental_cpp_component)

View File

@ -1,196 +0,0 @@
/* ESP Timer C++ unit tests
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.
*/
#define CATCH_CONFIG_MAIN
#include <stdio.h>
#include <stdexcept>
#include "esp_err.h"
#include "esp_timer_cxx.hpp"
#include "catch.hpp"
extern "C" {
#include "Mockesp_timer.h"
}
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "test";
}
using namespace std;
using namespace idf;
using namespace idf::esp_timer;
struct FixtureException : std::exception {
const char *what() const noexcept override {
return "CMock failed";
}
};
struct TimerCreationFixture {
TimerCreationFixture(bool expect_stop = false) : out_handle(reinterpret_cast<esp_timer_handle_t>(1))
{
if (!TEST_PROTECT()) {
throw FixtureException();
}
esp_timer_create_ExpectAnyArgsAndReturn(ESP_OK);
esp_timer_create_ReturnThruPtr_out_handle(&out_handle);
if (expect_stop) {
esp_timer_stop_ExpectAndReturn(out_handle, ESP_OK); // implementation may always call stop
} else {
esp_timer_stop_IgnoreAndReturn(ESP_OK); // implementation may always call stop
}
esp_timer_delete_ExpectAndReturn(out_handle, ESP_OK);
}
virtual ~TimerCreationFixture()
{
Mockesp_timer_Verify();
}
esp_timer_handle_t out_handle;
};
static void (*trigger_timer_callback)(void *data) = nullptr;
esp_err_t cmock_timer_create_callback(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle, int cmock_num_calls)
{
trigger_timer_callback = create_args->callback;
return ESP_OK;
}
struct TimerCallbackFixture : public TimerCreationFixture {
TimerCallbackFixture(bool expect_stop = false) : TimerCreationFixture(expect_stop)
{
esp_timer_create_AddCallback(cmock_timer_create_callback);
}
~TimerCallbackFixture()
{
trigger_timer_callback = nullptr;
}
};
TEST_CASE("get_time works")
{
esp_timer_get_time_ExpectAndReturn(static_cast<uint64_t>(0xfeeddeadbeef));
CHECK(get_time() == std::chrono::microseconds(0xfeeddeadbeef));
}
TEST_CASE("get_next_alarm works")
{
esp_timer_get_next_alarm_ExpectAndReturn(static_cast<uint64_t>(47u));
CHECK(get_next_alarm() == std::chrono::microseconds(47u));
}
TEST_CASE("ESPTimer null function")
{
CHECK_THROWS_AS(ESPTimer(nullptr), ESPException&);
}
TEST_CASE("ESPTimer empty std::function")
{
function<void()> nothing;
CHECK_THROWS_AS(ESPTimer(nothing, "test"), ESPException&);
}
TEST_CASE("ESPTimer initializes and deletes itself")
{
TimerCreationFixture fix;
function<void()> timer_cb = [&]() { };
ESPTimer(timer_cb, "test");
}
TEST_CASE("ESPTimer start throws on invalid state failure")
{
TimerCreationFixture fix;
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
CHECK_THROWS_AS(timer.start(chrono::microseconds(5000)), ESPException&);
}
TEST_CASE("ESPTimer start periodically throws on invalid state failure")
{
TimerCreationFixture fix;
esp_timer_start_periodic_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
CHECK_THROWS_AS(timer.start_periodic(chrono::microseconds(5000)), ESPException&);
}
TEST_CASE("ESPTimer stopp throws on invaid state failure")
{
TimerCreationFixture fix;
// Overriding stop part of the fixture
esp_timer_stop_StopIgnore();
esp_timer_stop_IgnoreAndReturn(ESP_ERR_INVALID_STATE);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
CHECK_THROWS_AS(timer.stop(), ESPException&);
}
TEST_CASE("ESPTimer stops in destructor")
{
TimerCreationFixture fix(true);
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
timer.start(chrono::microseconds(5000));
}
TEST_CASE("ESPTimer stops correctly")
{
TimerCreationFixture fix(true);
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK);
// Additional stop needed because stop is called in ESPTimer::stop and ~ESPTimer.
esp_timer_stop_ExpectAndReturn(fix.out_handle, ESP_OK);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
timer.start(chrono::microseconds(5000));
timer.stop();
}
TEST_CASE("ESPTimer callback works")
{
TimerCallbackFixture fix;
int flag = 0;
function<void()> timer_cb = [&]() { flag = 47; };
ESPTimer timer(timer_cb);
trigger_timer_callback(&timer);
REQUIRE(trigger_timer_callback != nullptr);
CHECK(flag == 47);
}

View File

@ -1,10 +0,0 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.linux
@pytest.mark.host_test
def test_esp_timer_cxx(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=5)

View File

@ -1,3 +0,0 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@ -1,361 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*
* 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 "catch.hpp"
#include "gpio_cxx.hpp"
#include "driver/spi_master.h"
#include "spi_cxx.hpp"
#include "i2c_cxx.hpp"
extern "C" {
#include "Mockgpio.h"
#include "Mockspi_master.h"
#include "Mockspi_common.h"
#include "Mocki2c.h"
}
static const idf::GPIONum VALID_GPIO(18);
/**
* Exception which is thrown if there is some internal cmock error which results in a
* longjump to the location of a TEST_PROTECT() call.
*
* @note This is a temporary solution until there is a better integration of CATCH into CMock.
* Note also that usually there will be a segfault when cmock fails a second time.
* This means paying attention to the first error message is crucial for removing errors.
*/
class CMockException : public std::exception {
public:
virtual ~CMockException() { }
/**
* @return A reminder to look at the actual cmock log.
*/
virtual const char *what() const noexcept
{
return "CMock encountered an error. Look at the CMock log";
}
};
/**
* Helper macro for setting up a test protect call for CMock.
*
* This macro should be used at the beginning of any test cases
* that uses generated CMock mock functions.
* This is necessary because CMock uses longjmp which screws up C++ stacks and
* also the CATCH mechanisms.
*
* @note This is a temporary solution until there is a better integration of CATCH into CMock.
* Note also that usually there will be a segfault when cmock fails a second time.
* This means paying attention to the first error message is crucial for removing errors.
*/
#define CMOCK_SETUP() \
do { \
if (!TEST_PROTECT()) { \
throw CMockException(); \
} \
} \
while (0)
struct CMockFixture {
CMockFixture()
{
CMOCK_SETUP();
}
~CMockFixture()
{
// Verify that all expected methods have been called.
Mockgpio_Verify();
Mockspi_master_Verify();
Mockspi_common_Verify();
}
};
struct GPIOFixture : public CMockFixture {
GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT)
: CMockFixture(), num(gpio_num)
{
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), mode, ESP_OK);
}
idf::GPIONum num;
};
struct SPIFix;
struct SPIDevFix;
struct SPITransactionDescriptorFix;
struct SPITransactionTimeoutFix;
struct SPITransactionFix;
static SPIFix *g_fixture;
static SPIDevFix *g_dev_fixture;
static SPITransactionDescriptorFix *g_trans_desc_fixture;
static SPITransactionTimeoutFix *g_trans_timeout_fixture;
static SPITransactionFix *g_trans_fixture;
struct SPIFix : public CMockFixture {
SPIFix(spi_host_device_t host_id = spi_host_device_t(1),
uint32_t mosi = 1,
uint32_t miso = 2,
uint32_t sclk = 3) : CMockFixture(), bus_config() {
bus_config.mosi_io_num = mosi;
bus_config.miso_io_num = miso;
bus_config.sclk_io_num = sclk;
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
spi_bus_initialize_ExpectWithArrayAndReturn(host_id, &bus_config, 1, spi_common_dma_t::SPI_DMA_CH_AUTO, ESP_OK);
spi_bus_free_ExpectAnyArgsAndReturn(ESP_OK);
g_fixture = this;
}
~SPIFix() {
g_fixture = nullptr;
}
spi_bus_config_t bus_config;
};
struct QSPIFix : public SPIFix {
QSPIFix(spi_host_device_t host_id = spi_host_device_t(1),
uint32_t mosi = 1,
uint32_t miso = 2,
uint32_t sclk = 3,
uint32_t wp = 4,
uint32_t hd = 5) : SPIFix(host_id, mosi, miso, sclk)
{
bus_config.quadwp_io_num = wp;
bus_config.quadhd_io_num = hd;
}
};
enum class CreateAnd {
FAIL,
SUCCEED,
IGNORE
};
struct SPIDevFix {
SPIDevFix(CreateAnd flags)
: dev_handle(reinterpret_cast<spi_device_handle_t>(47)),
dev_config()
{
dev_config.spics_io_num = 4;
if (flags == CreateAnd::FAIL) {
spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_FAIL);
} else if (flags == CreateAnd::IGNORE) {
spi_bus_add_device_IgnoreAndReturn(ESP_OK);
spi_bus_remove_device_IgnoreAndReturn(ESP_OK);
} else {
spi_bus_add_device_AddCallback(add_dev_cb);
spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_OK);
spi_bus_remove_device_ExpectAndReturn(dev_handle, ESP_OK);
}
g_dev_fixture = this;
}
~SPIDevFix()
{
spi_bus_add_device_AddCallback(nullptr);
g_dev_fixture = nullptr;
}
spi_device_handle_t dev_handle;
spi_device_interface_config_t dev_config;
static esp_err_t add_dev_cb(spi_host_device_t host_id,
const spi_device_interface_config_t* dev_config,
spi_device_handle_t* handle,
int cmock_num_calls)
{
SPIDevFix *fix = static_cast<SPIDevFix*>(g_dev_fixture);
*handle = fix->dev_handle;
fix->dev_config = *dev_config;
return ESP_OK;
}
};
struct SPITransactionFix {
SPITransactionFix(esp_err_t get_trans_return = ESP_OK) : get_transaction_return(get_trans_return)
{
spi_device_queue_trans_AddCallback(queue_trans_cb);
spi_device_get_trans_result_AddCallback(get_trans_result_cb);
spi_device_queue_trans_ExpectAnyArgsAndReturn(ESP_OK);
spi_device_get_trans_result_ExpectAnyArgsAndReturn(get_trans_return);
g_trans_fixture = this;
}
~SPITransactionFix()
{
spi_device_get_trans_result_AddCallback(nullptr);
spi_device_queue_trans_AddCallback(nullptr);
g_trans_fixture = nullptr;
}
static esp_err_t queue_trans_cb(spi_device_handle_t handle,
spi_transaction_t* trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionFix *fix = static_cast<SPITransactionFix*> (g_trans_fixture);
fix->orig_trans = trans_desc;
return ESP_OK;
}
static esp_err_t get_trans_result_cb(spi_device_handle_t handle,
spi_transaction_t** trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionFix *fix = static_cast<SPITransactionFix*> (g_trans_fixture);
*trans_desc = fix->orig_trans;
return fix->get_transaction_return;
}
esp_err_t get_transaction_return;
spi_transaction_t *orig_trans;
};
struct SPITransactionDescriptorFix {
SPITransactionDescriptorFix(size_t size = 1, bool ignore_handle = false, TickType_t wait_time = portMAX_DELAY)
: size(size), handle(reinterpret_cast<spi_device_handle_t>(0x01020304))
{
spi_device_queue_trans_AddCallback(queue_trans_cb);
spi_device_get_trans_result_AddCallback(get_trans_result_cb);
spi_device_acquire_bus_ExpectAndReturn(handle, portMAX_DELAY, ESP_OK);
if (ignore_handle) {
spi_device_acquire_bus_IgnoreArg_device();
}
spi_device_queue_trans_ExpectAndReturn(handle, nullptr, 0, ESP_OK);
spi_device_queue_trans_IgnoreArg_trans_desc();
if (ignore_handle) {
spi_device_queue_trans_IgnoreArg_handle();
}
spi_device_get_trans_result_ExpectAndReturn(handle, nullptr, wait_time, ESP_OK);
spi_device_get_trans_result_IgnoreArg_trans_desc();
if (ignore_handle) {
spi_device_get_trans_result_IgnoreArg_handle();
}
spi_device_release_bus_ExpectAnyArgs();
g_trans_desc_fixture = this;
}
~SPITransactionDescriptorFix()
{
spi_device_get_trans_result_AddCallback(nullptr);
spi_device_queue_trans_AddCallback(nullptr);
g_trans_desc_fixture = nullptr;
}
static esp_err_t queue_trans_cb(spi_device_handle_t handle,
spi_transaction_t* trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionDescriptorFix *fix = static_cast<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
fix->orig_trans = trans_desc;
return ESP_OK;
}
static esp_err_t get_trans_result_cb(spi_device_handle_t handle,
spi_transaction_t** trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionDescriptorFix *fix = static_cast<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
for (int i = 0; i < fix->size; i++) {
static_cast<uint8_t*>(fix->orig_trans->rx_buffer)[i] = fix->rx_data[i];
}
*trans_desc = fix->orig_trans;
return ESP_OK;
}
size_t size;
spi_transaction_t *orig_trans;
spi_device_handle_t handle;
std::vector<uint8_t> tx_data;
std::vector<uint8_t> rx_data;
};
struct I2CMasterFix {
I2CMasterFix(i2c_port_t port_arg = I2C_NUM_0) : i2c_conf(), port(port_arg)
{
i2c_conf.mode = i2c_mode_t::I2C_MODE_MASTER;
i2c_conf.sda_io_num = 2;
i2c_conf.scl_io_num = 1;
i2c_conf.sda_pullup_en = true;
i2c_conf.scl_pullup_en = true;
i2c_conf.master.clk_speed = 400000;
i2c_conf.clk_flags = 0;
i2c_param_config_ExpectWithArrayAndReturn(i2c_port_t(0), &i2c_conf, 1, ESP_OK);
i2c_driver_install_ExpectAndReturn(i2c_port_t(0), i2c_mode_t::I2C_MODE_MASTER, 0, 0, 0, ESP_OK);
i2c_driver_delete_ExpectAndReturn(i2c_port_t(0), ESP_OK);
}
i2c_config_t i2c_conf;
i2c_port_t port;
};
#if CONFIG_SOC_I2C_SUPPORT_SLAVE
struct I2CSlaveFix {
I2CSlaveFix(CreateAnd flags, i2c_port_t port_arg = I2C_NUM_0, size_t buffer_size = 64) : i2c_conf(), port(port_arg)
{
if (flags == CreateAnd::SUCCEED) {
i2c_conf.mode = i2c_mode_t::I2C_MODE_SLAVE;
i2c_conf.sda_io_num = 2;
i2c_conf.scl_io_num = 1;
i2c_conf.sda_pullup_en = true;
i2c_conf.scl_pullup_en = true;
i2c_conf.slave.addr_10bit_en = 0;
i2c_conf.slave.slave_addr = 0x47;
i2c_param_config_ExpectWithArrayAndReturn(port, &i2c_conf, 1, ESP_OK);
i2c_driver_install_ExpectAndReturn(port, i2c_mode_t::I2C_MODE_SLAVE, buffer_size, buffer_size, 0, ESP_OK);
i2c_driver_delete_ExpectAndReturn(port, ESP_OK);
} else if (flags == CreateAnd::IGNORE) {
i2c_param_config_IgnoreAndReturn(ESP_OK);
i2c_driver_install_IgnoreAndReturn(ESP_OK);
i2c_driver_delete_IgnoreAndReturn(ESP_OK);
} else {
throw idf::I2CException(ESP_ERR_INVALID_ARG);
}
}
i2c_config_t i2c_conf;
i2c_port_t port;
};
#endif // CONFIG_SOC_I2C_SUPPORT_SLAVE
struct I2CCmdLinkFix
{
I2CCmdLinkFix(uint8_t expected_addr, i2c_rw_t type = I2C_MASTER_WRITE) : dummy_handle(reinterpret_cast<i2c_cmd_handle_t>(0xbeef))
{
i2c_cmd_link_create_ExpectAndReturn(&dummy_handle);
i2c_master_start_ExpectAndReturn(&dummy_handle, ESP_OK);
i2c_master_write_byte_ExpectAndReturn(&dummy_handle, expected_addr << 1 | type, true, ESP_OK);
i2c_cmd_link_delete_Expect(&dummy_handle);
}
i2c_cmd_handle_t dummy_handle;
};

View File

@ -1,13 +0,0 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
# Overriding components which should be mocked
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
# Including experimental component here because it's outside IDF's main component directory
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_gpio_cxx_host)

View File

@ -1,8 +0,0 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/test_gpio_cxx_host.elf`

View File

@ -1,13 +0,0 @@
idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR)
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "gpio_cxx_test.cpp"
"${cpp_component}/esp_exception.cpp"
"${cpp_component}/gpio_cxx.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/host_test/fixtures"
"${cpp_component}/include"
"${cpp_component}/test" # FIXME for unity_cxx.hpp, make it generally available instead
$ENV{IDF_PATH}/tools/catch
REQUIRES driver cmock)

View File

@ -1,404 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*
* GPIO C++ unit tests
*
* 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.
*/
#define CATCH_CONFIG_MAIN
#include <stdio.h>
#include "esp_err.h"
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "gpio_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
extern "C" {
#include "Mockgpio.h"
}
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "test";
}
using namespace std;
using namespace idf;
TEST_CASE("gpio num out of range")
{
CHECK_THROWS_AS(GPIONum(-1), GPIOException&);
CHECK_THROWS_AS(GPIONum(static_cast<uint32_t>(GPIO_NUM_MAX)), GPIOException&);
CHECK_THROWS_AS(GPIONum(24), GPIOException&); // On ESP32, 24 isn't a valid GPIO number
}
TEST_CASE("gpio num operator")
{
GPIONum gpio_num_0(18u);
GPIONum gpio_num_1(18u);
GPIONum gpio_num_2(19u);
CHECK(gpio_num_0 == gpio_num_1);
CHECK(gpio_num_2 != gpio_num_1);
}
TEST_CASE("drive strength out of range")
{
CHECK_THROWS_AS(GPIODriveStrength(-1), GPIOException&);
CHECK_THROWS_AS(GPIODriveStrength(static_cast<uint32_t>(GPIO_DRIVE_CAP_MAX)), GPIOException&);
}
TEST_CASE("drive strength as expected")
{
CHECK(GPIODriveStrength::DEFAULT().get_strength() == GPIO_DRIVE_CAP_2);
CHECK(GPIODriveStrength::WEAK().get_strength() == GPIO_DRIVE_CAP_0);
CHECK(GPIODriveStrength::LESS_WEAK().get_strength() == GPIO_DRIVE_CAP_1);
CHECK(GPIODriveStrength::MEDIUM().get_strength() == GPIO_DRIVE_CAP_2);
CHECK(GPIODriveStrength::STRONGEST().get_strength() == GPIO_DRIVE_CAP_3);
}
TEST_CASE("pull mode create functions work as expected")
{
CHECK(GPIOPullMode::FLOATING().get_pull_mode() == 3);
CHECK(GPIOPullMode::PULLUP().get_pull_mode() == 0);
CHECK(GPIOPullMode::PULLDOWN().get_pull_mode() == 1);
}
TEST_CASE("GPIOIntrType create functions work as expected")
{
CHECK(GPIOWakeupIntrType::LOW_LEVEL().get_level() == GPIO_INTR_LOW_LEVEL);
CHECK(GPIOWakeupIntrType::HIGH_LEVEL().get_level() == GPIO_INTR_HIGH_LEVEL);
}
TEST_CASE("output resetting pin fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("output setting direction fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("output constructor sets correct arguments")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), GPIO_MODE_OUTPUT, ESP_OK);
GPIO_Output gpio(VALID_GPIO);
Mockgpio_Verify();
}
TEST_CASE("output set high fails")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_FAIL);
GPIO_Output gpio(fix.num);
CHECK_THROWS_AS(gpio.set_high(), GPIOException&);
}
TEST_CASE("output set high success")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_OK);
GPIO_Output gpio(fix.num);
gpio.set_high();
}
TEST_CASE("output set low fails")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_FAIL);
GPIO_Output gpio(fix.num);
CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
}
TEST_CASE("output set low success")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_OK);
GPIO_Output gpio(fix.num);
gpio.set_low();
}
TEST_CASE("output set drive strength")
{
GPIOFixture fix(VALID_GPIO);
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_DRIVE_CAP_0, ESP_OK);
GPIO_Output gpio(fix.num);
gpio.set_drive_strength(GPIODriveStrength::WEAK());
}
TEST_CASE("output get drive strength")
{
GPIOFixture fix(VALID_GPIO);
gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);
GPIO_Output gpio(fix.num);
CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
}
TEST_CASE("GPIOInput setting direction fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIOInput gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("constructor sets correct arguments")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), GPIO_MODE_INPUT, ESP_OK);
GPIOInput gpio(VALID_GPIO);
Mockgpio_Verify();
}
TEST_CASE("get level low")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0);
GPIOInput gpio(fix.num);
CHECK(gpio.get_level() == GPIOLevel::LOW);
}
TEST_CASE("get level high")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1);
GPIOInput gpio(fix.num);
CHECK(gpio.get_level() == GPIOLevel::HIGH);
}
TEST_CASE("set pull mode fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_FLOATING, ESP_FAIL);
GPIOInput gpio(fix.num);
CHECK_THROWS_AS(gpio.set_pull_mode(GPIOPullMode::FLOATING()), GPIOException&);
}
TEST_CASE("GPIOInput set pull mode floating")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_FLOATING, ESP_OK);
GPIOInput gpio(fix.num);
gpio.set_pull_mode(GPIOPullMode::FLOATING());
}
TEST_CASE("GPIOInput set pull mode pullup")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_PULLUP_ONLY, ESP_OK);
GPIOInput gpio(fix.num);
gpio.set_pull_mode(GPIOPullMode::PULLUP());
}
TEST_CASE("GPIOInput set pull mode pulldown")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_PULLDOWN_ONLY, ESP_OK);
GPIOInput gpio(fix.num);
gpio.set_pull_mode(GPIOPullMode::PULLDOWN());
}
TEST_CASE("GPIOInput wake up enable fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_INTR_LOW_LEVEL, ESP_FAIL);
GPIOInput gpio(fix.num);
CHECK_THROWS_AS(gpio.wakeup_enable(GPIOWakeupIntrType::LOW_LEVEL()), GPIOException&);
}
TEST_CASE("GPIOInput wake up enable high int")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_INTR_HIGH_LEVEL, ESP_OK);
GPIOInput gpio(fix.num);
gpio.wakeup_enable(GPIOWakeupIntrType::HIGH_LEVEL());
}
TEST_CASE("GPIOInput wake up disable fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), ESP_FAIL);
GPIOInput gpio(fix.num);
CHECK_THROWS_AS(gpio.wakeup_disable(), GPIOException&);
}
TEST_CASE("GPIOInput wake up disable high int")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), ESP_OK);
GPIOInput gpio(fix.num);
gpio.wakeup_disable();
}
TEST_CASE("GPIO_OpenDrain setting direction fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIO_OpenDrain gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("GPIO_OpenDrain constructor sets correct arguments")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT,
ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
GPIO_OpenDrain gpio(VALID_GPIO);
Mockgpio_Verify();
}
TEST_CASE("GPIO_OpenDrain set floating fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_FAIL);
GPIO_OpenDrain gpio(fix.num);
CHECK_THROWS_AS(gpio.set_floating(), GPIOException&);
}
TEST_CASE("GPIO_OpenDrain set floating success")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_OK);
GPIO_OpenDrain gpio(fix.num);
gpio.set_floating();
}
TEST_CASE("GPIO_OpenDrain set low fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_FAIL);
GPIO_OpenDrain gpio(fix.num);
CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
}
TEST_CASE("GPIO_OpenDrain set low success")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_OK);
GPIO_OpenDrain gpio(fix.num);
gpio.set_low();
}
TEST_CASE("GPIO_OpenDrain set drive strength")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_DRIVE_CAP_0, ESP_OK);
GPIO_OpenDrain gpio(fix.num);
gpio.set_drive_strength(GPIODriveStrength::WEAK());
}
TEST_CASE("GPIO_OpenDrain get drive strength")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);
GPIO_OpenDrain gpio(fix.num);
CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
}

View File

@ -1,10 +0,0 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.linux
@pytest.mark.host_test
def test_gpio_cxx(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=5)

View File

@ -1,3 +0,0 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@ -1,14 +0,0 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
# Overriding components which should be mocked
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
# Including experimental component here because it's outside IDF's main component directory
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_i2c_cxx_host)

View File

@ -1,8 +0,0 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/host_i2c_cxx_test.elf`

View File

@ -1,11 +0,0 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "i2c_cxx_test.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/host_test/fixtures"
"${cpp_component}/private_include"
$ENV{IDF_PATH}/tools/catch
REQUIRES cmock driver experimental_cpp_component)
target_link_libraries(${COMPONENT_LIB} -lpthread)

View File

@ -1,463 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*
* I2C C++ unit tests
*
* 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.
*/
#define CATCH_CONFIG_MAIN
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "driver/i2c.h"
#include "i2c_cxx.hpp"
#include "system_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
extern "C" {
#include "Mocki2c.h"
}
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "host_test error";
}
using namespace std;
using namespace idf;
TEST_CASE("I2CNumber")
{
CMockFixture fix;
CHECK(I2CNumber::I2C0().get_num() == 0);
}
TEST_CASE("I2CAddr")
{
CMockFixture fix;
CHECK_THROWS_AS(I2CAddress(-1), I2CException&);
I2CAddress(0);
I2CAddress(127);
CHECK_THROWS_AS(I2CAddress(128), I2CException&);
I2CAddress addr(47);
CHECK(addr.get_addr() == 47);
}
TEST_CASE("I2CMaster parameter configuration fails")
{
CMockFixture fix;
i2c_param_config_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CMaster(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000)), I2CException&);
}
TEST_CASE("I2CMaster driver install failure")
{
CMockFixture fix;
i2c_param_config_ExpectAnyArgsAndReturn(ESP_OK);
i2c_driver_install_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CMaster(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000)), I2CException&);
}
TEST_CASE("I2CMaster success")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CMaster(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
}
TEST_CASE("I2CWrite empty data throws")
{
CMockFixture fix;
std::vector<uint8_t> empty;
CHECK_THROWS_AS(I2CWrite writer(empty), I2CException&);
}
TEST_CASE("I2CRead zero length throws")
{
CMockFixture fix;
std::vector<uint8_t> empty;
CHECK_THROWS_AS(I2CRead reader(0), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at link creation")
{
CMockFixture fix;
i2c_cmd_link_create_ExpectAndReturn(nullptr);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at start")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at write byte")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at write")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_IgnoreAndReturn(ESP_OK);
i2c_master_write_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at stop")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_IgnoreAndReturn(ESP_OK);
i2c_master_write_IgnoreAndReturn(ESP_OK);
i2c_master_stop_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer execution times out")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_IgnoreAndReturn(ESP_OK);
i2c_master_write_IgnoreAndReturn(ESP_OK);
i2c_master_stop_IgnoreAndReturn(ESP_OK);
i2c_master_cmd_begin_ExpectAnyArgsAndReturn(ESP_ERR_TIMEOUT);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CTransferException&);
}
TEST_CASE("I2CWrite calls driver correctly")
{
CMockFixture fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0xAB, 0xBA};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually write the data but for the tests it is enough for now
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
std::vector<uint8_t> WRITE_BYTES = {0xAB, 0xBA};
I2CWrite write(WRITE_BYTES);
write.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47));
}
TEST_CASE("I2CRead do_transfer fails at read")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_ExpectAndReturn(&dummy_handle);
i2c_master_start_ExpectAnyArgsAndReturn(ESP_OK);
i2c_master_write_byte_ExpectAnyArgsAndReturn(ESP_OK);
i2c_master_read_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CRead reader(2);
CHECK_THROWS_AS(reader.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CRead calls driver correctly")
{
CMockFixture fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_READ);
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, READ_SIZE, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CRead reader(READ_SIZE);
std::vector<uint8_t> result = reader.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47));
CHECK(result[0] == 0xAB);
CHECK(result[1] == 0xBA);
}
TEST_CASE("I2CComposed try to read size 0 throws")
{
CMockFixture fix;
I2CComposed composed_transfer;
CHECK_THROWS_AS(composed_transfer.add_read(0), I2CException&);
}
TEST_CASE("I2CComposed try to write empy vector throws")
{
CMockFixture fix;
I2CComposed composed_transfer;
CHECK_THROWS_AS(composed_transfer.add_write({}), I2CException&);
}
TEST_CASE("I2CComposed calls driver correctly")
{
CMockFixture fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0x47, 0x48, 0x49};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
// the write-read transaction with repeated start:
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_start_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_write_byte_ExpectAndReturn(&cmd_fix.dummy_handle, 0x47 << 1 | I2C_MASTER_READ, true, ESP_OK);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, 2, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CComposed composed_transfer;
composed_transfer.add_write({0x47, 0x48, 0x49});
composed_transfer.add_read(READ_SIZE);
vector<vector<uint8_t> > read_result = composed_transfer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47));
TEST_ASSERT_EQUAL(1, read_result.size());
TEST_ASSERT_EQUAL(READ_SIZE, read_result[0].size());
for (int i = 0; i < READ_SIZE; i++) {
TEST_ASSERT_EQUAL(READ_DATA[i], read_result[0][i]);
}
}
TEST_CASE("I2CWrite transfer calls driver correctly")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0xAB, 0xBA};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually write the data but for the tests it is enough for now
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
std::vector<uint8_t> WRITE_BYTES = {0xAB, 0xBA};
auto writer = make_shared<I2CWrite>(WRITE_BYTES);
master.transfer(I2CAddress(0x47), writer);
}
TEST_CASE("I2CMaster synchronous write")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0xAB, 0xBA};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually write the data but for the tests it is enough for now
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
std::vector<uint8_t> WRITE_BYTES = {0xAB, 0xBA};
master.sync_write(I2CAddress(0x47), WRITE_BYTES);
}
TEST_CASE("I2CMaster synchronous read")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_READ);
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, READ_SIZE, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
std::vector<uint8_t> result = master.sync_read(I2CAddress(0x47), READ_SIZE);
REQUIRE(result.size() == READ_SIZE);
CHECK(result[0] == 0xAB);
CHECK(result[1] == 0xBA);
}
TEST_CASE("I2CMaster syncronous transfer (read and write)")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
uint8_t expected_write [] = {0x47, 0x48, 0x49};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
// the write-read transaction with repeated start:
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_start_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_write_byte_ExpectAndReturn(&cmd_fix.dummy_handle, 0x47 << 1 | I2C_MASTER_READ, true, ESP_OK);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, 2, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(I2C_NUM_0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
vector<uint8_t> read_result = master.sync_transfer(I2CAddress(0x47), {0x47, 0x48, 0x49}, READ_SIZE);
CHECK(read_result.size() == READ_SIZE);
for (int i = 0; i < READ_SIZE; i++) {
CHECK(read_result[i] == READ_DATA[i]);
}
}
#if SOC_I2C_SUPPORT_SLAVE
TEST_CASE("I2CSlave parameter configuration fails")
{
CMockFixture fix;
i2c_param_config_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CSlave(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), I2CAddress(0x47), 64, 64), I2CException&);
}
TEST_CASE("I2CSlave driver installation fails")
{
CMockFixture fix;
i2c_param_config_IgnoreAndReturn(ESP_OK);
i2c_driver_install_IgnoreAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CSlave (I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), I2CAddress(0x47), 64, 64), I2CException&);
}
TEST_CASE("I2CSlave calls driver functions correctly")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::SUCCEED);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), I2CAddress(0x47), 64, 64);
}
TEST_CASE("I2CSlave write fails")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
const uint8_t WRITE_BUFFER[] = {0xAB, 0xCD};
const size_t WRITE_BUFFER_LEN = sizeof(WRITE_BUFFER);
i2c_slave_write_buffer_ExpectAnyArgsAndReturn(-1);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.write_raw(WRITE_BUFFER, WRITE_BUFFER_LEN, chrono::milliseconds(0)) == -1);
}
TEST_CASE("I2CSlave write calls driver functions correctly")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
const uint8_t WRITE_BUFFER[] = {0xAB, 0xCD};
const size_t WRITE_BUFFER_LEN = sizeof(WRITE_BUFFER);
i2c_slave_write_buffer_ExpectWithArrayAndReturn(0,
WRITE_BUFFER,
WRITE_BUFFER_LEN,
WRITE_BUFFER_LEN,
500 / portTICK_PERIOD_MS,
WRITE_BUFFER_LEN);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.write_raw(WRITE_BUFFER, WRITE_BUFFER_LEN, chrono::milliseconds(500)) == WRITE_BUFFER_LEN);
}
TEST_CASE("I2CSlave read fails")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
const size_t READ_BUFFER_LEN = 2;
uint8_t read_buffer[READ_BUFFER_LEN];
i2c_slave_read_buffer_ExpectAnyArgsAndReturn(-1);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.read_raw(read_buffer, READ_BUFFER_LEN, chrono::milliseconds(0)) == -1);
}
TEST_CASE("I2CSlave read calls driver functions correctly")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
uint8_t WRITE_BUFFER[] = {0xAB, 0xCD};
const size_t BUFFER_LEN = sizeof(WRITE_BUFFER);
uint8_t read_buffer[BUFFER_LEN];
i2c_slave_read_buffer_ExpectAndReturn(0, read_buffer, BUFFER_LEN, 500 / portTICK_PERIOD_MS, BUFFER_LEN);
i2c_slave_read_buffer_ReturnArrayThruPtr_data(WRITE_BUFFER, BUFFER_LEN);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.read_raw(read_buffer, BUFFER_LEN, chrono::milliseconds(500)) == BUFFER_LEN);
for (size_t i = 0; i < BUFFER_LEN; i++) {
CHECK(read_buffer[i] == WRITE_BUFFER[i]);
}
}
#endif // SOC_I2C_SUPPORT_SLAVE

View File

@ -1,10 +0,0 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.linux
@pytest.mark.host_test
def test_i2c_cxx(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=5)

View File

@ -1,3 +0,0 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@ -1,14 +0,0 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
# Overriding components which should be mocked
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
# Including experimental component here because it's outside IDF's main component directory
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_spi_cxx_host)

View File

@ -1,8 +0,0 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/host_spi_cxx_test.elf`

View File

@ -1,9 +0,0 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "spi_cxx_test.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/host_test/fixtures"
"${cpp_component}/private_include"
$ENV{IDF_PATH}/tools/catch
REQUIRES cmock driver experimental_cpp_component)

View File

@ -1,453 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*
* This test 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.
*/
#define CATCH_CONFIG_MAIN
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "spi_host_cxx.hpp"
#include "spi_host_private_cxx.hpp"
#include "system_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "host_test error";
}
using namespace std;
using namespace idf;
TEST_CASE("SPITransferSize basic construction")
{
SPITransferSize transfer_size_0(0);
CHECK(0 == transfer_size_0.get_value());
SPITransferSize transfer_size_1(47);
CHECK(47 == transfer_size_1.get_value());
SPITransferSize transfer_size_default = SPITransferSize::default_size();
CHECK(0 == transfer_size_default.get_value());
}
TEST_CASE("SPI gpio numbers work correctly")
{
GPIONum gpio_num_0(19);
MOSI mosi_0(18);
MOSI mosi_1(gpio_num_0.get_num());
MOSI mosi_2(mosi_0);
CHECK(mosi_0 != mosi_1);
CHECK(mosi_2 == mosi_0);
CHECK(mosi_2.get_num() == 18u);
}
TEST_CASE("SPI_DMAConfig valid")
{
CHECK(SPI_DMAConfig::AUTO().get_num() == spi_common_dma_t::SPI_DMA_CH_AUTO);
CHECK(SPI_DMAConfig::DISABLED().get_num() == spi_common_dma_t::SPI_DMA_DISABLED);
}
TEST_CASE("SPINum invalid argument")
{
CHECK_THROWS_AS(SPINum(-1), SPIException&);
uint32_t host_raw = spi_host_device_t::SPI_HOST_MAX;
CHECK_THROWS_AS(SPINum host(host_raw), SPIException&);
}
TEST_CASE("Master init failure")
{
CMockFixture cmock_fix;
spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&);
}
TEST_CASE("Master invalid state")
{
CMockFixture cmock_fix;
spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE);
CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&);
}
TEST_CASE("build master")
{
SPIFix fix;
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num));
}
TEST_CASE("build QSPI master")
{
QSPIFix fix;
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num),
QSPIWP(fix.bus_config.quadwp_io_num),
QSPIHD(fix.bus_config.quadhd_io_num));
}
TEST_CASE("Master build device")
{
SPIFix fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num));
master.create_dev(CS(4), Frequency::MHz(1));
}
TEST_CASE("SPIDeviceHandle throws on driver error")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::FAIL);
CHECK_THROWS_AS(SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)), SPIException&);
}
TEST_CASE("SPIDeviceHandle succeed")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
}
TEST_CASE("SPIDevice succeed")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
}
TEST_CASE("SPI transaction empty data throws")
{
CHECK_THROWS_AS(SPITransactionDescriptor transaction({}, reinterpret_cast<SPIDeviceHandle*>(4747)), SPIException&);
}
TEST_CASE("SPI transaction device handle nullptr throws")
{
CHECK_THROWS_AS(SPITransactionDescriptor transaction({47}, nullptr), SPIException&);
}
TEST_CASE("SPI transaction not started wait_for")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.wait_for(std::chrono::milliseconds(47)), SPITransferException&);
}
TEST_CASE("SPI transaction not started wait")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.wait(), SPITransferException&);
}
TEST_CASE("SPI transaction not started get")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.get(), SPITransferException&);
}
TEST_CASE("SPI transaction wait_for timeout")
{
CMockFixture cmock_fix;
SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT);
SPIDevFix dev_fix(CreateAnd::IGNORE);
spi_device_acquire_bus_IgnoreAndReturn(ESP_OK);
spi_device_release_bus_Ignore();
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
transaction.start();
CHECK(transaction.wait_for(std::chrono::milliseconds(47)) == false);
// We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the
// allocated transaction descriptor.
transaction_fix.get_transaction_return = ESP_OK;
spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK);
transaction.wait();
}
TEST_CASE("SPI transaction one byte")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix fix(1, true);
SPIDevFix dev_fix(CreateAnd::IGNORE);
fix.rx_data = {0xA6};
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
transaction.start();
auto out_data = transaction.get();
CHECK(1 * 8 == fix.orig_trans->length);
CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction two byte")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix fix(2, true);
SPIDevFix dev_fix(CreateAnd::IGNORE);
fix.rx_data = {0xA6, 0xA7};
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47, 48}, &handle);
transaction.start();
auto out_data = transaction.get();
CHECK(fix.size * 8 == fix.orig_trans->length);
CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]);
CHECK(48 == ((uint8_t*) fix.orig_trans->tx_buffer)[1]);
REQUIRE(out_data.begin() != out_data.end());
REQUIRE(out_data.size() == 2);
CHECK(0xA6 == out_data[0]);
CHECK(0xA7 == out_data[1]);
}
TEST_CASE("SPI transaction future")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
vector<uint8_t> out_data = result.get();
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction with pre_callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47}, [&] (void *user) { pre_cb_called = true; });
vector<uint8_t> out_data = result.get();
SPITransactionDescriptor *transaction = reinterpret_cast<SPITransactionDescriptor*>(trans_fix.orig_trans->user);
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction with post_callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool post_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47}, [&] (void *user) { }, [&] (void *user) { post_cb_called = true; });
vector<uint8_t> out_data = result.get();
dev_fix.dev_config.post_cb(trans_fix.orig_trans);
CHECK(true == post_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction data routed to pre callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47},
[&] (void *user) { *(static_cast<bool*>(user)) = true; },
[&] (void *user) { },
&pre_cb_called);
result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
}
TEST_CASE("SPI transaction data routed to post callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool post_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47},
[&] (void *user) { },
[&] (void *user) { *(static_cast<bool*>(user)) = true; },
&post_cb_called);
result.get();
dev_fix.dev_config.post_cb(trans_fix.orig_trans);
CHECK(true == post_cb_called);
}
TEST_CASE("SPI two transactions")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
std::function<void(void *)> pre_callback = [&] (void *user) {
pre_cb_called = true;
};
auto result = dev.transfer({47}, pre_callback);
vector<uint8_t> out_data = result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
// preparing the second transfer
pre_cb_called = false;
spi_device_acquire_bus_ExpectAndReturn(trans_fix.handle, portMAX_DELAY, ESP_OK);
spi_device_acquire_bus_IgnoreArg_device();
spi_device_queue_trans_ExpectAndReturn(trans_fix.handle, nullptr, 0, ESP_OK);
spi_device_queue_trans_IgnoreArg_trans_desc();
spi_device_queue_trans_IgnoreArg_handle();
spi_device_get_trans_result_ExpectAndReturn(trans_fix.handle, nullptr, portMAX_DELAY, ESP_OK);
spi_device_get_trans_result_IgnoreArg_trans_desc();
spi_device_get_trans_result_IgnoreArg_handle();
spi_device_release_bus_Ignore();
result = dev.transfer({47}, pre_callback);
result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
}
TEST_CASE("SPIFuture invalid after default construction")
{
SPIFuture future;
CHECK(false == future.valid());
}
TEST_CASE("SPIFuture valid")
{
CMOCK_SETUP();
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
shared_ptr<SPITransactionDescriptor> trans(new SPITransactionDescriptor(std::vector<uint8_t>(47), &handle));
SPIFuture future(trans);
CHECK(true == future.valid());
}
TEST_CASE("SPIFuture wait_for timeout")
{
CMockFixture cmock_fix;
SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT);
SPIDevFix dev_fix(CreateAnd::IGNORE);
spi_device_acquire_bus_IgnoreAndReturn(ESP_OK);
spi_device_release_bus_Ignore();
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
shared_ptr<SPITransactionDescriptor> transaction(new SPITransactionDescriptor(std::vector<uint8_t>(47), &handle));
SPIFuture future(transaction);
transaction->start();
CHECK(future.wait_for(std::chrono::milliseconds(47)) == std::future_status::timeout);
// We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the
// allocated transaction descriptor.
transaction_fix.get_transaction_return = ESP_OK;
spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK);
future.wait();
}
TEST_CASE("SPIFuture wait_for on SPIFuture")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true, 20);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
CHECK(result.wait_for(std::chrono::milliseconds(20)) == std::future_status::ready);
}
TEST_CASE("SPIFuture wait on SPIFuture")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
result.wait();
vector<uint8_t> out_data = result.get();
CHECK(out_data.size() == 1);
}

View File

@ -1,10 +0,0 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.linux
@pytest.mark.host_test
def test_spi_cxx(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=5)

View File

@ -1,3 +0,0 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@ -1,34 +0,0 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
idf_component_set_property(driver USE_MOCK 1)
# Overriding components which should be mocked
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
# Including experimental component here because it's outside IDF's main component directory
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_system_cxx_host)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
COMMAND lcov --capture --directory . --output-file coverage.info
COMMENT "Create coverage report"
)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
COMMAND genhtml coverage.info --output-directory coverage_report/
COMMENT "Turn coverage report into html-based visualization"
)
add_custom_target(coverage
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
DEPENDS "coverage_report/"
)

View File

@ -1,8 +0,0 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/system_cxx_host_test.elf`

View File

@ -1,12 +0,0 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "system_cxx_test.cpp"
"${cpp_component}/esp_exception.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/include"
$ENV{IDF_PATH}/tools/catch
REQUIRES driver)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage)
target_link_libraries(${COMPONENT_LIB} --coverage)

View File

@ -1,95 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* This test 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.
*/
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "system_cxx.hpp"
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "test";
}
using namespace std;
using namespace idf;
TEST_CASE("Frequency invalid")
{
CHECK_THROWS_AS(Frequency(0), ESPException&);
}
TEST_CASE("Frequency constructors correct")
{
Frequency f0(440);
CHECK(440 == f0.get_value());
Frequency f1 = Frequency::Hz(440);
CHECK(440 == f1.get_value());
Frequency f2 = Frequency::KHz(440);
CHECK(440000 == f2.get_value());
Frequency f3 = Frequency::MHz(440);
CHECK(440000000 == f3.get_value());
}
TEST_CASE("Frequency op ==")
{
Frequency f0(440);
Frequency f1(440);
CHECK(f1 == f0);
}
TEST_CASE("Frequency op !=")
{
Frequency f0(440);
Frequency f1(441);
CHECK(f1 != f0);
}
TEST_CASE("Frequency op >")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK(f1 > f0);
CHECK(!(f0 > f1));
CHECK(!(f0 > f2));
}
TEST_CASE("Frequency op <")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK(f0 < f1);
CHECK(!(f1 < f0));
CHECK(!(f0 < f2));
}
TEST_CASE("Frequency op >=")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK (f1 >= f0);
CHECK(!(f0 >= f1));
CHECK (f0 >= f2);
}
TEST_CASE("Frequency op <=")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK (f0 <= f1);
CHECK(!(f1 <= f0));
CHECK (f0 <= f2);
}

View File

@ -1,10 +0,0 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.linux
@pytest.mark.host_test
def test_system_cxx(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=5)

View File

@ -1,3 +0,0 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@ -1,305 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __cpp_exceptions
#include "driver/i2c.h"
#include "i2c_cxx.hpp"
using namespace std;
namespace idf {
#define I2C_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), I2CException)
/**
* I2C bus are defined in the header files, let's check that the values are correct
*/
#if SOC_I2C_NUM >= 2
static_assert(I2C_NUM_1 == 1, "I2C_NUM_1 must be equal to 1");
#endif // SOC_I2C_NUM >= 2
static_assert(I2C_NUM_MAX == SOC_I2C_NUM, "I2C_NUM_MAX must be equal to SOC_I2C_NUM");
namespace {
i2c_port_t i2c_num_to_driver_type(I2CNumber num) {
return static_cast<i2c_port_t>(num.get_num());
}
}
esp_err_t check_i2c_num(uint32_t i2c_num) noexcept
{
if (i2c_num >= I2C_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
esp_err_t check_i2c_addr(uint32_t addr) noexcept
{
// maximum I2C address currently supported in the C++ classes is 127
if (addr > 0x7f) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
I2CException::I2CException(esp_err_t error) : ESPException(error) { }
I2CTransferException::I2CTransferException(esp_err_t error) : I2CException(error) { }
uint32_t I2CNumber::get_num()
{
return get_value();
}
I2CAddress::I2CAddress(uint8_t addr) : StrongValueComparable<uint8_t> (addr)
{
esp_err_t error = check_i2c_addr(addr);
if (error != ESP_OK) {
throw I2CException(error);
}
}
uint8_t I2CAddress::get_addr()
{
return get_value();
}
I2CCommandLink::I2CCommandLink()
{
handle = i2c_cmd_link_create();
if (!handle) {
throw I2CException(ESP_ERR_NO_MEM);
}
}
I2CCommandLink::~I2CCommandLink()
{
i2c_cmd_link_delete(handle);
}
void I2CCommandLink::start()
{
I2C_CHECK_THROW(i2c_master_start(handle));
}
void I2CCommandLink::write(const std::vector<uint8_t> &bytes, bool expect_ack)
{
I2C_CHECK_THROW(i2c_master_write(handle, bytes.data(), bytes.size(), expect_ack));
}
void I2CCommandLink::write_byte(uint8_t byte, bool expect_ack)
{
I2C_CHECK_THROW(i2c_master_write_byte(handle, byte, expect_ack));
}
void I2CCommandLink::read(std::vector<uint8_t> &bytes)
{
I2C_CHECK_THROW(i2c_master_read(handle, bytes.data(), bytes.size(), I2C_MASTER_LAST_NACK));
}
void I2CCommandLink::stop()
{
I2C_CHECK_THROW(i2c_master_stop(handle));
}
void I2CCommandLink::execute_transfer(I2CNumber i2c_num, chrono::milliseconds driver_timeout)
{
esp_err_t err = i2c_master_cmd_begin(i2c_num_to_driver_type(i2c_num), handle, driver_timeout.count() / portTICK_PERIOD_MS);
if (err != ESP_OK) {
throw I2CTransferException(err);
}
}
I2CBus::I2CBus(I2CNumber i2c_number) : i2c_num(std::move(i2c_number)) { }
I2CBus::~I2CBus() { }
I2CMaster::I2CMaster(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
Frequency clock_speed,
bool scl_pullup,
bool sda_pullup)
: I2CBus(std::move(i2c_number))
{
i2c_config_t conf = {};
conf.mode = I2C_MODE_MASTER;
conf.scl_io_num = scl_gpio.get_num();
conf.scl_pullup_en = scl_pullup;
conf.sda_io_num = sda_gpio.get_num();
conf.sda_pullup_en = sda_pullup;
conf.master.clk_speed = clock_speed.get_value();
I2C_CHECK_THROW(i2c_param_config(i2c_num_to_driver_type(i2c_num), &conf));
I2C_CHECK_THROW(i2c_driver_install(i2c_num_to_driver_type(i2c_num), conf.mode, 0, 0, 0));
}
I2CMaster::~I2CMaster()
{
i2c_driver_delete(i2c_num_to_driver_type(i2c_num));
}
void I2CMaster::sync_write(I2CAddress i2c_addr, const vector<uint8_t> &data)
{
I2CWrite writer(data);
writer.do_transfer(i2c_num, i2c_addr);
}
std::vector<uint8_t> I2CMaster::sync_read(I2CAddress i2c_addr, size_t n_bytes)
{
I2CRead reader(n_bytes);
return reader.do_transfer(i2c_num, i2c_addr);
}
vector<uint8_t> I2CMaster::sync_transfer(I2CAddress i2c_addr,
const std::vector<uint8_t> &write_data,
size_t read_n_bytes)
{
I2CComposed composed_transfer;
composed_transfer.add_write(write_data);
composed_transfer.add_read(read_n_bytes);
return composed_transfer.do_transfer(i2c_num, i2c_addr)[0];
}
#if CONFIG_SOC_I2C_SUPPORT_SLAVE
I2CSlave::I2CSlave(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
I2CAddress slave_addr,
size_t rx_buf_len,
size_t tx_buf_len,
bool scl_pullup,
bool sda_pullup)
: I2CBus(std::move(i2c_number))
{
i2c_config_t conf = {};
conf.mode = I2C_MODE_SLAVE;
conf.scl_io_num = scl_gpio.get_value();
conf.scl_pullup_en = scl_pullup;
conf.sda_io_num = sda_gpio.get_value();
conf.sda_pullup_en = sda_pullup;
conf.slave.addr_10bit_en = 0;
conf.slave.slave_addr = slave_addr.get_addr();
I2C_CHECK_THROW(i2c_param_config(i2c_num_to_driver_type(i2c_num), &conf));
I2C_CHECK_THROW(i2c_driver_install(i2c_num_to_driver_type(i2c_num), conf.mode, rx_buf_len, tx_buf_len, 0));
}
I2CSlave::~I2CSlave()
{
i2c_driver_delete(i2c_num_to_driver_type(i2c_num));
}
int I2CSlave::write_raw(const uint8_t *data, size_t data_len, chrono::milliseconds timeout)
{
return i2c_slave_write_buffer(i2c_num_to_driver_type(i2c_num), data, data_len, (TickType_t) timeout.count() / portTICK_PERIOD_MS);
}
int I2CSlave::read_raw(uint8_t *buffer, size_t buffer_len, chrono::milliseconds timeout)
{
return i2c_slave_read_buffer(i2c_num_to_driver_type(i2c_num), buffer, buffer_len, (TickType_t) timeout.count() / portTICK_PERIOD_MS);
}
#endif // CONFIG_SOC_I2C_SUPPORT_SLAVE
I2CWrite::I2CWrite(const vector<uint8_t> &bytes, chrono::milliseconds driver_timeout)
: I2CTransfer<void>(driver_timeout), bytes(bytes)
{
if (bytes.empty()) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
}
void I2CWrite::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.start();
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_WRITE);
handle.write(bytes);
}
void I2CWrite::process_result() { }
I2CRead::I2CRead(size_t size, chrono::milliseconds driver_timeout)
: I2CTransfer<vector<uint8_t> >(driver_timeout), bytes(size)
{
if (size == 0) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
}
void I2CRead::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.start();
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_READ);
handle.read(bytes);
}
vector<uint8_t> I2CRead::process_result()
{
return bytes;
}
I2CComposed::I2CComposed(chrono::milliseconds driver_timeout)
: I2CTransfer<vector<vector<uint8_t> > >(driver_timeout), transfer_list() { }
void I2CComposed::CompTransferNodeRead::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_READ);
handle.read(bytes);
}
void I2CComposed::CompTransferNodeRead::process_result(std::vector<std::vector<uint8_t> > &read_results)
{
read_results.push_back(bytes);
}
void I2CComposed::CompTransferNodeWrite::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_WRITE);
handle.write(bytes);
}
void I2CComposed::add_read(size_t size)
{
if (!size) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
transfer_list.push_back(make_shared<CompTransferNodeRead>(size));
}
void I2CComposed::add_write(std::vector<uint8_t> bytes)
{
if (bytes.empty()) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
transfer_list.push_back(make_shared<CompTransferNodeWrite>(bytes));
}
void I2CComposed::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
for (auto it = transfer_list.begin(); it != transfer_list.end(); it++) {
handle.start();
(*it)->queue_cmd(handle, i2c_addr);
}
}
std::vector<std::vector<uint8_t> > I2CComposed::process_result()
{
std::vector<std::vector<uint8_t> > results;
for (auto it = transfer_list.begin(); it != transfer_list.end(); it++) {
(*it)->process_result(results);
}
return results;
}
} // idf
#endif // __cpp_exceptions

View File

@ -1,122 +0,0 @@
#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

@ -1,463 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#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) { }
ESPEventID(const ESPEventID &rhs) : id(rhs.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

@ -1,66 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cpp_exceptions
#include "esp_err.h"
#include <exception>
namespace idf {
/**
* @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.
*/
class ESPException : public std::exception {
public:
/**
* @param error Error from underlying IDF functions.
*/
ESPException(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;
};
/**
* Convenience macro to help converting IDF error codes into ESPException.
*/
#define CHECK_THROW(error_) \
do { \
esp_err_t result = error_; \
if (result != ESP_OK) throw idf::ESPException(result); \
} while (0)
/**
* Convenience macro to help converting IDF error codes into a child of ESPException.
*/
#define CHECK_THROW_SPECIFIC(error_, exception_type_) \
do { \
esp_err_t result = (error_); \
if (result != ESP_OK) throw idf::exception_type_(result); \
} while (0)
} // namespace idf
#endif // __cpp_exceptions

View File

@ -1,145 +0,0 @@
// Copyright 2020 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.
#pragma once
#ifdef __cpp_exceptions
#include <chrono>
#include <functional>
#include <string>
#include "esp_exception.hpp"
#include "esp_timer.h"
namespace idf {
namespace esp_timer {
/**
* @brief Get time since boot
* @return time since \c esp_timer_init() was called (this normally happens early during application startup).
*/
static inline std::chrono::microseconds get_time()
{
return std::chrono::microseconds(esp_timer_get_time());
}
/**
* @brief Get the timestamp when the next timeout is expected to occur
* @return Timestamp of the nearest timer event.
* The timebase is the same as for the values returned by \c get_time().
*/
static inline std::chrono::microseconds get_next_alarm()
{
return std::chrono::microseconds(esp_timer_get_next_alarm());
}
/**
* @brief
* A timer using the esp_timer component which can be started either as one-shot timer or periodically.
*/
class ESPTimer {
public:
/**
* @param timeout_cb The timeout callback.
* @param timer_name The name of the timer (optional). This is for debugging using \c esp_timer_dump().
*/
ESPTimer(std::function<void()> timeout_cb, const std::string &timer_name = "ESPTimer");
/**
* Stop the timer if necessary and delete it.
*/
~ESPTimer();
/**
* Default copy constructor is deleted since one instance of esp_timer_handle_t must not be shared.
*/
ESPTimer(const ESPTimer&) = delete;
/**
* Default copy assignment is deleted since one instance of esp_timer_handle_t must not be shared.
*/
ESPTimer &operator=(const ESPTimer&) = delete;
/**
* @brief Start one-shot timer
*
* Timer should not be running (started) when this function is called.
*
* @param timeout timer timeout, in microseconds relative to the current moment.
*
* @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running.
*/
inline void start(std::chrono::microseconds timeout)
{
CHECK_THROW(esp_timer_start_once(timer_handle, timeout.count()));
}
/**
* @brief Start periodic timer
*
* Timer should not be running when this function is called. This function will
* start a timer which will trigger every 'period' microseconds.
*
* Timer should not be running (started) when this function is called.
*
* @param timeout timer timeout, in microseconds relative to the current moment.
*
* @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running.
*/
inline void start_periodic(std::chrono::microseconds period)
{
CHECK_THROW(esp_timer_start_periodic(timer_handle, period.count()));
}
/**
* @brief Stop the previously started timer.
*
* This function stops the timer previously started using \c start() or \c start_periodic().
*
* @throws ESPException with error ESP_ERR_INVALID_STATE if the timer has not been started yet.
*/
inline void stop()
{
CHECK_THROW(esp_timer_stop(timer_handle));
}
private:
/**
* Internal callback to hook into esp_timer component.
*/
static void esp_timer_cb(void *arg);
/**
* Timer instance of the underlying esp_event component.
*/
esp_timer_handle_t timer_handle;
/**
* Callback which will be called once the timer triggers.
*/
std::function<void()> timeout_cb;
/**
* Name of the timer, will be passed to the underlying timer framework and is used for debugging.
*/
const std::string name;
};
} // esp_timer
} // idf
#endif // __cpp_exceptions

View File

@ -1,402 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include "esp_exception.hpp"
#include "system_cxx.hpp"
namespace idf {
/**
* @brief Exception thrown for errors in the GPIO C++ API.
*/
struct GPIOException : public ESPException {
/**
* @param error The IDF error representing the error class of the error to throw.
*/
GPIOException(esp_err_t error);
};
/**
* Check if the numeric pin number is valid on the current hardware.
*/
esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept;
/**
* Check if the numeric value of a drive strength is valid on the current hardware.
*/
esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept;
/**
* This is a "Strong Value Type" class for GPIO. The GPIO pin number is checked during construction according to
* the hardware capabilities. This means that any GPIONumBase object is guaranteed to contain a valid GPIO number.
* See also the template class \c StrongValue.
*/
template<typename GPIONumFinalType>
class GPIONumBase final : public StrongValueComparable<uint32_t> {
public:
/**
* @brief Create a numerical pin number representation and make sure it's correct.
*
* @throw GPIOException if the number does not reflect a valid GPIO number on the current hardware.
*/
explicit GPIONumBase(uint32_t pin) : StrongValueComparable<uint32_t>(pin)
{
esp_err_t pin_check_result = check_gpio_pin_num(pin);
if (pin_check_result != ESP_OK) {
throw GPIOException(pin_check_result);
}
}
using StrongValueComparable<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::operator!=;
/**
* Retrieves the valid numerical representation of the GPIO number.
*/
uint32_t get_num() const { return get_value(); };
};
/**
* This is a TAG type whose sole purpose is to create a distinct type from GPIONumBase.
*/
class GPIONumType;
/**
* A GPIO number type used for general GPIOs, in contrast to specific GPIO pins like e.g. SPI_SCLK.
*/
using GPIONum = GPIONumBase<class GPIONumType>;
/**
* Level of an input GPIO.
*/
enum class GPIOLevel {
HIGH,
LOW
};
/**
* Represents a valid pull up configuration for GPIOs.
* It is supposed to resemble an enum type, hence it has static creation methods and a private constructor.
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
*/
class GPIOPullMode final : public StrongValueComparable<uint32_t> {
private:
/**
* Constructor is private since it should only be accessed by the static creation methods.
*
* @param pull_mode A valid numerical respresentation of the pull up configuration. Must be valid!
*/
explicit GPIOPullMode(uint32_t pull_mode) : StrongValueComparable<uint32_t>(pull_mode) { }
public:
/**
* Create a representation of a floating pin configuration.
* For more information, check the driver and HAL files.
*/
static GPIOPullMode FLOATING();
/**
* Create a representation of a pullup configuration.
* For more information, check the driver and HAL files.
*/
static GPIOPullMode PULLUP();
/**
* Create a representation of a pulldown configuration.
* For more information, check the driver and HAL files.
*/
static GPIOPullMode PULLDOWN();
using StrongValueComparable<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::operator!=;
/**
* Retrieves the valid numerical representation of the pull mode.
*/
uint32_t get_pull_mode() const { return get_value(); };
};
/**
* @brief Represents a valid wakup interrupt type for GPIO inputs.
*
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
* It is supposed to resemble an enum type, hence it has static creation methods and a private constructor.
* For a detailed mapping of interrupt types to numeric values, please refer to the driver types and implementation.
*/
class GPIOWakeupIntrType final: public StrongValueComparable<uint32_t> {
private:
/**
* Constructor is private since it should only be accessed by the static creation methods.
*
* @param pull_mode A valid numerical respresentation of a possible interrupt level to wake up. Must be valid!
*/
explicit GPIOWakeupIntrType(uint32_t interrupt_level) : StrongValueComparable<uint32_t>(interrupt_level) { }
public:
static GPIOWakeupIntrType LOW_LEVEL();
static GPIOWakeupIntrType HIGH_LEVEL();
/**
* Retrieves the valid numerical representation of the pull mode.
*/
uint32_t get_level() const noexcept { return get_value(); };
};
/**
* Class representing a valid drive strength for GPIO outputs.
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
* For a detailed mapping for values to drive strengths, please refer to the datasheet of the chip you are using.
* E.g. for ESP32, the values in general are the following:
* - WEAK: 5mA
* - STRONGER: 10mA
* - DEFAULT/MEDIUM: 20mA
* - STRONGEST: 40mA
*/
class GPIODriveStrength final : public StrongValueComparable<uint32_t> {
public:
/**
* @brief Create a drive strength representation and checks its validity.
*
* After construction, this class should have a guaranteed valid strength representation.
*
* @param strength the numeric value mapping for a particular strength. For possible ranges, look at the
* static creation functions below.
* @throws GPIOException if the supplied number is out of the hardware capable range.
*/
explicit GPIODriveStrength(uint32_t strength) : StrongValueComparable<uint32_t>(strength)
{
esp_err_t strength_check_result = check_gpio_drive_strength(strength);
if (strength_check_result != ESP_OK) {
throw GPIOException(strength_check_result);
}
}
/**
* Create a representation of the default drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength DEFAULT();
/**
* Create a representation of the weak drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength WEAK();
/**
* Create a representation of the less weak drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength LESS_WEAK();
/**
* Create a representation of the medium drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength MEDIUM();
/**
* Create a representation of the strong drive strength.
*/
static GPIODriveStrength STRONGEST();
using StrongValueComparable<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::operator!=;
/**
* Retrieves the valid numerical representation of the drive strength.
*/
uint32_t get_strength() const { return get_value(); };
};
/**
* @brief Implementations commonly used functionality for all GPIO configurations.
*
* Some functionality is only for specific configurations (set and get drive strength) but is necessary here
* to avoid complicating the inheritance hierarchy of the GPIO classes.
* Child classes implementing any GPIO configuration (output, input, etc.) are meant to intherit from this class
* and possibly make some of the functionality publicly available.
*/
class GPIOBase {
protected:
/**
* @brief Construct a GPIO.
*
* This constructor will only reset the GPIO but leaves the actual configuration (input, output, etc.) to
* the sub class.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIOBase(GPIONum num);
/**
* @brief Enable gpio pad hold function.
*
* The gpio pad hold function works in both input and output modes, but must be output-capable gpios.
* If pad hold enabled:
* in output mode: the output level of the pad will be force locked and can not be changed.
* in input mode: the input value read will not change, regardless the changes of input signal.
*
* @throws GPIOException if the underlying driver function fails.
*/
void hold_en();
/**
* @brief Disable gpio pad hold function.
*
* @throws GPIOException if the underlying driver function fails.
*/
void hold_dis();
/**
* @brief Configure the drive strength of the GPIO.
*
* @param strength The drive strength. Refer to \c GPIODriveStrength for more details.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_drive_strength(GPIODriveStrength strength);
/**
* @brief Return the current drive strength of the GPIO.
*
* @return The currently configured drive strength. Refer to \c GPIODriveStrength for more details.
*
* @throws GPIOException if the underlying driver function fails.
*/
GPIODriveStrength get_drive_strength();
/**
* @brief The number of the configured GPIO pin.
*/
GPIONum gpio_num;
};
/**
* @brief This class represents a GPIO which is configured as output.
*/
class GPIO_Output : public GPIOBase {
public:
/**
* @brief Construct and configure a GPIO as output.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIO_Output(GPIONum num);
/**
* @brief Set GPIO to high level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_high();
/**
* @brief Set GPIO to low level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_low();
using GPIOBase::set_drive_strength;
using GPIOBase::get_drive_strength;
};
/**
* @brief This class represents a GPIO which is configured as input.
*/
class GPIOInput : public GPIOBase {
public:
/**
* @brief Construct and configure a GPIO as input.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIOInput(GPIONum num);
/**
* @brief Read the current level of the GPIO.
*
* @return The GPIO current level of the GPIO.
*/
GPIOLevel get_level() const noexcept;
/**
* @brief Configure the internal pull-up and pull-down restors.
*
* @param mode The pull-up/pull-down configuration see \c GPIOPullMode.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_pull_mode(GPIOPullMode mode);
/**
* @brief Configure the pin as wake up pin.
*
* @throws GPIOException if the underlying driver function fails.
*/
void wakeup_enable(GPIOWakeupIntrType interrupt_type);
/**
* @brief Disable wake up functionality for this pin if it was enabled before.
*
* @throws GPIOException if the underlying driver function fails.
*/
void wakeup_disable();
};
/**
* @brief This class represents a GPIO which is configured as open drain output and input at the same time.
*
* This class facilitates bit-banging for single wire protocols.
*/
class GPIO_OpenDrain : public GPIOInput {
public:
/**
* @brief Construct and configure a GPIO as open drain output as well as input.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIO_OpenDrain(GPIONum num);
/**
* @brief Set GPIO to floating level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_floating();
/**
* @brief Set GPIO to low level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_low();
using GPIOBase::set_drive_strength;
using GPIOBase::get_drive_strength;
};
}
#endif

View File

@ -1,623 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifndef __cpp_exceptions
#error I2C class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
#endif
#include <exception>
#include <memory>
#include <chrono>
#include <vector>
#include <list>
#include <future>
#include "sdkconfig.h"
#include "esp_exception.hpp"
#include "system_cxx.hpp"
#include "gpio_cxx.hpp"
namespace idf {
/**
* @brief Check if the provided numerical value is a valid I2C address.
*
* @param addr raw number to be checked.
* @return ESP_OK if \c addr is a valid I2C address, otherwise ESP_ERR_INVALID_ARG.
*/
esp_err_t check_i2c_addr(uint32_t addr) noexcept;
struct I2CException : public ESPException {
I2CException(esp_err_t error);
};
struct I2CTransferException : public I2CException {
I2CTransferException(esp_err_t error);
};
/**
* @brief Represents a valid SDA signal pin number.
*/
class SDA_type;
using SDA_GPIO = GPIONumBase<class SDA_type>;
/**
* @brief Represents a valid SCL signal pin number.
*/
class SCL_type;
using SCL_GPIO = GPIONumBase<class SCL_type>;
/**
* @brief Valid representation of I2C number.
*
* A chip can have multiple I2C interfaces, each identified by a bus number, subsequently called I2C number.
* Instances of this class are guaranteed to always contain a valid I2C number.
*/
class I2CNumber : public StrongValueComparable<uint32_t> {
/**
* Construct a valid representation of the I2C number.
*
* This constructor is private because the it can only be accessed but the static creation methods below.
* This guarantees that an instance of I2CNumber always carries a valid number.
*/
constexpr explicit I2CNumber(uint32_t number) : StrongValueComparable<uint32_t>(number) { }
public:
/**
* @brief create an I2C number representing the first I2C bus of the chip.
*/
constexpr static I2CNumber I2C0() {
return I2CNumber(0);
}
#if CONFIG_SOC_I2C_NUM == 2
/**
* @brief create an I2C number representing the second I2C bus of the chip.
*/
constexpr static I2CNumber I2C1() {
return I2CNumber(1);
}
#endif
/**
* Retrieves the valid numerical representation of the I2C number.
*/
uint32_t get_num();
};
/**
* @brief Valid representation of I2C address.
*
* Instances of this class are guaranteed to always contain a valid I2C address.
*/
class I2CAddress : public StrongValueComparable<uint8_t> {
public:
/**
*
*/
explicit I2CAddress(uint8_t addr);
/**
* Retrieves the valid numerical representation of the I2C adress.
*/
uint8_t get_addr();
};
/**
* @brief Low-level I2C transaction descriptor
*
* This class records and decribes a low-level transaction. Users use the methods (except \c execute_transfer)
* to record the transaction. Afterwards, the transaction will be executed by calling \c execute_transfer,
* which blocks until the transaction is finished.
*
* @note This is a low-level class, which only exists due to the underlying I2C driver. All data referenced in
* read and write calls must not be changed and must stay allocated until at least \c execute_transfer
* has finished.
*/
class I2CCommandLink {
public:
/**
* @brief Allocate and create the transaction descriptor.
*/
I2CCommandLink();
/**
* @brief Delete the transaction descriptor, de-allocate all resources.
*/
~I2CCommandLink();
I2CCommandLink(const I2CCommandLink&) = delete;
I2CCommandLink operator=(const I2CCommandLink&) = delete;
/**
* @brief Record a start signal on the I2C bus.
*/
void start();
/**
* @brief Record a write of the vector \c bytes on the I2C bus.
*
* @param[in] bytes The data to be written. Must stay allocated until execute_transfer has finished or
* destructor of this class has been called.
* @param[in] expect_ack If acknowledgement shall be requested after each written byte, pass true,
* otherwise false.
*/
void write(const std::vector<uint8_t> &bytes, bool expect_ack = true);
/**
* @brief Record a one-byte-write on the I2C bus.
*
* @param[in] byte The data to be written. No restrictions apply.
* @param[in] expect_ack If acknowledgement shall be requested after writing the byte, pass true,
* otherwise false.
*/
void write_byte(uint8_t byte, bool expect_ack = true);
/**
* @brief Record a read of the size of vector \c bytes on the I2C bus.
*
* @param[in] bytes Vector with the size of the data to be read (in bytes). Must stay allocated until
* execute_transfer has finished or destructor of this class has been called.
* @param[in] expect_ack If acknowledgement shall be requested after each written byte, pass true,
* otherwise false.
*/
void read(std::vector<uint8_t> &bytes);
/**
* @brief Record a stop command on the I2C bus.
*/
void stop();
/**
* @brief Execute the transaction and wait until it has finished.
*
* This method will issue the transaction with the operations in the order in which they have been recorded
* before.
*
* @param i2c_num I2C bus number on the chip.
* @param driver_timeout Timeout for this transaction.
*/
void execute_transfer(I2CNumber i2c_num, std::chrono::milliseconds driver_timeout);
private:
/**
* @brief Internal driver data.
*/
void *handle;
};
/**
* Superclass for all transfer objects which are accepted by \c I2CMaster::transfer().
*/
template<typename TReturn>
class I2CTransfer {
public:
/**
* Helper typedef to facilitate type resolution during calls to I2CMaster::transfer().
*/
typedef TReturn TransferReturnT;
/**
* @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
*/
I2CTransfer(std::chrono::milliseconds driver_timeout_arg = std::chrono::milliseconds(1000));
virtual ~I2CTransfer() { }
/**
* Do all general parts of the I2C transfer:
* - initialize the command link
* - issuing a start to the command link queue
* - calling \c queue_cmd() in the subclass to issue specific commands to the command link queue
* - issuing a stop to the command link queue
* - executing the assembled commands on the I2C bus
* - calling \c process_result() to process the results of the commands or calling process_exception() if
* there was an exception
* - deleting the command link
* This method is normally called by I2CMaster, but can also be used stand-alone if the bus corresponding to
* \c i2c_num has be initialized.
*
* @throws I2CException for any particular I2C error
*/
TReturn do_transfer(I2CNumber i2c_num, I2CAddress i2c_addr);
protected:
/**
* Implementation of the I2C command is implemented by subclasses.
* The I2C command handle is initialized already at this stage.
* The first action is issuing the I2C address and the read/write bit, depending on what the subclass implements.
* On error, this method has to throw an instance of I2CException.
*
* @param handle the initialized command handle of the I2C driver.
* @param i2c_addr The slave's I2C address.
*
* @throw I2CException
*/
virtual void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) = 0;
/**
* Implementation of whatever neccessary action after successfully sending the I2C command.
* On error, this method has to throw an instance of I2CException.
*
* @throw I2CException
*/
virtual TReturn process_result() = 0;
/**
* For some calls to the underlying driver (e.g. \c i2c_master_cmd_begin() ), this general timeout will be passed.
*/
std::chrono::milliseconds driver_timeout;
};
/**
* @brief Super class for any I2C master or slave
*/
class I2CBus {
public:
/*
* @brief Initialize I2C master bus.
*
* Initialize and install the bus driver in master mode.
*
* @param i2c_number The I2C port number.
*/
explicit I2CBus(I2CNumber i2c_number);
/**
* @brief uninstall the bus driver.
*/
virtual ~I2CBus();
/**
* The I2C port number.
*/
const I2CNumber i2c_num;
};
/**
* @brief Simple I2C Master object
*
* This class provides to ways to issue I2C read and write requests. The simplest way is to use \c sync_write() and
* sync_read() to write and read, respectively. As the name suggests, they block during the whole transfer.
* For all asynchrounous transfers as well as combined write-read transfers, use \c transfer().
*/
class I2CMaster : public I2CBus {
public:
/**
* Initialize and install the driver of an I2C master peripheral.
*
* Initialize and install the bus driver in master mode. Pullups will be enabled for both pins. If you want a
* different configuration, use configure() and i2c_set_pin() of the underlying driver to disable one or both
* pullups.
*
* @param i2c_number The number of the I2C device.
* @param scl_gpio GPIO number of the SCL line.
* @param sda_gpio GPIO number of the SDA line.
* @param clock_speed The master clock speed.
* @param scl_pullup Enable SCL pullup.
* @param sda_pullup Enable SDA pullup.
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
*/
explicit I2CMaster(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
Frequency clock_speed,
bool scl_pullup = true,
bool sda_pullup = true);
/**
* Delete the driver.
*/
virtual ~I2CMaster();
/**
* Issue an asynchronous I2C transfer which is executed in the background.
*
* This method uses a C++ \c std::future as mechanism to wait for the asynchronous return value.
* The return value can be accessed with \c future::get(). \c future::get() also synchronizes with the thread
* doing the work in the background, i.e. it waits until the return value has been issued.
*
* The actual implementation is delegated to the TransferT object. It will be given the I2C number to work
* with.
*
* Requirements for TransferT: It should implement or imitate the interface of I2CTransfer.
*
* @param xfer The transfer to execute. What the transfer does, depends on it's implementation in
* \c TransferT::do_transfer(). It also determines the future template of this function, indicated by
* \c TransferT::TransferReturnT.
*
* @param i2c_addr The address of the I2C slave device targeted by the transfer.
*
* @return A future with \c TransferT::TransferReturnT. It depends on which template type is used for xfer.
* In case of a simple write (I2CWrite), it's future<void>.
* In case of a read (I2CRead), it's future<vector<uint8_t> > corresponding to the length of the read
* operation.
* If TransferT is a combined transfer with repeated reads (I2CComposed), then the return type is
* future<vector<vector<uint8_t> > >, a vector of results corresponding to the queued read operations.
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
template<typename TransferT>
std::future<typename TransferT::TransferReturnT> transfer(I2CAddress i2c_addr, std::shared_ptr<TransferT> xfer);
/**
* Do a synchronous write.
*
* All data in data will be written to the I2C device with i2c_addr at once.
* This method will block until the I2C write is complete.
*
* @param i2c_addr The address of the I2C device to which the data shall be sent.
* @param data The data to send (size to be sent is determined by data.size()).
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
void sync_write(I2CAddress i2c_addr, const std::vector<uint8_t> &data);
/**
* Do a synchronous read.
* This method will block until the I2C read is complete.
*
* n_bytes bytes of data will be read from the I2C device with i2c_addr.
* While reading the last byte, the master finishes the reading by sending a NACK, before issuing a stop.
*
* @param i2c_addr The address of the I2C device from which to read.
* @param n_bytes The number of bytes to read.
*
* @return the read bytes
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
std::vector<uint8_t> sync_read(I2CAddress i2c_addr, size_t n_bytes);
/**
* Do a simple synchronous write-read transfer.
*
* First, \c write_data will be written to the bus, then a number of \c read_n_bytes will be read from the bus
* with a repeated start condition. The slave device is determined by \c i2c_addr.
* While reading the last byte, the master finishes the reading by sending a NACK, before issuing a stop.
* This method will block until the I2C transfer is complete.
*
* @param i2c_addr The address of the I2C device from which to read.
* @param write_data The data to write to the bus before reading.
* @param read_n_bytes The number of bytes to read.
*
* @return the read bytes
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
std::vector<uint8_t> sync_transfer(I2CAddress i2c_addr,
const std::vector<uint8_t> &write_data,
size_t read_n_bytes);
};
#if CONFIG_SOC_I2C_SUPPORT_SLAVE
/**
* @brief Responsible for initialization and de-initialization of an I2C slave peripheral.
*/
class I2CSlave : public I2CBus {
public:
/**
* Initialize and install the driver of an I2C slave peripheral.
*
* Initialize and install the bus driver in slave mode. Pullups will be enabled for both pins. If you want a
* different configuration, use configure() and i2c_set_pin() of the underlying driver to disable one or both
* pullups.
*
* @param i2c_number The number of the I2C device.
* @param scl_gpio GPIO number of the SCL line.
* @param sda_gpio GPIO number of the SDA line.
* @param slave_addr The address of the slave device on the I2C bus.
* @param rx_buf_len Receive buffer length.
* @param tx_buf_len Transmit buffer length.
* @param scl_pullup Enable SCL pullup.
* @param sda_pullup Enable SDA pullup.
*
* @throws
*/
I2CSlave(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
I2CAddress slave_addr,
size_t rx_buf_len,
size_t tx_buf_len,
bool scl_pullup = true,
bool sda_pullup = true);
/**
* Delete the driver.
*/
virtual ~I2CSlave();
/**
* Schedule a raw data write once master is ready.
*
* The data is saved in a buffer, waiting for the master to pick it up.
*/
virtual int write_raw(const uint8_t* data, size_t data_len, std::chrono::milliseconds timeout);
/**
* Read raw data from the bus.
*
* The data is read directly from the buffer. Hence, it has to be written already by master.
*/
virtual int read_raw(uint8_t* buffer, size_t buffer_len, std::chrono::milliseconds timeout);
};
#endif // CONFIG_SOC_I2C_SUPPORT_SLAVE
/**
* Implementation for simple I2C writes, which can be executed by \c I2CMaster::transfer().
* It stores the bytes to be written as a vector.
*/
class I2CWrite : public I2CTransfer<void> {
public:
/**
* @param bytes The bytes which should be written.
* @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
*/
I2CWrite(const std::vector<uint8_t> &bytes, std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
protected:
/**
* Write the address and set the read bit to 0 to issue the address and request a write.
* Then write the bytes.
*
* @param handle The initialized I2C command handle.
* @param i2c_addr The I2C address of the slave.
*/
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
/**
* Set the value of the promise to unblock any callers waiting on it.
*/
void process_result() override;
private:
/**
* The bytes to write.
*/
std::vector<uint8_t> bytes;
};
/**
* Implementation for simple I2C reads, which can be executed by \c I2CMaster::transfer().
* It stores the bytes to be read as a vector to be returned later via a future.
*/
class I2CRead : public I2CTransfer<std::vector<uint8_t> > {
public:
/**
* @param The number of bytes to read.
* @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
*/
I2CRead(size_t size, std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
protected:
/**
* Write the address and set the read bit to 1 to issue the address and request a read.
* Then read into bytes.
*
* @param handle The initialized I2C command handle.
* @param i2c_addr The I2C address of the slave.
*/
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
/**
* Set the return value of the promise to unblock any callers waiting on it.
*/
std::vector<uint8_t> process_result() override;
private:
/**
* The bytes to read.
*/
std::vector<uint8_t> bytes;
};
/**
* This kind of transfer uses repeated start conditions to chain transfers coherently.
* In particular, this can be used to chain multiple single write and read transfers into a single transfer with
* repeated starts as it is commonly done for I2C devices.
* The result is a vector of vectors representing the reads in the order of how they were added using add_read().
*/
class I2CComposed : public I2CTransfer<std::vector<std::vector<uint8_t> > > {
public:
I2CComposed(std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
/**
* Add a read to the chain.
*
* @param size The size of the read in bytes.
*/
void add_read(size_t size);
/**
* Add a write to the chain.
*
* @param bytes The bytes to write; size will be bytes.size()
*/
void add_write(std::vector<uint8_t> bytes);
protected:
/**
* Write all chained transfers, including a repeated start issue after each but the last transfer.
*
* @param handle The initialized I2C command handle.
* @param i2c_addr The I2C address of the slave.
*/
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
/**
* Creates the vector with the vectors from all reads.
*/
std::vector<std::vector<uint8_t> > process_result() override;
private:
class CompTransferNode {
public:
virtual ~CompTransferNode() { }
virtual void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) = 0;
virtual void process_result(std::vector<std::vector<uint8_t> > &read_results) { }
};
class CompTransferNodeRead : public CompTransferNode {
public:
CompTransferNodeRead(size_t size) : bytes(size) { }
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
void process_result(std::vector<std::vector<uint8_t> > &read_results) override;
private:
std::vector<uint8_t> bytes;
};
class CompTransferNodeWrite : public CompTransferNode {
public:
CompTransferNodeWrite(std::vector<uint8_t> bytes) : bytes(bytes) { }
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
private:
std::vector<uint8_t> bytes;
};
/**
* The chained transfers.
*/
std::list<std::shared_ptr<CompTransferNode> > transfer_list;
};
template<typename TReturn>
I2CTransfer<TReturn>::I2CTransfer(std::chrono::milliseconds driver_timeout_arg)
: driver_timeout(driver_timeout_arg) { }
template<typename TReturn>
TReturn I2CTransfer<TReturn>::do_transfer(I2CNumber i2c_num, I2CAddress i2c_addr)
{
I2CCommandLink cmd_link;
queue_cmd(cmd_link, i2c_addr);
cmd_link.stop();
cmd_link.execute_transfer(i2c_num, driver_timeout);
return process_result();
}
template<typename TransferT>
std::future<typename TransferT::TransferReturnT> I2CMaster::transfer(I2CAddress i2c_addr, std::shared_ptr<TransferT> xfer)
{
if (!xfer) throw I2CException(ESP_ERR_INVALID_ARG);
return std::async(std::launch::async, [this](std::shared_ptr<TransferT> xfer, I2CAddress i2c_addr) {
return xfer->do_transfer(i2c_num, i2c_addr);
}, xfer, i2c_addr);
}
} // idf

View File

@ -1,152 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include "esp_exception.hpp"
#include "gpio_cxx.hpp"
#include "system_cxx.hpp"
namespace idf {
/**
* @brief Exception which is thrown in the context of SPI C++ classes.
*/
struct SPIException : public ESPException {
SPIException(esp_err_t error);
};
/**
* @brief The maximum SPI transfer size in bytes.
*/
class SPITransferSize : public StrongValueOrdered<size_t> {
public:
/**
* @brief Create a valid SPI transfer size.
*
* @param transfer_size The raw transfer size in bytes.
*/
explicit SPITransferSize(size_t transfer_size) noexcept : StrongValueOrdered<size_t>(transfer_size) { }
static SPITransferSize default_size() {
return SPITransferSize(0);
}
};
/**
* @brief Check if the raw uint32_t spi number is in the range according to the hardware.
*/
esp_err_t check_spi_num(uint32_t spi_num) noexcept;
/**
* @brief Represents a valid SPI host number.
*
* ESP chips may have different independent SPI peripherals. This SPI number distinguishes between them.
*/
class SPINum : public StrongValueComparable<uint32_t> {
public:
/**
* @brief Create a valid SPI host number.
*
* @param host_id_raw The raw SPI host number.
*
* @throw SPIException if the passed SPI host number is incorrect.
*/
explicit SPINum(uint32_t host_id_raw) : StrongValueComparable<uint32_t>(host_id_raw)
{
esp_err_t spi_num_check_result = check_spi_num(host_id_raw);
if (spi_num_check_result != ESP_OK) {
throw SPIException(spi_num_check_result);
}
}
/**
* @brief Return the raw value of the SPI host.
*
* This should only be used when calling driver and other interfaces which don't support the C++ class.
*
* @return the raw value of the SPI host.
*/
uint32_t get_spi_num() const
{
return get_value();
}
};
/**
* @brief Represents a valid MOSI signal pin number.
*/
class MOSI_type;
using MOSI = GPIONumBase<class MOSI_type>;
/**
* @brief Represents a valid MISO signal pin number.
*/
class MISO_type;
using MISO = GPIONumBase<class MISO_type>;
/**
* @brief Represents a valid SCLK signal pin number.
*/
class SCLK_type;
using SCLK = GPIONumBase<class SCLK_type>;
/**
* @brief Represents a valid CS (chip select) signal pin number.
*/
class CS_type;
using CS = GPIONumBase<class CS_type>;
/**
* @brief Represents a valid QSPIWP signal pin number.
*/
class QSPIWP_type;
using QSPIWP = GPIONumBase<class QSPIWP_type>;
/**
* @brief Represents a valid QSPIHD signal pin number.
*/
class QSPIHD_type;
using QSPIHD = GPIONumBase<class QSPIHD_type>;
/**
* @brief Represents a valid SPI DMA configuration. Use it similar to an enum.
*/
class SPI_DMAConfig : public StrongValueComparable<uint32_t> {
/**
* Constructor is hidden to enforce object invariants.
* Use the static creation methods to create instances.
*/
explicit SPI_DMAConfig(uint32_t channel_num) : StrongValueComparable<uint32_t>(channel_num) { }
public:
/**
* @brief Create a configuration with DMA disabled.
*/
static SPI_DMAConfig DISABLED();
/**
* @brief Create a configuration where the driver allocates DMA.
*/
static SPI_DMAConfig AUTO();
/**
* @brief Return the raw value of the DMA configuration.
*
* This should only be used when calling driver and other interfaces which don't support the C++ class.
*
* @return the raw value of the DMA configuration.
*/
uint32_t get_num() const {
return get_value();
}
};
}
#endif

View File

@ -1,414 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include <exception>
#include <memory>
#include <chrono>
#include <vector>
#include <list>
#include <future>
#include "system_cxx.hpp"
#include "spi_cxx.hpp"
namespace idf {
/**
* @brief Exception which is thrown in the context of SPI Transactions.
*/
struct SPITransferException : public SPIException {
SPITransferException(esp_err_t error);
};
class SPIDevice;
class SPIDeviceHandle;
/**
* @brief Describes and encapsulates the transaction.
*
* @note This class is intended to be used internally by the SPI C++ classes, but not publicly.
* Furthermore, currently only one transaction per time can be handled. If you need to
* send several transactions in parallel, you need to build your own mechanism around a
* FreeRTOS task and a queue.
*/
class SPITransactionDescriptor {
friend class SPIDeviceHandle;
public:
/**
* @brief Create a SPITransactionDescriptor object, describing a full duplex transaction.
*
* @param data_to_send The data sent to the SPI device. It can be dummy data if a read-only
* transaction is intended. Its length determines the length of both write and read operation.
* @param handle to the internal driver handle
* @param pre_callback If non-empty, this callback will be called directly before the transaction.
* @param post_callback If non-empty, this callback will be called directly after the transaction.
* @param user_data optional data which will be accessible in the callbacks declared above
*/
SPITransactionDescriptor(const std::vector<uint8_t> &data_to_send,
SPIDeviceHandle *handle,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> post_callback = nullptr,
void* user_data = nullptr);
/**
* @brief Deinitialize and delete all data of the transaction.
*
* @note This destructor must not becalled before the transaction is finished by the driver.
*/
~SPITransactionDescriptor();
SPITransactionDescriptor(const SPITransactionDescriptor&) = delete;
SPITransactionDescriptor operator=(const SPITransactionDescriptor&) = delete;
/**
* @brief Queue the transaction asynchronously.
*/
void start();
/**
* @brief Synchronously (blocking) wait for the result and return the result data or throw an exception.
*
* @return The data read from the SPI device. Its length is the length of \c data_to_send passed in the
* constructor.
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
*/
std::vector<uint8_t> get();
/**
* @brief Wait until the asynchronous operation is done.
*
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
*/
void wait();
/**
* @brief Wait for a result of the transaction up to timeout ms.
*
* @param timeout Maximum timeout value for waiting
*
* @return true if result is available, false if wait timed out
*
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
*/
bool wait_for(const std::chrono::milliseconds &timeout);
private:
/**
* Private descriptor data.
*/
void *private_transaction_desc;
/**
* Private device data.
*/
SPIDeviceHandle *device_handle;
/**
* @brief If non-empty, this callback will be called directly before the transaction.
*/
std::function<void(void *)> pre_callback;
/**
* @brief If non-empty, this callback will be called directly after the transaction.
*/
std::function<void(void *)> post_callback;
/**
* Buffer in spi_transaction_t is const, so we have to declare it here because we want to
* allocate and delete it.
*/
uint8_t *tx_buffer;
/**
* @brief User data which will be provided in the callbacks.
*/
void *user_data;
/**
* Tells if data has been received, i.e. the transaction has finished and the result can be acquired.
*/
bool received_data;
/**
* Tells if the transaction has been initiated and is at least in-flight, if not finished.
*/
bool started;
};
/**
* @brief SPIFuture for asynchronous transaction results, mostly equivalent to std::future.
*
* This re-implementation is necessary as std::future is incompatible with the IDF SPI driver interface.
*/
class SPIFuture {
public:
/**
* @brief Create an invalid future.
*/
SPIFuture();
/**
* @brief Create a valid future with \c transaction as shared state.
*
* @param transaction the shared transaction state
*/
SPIFuture(std::shared_ptr<SPITransactionDescriptor> transaction);
SPIFuture(const SPIFuture &other) = delete;
/**
* @brief Move constructor as in std::future, leaves \c other invalid.
*
* @param other object to move from, will become invalid during this constructor
*/
SPIFuture(SPIFuture &&other) noexcept;
/**
* @brief Move assignment as in std::future, leaves \c other invalid.
*
* @param other object to move from, will become invalid during this constructor
* @return A reference to the newly created SPIFuture object
*/
SPIFuture &operator=(SPIFuture&& other) noexcept;
/**
* @brief Wait until the asynchronous operation is done and return the result or throw and exception.
*
* @throws std::future_error if this future is not valid.
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
* @return The result of the asynchronous SPI transaction.
*/
std::vector<uint8_t> get();
/**
* @brief Wait for a result up to timeout ms.
*
* @param timeout Maximum timeout value for waiting
*
* @return std::future_status::ready if result is available, std::future_status::timeout if wait timed out
*/
std::future_status wait_for(std::chrono::milliseconds timeout);
/**
* @brief Wait for a result indefinitely.
*/
void wait();
/**
* @return true if this future is valid, otherwise false.
*/
bool valid() const noexcept;
private:
/**
* The SPITransactionDescriptor, which is the shared state of this future.
*/
std::shared_ptr<SPITransactionDescriptor> transaction;
/**
* Indicates if this future is valid.
*/
bool is_valid;
};
/**
* @brief Represents an device on an initialized Master Bus.
*/
class SPIDevice {
public:
/**
* @brief Create and initialize a device on the master bus corresponding to spi_host.
*
* @param cs The pin number of the chip select signal for the device to create.
* @param spi_host the spi_host (bus) to which the device shall be attached.
* @param frequency The devices frequency. this frequency will be set during transactions to the device which will be
* created.
* @param transaction_queue_size The of the transaction queue of this device. This determines how many
* transactions can be queued at the same time. Currently, it is set to 1 since the
* implementation exclusively acquires the bus for each transaction. This may change in the future.
*/
SPIDevice(SPINum spi_host,
CS cs,
Frequency frequency = Frequency::MHz(1),
QueueSize transaction_queue_size = QueueSize(1u));
SPIDevice(const SPIDevice&) = delete;
SPIDevice operator=(const SPIDevice&) = delete;
/**
* @brief De-initializes and destroys the device.
*
* @warning Behavior is undefined if a device is destroyed while there is still an ongoing transaction
* from that device.
*/
~SPIDevice();
/**
* @brief Queue a transfer to this device.
*
* This method creates a full-duplex transfer to the device represented by the current instance of this class.
* It then queues that transfer and returns a "future" object. The future object will become ready once
* the transfer finishes.
*
* @param data_to_send Data which will be sent to the device. The length of the data determines the length
* of the full-deplex transfer. I.e., the same amount of bytes will be received from the device.
* @param pre_callback If non-empty, this callback will be called directly before the transaction.
* If empty, it will be ignored.
* @param post_callback If non-empty, this callback will be called directly after the transaction.
* If empty, it will be ignored.
* @param user_data This pointer will be sent to pre_callback and/or pre_callback, if any of them is non-empty.
*
* @return a future object which will become ready once the transfer has finished. See also \c SPIFuture.
*/
SPIFuture transfer(const std::vector<uint8_t> &data_to_send,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> post_callback = nullptr,
void* user_data = nullptr);
/**
* @brief Queue a transfer to this device like \c transfer, but using begin/end iterators instead of a
* data vector.
*
* This method is equivalent to \c transfer(), except for the parameters.
*
* @param begin Iterator to the begin of the data which will be sent to the device.
* @param end Iterator to the end of the data which will be sent to the device.
* This iterator determines the length of the data and hence the length of the full-deplex transfer.
* I.e., the same amount of bytes will be received from the device.
* @param pre_callback If non-empty, this callback will be called directly before the transaction.
* If empty, it will be ignored.
* @param post_callback If non-empty, this callback will be called directly after the transaction.
* If empty, it will be ignored.
* @param user_data This pointer will be sent to pre_callback and/or pre_callback, if any of them is non-empty.
*
* @return a future object which will become ready once the transfer has finished. See also \c SPIFuture.
*/
template<typename IteratorT>
SPIFuture transfer(IteratorT begin,
IteratorT end,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> post_callback = nullptr,
void* user_data = nullptr);
private:
/**
* Private device data.
*/
SPIDeviceHandle *device_handle;
/**
* Saves the current transaction descriptor in case the user's loses its future with the other
* reference to the transaction.
*/
std::shared_ptr<SPITransactionDescriptor> current_transaction;
};
/**
* @brief Represents an SPI Master Bus.
*/
class SPIMaster {
public:
/*
* @brief Create an SPI Master Bus.
*
* @param host The SPI host (bus) which should be used. ESP chips have a number of different possible SPI hosts,
* each of which will create its own bus. Consult the datasheet and TRM on which host to choose.
* @param mosi The pin number for the MOSI signal of this bus.
* @param miso The pin number for the MISO signal of this bus.
* @param sclk The pin number for the clock signal of this bus.
* @param dma_config The DMA configuration for this bus, see \c DMAConfig.
* @param max_transfer_size The maximum transfer size in bytes.
*
* @throws SPIException with IDF error code if the underlying driver fails.
*/
explicit SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
SPI_DMAConfig dma_config = SPI_DMAConfig::AUTO(),
SPITransferSize max_transfer_size = SPITransferSize::default_size());
/*
* @brief Create an SPI Master Bus.
*
* @param host The SPI host (bus) which should be used. ESP chips have a number of different possible SPI hosts,
* each of which will create its own bus. Consult the datasheet and TRM on which host to choose.
* @param mosi The pin number for the MOSI signal of this bus.
* @param miso The pin number for the MISO signal of this bus.
* @param sclk The pin number for the clock signal of this bus.
* @param qspiwp The pin number for the QSPIWP signal of this bus.
* @param qspihd The pin number for the QSPIHD signal of this bus.
* @param dma_config The DMA configuration for this bus, see \c DMAConfig.
* @param max_transfer_size The maximum transfer size in bytes.
*
* @throws SPIException with IDF error code if the underlying driver fails.
*/
explicit SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
const QSPIWP &qspiwp,
const QSPIHD &qspihd,
SPI_DMAConfig dma_config = SPI_DMAConfig::AUTO(),
SPITransferSize max_transfer_size = SPITransferSize::default_size());
SPIMaster(const SPIMaster&) = delete;
SPIMaster operator=(const SPIMaster&) = delete;
SPIMaster(SPIMaster&&) = default;
SPIMaster &operator=(SPIMaster&&) = default;
/*
* @brief De-initializes and destroys the SPI Master Bus.
*
* @note Devices created before which try to initialize an exception after the bus is destroyed will throw
* and exception.
*/
virtual ~SPIMaster();
/**
* @brief Create a representation of a device on this bus.
*
* @param cs The pin number for the CS (chip select) signal to talk to the device.
* @param f The frequency used to talk to the device.
*/
std::shared_ptr<SPIDevice> create_dev(CS cs, Frequency frequency = Frequency::MHz(1));
private:
/**
* @brief Host identifier for internal use.
*/
SPINum spi_host;
};
template<typename IteratorT>
SPIFuture SPIDevice::transfer(IteratorT begin,
IteratorT end,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data)
{
std::vector<uint8_t> write_data;
write_data.assign(begin, end);
return transfer(write_data, pre_callback, post_callback, user_data);
}
}
#endif

View File

@ -1,145 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* This file contains helper classes for commonly used IDF types. The classes make the use of these types easier and
* safer.
* In particular, their usage provides greater type-safety of function arguments and "correctness by construction".
*/
#pragma once
#ifndef __cpp_exceptions
#error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
#endif
#include "esp_exception.hpp"
/**
* This is a "Strong Value Type" base class for types in IDF C++ classes.
* The idea is that subclasses completely check the contained value during construction.
* After that, it's trapped and encapsulated inside and cannot be changed anymore.
* Consequently, the API functions receiving a correctly implemented sub class as parameter
* don't need to check it anymore. Only at API boundaries the valid value will be retrieved
* with get_value().
*/
template<typename ValueT>
class StrongValue {
protected:
constexpr StrongValue(ValueT value_arg) : value(value_arg) { }
ValueT get_value() const {
return value;
}
private:
ValueT value;
};
/**
* This class adds comparison properties to StrongValue, but no sorting and ordering properties.
*/
template<typename ValueT>
class StrongValueComparable : public StrongValue<ValueT> {
protected:
constexpr StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
public:
using StrongValue<ValueT>::get_value;
bool operator==(const StrongValueComparable<ValueT> &other_gpio) const
{
return get_value() == other_gpio.get_value();
}
bool operator!=(const StrongValueComparable<ValueT> &other_gpio) const
{
return get_value() != other_gpio.get_value();
}
};
namespace idf {
/**
* This class adds ordering and sorting properties to StrongValue.
*/
template<typename ValueT>
class StrongValueOrdered : public StrongValueComparable<ValueT> {
public:
StrongValueOrdered(ValueT value) : StrongValueComparable<ValueT>(value) { }
using StrongValueComparable<ValueT>::get_value;
bool operator>(const StrongValueOrdered<ValueT> &other) const
{
return get_value() > other.get_value();
}
bool operator<(const StrongValueOrdered<ValueT> &other) const
{
return get_value() < other.get_value();
}
bool operator>=(const StrongValueOrdered<ValueT> &other) const
{
return get_value() >= other.get_value();
}
bool operator<=(const StrongValueOrdered<ValueT> &other) const
{
return get_value() <= other.get_value();
}
};
/**
* A general frequency class to be used whereever an unbound frequency value is necessary.
*/
class Frequency : public StrongValueOrdered<size_t> {
public:
explicit Frequency(size_t frequency) : StrongValueOrdered<size_t>(frequency)
{
if (frequency == 0) {
throw ESPException(ESP_ERR_INVALID_ARG);
}
}
Frequency(const Frequency&) = default;
Frequency &operator=(const Frequency&) = default;
using StrongValueOrdered<size_t>::get_value;
static Frequency Hz(size_t frequency)
{
return Frequency(frequency);
}
static Frequency KHz(size_t frequency)
{
return Frequency(frequency * 1000);
}
static Frequency MHz(size_t frequency)
{
return Frequency(frequency * 1000 * 1000);
}
};
/**
* Queue size mainly for operating system queues.
*/
class QueueSize {
public:
explicit QueueSize(size_t q_size) : queue_size(q_size) { }
size_t get_size()
{
return queue_size;
}
private:
size_t queue_size;
};
}

View File

@ -1,141 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* The code in this file includes driver headers directly, hence it's a private include.
* It should only be used in C++ source files, while header files use forward declarations of the types.
* This way, public headers don't need to depend on (i.e. include) driver headers.
*/
#ifdef __cpp_exceptions
#include "hal/spi_types.h"
#include "driver/spi_master.h"
using namespace std;
namespace idf {
#define SPI_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), SPIException)
namespace {
/**
* @brief Convenience method to convert a SPINum object into the driver type. Avoids long static casts.
*/
spi_host_device_t spi_num_to_driver_type(const SPINum &num) noexcept {
return static_cast<spi_host_device_t>(num.get_spi_num());
}
}
/**
* This class wraps closely around the SPI master device driver functions.
* It is used to hide the implementation, in particular the dependencies on the driver and HAL layer headers.
* Public header files only use a pointer to this class which is forward declared in spi_host_cxx.hpp.
* Implementations (source files) can include this private header and use the class definitions.
*
* Furthermore, this class ensures RAII-capabilities of an SPI master device allocation and initiates pre- and
* post-transaction callback for each transfer. In constrast to the IDF driver, the callbacks are not per-device
* but per transaction in the C++ wrapper framework.
*
* For information on the public member functions, refer to the corresponding driver functions in spi_master.h
*/
class SPIDeviceHandle {
public:
/**
* Create a device instance on the SPI bus identified by spi_host, allocate all corresponding resources.
*/
SPIDeviceHandle(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size)
{
spi_device_interface_config_t dev_config = {};
dev_config.clock_speed_hz = frequency.get_value();
dev_config.spics_io_num = cs.get_num();
dev_config.pre_cb = pr_cb;
dev_config.post_cb = post_cb;
dev_config.queue_size = q_size.get_size();
SPI_CHECK_THROW(spi_bus_add_device(spi_num_to_driver_type(spi_host), &dev_config, &handle));
}
SPIDeviceHandle(const SPIDeviceHandle &other) = delete;
SPIDeviceHandle(SPIDeviceHandle &&other) noexcept : handle(std::move(other.handle))
{
// Only to indicate programming errors where users use an instance after moving it.
other.handle = nullptr;
}
/**
* Remove device instance from the SPI bus, deallocate all corresponding resources.
*/
~SPIDeviceHandle()
{
// We ignore the return value here.
// Only possible errors are wrong handle (impossible by object invariants) and
// handle already freed, which we can ignore.
spi_bus_remove_device(handle);
}
SPIDeviceHandle &operator=(SPIDeviceHandle&& other) noexcept
{
if (this != &other) {
handle = std::move(other.handle);
// Only to indicate programming errors where users use an instance after moving it.
other.handle = nullptr;
}
return *this;
}
esp_err_t acquire_bus(TickType_t wait)
{
return spi_device_acquire_bus(handle, portMAX_DELAY);
}
esp_err_t queue_trans(spi_transaction_t *trans_desc, TickType_t wait)
{
return spi_device_queue_trans(handle, trans_desc, wait);
}
esp_err_t get_trans_result(spi_transaction_t **trans_desc, TickType_t ticks_to_wait)
{
return spi_device_get_trans_result(handle, trans_desc, ticks_to_wait);
}
void release_bus()
{
spi_device_release_bus(handle);
}
private:
/**
* Route the callback to the callback in the specific SPITransactionDescriptor instance.
*/
static void pr_cb(spi_transaction_t *driver_transaction)
{
SPITransactionDescriptor *transaction = static_cast<SPITransactionDescriptor*>(driver_transaction->user);
if (transaction->pre_callback) {
transaction->pre_callback(transaction->user_data);
}
}
/**
* Route the callback to the callback in the specific SPITransactionDescriptor instance.
*/
static void post_cb(spi_transaction_t *driver_transaction)
{
SPITransactionDescriptor *transaction = static_cast<SPITransactionDescriptor*>(driver_transaction->user);
if (transaction->post_callback) {
transaction->post_callback(transaction->user_data);
}
}
spi_device_handle_t handle;
};
}
#endif

View File

@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include "driver/spi_common.h"
#include "esp_exception.hpp"
#include "spi_cxx.hpp"
namespace idf {
esp_err_t check_spi_num(uint32_t spi_num) noexcept {
if (spi_num >= static_cast<uint32_t>(SPI_HOST_MAX)) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
SPI_DMAConfig SPI_DMAConfig::DISABLED() {
return SPI_DMAConfig(static_cast<uint32_t>(spi_common_dma_t::SPI_DMA_DISABLED));
}
SPI_DMAConfig SPI_DMAConfig::AUTO() {
return SPI_DMAConfig(static_cast<uint32_t>(spi_common_dma_t::SPI_DMA_CH_AUTO));
}
}
#endif

View File

@ -1,267 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include <stdint.h>
#include <cstring>
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "hal/spi_types.h"
#include "driver/spi_master.h"
#include "spi_host_cxx.hpp"
#include "spi_host_private_cxx.hpp"
using namespace std;
namespace idf {
SPIException::SPIException(esp_err_t error) : ESPException(error) { }
SPITransferException::SPITransferException(esp_err_t error) : SPIException(error) { }
SPIMaster::SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
SPI_DMAConfig dma_config,
SPITransferSize transfer_size)
: spi_host(host)
{
spi_bus_config_t bus_config = {};
bus_config.mosi_io_num = mosi.get_num();
bus_config.miso_io_num = miso.get_num();
bus_config.sclk_io_num = sclk.get_num();
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
bus_config.max_transfer_sz = transfer_size.get_value();
SPI_CHECK_THROW(spi_bus_initialize(spi_num_to_driver_type(spi_host), &bus_config, dma_config.get_num()));
}
SPIMaster::SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
const QSPIWP &qspiwp,
const QSPIHD &qspihd,
SPI_DMAConfig dma_config,
SPITransferSize transfer_size)
: spi_host(host)
{
spi_bus_config_t bus_config = {};
bus_config.mosi_io_num = mosi.get_num();
bus_config.miso_io_num = miso.get_num();
bus_config.sclk_io_num = sclk.get_num();
bus_config.quadwp_io_num = qspiwp.get_num();
bus_config.quadhd_io_num = qspihd.get_num();
bus_config.max_transfer_sz = transfer_size.get_value();
SPI_CHECK_THROW(spi_bus_initialize(spi_num_to_driver_type(spi_host), &bus_config, dma_config.get_num()));
}
SPIMaster::~SPIMaster()
{
spi_bus_free(spi_num_to_driver_type(spi_host));
}
shared_ptr<SPIDevice> SPIMaster::create_dev(CS cs, Frequency frequency)
{
return make_shared<SPIDevice>(spi_host, cs, frequency);
}
SPIFuture::SPIFuture()
: transaction(), is_valid(false)
{
}
SPIFuture::SPIFuture(shared_ptr<SPITransactionDescriptor> transaction)
: transaction(transaction), is_valid(true)
{
}
SPIFuture::SPIFuture(SPIFuture &&other) noexcept
: transaction(std::move(other.transaction)), is_valid(true)
{
other.is_valid = false;
}
SPIFuture &SPIFuture::operator=(SPIFuture &&other) noexcept
{
if (this != &other) {
transaction = std::move(other.transaction);
is_valid = other.is_valid;
other.is_valid = false;
}
return *this;
}
vector<uint8_t> SPIFuture::get()
{
if (!is_valid) {
throw std::future_error(future_errc::no_state);
}
return transaction->get();
}
future_status SPIFuture::wait_for(chrono::milliseconds timeout)
{
if (transaction->wait_for(timeout)) {
return std::future_status::ready;
} else {
return std::future_status::timeout;
}
}
void SPIFuture::wait()
{
transaction->wait();
}
bool SPIFuture::valid() const noexcept
{
return is_valid;
}
SPIDevice::SPIDevice(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size) : device_handle()
{
device_handle = new SPIDeviceHandle(spi_host, cs, frequency, q_size);
}
SPIDevice::~SPIDevice()
{
delete device_handle;
}
SPIFuture SPIDevice::transfer(const vector<uint8_t> &data_to_send,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data)
{
current_transaction = make_shared<SPITransactionDescriptor>(data_to_send,
device_handle,
std::move(pre_callback),
std::move(post_callback),
user_data);
current_transaction->start();
return SPIFuture(current_transaction);
}
SPITransactionDescriptor::SPITransactionDescriptor(const std::vector<uint8_t> &data_to_send,
SPIDeviceHandle *handle,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data_arg)
: device_handle(handle),
pre_callback(std::move(pre_callback)),
post_callback(std::move(post_callback)),
user_data(user_data_arg),
received_data(false),
started(false)
{
// C++11 vectors don't have size() or empty() members yet
if (data_to_send.begin() == data_to_send.end()) {
throw SPITransferException(ESP_ERR_INVALID_ARG);
}
if (handle == nullptr) {
throw SPITransferException(ESP_ERR_INVALID_ARG);
}
size_t trans_size = data_to_send.size();
spi_transaction_t *trans_desc;
trans_desc = new spi_transaction_t;
memset(trans_desc, 0, sizeof(spi_transaction_t));
trans_desc->rx_buffer = new uint8_t [trans_size];
tx_buffer = new uint8_t [trans_size];
for (size_t i = 0; i < trans_size; i++) {
tx_buffer[i] = data_to_send[i];
}
trans_desc->length = trans_size * 8;
trans_desc->tx_buffer = tx_buffer;
trans_desc->user = this;
private_transaction_desc = trans_desc;
}
SPITransactionDescriptor::~SPITransactionDescriptor()
{
if (started) {
assert(received_data); // We need to make sure that trans_desc has been received, otherwise the
// driver may still write into it afterwards.
}
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
delete [] tx_buffer;
delete [] static_cast<uint8_t*>(trans_desc->rx_buffer);
delete trans_desc;
}
void SPITransactionDescriptor::start()
{
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
SPI_CHECK_THROW(device_handle->acquire_bus(portMAX_DELAY));
SPI_CHECK_THROW(device_handle->queue_trans(trans_desc, 0));
started = true;
}
void SPITransactionDescriptor::wait()
{
while (wait_for(chrono::milliseconds(portMAX_DELAY)) == false) { }
}
bool SPITransactionDescriptor::wait_for(const chrono::milliseconds &timeout_duration)
{
if (received_data) {
return true;
}
if (!started) {
throw SPITransferException(ESP_ERR_INVALID_STATE);
}
spi_transaction_t *acquired_trans_desc;
esp_err_t err = device_handle->get_trans_result(&acquired_trans_desc,
(TickType_t) timeout_duration.count() / portTICK_PERIOD_MS);
if (err == ESP_ERR_TIMEOUT) {
return false;
}
if (err != ESP_OK) {
throw SPITransferException(err);
}
if (acquired_trans_desc != reinterpret_cast<spi_transaction_t*>(private_transaction_desc)) {
throw SPITransferException(ESP_ERR_INVALID_STATE);
}
received_data = true;
device_handle->release_bus();
return true;
}
std::vector<uint8_t> SPITransactionDescriptor::get()
{
if (!received_data) {
wait();
}
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
const size_t TRANSACTION_LENGTH = trans_desc->length / 8;
vector<uint8_t> result(TRANSACTION_LENGTH);
for (int i = 0; i < TRANSACTION_LENGTH; i++) {
result[i] = static_cast<uint8_t*>(trans_desc->rx_buffer)[i];
}
return result;
}
} // idf
#endif // __cpp_exceptions

View File

@ -1,4 +0,0 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS .
PRIV_REQUIRES cmock test_utils experimental_cpp_component)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@ -1,61 +0,0 @@
#include <stdio.h>
#include <cstring>
#include "unity.h"
#include "unity_cxx.hpp"
#include "esp_exception.hpp"
#ifdef __cpp_exceptions
using namespace std;
using namespace idf;
#define TAG "CXX Exception Test"
#if CONFIG_IDF_TARGET_ESP32
#define LEAKS "300"
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
#define LEAKS "800"
#else
#error "unknown target in CXX tests, can't set leaks threshold"
#endif
TEST_CASE("TEST_THROW catches exception", "[cxx exception][leaks=" LEAKS "]")
{
TEST_THROW(throw ESPException(ESP_FAIL);, ESPException);
}
/* The following two test cases are expected to fail */
TEST_CASE("TEST_THROW asserts catching different exception", "[cxx exception][ignore]")
{
TEST_THROW(throw std::exception();, ESPException);
}
TEST_CASE("TEST_THROW asserts not catching any exception", "[cxx exception][ignore]")
{
TEST_THROW(printf(" ");, ESPException); // need statement with effect
}
TEST_CASE("CHECK_THROW continues on ESP_OK", "[cxx exception][leaks=" LEAKS "]")
{
esp_err_t error = ESP_OK;
CHECK_THROW(error);
}
TEST_CASE("CHECK_THROW throws", "[cxx exception][leaks=" LEAKS "]")
{
esp_err_t error = ESP_FAIL;
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

@ -1,761 +0,0 @@
#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,114 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __cpp_exceptions
#include "unity.h"
#include "unity_cxx.hpp"
#include <limits>
#include <stdio.h>
#include <iostream>
#include "test_utils.h" // ref clock
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer_cxx.hpp"
#include "esp_exception.hpp"
using namespace std;
using namespace idf;
using namespace idf::esp_timer;
struct RefClock {
RefClock()
{
ref_clock_init();
};
~RefClock()
{
ref_clock_deinit();
}
};
TEST_CASE("ESPTimer produces correct delay", "[ESPTimer]")
{
int64_t t_end;
RefClock ref_clock;
function<void()> timer_cb = [&t_end]() {
t_end = ref_clock_get();
};
ESPTimer timer(timer_cb, "timer1");
const int delays_ms[] = {20, 100, 200, 250};
const size_t delays_count = sizeof(delays_ms)/sizeof(delays_ms[0]);
for (size_t i = 0; i < delays_count; ++i) {
t_end = 0;
int64_t t_start = ref_clock_get();
timer.start(chrono::microseconds(delays_ms[i] * 1000));
vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS);
TEST_ASSERT(t_end != 0);
int32_t ms_diff = (t_end - t_start) / 1000;
printf("%d %d\n", delays_ms[i], ms_diff);
TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff);
}
}
TEST_CASE("ESPtimer produces correct periodic delays", "[ESPTimer]")
{
const size_t NUM_INTERVALS = 3u;
size_t cur_interval = 0;
int intervals[NUM_INTERVALS];
int64_t t_start;
SemaphoreHandle_t done;
const int DELAY_MS = 100;
function<void()> timer_cb = [&]() {
int64_t t_end = ref_clock_get();
int32_t ms_diff = (t_end - t_start) / 1000;
printf("timer #%d %dms\n", cur_interval, ms_diff);
if (cur_interval < NUM_INTERVALS) {
intervals[cur_interval++] = ms_diff;
}
// Deliberately make timer handler run longer.
// We check that this doesn't affect the result.
esp_rom_delay_us(10*1000);
if (cur_interval == NUM_INTERVALS) {
printf("done\n");
xSemaphoreGive(done);
}
};
ESPTimer timer(timer_cb, "timer1");
RefClock ref_clock;
t_start = ref_clock_get();
done = xSemaphoreCreateBinary();
timer.start_periodic(chrono::microseconds(DELAY_MS * 1000));
TEST_ASSERT(xSemaphoreTake(done, DELAY_MS * NUM_INTERVALS * 2));
timer.stop();
TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, cur_interval);
for (size_t i = 0; i < NUM_INTERVALS; ++i) {
TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * DELAY_MS, intervals[i]);
}
TEST_ESP_OK(esp_timer_dump(stdout));
vSemaphoreDelete(done);
#undef NUM_INTERVALS
}
#endif // __cpp_exceptions

View File

@ -1,261 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_cxx.hpp"
#include <limits>
#include <stdio.h>
#include <iostream>
#include "test_utils.h" // unity_send_signal
#ifdef __cpp_exceptions
#include "i2c_cxx.hpp"
#include "driver/i2c.h"
using namespace std;
using namespace idf;
#define ADDR 0x47
#define MAGIC_TEST_NUMBER 47
static constexpr I2CNumber I2C_SLAVE_NUM(I2CNumber::I2C0()); /*!<I2C port number for slave dev */
#if CONFIG_IDF_TARGET_ESP32C3
#define I2C_SLAVE_SCL_IO 5 /*!<gpio number for i2c slave clock */
#define I2C_SLAVE_SDA_IO 6 /*!<gpio number for i2c slave data */
#else
#define I2C_SLAVE_SCL_IO 19 /*!<gpio number for i2c slave clock */
#define I2C_SLAVE_SDA_IO 18 /*!<gpio number for i2c slave data */
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
static constexpr I2CNumber I2C_MASTER_NUM(I2CNumber::I2C0()); /*!< I2C port number for master dev */
#define I2C_MASTER_SCL_IO 5 /*!<gpio number for i2c master clock */
#define I2C_MASTER_SDA_IO 6 /*!<gpio number for i2c master data */
#else
static constexpr I2CNumber I2C_MASTER_NUM(I2CNumber::I2C1()); /*!< I2C port number for master dev */
#define I2C_MASTER_SCL_IO 19 /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO 18 /*!< gpio number for I2C master data */
#endif
struct MasterFixture {
MasterFixture(const vector<uint8_t> &data_arg = {47u}) :
master(new I2CMaster(I2CNumber(I2C_MASTER_NUM),
SCL_GPIO(I2C_MASTER_SCL_IO),
SDA_GPIO(I2C_MASTER_SDA_IO),
Frequency(400000))),
data(data_arg) { }
std::shared_ptr<I2CMaster> master;
vector<uint8_t> data;
};
// TODO The I2C driver tests are disabled, so disable them here, too. Probably due to no runners.
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3, ESP32C2, ESP32C6, ESP32H2)
static void i2c_slave_read_raw_byte(void)
{
I2CSlave slave(I2CNumber(I2C_SLAVE_NUM), SCL_GPIO(I2C_SLAVE_SCL_IO), SDA_GPIO(I2C_SLAVE_SDA_IO), I2CAddress(ADDR), 512, 512);
uint8_t buffer = 0;
unity_send_signal("slave init");
unity_wait_for_signal("master write");
TEST_ASSERT_EQUAL(1, slave.read_raw(&buffer, 1, chrono::milliseconds(1000)));
TEST_ASSERT_EQUAL(MAGIC_TEST_NUMBER, buffer);
}
static void i2c_slave_write_raw_byte(void)
{
I2CSlave slave(I2CNumber(I2C_SLAVE_NUM), SCL_GPIO(I2C_SLAVE_SCL_IO), SDA_GPIO(I2C_SLAVE_SDA_IO), I2CAddress(ADDR), 512, 512);
uint8_t WRITE_BUFFER = MAGIC_TEST_NUMBER;
unity_wait_for_signal("master init");
TEST_ASSERT_EQUAL(1, slave.write_raw(&WRITE_BUFFER, 1, chrono::milliseconds(1000)));
unity_send_signal("slave write");
// This last synchronization is necessary to prevent slave from going out of scope hence de-initializing already
// before master has read
unity_wait_for_signal("master read done");
}
static void i2c_slave_read_multiple_raw_bytes(void)
{
I2CSlave slave(I2CNumber(I2C_SLAVE_NUM), SCL_GPIO(I2C_SLAVE_SCL_IO), SDA_GPIO(I2C_SLAVE_SDA_IO), I2CAddress(ADDR), 512, 512);
uint8_t buffer [8] = {};
unity_send_signal("slave init");
unity_wait_for_signal("master write");
TEST_ASSERT_EQUAL(8, slave.read_raw(buffer, 8, chrono::milliseconds(1000)));
for (int i = 0; i < 8; i++) {
TEST_ASSERT_EQUAL(i, buffer[i]);
}
}
static void i2c_slave_write_multiple_raw_bytes(void)
{
I2CSlave slave(I2CNumber(I2C_SLAVE_NUM), SCL_GPIO(I2C_SLAVE_SCL_IO), SDA_GPIO(I2C_SLAVE_SDA_IO), I2CAddress(ADDR), 512, 512);
uint8_t WRITE_BUFFER [8] = {0, 1, 2, 3, 4, 5, 6, 7};
unity_wait_for_signal("master init");
TEST_ASSERT_EQUAL(8, slave.write_raw(WRITE_BUFFER, 8, chrono::milliseconds(1000)));
unity_send_signal("slave write");
unity_wait_for_signal("master read done");
}
static void i2c_slave_composed_trans(void)
{
I2CSlave slave(I2CNumber(I2C_SLAVE_NUM), SCL_GPIO(I2C_SLAVE_SCL_IO), SDA_GPIO(I2C_SLAVE_SDA_IO), I2CAddress(ADDR), 512, 512);
size_t BUF_SIZE = 2;
const uint8_t SLAVE_WRITE_BUFFER [BUF_SIZE] = {0xde, 0xad};
uint8_t slave_read_buffer = 0;
unity_send_signal("slave init");
TEST_ASSERT_EQUAL(BUF_SIZE, slave.write_raw(SLAVE_WRITE_BUFFER, BUF_SIZE, chrono::milliseconds(1000)));
unity_wait_for_signal("master transfer");
TEST_ASSERT_EQUAL(1, slave.read_raw(&slave_read_buffer, 1, chrono::milliseconds(1000)));
TEST_ASSERT_EQUAL(MAGIC_TEST_NUMBER, slave_read_buffer);
}
static void i2c_master_read_raw_byte(void)
{
MasterFixture fix;
unity_send_signal("master init");
unity_wait_for_signal("slave write");
std::shared_ptr<I2CRead> reader(new I2CRead(1));
future<vector<uint8_t> > fut = fix.master->transfer(I2CAddress(ADDR), reader);
vector<uint8_t> data;
data = fut.get();
unity_send_signal("master read done");
TEST_ASSERT_EQUAL(1, data.size());
TEST_ASSERT_EQUAL(MAGIC_TEST_NUMBER, data[0]);
}
TEST_CASE_MULTIPLE_DEVICES("I2CMaster read one byte", "[cxx i2c][test_env=UT_T2_I2C][timeout=150]",
i2c_master_read_raw_byte, i2c_slave_write_raw_byte);
static void i2c_master_write_raw_byte(void)
{
MasterFixture fix;
unity_wait_for_signal("slave init");
std::shared_ptr<I2CWrite> writer(new I2CWrite(fix.data));
future<void> fut = fix.master->transfer(I2CAddress(ADDR), writer);
fut.get();
unity_send_signal("master write");
}
TEST_CASE_MULTIPLE_DEVICES("I2CMaster write one byte", "[cxx i2c][test_env=UT_T2_I2C][timeout=150]",
i2c_master_write_raw_byte, i2c_slave_read_raw_byte);
static void i2c_master_read_multiple_raw_bytes(void)
{
MasterFixture fix;
unity_send_signal("master init");
unity_wait_for_signal("slave write");
std::shared_ptr<I2CRead> reader(new I2CRead(8));
future<vector<uint8_t> > fut = fix.master->transfer(I2CAddress(ADDR), reader);
vector<uint8_t> data = fut.get();
unity_send_signal("master read done");
TEST_ASSERT_EQUAL(8, data.size());
for (int i = 0; i < 8; i++) {
TEST_ASSERT_EQUAL(i, data[i]);
}
}
TEST_CASE_MULTIPLE_DEVICES("I2CMaster read multiple bytes", "[cxx i2c][test_env=UT_T2_I2C][timeout=150]",
i2c_master_read_multiple_raw_bytes, i2c_slave_write_multiple_raw_bytes);
static void i2c_master_write_multiple_raw_bytes(void)
{
MasterFixture fix({0, 1, 2, 3, 4, 5, 6, 7});
unity_wait_for_signal("slave init");
std::shared_ptr<I2CWrite> writer(new I2CWrite(fix.data));
future<void> fut = fix.master->transfer(I2CAddress(ADDR), writer);
fut.get();
unity_send_signal("master write");
}
TEST_CASE_MULTIPLE_DEVICES("I2CMaster write multiple bytes", "[cxx i2c][test_env=UT_T2_I2C][timeout=150]",
i2c_master_write_multiple_raw_bytes, i2c_slave_read_multiple_raw_bytes);
static void i2c_master_sync_transfer(void)
{
MasterFixture fix;
size_t READ_SIZE = 2;
const uint8_t DESIRED_READ [READ_SIZE] = {0xde, 0xad};
unity_wait_for_signal("slave init");
vector<uint8_t> read_data = fix.master->sync_transfer(I2CAddress(ADDR), fix.data, READ_SIZE);
unity_send_signal("master transfer");
TEST_ASSERT_EQUAL(READ_SIZE, read_data.size());
for (int i = 0; i < READ_SIZE; i++) {
TEST_ASSERT_EQUAL(DESIRED_READ[i], read_data[i]);
}
}
TEST_CASE_MULTIPLE_DEVICES("I2CMaster sync transfer", "[cxx i2c][test_env=UT_T2_I2C][timeout=150]",
i2c_master_sync_transfer, i2c_slave_composed_trans);
static void i2c_master_composed_trans(void)
{
MasterFixture fix;
size_t BUF_SIZE = 2;
const uint8_t SLAVE_WRITE_BUFFER [BUF_SIZE] = {0xde, 0xad};
std::shared_ptr<I2CComposed> composed_transfer(new I2CComposed);
composed_transfer->add_write({47u});
composed_transfer->add_read(BUF_SIZE);
unity_wait_for_signal("slave init");
future<vector<vector<uint8_t> > > result = fix.master->transfer(I2CAddress(ADDR), composed_transfer);
unity_send_signal("master transfer");
vector<vector<uint8_t> > read_data = result.get();
TEST_ASSERT_EQUAL(1, read_data.size());
TEST_ASSERT_EQUAL(2, read_data[0].size());
for (int i = 0; i < BUF_SIZE; i++) {
TEST_ASSERT_EQUAL(SLAVE_WRITE_BUFFER[i], read_data[0][i]);
}
}
TEST_CASE_MULTIPLE_DEVICES("I2CMaster Composed transfer", "[cxx i2c][test_env=UT_T2_I2C][timeout=150]",
i2c_master_composed_trans, i2c_slave_composed_trans);
#endif //TEMPORARY_DISABLED_FOR_TARGETS(...)
#endif // __cpp_exceptions

View File

@ -1,37 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "unity.h"
#define CXX_UNITY_TYPE_TO_STR(x) #x
/**
* Very simple helper macro to catch exceptions.
*
* @note
* * If there is any exception which not a child of std::exception, it will terminate the program!
* * If there is no exception, it will jump from the current frame without de-initializing
* destructors!
*/
#define TEST_THROW(expr_, exception_) \
do { \
bool caught = false; \
bool caught_different = false; \
try { \
expr_; \
} catch ( exception_ &e) { \
caught = true; \
} catch ( std::exception &e) { \
caught_different = true; \
} \
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 " CXX_UNITY_TYPE_TO_STR(exception_) \
", but no exception thrown."); \
} \
while (0)

View File

@ -1,8 +0,0 @@
# The following 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.16)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(simple_i2c_rw_example)

View File

@ -1,63 +0,0 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# Example: C++ I2C sensor read for MPU9250
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of C++ exceptions in ESP-IDF. It is the C++ equivalent to the [I2C Simple Example](../../../peripherals/i2c/i2c_simple/) which is written in C.
In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option. This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling. This is necessary for the C++ I2C API.
## How to Use This Example
### Hardware Required
To run this example, you should have one ESP32, ESP32-S series or ESP32-C series based development board as well as an MPU9250. MPU9250 is an inertial measurement unit, which contains an accelerometer, gyroscope as well as a magnetometer, for more information about it, you can read the [datasheet of the MPU9250 sensor](https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf).
#### Pin Assignment:
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
| | SDA | SCL |
| ---------------- | -------------- | -------------- |
| ESP I2C Master | I2C_MASTER_SDA | I2C_MASTER_SCL |
| MPU9250 Sensor | SDA | SCL |
For the actual default value of `I2C_MASTER_SDA` and `I2C_MASTER_SCL`, see `Example Configuration` in `menuconfig`.
**Note:** There's no need to add external pull-up resistors for SDA/SCL pins, because the driver will enable the internal pull-up resistors.
### Configure the project
```
idf.py menuconfig
```
### Build and Flash
```
idf.py -p <PORT> flash monitor
```
Replace <PORT> with the name of the serial port. To exit the serial monitor, type ``Ctrl-]``.
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
If the sensor is read correctly:
```bash
I (328) i2c-simple-example: I2C initialized successfully
I (338) i2c-simple-example: WHO_AM_I = 71
I (338) i2c-simple-example: I2C de-initialized successfully
```
If something went wrong:
```
I2C Exception with error: ESP_FAIL (-1)
Couldn't read sensor!
```

View File

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

Some files were not shown because too many files have changed in this diff Show More