diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9261d60d5f..2c5b8ec94f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,7 @@ repos: hooks: - id: commit message scopes name: "commit message must be scoped with: mdns, modem, websocket, asio, mqtt_cxx, console, common" - entry: '\A(?!(feat|fix|ci|bump|test|docs)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples)\)\:)' + entry: '\A(?!(feat|fix|ci|bump|test|docs)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp_link)\)\:)' language: pygrep args: [--multiline] stages: [commit-msg] diff --git a/components/eppp_link/.cz.yaml b/components/eppp_link/.cz.yaml new file mode 100644 index 0000000000..abdb707180 --- /dev/null +++ b/components/eppp_link/.cz.yaml @@ -0,0 +1,8 @@ +--- +commitizen: + bump_message: 'bump(eppp_link): $current_version -> $new_version' + pre_bump_hooks: python ../../ci/changelog.py eppp_link + tag_format: epp_link-v$version + version: 0.0.1 + version_files: + - idf_component.yml diff --git a/components/eppp_link/CMakeLists.txt b/components/eppp_link/CMakeLists.txt new file mode 100644 index 0000000000..e7e9c1432e --- /dev/null +++ b/components/eppp_link/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "eppp_link.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES esp_netif esp_driver_spi esp_driver_gpio esp_timer driver) diff --git a/components/eppp_link/Kconfig b/components/eppp_link/Kconfig new file mode 100644 index 0000000000..4327cba334 --- /dev/null +++ b/components/eppp_link/Kconfig @@ -0,0 +1,35 @@ +menu "eppp_link" + + choice EPPP_LINK_DEVICE + prompt "Choose PPP device" + default EPPP_LINK_DEVICE_UART + help + Select which peripheral to use for PPP link + + config EPPP_LINK_DEVICE_UART + bool "UART" + help + Use UART. + + config EPPP_LINK_DEVICE_SPI + bool "SPI" + help + Use SPI. + endchoice + + config EPPP_LINK_CONN_MAX_RETRY + int "Maximum retry" + default 6 + help + Set the Maximum retry to infinitely avoid reconnecting + This is used only with the simplified API (eppp_connect() + and eppp_listen()) + + config EPPP_LINK_PACKET_QUEUE_SIZE + int "Packet queue size" + default 64 + help + Size of the Tx packet queue. + You can decrease the number for slower bit rates. + +endmenu diff --git a/components/eppp_link/LICENSE b/components/eppp_link/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/components/eppp_link/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/components/eppp_link/README.md b/components/eppp_link/README.md new file mode 100644 index 0000000000..51ba0ae6d5 --- /dev/null +++ b/components/eppp_link/README.md @@ -0,0 +1,44 @@ +# ESP PPP Link component (eppp_link) + +The component provides a general purpose connectivity engine between two micro-controllers, one acting as PPP server (slave), the other one as PPP client (host). Typical application is a WiFi connectivity provider for chips that do not have WiFi: + +``` + SLAVE micro HOST micro + \|/ +----------------+ +----------------+ + | | | serial line | | + +---+ WiFi NAT PPPoS |======== UART / SPI =======| PPPoS client | + | (server)| | | + +----------------+ +----------------+ +``` + +## API + +### Client + +* `eppp_connect()` -- Simplified API. Provides the initialization, starts the task and blocks until we're connected + +### Server + +* `eppp_listen()` -- Simplified API. Provides the initialization, starts the task and blocks until the client connects + +### Manual actions + +* `eppp_init()` -- Initializes one endpoint (client/server). +* `eppp_deinit()` -- Destroys the endpoint +* `eppp_netif_start()` -- Starts the network, could be called after startup or whenever a connection is lost +* `eppp_netif_stop()` -- Stops the network +* `eppp_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) + +## Throughput + +Tested with WiFi-NAPT example, no IRAM optimizations + +### UART @ 3Mbauds + +* TCP - 2Mbits +* UDP - 2Mbits + +### SPI @ 20MHz + +* TCP - 6Mbits +* UDP - 10Mbits diff --git a/components/eppp_link/eppp_link.c b/components/eppp_link/eppp_link.c new file mode 100644 index 0000000000..46e2d03b1a --- /dev/null +++ b/components/eppp_link/eppp_link.c @@ -0,0 +1,853 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include <string.h> +#include <stdint.h> +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_check.h" +#include "esp_event.h" +#include "esp_netif_ppp.h" +#include "eppp_link.h" + +#if CONFIG_EPPP_LINK_DEVICE_SPI +#include "driver/spi_master.h" +#include "driver/spi_slave.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#elif CONFIG_EPPP_LINK_DEVICE_UART +#include "driver/uart.h" +#endif + +static const int GOT_IPV4 = BIT0; +static const int CONNECTION_FAILED = BIT1; +#define CONNECT_BITS (GOT_IPV4|CONNECTION_FAILED) + +static EventGroupHandle_t s_event_group = NULL; +static const char *TAG = "eppp_link"; +static int s_retry_num = 0; +static int s_eppp_netif_count = 0; // used as a suffix for the netif key +static eppp_channel_fn_t s_rx = NULL; + +struct eppp_handle { +#if CONFIG_EPPP_LINK_DEVICE_SPI + QueueHandle_t out_queue; + QueueHandle_t ready_semaphore; + spi_device_handle_t spi_device; + spi_host_device_t spi_host; + int gpio_intr; +#elif CONFIG_EPPP_LINK_DEVICE_UART + QueueHandle_t uart_event_queue; + uart_port_t uart_port; +#endif + esp_netif_t *netif; + enum eppp_type role; + bool stop; + bool exited; + bool netif_stop; +}; + +struct packet { + size_t len; + uint8_t *data; + int channel; +}; + +static esp_err_t transmit_channel(void *netif, void *buffer, size_t len) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + struct packet buf = { .len = len }; + buf.channel = 1; + buf.data = malloc(len); + if (buf.data == NULL) { + ESP_LOGE(TAG, "Failed to allocate packet"); + return ESP_FAIL; + } + memcpy(buf.data, buffer, len); + BaseType_t ret = xQueueSend(h->out_queue, &buf, pdMS_TO_TICKS(10)); + if (ret != pdTRUE) { + ESP_LOGE(TAG, "Failed to queue packet to slave!"); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t transmit(void *h, void *buffer, size_t len) +{ + struct eppp_handle *handle = h; +#if CONFIG_EPPP_LINK_DEVICE_SPI +#define MAX_PAYLOAD 1600 + struct packet buf = { }; + + uint8_t *current_buffer = buffer; + size_t remaining = len; + do { + size_t batch = remaining > MAX_PAYLOAD ? MAX_PAYLOAD : remaining; + buf.data = malloc(batch); + if (buf.data == NULL) { + ESP_LOGE(TAG, "Failed to allocate packet"); + return ESP_FAIL; + } + buf.len = batch; + remaining -= batch; + memcpy(buf.data, current_buffer, batch); + current_buffer += batch; + BaseType_t ret = xQueueSend(handle->out_queue, &buf, pdMS_TO_TICKS(10)); + if (ret != pdTRUE) { + ESP_LOGE(TAG, "Failed to queue packet to slave!"); + return ESP_FAIL; + } + } while (remaining > 0); +#elif CONFIG_EPPP_LINK_DEVICE_UART + ESP_LOG_BUFFER_HEXDUMP("ppp_uart_send", buffer, len, ESP_LOG_VERBOSE); + uart_write_bytes(handle->uart_port, buffer, len); +#endif + return ESP_OK; +} + +static void netif_deinit(esp_netif_t *netif) +{ + if (netif == NULL) { + return; + } + struct eppp_handle *h = esp_netif_get_io_driver(netif); + if (h == NULL) { + return; + } +#if CONFIG_EPPP_LINK_DEVICE_SPI + vQueueDelete(h->out_queue); + if (h->role == EPPP_CLIENT) { + vSemaphoreDelete(h->ready_semaphore); + } +#endif + free(h); + esp_netif_destroy(netif); + if (s_eppp_netif_count > 0) { + s_eppp_netif_count--; + } +} + +static esp_netif_t *netif_init(enum eppp_type role) +{ + if (s_eppp_netif_count > 9) { + ESP_LOGE(TAG, "Cannot create more than 10 instances"); + return NULL; + } + + // Create the object first + struct eppp_handle *h = calloc(1, sizeof(struct eppp_handle)); + if (!h) { + ESP_LOGE(TAG, "Failed to allocate eppp_handle"); + return NULL; + } + h->role = role; +#if CONFIG_EPPP_LINK_DEVICE_SPI + h->out_queue = xQueueCreate(CONFIG_EPPP_LINK_PACKET_QUEUE_SIZE, sizeof(struct packet)); + if (!h->out_queue) { + ESP_LOGE(TAG, "Failed to create the packet queue"); + free(h); + return NULL; + } + if (role == EPPP_CLIENT) { + h->ready_semaphore = xSemaphoreCreateBinary(); + if (!h->ready_semaphore) { + ESP_LOGE(TAG, "Failed to create the packet queue"); + vQueueDelete(h->out_queue); + free(h); + return NULL; + } + } +#endif + + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = h, + .transmit = transmit, + }; + const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg; + + esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP(); + char if_key[] = "EPPP0"; // netif key needs to be unique + if_key[sizeof(if_key) - 2 /* 2 = two chars before the terminator */ ] += s_eppp_netif_count++; + base_netif_cfg.if_key = if_key; + if (role == EPPP_CLIENT) { + base_netif_cfg.if_desc = "pppos_client"; + } else { + base_netif_cfg.if_desc = "pppos_server"; + } + esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg, + .driver = ppp_driver_cfg, + .stack = ESP_NETIF_NETSTACK_DEFAULT_PPP + }; + + esp_netif_t *netif = esp_netif_new(&netif_ppp_config); + if (!netif) { + ESP_LOGE(TAG, "Failed to create esp_netif"); +#if CONFIG_EPPP_LINK_DEVICE_SPI + vQueueDelete(h->out_queue); +#endif + free(h); + return NULL; + } + return netif; + +} + +esp_err_t eppp_netif_stop(esp_netif_t *netif, TickType_t stop_timeout) +{ + esp_netif_action_disconnected(netif, 0, 0, 0); + esp_netif_action_stop(netif, 0, 0, 0); + struct eppp_handle *h = esp_netif_get_io_driver(netif); + for (int wait = 0; wait < 100; wait++) { + vTaskDelay(stop_timeout / 100); + if (h->netif_stop) { + break; + } + } + if (!h->netif_stop) { + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t eppp_netif_start(esp_netif_t *netif) +{ + esp_netif_action_start(netif, 0, 0, 0); + esp_netif_action_connected(netif, 0, 0, 0); + return ESP_OK; +} + +static int get_netif_num(esp_netif_t *netif) +{ + if (netif == NULL) { + return -1; + } + const char *ifkey = esp_netif_get_ifkey(netif); + if (strstr(ifkey, "EPPP") == NULL) { + return -1; // not our netif + } + int netif_cnt = ifkey[4] - '0'; + if (netif_cnt < 0 || netif_cnt > 9) { + ESP_LOGE(TAG, "Unexpected netif key %s", ifkey); + return -1; + } + return netif_cnt; +} + +static void on_ppp_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + esp_netif_t **netif = data; + if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) { + ESP_LOGI(TAG, "Disconnected %d", get_netif_num(*netif)); + struct eppp_handle *h = esp_netif_get_io_driver(*netif); + h->netif_stop = true; + } +} + +static void on_ip_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + ip_event_got_ip_t *event = (ip_event_got_ip_t *)data; + esp_netif_t *netif = event->esp_netif; + if (event_id == IP_EVENT_STA_GOT_IP) { + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif), + esp_netif_get_ifkey(netif), IP2STR(&event->ip_info.ip)); + } + int netif_cnt = get_netif_num(netif); + if (netif_cnt < 0) { + return; + } + if (event_id == IP_EVENT_PPP_GOT_IP) { + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif), + esp_netif_get_ifkey(netif), IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(s_event_group, GOT_IPV4 << (netif_cnt * 2)); + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Disconnected"); + s_retry_num++; + if (s_retry_num > CONFIG_EPPP_LINK_CONN_MAX_RETRY) { + ESP_LOGE(TAG, "PPP Connection failed %d times, stop reconnecting.", s_retry_num); + xEventGroupSetBits(s_event_group, CONNECTION_FAILED << (netif_cnt * 2)); + } else { + ESP_LOGI(TAG, "PPP Connection failed %d times, try to reconnect.", s_retry_num); + eppp_netif_start(netif); + } + } +} + +#if CONFIG_EPPP_LINK_DEVICE_SPI + +#define TRANSFER_SIZE (MAX_PAYLOAD + 4) +#define SHORT_PAYLOAD (48) +#define CONTROL_SIZE (SHORT_PAYLOAD + 4) + +#define CONTROL_MASTER 0xA5 +#define CONTROL_MASTER_WITH_DATA 0xA6 +#define CONTROL_SLAVE 0x5A +#define CONTROL_SLAVE_WITH_DATA 0x5B +#define DATA_MASTER 0xAF +#define DATA_SLAVE 0xFA + +#define MAX(a,b) (((a)>(b))?(a):(b)) + +struct header { + union { + uint16_t size; + struct { + uint8_t short_size; + uint8_t long_size; + } __attribute__((packed)); + }; + uint8_t magic; + uint8_t channel; + uint8_t checksum; +} __attribute__((packed)); + +static void IRAM_ATTR gpio_isr_handler(void *arg) +{ + static uint32_t s_last_time; + uint32_t now = esp_timer_get_time(); + uint32_t diff = now - s_last_time; + if (diff < 5) { // debounce + return; + } + s_last_time = now; + + BaseType_t yield = false; + struct eppp_handle *h = arg; + xSemaphoreGiveFromISR(h->ready_semaphore, &yield); + if (yield) { + portYIELD_FROM_ISR(); + } +} + +static esp_err_t deinit_master(esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + ESP_RETURN_ON_ERROR(spi_bus_remove_device(h->spi_device), TAG, "Failed to remove SPI bus"); + ESP_RETURN_ON_ERROR(spi_bus_free(h->spi_host), TAG, "Failed to free SPI bus"); + return ESP_OK; +} + +static esp_err_t init_master(struct eppp_config_spi_s *config, esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->spi_host = config->host; + h->gpio_intr = config->intr; + spi_bus_config_t bus_cfg = {}; + bus_cfg.mosi_io_num = config->mosi; + bus_cfg.miso_io_num = config->miso; + bus_cfg.sclk_io_num = config->sclk; + bus_cfg.quadwp_io_num = -1; + bus_cfg.quadhd_io_num = -1; + bus_cfg.max_transfer_sz = 2000; + bus_cfg.flags = 0; + bus_cfg.intr_flags = 0; + + // TODO: Init and deinit SPI bus separately (per Kconfig?) + if (spi_bus_initialize(config->host, &bus_cfg, SPI_DMA_CH_AUTO) != ESP_OK) { + return ESP_FAIL; + } + + spi_device_interface_config_t dev_cfg = {}; + dev_cfg.clock_speed_hz = config->freq; + dev_cfg.mode = 0; + dev_cfg.spics_io_num = config->cs; + dev_cfg.cs_ena_pretrans = 0; + dev_cfg.cs_ena_posttrans = 0; + dev_cfg.duty_cycle_pos = 128; + dev_cfg.input_delay_ns = 6; + dev_cfg.pre_cb = NULL; + dev_cfg.post_cb = NULL; + dev_cfg.cs_ena_posttrans = 3; + dev_cfg.queue_size = 3; + + if (spi_bus_add_device(config->host, &dev_cfg, &h->spi_device) != ESP_OK) { + return ESP_FAIL; + } + + //GPIO config for the handshake line. + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_POSEDGE, + .mode = GPIO_MODE_INPUT, + .pull_up_en = 1, + .pin_bit_mask = BIT64(config->intr), + }; + + gpio_config(&io_conf); + gpio_install_isr_service(0); + gpio_set_intr_type(config->intr, GPIO_INTR_POSEDGE); + gpio_isr_handler_add(config->intr, gpio_isr_handler, esp_netif_get_io_driver(netif)); + return ESP_OK; +} + +static void post_setup(spi_slave_transaction_t *trans) +{ + gpio_set_level((int)trans->user, 1); +} + +static void post_trans(spi_slave_transaction_t *trans) +{ + gpio_set_level((int)trans->user, 0); +} + +static esp_err_t deinit_slave(esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + ESP_RETURN_ON_ERROR(spi_slave_free(h->spi_host), TAG, "Failed to free SPI slave host"); + ESP_RETURN_ON_ERROR(spi_bus_remove_device(h->spi_device), TAG, "Failed to remove SPI device"); + ESP_RETURN_ON_ERROR(spi_bus_free(h->spi_host), TAG, "Failed to free SPI bus"); + return ESP_OK; +} + +static esp_err_t init_slave(struct eppp_config_spi_s *config, esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->spi_host = config->host; + h->gpio_intr = config->intr; + spi_bus_config_t bus_cfg = {}; + bus_cfg.mosi_io_num = config->mosi; + bus_cfg.miso_io_num = config->miso; + bus_cfg.sclk_io_num = config->sclk; + bus_cfg.quadwp_io_num = -1; + bus_cfg.quadhd_io_num = -1; + bus_cfg.flags = 0; + bus_cfg.intr_flags = 0; + + //Configuration for the SPI slave interface + spi_slave_interface_config_t slvcfg = { + .mode = 0, + .spics_io_num = config->cs, + .queue_size = 3, + .flags = 0, + .post_setup_cb = post_setup, + .post_trans_cb = post_trans, + }; + + //Configuration for the handshake line + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(config->intr), + }; + + gpio_config(&io_conf); + gpio_set_pull_mode(config->mosi, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(config->sclk, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(config->cs, GPIO_PULLUP_ONLY); + + //Initialize SPI slave interface + if (spi_slave_initialize(config->host, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO) != ESP_OK) { + return ESP_FAIL; + } + return ESP_OK; +} + +union transaction { + spi_transaction_t master; + spi_slave_transaction_t slave; +}; + +typedef void (*set_transaction_t)(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer, int gpio_intr); +typedef esp_err_t (*perform_transaction_t)(union transaction *t, struct eppp_handle *h); + +static void set_transaction_master(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer, int gpio_intr) +{ + t->master.length = len * 8; + t->master.tx_buffer = tx_buffer; + t->master.rx_buffer = rx_buffer; +} + +static void set_transaction_slave(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer, int gpio_intr) +{ + t->slave.user = (void *)gpio_intr; + t->slave.length = len * 8; + t->slave.tx_buffer = tx_buffer; + t->slave.rx_buffer = rx_buffer; +} + +static esp_err_t perform_transaction_master(union transaction *t, struct eppp_handle *h) +{ + xSemaphoreTake(h->ready_semaphore, portMAX_DELAY); // Wait until slave is ready + return spi_device_transmit(h->spi_device, &t->master); +} + +static esp_err_t perform_transaction_slave(union transaction *t, struct eppp_handle *h) +{ + return spi_slave_transmit(h->spi_host, &t->slave, portMAX_DELAY); +} + +esp_err_t eppp_add_channel(int nr, eppp_channel_fn_t *tx, const eppp_channel_fn_t rx) +{ + + *tx = transmit_channel; + s_rx = rx; + return ESP_OK; +} + +esp_err_t eppp_perform(esp_netif_t *netif) +{ + static WORD_ALIGNED_ATTR uint8_t out_buf[TRANSFER_SIZE] = {}; + static WORD_ALIGNED_ATTR uint8_t in_buf[TRANSFER_SIZE] = {}; + + struct eppp_handle *h = esp_netif_get_io_driver(netif); + union transaction t; + + // Three types of frames (control, control+data, data), two roles (master, slave) + const uint8_t FRAME_OUT_CTRL = h->role == EPPP_CLIENT ? CONTROL_MASTER : CONTROL_SLAVE; + const uint8_t FRAME_OUT_CTRL_EX = h->role == EPPP_CLIENT ? CONTROL_MASTER_WITH_DATA : CONTROL_SLAVE_WITH_DATA; + const uint8_t FRAME_OUT_DATA = h->role == EPPP_CLIENT ? DATA_MASTER : DATA_SLAVE; + const uint8_t FRAME_IN_CTRL = h->role == EPPP_SERVER ? CONTROL_MASTER : CONTROL_SLAVE; + const uint8_t FRAME_IN_CTRL_EX = h->role == EPPP_SERVER ? CONTROL_MASTER_WITH_DATA : CONTROL_SLAVE_WITH_DATA; + const uint8_t FRAME_IN_DATA = h->role == EPPP_SERVER ? DATA_MASTER : DATA_SLAVE; + // Two actions (prepare and perform transaction) for these two roles (master, slave) + const set_transaction_t set_transaction = h->role == EPPP_CLIENT ? set_transaction_master : set_transaction_slave; + const perform_transaction_t perform_transaction = h->role == EPPP_CLIENT ? perform_transaction_master : perform_transaction_slave; + + if (h->stop) { + return ESP_ERR_TIMEOUT; + } + + struct packet buf = { .len = 0 }; + struct header *head = (void *)out_buf; + bool need_data_frame = false; + size_t out_long_payload = 0; + head->magic = FRAME_OUT_CTRL_EX; + head->size = 0; + head->checksum = 0; + head->channel = 0; + BaseType_t tx_queue_stat = xQueueReceive(h->out_queue, &buf, 0); + if (tx_queue_stat == pdTRUE && buf.data) { + head->channel = buf.channel; + if (buf.len > SHORT_PAYLOAD) { + head->magic = FRAME_OUT_CTRL; + head->size = buf.len; + out_long_payload = buf.len; + need_data_frame = true; + } else { + head->magic = FRAME_OUT_CTRL_EX; + head->long_size = 0; + head->short_size = buf.len; + memcpy(out_buf + sizeof(struct header), buf.data, buf.len); + free(buf.data); + } + } + memset(&t, 0, sizeof(t)); + set_transaction(&t, CONTROL_SIZE, out_buf, in_buf, h->gpio_intr); + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + head->checksum += out_buf[i]; + } + esp_err_t ret = perform_transaction(&t, h); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "spi_device_transmit failed"); + return ESP_FAIL; + } + head = (void *)in_buf; + uint8_t checksum = 0; + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + checksum += in_buf[i]; + } + if (checksum != head->checksum) { + ESP_LOGE(TAG, "Wrong checksum"); + return ESP_FAIL; + } + if (head->magic != FRAME_IN_CTRL && head->magic != FRAME_IN_CTRL_EX) { + ESP_LOGE(TAG, "Wrong magic"); + return ESP_FAIL; + } + if (head->magic == FRAME_IN_CTRL_EX && head->short_size > 0) { + if (head->channel == 0) { + esp_netif_receive(netif, in_buf + sizeof(struct header), head->short_size, NULL); + } else { +// ESP_LOGE(TAG, "Got channel %d size %d", head->channel, head->short_size); + if (s_rx != NULL) { + s_rx(netif, in_buf + sizeof(struct header), head->short_size); + } + } + } + size_t in_long_payload = 0; + if (head->magic == FRAME_IN_CTRL) { + need_data_frame = true; + in_long_payload = head->size; + } + if (!need_data_frame) { + return ESP_OK; + } + + // now, we need data frame + head = (void *)out_buf; + head->magic = FRAME_OUT_DATA; + head->size = out_long_payload; + head->checksum = 0; + head->channel = buf.channel; + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + head->checksum += out_buf[i]; + } + if (head->size > 0) { + memcpy(out_buf + sizeof(struct header), buf.data, buf.len); +// ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf + sizeof(struct header), head->size, ESP_LOG_INFO); + free(buf.data); + } + + memset(&t, 0, sizeof(t)); + set_transaction(&t, MAX(in_long_payload, out_long_payload) + sizeof(struct header), out_buf, in_buf, h->gpio_intr); + + ret = perform_transaction(&t, h); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "spi_device_transmit failed"); + return ESP_FAIL; + } + head = (void *)in_buf; + checksum = 0; + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + checksum += in_buf[i]; + } + if (checksum != head->checksum) { + ESP_LOGE(TAG, "Wrong checksum"); + return ESP_FAIL; + } + if (head->magic != FRAME_IN_DATA) { + ESP_LOGE(TAG, "Wrong magic"); + return ESP_FAIL; + } + + if (head->size > 0) { + ESP_LOG_BUFFER_HEXDUMP(TAG, in_buf + sizeof(struct header), head->size, ESP_LOG_VERBOSE); + if (head->channel == 0) { + esp_netif_receive(netif, in_buf + sizeof(struct header), head->size, NULL); + } else { +// ESP_LOGE(TAG, "Got channel %d size %d", head->channel, head->size); + if (s_rx != NULL) { + s_rx(netif, in_buf + sizeof(struct header), head->size); + } + } + } + return ESP_OK; +} + +static void ppp_task(void *args) +{ + esp_netif_t *netif = args; + while (eppp_perform(netif) != ESP_ERR_TIMEOUT) {} + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->exited = true; + vTaskDelete(NULL); +} + +#elif CONFIG_EPPP_LINK_DEVICE_UART +#define BUF_SIZE (1024) + +static esp_err_t init_uart(struct eppp_handle *h, eppp_config_t *config) +{ + h->uart_port = config->uart.port; + uart_config_t uart_config = {}; + uart_config.baud_rate = config->uart.baud; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_DEFAULT; + + ESP_RETURN_ON_ERROR(uart_driver_install(h->uart_port, config->uart.rx_buffer_size, 0, config->uart.queue_size, &h->uart_event_queue, 0), TAG, "Failed to install UART"); + ESP_RETURN_ON_ERROR(uart_param_config(h->uart_port, &uart_config), TAG, "Failed to set params"); + ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->uart.tx_io, config->uart.rx_io, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "Failed to set UART pins"); + ESP_RETURN_ON_ERROR(uart_set_rx_timeout(h->uart_port, 1), TAG, "Failed to set UART Rx timeout"); + return ESP_OK; +} + +static void deinit_uart(struct eppp_handle *h) +{ + uart_driver_delete(h->uart_port); +} + +esp_err_t eppp_perform(esp_netif_t *netif) +{ + static uint8_t buffer[BUF_SIZE] = {}; + struct eppp_handle *h = esp_netif_get_io_driver(netif); + uart_event_t event = {}; + if (h->stop) { + return ESP_ERR_TIMEOUT; + } + + if (xQueueReceive(h->uart_event_queue, &event, pdMS_TO_TICKS(100)) != pdTRUE) { + return ESP_OK; + } + if (event.type == UART_DATA) { + size_t len; + uart_get_buffered_data_len(h->uart_port, &len); + if (len) { + len = uart_read_bytes(h->uart_port, buffer, BUF_SIZE, 0); + ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE); + esp_netif_receive(netif, buffer, len, NULL); + } + } else { + ESP_LOGW(TAG, "Received UART event: %d", event.type); + } + return ESP_OK; +} + +static void ppp_task(void *args) +{ + esp_netif_t *netif = args; + while (eppp_perform(netif) == ESP_OK) {} + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->exited = true; + vTaskDelete(NULL); +} + +#endif // CONFIG_EPPP_LINK_DEVICE_SPI / UART + +static bool have_some_eppp_netif(esp_netif_t *netif, void *ctx) +{ + return get_netif_num(netif) > 0; +} + +static void remove_handlers(void) +{ + esp_netif_t *netif = esp_netif_find_if(have_some_eppp_netif, NULL); + if (netif == NULL) { + // if EPPP netif in the system, we cleanup the statics + vEventGroupDelete(s_event_group); + s_event_group = NULL; + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event); + esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event); + } +} + +void eppp_deinit(esp_netif_t *netif) +{ +#if CONFIG_EPPP_LINK_DEVICE_SPI + struct eppp_handle *h = esp_netif_get_io_driver(netif); + if (h->role == EPPP_CLIENT) { + deinit_master(netif); + } else { + deinit_slave(netif); + } +#elif CONFIG_EPPP_LINK_DEVICE_UART + deinit_uart(esp_netif_get_io_driver(netif)); +#endif + netif_deinit(netif); +} + +esp_netif_t *eppp_init(enum eppp_type role, eppp_config_t *config) +{ + esp_netif_t *netif = netif_init(role); + if (!netif) { + ESP_LOGE(TAG, "Failed to initialize PPP netif"); + remove_handlers(); + return NULL; + } + esp_netif_ppp_config_t netif_params; + ESP_ERROR_CHECK(esp_netif_ppp_get_params(netif, &netif_params)); + netif_params.ppp_our_ip4_addr.addr = config->ppp.our_ip4_addr; + netif_params.ppp_their_ip4_addr.addr = config->ppp.their_ip4_addr; + netif_params.ppp_error_event_enabled = true; + ESP_ERROR_CHECK(esp_netif_ppp_set_params(netif, &netif_params)); +#if CONFIG_EPPP_LINK_DEVICE_SPI + if (role == EPPP_CLIENT) { + init_master(&config->spi, netif); + + // as a client, try to actively connect (not waiting for server's interrupt) + struct eppp_handle *h = esp_netif_get_io_driver(netif); + xSemaphoreGive(h->ready_semaphore); + } else { + init_slave(&config->spi, netif); + + } +#elif CONFIG_EPPP_LINK_DEVICE_UART + init_uart(esp_netif_get_io_driver(netif), config); +#endif + return netif; +} + +esp_netif_t *eppp_open(enum eppp_type role, eppp_config_t *config, TickType_t connect_timeout) +{ +#if CONFIG_EPPP_LINK_DEVICE_UART + if (config->transport != EPPP_TRANSPORT_UART) { + ESP_LOGE(TAG, "Invalid transport: UART device must be enabled in Kconfig"); + return NULL; + } +#endif +#if CONFIG_EPPP_LINK_DEVICE_SPI + if (config->transport != EPPP_TRANSPORT_SPI) { + ESP_LOGE(TAG, "Invalid transport: SPI device must be enabled in Kconfig"); + return NULL; + } +#endif + + if (config->task.run_task == false) { + ESP_LOGE(TAG, "task.run_task == false is invalid in this API. Please use eppp_init()"); + return NULL; + } + if (s_event_group == NULL) { + s_event_group = xEventGroupCreate(); + if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + remove_handlers(); + return NULL; + } + if (esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to register PPP status handler"); + remove_handlers(); + return NULL; + } + } + esp_netif_t *netif = eppp_init(role, config); + if (!netif) { + remove_handlers(); + return NULL; + } + + eppp_netif_start(netif); + + if (xTaskCreate(ppp_task, "ppp connect", config->task.stack_size, netif, config->task.priority, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Failed to create a ppp connection task"); + eppp_deinit(netif); + return NULL; + } + int netif_cnt = get_netif_num(netif); + if (netif_cnt < 0) { + eppp_close(netif); + return NULL; + } + ESP_LOGI(TAG, "Waiting for IP address %d", netif_cnt); + EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS << (netif_cnt * 2), pdFALSE, pdFALSE, connect_timeout); + if (bits & (CONNECTION_FAILED << (netif_cnt * 2))) { + ESP_LOGE(TAG, "Connection failed!"); + eppp_close(netif); + return NULL; + } + ESP_LOGI(TAG, "Connected! %d", netif_cnt); + return netif; +} + +esp_netif_t *eppp_connect(eppp_config_t *config) +{ + return eppp_open(EPPP_CLIENT, config, portMAX_DELAY); +} + +esp_netif_t *eppp_listen(eppp_config_t *config) +{ + return eppp_open(EPPP_SERVER, config, portMAX_DELAY); +} + +void eppp_close(esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + if (eppp_netif_stop(netif, pdMS_TO_TICKS(60000)) != ESP_OK) { + ESP_LOGE(TAG, "Network didn't exit cleanly"); + } + h->stop = true; + for (int wait = 0; wait < 100; wait++) { + vTaskDelay(pdMS_TO_TICKS(10)); + if (h->exited) { + break; + } + } + if (!h->exited) { + ESP_LOGE(TAG, "Cannot stop ppp_task"); + } + eppp_deinit(netif); + remove_handlers(); +} diff --git a/components/eppp_link/eppp_link_types.h b/components/eppp_link/eppp_link_types.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/eppp_link/examples/host/CMakeLists.txt b/components/eppp_link/examples/host/CMakeLists.txt new file mode 100644 index 0000000000..3405abc526 --- /dev/null +++ b/components/eppp_link/examples/host/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/iperf) + + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pppos_host) diff --git a/components/eppp_link/examples/host/README.md b/components/eppp_link/examples/host/README.md new file mode 100644 index 0000000000..a6592627c6 --- /dev/null +++ b/components/eppp_link/examples/host/README.md @@ -0,0 +1,9 @@ + +# Client side demo of ESP-PPP-Link + +This is a basic demo of using esp-mqtt library, but connects to the internet using a PPPoS client. To run this example, you would need a PPP server that provides connectivity to the MQTT broker used in this example (by default a public broker accessible on the internet). + +If configured, this example could also run a ping session and an iperf console. + + +The PPP server could be a Linux computer with `pppd` service or an ESP32 acting like a connection gateway with PPPoS server (see the "slave" project). diff --git a/components/eppp_link/examples/host/main/CMakeLists.txt b/components/eppp_link/examples/host/main/CMakeLists.txt new file mode 100644 index 0000000000..0198ddd323 --- /dev/null +++ b/components/eppp_link/examples/host/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS app_main.c register_iperf.c + INCLUDE_DIRS ".") diff --git a/components/eppp_link/examples/host/main/Kconfig.projbuild b/components/eppp_link/examples/host/main/Kconfig.projbuild new file mode 100644 index 0000000000..02881e10a3 --- /dev/null +++ b/components/eppp_link/examples/host/main/Kconfig.projbuild @@ -0,0 +1,43 @@ +menu "Example Configuration" + + config EXAMPLE_GLOBAL_DNS + hex "Set global DNS server" + range 0 0xFFFFFFFF + default 0x08080808 + help + Global DNS server address. + + config EXAMPLE_MQTT + bool "Run mqtt example" + default y + help + Run MQTT client after startup. + + config EXAMPLE_BROKER_URL + string "Broker URL" + depends on EXAMPLE_MQTT + default "mqtt://mqtt.eclipseprojects.io" + help + URL of the broker to connect to. + + config EXAMPLE_ICMP_PING + bool "Run ping example" + default y + help + Ping configured address after startup. + + config EXAMPLE_PING_ADDR + hex "Ping IPv4 address" + depends on EXAMPLE_ICMP_PING + range 0 0xFFFFFFFF + default 0x08080808 + help + Address to send ping requests. + + config EXAMPLE_IPERF + bool "Run iperf" + default y + help + Init and run iperf console. + +endmenu diff --git a/components/eppp_link/examples/host/main/app_main.c b/components/eppp_link/examples/host/main/app_main.c new file mode 100644 index 0000000000..2f147c9f5a --- /dev/null +++ b/components/eppp_link/examples/host/main/app_main.c @@ -0,0 +1,212 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "eppp_link.h" +#include "lwip/sockets.h" +#include "esp_log.h" +#include "mqtt_client.h" +#include "ping/ping_sock.h" +#include "esp_console.h" + +void register_iperf(void); + +static const char *TAG = "eppp_host_example"; + +#if CONFIG_EXAMPLE_MQTT +static void mqtt_event_handler(void *args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id); + esp_mqtt_event_handle_t event = event_data; + esp_mqtt_client_handle_t client = event->client; + int msg_id; + switch ((esp_mqtt_event_id_t)event_id) { + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0); + ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1"); + ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0); + ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA"); + printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); + printf("DATA=%.*s\r\n", event->data_len, event->data); + break; + case MQTT_EVENT_ERROR: + ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); + } + break; + default: + ESP_LOGI(TAG, "Other event id:%d", event->event_id); + break; + } +} + +static void mqtt_app_start(void) +{ + esp_mqtt_client_config_t mqtt_cfg = { + .broker.address.uri = CONFIG_EXAMPLE_BROKER_URL, + }; + + esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); + /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */ + esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + esp_mqtt_client_start(client); +} +#endif // MQTT + +#if CONFIG_EXAMPLE_ICMP_PING +static void test_on_ping_success(esp_ping_handle_t hdl, void *args) +{ + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + printf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n", + recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); +} + +static void test_on_ping_timeout(esp_ping_handle_t hdl, void *args) +{ + uint16_t seqno; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); +} + +static void test_on_ping_end(esp_ping_handle_t hdl, void *args) +{ + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + printf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms); + +} +#endif // PING + +void app_main(void) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* Sets up the default EPPP-connection + */ + eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG(); +#if CONFIG_EPPP_LINK_DEVICE_SPI + config.transport = EPPP_TRANSPORT_SPI; +#else + config.transport = EPPP_TRANSPORT_UART; + config.uart.tx_io = 10; + config.uart.rx_io = 11; + config.uart.baud = 2000000; +#endif + esp_netif_t *eppp_netif = eppp_connect(&config); + if (eppp_netif == NULL) { + ESP_LOGE(TAG, "Failed to connect"); + return ; + } + // Setup global DNS + esp_netif_dns_info_t dns; + dns.ip.u_addr.ip4.addr = esp_netif_htonl(CONFIG_EXAMPLE_GLOBAL_DNS); + dns.ip.type = ESP_IPADDR_TYPE_V4; + ESP_ERROR_CHECK(esp_netif_set_dns_info(eppp_netif, ESP_NETIF_DNS_MAIN, &dns)); + +#if CONFIG_EXAMPLE_IPERF + esp_console_repl_t *repl = NULL; + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + repl_config.prompt = "iperf>"; + // init console REPL environment + ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl)); + + register_iperf(); + + printf("\n =======================================================\n"); + printf(" | Steps to Test PPP Client Bandwidth |\n"); + printf(" | |\n"); + printf(" | 1. Enter 'help', check all supported commands |\n"); + printf(" | 2. Start PPP server on host system |\n"); + printf(" | - pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 modem local noauth debug nocrtscts nodetach +ipv6\n"); + printf(" | 3. Wait ESP32 to get IP from PPP server |\n"); + printf(" | 4. Enter 'pppd info' (optional) |\n"); + printf(" | 5. Server: 'iperf -u -s -i 3' |\n"); + printf(" | 6. Client: 'iperf -u -c SERVER_IP -t 60 -i 3' |\n"); + printf(" | |\n"); + printf(" =======================================================\n\n"); + + // start console REPL + ESP_ERROR_CHECK(esp_console_start_repl(repl)); +#endif + +#if CONFIG_EXAMPLE_ICMP_PING + ip_addr_t target_addr = { .type = IPADDR_TYPE_V4, .u_addr.ip4.addr = esp_netif_htonl(CONFIG_EXAMPLE_PING_ADDR) }; + + esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); + ping_config.timeout_ms = 2000; + ping_config.interval_ms = 20, + ping_config.target_addr = target_addr; + ping_config.count = 100; // ping in infinite mode + /* set callback functions */ + esp_ping_callbacks_t cbs; + cbs.on_ping_success = test_on_ping_success; + cbs.on_ping_timeout = test_on_ping_timeout; + cbs.on_ping_end = test_on_ping_end; + esp_ping_handle_t ping; + esp_ping_new_session(&ping_config, &cbs, &ping); + /* start ping */ + esp_ping_start(ping); +#endif // PING + +#if CONFIG_EXAMPLE_MQTT + mqtt_app_start(); +#endif +} diff --git a/components/eppp_link/examples/host/main/idf_component.yml b/components/eppp_link/examples/host/main/idf_component.yml new file mode 100644 index 0000000000..7ecb517e8a --- /dev/null +++ b/components/eppp_link/examples/host/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../.." diff --git a/components/eppp_link/examples/host/main/register_iperf.c b/components/eppp_link/examples/host/main/register_iperf.c new file mode 100644 index 0000000000..d2169c3763 --- /dev/null +++ b/components/eppp_link/examples/host/main/register_iperf.c @@ -0,0 +1,183 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "sys/socket.h" // for INADDR_ANY +#include "esp_netif.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include "esp_console.h" +#include "esp_event.h" +#include "esp_bit_defs.h" +#include "argtable3/argtable3.h" +#include "iperf.h" +#include "sdkconfig.h" + +/* "iperf" command */ + +static struct { + struct arg_str *ip; + struct arg_lit *server; + struct arg_lit *udp; + struct arg_lit *version; + struct arg_int *port; + struct arg_int *length; + struct arg_int *interval; + struct arg_int *time; + struct arg_int *bw_limit; + struct arg_lit *abort; + struct arg_end *end; +} iperf_args; + +static int ppp_cmd_iperf(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&iperf_args); + iperf_cfg_t cfg; + + if (nerrors != 0) { + arg_print_errors(stderr, iperf_args.end, argv[0]); + return 0; + } + + memset(&cfg, 0, sizeof(cfg)); + + // ethernet iperf only support IPV4 address + cfg.type = IPERF_IP_TYPE_IPV4; + + /* iperf -a */ + if (iperf_args.abort->count != 0) { + iperf_stop(); + return 0; + } + + if (((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) || + ((iperf_args.ip->count != 0) && (iperf_args.server->count != 0))) { + ESP_LOGE(__func__, "Wrong mode! ESP32 should run in client or server mode"); + return 0; + } + + /* iperf -s */ + if (iperf_args.ip->count == 0) { + cfg.flag |= IPERF_FLAG_SERVER; + } + /* iperf -c SERVER_ADDRESS */ + else { + cfg.destination_ip4 = esp_ip4addr_aton(iperf_args.ip->sval[0]); + cfg.flag |= IPERF_FLAG_CLIENT; + } + + if (iperf_args.length->count == 0) { + cfg.len_send_buf = 0; + } else { + cfg.len_send_buf = iperf_args.length->ival[0]; + } + + cfg.source_ip4 = INADDR_ANY; + + /* iperf -u */ + if (iperf_args.udp->count == 0) { + cfg.flag |= IPERF_FLAG_TCP; + } else { + cfg.flag |= IPERF_FLAG_UDP; + } + + /* iperf -p */ + if (iperf_args.port->count == 0) { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + if (cfg.flag & IPERF_FLAG_SERVER) { + cfg.sport = iperf_args.port->ival[0]; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = iperf_args.port->ival[0]; + } + } + + /* iperf -i */ + if (iperf_args.interval->count == 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } else { + cfg.interval = iperf_args.interval->ival[0]; + if (cfg.interval <= 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } + } + + /* iperf -t */ + if (iperf_args.time->count == 0) { + cfg.time = IPERF_DEFAULT_TIME; + } else { + cfg.time = iperf_args.time->ival[0]; + if (cfg.time <= cfg.interval) { + cfg.time = cfg.interval; + } + } + + /* iperf -b */ + if (iperf_args.bw_limit->count == 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } else { + cfg.bw_lim = iperf_args.bw_limit->ival[0]; + if (cfg.bw_lim <= 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } + } + + printf("mode=%s-%s sip=" IPSTR ":%" PRIu16 ", dip=%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ":%" PRIu16 ", interval=%" PRIu32 ", time=%" PRIu32 "\r\n", + cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp", + cfg.flag & IPERF_FLAG_SERVER ? "server" : "client", + (uint16_t) cfg.source_ip4 & 0xFF, + (uint16_t) (cfg.source_ip4 >> 8) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 16) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 24) & 0xFF, + cfg.sport, + cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF, + (cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport, + cfg.interval, cfg.time); + + iperf_start(&cfg); + return 0; +} + +void register_iperf(void) +{ + + iperf_args.ip = arg_str0("c", "client", "<ip>", + "run in client mode, connecting to <host>"); + iperf_args.server = arg_lit0("s", "server", "run in server mode"); + iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP"); + iperf_args.version = arg_lit0("V", "ipv6_domain", "use IPV6 address rather than IPV4"); + iperf_args.port = arg_int0("p", "port", "<port>", + "server port to listen on/connect to"); + iperf_args.length = arg_int0("l", "len", "<length>", "set read/write buffer size"); + iperf_args.interval = arg_int0("i", "interval", "<interval>", + "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "<time>", "time in seconds to transmit for (default 10 secs)"); + iperf_args.bw_limit = arg_int0("b", "bandwidth", "<bandwidth>", "bandwidth to send at in Mbits/sec"); + iperf_args.abort = arg_lit0("a", "abort", "abort running iperf"); + iperf_args.end = arg_end(1); + const esp_console_cmd_t iperf_cmd = { + .command = "iperf", + .help = "iperf command", + .hint = NULL, + .func = &ppp_cmd_iperf, + .argtable = &iperf_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd)); +} diff --git a/components/eppp_link/examples/host/sdkconfig.defaults b/components/eppp_link/examples/host/sdkconfig.defaults new file mode 100644 index 0000000000..2857f7ac8e --- /dev/null +++ b/components/eppp_link/examples/host/sdkconfig.defaults @@ -0,0 +1,8 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.3.0 Project Minimal Configuration +# +CONFIG_UART_ISR_IN_IRAM=y +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n +CONFIG_LWIP_PPP_DEBUG_ON=y +CONFIG_EPPP_LINK_DEVICE_SPI=y diff --git a/components/eppp_link/examples/rpc/client/CMakeLists.txt b/components/eppp_link/examples/rpc/client/CMakeLists.txt new file mode 100644 index 0000000000..3405abc526 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/iperf) + + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pppos_host) diff --git a/components/eppp_link/examples/rpc/client/README.md b/components/eppp_link/examples/rpc/client/README.md new file mode 100644 index 0000000000..a6592627c6 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/README.md @@ -0,0 +1,9 @@ + +# Client side demo of ESP-PPP-Link + +This is a basic demo of using esp-mqtt library, but connects to the internet using a PPPoS client. To run this example, you would need a PPP server that provides connectivity to the MQTT broker used in this example (by default a public broker accessible on the internet). + +If configured, this example could also run a ping session and an iperf console. + + +The PPP server could be a Linux computer with `pppd` service or an ESP32 acting like a connection gateway with PPPoS server (see the "slave" project). diff --git a/components/eppp_link/examples/rpc/client/main/CMakeLists.txt b/components/eppp_link/examples/rpc/client/main/CMakeLists.txt new file mode 100644 index 0000000000..e6169a604e --- /dev/null +++ b/components/eppp_link/examples/rpc/client/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS app_main.c register_iperf.c client.cpp + PRIV_INCLUDE_DIRS ../../common + INCLUDE_DIRS ".") diff --git a/components/eppp_link/examples/rpc/client/main/Kconfig.projbuild b/components/eppp_link/examples/rpc/client/main/Kconfig.projbuild new file mode 100644 index 0000000000..503bfa61a5 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +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 EXAMPLE_IPERF + bool "Run iperf" + default y + help + Init and run iperf console. + +endmenu diff --git a/components/eppp_link/examples/rpc/client/main/app_main.c b/components/eppp_link/examples/rpc/client/main/app_main.c new file mode 100644 index 0000000000..4c85c6abe3 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/main/app_main.c @@ -0,0 +1,305 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "eppp_link.h" +#include "lwip/sockets.h" +#include "esp_log.h" +#include "mqtt_client.h" +#include "ping/ping_sock.h" +#include "esp_console.h" +#include "esp_wifi_remote.h" + +void register_iperf(void); +esp_err_t client_init(void); + +static const char *TAG = "eppp_host_example"; + +#if CONFIG_EXAMPLE_MQTT +static void mqtt_event_handler(void *args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id); + esp_mqtt_event_handle_t event = event_data; + esp_mqtt_client_handle_t client = event->client; + int msg_id; + switch ((esp_mqtt_event_id_t)event_id) { + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0); + ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1"); + ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0); + ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA"); + printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); + printf("DATA=%.*s\r\n", event->data_len, event->data); + break; + case MQTT_EVENT_ERROR: + ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); + } + break; + default: + ESP_LOGI(TAG, "Other event id:%d", event->event_id); + break; + } +} + +static void mqtt_app_start(void) +{ + esp_mqtt_client_config_t mqtt_cfg = { + .broker.address.uri = CONFIG_EXAMPLE_BROKER_URL, + }; + + esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); + /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */ + esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + esp_mqtt_client_start(client); +} +#endif // MQTT + +#if CONFIG_EXAMPLE_ICMP_PING +static void test_on_ping_success(esp_ping_handle_t hdl, void *args) +{ + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + printf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n", + recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); +} + +static void test_on_ping_timeout(esp_ping_handle_t hdl, void *args) +{ + uint16_t seqno; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); +} + +static void test_on_ping_end(esp_ping_handle_t hdl, void *args) +{ + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + printf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms); + +} +#endif // PING + +/* + * local netifs (wifi and ppp) + */ +static esp_netif_t *s_wifi_netif; +static esp_netif_t *s_ppp_netif; +static eppp_channel_fn_t s_tx; + +static esp_err_t remote_wifi_receive(void *h, void *buffer, size_t len) +{ + if (s_wifi_netif) { + return esp_netif_receive(s_wifi_netif, buffer, len, NULL); + } + return ESP_OK; +} + +esp_err_t remote_wifi_transmit_wrap(void *h, void *buffer, size_t len, void *netstack_buffer) +{ + if (s_tx) { + return s_tx(s_ppp_netif, buffer, len); + } + return ESP_OK; +} + +static esp_err_t remote_wifi_transmit(void *h, void *buffer, size_t len) +{ + if (s_tx) { + return s_tx(s_ppp_netif, buffer, len); + } + return ESP_OK; +} + +// this is needed as the ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA config frees the eb on pbuf-free +static void wifi_free(void *h, void *buffer) +{ +} + +static void remote_wifi_netif(void) +{ + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = (void *)1, + .transmit = remote_wifi_transmit, + .transmit_wrap = remote_wifi_transmit_wrap, + .driver_free_rx_buffer = wifi_free + + }; + const esp_netif_driver_ifconfig_t *wifi_driver_cfg = &driver_cfg; + esp_netif_config_t netif_config = { + .base = ESP_NETIF_BASE_DEFAULT_WIFI_STA, + .driver = wifi_driver_cfg, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA + }; + s_wifi_netif = esp_netif_new(&netif_config); +} + +static void wifi_init(void *ctx) +{ + client_init(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_remote_init(&cfg)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_ESP_WIFI_SSID, + .password = CONFIG_ESP_WIFI_PASSWORD, + }, + }; + + esp_err_t err = esp_wifi_remote_set_mode(WIFI_MODE_STA); + ESP_LOGI(TAG, "esp_wifi_remote_set_mode() returned = %x", err); + ESP_ERROR_CHECK(esp_wifi_remote_set_config(WIFI_IF_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_remote_start() ); + vTaskDelay(pdMS_TO_TICKS(1000)); + uint8_t mac[6]; + ESP_ERROR_CHECK(esp_wifi_remote_get_mac(WIFI_IF_STA, mac) ); + + esp_netif_set_mac(s_wifi_netif, mac); + vTaskDelay(pdMS_TO_TICKS(1000)); // we're not supporting WIFI_EVENT yet, just play with delays for now + + esp_netif_action_start(s_wifi_netif, 0, 0, 0); + ESP_ERROR_CHECK(esp_wifi_remote_connect() ); + + esp_netif_action_connected(s_wifi_netif, 0, 0, 0); + vTaskDelete(NULL); +} + +void app_main(void) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* Sets up the default EPPP-connection + */ + eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG(); +#if CONFIG_EPPP_LINK_DEVICE_SPI + config.transport = EPPP_TRANSPORT_SPI; + config.task.priority = 14; + config.spi.freq = 40000000; +#else + config.transport = EPPP_TRANSPORT_UART; + config.uart.tx_io = 10; + config.uart.rx_io = 11; + config.uart.baud = 2000000; +#endif + s_ppp_netif = eppp_connect(&config); + if (s_ppp_netif == NULL) { + ESP_LOGE(TAG, "Failed to connect"); + return ; + } + eppp_add_channel(1, &s_tx, remote_wifi_receive); + remote_wifi_netif(); + if (s_wifi_netif == NULL) { + ESP_LOGE(TAG, "Failed to create wifi netif"); + return ; + } + + xTaskCreate(&wifi_init, "initwifi", 8192, NULL, 18, NULL); + +#if CONFIG_EXAMPLE_IPERF + esp_console_repl_t *repl = NULL; + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + repl_config.prompt = "iperf>"; + // init console REPL environment + ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl)); + + register_iperf(); + + printf("\n =======================================================\n"); + printf(" | Steps to Test PPP Client Bandwidth |\n"); + printf(" | |\n"); + printf(" | 1. Enter 'help', check all supported commands |\n"); + printf(" | 2. Start PPP server on host system |\n"); + printf(" | - pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 modem local noauth debug nocrtscts nodetach +ipv6\n"); + printf(" | 3. Wait ESP32 to get IP from PPP server |\n"); + printf(" | 4. Enter 'pppd info' (optional) |\n"); + printf(" | 5. Server: 'iperf -u -s -i 3' |\n"); + printf(" | 6. Client: 'iperf -u -c SERVER_IP -t 60 -i 3' |\n"); + printf(" | |\n"); + printf(" =======================================================\n\n"); + + // start console REPL + ESP_ERROR_CHECK(esp_console_start_repl(repl)); +#endif + +#if CONFIG_EXAMPLE_ICMP_PING + ip_addr_t target_addr = { .type = IPADDR_TYPE_V4, .u_addr.ip4.addr = esp_netif_htonl(CONFIG_EXAMPLE_PING_ADDR) }; + + esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); + ping_config.timeout_ms = 2000; + ping_config.interval_ms = 20, + ping_config.target_addr = target_addr; + ping_config.count = 100; // ping in infinite mode + /* set callback functions */ + esp_ping_callbacks_t cbs; + cbs.on_ping_success = test_on_ping_success; + cbs.on_ping_timeout = test_on_ping_timeout; + cbs.on_ping_end = test_on_ping_end; + esp_ping_handle_t ping; + esp_ping_new_session(&ping_config, &cbs, &ping); + /* start ping */ + esp_ping_start(ping); +#endif // PING + +#if CONFIG_EXAMPLE_MQTT + mqtt_app_start(); +#endif +} diff --git a/components/eppp_link/examples/rpc/client/main/client.cpp b/components/eppp_link/examples/rpc/client/main/client.cpp new file mode 100644 index 0000000000..c06242a928 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/main/client.cpp @@ -0,0 +1,192 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "esp_log.h" +#include "esp_tls.h" +#include "esp_wifi.h" +#include <sys/socket.h> +#include <netdb.h> +#include <errno.h> +#include <memory> +#include "rpc.hpp" +#include "esp_wifi_remote.h" + +#define PORT 3333 + + +static const char *TAG = "client"; +esp_tls_t *tls; + + + +extern "C" esp_err_t client_init(void) +{ + esp_err_t ret = ESP_OK; +// char buf[512]; +// int len; + const char host[] = "192.168.11.1"; + int port = 3333; + esp_tls_cfg_t cfg = {}; + cfg.skip_common_name = true; + + tls = esp_tls_init(); + if (!tls) { + ESP_LOGE(TAG, "Failed to allocate esp_tls handle!"); + ret = ESP_FAIL; + goto exit; + } + if (esp_tls_conn_new_sync(host, strlen(host), port, &cfg, tls) <= 0) { + ESP_LOGE(TAG, "Failed to open a new connection"); + ret = ESP_FAIL; + goto exit; + } + +// +// +// len = esp_tls_conn_write(tls,"AHOJ",4); +// if (len <= 0) { +// ESP_LOGE(TAG, "Failed to write data to the connection"); +// goto cleanup; +// } +// memset(buf, 0x00, sizeof(buf)); +// len = esp_tls_conn_read(tls, (char *)buf, len); +// if (len <= 0) { +// ESP_LOGE(TAG, "Failed to read data from the connection"); +// goto cleanup; +// } +// ESP_LOGI(TAG, "Data from the connection (size=%d)", len); +// ESP_LOG_BUFFER_HEXDUMP(TAG, buf, len, ESP_LOG_INFO); + +//cleanup: +// esp_tls_conn_destroy(tls); +exit: + return ret; +} + +extern "C" int client_deinit(void) +{ + return esp_tls_conn_destroy(tls); +} + + +//template <typename T> uint8_t *marshall(uint32_t id, T *t, size_t& len) +//{ +// len = sizeof(RpcData<T>); +// auto *data = new RpcData<T>(); +// data->id = id; +// data->size = sizeof(RpcData<T>); +// memcpy(&data->value, t, sizeof(T)); +// return data->get(); +//} + + +extern "C" esp_err_t esp_wifi_remote_set_mode(wifi_mode_t mode) +{ +// auto data = std::make_unique<RpcData<wifi_mode_t>>(SET_MODE); +// size_t size; +// auto buf = data->marshall(&mode, size); +//// auto buf = marshall(SET_MODE, &mode, size); +//// ESP_LOGE(TAG, "size=%d", (int)data->size_); +// ESP_LOG_BUFFER_HEXDUMP(TAG, buf, size, ESP_LOG_INFO); +// int len = esp_tls_conn_write(tls, buf, size); +// if (len <= 0) { +// ESP_LOGE(TAG, "Failed to write data to the connection"); +// return ESP_FAIL; +// } + RpcEngine rpc(tls); + + if (rpc.send(SET_MODE, &mode) != ESP_OK) { + return ESP_FAIL; + } + +// RpcHeader header{}; + + auto header = rpc.get_header(); + auto resp = rpc.get_payload<esp_err_t>(SET_MODE, header); + +// int len = esp_tls_conn_read(tls, (char *)&header, sizeof(header)); +// if (len <= 0) { +// ESP_LOGE(TAG, "Failed to read data from the connection"); +// return ESP_FAIL; +// } + +// auto resp = std::make_unique<RpcData<esp_err_t>>(SET_MODE); +// if (resp->head.size != header.size || resp->head.id != header.id) { +// ESP_LOGE(TAG, "Data size mismatch problem! %d expected, %d given", (int)resp->head.size, (int)header.size); +// ESP_LOGE(TAG, "API ID mismatch problem! %d expected, %d given", (int)resp->head.id, (int)header.id); +// return ESP_FAIL; +// } +// int len = esp_tls_conn_read(tls, (char *)resp->value(), resp->head.size); +// if (len <= 0) { +// ESP_LOGE(TAG, "Failed to read data from the connection"); +// return ESP_FAIL; +// } +// ESP_LOG_BUFFER_HEXDUMP(TAG, resp->value(), data->head.size, ESP_LOG_INFO); +// ESP_LOGE(TAG, "value_ is returned %x", *(int*)(resp->value())); + + return resp; +} + + +extern "C" esp_err_t esp_wifi_remote_set_config(wifi_interface_t interface, wifi_config_t *conf) +{ + RpcEngine rpc(tls); + esp_wifi_remote_config params = { .interface = interface, .conf = {} }; + memcpy(¶ms.conf, conf, sizeof(wifi_config_t)); + if (rpc.send(SET_CONFIG, ¶ms) != ESP_OK) { + return ESP_FAIL; + } + auto header = rpc.get_header(); + return rpc.get_payload<esp_err_t>(SET_CONFIG, header); +} + +extern "C" esp_err_t esp_wifi_remote_init(wifi_init_config_t *config) +{ + RpcEngine rpc(tls); + + if (rpc.send(INIT, config) != ESP_OK) { + return ESP_FAIL; + } + auto header = rpc.get_header(); + return rpc.get_payload<esp_err_t>(INIT, header); +} + +extern "C" esp_err_t esp_wifi_remote_start(void) +{ + RpcEngine rpc(tls); + + if (rpc.send(START) != ESP_OK) { + return ESP_FAIL; + } + auto header = rpc.get_header(); + return rpc.get_payload<esp_err_t>(START, header); +} + +extern "C" esp_err_t esp_wifi_remote_connect(void) +{ + RpcEngine rpc(tls); + + if (rpc.send(CONNECT) != ESP_OK) { + return ESP_FAIL; + } + auto header = rpc.get_header(); + return rpc.get_payload<esp_err_t>(CONNECT, header); +} + +extern "C" esp_err_t esp_wifi_remote_get_mac(wifi_interface_t ifx, uint8_t mac[6]) +{ + RpcEngine rpc(tls); + + if (rpc.send(GET_MAC, &ifx) != ESP_OK) { + return ESP_FAIL; + } + auto header = rpc.get_header(); + auto ret = rpc.get_payload<esp_wifi_remote_mac_t>(GET_MAC, header); + ESP_LOG_BUFFER_HEXDUMP("MAC", ret.mac, 6, ESP_LOG_INFO); + + memcpy(mac, ret.mac, 6); + return ret.err; + +} diff --git a/components/eppp_link/examples/rpc/client/main/idf_component.yml b/components/eppp_link/examples/rpc/client/main/idf_component.yml new file mode 100644 index 0000000000..42c0a17d95 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../../.." diff --git a/components/eppp_link/examples/rpc/client/main/register_iperf.c b/components/eppp_link/examples/rpc/client/main/register_iperf.c new file mode 100644 index 0000000000..63fded10c5 --- /dev/null +++ b/components/eppp_link/examples/rpc/client/main/register_iperf.c @@ -0,0 +1,179 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "sys/socket.h" // for INADDR_ANY +#include "esp_netif.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include "esp_console.h" +#include "esp_event.h" +#include "esp_bit_defs.h" +#include "argtable3/argtable3.h" +#include "iperf.h" +#include "sdkconfig.h" + +/* "iperf" command */ + +static struct { + struct arg_str *ip; + struct arg_lit *server; + struct arg_lit *udp; + struct arg_lit *version; + struct arg_int *port; + struct arg_int *length; + struct arg_int *interval; + struct arg_int *time; + struct arg_int *bw_limit; + struct arg_lit *abort; + struct arg_end *end; +} iperf_args; + +static int ppp_cmd_iperf(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&iperf_args); + // ethernet iperf only support IPV4 address + iperf_cfg_t cfg = {.type = IPERF_IP_TYPE_IPV4}; + + if (nerrors != 0) { + arg_print_errors(stderr, iperf_args.end, argv[0]); + return 0; + } + + /* iperf -a */ + if (iperf_args.abort->count != 0) { + iperf_stop(); + return 0; + } + + if (((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) || + ((iperf_args.ip->count != 0) && (iperf_args.server->count != 0))) { + ESP_LOGE(__func__, "Wrong mode! ESP32 should run in client or server mode"); + return 0; + } + + /* iperf -s */ + if (iperf_args.ip->count == 0) { + cfg.flag |= IPERF_FLAG_SERVER; + } + /* iperf -c SERVER_ADDRESS */ + else { + cfg.destination_ip4 = esp_ip4addr_aton(iperf_args.ip->sval[0]); + cfg.flag |= IPERF_FLAG_CLIENT; + } + + if (iperf_args.length->count == 0) { + cfg.len_send_buf = 0; + } else { + cfg.len_send_buf = iperf_args.length->ival[0]; + } + + cfg.source_ip4 = INADDR_ANY; + + /* iperf -u */ + if (iperf_args.udp->count == 0) { + cfg.flag |= IPERF_FLAG_TCP; + } else { + cfg.flag |= IPERF_FLAG_UDP; + } + + /* iperf -p */ + if (iperf_args.port->count == 0) { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + if (cfg.flag & IPERF_FLAG_SERVER) { + cfg.sport = iperf_args.port->ival[0]; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = iperf_args.port->ival[0]; + } + } + + /* iperf -i */ + if (iperf_args.interval->count == 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } else { + cfg.interval = iperf_args.interval->ival[0]; + if (cfg.interval <= 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } + } + + /* iperf -t */ + if (iperf_args.time->count == 0) { + cfg.time = IPERF_DEFAULT_TIME; + } else { + cfg.time = iperf_args.time->ival[0]; + if (cfg.time <= cfg.interval) { + cfg.time = cfg.interval; + } + } + + /* iperf -b */ + if (iperf_args.bw_limit->count == 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } else { + cfg.bw_lim = iperf_args.bw_limit->ival[0]; + if (cfg.bw_lim <= 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } + } + + printf("mode=%s-%s sip=" IPSTR ":%" PRIu16 ", dip=%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ":%" PRIu16 ", interval=%" PRIu32 ", time=%" PRIu32 "\r\n", + cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp", + cfg.flag & IPERF_FLAG_SERVER ? "server" : "client", + (uint16_t) cfg.source_ip4 & 0xFF, + (uint16_t) (cfg.source_ip4 >> 8) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 16) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 24) & 0xFF, + cfg.sport, + cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF, + (cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport, + cfg.interval, cfg.time); + + iperf_start(&cfg); + return 0; +} + +void register_iperf(void) +{ + + iperf_args.ip = arg_str0("c", "client", "<ip>", + "run in client mode, connecting to <host>"); + iperf_args.server = arg_lit0("s", "server", "run in server mode"); + iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP"); + iperf_args.version = arg_lit0("V", "ipv6_domain", "use IPV6 address rather than IPV4"); + iperf_args.port = arg_int0("p", "port", "<port>", + "server port to listen on/connect to"); + iperf_args.length = arg_int0("l", "len", "<length>", "set read/write buffer size"); + iperf_args.interval = arg_int0("i", "interval", "<interval>", + "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "<time>", "time in seconds to transmit for (default 10 secs)"); + iperf_args.bw_limit = arg_int0("b", "bandwidth", "<bandwidth>", "bandwidth to send at in Mbits/sec"); + iperf_args.abort = arg_lit0("a", "abort", "abort running iperf"); + iperf_args.end = arg_end(1); + const esp_console_cmd_t iperf_cmd = { + .command = "iperf", + .help = "iperf command", + .hint = NULL, + .func = &ppp_cmd_iperf, + .argtable = &iperf_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd)); +} diff --git a/components/eppp_link/examples/rpc/client/sdkconfig.defaults b/components/eppp_link/examples/rpc/client/sdkconfig.defaults new file mode 100644 index 0000000000..b4b1b3a8fc --- /dev/null +++ b/components/eppp_link/examples/rpc/client/sdkconfig.defaults @@ -0,0 +1,12 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.3.0 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_TLS_INSECURE=y +CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_SERVER_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n +CONFIG_EPPP_LINK_DEVICE_SPI=y +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096 diff --git a/components/eppp_link/examples/rpc/common/esp_wifi_remote.h b/components/eppp_link/examples/rpc/common/esp_wifi_remote.h new file mode 100644 index 0000000000..2c59d65aff --- /dev/null +++ b/components/eppp_link/examples/rpc/common/esp_wifi_remote.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#include "esp_wifi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct esp_wifi_remote_config { + wifi_interface_t interface; + wifi_config_t conf; +}; + +struct esp_wifi_remote_mac_t { + esp_err_t err; + uint8_t mac[6]; +}; + + +esp_err_t esp_wifi_remote_set_config(wifi_interface_t interface, wifi_config_t *conf); + +esp_err_t esp_wifi_remote_set_mode(wifi_mode_t mode); + +esp_err_t esp_wifi_remote_init(wifi_init_config_t *config); + +esp_err_t esp_wifi_remote_start(void); + +esp_err_t esp_wifi_remote_connect(void); + +esp_err_t esp_wifi_remote_get_mac(wifi_interface_t ifx, uint8_t mac[6]); + +#ifdef __cplusplus +} +#endif diff --git a/components/eppp_link/examples/rpc/common/rpc.hpp b/components/eppp_link/examples/rpc/common/rpc.hpp new file mode 100644 index 0000000000..1f5fbf02e6 --- /dev/null +++ b/components/eppp_link/examples/rpc/common/rpc.hpp @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +typedef enum api_id { + INIT, + SET_MODE, + SET_CONFIG, + START, + CONNECT, + GET_MAC +} api_id_t; + +struct RpcHeader { + uint32_t id; + uint32_t size; +} __attribute((__packed__)); + +template <typename T> struct RpcData { + RpcHeader head; + T value_; + explicit RpcData(api_id_t id): head{id, sizeof(T)} {} + + + uint8_t *value() + { + return (uint8_t *)&value_; + } + + uint8_t *marshall(T *t, size_t &size) + { + size = head.size + sizeof(RpcHeader); + memcpy(&value_, t, sizeof(T)); + return (uint8_t *)this; + } + + +} __attribute((__packed__)); + +class RpcEngine { +public: + explicit RpcEngine(esp_tls_t *tls): tls_(tls) {} + + template <typename T> esp_err_t send(api_id_t id, T *t) + { + RpcData<T> req(id); + size_t size; + auto buf = req.marshall(t, size); + ESP_LOGD("rpc", "Sending API id:%d", (int)id); + ESP_LOG_BUFFER_HEXDUMP("rpc", buf, size, ESP_LOG_VERBOSE); + int len = esp_tls_conn_write(tls_, buf, size); + if (len <= 0) { + ESP_LOGE("rpc", "Failed to write data to the connection"); + return ESP_FAIL; + } + return ESP_OK; + } + + esp_err_t send(api_id_t id) // specialization for (void) + { + RpcHeader head = { .id = id, .size = 0 }; + int len = esp_tls_conn_write(tls_, &head, sizeof(head)); + if (len <= 0) { + ESP_LOGE("rpc", "Failed to write data to the connection"); + return ESP_FAIL; + } + return ESP_OK; + } + + RpcHeader get_header() + { + RpcHeader header{}; + int len = esp_tls_conn_read(tls_, (char *)&header, sizeof(header)); + if (len <= 0) { + ESP_LOGE("prc", "Failed to read data from the connection"); + return {}; + } + return header; + } + + template <typename T> T get_payload(api_id_t id, RpcHeader &head) + { + RpcData<T> resp(id); + if (head.id != id || head.size != resp.head.size) { + ESP_LOGE("prc", "Failed to read data from the connection"); + return {}; + } + int len = esp_tls_conn_read(tls_, (char *)resp.value(), resp.head.size); + if (len <= 0) { + ESP_LOGE("rps", "Failed to read data from the connection"); + return {}; + } + return resp.value_; + } + +private: + esp_tls_t *tls_; + +}; diff --git a/components/eppp_link/examples/rpc/server/CMakeLists.txt b/components/eppp_link/examples/rpc/server/CMakeLists.txt new file mode 100644 index 0000000000..144b9e1a21 --- /dev/null +++ b/components/eppp_link/examples/rpc/server/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/rpc/server/README.md b/components/eppp_link/examples/rpc/server/README.md new file mode 100644 index 0000000000..a8ff85f9f4 --- /dev/null +++ b/components/eppp_link/examples/rpc/server/README.md @@ -0,0 +1,7 @@ + +# Wi-Fi station to PPPoS server + +This example demonstrate using NAPT to bring connectivity from WiFi station to PPPoS server. + +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) diff --git a/components/eppp_link/examples/rpc/server/main/CMakeLists.txt b/components/eppp_link/examples/rpc/server/main/CMakeLists.txt new file mode 100644 index 0000000000..d22419860c --- /dev/null +++ b/components/eppp_link/examples/rpc/server/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "station_example_main.c" "server.cpp" + PRIV_INCLUDE_DIRS ../../common + INCLUDE_DIRS ".") diff --git a/components/eppp_link/examples/rpc/server/main/Kconfig.projbuild b/components/eppp_link/examples/rpc/server/main/Kconfig.projbuild new file mode 100644 index 0000000000..ae4d7fb9a8 --- /dev/null +++ b/components/eppp_link/examples/rpc/server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +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. + +endmenu diff --git a/components/eppp_link/examples/rpc/server/main/idf_component.yml b/components/eppp_link/examples/rpc/server/main/idf_component.yml new file mode 100644 index 0000000000..42c0a17d95 --- /dev/null +++ b/components/eppp_link/examples/rpc/server/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../../.." diff --git a/components/eppp_link/examples/rpc/server/main/server.cpp b/components/eppp_link/examples/rpc/server/main/server.cpp new file mode 100644 index 0000000000..a5c34d84f8 --- /dev/null +++ b/components/eppp_link/examples/rpc/server/main/server.cpp @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +// +// Created by david on 1/10/24. +// +#include "esp_log.h" +#include "esp_tls.h" +#include <sys/socket.h> +#include <netdb.h> +#include <errno.h> +#include <memory> +#include <esp_private/wifi.h> +#include "esp_wifi.h" +#include "rpc.hpp" +#include "esp_wifi_remote.h" + +#define PORT 3333 +static const char *TAG = "server"; +static esp_tls_t *tls; + +const unsigned char servercert[] = "-----BEGIN CERTIFICATE-----\n" + "MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\n" + "BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx\n" + "MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ\n" + "UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" + "ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T\n" + "sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k\n" + "qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd\n" + "GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4\n" + "sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb\n" + "jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/\n" + "ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud\n" + "EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3\n" + "emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY\n" + "W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx\n" + "bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN\n" + "ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl\n" + "hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=\n" + "-----END CERTIFICATE-----"; +const unsigned char prvtkey[] = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH\n" + "JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw\n" + "h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT\n" + "aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al\n" + "3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg\n" + "0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB\n" + "vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui\n" + "f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9\n" + "Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y\n" + "JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX\n" + "49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc\n" + "+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6\n" + "pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D\n" + "0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG\n" + "YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV\n" + "MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL\n" + "CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin\n" + "7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1\n" + "noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8\n" + "4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g\n" + "Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/\n" + "nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3\n" + "q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2\n" + "lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB\n" + "jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr\n" + "v/t+MeGJP/0Zw8v/X2CFll96\n" + "-----END PRIVATE KEY-----"; + + +extern "C" esp_err_t rpc_example_wifi_recv(void *buffer, uint16_t len, void *eb); + +static esp_err_t perform() +{ + RpcEngine rpc(tls); + + auto header = rpc.get_header(); + + switch (header.id) { + case SET_MODE: { + auto req = rpc.get_payload<wifi_mode_t>(SET_MODE, header); + auto ret = esp_wifi_set_mode(req); + if (rpc.send(SET_MODE, &ret) != ESP_OK) { + return ESP_FAIL; + } + break; + } + case INIT: { + auto req = rpc.get_payload<wifi_init_config_t>(INIT, header); + req.osi_funcs = &g_wifi_osi_funcs; + req.wpa_crypto_funcs = g_wifi_default_wpa_crypto_funcs; + auto ret = esp_wifi_init(&req); + if (rpc.send(INIT, &ret) != ESP_OK) { + return ESP_FAIL; + } + break; + } + case SET_CONFIG: { + auto req = rpc.get_payload<esp_wifi_remote_config>(SET_CONFIG, header); + auto ret = esp_wifi_set_config(req.interface, &req.conf); + if (rpc.send(SET_CONFIG, &ret) != ESP_OK) { + return ESP_FAIL; + } + break; + } + case START: { + if (header.size != 0) { + return ESP_FAIL; + } + + // setup wifi callbacks now + esp_wifi_internal_reg_rxcb(WIFI_IF_STA, rpc_example_wifi_recv); + esp_wifi_internal_reg_netstack_buf_cb(esp_netif_netstack_buf_ref, esp_netif_netstack_buf_free); + // + auto ret = esp_wifi_start(); + if (rpc.send(START, &ret) != ESP_OK) { + return ESP_FAIL; + } + break; + } + case CONNECT: { + if (header.size != 0) { + return ESP_FAIL; + } + + auto ret = esp_wifi_connect(); + if (rpc.send(CONNECT, &ret) != ESP_OK) { + return ESP_FAIL; + } + break; + } + case GET_MAC: { + auto req = rpc.get_payload<wifi_interface_t>(GET_MAC, header); + esp_wifi_remote_mac_t resp = {}; + resp.err = esp_wifi_get_mac(req, resp.mac); + if (rpc.send(GET_MAC, &resp) != ESP_OK) { + return ESP_FAIL; + } + break; + } + } + return ESP_OK; +} + +static void server(void *ctx) +{ + struct sockaddr_in dest_addr = {}; + int ret; + int opt = 1; + dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(PORT); + int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (listen_sock < 0) { + printf("Unable to create socket: errno %d", errno); + return; + } + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + ret = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (ret != 0) { + printf("Socket unable to bind: errno %d", errno); + return; + } + + ret = listen(listen_sock, 1); + if (ret != 0) { + printf("Error occurred during listen: errno %d", errno); + return; + } + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); + if (sock < 0) { + printf("Unable to accept connection: errno %d", errno); + return; + } + printf("Socket accepted ip address: %s\n", inet_ntoa(((struct sockaddr_in *)&source_addr)->sin_addr)); + + + esp_tls_cfg_server_t cfg = {}; + cfg.servercert_buf = servercert; + cfg.servercert_bytes = sizeof(servercert); + cfg.serverkey_buf = prvtkey; + cfg.serverkey_bytes = sizeof(prvtkey); + + + + tls = esp_tls_init(); + if (!tls) { + goto exit; + } + ESP_LOGI(TAG, "performing session handshake"); + ret = esp_tls_server_session_create(&cfg, sock, tls); + if (ret != 0) { + ESP_LOGE(TAG, "esp_tls_create_server_session failed"); + goto exit; + } + ESP_LOGI(TAG, "Secure socket open"); + while (perform() == ESP_OK) {} + + esp_tls_server_session_delete(tls); +exit: + vTaskDelete(nullptr); + +} + +extern "C" esp_err_t server_init(void) +{ + xTaskCreate(&server, "server", 8192, NULL, 5, NULL); + return ESP_OK; +} diff --git a/components/eppp_link/examples/rpc/server/main/station_example_main.c b/components/eppp_link/examples/rpc/server/main/station_example_main.c new file mode 100644 index 0000000000..dbcd1bacfa --- /dev/null +++ b/components/eppp_link/examples/rpc/server/main/station_example_main.c @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <string.h> +#include <esp_private/wifi.h> +#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 "nvs_flash.h" +#include "eppp_link.h" +#include "esp_wifi_remote.h" + +static const char *TAG = "sta2pppos"; + +esp_err_t server_init(void); + +static eppp_channel_fn_t s_tx; +static esp_netif_t *s_ppp_netif; + +static esp_err_t netif_recv(void *h, void *buffer, size_t len) +{ + return esp_wifi_internal_tx(WIFI_IF_STA, buffer, len); +} + +esp_err_t rpc_example_wifi_recv(void *buffer, uint16_t len, void *eb) +{ + if (s_tx) { + esp_err_t ret = s_tx(s_ppp_netif, buffer, len); + esp_wifi_internal_free_rx_buffer(eb); + return ret; + } + return ESP_OK; +} + +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()); + eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG(); + config.transport = EPPP_TRANSPORT_SPI; + s_ppp_netif = eppp_listen(&config); + if (s_ppp_netif == NULL) { + ESP_LOGE(TAG, "Failed to setup connection"); + return ; + } + + eppp_add_channel(1, &s_tx, netif_recv); + + server_init(); +} diff --git a/components/eppp_link/examples/rpc/server/sdkconfig.defaults b/components/eppp_link/examples/rpc/server/sdkconfig.defaults new file mode 100644 index 0000000000..2eb96884e3 --- /dev/null +++ b/components/eppp_link/examples/rpc/server/sdkconfig.defaults @@ -0,0 +1,5 @@ +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 +CONFIG_EPPP_LINK_DEVICE_SPI=y diff --git a/components/eppp_link/examples/slave/CMakeLists.txt b/components/eppp_link/examples/slave/CMakeLists.txt new file mode 100644 index 0000000000..144b9e1a21 --- /dev/null +++ b/components/eppp_link/examples/slave/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/README.md b/components/eppp_link/examples/slave/README.md new file mode 100644 index 0000000000..a8ff85f9f4 --- /dev/null +++ b/components/eppp_link/examples/slave/README.md @@ -0,0 +1,7 @@ + +# Wi-Fi station to PPPoS server + +This example demonstrate using NAPT to bring connectivity from WiFi station to PPPoS server. + +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) diff --git a/components/eppp_link/examples/slave/main/CMakeLists.txt b/components/eppp_link/examples/slave/main/CMakeLists.txt new file mode 100644 index 0000000000..2ba044442a --- /dev/null +++ b/components/eppp_link/examples/slave/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "station_example_main.c" + INCLUDE_DIRS ".") diff --git a/components/eppp_link/examples/slave/main/Kconfig.projbuild b/components/eppp_link/examples/slave/main/Kconfig.projbuild new file mode 100644 index 0000000000..ae4d7fb9a8 --- /dev/null +++ b/components/eppp_link/examples/slave/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +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. + +endmenu diff --git a/components/eppp_link/examples/slave/main/idf_component.yml b/components/eppp_link/examples/slave/main/idf_component.yml new file mode 100644 index 0000000000..7ecb517e8a --- /dev/null +++ b/components/eppp_link/examples/slave/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../.." diff --git a/components/eppp_link/examples/slave/main/station_example_main.c b/components/eppp_link/examples/slave/main/station_example_main.c new file mode 100644 index 0000000000..3b1445472f --- /dev/null +++ b/components/eppp_link/examples/slave/main/station_example_main.c @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <string.h> +#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 "nvs_flash.h" +#include "eppp_link.h" + +/* 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 + +static const char *TAG = "sta2pppos"; + +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) { + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "retry to connect to the AP"); + } else { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + } + ESP_LOGI(TAG, "connect to the AP fail"); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num = 0; + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } +} + +void wifi_init_sta(void) +{ + s_wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + 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."); + + /* 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"); + } +} + +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_LOGI(TAG, "ESP_WIFI_MODE_STA"); + wifi_init_sta(); + + eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG(); +#if CONFIG_EPPP_LINK_DEVICE_SPI + config.transport = EPPP_TRANSPORT_SPI; +#else + config.transport = EPPP_TRANSPORT_UART; + config.uart.tx_io = 11; + config.uart.rx_io = 10; + config.uart.baud = 2000000; +#endif + esp_netif_t *eppp_netif = eppp_listen(&config); + if (eppp_netif == NULL) { + ESP_LOGE(TAG, "Failed to setup connection"); + return ; + } + ESP_ERROR_CHECK(esp_netif_napt_enable(eppp_netif)); +} diff --git a/components/eppp_link/examples/slave/sdkconfig.defaults b/components/eppp_link/examples/slave/sdkconfig.defaults new file mode 100644 index 0000000000..ec1a1a43b0 --- /dev/null +++ b/components/eppp_link/examples/slave/sdkconfig.defaults @@ -0,0 +1,8 @@ +CONFIG_UART_ISR_IN_IRAM=y +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 +CONFIG_EPPP_LINK_DEVICE_SPI=y diff --git a/components/eppp_link/idf_component.yml b/components/eppp_link/idf_component.yml new file mode 100644 index 0000000000..fbab49cb81 --- /dev/null +++ b/components/eppp_link/idf_component.yml @@ -0,0 +1,6 @@ +version: 0.0.9 +url: https://github.com/espressif/esp-protocols/tree/master/components/eppp_link +description: The component provides a general purpose PPP connectivity, typically used as WiFi-PPP router +dependencies: + idf: + version: '>=5.2' diff --git a/components/eppp_link/include/eppp_link.h b/components/eppp_link/include/eppp_link.h new file mode 100644 index 0000000000..ad448ea7d7 --- /dev/null +++ b/components/eppp_link/include/eppp_link.h @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define EPPP_DEFAULT_SERVER_IP() ESP_IP4TOADDR(192, 168, 11, 1) +#define EPPP_DEFAULT_CLIENT_IP() ESP_IP4TOADDR(192, 168, 11, 2) + +#define EPPP_DEFAULT_CONFIG(our_ip, their_ip) { \ + .transport = EPPP_TRANSPORT_UART, \ + .spi = { \ + .host = 1, \ + .mosi = 11, \ + .miso = 13, \ + .sclk = 12, \ + .cs = 10, \ + .intr = 2, \ + .freq = 20*1000*1000, \ + }, \ + .uart = { \ + .port = 1, \ + .baud = 921600, \ + .tx_io = 25, \ + .rx_io = 26, \ + .queue_size = 16, \ + .rx_buffer_size = 1024, \ + }, \ + . task = { \ + .run_task = true, \ + .stack_size = 4096, \ + .priority = 18, \ + }, \ + . ppp = { \ + .our_ip4_addr = our_ip, \ + .their_ip4_addr = their_ip, \ + } \ +} + +#define EPPP_DEFAULT_SERVER_CONFIG() EPPP_DEFAULT_CONFIG(EPPP_DEFAULT_SERVER_IP(), EPPP_DEFAULT_CLIENT_IP()) +#define EPPP_DEFAULT_CLIENT_CONFIG() EPPP_DEFAULT_CONFIG(EPPP_DEFAULT_CLIENT_IP(), EPPP_DEFAULT_SERVER_IP()) + +typedef enum eppp_type { + EPPP_SERVER, + EPPP_CLIENT, +} eppp_type_t; + +typedef enum eppp_transport { + EPPP_TRANSPORT_UART, + EPPP_TRANSPORT_SPI, +} eppp_transport_t; + + +typedef struct eppp_config_t { + eppp_transport_t transport; + + struct eppp_config_spi_s { + int host; + int mosi; + int miso; + int sclk; + int cs; + int intr; + int freq; + } spi; + + struct eppp_config_uart_s { + int port; + int baud; + int tx_io; + int rx_io; + int queue_size; + int rx_buffer_size; + } uart; + + struct eppp_config_task_s { + bool run_task; + int stack_size; + int priority; + } task; + + struct eppp_config_pppos_s { + uint32_t our_ip4_addr; + uint32_t their_ip4_addr; + } ppp; + +} eppp_config_t; + +esp_netif_t *eppp_connect(eppp_config_t *config); + +esp_netif_t *eppp_listen(eppp_config_t *config); + +void eppp_close(esp_netif_t *netif); + +esp_netif_t *eppp_init(enum eppp_type role, eppp_config_t *config); + +void eppp_deinit(esp_netif_t *netif); + +esp_netif_t *eppp_open(enum eppp_type role, eppp_config_t *config, TickType_t connect_timeout); + +esp_err_t eppp_netif_stop(esp_netif_t *netif, TickType_t stop_timeout); + +esp_err_t eppp_netif_start(esp_netif_t *netif); + +esp_err_t eppp_perform(esp_netif_t *netif); + +typedef esp_err_t (*eppp_channel_fn_t)(void *h, void *buffer, size_t len); + +esp_err_t eppp_add_channel(int nr, eppp_channel_fn_t *tx, const eppp_channel_fn_t rx); diff --git a/components/eppp_link/test/test_app/CMakeLists.txt b/components/eppp_link/test/test_app/CMakeLists.txt new file mode 100644 index 0000000000..cc4589c454 --- /dev/null +++ b/components/eppp_link/test/test_app/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/tools/unit-test-app/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_app) diff --git a/components/eppp_link/test/test_app/README.md b/components/eppp_link/test/test_app/README.md new file mode 100644 index 0000000000..cb8add3aa4 --- /dev/null +++ b/components/eppp_link/test/test_app/README.md @@ -0,0 +1,73 @@ + +# Test application running both server and client on the same device + +Need to connect client's Tx to server's Rx and vice versa: +GPIO25 - GPIO4 +GPIO26 - GPIO5 + +We wait for the connection and then we start pinging the client's address on server's netif. + +## Example of output: + +``` +I (393) eppp_test_app: [APP] Startup.. +I (393) eppp_test_app: [APP] Free memory: 296332 bytes +I (393) eppp_test_app: [APP] IDF version: v5.3-dev-1154-gf14d9e7431-dirty +I (423) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (423) uart: queue free spaces: 16 +I (433) eppp_link: Waiting for IP address +I (433) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (443) uart: queue free spaces: 16 +I (443) eppp_link: Waiting for IP address +I (6473) esp-netif_lwip-ppp: Connected +I (6513) eppp_link: Got IPv4 event: Interface "pppos_client" address: 192.168.11.2 +I (6523) esp-netif_lwip-ppp: Connected +I (6513) eppp_link: Connected! +I (6523) eppp_link: Got IPv4 event: Interface "pppos_server" address: 192.168.11.1 +I (6553) main_task: Returned from app_main() +64bytes from 192.168.11.2 icmp_seq=1 ttl=255 time=18 ms +64bytes from 192.168.11.2 icmp_seq=2 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=3 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=4 ttl=255 time=20 ms +64bytes from 192.168.11.2 icmp_seq=5 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=6 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=7 ttl=255 time=19 ms +From 192.168.11.2 icmp_seq=8 timeout // <-- Disconnected Tx-Rx wires +From 192.168.11.2 icmp_seq=9 timeout +``` +## Test cases + +This test app exercises these methods of setting up server-client connection: +* simple blocking API (eppp_listen() <--> eppp_connect()): Uses network events internally and waits for connection +* simplified non-blocking API (eppp_open(EPPP_SERVER, ...) <--> eppp_open(EPPP_SERVER, ...) ): Uses events internally, optionally waits for connecting +* manual API (eppp_init(), eppp_netif_start(), eppp_perform()): User to manually drive Rx task + - Note that the ping test for this test case takes longer, since we call perform for both server and client from one task, for example: + +``` +TEST(eppp_test, open_close_taskless)I (28562) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (28572) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +Note: esp_netif_init() has been called. Until next reset, TCP/IP task will periodicially allocate memory and consume CPU time. +I (28602) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (28612) uart: queue free spaces: 16 +I (28612) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (28622) uart: queue free spaces: 16 +I (28642) esp-netif_lwip-ppp: Connected +I (28642) esp-netif_lwip-ppp: Connected +I (28642) test: Got IPv4 event: Interface "pppos_server(EPPP0)" address: 192.168.11.1 +I (28642) esp-netif_lwip-ppp: Connected +I (28652) test: Got IPv4 event: Interface "pppos_client(EPPP1)" address: 192.168.11.2 +I (28662) esp-netif_lwip-ppp: Connected +64bytes from 192.168.11.2 icmp_seq=1 ttl=255 time=93 ms +64bytes from 192.168.11.2 icmp_seq=2 ttl=255 time=98 ms +64bytes from 192.168.11.2 icmp_seq=3 ttl=255 time=99 ms +64bytes from 192.168.11.2 icmp_seq=4 ttl=255 time=99 ms +64bytes from 192.168.11.2 icmp_seq=5 ttl=255 time=99 ms +5 packets transmitted, 5 received, time 488ms +I (29162) esp-netif_lwip-ppp: User interrupt +I (29162) test: Disconnected interface "pppos_client(EPPP1)" +I (29172) esp-netif_lwip-ppp: User interrupt +I (29172) test: Disconnected interface "pppos_server(EPPP0)" +MALLOC_CAP_8BIT usage: Free memory delta: 0 Leak threshold: -64 +MALLOC_CAP_32BIT usage: Free memory delta: 0 Leak threshold: -64 + PASS +``` diff --git a/components/eppp_link/test/test_app/main/CMakeLists.txt b/components/eppp_link/test/test_app/main/CMakeLists.txt new file mode 100644 index 0000000000..c75a706cc2 --- /dev/null +++ b/components/eppp_link/test/test_app/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS app_main.c + INCLUDE_DIRS "." + REQUIRES test_utils + PRIV_REQUIRES unity nvs_flash esp_netif driver esp_event) diff --git a/components/eppp_link/test/test_app/main/app_main.c b/components/eppp_link/test/test_app/main/app_main.c new file mode 100644 index 0000000000..e4edfa487b --- /dev/null +++ b/components/eppp_link/test/test_app/main/app_main.c @@ -0,0 +1,344 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include "esp_system.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "eppp_link.h" +#include "lwip/sockets.h" +#include "esp_log.h" +#include "ping/ping_sock.h" +#include "driver/uart.h" +#include "test_utils.h" +#include "unity.h" +#include "test_utils.h" +#include "unity_fixture.h" +#include "memory_checks.h" +#include "lwip/sys.h" + +#define CLIENT_INFO_CONNECTED BIT0 +#define CLIENT_INFO_DISCONNECT BIT1 +#define CLIENT_INFO_CLOSED BIT2 +#define PING_SUCCEEDED BIT3 +#define PING_FAILED BIT4 +#define STOP_WORKER_TASK BIT5 +#define WORKER_TASK_STOPPED BIT6 + +TEST_GROUP(eppp_test); +TEST_SETUP(eppp_test) +{ + // Perform some open/close operations to disregard lazy init one-time allocations + // LWIP: core protection mutex + sys_arch_protect(); + sys_arch_unprotect(0); + // UART: install and delete both drivers to disregard potential leak in allocated interrupt slot + TEST_ESP_OK(uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0)); + TEST_ESP_OK(uart_driver_delete(UART_NUM_1)); + TEST_ESP_OK(uart_driver_install(UART_NUM_2, 256, 0, 0, NULL, 0)); + TEST_ESP_OK(uart_driver_delete(UART_NUM_2)); + // PING: used for timestamps + struct timeval time; + gettimeofday(&time, NULL); + + test_utils_record_free_mem(); + TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL)); +} + +TEST_TEAR_DOWN(eppp_test) +{ + test_utils_finish_and_evaluate_leaks(32, 64); +} + +static void test_on_ping_end(esp_ping_handle_t hdl, void *args) +{ + EventGroupHandle_t event = args; + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + printf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms); + if (transmitted == received) { + xEventGroupSetBits(event, PING_SUCCEEDED); + } else { + xEventGroupSetBits(event, PING_FAILED); + } +} + +static void test_on_ping_success(esp_ping_handle_t hdl, void *args) +{ + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + printf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n", + recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); +} + +struct client_info { + esp_netif_t *netif; + EventGroupHandle_t event; +}; + +static void open_client_task(void *ctx) +{ + struct client_info *info = ctx; + eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG(); + config.uart.port = UART_NUM_2; + config.uart.tx_io = 4; + config.uart.rx_io = 5; + + info->netif = eppp_connect(&config); + xEventGroupSetBits(info->event, CLIENT_INFO_CONNECTED); + + // wait for disconnection trigger + EventBits_t bits = xEventGroupWaitBits(info->event, CLIENT_INFO_DISCONNECT, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & CLIENT_INFO_DISCONNECT, CLIENT_INFO_DISCONNECT); + eppp_close(info->netif); + xEventGroupSetBits(info->event, CLIENT_INFO_CLOSED); + vTaskDelete(NULL); +} + +TEST(eppp_test, init_deinit) +{ + // Init and deinit server size + eppp_config_t config = EPPP_DEFAULT_CONFIG(0, 0); + esp_netif_t *netif = eppp_init(EPPP_SERVER, &config); + TEST_ASSERT_NOT_NULL(netif); + eppp_deinit(netif); + netif = NULL; + // Init and deinit client size + netif = eppp_init(EPPP_CLIENT, &config); + TEST_ASSERT_NOT_NULL(netif); + eppp_deinit(netif); +} + +static EventBits_t ping_test(uint32_t addr, esp_netif_t *netif, EventGroupHandle_t event) +{ + ip_addr_t target_addr = { .type = IPADDR_TYPE_V4, .u_addr.ip4.addr = addr }; + esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); + ping_config.interval_ms = 100; + ping_config.target_addr = target_addr; + ping_config.interface = esp_netif_get_netif_impl_index(netif); + esp_ping_callbacks_t cbs = { .cb_args = event, .on_ping_end = test_on_ping_end, .on_ping_success = test_on_ping_success }; + esp_ping_handle_t ping; + esp_ping_new_session(&ping_config, &cbs, &ping); + esp_ping_start(ping); + // Wait for the client thread closure and delete locally created objects + EventBits_t bits = xEventGroupWaitBits(event, PING_SUCCEEDED | PING_FAILED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + esp_ping_stop(ping); + esp_ping_delete_session(ping); + return bits; +} + +TEST(eppp_test, open_close) +{ + test_case_uses_tcpip(); + + eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG(); + struct client_info client = { .netif = NULL, .event = xEventGroupCreate()}; + + TEST_ESP_OK(esp_event_loop_create_default()); + + TEST_ASSERT_NOT_NULL(client.event); + + // Need to connect the client in a separate thread, as the simplified API blocks until connection + xTaskCreate(open_client_task, "client_task", 4096, &client, 5, NULL); + + // Now start the server + esp_netif_t *eppp_server = eppp_listen(&config); + + // Wait for the client to connect + EventBits_t bits = xEventGroupWaitBits(client.event, CLIENT_INFO_CONNECTED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & CLIENT_INFO_CONNECTED, CLIENT_INFO_CONNECTED); + + // Check that both server and client are valid netif pointers + TEST_ASSERT_NOT_NULL(eppp_server); + TEST_ASSERT_NOT_NULL(client.netif); + + // Now that we're connected, let's try to ping clients address + bits = ping_test(config.ppp.their_ip4_addr, eppp_server, client.event); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + + // Trigger client disconnection and close the server + xEventGroupSetBits(client.event, CLIENT_INFO_DISCONNECT); + eppp_close(eppp_server); + + // Wait for the client thread closure and delete locally created objects + bits = xEventGroupWaitBits(client.event, CLIENT_INFO_CLOSED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & CLIENT_INFO_CLOSED, CLIENT_INFO_CLOSED); + + TEST_ESP_OK(esp_event_loop_delete_default()); + vEventGroupDelete(client.event); + + // wait for the lwip sockets to close cleanly + vTaskDelay(pdMS_TO_TICKS(1000)); +} + +static void on_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + EventGroupHandle_t event = arg; + if (base == IP_EVENT && event_id == IP_EVENT_PPP_GOT_IP) { + ip_event_got_ip_t *e = (ip_event_got_ip_t *)data; + esp_netif_t *netif = e->esp_netif; + ESP_LOGI("test", "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif), + esp_netif_get_ifkey(netif), IP2STR(&e->ip_info.ip)); + if (strcmp("pppos_server", esp_netif_get_desc(netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_SERVER); + } else if (strcmp("pppos_client", esp_netif_get_desc(netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_CLIENT); + } + } else if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) { + esp_netif_t **netif = data; + ESP_LOGI("test", "Disconnected interface \"%s(%s)\"", esp_netif_get_desc(*netif), esp_netif_get_ifkey(*netif)); + if (strcmp("pppos_server", esp_netif_get_desc(*netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_SERVER); + } else if (strcmp("pppos_client", esp_netif_get_desc(*netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_CLIENT); + } + } +} + +TEST(eppp_test, open_close_nonblocking) +{ + test_case_uses_tcpip(); + EventGroupHandle_t event = xEventGroupCreate(); + + eppp_config_t server_config = EPPP_DEFAULT_SERVER_CONFIG(); + TEST_ESP_OK(esp_event_loop_create_default()); + + // Open the server size + TEST_ESP_OK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, event)); + esp_netif_t *eppp_server = eppp_open(EPPP_SERVER, &server_config, 0); + TEST_ASSERT_NOT_NULL(eppp_server); + // Open the client size + eppp_config_t client_config = EPPP_DEFAULT_SERVER_CONFIG(); + client_config.uart.port = UART_NUM_2; + client_config.uart.tx_io = 4; + client_config.uart.rx_io = 5; + esp_netif_t *eppp_client = eppp_open(EPPP_CLIENT, &client_config, 0); + TEST_ASSERT_NOT_NULL(eppp_client); + const EventBits_t wait_bits = (1 << EPPP_SERVER) | (1 << EPPP_CLIENT); + EventBits_t bits = xEventGroupWaitBits(event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits); + + // Now that we're connected, let's try to ping clients address + bits = ping_test(server_config.ppp.their_ip4_addr, eppp_server, event); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + + // stop network for both client and server + eppp_netif_stop(eppp_client, 0); // ignore result, since we're not waiting for clean close + eppp_close(eppp_server); + eppp_close(eppp_client); // finish client close + TEST_ESP_OK(esp_event_loop_delete_default()); + vEventGroupDelete(event); + + // wait for the lwip sockets to close cleanly + vTaskDelay(pdMS_TO_TICKS(1000)); +} + + +struct worker { + esp_netif_t *eppp_server; + esp_netif_t *eppp_client; + EventGroupHandle_t event; +}; + +static void worker_task(void *ctx) +{ + struct worker *info = ctx; + while (1) { + eppp_perform(info->eppp_server); + eppp_perform(info->eppp_client); + if (xEventGroupGetBits(info->event) & STOP_WORKER_TASK) { + break; + } + } + xEventGroupSetBits(info->event, WORKER_TASK_STOPPED); + vTaskDelete(NULL); +} + +TEST(eppp_test, open_close_taskless) +{ + test_case_uses_tcpip(); + struct worker info = { .event = xEventGroupCreate() }; + + TEST_ESP_OK(esp_event_loop_create_default()); + TEST_ESP_OK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, info.event)); + TEST_ESP_OK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_event, info.event)); + + // Create server + eppp_config_t server_config = EPPP_DEFAULT_SERVER_CONFIG(); + info.eppp_server = eppp_init(EPPP_SERVER, &server_config); + TEST_ASSERT_NOT_NULL(info.eppp_server); + // Create client + eppp_config_t client_config = EPPP_DEFAULT_CLIENT_CONFIG(); + client_config.uart.port = UART_NUM_2; + client_config.uart.tx_io = 4; + client_config.uart.rx_io = 5; + info.eppp_client = eppp_init(EPPP_CLIENT, &client_config); + TEST_ASSERT_NOT_NULL(info.eppp_client); + // Start workers + xTaskCreate(worker_task, "worker", 4096, &info, 5, NULL); + // Start network + TEST_ESP_OK(eppp_netif_start(info.eppp_server)); + TEST_ESP_OK(eppp_netif_start(info.eppp_client)); + + const EventBits_t wait_bits = (1 << EPPP_SERVER) | (1 << EPPP_CLIENT); + EventBits_t bits = xEventGroupWaitBits(info.event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits); + xEventGroupClearBits(info.event, wait_bits); + + // Now that we're connected, let's try to ping clients address + bits = ping_test(server_config.ppp.their_ip4_addr, info.eppp_server, info.event); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + + // stop network for both client and server, we won't wait for completion so expecting ESP_FAIL + TEST_ASSERT_EQUAL(eppp_netif_stop(info.eppp_client, 0), ESP_FAIL); + TEST_ASSERT_EQUAL(eppp_netif_stop(info.eppp_server, 0), ESP_FAIL); + // and wait for completion + bits = xEventGroupWaitBits(info.event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits); + + // now stop the worker + xEventGroupSetBits(info.event, STOP_WORKER_TASK); + bits = xEventGroupWaitBits(info.event, WORKER_TASK_STOPPED, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & WORKER_TASK_STOPPED, WORKER_TASK_STOPPED); + + // and destroy objects + eppp_deinit(info.eppp_server); + eppp_deinit(info.eppp_client); + TEST_ESP_OK(esp_event_loop_delete_default()); + vEventGroupDelete(info.event); + + // wait for the lwip sockets to close cleanly + vTaskDelay(pdMS_TO_TICKS(1000)); +} + + +TEST_GROUP_RUNNER(eppp_test) +{ + RUN_TEST_CASE(eppp_test, init_deinit) + RUN_TEST_CASE(eppp_test, open_close) + RUN_TEST_CASE(eppp_test, open_close_nonblocking) + RUN_TEST_CASE(eppp_test, open_close_taskless) +} + +void app_main(void) +{ + UNITY_MAIN(eppp_test); +} diff --git a/components/eppp_link/test/test_app/main/idf_component.yml b/components/eppp_link/test/test_app/main/idf_component.yml new file mode 100644 index 0000000000..7ecb517e8a --- /dev/null +++ b/components/eppp_link/test/test_app/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../.." diff --git a/components/eppp_link/test/test_app/sdkconfig.defaults b/components/eppp_link/test/test_app/sdkconfig.defaults new file mode 100644 index 0000000000..0442a3bc8a --- /dev/null +++ b/components/eppp_link/test/test_app/sdkconfig.defaults @@ -0,0 +1,12 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.3.0 Project Minimal Configuration +# +CONFIG_UART_ISR_IN_IRAM=y +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=0 +CONFIG_FREERTOS_UNICORE=y +CONFIG_HEAP_TRACING_STANDALONE=y +CONFIG_HEAP_TRACING_STACK_DEPTH=6 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n +CONFIG_LWIP_PPP_DEBUG_ON=y +CONFIG_UNITY_ENABLE_FIXTURE=y