2026-09-06 17:04:11 +08:00
|
|
|
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
|
|
#include "network.h"
|
|
|
|
|
|
|
|
|
|
#include <freertos/FreeRTOS.h>
|
|
|
|
|
#include <freertos/event_groups.h>
|
|
|
|
|
#include <esp_err.h>
|
|
|
|
|
#include <esp_event.h>
|
|
|
|
|
#include <esp_wifi.h>
|
|
|
|
|
#include <esp_log.h>
|
2026-10-08 10:06:44 +08:00
|
|
|
#include <esp_heap_caps.h>
|
|
|
|
|
#include <esp_timer.h>
|
2026-09-06 17:04:11 +08:00
|
|
|
#include <nvs.h>
|
|
|
|
|
#include <nvs_flash.h>
|
|
|
|
|
|
|
|
|
|
#include "configuration_ap.h"
|
|
|
|
|
#include "platform_client.h"
|
2026-10-08 10:06:44 +08:00
|
|
|
#include "ble_cloud.h"
|
2026-09-06 17:04:11 +08:00
|
|
|
|
|
|
|
|
#define TAG "NETWORK"
|
|
|
|
|
|
|
|
|
|
#define WIFI_EVENT_CONNECTED BIT0
|
|
|
|
|
#define WIFI_EVENT_FAILED BIT1
|
|
|
|
|
#define WIFI_EVENT_STARTED BIT2
|
|
|
|
|
#define MAX_RECONNECT_COUNT 5
|
2026-10-08 10:06:44 +08:00
|
|
|
#define RECONNECT_DELAY_US (10 * 1000 * 1000)
|
2026-09-06 17:04:11 +08:00
|
|
|
|
|
|
|
|
static network_t network;
|
2026-10-08 10:06:44 +08:00
|
|
|
static esp_err_t network_error;
|
|
|
|
|
static esp_timer_handle_t reconnect_timer;
|
|
|
|
|
static bool reconnect_enabled;
|
|
|
|
|
esp_err_t network_last_error(void) { return network_error; }
|
|
|
|
|
|
|
|
|
|
static void reconnect_saved_network(void *arg)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
|
if (!reconnect_enabled ||
|
|
|
|
|
(xEventGroupGetBits(network.event_group) & WIFI_EVENT_CONNECTED)) return;
|
|
|
|
|
esp_err_t err = esp_wifi_connect();
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
ESP_LOGW(TAG, "WiFi reconnect deferred: %s", esp_err_to_name(err));
|
|
|
|
|
esp_timer_start_once(reconnect_timer, RECONNECT_DELAY_US);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void schedule_reconnect(void)
|
|
|
|
|
{
|
|
|
|
|
// Fast retries recover a short interruption; slower retries continue for as
|
|
|
|
|
// long as the saved network is unavailable, including a phone hotspot.
|
|
|
|
|
if (reconnect_enabled && reconnect_timer && !esp_timer_is_active(reconnect_timer))
|
|
|
|
|
esp_timer_start_once(reconnect_timer, RECONNECT_DELAY_US);
|
|
|
|
|
}
|
2026-09-06 17:04:11 +08:00
|
|
|
|
|
|
|
|
int8_t get_rssi()
|
|
|
|
|
{
|
|
|
|
|
// Get station info
|
|
|
|
|
wifi_ap_record_t ap_info;
|
|
|
|
|
if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) return 0;
|
|
|
|
|
return ap_info.rssi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t get_channel()
|
|
|
|
|
{
|
|
|
|
|
// Get station info
|
|
|
|
|
wifi_ap_record_t ap_info;
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap_info));
|
|
|
|
|
return ap_info.primary;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handler_event_wifi_st(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
|
|
|
|
{
|
|
|
|
|
if (event_id == WIFI_EVENT_STA_START)
|
|
|
|
|
{
|
|
|
|
|
xEventGroupSetBits(network.event_group, WIFI_EVENT_STARTED);
|
|
|
|
|
}
|
|
|
|
|
else if (event_id == WIFI_EVENT_STA_DISCONNECTED)
|
|
|
|
|
{
|
|
|
|
|
xEventGroupClearBits(network.event_group, WIFI_EVENT_CONNECTED);
|
2026-10-08 10:06:44 +08:00
|
|
|
network.ip_address[0] = '\0';
|
|
|
|
|
if (!reconnect_enabled) return;
|
2026-09-06 17:04:11 +08:00
|
|
|
if (network.reconnect_count < MAX_RECONNECT_COUNT)
|
|
|
|
|
{
|
|
|
|
|
/* Retry the selected AP first, but allow another AP if it is no longer
|
|
|
|
|
* reachable. Never permanently bind this device to one mesh node. */
|
|
|
|
|
if (network.reconnect_count == 2) {
|
|
|
|
|
wifi_config_t config;
|
|
|
|
|
if (esp_wifi_get_config(WIFI_IF_STA, &config) == ESP_OK && config.sta.bssid_set) {
|
|
|
|
|
config.sta.bssid_set = false;
|
|
|
|
|
config.sta.channel = 0;
|
|
|
|
|
esp_wifi_set_config(WIFI_IF_STA, &config);
|
|
|
|
|
ESP_LOGW(TAG, "Preferred AP unavailable; falling back to SSID scan");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-08 10:06:44 +08:00
|
|
|
esp_err_t err = esp_wifi_connect();
|
2026-09-06 17:04:11 +08:00
|
|
|
network.reconnect_count++;
|
|
|
|
|
ESP_LOGI(TAG, "Reconnecting WiFi (attempt %d)", network.reconnect_count);
|
2026-10-08 10:06:44 +08:00
|
|
|
if (err != ESP_OK) schedule_reconnect();
|
2026-09-06 17:04:11 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
xEventGroupSetBits(network.event_group, WIFI_EVENT_FAILED);
|
2026-10-08 10:06:44 +08:00
|
|
|
schedule_reconnect();
|
|
|
|
|
ESP_LOGI(TAG, "WiFi unavailable; retrying every 10 seconds");
|
2026-09-06 17:04:11 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-08 10:06:44 +08:00
|
|
|
else if (event_id == WIFI_EVENT_STA_STOP)
|
|
|
|
|
{
|
|
|
|
|
reconnect_enabled = false;
|
|
|
|
|
if (reconnect_timer) esp_timer_stop(reconnect_timer);
|
|
|
|
|
xEventGroupClearBits(network.event_group, WIFI_EVENT_CONNECTED | WIFI_EVENT_STARTED);
|
|
|
|
|
network.ip_address[0] = '\0';
|
|
|
|
|
}
|
2026-09-06 17:04:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handler_event_ip_st(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
|
|
|
|
{
|
|
|
|
|
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
|
|
|
|
network.reconnect_count = 0;
|
2026-10-08 10:06:44 +08:00
|
|
|
if (reconnect_timer) esp_timer_stop(reconnect_timer);
|
2026-09-06 17:04:11 +08:00
|
|
|
|
|
|
|
|
esp_ip4addr_ntoa(&event->ip_info.ip, network.ip_address, sizeof(network.ip_address));
|
|
|
|
|
ESP_LOGI(TAG, "Got IP: %s", network.ip_address);
|
2026-10-08 10:06:44 +08:00
|
|
|
xEventGroupClearBits(network.event_group, WIFI_EVENT_FAILED);
|
2026-09-06 17:04:11 +08:00
|
|
|
xEventGroupSetBits(network.event_group, WIFI_EVENT_CONNECTED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_connected()
|
|
|
|
|
{
|
2026-10-08 10:06:44 +08:00
|
|
|
return network.event_group && (xEventGroupGetBits(network.event_group) & WIFI_EVENT_CONNECTED);
|
2026-09-06 17:04:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool configure_network()
|
|
|
|
|
{
|
2026-10-08 10:06:44 +08:00
|
|
|
network_error=ESP_OK;
|
2026-09-06 17:04:11 +08:00
|
|
|
|
|
|
|
|
// Create the event group
|
|
|
|
|
network.ssid[0] = '\0';
|
|
|
|
|
network.password[0] = '\0';
|
|
|
|
|
network.reconnect_count = 0;
|
|
|
|
|
network.ip_address[0] = '\0';
|
|
|
|
|
network.event_group = xEventGroupCreate();
|
2026-10-08 10:06:44 +08:00
|
|
|
if(!network.event_group){network_error=ESP_ERR_NO_MEM;return false;}
|
|
|
|
|
if (!reconnect_timer) {
|
|
|
|
|
const esp_timer_create_args_t timer_config = {
|
|
|
|
|
.callback = reconnect_saved_network,
|
|
|
|
|
.name = "wifi_reconnect"
|
|
|
|
|
};
|
|
|
|
|
network_error = esp_timer_create(&timer_config, &reconnect_timer);
|
|
|
|
|
if (network_error != ESP_OK) return false;
|
|
|
|
|
}
|
2026-09-06 17:04:11 +08:00
|
|
|
|
|
|
|
|
// Get ssid and password from NVS
|
|
|
|
|
nvs_handle_t nvs_handle;
|
|
|
|
|
uint8_t setup_requested = 0;
|
|
|
|
|
esp_err_t ret = nvs_open("wifi", NVS_READONLY, &nvs_handle);
|
|
|
|
|
if (ret == ESP_OK)
|
|
|
|
|
{
|
|
|
|
|
nvs_get_u8(nvs_handle, "setup_requested", &setup_requested);
|
|
|
|
|
size_t length = sizeof(network.ssid);
|
|
|
|
|
nvs_get_str(nvs_handle, "ssid", network.ssid, &length);
|
|
|
|
|
length = sizeof(network.password);
|
|
|
|
|
nvs_get_str(nvs_handle, "password", network.password, &length);
|
|
|
|
|
nvs_close(nvs_handle);
|
|
|
|
|
}
|
|
|
|
|
if (setup_requested || !platform_identity_is_configured())
|
|
|
|
|
{
|
|
|
|
|
ESP_LOGI(TAG, "Starting setup: requested or platform identity missing");
|
|
|
|
|
configure_ap();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
|
|
|
|
|
if (network.ssid[0] != '\0')
|
|
|
|
|
{
|
|
|
|
|
ESP_LOGI(TAG, "Connecting to WiFi %s", network.ssid);
|
|
|
|
|
// Initialize the TCP/IP stack
|
|
|
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
|
|
|
|
|
|
|
|
|
esp_event_handler_instance_t instance_any_id;
|
|
|
|
|
esp_event_handler_instance_t instance_got_ip;
|
|
|
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
|
|
|
|
ESP_EVENT_ANY_ID,
|
|
|
|
|
&handler_event_wifi_st,
|
|
|
|
|
NULL,
|
|
|
|
|
&instance_any_id));
|
|
|
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
|
|
|
|
|
IP_EVENT_STA_GOT_IP,
|
|
|
|
|
&handler_event_ip_st,
|
|
|
|
|
NULL,
|
|
|
|
|
&instance_got_ip));
|
|
|
|
|
|
|
|
|
|
// Create the default event loop
|
|
|
|
|
esp_netif_t *station_netif = esp_netif_create_default_wifi_sta();
|
|
|
|
|
|
|
|
|
|
// Initialize the WiFi stack in station mode
|
|
|
|
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
2026-10-08 10:06:44 +08:00
|
|
|
// RTC and TLS share internal RAM with the Wi-Fi DMA buffers. Six static
|
|
|
|
|
// RX/TX frames retain streaming capacity without reserving 32 DMA frames.
|
|
|
|
|
cfg.static_rx_buf_num=6;
|
|
|
|
|
cfg.static_tx_buf_num=6;
|
|
|
|
|
// Dynamic RX buffers prefer PSRAM; match the 32KiB TCP receive window.
|
|
|
|
|
cfg.dynamic_rx_buf_num=32;
|
|
|
|
|
cfg.cache_tx_buf_num=8;
|
|
|
|
|
cfg.rx_ba_win=6;
|
|
|
|
|
ESP_LOGI(TAG,"WiFi init internal_free=%u largest=%u rx=%d tx=%d",
|
|
|
|
|
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
|
|
|
|
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),cfg.static_rx_buf_num,cfg.static_tx_buf_num);
|
|
|
|
|
network_error=esp_wifi_init(&cfg);
|
|
|
|
|
if(network_error!=ESP_OK){
|
|
|
|
|
ESP_LOGE(TAG,"WiFi initialization unavailable: %s",esp_err_to_name(network_error));
|
|
|
|
|
esp_event_handler_instance_unregister(WIFI_EVENT,ESP_EVENT_ANY_ID,instance_any_id);
|
|
|
|
|
esp_event_handler_instance_unregister(IP_EVENT,IP_EVENT_STA_GOT_IP,instance_got_ip);
|
|
|
|
|
esp_netif_destroy_default_wifi(station_netif);
|
|
|
|
|
return false; // No abort/reboot loop on insufficient internal RAM.
|
|
|
|
|
}
|
2026-09-06 17:04:11 +08:00
|
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
|
|
|
|
|
|
|
|
|
ESP_LOGI(TAG, "Connecting to WiFi ssid=%s password=***", network.ssid);
|
|
|
|
|
wifi_config_t wifi_config;
|
|
|
|
|
bzero(&wifi_config, sizeof(wifi_config));
|
|
|
|
|
strncpy((char *)wifi_config.sta.ssid, network.ssid, sizeof(wifi_config.sta.ssid));
|
|
|
|
|
strncpy((char *)wifi_config.sta.password, network.password, sizeof(wifi_config.sta.password));
|
|
|
|
|
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
|
|
|
|
|
wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
|
|
|
|
|
wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN;
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
|
|
|
|
|
|
|
|
|
// Start the WiFi stack
|
2026-10-08 10:06:44 +08:00
|
|
|
reconnect_enabled = true;
|
2026-09-06 17:04:11 +08:00
|
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
|
|
|
|
xEventGroupWaitBits(network.event_group, WIFI_EVENT_STARTED, pdFALSE, pdFALSE, pdMS_TO_TICKS(5000));
|
|
|
|
|
wifi_scan_config_t scan = {.ssid = (uint8_t *)network.ssid};
|
|
|
|
|
if (esp_wifi_scan_start(&scan, true) == ESP_OK) {
|
|
|
|
|
wifi_ap_record_t records[16];
|
|
|
|
|
uint16_t count = 16;
|
|
|
|
|
if (esp_wifi_scan_get_ap_records(&count, records) == ESP_OK && count > 0) {
|
|
|
|
|
uint16_t best = 0;
|
|
|
|
|
for (uint16_t i = 1; i < count; ++i) {
|
|
|
|
|
if (records[i].rssi > records[best].rssi) best = i;
|
|
|
|
|
}
|
|
|
|
|
wifi_config.sta.bssid_set = true;
|
|
|
|
|
memcpy(wifi_config.sta.bssid, records[best].bssid, sizeof(wifi_config.sta.bssid));
|
|
|
|
|
wifi_config.sta.channel = records[best].primary;
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
|
|
|
|
ESP_LOGI(TAG, "Selected strongest AP among %u: rssi=%d channel=%u bssid=%02x:%02x:%02x:%02x:%02x:%02x",
|
|
|
|
|
count, records[best].rssi, records[best].primary,
|
|
|
|
|
records[best].bssid[0], records[best].bssid[1], records[best].bssid[2],
|
|
|
|
|
records[best].bssid[3], records[best].bssid[4], records[best].bssid[5]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
|
|
|
|
|
|
|
|
|
// Wait for the WiFi stack to start
|
|
|
|
|
EventBits_t bits = xEventGroupWaitBits(network.event_group, WIFI_EVENT_CONNECTED | WIFI_EVENT_FAILED, pdFALSE, pdFALSE, pdMS_TO_TICKS(30 * 1000));
|
|
|
|
|
if (!(bits & WIFI_EVENT_CONNECTED))
|
|
|
|
|
{
|
|
|
|
|
ESP_LOGE(TAG, "WifiStation failed");
|
|
|
|
|
// Reset the WiFi stack
|
|
|
|
|
|
|
|
|
|
// 取消注册事件处理程序
|
2026-10-08 10:06:44 +08:00
|
|
|
reconnect_enabled = false;
|
|
|
|
|
esp_timer_stop(reconnect_timer);
|
2026-09-06 17:04:11 +08:00
|
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
|
|
|
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_stop());
|
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_deinit());
|
|
|
|
|
esp_netif_destroy_default_wifi(station_netif);
|
|
|
|
|
xEventGroupClearBits(network.event_group, WIFI_EVENT_CONNECTED | WIFI_EVENT_FAILED);
|
|
|
|
|
// nvs_flash_erase();
|
|
|
|
|
// return false;
|
|
|
|
|
} else {
|
|
|
|
|
ESP_LOGI(TAG, "Connected to %s rssi=%d channel=%d", network.ssid, get_rssi(), get_channel());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!is_connected())
|
|
|
|
|
{
|
2026-10-08 10:06:44 +08:00
|
|
|
if (ble_cloud_configured()) {
|
|
|
|
|
// Completed BLE devices need the same phone flow when taken away from
|
|
|
|
|
// their old Wi-Fi. Reboot to release normal-mode buffers before NimBLE.
|
|
|
|
|
network_error = nvs_open("wifi", NVS_READWRITE, &nvs_handle);
|
|
|
|
|
if (network_error == ESP_OK) {
|
|
|
|
|
network_error = nvs_set_u8(nvs_handle, "setup_requested", 1);
|
|
|
|
|
if (network_error == ESP_OK) network_error = nvs_commit(nvs_handle);
|
|
|
|
|
nvs_close(nvs_handle);
|
|
|
|
|
}
|
|
|
|
|
ESP_LOGI(TAG, "Saved WiFi unavailable; requesting Bluetooth setup");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-09-06 17:04:11 +08:00
|
|
|
configure_ap();
|
|
|
|
|
ESP_LOGI(TAG, "WiFi not connected, starting AP");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|