Przeglądaj źródła

Merge branch 'feature/gpio_cxx' into 'master'

CXX GPIO classes

See merge request espressif/esp-idf!13989
Jakob Hasse 4 lat temu
rodzic
commit
847e3d7e46

+ 7 - 0
.gitlab/ci/host-test.yml

@@ -362,3 +362,10 @@ test_rom_on_linux_works:
     - cd ${IDF_PATH}/components/esp_rom/host_test/rom_test
     - idf.py build
     - build/test_rom_host.elf
+
+test_cxx_gpio:
+  extends: .host_test_template
+  script:
+    - cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/gpio
+    - idf.py build
+    - build/test_gpio_cxx_host.elf

+ 1 - 0
.gitlab/ci/rules.yml

@@ -37,6 +37,7 @@
 
 .patterns-build_components: &patterns-build_components
   - "components/**/*"
+  - "examples/cxx/experimental/experimental_cpp_component/*"
 
 .patterns-build_system: &patterns-build_system
   - "tools/cmake/**/*"

+ 10 - 0
examples/cxx/experimental/blink_cxx/CMakeLists.txt

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

+ 57 - 0
examples/cxx/experimental/blink_cxx/README.md

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

+ 2 - 0
examples/cxx/experimental/blink_cxx/main/CMakeLists.txt

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

+ 39 - 0
examples/cxx/experimental/blink_cxx/main/main.cpp

@@ -0,0 +1,39 @@
+/* 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");
+    }
+}

+ 3 - 0
examples/cxx/experimental/blink_cxx/sdkconfig.defaults

@@ -0,0 +1,3 @@
+# Enable C++ exceptions and set emergency pool size for exception objects
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
+CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024

+ 1 - 0
examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt

@@ -1,6 +1,7 @@
 idf_component_register(SRCS
                     "esp_exception.cpp"
                     "i2c_cxx.cpp"
+                    "gpio_cxx.cpp"
                     "esp_event_api.cpp"
                     "esp_event_cxx.cpp"
                     "esp_timer_cxx.cpp"

+ 208 - 0
examples/cxx/experimental/experimental_cpp_component/gpio_cxx.cpp

@@ -0,0 +1,208 @@
+/*
+ * SPDX-FileCopyrightText: 2021 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 = {};
+#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

+ 78 - 0
examples/cxx/experimental/experimental_cpp_component/host_test/fixtures/test_fixtures.hpp

@@ -0,0 +1,78 @@
+// Copyright 2015-2021 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 "catch.hpp"
+#include "gpio_cxx.hpp"
+extern "C" {
+#include "Mockgpio.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
+ * which use 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 GPIOFixture {
+    GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) : num(gpio_num)
+    {
+        CMOCK_SETUP();
+        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);
+    }
+
+    ~GPIOFixture()
+    {
+        // Verify that all expected methods have been called.
+        Mockgpio_Verify();
+    }
+
+    idf::GPIONum num;
+};

+ 9 - 0
examples/cxx/experimental/experimental_cpp_component/host_test/gpio/CMakeLists.txt

@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(COMPONENTS main)
+
+idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND)
+list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/mocks/driver/")
+list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
+project(test_gpio_cxx_host)

+ 8 - 0
examples/cxx/experimental/experimental_cpp_component/host_test/gpio/README.md

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

+ 13 - 0
examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/CMakeLists.txt

@@ -0,0 +1,13 @@
+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 esp_common)

+ 397 - 0
examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/gpio_cxx_test.cpp

@@ -0,0 +1,397 @@
+/* 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 "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());
+}

+ 3 - 0
examples/cxx/experimental/experimental_cpp_component/host_test/gpio/sdkconfig.defaults

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

+ 402 - 0
examples/cxx/experimental/experimental_cpp_component/include/gpio_cxx.hpp

@@ -0,0 +1,402 @@
+/*
+ * 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.
+     */
+    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!
+     */
+    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!
+     */
+    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.
+     */
+    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

+ 53 - 0
examples/cxx/experimental/experimental_cpp_component/include/system_cxx.hpp

@@ -0,0 +1,53 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#ifndef __cpp_exceptions
+#error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
+#endif
+
+/**
+ * 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:
+    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 properties.
+ */
+template<typename ValueT>
+class StrongValueComparable : public StrongValue<ValueT> {
+protected:
+    StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
+
+    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();
+    }
+};

+ 1 - 0
tools/ci/check_examples_cmake_make-cmake_ignore.txt

@@ -4,3 +4,4 @@ cxx/experimental/experimental_cpp_component/
 main/
 build_system/cmake/
 mb_example_common/
+examples/cxx/experimental/blink_cxx

+ 1 - 0
tools/ci/check_examples_cmake_make-make_ignore.txt

@@ -1,3 +1,4 @@
 build_system/cmake
 temp_
 examples/bluetooth/bluedroid/ble_50/
+examples/cxx/experimental/blink_cxx