fix(wifi_remote): Make services restartable, code cleanup

This commit is contained in:
David Cermak
2024-04-11 15:44:47 +02:00
parent d2b7c55b89
commit 6c82ce2915
11 changed files with 122 additions and 111 deletions

View File

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

View File

@ -15,21 +15,7 @@ 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
help
Select type of EPPP transport
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
@ -43,7 +29,6 @@ endchoice
range 0 31
help
Pin number of UART RX.
endif
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

View File

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

View File

@ -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<typename T>
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<T>(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<RpcInstance *>(ctx);
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<RpcInstance *>(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;
}
return &client::instance;
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;
return 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;
}
} // namespace eppp_rpc

View File

@ -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<typename T>
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<typename T>
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));

View File

@ -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<RpcInstance *>(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()

View File

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

View File

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

View File

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

View File

@ -5,6 +5,6 @@ dependencies:
espressif/eppp_link:
version: '0.0.1'
idf:
version: '5.3'
version: '>=5.3'
# espressif/esp_hosted:
# version: '*'

View File

@ -2,7 +2,7 @@
dependencies:
## Required IDF version
idf:
version: "5.3"
version: '>=5.3'
espressif/esp_wifi_remote:
version: "*"
override_path: ../../..