記事の概要
ESP32のWiFi機能の基本的な使い方について説明します。
開発環境はESP-IDFとVScodeのPlatformIOを使用します。
0. ESP32のWiFi機能
ESP32のWiFi機能にはSTAモードとAPモードがあります。
STAモードは、ESP32がWiFiルータなどのアクセスポイントに接続し、ノードとして機能します。
APモードは、ESP32自体がアクセスポイントになって、スマホやPCがWiFiネットワークに接続できます。
この2つの違いは以下の動画を見ると分かりやすいです。
前半に紹介されるのがAPモードで、後半に紹介されるのがSTAモードです。
Arduino環境を使用すると簡単にできますが、今回はESP-IDF環境を使用する場合を説明します。
ESP-IDF環境の構築方法は以下を参照してください。
1. STAモード
以下のサンプルプログラムを例にSTAモードの使用方法を見てみます。
動作確認したい場合は、以下のサンプルのWiFi IDとパスワード、最大再接続回数を自分で設定して、自分のEPS32に書き込んでみてください。
シリアルモニタに
I (2089) wifi station: connected to ap SSID:myssid password:mypassword
が表示されれば成功です。
1.1 WiFiの初期化
1.1.1 NVSの初期化
EPS32に内蔵されたFlashメモリのnvs (non volatile storage)領域を初期化します。
nvsはWiFiライブラリで使用します。
nvs_flash_init
関数を実行して、返り値に問題がなければ終了し、エラーならばnvs_flash_erase
関数でメモリを消去してから、再度nvs_flash_init
関数を実行します。
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
1.1.2 WiFiイベントの作成とWiFiの初期設定
WiFiイベントを作成し、イベントに対応する処理を紐付けます。
また、WiFi接続前にしておくべき幾つかの設定を行います。
まずWiFiイベント変数を作成します。
s_wifi_event_group = xEventGroupCreate();
イベント検知するための初期設定関数'esp_event_loop_create_default'、およびnetifやSTAの初期化関数などを実行します。
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
証明書を扱う変数を作成します。
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
そしてWIFI_EVENT
とIP_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_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
1.1.3 WiFiの設定
WiFi接続の設定をします。
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
自分のWiFiの設定を定義しておきます。
安全性のためにはIDとパスワードをコードの中に含めず、外部から入力できるようにした方がいいと思いますが、今はこのままにします。
#define EXAMPLE_ESP_WIFI_SSID "Matsui's WiFi"
#define EXAMPLE_ESP_WIFI_PASS "abcdefg123456"
1.1.4 WiFi接続の開始
esp_wifi_set_mode
関数でSTAモードに設定し、esp_wifi_set_config
関数でWiFiの設定を有効化し、esp_wifi_start
関数でWiFi接続を開始します。
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
1.1.5 WiFiイベントの待機
WiFiイベントの応答を検知して、WiFi接続の成否を判定します。
xEventGroupWaitBits
関数により、イベントの応答を待機して、変数`bits'に結果を格納します。
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
変数`bits'を確認して、WiFi接続に成功したか、失敗したかで処理を分岐させます。
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
1.2 割り込み処理
WiFiイベント発生時には割り込み処理が実行されます。
この割り込み処理は、xEventGroupWaitBits
関数を実行中に発生します。
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
WIFI_EVENT_STA_START
イベント発生時には、esp_wifi_connect
関数を実行してWiFiネットワークに接続します。
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
WIFI_EVENT_STA_DISCONNECTED
イベント発生時には、EXAMPLE_ESP_MAXIMUM_RETRYで設定した最大回数まで再接続を試みて、それにも失敗すればxEventGroupSetBits
でWiFiイベント変数のWiFi接続に関するビットにWIFI_FAIL_BIT
を設定して、再接続を終了します。
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
WIFI_FAIL_BIT
を設定したことで、xEventGroupWaitBits
関数を抜けて、1.1.5に先述した以下の分岐処理が実行されます。
else if (bits & WIFI_FAIL_BIT)
WiFi接続に成功後にはIP_EVENT_STA_GOT_IP
イベントが発生するはずです。
イベントデータを取得し、xEventGroupSetBits
でWiFiイベント変数のWiFi接続に関するビットにWIFI_CONNECTED_BIT
を設定します。
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
WIFI_CONNECTED_BIT
を設定したことで、xEventGroupWaitBits
関数を抜けて、1.1.5に先述した以下の分岐処理が実行されます。
if (bits & WIFI_CONNECTED_BIT)
1.3 ページリクエスト
ESP32はWiFiルータをアクセス・ポイントにして、他のサイトのデータ転送をリクエストできます。
そのためには、あらかじめWiFi接続成功時に、FreeRTOSを利用して、ページリクエスト用のタスクを生成しておくといいでしょう。
WIFI_CONNECTED_BIT
の分岐処理にタスク生成関数を追加します。
if (bits & WIFI_CONNECTED_BIT) {
xTaskCreate(request_http, "request http", 10*configMINIMAL_STACK_SIZE, NULL, 10, NULL);
}
そしてrequest_http
関数を以下のように作成します。
configにアクセスするサイトのURLを設定してください。
なお、以下では証明書の要らないhttpサイトを対象にしています。
(httpsサイトにアクセスするには、アクセスしたいサイトのpem ファイルを入手して、以下の関数に設定を追加しないといけませんが、ここでは説明を省略します。)
void request_http(void *arg)
{
esp_http_client_config_t config = {
.url = "http://www.chiseki.go.jp/about/point/index.html",
.event_handler = http_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
if (esp_http_client_perform(client) != ESP_OK)
ESP_LOGE(TAG, "HTTP REQUEST ERROR");
esp_http_client_cleanup(client);
vTaskDelete(NULL);
}
HTTPイベント発生時に実行する割り込み処理も作成します。
今回は、HTTP_EVENT_ON_DATA
イベントにより、HTMLのデータ部分をコンソール画面に表示させました。
HTML構文が表示されるはずです。
他のイベント発生時には、特にすることがないのでメッセージだけを表示させています。
esp_err_t http_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id)
{
case HTTP_EVENT_ERROR:
printf("HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
printf("HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
printf("HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
printf("HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
printf("%.*s\n", evt->data_len, (char *)evt->data);
break;
case HTTP_EVENT_ON_FINISH:
printf("HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
printf("HTTP_EVENT_DISCONNECTED");
break;
default:
break;
}
return ESP_OK;
}
これを利用して様々なサイトからのデータを収集することができます。
逆にこちらから相手サイトにデータ転送することもできますが、ここでは説明しません。
なお、サンプルプログラムにはページリクエストの部分はないので、以下のサイトやGithubの複数のサンプルを参照しました。
2. APモード
以下のサンプルプログラムを例にAPモードの使用方法を見てみます。
2.1 WiFiの初期化
2.1.1 NVSの初期化
STAモードの時と同様にします。
2.1.2 WiFiの初期設定
WiFi初期設定は、STAモードの時と同様です。
ただし、APモードの場合はesp_netif_create_default_wifi_ap
関数を使います。
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
STAモードと異なりWiFiイベントは必須ではありませんが、WiFi接続時や切断時に何か処理を実行したいのならば、以下のように割り込み処理を登録しておきます。
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
2.1.3 WiFiの設定
WiFi接続の設定をします。
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
.channel = EXAMPLE_ESP_WIFI_CHANNEL,
.password = EXAMPLE_ESP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA_WPA2_PSK
},
};
今回は定数を以下のように設定しておきます。
#define EXAMPLE_ESP_WIFI_SSID "esp32_ap_test"
#define EXAMPLE_ESP_WIFI_PASS "my_pass_0123"
#define EXAMPLE_ESP_WIFI_CHANNEL 11
#define EXAMPLE_MAX_STA_CONN 1
2.1.4 WiFi接続の開始
esp_wifi_set_mode
関数でAPモードに設定し、esp_wifi_set_config
関数でWiFiの設定を有効化し、esp_wifi_start
関数でWiFi接続を開始します。
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
2.2 割り込み処理
WiFi接続時にはWIFI_EVENT_AP_STACONNECTED
イベントが発生し、切断時にはWIFI_EVENT_AP_STADISCONNECTED
イベントが発生します。
以下では、これらのイベント発生時にメッセージを表示させています。
これらの処理がなくても、動作には問題ありません。
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
2.3 HTTPサーバー
以下を参考にしてHTTPサーバーを作成します。
2.3.1 HTTPサーバ設定
HTTPメソッドのPOSTとGETがリクエストされた際の割り込み処理を登録します。
httpd_uri_t uri_get = {
.uri = "/",
.method = HTTP_GET,
.handler = get_handler,
.user_ctx = NULL};
httpd_uri_t uri_post = {
.uri = "/",
.method = HTTP_POST,
.handler = post_handler,
.user_ctx = NULL};
void start_webserver(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
/* Register URI handlers */
httpd_register_uri_handler(server, &uri_get);
httpd_register_uri_handler(server, &uri_post);
}
}
2.3.2 POSTとGETの割り込み処理
GETがリクエストされたならば、割り込み処理get_handler
を実行します。
httpd_resp_send
関数により、HTMLデータを転送します。
POSTがリクエストされたならば、割り込み処理post_handler
を実行します。
httpd_req_recv
関数により転送されたデータを受信します。
また、応答処理が必要ならば、httpd_resp_send
関数によりデータを転送します。
static const char *html_sample
= "<html><form action=\"/\" method=\"post\">"
"<label for=\"your_name\">Your Name:</label><br>"
"<input type=\"text\" id=\"your_name\" name=\"your_name\"><br>"
"<input type=\"submit\" value=\"Submit\">"
"</form></html>";
esp_err_t get_handler(httpd_req_t *req)
{
httpd_resp_send(req, html_sample , HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t post_handler(httpd_req_t *req)
{
char content[100];
size_t recv_size = MIN(req->content_len, sizeof(content));
int ret = httpd_req_recv(req, content, recv_size);
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
const char resp[] = "Response";
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
2.3.3 動作確認
まずスマホやPCのWiFi一覧から、先ほどに設定したWiFiのSSIDである"esp32_ap_test"を探して接続します。
パスワードは、先ほどに設定した"my_pass_0123"です。
次にブラウザを開いて、ホスト・アドレスに行きます。
例えば、今回私が使用しているESP32-WROOMのホスト・アドレスは、192.168.4.1です。
URLにこのアドレスを入力すれば、GETリクエストにより、html_sample で与えたHTMLが表示されます。
ブラウザのYour Nameの欄に名前を入力してSubmitボタンをクリックすれば、POSTリクエストにより、入力したデータをEPS32が受信し、"Response"と表示される応答画面に遷移します。