###### tags: `ESP32` ESP32 WIFI automatically connect after reboot === ## 前言: 過去在用ESP32撰寫一些專案,都會將無線網路的SSID和PASSWORD寫死在程式之中。但換個環境還需要將程式重寫燒錄實在是不太合理。於是,我搜尋了網路上的一些資料,找到使用HTTP WEB設定ESP32網路的方式。但我不太想寫網頁(美術0分),因此我找到了另外一個快速使ESP32連上網路的方法 - ESPTOUCH。 ESPTOUCH可利用ESP晶片官方提供的協定,透過broadcast,幫助ESP晶片(包含ESP32, ESP8266, ...)連上網路。 [ESPTOUCH官網](https://www.espressif.com/zh-hans/products/software/esp-touch/resources) [APP STORE](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwj0_r3To53uAhW5KqYKHQwXC_oQFjACegQIAxAC&url=https%3A%2F%2Fapps.apple.com%2Ftw%2Fapp%2Fespressif-esptouch%2Fid1071176700&usg=AOvVaw2D7x9GidDtOzJpZnMBDQhG) [ANDROID](https://github.com/EspressifApp/EsptouchForAndroid/releases/download/v1.1.1/esptouch.apk) :::info 詳細文件可以參考官方文件: https://www.espressif.com/sites/default/files/documentation/esp-touch_user_guide_en.pdf ::: --- ## 正題: ESPTOUCH可以快速幫助ESP晶片連上網路,但並不會幫你紀錄SSID,PASSWORD等連線資訊。也就是說,當系統重新開機後(斷電重啟),將會失去連線資訊。因此,ESP內建的Non-volatile storage(NVS)派上用場了。 :::info NVS文件: [https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html) 經由NameSpace和KEY,讀寫NVS內容。 ::: :::warning PS: 整篇採用ESP-IDF撰寫 ::: :::info 本篇程式範例: [https://github.com/Raspberry-Pi-4-MCU/ESP32-homekit-ws2812/tree/master/examples/Lightbulb/main](https://github.com/Raspberry-Pi-4-MCU/ESP32-homekit-ws2812/tree/master/examples/Lightbulb/main) ::: * 整個程式流程: 1. 讀取NVS,取得過去儲存的連線資訊。若找不到生成假的資訊回傳。 2. 透過連線資訊連線。如果成功就結束連線設定。失敗就跳到ESPTOUCH模式。 3. 透過ESPTOUCH取得SSID和PASSWORD。連線上該網路,並儲存設定。 4. 結束連線設定。 接著,我們開始撰寫程式了。 首先,先撰寫WIFI連接。 wificonnect.h: ```Clike= #ifndef _WIFICONNECT_H_ #define _WIFICONNECT_H_ #include <string.h> #include "esp_system.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "nvs_flash.h" #include "freertos/event_groups.h" #include "lwip/err.h" #include "lwip/sys.h" #include "wificonfig.h" #include "esp_smartconfig.h" static const int CONNECTED_BIT = BIT0; EventGroupHandle_t s_wifi_event_group; void wifi_start_connect(); #endif ``` 這個標頭檔,包含了 ```Clike= // WIFI 連線 void wifi_start_connect(); ``` ```Clike= // WIFI 連線狀態事件 EventGroupHandle_t s_wifi_event_group; ``` wificonnect.c: ```Clike= #include "wificonnect.h" const char *TAG = "WIFI_CONNECT"; static const int ESPTOUCH_DONE_BIT = BIT1; static void smartconfig_example_task(void * parm); static void event_handle(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { // wifi event if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START){ // read wificonnfig from nvs wifi_config_t wificonfig; ESP_ERROR_CHECK(esp_wifi_disconnect()); wificonfig = wificonfig_read(); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wificonfig)); ESP_ERROR_CHECK(esp_wifi_connect()); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED){ ESP_LOGW(TAG, "Connect to the AP failed. switch smart. "); xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT); xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP){ xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT); ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); }else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { ESP_LOGI(TAG, "Got SSID and password"); smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data; wifi_config_t wifi_config; uint8_t ssid[33] = { 0 }; uint8_t password[65] = { 0 }; bzero(&wifi_config, sizeof(wifi_config_t)); memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid)); memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password)); wifi_config.sta.bssid_set = evt->bssid_set; if (wifi_config.sta.bssid_set == true) { memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid)); } memcpy(ssid, evt->ssid, sizeof(evt->ssid)); memcpy(password, evt->password, sizeof(evt->password)); ESP_LOGI(TAG, "SSID:%s", ssid); ESP_LOGI(TAG, "PASSWORD:%s", password); ESP_ERROR_CHECK( esp_wifi_disconnect() ); ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); ESP_ERROR_CHECK( esp_wifi_connect()); // write NVS wificonfig_write(wifi_config); }else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) { xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT); } } void wifi_start_connect() { // wifi initial esp_event_loop_create_default(); ESP_ERROR_CHECK(esp_netif_init()); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // wifi flag initial s_wifi_event_group = xEventGroupCreate(); // register event 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, &event_handle, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handle, NULL, &instance_got_ip)); ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handle, NULL)); // wifi start ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); } static void smartconfig_example_task(void * parm) { EventBits_t uxBits; ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH)); smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); while (1) { uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); if(uxBits & CONNECTED_BIT) { ESP_LOGI(TAG, "WiFi Connected to ap"); } if(uxBits & ESPTOUCH_DONE_BIT) { ESP_LOGI(TAG, "smartconfig over"); esp_smartconfig_stop(); vTaskDelete(NULL); } } } ``` 這個程式看上去非常複雜,我們慢慢拆開來解說。ESP-IDF整個架構建置在FreeRTOS之上,將各個模組以不同task分開。task以FreeRTOS的event, queue, message, ...進行溝通。 * 首先先看到這個function - wifi_start_connect(): ```Clike= void wifi_start_connect() { // wifi initial esp_event_loop_create_default(); ESP_ERROR_CHECK(esp_netif_init()); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // wifi flag initial s_wifi_event_group = xEventGroupCreate(); // register event 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, &event_handle, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handle, NULL, &instance_got_ip)); ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handle, NULL)); // wifi start ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); } ``` ```Clike= // 新增一個TASK(FreeRTOS的Task) esp_event_loop_create_default(); // 初始化TCP/IP esp_netif_init(); // 設定TCP/IP為Station模式 esp_netif_create_default_wifi_sta(); // 生成一個定義的WIFI設定 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); // 建立WIFI狀態旗標(FreeRTOS的event) s_wifi_event_group = xEventGroupCreate(); ``` 註冊ESP-IDF定義好的FreeRTOS事件: ```Clike= 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, &event_handle, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handle, NULL, &instance_got_ip)); ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handle, NULL)); ``` :::info ESP32包裝FreeRTOS的包裝函式: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_event.html ::: ```Clike= // 設定WIFI到station模式,並啟動WIFI esp_wifi_set_mode(WIFI_MODE_STA); sp_wifi_start(); ``` 接著來解析TASK的內容,主要分成以下的部分: ```Clike= static void event_handle(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { // 當station模式開始時候: if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START){ 讀取NVS設定,並嘗試連線。 } // 當連線失敗的時候: else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED){ 切換到ESPTOUCH模式(生成一個新的TASK)。 } // 當獲取IP時: else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP){ 結束連線。 } // 當獲取SSID和PASSWORD時(ESPTOUCH模式): else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { 嘗試透過SSID和PASSWORD連線到網路。並寫入NVS。 } // 當獲取ESPTOUCH計算ACK回到行動裝置時(ESPTOUCH模式): else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) { ... } } ``` ```Clike= static void smartconfig_example_task(void * parm) { EventBits_t uxBits; ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH)); smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); while (1) { // 等待event發生 uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); // 如果連線成功 if(uxBits & CONNECTED_BIT) { ESP_LOGI(TAG, "WiFi Connected to ap"); } // 如果ESPTOUCH完成工作 if(uxBits & ESPTOUCH_DONE_BIT) { ESP_LOGI(TAG, "smartconfig over"); esp_smartconfig_stop(); // 破壞本身的TASK vTaskDelete(NULL); } } } ``` --- 接著,我們來說明NVS的程式,首先是標頭檔: wificonfig.h: ```Clike= #ifndef _WIFICONFIG_H_ #define _WIFICONFIG_H_ #include <freertos/FreeRTOS.h> #include <freertos/task.h> #include "freertos/event_groups.h" #include "esp_wifi.h" #include "nvs_flash.h" #include <freertos/queue.h> #include <string.h> // Namespace #define NVS_CUSTOMER "wifiregion" // Key #define NVS_REGION "wifidata" // 讀取WIFI設定 wifi_config_t wificonfig_read(void); // 寫入WIFI設定 void wificonfig_write(wifi_config_t); #endif ``` wificonfig.c: ```Clike= #include "wificonfig.h" wifi_config_t wificonfig_read(void) { nvs_handle_t handle; ESP_ERROR_CHECK(nvs_open(NVS_CUSTOMER, NVS_READWRITE, &handle)); // key is exist or not size_t size_name = 0; nvs_get_used_entry_count(handle, &size_name); if(size_name < 5){ // send fake wifi info wifi_config_t wifi_config = { .sta = { .ssid = "87654321", .password = "12345678", }, }; return wifi_config; } wifi_config_t wifi_config_stored; memset(&wifi_config_stored, 0x0, sizeof(wifi_config_stored)); uint32_t len = sizeof(wifi_config_stored); ESP_ERROR_CHECK(nvs_get_blob(handle, NVS_REGION, &wifi_config_stored, &len)); nvs_close(handle); return wifi_config_stored; } // 將WIFI設定,寫入NVS void wificonfig_write(wifi_config_t wifi_config) { nvs_handle_t handle; ESP_ERROR_CHECK(nvs_open(NVS_CUSTOMER, NVS_READWRITE, &handle)); ESP_ERROR_CHECK(nvs_set_blob(handle, NVS_REGION, &wifi_config, sizeof(wifi_config))); nvs_close(handle); } ``` function wificonfig_read: ```Clike= wifi_config_t wificonfig_read(void) { nvs_handle_t handle; // 開啟NVS ESP_ERROR_CHECK(nvs_open(NVS_CUSTOMER, NVS_READWRITE, &handle)); // 檢查NVS內是否有WIFI資訊,沒有救生成假的資訊: size_t size_name = 0; nvs_get_used_entry_count(handle, &size_name); if(size_name < 5){ // send fake wifi info wifi_config_t wifi_config = { .sta = { .ssid = "87654321", .password = "12345678", }, }; return wifi_config; } // 回傳讀出來的WIFI資訊: wifi_config_t wifi_config_stored; memset(&wifi_config_stored, 0x0, sizeof(wifi_config_stored)); uint32_t len = sizeof(wifi_config_stored); ESP_ERROR_CHECK(nvs_get_blob(handle, NVS_REGION, &wifi_config_stored, &len)); nvs_close(handle); return wifi_config_stored; } ```