From 628283eef9166764c2831c6cf319d3466270d255 Mon Sep 17 00:00:00 2001 From: Georgii Larionenko <41651290+glarionenko@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:18:42 +0300 Subject: [PATCH 1/6] Add REST Wi-Fi control example with web UI for eppp_link Introduces a new example 'slave_wifi_rest_api' for the eppp_link component, providing a REST API for Wi-Fi management (status, scan, connect, disconnect) and a ready-to-use HTML web panel. Includes CMake, Kconfig, and configuration files for multiple transports, as well as implementation for both direct and channel-based Wi-Fi control. Enables dynamic Wi-Fi reconfiguration and CORS support for browser-based management. --- .../slave_wifi_rest_api/CMakeLists.txt | 6 + .../examples/slave_wifi_rest_api/README.md | 102 +++++ .../slave_wifi_rest_api/main/CMakeLists.txt | 10 + .../main/Kconfig.projbuild | 111 +++++ .../main/channel_wifi_station.c | 169 ++++++++ .../slave_wifi_rest_api/main/eppp_slave.c | 393 ++++++++++++++++++ .../main/idf_component.yml | 4 + .../slave_wifi_rest_api/sdkconfig.ci.eth | 2 + .../slave_wifi_rest_api/sdkconfig.ci.sdio | 2 + .../slave_wifi_rest_api/sdkconfig.ci.spi | 1 + .../slave_wifi_rest_api/sdkconfig.ci.uart | 1 + .../slave_wifi_rest_api/sdkconfig.defaults | 6 + .../slave_wifi_rest_api/webPanel/README.md | 79 ++++ .../webPanel/webPanel.html | 110 +++++ 14 files changed, 996 insertions(+) create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/CMakeLists.txt create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/README.md create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/main/CMakeLists.txt create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/main/Kconfig.projbuild create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/main/channel_wifi_station.c create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/main/eppp_slave.c create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/main/idf_component.yml create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.eth create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.sdio create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.spi create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.uart create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.defaults create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md create mode 100644 components/eppp_link/examples/slave_wifi_rest_api/webPanel/webPanel.html diff --git a/components/eppp_link/examples/slave_wifi_rest_api/CMakeLists.txt b/components/eppp_link/examples/slave_wifi_rest_api/CMakeLists.txt new file mode 100644 index 0000000000..144b9e1a21 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pppos_slave) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/README.md b/components/eppp_link/examples/slave_wifi_rest_api/README.md new file mode 100644 index 0000000000..da0f3e8757 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/README.md @@ -0,0 +1,102 @@ +# ESP32 eppp\_slave Example – Modifications and REST Wi-Fi Control + +This document describes the key modifications and usage instructions for the enhanced `eppp_slave` example, enabling REST-based Wi-Fi configuration and monitoring. + +--- + +## Overview + +This version extends the original `eppp_slave` PPP example with a full-featured REST API for Wi-Fi management. All new functionality is backward compatible; Wi-Fi can still be set via Kconfig, but runtime control is now possible from your browser or scripts. + +--- + +## Key Changes + +### 1. REST API for Wi-Fi + +* Added HTTP server (`esp_http_server`) and JSON parsing (`cJSON`). +* REST endpoints: + + * `GET /wifi/status` — current status (connected/disconnected, SSID, RSSI). + * `GET /wifi/scan` — scan available Wi-Fi networks. + * `POST /wifi/connect` — connect to network (JSON: `{ "ssid": ..., "password": ... }`). + * `POST /wifi/disconnect` — disconnect from Wi-Fi. + * `OPTIONS /*` — CORS preflight for all URIs. + +### 2. Dynamic Wi-Fi Reconfiguration + +* Supports runtime change of Wi-Fi credentials without reboot or reflash. +* Connect/disconnect sequence is carefully synchronized using event groups for stability. +* If no default SSID is set, the device waits for configuration via REST. + +### 3. Event Management Improvements + +* Uses EventGroup bits to distinguish connection, failure, and disconnection events. +* Manual reconnect flag to suppress auto-retry logic during REST-initiated reconnects. + +### 4. CORS Support + +* All REST responses include `Access-Control-Allow-Origin: *` for easy browser access. + +--- + +## How to Use + +### Build & Flash + +* Add `esp_http_server` and `cJSON` to your project. +* Build and flash as usual with ESP-IDF. + +### Default Startup + +* If Wi-Fi SSID and password are set in `sdkconfig`, ESP32 tries to connect at startup. +* If no SSID set, device waits for Wi-Fi config from REST client. + +### REST API Usage + +Interact from browser, curl, or your own UI: + +* **Get status:** + + ```bash + curl http:///wifi/status + ``` +* **Scan networks:** + + ```bash + curl http:///wifi/scan + ``` +* **Connect:** + + ```bash + curl -X POST http:///wifi/connect -H "Content-Type: application/json" -d '{"ssid":"YOUR_SSID","password":"YOUR_PASS"}' + ``` +* **Disconnect:** + + ```bash + curl -X POST http:///wifi/disconnect + ``` + +> See [README.md](./README.md) for a ready-to-use web UI! + +--- + +## Implementation Notes + +* All Wi-Fi connect/disconnect operations are handled asynchronously and robustly. +* If REST connect/disconnect is called in the middle of another operation, API will wait for proper sequence. +* All actions are CORS-enabled for direct use from web apps. +* Wi-Fi scan returns SSID, RSSI, and auth mode for each visible AP (max 16). + +--- + +## Example: Integrating with Your UI + +You can use the provided web UI (see `README.md`) or integrate these endpoints into any admin dashboard, mobile app, or automation script. + +--- + +## Credits + +* Enhanced by \glarionenko, \2025 +* Based on original code © 2023-2025 Espressif Systems (Shanghai) CO LTD diff --git a/components/eppp_link/examples/slave_wifi_rest_api/main/CMakeLists.txt b/components/eppp_link/examples/slave_wifi_rest_api/main/CMakeLists.txt new file mode 100644 index 0000000000..83ef0b1d42 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/main/CMakeLists.txt @@ -0,0 +1,10 @@ +if(CONFIG_EXAMPLE_WIFI_OVER_EPPP_CHANNEL) + set(wifi_over_channels channel_wifi_station.c) +endif() + +idf_component_register( + SRCS eppp_slave.c + ${wifi_over_channels} + INCLUDE_DIRS "." + REQUIRES json esp_wifi nvs_flash esp_http_server +) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/main/Kconfig.projbuild b/components/eppp_link/examples/slave_wifi_rest_api/main/Kconfig.projbuild new file mode 100644 index 0000000000..532566d303 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/main/Kconfig.projbuild @@ -0,0 +1,111 @@ +menu "Example Configuration" + + config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + + config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + + config ESP_MAXIMUM_RETRY + int "Maximum retry" + default 5 + help + Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent. + + config EXAMPLE_SPI_HOST + int "SPI Host" + depends on EPPP_LINK_DEVICE_SPI + default 1 + range 0 2 + help + SPI host to use (SPI1_HOST=0, SPI2_HOST=1, SPI3_HOST=2). + + config EXAMPLE_SPI_MOSI_PIN + int "MOSI Pin Number" + depends on EPPP_LINK_DEVICE_SPI + default 23 + range 0 39 + help + Pin number of SPI MOSI. + + config EXAMPLE_SPI_MISO_PIN + int "MISO Pin Number" + depends on EPPP_LINK_DEVICE_SPI + default 19 + range 0 39 + help + Pin number of SPI MISO. + + config EXAMPLE_SPI_SCLK_PIN + int "SCLK Pin Number" + depends on EPPP_LINK_DEVICE_SPI + default 18 + range 0 39 + help + Pin number of SPI SCLK. + + config EXAMPLE_SPI_CS_PIN + int "CS Pin Number" + depends on EPPP_LINK_DEVICE_SPI + default 5 + range 0 39 + help + Pin number of SPI CS. + + config EXAMPLE_SPI_INTR_PIN + int "Interrupt Pin Number" + depends on EPPP_LINK_DEVICE_SPI + default 17 + range 0 39 + help + Pin number of SPI interrupt. + + config EXAMPLE_SPI_FREQUENCY + int "SPI Frequency (Hz)" + depends on EPPP_LINK_DEVICE_SPI + default 1000000 + range 100000 80000000 + help + SPI frequency in Hz. + + config EXAMPLE_UART_TX_PIN + int "TXD Pin Number" + depends on EPPP_LINK_DEVICE_UART + default 18 + range 0 31 + help + Pin number of UART TX. + + config EXAMPLE_UART_RX_PIN + int "RXD Pin Number" + depends on EPPP_LINK_DEVICE_UART + default 17 + range 0 31 + help + Pin number of UART RX. + + config EXAMPLE_UART_BAUDRATE + int "Baudrate" + depends on EPPP_LINK_DEVICE_UART + default 921600 + range 0 4000000 + help + Baudrate used by the PPP over UART + + config EXAMPLE_WIFI_OVER_EPPP_CHANNEL + bool "Use WiFi over EPPP channel" + default n + depends on EPPP_LINK_CHANNELS_SUPPORT + help + Enable this option to use WiFi over EPPP channel. + If this option is enabled, the example will only start the Wi-Fi driver, + but the Wi-Fi netif will reside on client's end and will channel + the Rx and Tx data via EPPP channels. + +endmenu diff --git a/components/eppp_link/examples/slave_wifi_rest_api/main/channel_wifi_station.c b/components/eppp_link/examples/slave_wifi_rest_api/main/channel_wifi_station.c new file mode 100644 index 0000000000..9e41364c18 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/main/channel_wifi_station.c @@ -0,0 +1,169 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include +#include "esp_system.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "eppp_link.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_private/wifi.h" + +#define CHAT_CHANNEL 1 +#define WIFI_CHANNEL 2 + +typedef enum { + UNKNOWN, + HELLO, + START, + ERROR, +} state_t; + +typedef struct context { + eppp_channel_fn_t transmit; + EventGroupHandle_t flags; + state_t state; + esp_netif_t *eppp; +} context_t; + +#define HELLO_BIT BIT0 +#define START_BIT BIT1 +#define CONNECT_BIT BIT2 +#define DISCONNECT_BIT BIT3 + +#define ALL_BITS (HELLO_BIT | START_BIT | CONNECT_BIT | DISCONNECT_BIT) + +static const char *TAG = "eppp_host_example_with_channels"; +static context_t *s_eppp_channel_ctx = NULL; + +static esp_err_t eppp_receive(esp_netif_t *netif, int nr, void *buffer, size_t len) +{ + context_t *ctx = eppp_get_context(netif); + if (nr == CHAT_CHANNEL) { + ESP_LOGI(TAG, "Received channel=%d len=%d %.*s", nr, (int)len, (int)len, (char *)buffer); + const char hello[] = "Hello server"; + const char mac[] = "Get MAC"; + if (len == sizeof(hello) && memcmp(buffer, hello, len) == 0) { + if (ctx->state == HELLO) { + xEventGroupSetBits(ctx->flags, HELLO_BIT); + } else { + ctx->state = ERROR; + } + } else if (len == sizeof(mac) && memcmp(buffer, mac, 5) == 0) { + if (ctx->state == HELLO) { + xEventGroupSetBits(ctx->flags, START_BIT); + } else { + ctx->state = ERROR; + } + } + } else if (nr == WIFI_CHANNEL) { + ESP_LOGD(TAG, "Received WIFI channel=%d len=%d", nr, (int)len); + ESP_LOG_BUFFER_HEXDUMP("wifi-receive", buffer, len, ESP_LOG_VERBOSE); + return esp_wifi_internal_tx(WIFI_IF_STA, buffer, len); + } else { + ESP_LOGE(TAG, "Incorrect channel number %d", nr); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t wifi_receive(void *buffer, uint16_t len, void *eb) +{ + s_eppp_channel_ctx->transmit(s_eppp_channel_ctx->eppp, WIFI_CHANNEL, buffer, len); + esp_wifi_internal_free_rx_buffer(eb); + return ESP_OK; +} + +static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + context_t *ctx = arg; + ESP_LOGI(TAG, "event_handler: event_base=%s event_id=%d", event_base, event_id); + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + ESP_LOGI(TAG, "WIFI start event"); + esp_wifi_connect(); + xEventGroupSetBits(ctx->flags, CONNECT_BIT); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGI(TAG, "connect to the AP fail"); + xEventGroupSetBits(ctx->flags, DISCONNECT_BIT); + } +} + + +static void init_wifi_driver(context_t *ctx) +{ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + event_handler, ctx)); + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_ESP_WIFI_SSID, + .password = CONFIG_ESP_WIFI_PASSWORD, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); +} + +void station_over_eppp_channel(void *arg) +{ + __attribute__((unused)) esp_err_t ret; + context_t ctx = { + .transmit = NULL, + .flags = NULL, + .state = UNKNOWN, + .eppp = (esp_netif_t *)arg + }; + ESP_GOTO_ON_FALSE(ctx.flags = xEventGroupCreate(), ESP_FAIL, err, TAG, "Failed to create event group"); + ESP_GOTO_ON_ERROR(eppp_add_channels(ctx.eppp, &ctx.transmit, eppp_receive, &ctx), err, TAG, "Failed to add channels"); + ESP_GOTO_ON_FALSE(ctx.transmit, ESP_FAIL, err, TAG, "Channel tx function is not set"); + init_wifi_driver(&ctx); + + while (1) { + EventBits_t bits = xEventGroupWaitBits(ctx.flags, ALL_BITS, pdTRUE, pdFALSE, pdMS_TO_TICKS(1000)); + if (bits & HELLO_BIT) { + ESP_LOGI(TAG, "Hello from client received"); + const char hello[] = "Hello client"; + ctx.transmit(ctx.eppp, CHAT_CHANNEL, (void*)hello, sizeof(hello)); + } else if (bits & START_BIT) { + ctx.state = START; + ESP_LOGI(TAG, "Starting WIFI"); + uint8_t mac[6]; + if (esp_wifi_get_mac(WIFI_IF_STA, mac) != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_get_mac failed"); + ctx.state = ERROR; + continue; + } + char mac_data[5 /* MAC: */ + 6 * 2 /* 6 bytes per char */ + 5 /* : */ + 1 /* \0 */]; + sprintf(mac_data, "MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + ESP_LOGI(TAG, "Sending MAC: %.*s", (int)sizeof(mac_data), mac_data); + ctx.transmit(ctx.eppp, CHAT_CHANNEL, (void*)mac_data, sizeof(mac_data)); + ret = esp_wifi_start(); + ESP_LOGI(TAG, "WIFI start result: %d", ret); + s_eppp_channel_ctx = &ctx; + esp_wifi_internal_reg_rxcb(WIFI_IF_STA, wifi_receive); + } else if (bits & CONNECT_BIT) { + ESP_LOGI(TAG, "WIFI connected"); + const char connected[] = "Connected"; + ctx.transmit(ctx.eppp, CHAT_CHANNEL, (void*)connected, sizeof(connected)); + } else if (bits & DISCONNECT_BIT) { + const char disconnected[] = "Disconnected"; + ctx.transmit(ctx.eppp, CHAT_CHANNEL, (void*)disconnected, sizeof(disconnected)); + } else if (ctx.state != START) { + ctx.state = HELLO; + const char up[] = "Server up"; + esp_wifi_disconnect(); + esp_wifi_stop(); + ctx.transmit(ctx.eppp, CHAT_CHANNEL, (void*)up, sizeof(up)); + } + } + +err: + vTaskDelete(NULL); +} diff --git a/components/eppp_link/examples/slave_wifi_rest_api/main/eppp_slave.c b/components/eppp_link/examples/slave_wifi_rest_api/main/eppp_slave.c new file mode 100644 index 0000000000..41509e9324 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/main/eppp_slave.c @@ -0,0 +1,393 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_check.h" +#include "nvs_flash.h" +#include "eppp_link.h" +#include "inttypes.h" +#include "esp_http_server.h" +#include "cJSON.h" + +static const char *TAG = "eppp_slave"; + +#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_EXAMPLE_WIFI_OVER_EPPP_CHANNEL) + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t s_wifi_event_group; + +/* The event group allows multiple bits for each event, but we only care about two events: + * - we are connected to the AP with an IP + * - we failed to connect after the maximum amount of retries */ +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 +#define WIFI_DISCONNECT_DONE_BIT BIT2 + +static volatile bool s_manual_reconnect = false; + +static int s_retry_num = 0; + +static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + wifi_config_t cfg; + if (esp_wifi_get_config(WIFI_IF_STA, &cfg) == ESP_OK && cfg.sta.ssid[0] != 0) { + esp_wifi_connect(); + } else { + ESP_LOGI(TAG, "STA_START: no stored SSID, waiting for config"); + } + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + wifi_event_sta_disconnected_t *disc = (wifi_event_sta_disconnected_t *)event_data; + ESP_LOGI(TAG, "DISCONNECTED, reason=%d", disc ? disc->reason : -1); + + xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + xEventGroupSetBits(s_wifi_event_group, WIFI_DISCONNECT_DONE_BIT); + + if (!s_manual_reconnect) { + if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) { + esp_err_t err = esp_wifi_connect(); + if (err == ESP_ERR_WIFI_CONN) { + ESP_LOGW(TAG, "esp_wifi_connect(): already connecting"); + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_connect() failed: 0x%x", (unsigned)err); + } else { + s_retry_num++; + ESP_LOGI(TAG, "retry to connect to the AP (%d)", s_retry_num); + } + } else { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + } + } else { + s_retry_num = 0; + } + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *ev = (ip_event_got_ip_t *)event_data; + ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&ev->ip_info.ip)); + s_retry_num = 0; + xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT); + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + ESP_LOGI(TAG, "CONNECTED to AP"); + } else { + ESP_LOGI(TAG, "Unhandled event: base=%s id=%" PRIi32, event_base, event_id); + } +} + + + +void init_network_interface(void) +{ + s_wifi_event_group = xEventGroupCreate(); + + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_t instance_any_id; + esp_event_handler_instance_t instance_got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &event_handler, + NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &event_handler, + NULL, + &instance_got_ip)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_ESP_WIFI_SSID, + .password = CONFIG_ESP_WIFI_PASSWORD, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + + if (wifi_config.sta.ssid[0] == 0) { + ESP_LOGW(TAG, "No default SSID configured, skipping wait; configure via REST"); + return; + } + /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum + * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually + * happened. */ + if (bits & WIFI_CONNECTED_BIT) { + ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", + CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); + } else if (bits & WIFI_FAIL_BIT) { + ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", + CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); + } else { + ESP_LOGE(TAG, "UNEXPECTED EVENT"); + } +} +#else + +// If the SoC does not have WiFi capabilities, we can initialize a different network interface, this function is a placeholder for that purpose. +// This function is also a no-op if EXAMPLE_WIFI_OVER_EPPP_CHANNEL==1, since the Wi-Fi network interface will live on the other peer (on the host side). +void init_network_interface(void) +{ +} + +#endif // SoC WiFi capable chip || WiFi over EPPP channel + +static const char *TAG_REST = "wifi_rest"; + +/* ---------- WiFi REST API --------- */ +esp_err_t wifi_status_handler(httpd_req_t *req) +{ + wifi_ap_record_t ap_info; + char buf[128]; + + if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { + snprintf(buf, sizeof(buf), + "{\"status\":\"connected\",\"ssid\":\"%s\",\"rssi\":%d}", + (char *)ap_info.ssid, ap_info.rssi); + } else { + snprintf(buf, sizeof(buf), "{\"status\":\"disconnected\"}"); + } + httpd_resp_set_type(req, "application/json"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_sendstr(req, buf); + return ESP_OK; +} + +esp_err_t wifi_scan_handler(httpd_req_t *req) +{ + wifi_scan_config_t scan_conf = {0}; + wifi_ap_record_t ap_records[16]; + uint16_t ap_num = 16; + + esp_wifi_scan_start(&scan_conf, true); + esp_wifi_scan_get_ap_records(&ap_num, ap_records); + + cJSON *root = cJSON_CreateArray(); + for (int i = 0; i < ap_num; ++i) { + cJSON *ap = cJSON_CreateObject(); + cJSON_AddStringToObject(ap, "ssid", (char*)ap_records[i].ssid); + cJSON_AddNumberToObject(ap, "rssi", ap_records[i].rssi); + cJSON_AddNumberToObject(ap, "auth", ap_records[i].authmode); + cJSON_AddItemToArray(root, ap); + } + char *resp = cJSON_Print(root); + httpd_resp_set_type(req, "application/json"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_sendstr(req, resp); + + cJSON_Delete(root); + free(resp); + return ESP_OK; +} + +esp_err_t wifi_connect_handler(httpd_req_t *req) +{ + char buf[256] = {0}; + httpd_req_recv(req, buf, sizeof(buf)-1); + + cJSON *json = cJSON_Parse(buf); + if (!json) { /* ... 400 ... */ return ESP_FAIL; } + + cJSON *ssid = cJSON_GetObjectItem(json, "ssid"); + cJSON *pass = cJSON_GetObjectItem(json, "password"); + if (!ssid || !ssid->valuestring) { /* ... 400 ... */ cJSON_Delete(json); return ESP_FAIL; } + + wifi_config_t wifi_config = {0}; + strncpy((char *)wifi_config.sta.ssid, ssid->valuestring, sizeof(wifi_config.sta.ssid)-1); + if (pass && pass->valuestring) { + strncpy((char *)wifi_config.sta.password, pass->valuestring, sizeof(wifi_config.sta.password)-1); + } + cJSON_Delete(json); + + s_manual_reconnect = true; + + xEventGroupClearBits(s_wifi_event_group, WIFI_DISCONNECT_DONE_BIT); + esp_err_t err = esp_wifi_disconnect(); + if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_CONNECT) { + s_manual_reconnect = false; + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "disconnect failed"); + } + + (void)xEventGroupWaitBits(s_wifi_event_group, WIFI_DISCONNECT_DONE_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(5000)); + + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); + + err = esp_wifi_connect(); + if (err == ESP_ERR_WIFI_CONN) { + ESP_LOGW(TAG, "Already connecting; continue"); + err = ESP_OK; + } + s_manual_reconnect = false; + + if (err != ESP_OK) { + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "connect failed"); + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_sendstr(req, "{\"result\":\"ok\"}"); + return ESP_OK; +} + + +esp_err_t wifi_disconnect_handler(httpd_req_t *req) +{ + s_manual_reconnect = true; + xEventGroupClearBits(s_wifi_event_group, WIFI_DISCONNECT_DONE_BIT); + + esp_err_t err = esp_wifi_disconnect(); + if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_CONNECT) { + s_manual_reconnect = false; + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "disconnect failed"); + } + + (void)xEventGroupWaitBits(s_wifi_event_group, WIFI_DISCONNECT_DONE_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(5000)); + + s_manual_reconnect = false; + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_sendstr(req, "{\"result\":\"ok\"}"); + return ESP_OK; +} + + +static esp_err_t cors_preflight_handler(httpd_req_t *req) +{ + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type"); + httpd_resp_sendstr(req, ""); + return ESP_OK; +} + +void start_rest_server() +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.uri_match_fn = httpd_uri_match_wildcard; + httpd_handle_t server = NULL; + httpd_start(&server, &config); + + // CORS pre-flight handler for OPTIONS + // Register OPTIONS handler on all URIs + httpd_uri_t uri_options = { + .uri = "/*", + .method = HTTP_OPTIONS, + .handler = cors_preflight_handler + }; + httpd_register_uri_handler(server, &uri_options); + + + // /wifi/status + httpd_uri_t uri_status = { + .uri = "/wifi/status", + .method = HTTP_GET, + .handler = wifi_status_handler + }; + httpd_register_uri_handler(server, &uri_status); + + // /wifi/scan + httpd_uri_t uri_scan = { + .uri = "/wifi/scan", + .method = HTTP_GET, + .handler = wifi_scan_handler + }; + httpd_register_uri_handler(server, &uri_scan); + + // /wifi/connect + httpd_uri_t uri_connect = { + .uri = "/wifi/connect", + .method = HTTP_POST, + .handler = wifi_connect_handler + }; + httpd_register_uri_handler(server, &uri_connect); + + // /wifi/disconnect + httpd_uri_t uri_disconnect = { + .uri = "/wifi/disconnect", + .method = HTTP_POST, + .handler = wifi_disconnect_handler + }; + httpd_register_uri_handler(server, &uri_disconnect); + + ESP_LOGI(TAG_REST, "REST Wi-Fi API started on PPPoS IP"); +} + + +void station_over_eppp_channel(void *arg); + +void app_main(void) +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + init_network_interface(); + + start_rest_server(); + eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG(); +#if CONFIG_EPPP_LINK_DEVICE_SPI + config.transport = EPPP_TRANSPORT_SPI; + config.spi.is_master = false; + config.spi.host = CONFIG_EXAMPLE_SPI_HOST; + config.spi.mosi = CONFIG_EXAMPLE_SPI_MOSI_PIN; + config.spi.miso = CONFIG_EXAMPLE_SPI_MISO_PIN; + config.spi.sclk = CONFIG_EXAMPLE_SPI_SCLK_PIN; + config.spi.cs = CONFIG_EXAMPLE_SPI_CS_PIN; + config.spi.intr = CONFIG_EXAMPLE_SPI_INTR_PIN; + config.spi.freq = CONFIG_EXAMPLE_SPI_FREQUENCY; +#elif CONFIG_EPPP_LINK_DEVICE_UART + config.transport = EPPP_TRANSPORT_UART; + config.uart.tx_io = CONFIG_EXAMPLE_UART_TX_PIN; + config.uart.rx_io = CONFIG_EXAMPLE_UART_RX_PIN; + config.uart.baud = CONFIG_EXAMPLE_UART_BAUDRATE; +#elif CONFIG_EPPP_LINK_DEVICE_SDIO + config.transport = EPPP_TRANSPORT_SDIO; +#endif + + esp_netif_t *eppp_netif = eppp_listen(&config); + if (eppp_netif == NULL) { + ESP_LOGE(TAG, "Failed to setup connection"); + return ; + } +#ifdef CONFIG_EXAMPLE_WIFI_OVER_EPPP_CHANNEL + station_over_eppp_channel(eppp_netif); +#else + { + esp_err_t err = esp_netif_napt_enable(eppp_netif); + if (err == ESP_OK) { + ESP_LOGI(TAG, "NAPT enabled on PPP interface"); + } else { + ESP_LOGE(TAG, "NAPT enable failed: %d", err); + } + } +#endif // CONFIG_EXAMPLE_WIFI_OVER_EPPP_CHANNEL +} \ No newline at end of file diff --git a/components/eppp_link/examples/slave_wifi_rest_api/main/idf_component.yml b/components/eppp_link/examples/slave_wifi_rest_api/main/idf_component.yml new file mode 100644 index 0000000000..7ecb517e8a --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../.." diff --git a/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.eth b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.eth new file mode 100644 index 0000000000..9b3f487dce --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.eth @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_EPPP_LINK_DEVICE_ETH=y diff --git a/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.sdio b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.sdio new file mode 100644 index 0000000000..395ff44868 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.sdio @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32c6" +CONFIG_EPPP_LINK_DEVICE_SDIO=y diff --git a/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.spi b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.spi new file mode 100644 index 0000000000..125cdee30a --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.spi @@ -0,0 +1 @@ +CONFIG_EPPP_LINK_DEVICE_SPI=y diff --git a/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.uart b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.uart new file mode 100644 index 0000000000..15d212a821 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.ci.uart @@ -0,0 +1 @@ +CONFIG_EPPP_LINK_DEVICE_UART=y diff --git a/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.defaults b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.defaults new file mode 100644 index 0000000000..f2afde6850 --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_LWIP_IP_FORWARD=y +CONFIG_LWIP_IPV4_NAPT=y +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_SERVER_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n diff --git a/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md b/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md new file mode 100644 index 0000000000..20df63f37f --- /dev/null +++ b/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md @@ -0,0 +1,79 @@ +# ESP32 Wi-Fi Manager Web Interface + +A simple HTML+JS page to manage ESP32 Wi-Fi connections via REST API. +Allows you to **scan networks**, **connect**, **disconnect**, and **monitor Wi-Fi status** directly from your browser. + +## Features + +* **View Wi-Fi status:** connected/disconnected, SSID, RSSI +* **Scan for available Wi-Fi networks** +* **Connect** to a Wi-Fi network by SSID and password +* **Disconnect** from Wi-Fi +* **Auto-fill SSID** from scanned networks list +* **Mobile-friendly**, lightweight, no build tools needed +* Works out of the box with the [modified ESP32 REST API firmware](../eppp_slave_modifications.md) + +## How to Use + +### 1. Configure ESP32 IP Address + +**Important:** +You must set the correct ESP32 IP address (from the PPP network, e.g., `192.168.11.2`) in the script before using the page. + +Find and update this line in your HTML: + +```js +const API_BASE = "http://"; +``` + +Replace `` with your ESP32's actual IP address. + +**Example:** + +```js +const API_BASE = "http://192.168.11.2"; +``` + +If you are unsure of the IP address, check your PPP server log, your router's DHCP leases, or the serial output of the ESP32. + +### 2. Open the Page + +* Open `index.html` in any modern browser (desktop or mobile). +* Make sure your computer or phone is in the same network as your ESP32. + +### 3. Usage + +* **Status:** See if ESP32 is connected and to which network. +* **Scan Networks:** Click "Scan Networks" to see available Wi-Fi networks. Click "Select" to autofill SSID in the form. +* **Connect to Wi-Fi:** Enter SSID and password (or use "Select"), then click "Connect". +* **Disconnect:** Click "Disconnect" to disconnect from Wi-Fi. +* Status refreshes automatically after actions. + +## Troubleshooting + +* **"ESP32 request error"** + + * Check that `API_BASE` is correct. + * Ensure ESP32 is running and accessible on the network. + * Make sure the REST API firmware is flashed and running. + * Check browser/network firewalls and NAT settings. + +* **No networks found** + + * Move ESP32 closer to Wi-Fi routers/APs. + * Check ESP32 logs for scan errors. + +* **CORS errors in browser** + + * The firmware should include `Access-Control-Allow-Origin: *` headers by default. + +## Customization + +* Style the page using the CSS in ` + + +

