From c603e2d956ddddf5fcf4a49bc4af5aed9a199528 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 8 Jun 2023 07:29:22 +0200 Subject: [PATCH] Examples/network: Add wifi station to usb 1:1 forwarder (L2) --- .../wifi_eth_usb_bridge/CMakeLists.txt | 9 + .../network/wifi_eth_usb_bridge/README.md | 97 +++++ .../wifi_eth_usb_bridge/main/CMakeLists.txt | 9 + .../main/Kconfig.projbuild | 17 + .../main/idf_component.yml | 5 + .../wifi_eth_usb_bridge/main/manual_config.c | 113 ++++++ .../wifi_eth_usb_bridge/main/provisioning.c | 96 +++++ .../wifi_eth_usb_bridge/main/provisioning.h | 22 ++ .../main/scheme_generic_httpd.c | 91 +++++ .../wifi_eth_usb_bridge/main/tusb_ncm_main.c | 339 ++++++++++++++++++ .../wifi_eth_usb_bridge/sdkconfig.defaults | 6 + 11 files changed, 804 insertions(+) create mode 100644 examples/network/wifi_eth_usb_bridge/CMakeLists.txt create mode 100644 examples/network/wifi_eth_usb_bridge/README.md create mode 100644 examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt create mode 100644 examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild create mode 100644 examples/network/wifi_eth_usb_bridge/main/idf_component.yml create mode 100644 examples/network/wifi_eth_usb_bridge/main/manual_config.c create mode 100644 examples/network/wifi_eth_usb_bridge/main/provisioning.c create mode 100644 examples/network/wifi_eth_usb_bridge/main/provisioning.h create mode 100644 examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c create mode 100644 examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c create mode 100644 examples/network/wifi_eth_usb_bridge/sdkconfig.defaults diff --git a/examples/network/wifi_eth_usb_bridge/CMakeLists.txt b/examples/network/wifi_eth_usb_bridge/CMakeLists.txt new file mode 100644 index 0000000000..09ccbdecf3 --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following 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) + +# This example needs a DNS server: let's use the simple DNS server implementation from captive portal example +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/http_server/captive_portal/components/dns_server) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tusb_ncm) diff --git a/examples/network/wifi_eth_usb_bridge/README.md b/examples/network/wifi_eth_usb_bridge/README.md new file mode 100644 index 0000000000..aa884d4c91 --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/README.md @@ -0,0 +1,97 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | + +# TinyUSB Network Control Model Device Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +Network Control Model (NCM) is a sub-class of Communication Device Class (CDC) USB Device for Ethernet-over-USB applications. + +In this example, we implemented the ESP development board to transmit WiFi data to the Linux host via USB, so that the Linux host could access the Internet. + +As a USB stack, a TinyUSB component is used. + +## How to use example + +This example demonstrate usage of USB NCM device as USB-WiFi bridge. It also allows for reconfiguring WiFi settings using a virtual network in NCM device. The reconfiguration mode is initialized if the WiFi settings are not available, connection fails or manually by long pressing the Boot button (GPIO0). +It is possible to configure WiFi settings (SSID and password) in a browser on an address `"wifi.settings"` or using unified provisioning. + +### Hardware Required + +Any ESP board that have USB-OTG supported. + +#### Pin Assignment + +_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). + +### Configure the project + +Open the project configuration menu (`idf.py menuconfig`). + +In the `Example Configuration` menu choose the provisioning method: +* `EXAMPLE_WIFI_CONFIGURATION_MANUAL` for manual configuration using a webpage +* `EXAMPLE_WIFI_CONFIGURATION_PROVISIONING` for standard provisioning over the virtual USB network + +To provision the device using IDF provisioning tools (if `EXAMPLE_WIFI_CONFIGURATION_PROVISIONING` is selected) you can use idf provisioning utility with transport set to `softap`: +```bash +esp-idf/tools/esp_prov$ python esp_prov.py --transport softap ... +``` +Please refer to the provisioning documentation and `esp_prov` script [documentation](../../../../../tools/esp_prov/README.md) for more details. + +### Build, Flash, and Run + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +After the flashing you should see the output at idf monitor: + +``` +I (725) usb_net: Wi-Fi STA connected +I (735) usb_net: CONNECTED_BIT + +I (735) usb_net: connect success + +I (735) wifi:BcnInt:102400, DTIM:1 +I (745) usb_net: USB net initialization +I (745) tusb_desc: +┌─────────────────────────────────┐ +│ USB Device Descriptor Summary │ +├───────────────────┬─────────────┤ +│bDeviceClass │ 239 │ +├───────────────────┼─────────────┤ +│bDeviceSubClass │ 2 │ +├───────────────────┼─────────────┤ +│bDeviceProtocol │ 1 │ +├───────────────────┼─────────────┤ +│bMaxPacketSize0 │ 64 │ +├───────────────────┼─────────────┤ +│idVendor │ 0x303a │ +├───────────────────┼─────────────┤ +│idProduct │ 0x4002 │ +├───────────────────┼─────────────┤ +│bcdDevice │ 0x100 │ +├───────────────────┼─────────────┤ +│iManufacturer │ 0x1 │ +├───────────────────┼─────────────┤ +│iProduct │ 0x2 │ +├───────────────────┼─────────────┤ +│iSerialNumber │ 0x3 │ +├───────────────────┼─────────────┤ +│bNumConfigurations │ 0x1 │ +└───────────────────┴─────────────┘ +I (915) TinyUSB: TinyUSB Driver installed +I (925) usb_net: USB NCM initialization DONE +``` diff --git a/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt b/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt new file mode 100644 index 0000000000..d9293eaaea --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt @@ -0,0 +1,9 @@ +if(CONFIG_EXAMPLE_WIFI_CONFIGURATION_MANUAL) + set(config_method manual_config.c) +else() + set(config_method provisioning.c scheme_generic_httpd.c) +endif() + +idf_component_register(SRCS tusb_ncm_main.c + ${config_method} + INCLUDE_DIRS "") diff --git a/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild b/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild new file mode 100644 index 0000000000..0d3677e2dc --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "Example Configuration" + + choice EXAMPLE_WIFI_CONFIGURATION + prompt "WiFi configuration" + default EXAMPLE_WIFI_CONFIGURATION_MANUAL + help + Choose how the WiFi settings should be configured. + + config EXAMPLE_WIFI_CONFIGURATION_MANUAL + bool + prompt "Manual configuration via http server" + config EXAMPLE_WIFI_CONFIGURATION_PROVISIONING + bool + prompt "Using unified provisioning" + endchoice + +endmenu diff --git a/examples/network/wifi_eth_usb_bridge/main/idf_component.yml b/examples/network/wifi_eth_usb_bridge/main/idf_component.yml new file mode 100644 index 0000000000..73302c2ced --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "^1.0.1" + idf: "^5.0" diff --git a/examples/network/wifi_eth_usb_bridge/main/manual_config.c b/examples/network/wifi_eth_usb_bridge/main/manual_config.c new file mode 100644 index 0000000000..f2bc863062 --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/manual_config.c @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "esp_wifi.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_http_server.h" +#include "dns_server.h" + +static const char *TAG = "NCM_configuration"; +static httpd_handle_t s_web_server = NULL; +static EventGroupHandle_t *s_flags = NULL; +static int s_success_bit; + +bool is_provisioned(void) +{ + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) { + return false; + } + + if (strlen((const char *) wifi_cfg.sta.ssid)) { + return true; + } + + return false; +} + +static esp_err_t http_get_handler(httpd_req_t *req) +{ + const char page[] = "


