mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
cxx: removed cxx experimental components
This commit is contained in:
parent
d825753387
commit
3574fc1918
@ -40,7 +40,6 @@
|
|||||||
|
|
||||||
.patterns-build_components: &patterns-build_components
|
.patterns-build_components: &patterns-build_components
|
||||||
- "components/**/*"
|
- "components/**/*"
|
||||||
- "examples/cxx/experimental/experimental_cpp_component/*"
|
|
||||||
|
|
||||||
.patterns-downloadable-tools: &patterns-downloadable-tools
|
.patterns-downloadable-tools: &patterns-downloadable-tools
|
||||||
- "tools/idf_tools.py"
|
- "tools/idf_tools.py"
|
||||||
|
@ -6,41 +6,6 @@ examples/cxx/exceptions:
|
|||||||
temporary: true
|
temporary: true
|
||||||
reason: lack of runners
|
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:
|
examples/cxx/pthread:
|
||||||
disable_test:
|
disable_test:
|
||||||
- if: IDF_TARGET not in ["esp32", "esp32c3"]
|
- if: IDF_TARGET not in ["esp32", "esp32c3"]
|
||||||
|
@ -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)
|
|
@ -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
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "main.cpp"
|
|
||||||
INCLUDE_DIRS ".")
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
||||||
```
|
|
@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "esp_event_async_cxx_example.cpp"
|
|
||||||
INCLUDE_DIRS ".")
|
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
||||||
```
|
|
@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "esp_event_cxx_example.cpp"
|
|
||||||
INCLUDE_DIRS ".")
|
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
@ -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()
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -1,2 +0,0 @@
|
|||||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
|
|
||||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
|
@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "mqtt_ssl_example.cpp"
|
|
||||||
INCLUDE_DIRS ".")
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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 );
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -1,2 +0,0 @@
|
|||||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
|
|
||||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
|
@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "mqtt_tcp_example.cpp"
|
|
||||||
INCLUDE_DIRS ".")
|
|
@ -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
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "esp_timer_example.cpp"
|
|
||||||
INCLUDE_DIRS ".")
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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})
|
|
@ -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
|
|
||||||
```
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
||||||
```
|
|
@ -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)
|
|
@ -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);
|
|
||||||
}
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
|
||||||
CONFIG_IDF_TARGET="linux"
|
|
||||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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;
|
|
||||||
};
|
|
@ -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)
|
|
@ -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`
|
|
@ -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)
|
|
@ -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());
|
|
||||||
}
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
|
||||||
CONFIG_IDF_TARGET="linux"
|
|
||||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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)
|
|
@ -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`
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
|
||||||
CONFIG_IDF_TARGET="linux"
|
|
||||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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)
|
|
@ -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`
|
|
@ -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)
|
|
@ -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);
|
|
||||||
}
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
|
||||||
CONFIG_IDF_TARGET="linux"
|
|
||||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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/"
|
|
||||||
)
|
|
@ -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`
|
|
@ -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)
|
|
@ -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);
|
|
||||||
}
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
|
||||||
CONFIG_IDF_TARGET="linux"
|
|
||||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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!
|
|
||||||
```
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue
Block a user