ESP32 Wi-Fi Manager

+
Status: ...
+ +
+ +

Connect to Wi-Fi

+
+ + + + +
+ + + + From 12e7015b6120e3282a0b1aebe65da93291444737 Mon Sep 17 00:00:00 2001 From: Georgii Larionenko <41651290+glarionenko@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:24:51 +0300 Subject: [PATCH 2/6] Update README with links to original and web panel docs Added a link to the original example and updated the web UI reference to point to the web panel documentation for better navigation and clarity. --- components/eppp_link/examples/slave_wifi_rest_api/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/README.md b/components/eppp_link/examples/slave_wifi_rest_api/README.md index da0f3e8757..24fc078d2b 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/README.md @@ -1,5 +1,7 @@ # ESP32 eppp\_slave Example – Modifications and REST Wi-Fi Control +[See the original example here](../slave/) + This document describes the key modifications and usage instructions for the enhanced `eppp_slave` example, enabling REST-based Wi-Fi configuration and monitoring. --- @@ -92,7 +94,8 @@ Interact from browser, curl, or your own UI: ## Example: Integrating with Your UI -You can use the provided web UI (see `README.md`) or integrate these endpoints into any admin dashboard, mobile app, or automation script. +You can use the provided web UI ([See documentation for the web panel](webPanel/README.md) +) or integrate these endpoints into any admin dashboard, mobile app, or automation script. --- From 544e1df797fb8199eaf8eae01a2a6c564715d12b Mon Sep 17 00:00:00 2001 From: Georgii Larionenko <41651290+glarionenko@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:33:14 +0300 Subject: [PATCH 3/6] Update README links for clarity and accuracy Revised links in the main and webPanel README files to reference the correct documentation and improve navigation. This enhances clarity for users following setup and usage instructions. --- components/eppp_link/examples/slave_wifi_rest_api/README.md | 6 +++--- .../examples/slave_wifi_rest_api/webPanel/README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/README.md b/components/eppp_link/examples/slave_wifi_rest_api/README.md index 24fc078d2b..81090a624e 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/README.md @@ -1,14 +1,14 @@ -# ESP32 eppp\_slave Example – Modifications and REST Wi-Fi Control +# ESP32 [eppp\_link\_slave](../slave/) Example – Modifications and REST Wi-Fi Control [See the original example here](../slave/) -This document describes the key modifications and usage instructions for the enhanced `eppp_slave` example, enabling REST-based Wi-Fi configuration and monitoring. +This document describes the key modifications and usage instructions for the enhanced [slave](../slave/) example, enabling REST-based Wi-Fi configuration and monitoring. --- ## Overview -This version extends the original `eppp_slave` PPP example with a full-featured REST API for Wi-Fi management. All new functionality is backward compatible; Wi-Fi can still be set via Kconfig, but runtime control is now possible from your browser or scripts. +This version extends the original [slave](../slave/) PPP example with a full-featured REST API for Wi-Fi management. All new functionality is backward compatible; Wi-Fi can still be set via Kconfig, but runtime control is now possible from your browser or scripts. --- diff --git a/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md b/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md index 20df63f37f..f370eccc03 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md @@ -11,7 +11,7 @@ Allows you to **scan networks**, **connect**, **disconnect**, and **monitor Wi-F * **Disconnect** from Wi-Fi * **Auto-fill SSID** from scanned networks list * **Mobile-friendly**, lightweight, no build tools needed -* Works out of the box with the [modified ESP32 REST API firmware](../eppp_slave_modifications.md) +* Works out of the box with the [modified ESP32 REST API firmware](../README.md) ## How to Use @@ -76,4 +76,4 @@ If you are unsure of the IP address, check your PPP server log, your router's DH ## Credits * Frontend by \glarionenko, \2025 -* Backend REST API — see [eppp\_slave\_modifications.md](../eppp_slave_modifications.md) +* Backend REST API — see [eppp\_link/slave\_wifi\_rest\_api/README.md](../README.md) From af4e921eb6976cb0d1da4dc8479e5f4efc966be9 Mon Sep 17 00:00:00 2001 From: Georgii Larionenko <41651290+glarionenko@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:35:13 +0300 Subject: [PATCH 4/6] Update README links in slave_wifi_rest_api example Fixed and clarified relative links in the main and webPanel README files for better navigation and accuracy. --- components/eppp_link/examples/slave_wifi_rest_api/README.md | 2 +- .../eppp_link/examples/slave_wifi_rest_api/webPanel/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/README.md b/components/eppp_link/examples/slave_wifi_rest_api/README.md index 81090a624e..0d8d38eec7 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/README.md @@ -1,4 +1,4 @@ -# ESP32 [eppp\_link\_slave](../slave/) Example – Modifications and REST Wi-Fi Control +# ESP32 [eppp\_link/slave](../slave/) Example – Modifications and REST Wi-Fi Control [See the original example here](../slave/) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md b/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md index f370eccc03..caee15ee58 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/webPanel/README.md @@ -11,7 +11,7 @@ Allows you to **scan networks**, **connect**, **disconnect**, and **monitor Wi-F * **Disconnect** from Wi-Fi * **Auto-fill SSID** from scanned networks list * **Mobile-friendly**, lightweight, no build tools needed -* Works out of the box with the [modified ESP32 REST API firmware](../README.md) +* Works out of the box with the [modified ESP32 REST API firmware](../) ## How to Use @@ -76,4 +76,4 @@ If you are unsure of the IP address, check your PPP server log, your router's DH ## Credits * Frontend by \glarionenko, \2025 -* Backend REST API — see [eppp\_link/slave\_wifi\_rest\_api/README.md](../README.md) +* Backend REST API — see [eppp\_link/slave\_wifi\_rest\_api/](../) From 1d79dc28f0fd6cfea6ed636abb7a2ec6f5218f89 Mon Sep 17 00:00:00 2001 From: Georgii Larionenko <41651290+glarionenko@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:36:26 +0300 Subject: [PATCH 5/6] Update README title for clarity Revised the README title to clarify that the example is based on eppp_link/slave, improving documentation accuracy. --- components/eppp_link/examples/slave_wifi_rest_api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/README.md b/components/eppp_link/examples/slave_wifi_rest_api/README.md index 0d8d38eec7..46269c3594 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/README.md @@ -1,4 +1,4 @@ -# ESP32 [eppp\_link/slave](../slave/) Example – Modifications and REST Wi-Fi Control +# ESP32 [eppp\_link/slave](../slave/) based Example – Modifications and REST Wi-Fi Control [See the original example here](../slave/) From 48f4849a74b4c575e50e792b8091f7081a7f72bf Mon Sep 17 00:00:00 2001 From: Georgii Larionenko <41651290+glarionenko@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:37:52 +0300 Subject: [PATCH 6/6] Update README.md --- components/eppp_link/examples/slave_wifi_rest_api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/eppp_link/examples/slave_wifi_rest_api/README.md b/components/eppp_link/examples/slave_wifi_rest_api/README.md index 46269c3594..f59788a6d4 100644 --- a/components/eppp_link/examples/slave_wifi_rest_api/README.md +++ b/components/eppp_link/examples/slave_wifi_rest_api/README.md @@ -79,7 +79,7 @@ Interact from browser, curl, or your own UI: curl -X POST http:///wifi/disconnect ``` -> See [README.md](./README.md) for a ready-to-use web UI! +> See [Web panel](webPanel/) for a ready-to-use web UI! ---