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 +#include +#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 +#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 + */ + 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 +#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", "