diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index a4a3face1..9ea0fea2b 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -18,7 +18,7 @@ jobs: with: esp_idf_version: v5.5.3 target: esp32s3 - command: GITHUB_ACTIONS="true" idf.py build + command: idf.py add-dependency "espressif/esp_tinyusb^2.0.1~1" ; GITHUB_ACTIONS="true" idf.py build path: 'test-ci' - name: Run tests diff --git a/components/connect/CMakeLists.txt b/components/connect/CMakeLists.txt index 91e85c375..b7bd08e99 100644 --- a/components/connect/CMakeLists.txt +++ b/components/connect/CMakeLists.txt @@ -1,6 +1,8 @@ idf_component_register( SRCS "connect.c" + "connect_wifi.c" + "usb_net.c" INCLUDE_DIRS "include" @@ -13,4 +15,6 @@ REQUIRES "esp_wifi" "esp_event" "stratum" + "esp_eth" + "esp_tinyusb" ) diff --git a/components/connect/connect.c b/components/connect/connect.c index 7d403fd03..d0816d0c4 100644 --- a/components/connect/connect.c +++ b/components/connect/connect.c @@ -9,6 +9,8 @@ #include "esp_wifi_types_generic.h" #include "connect.h" +#include "connect_wifi.h" +#include "usb_net.h" #include "global_state.h" #include "nvs_config.h" @@ -57,8 +59,6 @@ static int s_retry_num = 0; static int clients_connected_to_ap = 0; static const char *get_wifi_reason_string(int reason); -static void wifi_softap_on(void); -static void wifi_softap_off(void); esp_err_t get_wifi_current_rssi(int8_t *rssi) { @@ -138,7 +138,7 @@ static void ip_timeout_callback(TimerHandle_t xTimer) GlobalState *GLOBAL_STATE = (GlobalState *)pvTimerGetTimerID(xTimer); if (!GLOBAL_STATE->SYSTEM_MODULE.is_connected) { ESP_LOGI(TAG, "Timeout waiting for IP address. Disconnecting..."); - strcpy(GLOBAL_STATE->SYSTEM_MODULE.wifi_status, "IP Acquire Timeout"); + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "IP Acquire Timeout"); esp_wifi_disconnect(); } } @@ -164,13 +164,13 @@ static void event_handler(void * arg, esp_event_base_t event_base, int32_t event if (event_id == WIFI_EVENT_STA_START) { ESP_LOGI(TAG, "Connecting..."); - strcpy(GLOBAL_STATE->SYSTEM_MODULE.wifi_status, "Connecting..."); + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "Connecting..."); esp_wifi_connect(); } if (event_id == WIFI_EVENT_STA_CONNECTED) { ESP_LOGI(TAG, "Acquiring IP..."); - strcpy(GLOBAL_STATE->SYSTEM_MODULE.wifi_status, "Acquiring IP..."); + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "Acquiring IP..."); if (ip_acquire_timer == NULL) { ip_acquire_timer = xTimerCreate("ip_acquire_timer", pdMS_TO_TICKS(30000), pdFALSE, (void *)GLOBAL_STATE, ip_timeout_callback); @@ -190,15 +190,12 @@ static void event_handler(void * arg, esp_event_base_t event_base, int32_t event ESP_LOGI(TAG, "Could not connect to '%.*s' [rssi %d]: reason %d", event->ssid_len, event->ssid, event->rssi, event->reason); if (clients_connected_to_ap > 0) { ESP_LOGI(TAG, "Client(s) connected to AP, not retrying..."); - snprintf(GLOBAL_STATE->SYSTEM_MODULE.wifi_status, sizeof(GLOBAL_STATE->SYSTEM_MODULE.wifi_status), "Config AP connected!"); + snprintf(GLOBAL_STATE->SYSTEM_MODULE.network_status, sizeof(GLOBAL_STATE->SYSTEM_MODULE.network_status), "Config AP connected!"); return; } - GLOBAL_STATE->SYSTEM_MODULE.is_connected = false; - wifi_softap_on(); - - snprintf(GLOBAL_STATE->SYSTEM_MODULE.wifi_status, sizeof(GLOBAL_STATE->SYSTEM_MODULE.wifi_status), "%s (Error %d, retry #%d)", get_wifi_reason_string(event->reason), event->reason, s_retry_num); - ESP_LOGI(TAG, "Wi-Fi status: %s", GLOBAL_STATE->SYSTEM_MODULE.wifi_status); + snprintf(GLOBAL_STATE->SYSTEM_MODULE.network_status, sizeof(GLOBAL_STATE->SYSTEM_MODULE.network_status), "%s (Error %d, retry #%d)", get_wifi_reason_string(event->reason), event->reason, s_retry_num); + ESP_LOGI(TAG, "Wi-Fi status: %s", GLOBAL_STATE->SYSTEM_MODULE.network_status); // Wait a little vTaskDelay(5000 / portTICK_PERIOD_MS); @@ -245,9 +242,7 @@ static void event_handler(void * arg, esp_event_base_t event_base, int32_t event GLOBAL_STATE->SYSTEM_MODULE.is_connected = true; ESP_LOGI(TAG, "Connected to SSID: %s", GLOBAL_STATE->SYSTEM_MODULE.ssid); - strcpy(GLOBAL_STATE->SYSTEM_MODULE.wifi_status, "Connected!"); - - wifi_softap_off(); + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "Connected!"); // Create IPv6 link-local address after WiFi connection esp_netif_t *netif = event->esp_netif; @@ -323,163 +318,96 @@ static bool is_wifi_operation_allowed(esp_err_t err) void toggle_wifi_softap(void) { wifi_mode_t mode = WIFI_MODE_NULL; - esp_err_t err = esp_wifi_get_mode(&mode); - if (is_wifi_operation_allowed(err)) { - ESP_ERROR_CHECK(err); - - if (mode == WIFI_MODE_APSTA) { - wifi_softap_off(); - } else { - wifi_softap_on(); - } - } -} -static void wifi_softap_off(void) -{ - esp_err_t err = esp_wifi_set_mode(WIFI_MODE_STA); - if (is_wifi_operation_allowed(err)) { - ESP_ERROR_CHECK(err); - } -} + esp_err_t err = esp_wifi_get_mode(&mode); -static void wifi_softap_on(void) -{ - esp_err_t err = esp_wifi_set_mode(WIFI_MODE_APSTA); if (is_wifi_operation_allowed(err)) { - ESP_ERROR_CHECK(err); - } -} - -/* Initialize wifi station */ -esp_netif_t * wifi_init_sta(const char * wifi_ssid, const char * wifi_pass) -{ - esp_netif_t * esp_netif_sta = esp_netif_create_default_wifi_sta(); - - /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8). - * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value - * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to - * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards. - */ - wifi_auth_mode_t authmode; - - if (strlen(wifi_pass) == 0) { - ESP_LOGI(TAG, "No Wi-Fi password provided, using open network"); - authmode = WIFI_AUTH_OPEN; - } else { - ESP_LOGI(TAG, "Wi-Fi Password provided, using WPA2"); - authmode = WIFI_AUTH_WPA2_PSK; - } - - wifi_config_t wifi_sta_config = { - .sta = - { - .threshold.authmode = authmode, - .btm_enabled = 1, - .rm_enabled = 1, - .scan_method = WIFI_ALL_CHANNEL_SCAN, - .sort_method = WIFI_CONNECT_AP_BY_SIGNAL, - .pmf_cfg = - { - .capable = true, - .required = false - }, - }, - }; - - size_t ssid_len = strlen(wifi_ssid); - if (ssid_len > 32) ssid_len = 32; - memcpy(wifi_sta_config.sta.ssid, wifi_ssid, ssid_len); - if (ssid_len < 32) { - wifi_sta_config.sta.ssid[ssid_len] = '\0'; - } - - if (authmode != WIFI_AUTH_OPEN) { - strncpy((char *) wifi_sta_config.sta.password, wifi_pass, sizeof(wifi_sta_config.sta.password)); - wifi_sta_config.sta.password[sizeof(wifi_sta_config.sta.password) - 1] = '\0'; + switch (mode) { + case WIFI_MODE_NULL: + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + break; + case WIFI_MODE_STA: + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + break; + case WIFI_MODE_AP: + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); + break; + case WIFI_MODE_APSTA: + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + break; + default: + break; + } } - // strncpy((char *) wifi_sta_config.sta.password, wifi_pass, 63); - // wifi_sta_config.sta.password[63] = '\0'; - - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config)); - - // IPv6 link-local address will be created after WiFi connection - - // Start DHCP client for IPv4 - esp_netif_dhcpc_start(esp_netif_sta); - - ESP_LOGI(TAG, "wifi_init_sta finished."); - - return esp_netif_sta; } -void wifi_init(void * pvParameters) +void connect_init(void *pvParameters) { - GlobalState * GLOBAL_STATE = (GlobalState *) pvParameters; + GlobalState *GLOBAL_STATE = (GlobalState *)pvParameters; ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); - esp_event_handler_instance_t instance_any_id; - esp_event_handler_instance_t instance_got_ip; - esp_event_handler_instance_t instance_got_ip6; + esp_event_handler_instance_t instance_any_id, instance_got_ip, instance_got_ip6; ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, GLOBAL_STATE, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, GLOBAL_STATE, &instance_got_ip)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_GOT_IP6, &event_handler, GLOBAL_STATE, &instance_got_ip6)); - /* Initialize Wi-Fi */ + // Initialize Wi-Fi wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - wifi_softap_on(); + // Configure and start SoftAP + esp_err_t err = esp_wifi_set_mode(GLOBAL_STATE->SYSTEM_MODULE.network_mode == NETWORK_MODE_WIFI ? WIFI_MODE_APSTA : WIFI_MODE_AP); + if (is_wifi_operation_allowed(err)) { + ESP_ERROR_CHECK(err); + } - /* Initialize AP */ + // Initialize SoftAP wifi_init_softap(GLOBAL_STATE); - GLOBAL_STATE->SYSTEM_MODULE.ssid = nvs_config_get_string(NVS_CONFIG_WIFI_SSID); - - /* Skip connection if SSID is null */ - if (strlen(GLOBAL_STATE->SYSTEM_MODULE.ssid) == 0) { - ESP_LOGI(TAG, "No WiFi SSID provided, skipping connection"); - - /* Start WiFi */ - ESP_ERROR_CHECK(esp_wifi_start()); - - /* Disable power savings for best performance */ - ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); - - return; - } else { + // Configure STA BEFORE starting WiFi to avoid race condition + if (GLOBAL_STATE->SYSTEM_MODULE.network_mode == NETWORK_MODE_WIFI) { + ESP_LOGI(TAG, "Network mode: WiFi"); - char * wifi_pass = nvs_config_get_string(NVS_CONFIG_WIFI_PASS); - - /* Initialize STA */ - ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); - esp_netif_t * esp_netif_sta = wifi_init_sta(GLOBAL_STATE->SYSTEM_MODULE.ssid, wifi_pass); - - free(wifi_pass); - - /* Start Wi-Fi */ - ESP_ERROR_CHECK(esp_wifi_start()); + // Skip connection if SSID is null + if (strlen(GLOBAL_STATE->SYSTEM_MODULE.ssid) == 0) { + ESP_LOGI(TAG, "No WiFi SSID provided, running in AP-only mode"); + } else { + ESP_LOGI(TAG, "Configuring Wi-Fi STA"); + connect_wifi_init(GLOBAL_STATE); + } + } - /* Disable power savings for best performance */ - ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); + // Start Wi-Fi + ESP_ERROR_CHECK(esp_wifi_start()); + // Disable power savings for best performance + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); - char * hostname = nvs_config_get_string(NVS_CONFIG_HOSTNAME); + if (GLOBAL_STATE->SYSTEM_MODULE.network_mode == NETWORK_MODE_USB) { + ESP_LOGI(TAG, "Network mode: Ethernet-over-USB"); + usb_net_init(GLOBAL_STATE); + } - /* Set Hostname */ - esp_err_t err = esp_netif_set_hostname(esp_netif_sta, hostname); - if (err != ERR_OK) { - ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err)); - } else { - ESP_LOGI(TAG, "ESP_WIFI setting hostname to: %s", hostname); - } + ESP_LOGI(TAG, "Network initialization complete"); +} - free(hostname); +void connect_await_connection(void *pvParameters) +{ + GlobalState *GLOBAL_STATE = (GlobalState *)pvParameters; - ESP_LOGI(TAG, "wifi_init_sta finished."); + // Wait for connection to be established + ESP_LOGI(TAG, "Waiting for network connection..."); + while (!GLOBAL_STATE->SYSTEM_MODULE.is_connected) { + vTaskDelay(100 / portTICK_PERIOD_MS); + } - return; + // Disable AP after connection is established + ESP_LOGI(TAG, "Connection established, disabling AP"); + if (GLOBAL_STATE->SYSTEM_MODULE.network_mode == NETWORK_MODE_WIFI) { + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + } else if (GLOBAL_STATE->SYSTEM_MODULE.network_mode == NETWORK_MODE_USB) { + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); } } diff --git a/components/connect/connect_wifi.c b/components/connect/connect_wifi.c new file mode 100644 index 000000000..8464976ba --- /dev/null +++ b/components/connect/connect_wifi.c @@ -0,0 +1,95 @@ +#include +#include "esp_event.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_netif.h" + +#include "connect_wifi.h" +#include "global_state.h" +#include "nvs_config.h" + +static const char *TAG = "connect_wifi"; + +static esp_netif_t *wifi_init_sta(const char *wifi_ssid, const char *wifi_pass) +{ + esp_netif_t *esp_netif_sta = esp_netif_create_default_wifi_sta(); + + /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8). + * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value + * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to + * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards. + */ + wifi_auth_mode_t authmode; + + if (strlen(wifi_pass) == 0) { + ESP_LOGI(TAG, "No Wi-Fi password provided, using open network"); + authmode = WIFI_AUTH_OPEN; + } else { + ESP_LOGI(TAG, "Wi-Fi Password provided, using WPA2"); + authmode = WIFI_AUTH_WPA2_PSK; + } + + wifi_config_t wifi_sta_config = { + .sta = + { + .threshold.authmode = authmode, + .btm_enabled = 1, + .rm_enabled = 1, + .scan_method = WIFI_ALL_CHANNEL_SCAN, + .sort_method = WIFI_CONNECT_AP_BY_SIGNAL, + .pmf_cfg = + { + .capable = true, + .required = false + }, + }, + }; + + size_t ssid_len = strlen(wifi_ssid); + if (ssid_len > 32) ssid_len = 32; + memcpy(wifi_sta_config.sta.ssid, wifi_ssid, ssid_len); + if (ssid_len < 32) { + wifi_sta_config.sta.ssid[ssid_len] = '\0'; + } + + if (authmode != WIFI_AUTH_OPEN) { + strncpy((char *) wifi_sta_config.sta.password, wifi_pass, sizeof(wifi_sta_config.sta.password)); + wifi_sta_config.sta.password[sizeof(wifi_sta_config.sta.password) - 1] = '\0'; + } + + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config)); + + // IPv6 link-local address will be created after WiFi connection + + // Start DHCP client for IPv4 + esp_netif_dhcpc_start(esp_netif_sta); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + + return esp_netif_sta; +} + +esp_err_t connect_wifi_init(GlobalState *GLOBAL_STATE) +{ + char *wifi_ssid = GLOBAL_STATE->SYSTEM_MODULE.ssid; + char *wifi_pass = nvs_config_get_string(NVS_CONFIG_WIFI_PASS); + + ESP_LOGI(TAG, "Initializing WiFi STA mode"); + esp_netif_t *esp_netif_sta = wifi_init_sta(wifi_ssid, wifi_pass); + + free(wifi_pass); + + /* Set Hostname */ + char *hostname = nvs_config_get_string(NVS_CONFIG_HOSTNAME); + esp_err_t err = esp_netif_set_hostname(esp_netif_sta, hostname); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err)); + } else { + ESP_LOGI(TAG, "Setting hostname to: %s", hostname); + } + free(hostname); + + ESP_LOGI(TAG, "WiFi STA initialization complete"); + + return ESP_OK; +} diff --git a/components/connect/include/connect.h b/components/connect/include/connect.h index 17d9e043b..27a8db45a 100644 --- a/components/connect/include/connect.h +++ b/components/connect/include/connect.h @@ -15,7 +15,8 @@ typedef struct { } wifi_ap_record_simple_t; void toggle_wifi_softap(void); -void wifi_init(void * GLOBAL_STATE); +void connect_init(void *pvParameters); +void connect_await_connection(void *pvParameters); esp_err_t wifi_scan(wifi_ap_record_simple_t *ap_records, uint16_t *ap_count); esp_err_t get_wifi_current_rssi(int8_t *rssi); diff --git a/components/connect/include/connect_wifi.h b/components/connect/include/connect_wifi.h new file mode 100644 index 000000000..4ee38585e --- /dev/null +++ b/components/connect/include/connect_wifi.h @@ -0,0 +1,15 @@ +#ifndef CONNECT_WIFI_H_ +#define CONNECT_WIFI_H_ + +#include "esp_err.h" +#include "global_state.h" + +/** + * @brief Initialize WiFi STA mode + * + * @param GLOBAL_STATE Pointer to global state + * @return esp_err_t ESP_OK on success + */ +esp_err_t connect_wifi_init(GlobalState *GLOBAL_STATE); + +#endif /* CONNECT_WIFI_H_ */ diff --git a/components/connect/include/usb_net.h b/components/connect/include/usb_net.h new file mode 100644 index 000000000..191bb5ec2 --- /dev/null +++ b/components/connect/include/usb_net.h @@ -0,0 +1,18 @@ +#ifndef USB_NET_H_ +#define USB_NET_H_ + +/** + * @brief Initialize USB NCM (Network Control Model) network interface and CDC ACM for serial communication + * + * This function initializes TinyUSB with NCM support and creates an + * esp_netif interface for Ethernet-over-USB connectivity. The device will + * appear as an ethernet adapter to the host computer. + * + * Additionally, it initializes CDC ACM (Communication Device Class Abstract Control Model) + * for serial communication, allowing logging over USB serial connection. + * + * @param GLOBAL_STATE Pointer to the global state structure + */ +void usb_net_init(void * GLOBAL_STATE); + +#endif /* USB_NET_H_ */ diff --git a/components/connect/usb_net.c b/components/connect/usb_net.c new file mode 100644 index 000000000..df1997cfa --- /dev/null +++ b/components/connect/usb_net.c @@ -0,0 +1,251 @@ +#include "usb_net.h" +#include +#include +#include + +#include "esp_event.h" +#include "lwip/sockets.h" +#include "tinyusb_default_config.h" + +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_netif.h" + +#include "tinyusb_net.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_console.h" +#include "tinyusb.h" +#include "global_state.h" +#include "nvs_config.h" + +static const char *TAG = "usb_net"; + +// Static configuration structures +static esp_netif_driver_ifconfig_t driver_cfg; +static esp_netif_inherent_config_t netif_cfg; + +// Static configuration structures + +/** + * @brief Transmit function for sending packets to USB + */ +static esp_err_t netif_transmit(void *h, void *buffer, size_t len) +{ + esp_err_t err = tinyusb_net_send_sync(buffer, len, NULL, pdMS_TO_TICKS(100)); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + // ESP_ERR_INVALID_STATE during startup is expected - USB link not ready yet + // DHCP will retry automatically, so don't spam logs + ESP_LOGE(TAG, "Failed to send buffer to USB: %s", esp_err_to_name(err)); + } + return err; +} + +/** + * @brief Free function for RX buffers + */ +static void l2_free(void *h, void *buffer) +{ + free(buffer); +} + +/** + * @brief Callback when USB receives data from host + */ +static esp_err_t netif_recv_callback(void *buffer, uint16_t len, void *ctx) +{ + esp_netif_t *netif = (esp_netif_t *)ctx; + if (!netif) { + return ESP_OK; + } + + // Copy buffer because TinyUSB will reuse it + void *buf_copy = malloc(len); + if (!buf_copy) { + ESP_LOGE(TAG, "Failed to allocate RX buffer"); + return ESP_ERR_NO_MEM; + } + + memcpy(buf_copy, buffer, len); + return esp_netif_receive(netif, buf_copy, len, NULL); +} + +// Callback for CDC ACM when line state changes (DTR, RTS) +static void cdc_acm_line_state_changed_callback(int itf, cdcacm_event_t *event) +{ + if (event->type == CDC_EVENT_LINE_STATE_CHANGED) { + cdcacm_event_line_state_changed_data_t line_state = event->line_state_changed_data; + ESP_LOGI(TAG, "CDC ACM Line state changed - Interface: %d, DTR: %s, RTS: %s", + itf, line_state.dtr ? "true" : "false", line_state.rts ? "true" : "false"); + } +} + +// Event handler for Ethernet-over-USB IP events +static void usb_ip_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + GlobalState *GLOBAL_STATE = (GlobalState *)arg; + + if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + ESP_LOGI(TAG, "Got IP Address:" IPSTR, IP2STR(&event->ip_info.ip)); + + // Update global state with IP address + snprintf(GLOBAL_STATE->SYSTEM_MODULE.ip_addr_str, IP4ADDR_STRLEN_MAX, + IPSTR, IP2STR(&event->ip_info.ip)); + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "Connected!"); + GLOBAL_STATE->SYSTEM_MODULE.is_connected = true; + + // Create IPv6 link-local address after USB connection + esp_netif_t *netif = event->esp_netif; + esp_err_t ipv6_err = esp_netif_create_ip6_linklocal(netif); + if (ipv6_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create IPv6 link-local address: %s", esp_err_to_name(ipv6_err)); + } + } + + if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { + ip_event_got_ip6_t* event = (ip_event_got_ip6_t*) event_data; + + // Convert IPv6 address to string + char ipv6_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &event->ip6_info.ip, ipv6_str, sizeof(ipv6_str)); + + // Check if it's a link-local address (fe80::/10) + if ((event->ip6_info.ip.addr[0] & 0xFFC0) == 0xFE80) { + // Store link-local IPv6 address + strncpy(GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str, ipv6_str, sizeof(GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str) - 1); + GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str[sizeof(GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str) - 1] = '\0'; + ESP_LOGI(TAG, "IPv6 Address: %s", GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str); + } + } + + if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_LOST_IP) { + ESP_LOGI(TAG, "Lost IP"); + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "Lost IP"); + GLOBAL_STATE->SYSTEM_MODULE.is_connected = false; + GLOBAL_STATE->SYSTEM_MODULE.ip_addr_str[0] = '\0'; + GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str[0] = '\0'; + } +} + +void usb_net_init(void * pvParameters) +{ + GlobalState * GLOBAL_STATE = (GlobalState *) pvParameters; + + esp_event_handler_instance_t instance_got_ip_usb; + esp_event_handler_instance_t instance_got_ip6_usb; + esp_event_handler_instance_t instance_lost_ip_usb; + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &usb_ip_event_handler, GLOBAL_STATE, &instance_got_ip_usb)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_GOT_IP6, &usb_ip_event_handler, GLOBAL_STATE, &instance_got_ip6_usb)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_ETH_LOST_IP, &usb_ip_event_handler, GLOBAL_STATE, &instance_lost_ip_usb)); + + char * hostname = nvs_config_get_string(NVS_CONFIG_HOSTNAME); + + // Build USB string descriptors + static char *string_descriptors[4]; + string_descriptors[0] = "\x09\x04"; // Language ID: English (US) + + string_descriptors[1] = "ESP-Miner"; // Manufacturer + + static char product_str[64]; + snprintf(product_str, 64, "Bitaxe %s %s (%s)", GLOBAL_STATE->DEVICE_CONFIG.family.name, GLOBAL_STATE->DEVICE_CONFIG.board_version, hostname); + string_descriptors[2] = product_str; // Product + + uint8_t mac[6]; + esp_efuse_mac_get_default(mac); + + static char serial_str[13]; + snprintf(serial_str, 13, "%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + string_descriptors[3] = serial_str; // Serial + + // Initialize TinyUSB driver (descriptors from sdkconfig) + ESP_LOGI(TAG, "Initializing TinyUSB driver"); + tinyusb_config_t usb_cfg = TINYUSB_DEFAULT_CONFIG(); + usb_cfg.descriptor.string = (const char **)string_descriptors; + usb_cfg.descriptor.string_count = 4; + usb_cfg.task = TINYUSB_TASK_CUSTOM(4096, 5, 0); // 4KB stack, priority 5, CPU0 + + ESP_ERROR_CHECK(tinyusb_driver_install(&usb_cfg)); + + ESP_LOGI(TAG, "Initializing CDC ACM for serial communication"); + tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = NULL, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = &cdc_acm_line_state_changed_callback, + .callback_line_coding_changed = NULL + }; + ESP_ERROR_CHECK(tinyusb_cdcacm_init(&acm_cfg)); + ESP_ERROR_CHECK(tinyusb_console_init(TINYUSB_CDC_ACM_0)); + + // Setup netif inherent config + netif_cfg = (esp_netif_inherent_config_t)ESP_NETIF_INHERENT_DEFAULT_ETH(); + netif_cfg.flags = ESP_NETIF_FLAG_AUTOUP | ESP_NETIF_FLAG_EVENT_IP_MODIFIED; + netif_cfg.if_key = "USB_NCM"; + netif_cfg.if_desc = "usb ncm network device"; + netif_cfg.route_prio = 10; + + // Setup driver config + driver_cfg = (esp_netif_driver_ifconfig_t){ + .handle = (void *)1, // TinyUSB NCM is a singleton, just needs non-NULL + .transmit = netif_transmit, + .driver_free_rx_buffer = l2_free + }; + + // Create netif configuration + esp_netif_config_t cfg = { + .base = &netif_cfg, + .driver = &driver_cfg, + .stack = _g_esp_netif_netstack_default_eth, + }; + + // Create the network interface + esp_netif_t *netif = esp_netif_new(&cfg); + if (!netif) { + ESP_LOGE(TAG, "Failed to create netif"); + return; + } + + // Initialize TinyUSB Net + ESP_LOGI(TAG, "Initializing TinyUSB NCM"); + tinyusb_net_config_t net_cfg = { + .on_recv_callback = netif_recv_callback, + .user_context = netif, + }; + + // Get and derive MAC address from eFuse + uint8_t efuse_mac[6]; + ESP_ERROR_CHECK(esp_efuse_mac_get_default(efuse_mac)); + + ESP_ERROR_CHECK(esp_derive_local_mac(net_cfg.mac_addr, efuse_mac)); + + ESP_LOGI(TAG, "USB NCM MAC: %02x:%02x:%02x:%02x:%02x:%02x", + net_cfg.mac_addr[0], net_cfg.mac_addr[1], net_cfg.mac_addr[2], + net_cfg.mac_addr[3], net_cfg.mac_addr[4], net_cfg.mac_addr[5]); + + ESP_ERROR_CHECK(tinyusb_net_init(&net_cfg)); + + // The netif callbacks are already connected via the driver_cfg we passed to esp_netif_new + // TinyUSB will call netif_recv_callback when data arrives + // DHCP will call netif_transmit via esp_netif when it needs to send packets + + // Manually start the interface + ESP_LOGI(TAG, "Starting network interface"); + esp_netif_action_start(netif, NULL, 0, NULL); + + // Mark interface as connected (link up) + esp_netif_action_connected(netif, NULL, 0, NULL); + + // Give USB a moment to stabilize before starting DHCP + vTaskDelay(pdMS_TO_TICKS(100)); + + // Start the DHCP client + ESP_LOGI(TAG, "Starting DHCP client for Ethernet-over-USB"); + ESP_ERROR_CHECK(esp_netif_set_mac(netif, net_cfg.mac_addr)); + ESP_ERROR_CHECK(esp_netif_dhcpc_start(netif)); + + ESP_LOGI(TAG, "Ethernet-over-USB initialized successfully - waiting for IP from DHCP"); + + strcpy(GLOBAL_STATE->SYSTEM_MODULE.network_status, "Acquiring IP..."); +} diff --git a/ethernet-over-usb.md b/ethernet-over-usb.md new file mode 100644 index 000000000..5d25790e5 --- /dev/null +++ b/ethernet-over-usb.md @@ -0,0 +1,104 @@ +# Ethernet-over-USB + +This provides a direct wired connection via the USB port, useful for environments where WiFi is unavailable or undesirable. + +## Switching Network Modes + +Use the API to switch between WiFi and USB modes: + +```bash +# Switch to Ethernet-over-USB mode +curl -X PATCH http://YOUR-BITAXE-IP/api/system \ + -H "Content-Type: application/json" \ + -d '{"networkMode": "usb"}' + +# Restart to apply +curl -X POST http://YOUR-BITAXE-IP/api/system/restart + +# Switch back to WiFi mode +curl -X PATCH http://YOUR-BITAXE-IP/api/system \ + -H "Content-Type: application/json" \ + -d '{"networkMode": "wifi"}' +``` + +## Host Computer Setup (Linux) + +When using Ethernet-over-USB mode, your computer needs to provide network connectivity to the device. + +### Option 1: NetworkManager GUI (Easiest) +1. Connect the Bitaxe via USB. +2. Wait for the "Ethernet-over-USB" interface to appear in Network Settings. +3. Edit the connection → IPv4 Settings. +4. Set Method to **"Shared to other computers"**. +5. Save and reconnect. + +The device will automatically get an IP address (typically `10.42.0.X`), and you can access the web UI at that address. + +### Option 2: NetworkManager CLI +```bash +# Find the USB interface +nmcli device status | grep ethernet + +# Create and activate shared connection (replace usb0 with your interface) +nmcli connection add type ethernet ifname usb0 con-name "USB-Miner" \ + ipv4.method shared +nmcli connection up "USB-Miner" +``` + +### Option 3: Manual DHCP Server +```bash +# Install dnsmasq +sudo apt install dnsmasq + +# Configure interface +sudo ip addr add 192.168.7.1/24 dev usb0 +sudo ip link set usb0 up + +# Start DHCP server +sudo dnsmasq --interface=usb0 --dhcp-range=192.168.7.2,192.168.7.254 --no-daemon +``` + +## Technical Details + +- **Protocol**: USB NCM (Network Control Model) +- **Device Class**: CDC NCM (Communications Device Class) +- **USB Descriptors**: + - Vendor ID: 0x303A (Espressif) + - Manufacturer: "ESP-Miner" + - Product: "Bitaxe [family] [model] ([hostname])" + - Serial: [MAC address] + +## Limitations + +- The ESP32-S3 has a single USB port used for both flashing/logging and Ethernet-over-USB. +- When Ethernet-over-USB is active, USB serial logging is interrupted briefly when switching over from the ROM USB PHY to TinyUSB. +- To flash the device, it first needs to be put in bootloader mode by holding the BOOT button and resetting the device. +- Windows 10 may require manual driver installation for NCM support (see Windows 10 Setup section). + +## Windows 10 Setup + +To share your Windows 10 PC's internet connection to a device via USB using the Network Control Model (NCM) protocol, you need to enable Internet Connection Sharing (ICS) after ensuring the correct driver is installed for the connected device. + +### Step 1: Ensure the Correct USB NCM Driver is Installed +When you connect a device (like an Android phone or development board) and enable "USB tethering" on the device side, Windows should recognize it. If it doesn't appear correctly under Network adapters, you may need to manually update the driver to use the built-in Microsoft driver. + +1. Open Device Manager: Right-click the Windows Start menu and select Device Manager. +2. Locate the device: Look for the device under the "Other devices" category (it might appear as "CDC NCM" or an unknown device). +3. Update the driver: Right-click the entry and select "Update Driver". +4. Browse for drivers: Select "Browse my computer for drivers", then "Let me pick from a list of available drivers on my computer". +5. Select Network adapters: From the list of Common hardware types, scroll down and select "Network adapters", then click Next. +6. Select Microsoft and UsbNcm Host Device: Under Manufacturer, select "Microsoft". Under Model, select "UsbNcm Host Device" and click Next. +7. Confirm installation: Click Yes if a warning message appears. The device should now appear as a "UsbNcm Host Device" under the "Network adapters" category. + +### Step 2: Configure Internet Connection Sharing (ICS) +Once the device is correctly recognized as a network adapter, you can share your computer's active internet connection with it. + +1. Open Network Connections: Right-click the network icon in your system tray and select "Open Network & Internet settings". In the settings window, click "Change adapter options" under "Advanced network settings". +2. Identify your internet source: In the "Network Connections" window, right-click the network adapter that has the active internet access (e.g., your Wi-Fi or Ethernet connection). +3. Open properties: Select "Properties" from the context menu. +4. Enable sharing: Go to the "Sharing" tab. +5. Check the box for "Allow other network users to connect through this computer's Internet connection". +6. Select the NCM connection: In the dropdown menu under that checkbox, select the new USB network connection you just installed (labeled something like "Ethernet X" with "UsbNcm Host Device" underneath). +7. Confirm: Click OK. You may receive an alert about enabling ICS; click Yes to continue. + +Your connected device should now be able to use your Windows 10 PC's internet connection. You may need to unplug and replug the USB cable or disable/re-enable USB tethering on the connected device for the changes to take effect. diff --git a/main/bap/bap_subscription.c b/main/bap/bap_subscription.c index 61ee4cf18..ed1532ce1 100644 --- a/main/bap/bap_subscription.c +++ b/main/bap/bap_subscription.c @@ -36,6 +36,7 @@ typedef struct { char fan_speed[32]; char best_difficulty[32]; char block_height[32]; + char network_status[256]; char wifi_ssid[64]; char wifi_password[64]; char wifi_rssi[32]; @@ -54,6 +55,7 @@ typedef struct { bool fan_speed; bool best_difficulty; bool block_height; + bool network_status; bool wifi_ssid; bool wifi_password; bool wifi_rssi; @@ -317,13 +319,13 @@ void BAP_send_subscription_update(GlobalState *state) { case BAP_PARAM_WIFI: { - char wifi_status_str[256]; + char network_status_str[256]; char rssi_str[32]; char ip_str[32]; - snprintf(wifi_status_str, sizeof(wifi_status_str), "%s", state->SYSTEM_MODULE.wifi_status); + snprintf(network_status_str, sizeof(network_status_str), "%s", state->SYSTEM_MODULE.network_status); int8_t current_rssi = -128; // no connection - if (state->SYSTEM_MODULE.is_connected) { + if (state->SYSTEM_MODULE.is_connected && state->SYSTEM_MODULE.network_mode == NETWORK_MODE_WIFI) { get_wifi_current_rssi(¤t_rssi); } snprintf(rssi_str, sizeof(rssi_str), "%d", current_rssi); @@ -333,6 +335,7 @@ void BAP_send_subscription_update(GlobalState *state) { if (!wifi_pass) { wifi_pass = strdup(""); } + BAP_send_if_changed("network_status", network_status_str, last_values.network_status, sizeof(last_values.network_status), &last_values_valid.network_status); BAP_send_if_changed("wifi_ssid", state->SYSTEM_MODULE.ssid, last_values.wifi_ssid, sizeof(last_values.wifi_ssid), &last_values_valid.wifi_ssid); BAP_send_if_changed("wifi_password", wifi_pass, last_values.wifi_password, sizeof(last_values.wifi_password), &last_values_valid.wifi_password); BAP_send_if_changed("wifi_rssi", rssi_str, last_values.wifi_rssi, sizeof(last_values.wifi_rssi), &last_values_valid.wifi_rssi); diff --git a/main/global_state.h b/main/global_state.h index 19c90d25a..f5c156c87 100644 --- a/main/global_state.h +++ b/main/global_state.h @@ -29,6 +29,11 @@ typedef struct { uint32_t count; } RejectedReasonStat; +typedef enum { + NETWORK_MODE_WIFI, + NETWORK_MODE_USB, +} NetworkMode; + typedef struct { float current_hashrate; @@ -49,8 +54,9 @@ typedef struct char best_session_diff_string[DIFF_STRING_SIZE]; int block_found; bool show_new_block; + NetworkMode network_mode; char * ssid; - char wifi_status[256]; + char network_status[256]; char ip_addr_str[16]; // IP4ADDR_STRLEN_MAX char ipv6_addr_str[64]; // IPv6 address string with zone identifier (INET6_ADDRSTRLEN=46 + % + interface=15) char ap_ssid[12]; diff --git a/main/http_server/axe-os/src/app/components/network-edit/network.edit.component.html b/main/http_server/axe-os/src/app/components/network-edit/network.edit.component.html index ac9ffc6b4..f8f2c8c72 100644 --- a/main/http_server/axe-os/src/app/components/network-edit/network.edit.component.html +++ b/main/http_server/axe-os/src/app/components/network-edit/network.edit.component.html @@ -11,30 +11,53 @@ +
- -
- - + +
+ +
-
- -
- - + + + +
+ +
+ + +
-
+
+ +
+ + +
+
+