\n" + "SSID:

\n" + "Password:

\n" + " " + "
"; + char* buf = NULL; + size_t buf_len; + buf_len = httpd_req_get_url_query_len(req) + 1; + if (buf_len > 1) { + buf = malloc(buf_len); + if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { + ESP_LOGI(TAG, "Found URL query => %s", buf); + char param[32]; + wifi_config_t wifi_cfg = {}; + + if (httpd_query_key_value(buf, "ssid", param, sizeof(param)) == ESP_OK) { + ESP_LOGI(TAG, "ssid=%s", param); + strncpy((char*)wifi_cfg.sta.ssid, param, sizeof(wifi_cfg.sta.ssid)); + } + if (httpd_query_key_value(buf, "password", param, sizeof(param)) == ESP_OK) { + ESP_LOGI(TAG, "password=%s", param); + strncpy((char*)wifi_cfg.sta.password, param, sizeof(wifi_cfg.sta.password)); + } + + if (strlen((char*)wifi_cfg.sta.ssid) > 0 && strlen((char*)wifi_cfg.sta.password)) { + const char wifi_configured[] = "

Connecting...

"; + ESP_LOGI(TAG, "WiFi settings accepted!"); + esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, wifi_configured, strlen(wifi_configured)); + free(buf); + if (s_flags) { + xEventGroupSetBits(*s_flags, s_success_bit); + } + return ESP_OK; + } + } + free(buf); + } + + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, page, sizeof(page)); + + return ESP_OK; +} + +static const httpd_uri_t root = { + .uri = "/", + .method = HTTP_GET, + .handler = http_get_handler, +}; + +static void start_webserver(void) +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.max_open_sockets = 3; + config.lru_purge_enable = true; + + // Start the httpd server + ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); + if (httpd_start(&s_web_server, &config) == ESP_OK) { + // Set URI handlers + ESP_LOGI(TAG, "Registering URI handlers"); + httpd_register_uri_handler(s_web_server, &root); + } +} + +esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fail_bit) +{ + start_webserver(); + // Start the DNS server that will reply to "wifi.settings" with "usb" network interface address + dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("wifi.settings" /* name */, "usb" /* USB netif ID */); + start_dns_server(&config); + + s_flags = flags; + s_success_bit = success_bit; + return ESP_OK; +} diff --git a/examples/network/wifi_eth_usb_bridge/main/provisioning.c b/examples/network/wifi_eth_usb_bridge/main/provisioning.c new file mode 100644 index 0000000000..6c77bb1b2a --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/provisioning.c @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_event.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "dns_server.h" + +static const char *TAG = "NCM_provisioning"; + +struct events { + EventGroupHandle_t *flags; + int success_bit; + int fail_bit; + bool success; +}; + +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + struct events *handler_args = arg; + switch (event_id) { + case WIFI_PROV_START: + ESP_LOGI(TAG, "Provisioning started"); + break; + case WIFI_PROV_CRED_RECV: { + wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *) event_data; + ESP_LOGI(TAG, "Received Wi-Fi credentials" + "\n\tSSID : %s\n\tPassword : %s", + (const char *) wifi_sta_cfg->ssid, + (const char *) wifi_sta_cfg->password); + break; + } + case WIFI_PROV_CRED_FAIL: { + wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *) event_data; + ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s" + "\n\tPlease reset to factory and retry provisioning", + (*reason == WIFI_PROV_STA_AUTH_ERROR) ? + "Wi-Fi station authentication failed" : "Wi-Fi access-point not found"); + handler_args->success = false; + + break; + } + case WIFI_PROV_CRED_SUCCESS: + ESP_LOGI(TAG, "Provisioning successful"); + handler_args->success = true; + break; + case WIFI_PROV_END: + /* De-initialize manager once provisioning is finished */ + wifi_prov_mgr_deinit(); + xEventGroupSetBits(*handler_args->flags, handler_args->success ? handler_args->success_bit : handler_args->fail_bit); + free(handler_args); + break; + default: + break; + } +} + +extern const wifi_prov_scheme_t wifi_prov_scheme_httpd; + +esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fail_bit) +{ + // Start the DNS server that will reply to "wifi.settings" with "usb" network interface address + dns_server_config_t dns_config = DNS_SERVER_CONFIG_SINGLE("wifi.settings" /* name */, "usb" /* USB netif ID */); + start_dns_server(&dns_config); + struct events *handler_args = malloc(sizeof(struct events)); + handler_args->flags = flags; + handler_args->success_bit = success_bit; + handler_args->fail_bit = fail_bit; + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, event_handler, handler_args)); + /* Configuration for the provisioning manager */ + wifi_prov_mgr_config_t config = { + .scheme = wifi_prov_scheme_httpd, + }; + + /* Initialize provisioning manager with the + * configuration parameters set above */ + ESP_ERROR_CHECK(wifi_prov_mgr_init(config)); + + /* TODO: Add more security options to menuconfig + */ + ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_0, NULL, NULL, NULL)); + return ESP_OK; +} + +bool is_provisioned(void) +{ + bool provisioned = false; + ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned)); + return provisioned; +} diff --git a/examples/network/wifi_eth_usb_bridge/main/provisioning.h b/examples/network/wifi_eth_usb_bridge/main/provisioning.h new file mode 100644 index 0000000000..a17d98469b --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/provisioning.h @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once + +/** + * @brief Checks if the device has been provisioned + * @return true if WiFi is provisioned + */ +bool is_provisioned(void); + +/** + * @brief Initiate provisioning + * @param flags Event flags to indicate status of provisioning + * @param success_bit bits set in the event flags on success + * @param fail_bit bits set in the event flags on failure + * @return ESP_OK if provisioning started + */ +esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fail_bit); diff --git a/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c b/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c new file mode 100644 index 0000000000..c0fbef202d --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "sdkconfig.h" +#include +#include +#include +#include +#include + +static const char *TAG = "wifi_prov_scheme_httpd"; + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + protocomm_httpd_config_t default_config = { + .data = { + .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG() + } + }; + + /* Start protocomm server on top of HTTP */ + esp_err_t err = protocomm_httpd_start(pc, &default_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return err; + } + + return ESP_OK; +} + +static esp_err_t prov_stop(protocomm_t *pc) +{ + esp_err_t err = protocomm_httpd_stop(pc); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error occurred while stopping protocomm_httpd"); + } + + return err; +} + +/** + * @brief Creates a configuration for this custom provisioning scheme. + * + * We don't need to pass any config option at this moment, so we create + * a dummy configuration since provisioning manager check for non-nullptr. + * If needed we can extend this scheme to provide some options for httpd + * or wifi provisioning. + */ +static void *new_config(void) +{ + return (void*)1; +} + +static void delete_config(void *config) +{ +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + return ESP_OK; +} + +/** + * @brief Creating a generic HTTPD scheme + */ +const wifi_prov_scheme_t wifi_prov_scheme_httpd = { + .prov_start = prov_start, + .prov_stop = prov_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_STA +}; diff --git a/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c b/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c new file mode 100644 index 0000000000..99b100a8eb --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c @@ -0,0 +1,339 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* DESCRIPTION: + * This example contains code to make ESP32-S2/S3 as a USB network Device. + */ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_private/wifi.h" +#include "nvs_flash.h" +#include "dhcpserver/dhcpserver_options.h" +#include "esp_mac.h" +#include "driver/gpio.h" + +#include "tinyusb.h" +#include "tinyusb_net.h" +#include "provisioning.h" + +static const char *TAG = "USB_NCM"; + +static EventGroupHandle_t s_event_flags; +static bool s_wifi_is_connected = false; + +const int CONNECTED_BIT = BIT0; +const int DISCONNECTED_BIT = BIT1; +const int RECONFIGURE_BIT = BIT2; +const int PROV_SUCCESS_BIT = BIT3; +const int PROV_FAIL_BIT = BIT4; + +static esp_netif_t *s_netif = NULL; + +/** + * WiFi -- USB bridge functionality + */ + +static esp_err_t usb_recv_callback(void *buffer, uint16_t len, void* ctx) +{ + if (s_wifi_is_connected) { + if (esp_wifi_internal_tx(ESP_IF_WIFI_STA, buffer, len) != ESP_OK) { + ESP_LOGD(TAG, "Failed to send packet to WiFi!"); + } + } + return ESP_OK; +} + +static void wifi_buff_free(void* buffer, void* ctx) +{ + esp_wifi_internal_free_rx_buffer(buffer); +} + +static esp_err_t pkt_wifi2usb(void *buffer, uint16_t len, void *eb) +{ + if (tinyusb_net_send_sync(buffer, len, eb, pdMS_TO_TICKS(100)) != ESP_OK) { + esp_wifi_internal_free_rx_buffer(eb); + ESP_LOGD(TAG, "Failed to send packet to USB!"); + } + return ESP_OK; +} + +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Wi-Fi STA disconnected"); + s_wifi_is_connected = false; + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, NULL); + esp_wifi_connect(); + xEventGroupClearBits(s_event_flags, CONNECTED_BIT); + xEventGroupSetBits(s_event_flags, DISCONNECTED_BIT); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + ESP_LOGI(TAG, "Wi-Fi STA connected"); + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, pkt_wifi2usb); + s_wifi_is_connected = true; + xEventGroupClearBits(s_event_flags, DISCONNECTED_BIT); + xEventGroupSetBits(s_event_flags, CONNECTED_BIT); + } +} + +static esp_err_t connect_wifi(void) +{ + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) { + // configuration not available, report error to restart provisioning + return ESP_FAIL; + } + esp_wifi_connect(); + EventBits_t status = xEventGroupWaitBits(s_event_flags, CONNECTED_BIT, 0, 1, 10000/portTICK_PERIOD_MS); + if (status & CONNECTED_BIT) { + ESP_LOGI(TAG, "WiFi station connected successfully"); + return ESP_OK; + } + ESP_LOGE(TAG, "WiFi station connected failed"); + return ESP_ERR_TIMEOUT; +} + +static void on_usb_net_init(void *ctx) +{ + ESP_LOGE(TAG, "USB NET device has been initialized!"); +} + +static esp_err_t usb_ncm_wifi_bridge(void) +{ + const tinyusb_config_t tusb_cfg = { + .external_phy = false, + }; + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + tinyusb_net_config_t net_config = { + .on_recv_callback = usb_recv_callback, + .free_tx_buffer = wifi_buff_free, + .on_init_callback = on_usb_net_init + }; + + esp_read_mac(net_config.mac_addr, ESP_MAC_WIFI_STA); + + esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB net init but not connect wifi"); + return ret; + } + return ESP_OK; +} + +/** + * USB internal network functionality + */ + +esp_err_t netif_recv_callback(void *buffer, uint16_t len, void* ctx) +{ + if (s_netif) { + void *buf_copy = malloc(len); + if (!buf_copy) { + return ESP_ERR_NO_MEM; + } + memcpy(buf_copy, buffer, len); + return esp_netif_receive(s_netif, buf_copy, len, NULL); + } + return ESP_OK; +} + +static esp_err_t netif_transmit (void *h, void *buffer, size_t len) +{ + if (tinyusb_net_send_sync(buffer, len, NULL, pdMS_TO_TICKS(100)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to send buffer to USB!"); + } + return ESP_OK; +} + +static void l2_free(void *h, void* buffer) +{ + free(buffer); +} + +static esp_err_t usb_ncm_with_network(void) +{ + const tinyusb_config_t tusb_cfg = { + .external_phy = false, + }; + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + const tinyusb_net_config_t net_config = { + // locally administrated address for the ncm device as it's going to be used internally + // for configuration only + .mac_addr = {0x02, 0x02, 0x11, 0x22, 0x33, 0x01}, + .on_recv_callback = netif_recv_callback, + }; + + esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Cannot initialize USB Net device"); + return ret; + } + + // with OUI range MAC to create a virtual netif running http server + // this needs to be different to usb_interface_mac (==client) + uint8_t lwip_addr[6]= {0x02, 0x02, 0x11, 0x22, 0x33, 0x02}; + + + // 1) Derive the base config from the default AP (using DHCP server) + esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP(); + base_cfg.if_key = "usb"; + base_cfg.if_desc = "usb ncm config device"; + // 2) Use static config for driver's config pointing only to static transmit and free functions + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = (void*)1, // will be replaced by the driver pointer only tinyusb_net supports ti + .transmit = netif_transmit, + .driver_free_rx_buffer = l2_free + }; + + // Config the esp-netif with: + // 1) inherent config (behavioural settings of an interface) + // 2) driver's config (connection to IO functions -- usb) + // 3) stack config (using lwip IO functions -- derive from eth) + esp_netif_config_t cfg = { + .base = &base_cfg, + .driver = &driver_cfg, + // 3) use ethernet style of lwip netif settings + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + }; + + s_netif = esp_netif_new(&cfg); + if (s_netif == NULL) { + return ESP_FAIL; + } + esp_netif_set_mac(s_netif, lwip_addr); + + // set the minimum lease time + uint32_t lease_opt = 1; + esp_netif_dhcps_option(s_netif, ESP_NETIF_OP_SET, IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt)); + + // start the interface manually (as the driver has been started already) + esp_netif_action_start(s_netif, 0, 0, 0); + return ESP_OK; +} + +/** + * GPIO button functionality + */ + +#define GPIO_INPUT_IO_0 0 +#define GPIO_LONG_PUSH_US 2000000 /* push for 2 seconds to reconfigure */ + +static void IRAM_ATTR gpio_isr_handler(void* arg) +{ + static int64_t last_pushed = -1; + if (gpio_get_level(GPIO_INPUT_IO_0) == 0) { + last_pushed = esp_timer_get_time(); + } else { + uint64_t now = esp_timer_get_time(); + if (last_pushed != -1 && now - last_pushed > GPIO_LONG_PUSH_US) { + BaseType_t high_task_wakeup; + xEventGroupSetBitsFromISR(s_event_flags, RECONFIGURE_BIT, &high_task_wakeup); + if (high_task_wakeup) { + portYIELD_FROM_ISR(); + } + } + last_pushed = -1; + } +} + +static void gpio_init(void) +{ + gpio_config_t io_conf = { .intr_type = GPIO_INTR_ANYEDGE, + .pin_bit_mask = (1ULL<