From dfc9a5815d7463704c945a9f99d7fa9bddcb39b2 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 21 Dec 2023 13:34:45 +0100 Subject: [PATCH 01/14] feat(eppp_link): Added ESP-PPP-Link component --- .pre-commit-config.yaml | 2 +- components/eppp_link/.cz.yaml | 8 + components/eppp_link/CMakeLists.txt | 3 + components/eppp_link/Kconfig | 50 +++ components/eppp_link/LICENSE | 202 +++++++++ components/eppp_link/README.md | 43 ++ components/eppp_link/eppp_link.c | 527 +++++++++++++++++++++++ components/eppp_link/eppp_link_types.h | 0 components/eppp_link/idf_component.yml | 6 + components/eppp_link/include/eppp_link.h | 9 + 10 files changed, 849 insertions(+), 1 deletion(-) create mode 100644 components/eppp_link/.cz.yaml create mode 100644 components/eppp_link/CMakeLists.txt create mode 100644 components/eppp_link/Kconfig create mode 100644 components/eppp_link/LICENSE create mode 100644 components/eppp_link/README.md create mode 100644 components/eppp_link/eppp_link.c create mode 100644 components/eppp_link/eppp_link_types.h create mode 100644 components/eppp_link/idf_component.yml create mode 100644 components/eppp_link/include/eppp_link.h diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9261d60d5fd..2c5b8ec94ff 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 00000000000..abdb7071800 --- /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 00000000000..bd6123139b4 --- /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) diff --git a/components/eppp_link/Kconfig b/components/eppp_link/Kconfig new file mode 100644 index 00000000000..44c9e8836e5 --- /dev/null +++ b/components/eppp_link/Kconfig @@ -0,0 +1,50 @@ +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. + + config EPPP_LINK_SERVER_IP + hex "Server IP address" + range 0 0xFFFFFFFF + default 0xc0a80b01 + help + Preferred IP address of the server side. + + config EPPP_LINK_CLIENT_IP + hex "Client IP address" + range 0 0xFFFFFFFF + default 0xc0a80b02 + help + Preferred IP address of the client side. + + +endmenu diff --git a/components/eppp_link/LICENSE b/components/eppp_link/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /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 00000000000..c4725314047 --- /dev/null +++ b/components/eppp_link/README.md @@ -0,0 +1,43 @@ +# 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 + +* `eppp_client_init()` -- Initialization of the client. Need to run only once. +* `eppp_client_start()` -- Starts the connection, could be called after startup or whenever a connection is lost +* `eppp_client_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) + +### Server + +* `eppp_listen()` -- Simplified API. Provides the initialization, starts the task and blocks until the client connects +* `eppp_server_init()` -- Initialization of the server. Need to run only once. +* `eppp_server_start()` -- (Re)starts the connection, should be called after startup or whenever a connection is lost +* `eppp_server_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 @ 40MHz + +* 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 00000000000..b7253e4788a --- /dev/null +++ b/components/eppp_link/eppp_link.c @@ -0,0 +1,527 @@ +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_netif_ppp.h" +#include "eppp_link_types.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" +#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; + +#if CONFIG_EPPP_LINK_DEVICE_SPI +static spi_device_handle_t s_spi_device; +#define SPI_HOST SPI2_HOST +#define GPIO_MOSI 11 +#define GPIO_MISO 13 +#define GPIO_SCLK 12 +#define GPIO_CS 10 +#define GPIO_INTR 2 +#endif // CONFIG_EPPP_LINK_DEVICE_SPI + +enum eppp_type { + EPPP_SERVER, + EPPP_CLIENT, +}; + +struct eppp_handle { + QueueHandle_t out_queue; + QueueHandle_t ready_semaphore; + esp_netif_t *netif; + enum eppp_type role; +}; + +struct packet { + size_t len; + uint8_t *data; +}; + + +static esp_err_t transmit(void *h, void *buffer, size_t len) +{ +#if CONFIG_EXAMPLE_CONNECT_PPP_DEVICE_SPI +#define MAX_PAYLOAD 1600 + struct eppp_handle *handle = h; + 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); + 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!"); + } + } while (remaining > 0); +#endif + return ESP_OK; +} + +static esp_netif_t *netif_init(enum eppp_type role) +{ + static int s_eppp_netif_count = 0; // used as a suffix for the netif key + 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->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; + } + h->role = role; + 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; + } + } + + 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) - 1] += 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"); + vQueueDelete(h->out_queue); + free(h); + return NULL; + } + return netif; + +} + +static esp_err_t 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 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_PPP_GOT_IP) { + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(s_event_group, GOT_IPV4); + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Disconnect from PPP Server"); + 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); + } else { + ESP_LOGI(TAG, "PPP Connection failed %d times, try to reconnect.", s_retry_num); + 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 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 init_master(spi_device_handle_t *spi, esp_netif_t *netif) +{ + spi_bus_config_t bus_cfg = {}; + bus_cfg.mosi_io_num = GPIO_MOSI; + bus_cfg.miso_io_num = GPIO_MISO; + bus_cfg.sclk_io_num = GPIO_SCLK; + bus_cfg.quadwp_io_num = -1; + bus_cfg.quadhd_io_num = -1; + bus_cfg.max_transfer_sz = 14000; + bus_cfg.flags = 0; + bus_cfg.intr_flags = 0; + + if (spi_bus_initialize(SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO) != ESP_OK) { + return ESP_FAIL; + } + + spi_device_interface_config_t dev_cfg = {}; + dev_cfg.clock_speed_hz = 40 * 1000 * 1000; + dev_cfg.mode = 0; + dev_cfg.spics_io_num = GPIO_CS; + dev_cfg.cs_ena_pretrans = 0; + dev_cfg.cs_ena_posttrans = 0; + dev_cfg.duty_cycle_pos = 128; + dev_cfg.input_delay_ns = 0; + 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(SPI_HOST, &dev_cfg, spi) != 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(GPIO_INTR), + }; + + gpio_config(&io_conf); + gpio_install_isr_service(0); + gpio_set_intr_type(GPIO_INTR, GPIO_INTR_POSEDGE); + gpio_isr_handler_add(GPIO_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(GPIO_INTR, 1); +} + +static void post_trans(spi_slave_transaction_t *trans) +{ + gpio_set_level(GPIO_INTR, 0); +} + +static esp_err_t init_slave(spi_device_handle_t *spi, esp_netif_t *netif) +{ + spi_bus_config_t bus_cfg = {}; + bus_cfg.mosi_io_num = GPIO_MOSI; + bus_cfg.miso_io_num = GPIO_MISO; + bus_cfg.sclk_io_num = GPIO_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 = GPIO_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(GPIO_INTR), + }; + + gpio_config(&io_conf); + gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY); + + //Initialize SPI slave interface + if (spi_slave_initialize(SPI_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); +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) +{ + 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) +{ + 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(s_spi_device, &t->master); +} + +static esp_err_t perform_transaction_slave(union transaction *t, struct eppp_handle *h) +{ + return spi_slave_transmit(SPI_HOST, &t->slave, portMAX_DELAY); +} + +_Noreturn static void ppp_task(void *args) +{ + static WORD_ALIGNED_ATTR uint8_t out_buf[TRANSFER_SIZE] = {}; + static WORD_ALIGNED_ATTR uint8_t in_buf[TRANSFER_SIZE] = {}; + + esp_netif_t *netif = args; + struct eppp_handle *h = esp_netif_get_io_driver(netif); + union transaction t; + + 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; + 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->role == EPPP_CLIENT) { + // as a client, try to actively connect (not waiting for server's interrupt) + xSemaphoreGive(h->ready_semaphore); + } + while (1) { + 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; + BaseType_t tx_queue_stat = xQueueReceive(h->out_queue, &buf, 0); + if (tx_queue_stat == pdTRUE && buf.data) { + if (buf.len > SHORT_PAYLOAD) { + head->magic = FRAME_OUT_CTRL; + head->size = buf.len; + out_long_payload = buf.len; + need_data_frame = true; +// printf("need_data_frame %d\n", buf.len); + } 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); + 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"); + continue; + } + 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"); + continue; + } +// printf("MAGIC: %x\n", head->magic); + if (head->magic != FRAME_IN_CTRL && head->magic != FRAME_IN_CTRL_EX) { + ESP_LOGE(TAG, "Wrong magic"); + continue; + } + if (head->magic == FRAME_IN_CTRL_EX && head->short_size > 0) { + esp_netif_receive(netif, in_buf + sizeof(struct header), head->short_size, NULL); + } + 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) { + continue; + } + // now, we need data frame +// printf("performing data frame %d %d\n", out_long_payload, buf.len); + head = (void *)out_buf; + head->magic = FRAME_OUT_DATA; + head->size = out_long_payload; + head->checksum = 0; + 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); + + ret = perform_transaction(&t, h); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "spi_device_transmit failed"); + continue; + } + 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"); + continue; + } + if (head->magic != FRAME_IN_DATA) { + ESP_LOGE(TAG, "Wrong magic"); + continue; + } +// printf("got size %d\n", head->size); + + if (head->size > 0) { +// ESP_LOG_BUFFER_HEXDUMP(TAG, in_buf + sizeof(struct header), head->size, ESP_LOG_INFO); + esp_netif_receive(netif, in_buf + sizeof(struct header), head->size, NULL); + } + } +} +#endif // CONFIG_EPPP_LINK_DEVICE_SPI + + +static esp_netif_t *default_setup(enum eppp_type role) +{ + 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"); + return NULL; + } + esp_netif_t *netif = netif_init(role); + if (!netif) { + ESP_LOGE(TAG, "Failed to initialize PPP netif"); + vEventGroupDelete(s_event_group); + return NULL; + } + esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, netif); + esp_netif_ppp_config_t netif_params; + ESP_ERROR_CHECK(esp_netif_ppp_get_params(netif, &netif_params)); + netif_params.ppp_our_ip4_addr = esp_netif_htonl(role == EPPP_SERVER ? CONFIG_EPPP_LINK_SERVER_IP : CONFIG_EPPP_LINK_CLIENT_IP); + netif_params.ppp_their_ip4_addr = esp_netif_htonl(role == EPPP_SERVER ? CONFIG_EPPP_LINK_CLIENT_IP : CONFIG_EPPP_LINK_SERVER_IP); + ESP_ERROR_CHECK(esp_netif_ppp_set_params(netif, &netif_params)); +#if CONFIG_EPPP_LINK_DEVICE_SPI + if (role == EPPP_CLIENT) { + init_master(&s_spi_device, netif); + } else { + init_slave(&s_spi_device, netif); + } +#endif + + netif_start(netif); + + if (xTaskCreate(ppp_task, "ppp connect", 4096, netif, 18, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Failed to create a ppp connection task"); + return NULL; + } + ESP_LOGI(TAG, "Waiting for IP address"); + EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS, pdFALSE, pdFALSE, portMAX_DELAY); + if (bits & CONNECTION_FAILED) { + ESP_LOGE(TAG, "Connection failed!"); + return NULL; + } + ESP_LOGI(TAG, "Connected!"); + return netif; +} + +esp_netif_t *eppp_connect(void) +{ + return default_setup(EPPP_CLIENT); +} + +esp_netif_t *eppp_listen(void) +{ + return default_setup(EPPP_SERVER); +} diff --git a/components/eppp_link/eppp_link_types.h b/components/eppp_link/eppp_link_types.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/eppp_link/idf_component.yml b/components/eppp_link/idf_component.yml new file mode 100644 index 00000000000..fbab49cb81a --- /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 00000000000..4d8e820bfbd --- /dev/null +++ b/components/eppp_link/include/eppp_link.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +esp_netif_t *eppp_connect(void); + +esp_netif_t *eppp_listen(void); From c74f1fb2f9714d7188cc835a49ddb48c82821ba7 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 21 Dec 2023 14:28:28 +0100 Subject: [PATCH 02/14] feat(eppp_link): Added host/ppp-client example --- components/eppp_link/eppp_link.c | 2 +- .../eppp_link/examples/host/CMakeLists.txt | 8 + components/eppp_link/examples/host/README.md | 9 + .../examples/host/main/CMakeLists.txt | 2 + .../examples/host/main/Kconfig.projbuild | 43 ++++ .../eppp_link/examples/host/main/app_main.c | 203 ++++++++++++++++++ .../examples/host/main/idf_component.yml | 4 + .../examples/host/main/register_iperf.c | 183 ++++++++++++++++ .../examples/host/sdkconfig.defaults | 8 + 9 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 components/eppp_link/examples/host/CMakeLists.txt create mode 100644 components/eppp_link/examples/host/README.md create mode 100644 components/eppp_link/examples/host/main/CMakeLists.txt create mode 100644 components/eppp_link/examples/host/main/Kconfig.projbuild create mode 100644 components/eppp_link/examples/host/main/app_main.c create mode 100644 components/eppp_link/examples/host/main/idf_component.yml create mode 100644 components/eppp_link/examples/host/main/register_iperf.c create mode 100644 components/eppp_link/examples/host/sdkconfig.defaults diff --git a/components/eppp_link/eppp_link.c b/components/eppp_link/eppp_link.c index b7253e4788a..e5ac9c9bb67 100644 --- a/components/eppp_link/eppp_link.c +++ b/components/eppp_link/eppp_link.c @@ -57,7 +57,7 @@ struct packet { static esp_err_t transmit(void *h, void *buffer, size_t len) { -#if CONFIG_EXAMPLE_CONNECT_PPP_DEVICE_SPI +#if CONFIG_EPPP_LINK_DEVICE_SPI #define MAX_PAYLOAD 1600 struct eppp_handle *handle = h; struct packet buf = { }; diff --git a/components/eppp_link/examples/host/CMakeLists.txt b/components/eppp_link/examples/host/CMakeLists.txt new file mode 100644 index 00000000000..3405abc5268 --- /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 00000000000..a6592627c68 --- /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 00000000000..0198ddd3236 --- /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 00000000000..02881e10a3c --- /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 00000000000..765589c55c6 --- /dev/null +++ b/components/eppp_link/examples/host/main/app_main.c @@ -0,0 +1,203 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#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 + */ + esp_netif_t *eppp_netif = eppp_connect(); + 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 00000000000..7ecb517e8a6 --- /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 00000000000..d2169c3763c --- /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 +#include +#include +#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", "", + "run in client mode, connecting to "); + 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", "", + "server port to listen on/connect to"); + iperf_args.length = arg_int0("l", "len", "", "set read/write buffer size"); + iperf_args.interval = arg_int0("i", "interval", "", + "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "