---
# System prepended metadata

title: ESP32 WIFI automatically connect after reboot
tags: [ESP32]

---

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

