diff --git a/.github/workflows/wifi_remote__build.yml b/.github/workflows/wifi_remote__build.yml index e607787cf..e2d096e01 100644 --- a/.github/workflows/wifi_remote__build.yml +++ b/.github/workflows/wifi_remote__build.yml @@ -48,7 +48,7 @@ jobs: run: | ${IDF_PATH}/install.sh --enable-pytest . ${IDF_PATH}/export.sh - echo "python ./ci/build_apps.py ./components/esp_wifi_remote/${{matrix.test.path}} -vv --preserve-all" + python ./ci/build_apps.py ./components/esp_wifi_remote/${{matrix.test.path}} -vv --preserve-all build_wifi_remote_example: if: contains(github.event.pull_request.labels.*.name, 'wifi_remote') || github.event_name == 'push' diff --git a/components/esp_wifi_remote/Kconfig.rpc.in b/components/esp_wifi_remote/Kconfig.rpc.in index 204a40083..d63530ab7 100644 --- a/components/esp_wifi_remote/Kconfig.rpc.in +++ b/components/esp_wifi_remote/Kconfig.rpc.in @@ -15,35 +15,20 @@ choice ESP_WIFI_REMOTE_LIBRARY endchoice if ESP_WIFI_REMOTE_LIBRARY_EPPP - menu "WiFi remote by EPPP" - choice ESP_WIFI_REMOTE_EPPP_TRANSPORT - prompt "Choose EPPP transport" - default ESP_WIFI_REMOTE_EPPP_TRANSPORT_UART + config ESP_WIFI_REMOTE_EPPP_UART_TX_PIN + int "TXD Pin Number" + default 10 + range 0 31 help - Select type of EPPP transport + Pin number of UART TX. - config ESP_WIFI_REMOTE_EPPP_TRANSPORT_UART - bool "UART" - config ESP_WIFI_REMOTE_EPPP_TRANSPORT_SPI - bool "SPI" - endchoice - - if ESP_WIFI_REMOTE_EPPP_TRANSPORT_UART - config ESP_WIFI_REMOTE_EPPP_UART_TX_PIN - int "TXD Pin Number" - default 10 - range 0 31 - help - Pin number of UART TX. - - config ESP_WIFI_REMOTE_EPPP_UART_RX_PIN - int "RXD Pin Number" - default 11 - range 0 31 - help - Pin number of UART RX. - endif + config ESP_WIFI_REMOTE_EPPP_UART_RX_PIN + int "RXD Pin Number" + default 11 + range 0 31 + help + Pin number of UART RX. config ESP_WIFI_REMOTE_EPPP_SERVER_CA string "Servers CA certificate" @@ -68,6 +53,5 @@ endchoice config ESP_WIFI_REMOTE_EPPP_SERVER_KEY string "Server key" default "--- Please copy content of the Client key ---" - endmenu endif diff --git a/components/esp_wifi_remote/eppp/eppp_init.c b/components/esp_wifi_remote/eppp/eppp_init.c index 574ebfd2a..9c22eb2f3 100644 --- a/components/esp_wifi_remote/eppp/eppp_init.c +++ b/components/esp_wifi_remote/eppp/eppp_init.c @@ -7,17 +7,14 @@ #include "esp_wifi.h" #include "eppp_link.h" -esp_netif_t *wifi_remote_eppp_init(eppp_type_t role) +__attribute__((weak)) esp_netif_t *wifi_remote_eppp_init(eppp_type_t role) { uint32_t our_ip = role == EPPP_SERVER ? EPPP_DEFAULT_SERVER_IP() : EPPP_DEFAULT_CLIENT_IP(); uint32_t their_ip = role == EPPP_SERVER ? EPPP_DEFAULT_CLIENT_IP() : EPPP_DEFAULT_SERVER_IP(); eppp_config_t config = EPPP_DEFAULT_CONFIG(our_ip, their_ip); -#if CONFIG_ESP_WIFI_REMOTE_EPPP_TRANSPORT_UART + // We currently support only UART transport config.transport = EPPP_TRANSPORT_UART; config.uart.tx_io = CONFIG_ESP_WIFI_REMOTE_EPPP_UART_TX_PIN; config.uart.rx_io = CONFIG_ESP_WIFI_REMOTE_EPPP_UART_RX_PIN; -#else -#error ESP_WIFI_REMOTE supports only UART transport -#endif return eppp_open(role, &config, portMAX_DELAY); } diff --git a/components/esp_wifi_remote/eppp/wifi_remote_rpc_client.cpp b/components/esp_wifi_remote/eppp/wifi_remote_rpc_client.cpp index 2bbb76685..c287cc167 100644 --- a/components/esp_wifi_remote/eppp/wifi_remote_rpc_client.cpp +++ b/components/esp_wifi_remote/eppp/wifi_remote_rpc_client.cpp @@ -26,12 +26,15 @@ const char *TAG = "rpc_client"; const unsigned char ca_crt[] = "-----BEGIN CERTIFICATE-----\n" CONFIG_ESP_WIFI_REMOTE_EPPP_SERVER_CA "\n-----END CERTIFICATE-----"; const unsigned char crt[] = "-----BEGIN CERTIFICATE-----\n" CONFIG_ESP_WIFI_REMOTE_EPPP_CLIENT_CRT "\n-----END CERTIFICATE-----"; const unsigned char key[] = "-----BEGIN RSA PRIVATE KEY-----\n" CONFIG_ESP_WIFI_REMOTE_EPPP_CLIENT_KEY "\n-----END RSA PRIVATE KEY-----"; +// TODO: Add option to supply keys and certs via a global symbol (file) } using namespace client; -struct Sync { +class Sync { + friend class RpcInstance; +public: void lock() { xSemaphoreTake(mutex, portMAX_DELAY); @@ -48,7 +51,7 @@ struct Sync { } esp_err_t wait_for(EventBits_t bits, uint32_t timeout = portMAX_DELAY) { - return xEventGroupWaitBits(events, bits, pdTRUE, pdTRUE, timeout) == bits ? ESP_OK : ESP_FAIL; + return (xEventGroupWaitBits(events, bits, pdTRUE, pdTRUE, timeout) & bits) == bits ? ESP_OK : ESP_FAIL; } esp_err_t notify(EventBits_t bits) { @@ -64,30 +67,36 @@ struct Sync { vEventGroupDelete(events); } } + + +private: SemaphoreHandle_t mutex{nullptr}; EventGroupHandle_t events{nullptr}; + const int request = 1; const int resp_header = 2; const int resp_payload = 4; - + const int restart = 8; }; class RpcInstance { + friend class Sync; public: + template esp_err_t send(api_id id, T *t) { - ESP_RETURN_ON_ERROR(sync.notify(sync.request), TAG, "failed to notify req"); pending_resp = id; + ESP_RETURN_ON_ERROR(sync.notify(sync.request), TAG, "failed to notify req"); ESP_RETURN_ON_ERROR(rpc.send(id, t), TAG, "Failed to send request"); return ESP_OK; } - // specialization for (void) + // overload of the templated method (used for functions with no arguments) esp_err_t send(api_id id) { - ESP_RETURN_ON_ERROR(sync.notify(sync.request), TAG, "failed to notify req"); pending_resp = id; + ESP_RETURN_ON_ERROR(sync.notify(sync.request), TAG, "failed to notify req"); ESP_RETURN_ON_ERROR(rpc.send(id), TAG, "Failed to send request"); return ESP_OK; } @@ -103,6 +112,7 @@ public: esp_err_t init() { ESP_RETURN_ON_FALSE(netif = wifi_remote_eppp_init(EPPP_CLIENT), ESP_FAIL, TAG, "Failed to connect to EPPP server"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, got_ip, this), TAG, "Failed to register event"); ESP_RETURN_ON_ERROR(sync.init(), TAG, "Failed to init sync primitives"); ESP_RETURN_ON_ERROR(rpc.init(), TAG, "Failed to init RPC engine"); return xTaskCreate(task, "client", 8192, this, 5, nullptr) == pdTRUE ? ESP_OK : ESP_FAIL; @@ -168,15 +178,28 @@ private: static void task(void *ctx) { auto instance = static_cast(ctx); - while (instance->perform() == ESP_OK) {} + do { + while (instance->perform() == ESP_OK) {} + } while (instance->restart() == ESP_OK); vTaskDelete(nullptr); } + esp_err_t restart() + { + rpc.deinit(); + ESP_RETURN_ON_ERROR(sync.wait_for(sync.restart, pdMS_TO_TICKS(10000)), TAG, "Didn't receive EPPP address in time"); + return rpc.init(); + } + static void got_ip(void *ctx, esp_event_base_t base, int32_t id, void *data) + { + auto instance = static_cast(ctx); + instance->sync.notify(instance->sync.restart); + } esp_netif_t *netif{nullptr}; }; namespace client { -RpcInstance instance; +constinit RpcInstance instance; } // namespace client RpcInstance *RpcEngine::init_client() @@ -196,20 +219,17 @@ RpcInstance *RpcEngine::init_client() cfg.clientkey_bytes = sizeof(client::key); cfg.common_name = "espressif.local"; - tls_ = esp_tls_init(); - if (!tls_) { - ESP_LOGE(TAG, "Failed to allocate esp_tls handle!"); - goto exit; - } - if (esp_tls_conn_new_sync(host, strlen(host), rpc_port, &cfg, tls_) <= 0) { - ESP_LOGE(TAG, "Failed to open a new connection %s", host); - goto exit; + ESP_RETURN_ON_FALSE(tls_ = esp_tls_init(), nullptr, TAG, "Failed to create ESP-TLS instance"); + int retries = 0; + while (esp_tls_conn_new_sync(host, strlen(host), rpc_port, &cfg, tls_) <= 0) { + esp_tls_conn_destroy(tls_); + tls_ = nullptr; + ESP_RETURN_ON_FALSE(retries++ < 3, nullptr, TAG, "Failed to open connection to %s", host); + ESP_LOGW(TAG, "Connection to RPC server failed! Will retry in %d second(s)", retries); + vTaskDelay(pdMS_TO_TICKS(1000 * retries)); + ESP_RETURN_ON_FALSE(tls_ = esp_tls_init(), nullptr, TAG, "Failed to create ESP-TLS instance"); } return &client::instance; -exit: - esp_tls_conn_destroy(tls_); - tls_ = nullptr; - return nullptr; } } // namespace eppp_rpc diff --git a/components/esp_wifi_remote/eppp/wifi_remote_rpc_impl.hpp b/components/esp_wifi_remote/eppp/wifi_remote_rpc_impl.hpp index beb87d407..30a0104db 100644 --- a/components/esp_wifi_remote/eppp/wifi_remote_rpc_impl.hpp +++ b/components/esp_wifi_remote/eppp/wifi_remote_rpc_impl.hpp @@ -9,8 +9,11 @@ namespace eppp_rpc { -const int rpc_port = 3333; +static constexpr int rpc_port = 3333; +/** + * @brief Currently supported RPC commands/events + */ enum class api_id : uint32_t { ERROR, UNDEF, @@ -35,6 +38,9 @@ struct RpcHeader { uint32_t size; } __attribute((__packed__)); +/** + * @brief Structure holding the outgoing or incoming parameter + */ template struct RpcData { RpcHeader head; @@ -54,11 +60,17 @@ struct RpcData { } } __attribute((__packed__)); +/** + * @brief Singleton holding the static data for either the client or server side + */ class RpcInstance; +/** + * @brief Engine that implements a simple RPC mechanism + */ class RpcEngine { public: - explicit RpcEngine(role r) : tls_(nullptr), role_(r) {} + constexpr explicit RpcEngine(role r) : tls_(nullptr), role_(r) {} esp_err_t init() { @@ -74,6 +86,19 @@ public: return instance == nullptr ? ESP_FAIL : ESP_OK; } + void deinit() + { + if (tls_ == nullptr) { + return; + } + if (role_ == role::CLIENT) { + esp_tls_conn_destroy(tls_); + } else if (role_ == role::SERVER) { + esp_tls_server_session_delete(tls_); + } + tls_ = nullptr; + } + template esp_err_t send(api_id id, T *t) { @@ -90,7 +115,7 @@ public: return ESP_OK; } - esp_err_t send(api_id id) // specialization for (void) + esp_err_t send(api_id id) // overload for (void) { RpcHeader head = {.id = id, .size = 0}; int len = esp_tls_conn_write(tls_, &head, sizeof(head)); diff --git a/components/esp_wifi_remote/eppp/wifi_remote_rpc_server.cpp b/components/esp_wifi_remote/eppp/wifi_remote_rpc_server.cpp index aed9faa54..0a8645d95 100644 --- a/components/esp_wifi_remote/eppp/wifi_remote_rpc_server.cpp +++ b/components/esp_wifi_remote/eppp/wifi_remote_rpc_server.cpp @@ -26,6 +26,7 @@ const char *TAG = "rpc_server"; const unsigned char ca_crt[] = "-----BEGIN CERTIFICATE-----\n" CONFIG_ESP_WIFI_REMOTE_EPPP_CLIENT_CA "\n-----END CERTIFICATE-----"; const unsigned char crt[] = "-----BEGIN CERTIFICATE-----\n" CONFIG_ESP_WIFI_REMOTE_EPPP_SERVER_CRT "\n-----END CERTIFICATE-----"; const unsigned char key[] = "-----BEGIN RSA PRIVATE KEY-----\n" CONFIG_ESP_WIFI_REMOTE_EPPP_SERVER_KEY "\n-----END RSA PRIVATE KEY-----"; +// TODO: Add option to supply keys and certs via a global symbol (file) } @@ -53,7 +54,7 @@ private: { auto instance = static_cast(ctx); while (instance->perform() == ESP_OK) {} - vTaskDelete(nullptr); + esp_restart(); } esp_err_t start_server() { @@ -181,7 +182,7 @@ private: namespace server { -RpcInstance instance; +constinit RpcInstance instance; } RpcInstance *RpcEngine::init_server() diff --git a/components/esp_wifi_remote/examples/mqtt/CMakeLists.txt b/components/esp_wifi_remote/examples/mqtt/CMakeLists.txt index b07caaa52..500c42d77 100644 --- a/components/esp_wifi_remote/examples/mqtt/CMakeLists.txt +++ b/components/esp_wifi_remote/examples/mqtt/CMakeLists.txt @@ -1,23 +1,5 @@ # This project serves as a demo to enable using esp-mqtt on ESP platform targets as well as on linux cmake_minimum_required(VERSION 3.16) -if("${IDF_TARGET}" STREQUAL "linux") -# For linux-target we have two options: -# - With lwIP (must be defined on command line, e.g. idf.py -DWITH_LWIP=1) -# access networking from linux `tap` interface (TAP networking mode) -# - Without lwIP (must be defined on command line, e.g. idf.py -DWITH_LWIP=0) -# no designated interface, accesses user network via linux/socket sys calls - if(WITH_LWIP STREQUAL 1) - set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_tapif_io - "../../common_components/linux_compat/esp_timer") - set(COMPONENTS main esp_netif lwip protocol_examples_tapif_io startup esp_hw_support esp_system nvs_flash mqtt esp_timer) - else() - list(APPEND EXTRA_COMPONENT_DIRS - "../../common_components/linux_compat/esp_timer" - "$ENV{IDF_PATH}/examples/protocols/linux_stubs/esp_stubs") - set(COMPONENTS main nvs_flash esp-tls esp_stubs mqtt protocol_examples_common esp_timer) - endif() -endif() - include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp_mqtt_demo) diff --git a/components/esp_wifi_remote/examples/mqtt/README.md b/components/esp_wifi_remote/examples/mqtt/README.md index 3277edb00..0c81f4e21 100644 --- a/components/esp_wifi_remote/examples/mqtt/README.md +++ b/components/esp_wifi_remote/examples/mqtt/README.md @@ -4,40 +4,28 @@ This is a simple mqtt demo, that connects to WiFi AP first. This application has ## Overview -This is a simple example demonstrating connecting to an MQTT broker, subscribing and publishing some data. -This example uses IDF build system and could be configured to be build and executed: -* for any ESP32 family chip -* for linux target +When running this example on a target that doesn't natively support WiFi, please make sure that the remote target (slave application) is connected to your chipset via the configured transport interface. -## How to use example +Connection to the slave device also depends on RPC library used. It is recommended to use [`esp_hosted`](https://github.com/espressif/esp-hosted). Alternatively you can use [`eppp_link`](https://components.espressif.com/components/espressif/eppp_link). -### Hardware Required +Please note, that `esp_hosted` as a component is currently WIP, so the `wifi_remote` defaults to `eppp`, for now. -To run this example, you need any ESP32 development board or just PC/virtual machine/container running linux operating system. +## HW connection -### Host build modes +We currently support only `UART` transport, so the connection is very simple. You only need to connect Rx, Tx and GND with the remote target. +You need to configure these fields according to your connection: +* CONFIG_ESP_WIFI_REMOTE_EPPP_UART_TX_PIN +* CONFIG_ESP_WIFI_REMOTE_EPPP_UART_RX_PIN -Linux build is supported in these two modes: -* `WITH_LWIP=0`: Without lwIP component. The project uses linux BSD socket interface to interact with TCP/IP stack. There's no connection phase, we use the host network as users. This mode is often referred to as user-side networking. -* `WITH_LWIP=1`: Including lwIP component, which is added to the list of required components and compiled on host. In this mode, we have to map the host network (linux TCP/IP stack) to the target network (lwip). We use IDF's [`tapif_io`](https://github.com/espressif/esp-idf/tree/master/examples/common_components/protocol_examples_tapif_io) component to create a network interface, which will be used to pass packets to and from the simulated target. Please refer to the [README](https://github.com/espressif/esp-idf/tree/master/examples/common_components/protocol_examples_tapif_io#readme) for more details about the host side networking. +## SW configuration -### Building on linux +The RPC mechanism between the host and the slave micro uses TLS with mutual authentication, so you would have to configure certificates and keys for both parties. This application -- host target -- is considered RPC client, so it needs client's certificate and key, as well as the CA certificate to validate the server (slave application). +If self-signed certificates are acceptable, you can use [generate_test_certs](../test_certs/generate_test_certs.sh) script to generate both the CA and the keys itself and convert them to the PEM format that's accepted by the EPPP RPC engine. +You will have to configure these options: +* CONFIG_ESP_WIFI_REMOTE_EPPP_SERVER_CA +* CONFIG_ESP_WIFI_REMOTE_EPPP_CLIENT_CRT +* CONFIG_ESP_WIFI_REMOTE_EPPP_CLIENT_KEY -1) Configure linux target -```bash -idf.py --preview set-target linux -``` +## Setting up slave device -2) Build the project with preferred components (with or without lwip) -```bash -idf.py -DWITH_LWIP=0 build # Building without lwip (user networking) -idf.py -DWITH_LWIP=1 build # Building with lwip (TAP networking) -``` - -3) Run the project - -It is possible to run the project elf file directly, or using `idf.py` monitor target (no need to flash): -```bash -idf.py monitor -``` -idf.py -DWITH_LWIP=0 build # Building without lwip (user networking) +You need to set up the connection and configuration in a similar way on the slave part (connection pins + certificates and keys). Please refer to the [slave_application](../server/README.md) README for more information. diff --git a/components/esp_wifi_remote/examples/server/README.md b/components/esp_wifi_remote/examples/server/README.md index a8ff85f9f..f6e63d99b 100644 --- a/components/esp_wifi_remote/examples/server/README.md +++ b/components/esp_wifi_remote/examples/server/README.md @@ -1,7 +1,21 @@ +# WiFi remote EPPP RPC server -# Wi-Fi station to PPPoS server +This is a standalone application serving as the slave device for `esp_wifi_remote` users (with `eppp` RPC). -This example demonstrate using NAPT to bring connectivity from WiFi station to PPPoS server. +## Overview -This example expect a PPPoS client to connect to the server and use the connectivity. -The client could be a Linux computer with `pppd` service or another microcontroller with PPP client (or another ESP32 with not WiFi interface) +You need to configure and connect a slave device to the `esp_wifi_remote` host and run this application. Please fallow carefully these guidelines on HW connection and configuration of the slave device, based on the host device. + +## HW connection + +We currently support only `UART` transport you just need to connect Rx, Tx and GND and configure these fields accordingly: +* CONFIG_ESP_WIFI_REMOTE_EPPP_UART_TX_PIN +* CONFIG_ESP_WIFI_REMOTE_EPPP_UART_RX_PIN + +## SW configuration + +You will have to install server side certificates and keys, as well as the CA which should verify the client side. +Please configure these options: +* CONFIG_ESP_WIFI_REMOTE_EPPP_CLIENT_CA +* CONFIG_ESP_WIFI_REMOTE_EPPP_SERVER_CRT +* CONFIG_ESP_WIFI_REMOTE_EPPP_SERVER_KEY diff --git a/components/esp_wifi_remote/idf_component.yml b/components/esp_wifi_remote/idf_component.yml index 9acde64a7..826097bc8 100644 --- a/components/esp_wifi_remote/idf_component.yml +++ b/components/esp_wifi_remote/idf_component.yml @@ -5,6 +5,6 @@ dependencies: espressif/eppp_link: version: '0.0.1' idf: - version: '5.3' + version: '>=5.3' # espressif/esp_hosted: # version: '*' diff --git a/components/esp_wifi_remote/test/smoke_test/main/idf_component.yml b/components/esp_wifi_remote/test/smoke_test/main/idf_component.yml index ecffade56..cb6183a90 100644 --- a/components/esp_wifi_remote/test/smoke_test/main/idf_component.yml +++ b/components/esp_wifi_remote/test/smoke_test/main/idf_component.yml @@ -2,7 +2,7 @@ dependencies: ## Required IDF version idf: - version: "5.3" + version: '>=5.3' espressif/esp_wifi_remote: version: "*" override_path: ../../..