###### 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;
}
```