(7)新しい機能追加。(HTTP Server)

ここからは、Flashに保存してデータを編集する為にHTTP Serverを立ち上げて行きたいと思います。

Baseは、サンプルプログラム ”simple”

ESP32とAP接続し必要なプログラムを開発して行くのが筋ですが、プログラムの開発を考えると先ずSTA接続しサーバーを立ち上げ、その環境で全てプログラムを書き終えてからAP接続に変更した方が効率が良さそうです。先ずはESP32とはSTA接続する事にします。

今回HTTPサーバを立ち上げたいのですが、条件に有ったサンプルプログラムが、”~/esp/esp-idf/examples/protocols/http_server/simple”に有りました。これを元にプログラムを書いて行けそうなのですが、このサンプルで使用している関数、”example_connect()”が理解出来ません。この関数はESP32の接続を行う関数で、それが理解出来ないと先に進みません。そこでこの部分を代行する他のサンプルプログラムを探すと、”~/esp/esp-idf/examples/wifi/getting_started/station”にSTA接続するサンプルプログラムが有りました。”example_connect()”をこのサンプルのコードを元に書き換えてプログラムを書き始める事にしました。ちなみに、”~/esp/esp-idf/examples/wifi/getting_started/softAP”にAP接続のサンプルプログラムが有ります。

先ずはサンプル通りに

プロジェクトの構造は以下の様になっています。今回はmainフォルダーの下のmain.cの変更と、そこにKconfig.projbuildを追加しています。


- http_sample/
      - CMakeLists.txt
      - partitions_example.csv
      - sdkconfig.defaults
      - main/
                   - CMakeLists.txt
                   - main.c
                   - Kconfig.projbuild
      - components/
                   - spiffs_image/
                              - station.txt

先ずはmain.cの説明から。サンプルプログラムを元に下記の様にコードを書いて見ました。

main.c

#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include "nvs_flash.h"
#include <esp_http_server.h>
#include "mdns.h"
#include "esp_spiffs.h"
#include "freertos/event_groups.h"

static const char *TAG = "example";

struct st_info_data {         //Station info
    char _deco[5];
    char _st_ID[20];
    char _st_URL[200];
};

struct st_info_data info_data[24];

void read_station(void)
{
	char buf[330];
    FILE *fp = NULL;
    int a,b;

	fp = fopen("/spiffs/station.txt", "r");
	for(a = 0; a < 24; a ++)
	{
		fgets(buf, 300, fp);

		*(strchr(buf, ',')) = 0;
		strcpy(info_data[a]._deco, buf);
		b = strlen(buf) + 1;

		*(strchr(&buf[b], ',')) = 0;
		strcpy(info_data[a]._st_ID, &buf[b]);
		b += (strlen(&buf[b]) + 1);
		
		strcpy(info_data[a]._st_URL, &buf[b]);
	}	
	fclose(fp);
}

void init_spiffs(void)
{
    esp_vfs_spiffs_conf_t conf = {
      .base_path = "/spiffs",
      .partition_label = NULL,
      .max_files = 5,
      .format_if_mount_failed = false
    };

    // Use settings defined above to initialize and mount SPIFFS filesystem.
    // Note: esp_vfs_spiffs_register is an all-in-one convenience function.
    esp_vfs_spiffs_register(&conf);

}

esp_err_t get_handler(httpd_req_t *req)
{

    httpd_resp_set_type(req, "text/html");
    httpd_resp_send(req, "<h1>Hello Secure World!</h1>", HTTPD_RESP_USE_STRLEN);

    return ESP_OK;
}

httpd_handle_t start_webserver(void)
{

    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.lru_purge_enable = true;
    config.uri_match_fn = httpd_uri_match_wildcard;

    // Start the httpd server
    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    if (httpd_start(&server, &config) != ESP_OK) return NULL;

    // Set URI handlers
	httpd_uri_t hello = {
        .uri       = "/*",  // Match all URIs of type /path/to/file
    	.method    = HTTP_GET,
    	.handler   = get_handler,
    	.user_ctx  = "Hello World!"
	};
    ESP_LOGI(TAG, "Registering URI handlers");
    httpd_register_uri_handler(server, &hello);

   return server;
}

void stop_webserver(httpd_handle_t server)
{
    // Stop the httpd server
    httpd_stop(server);
}

void disconnect_handler(void* arg, esp_event_base_t event_base,
                               int32_t event_id, void* event_data)
{
    httpd_handle_t* server = (httpd_handle_t*) arg;
    if (*server) {
        ESP_LOGI(TAG, "Stopping webserver");
        stop_webserver(*server);
        *server = NULL;
    }
}

// FreeRTOS event group to signal when we are connected
static EventGroupHandle_t s_wifi_event_group;

// The event group allows multiple bits for each event, but we only care about two events:
// - we are connected to the AP with an IP
// - we failed to connect after the maximum amount of retries 
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1
#define EXAMPLE_ESP_MAXIMUM_RETRY  10

static int s_retry_num = 0;

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) 
    {
        esp_wifi_connect();
    } 
    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");
    } 
    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);
    }
}

esp_err_t wifi_init_sta(void)
{
    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    
    s_wifi_event_group = xEventGroupCreate();
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    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));
	
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = CONFIG_ESP_WIFI_SSID,
            .password = CONFIG_ESP_WIFI_PASSWORD,
	     	.threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    
    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() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

//  Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
//  number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above)
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

//  xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually	happened. 
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);
    if (bits & WIFI_CONNECTED_BIT) 
    {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
        return ESP_OK;         
    } 
    else if (bits & WIFI_FAIL_BIT) 
    {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
    } 
    else 
    {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
    return ESP_FAIL;
}
//========================================== Main ============================================================
void app_main(void)
{
    esp_err_t err;
    
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    err = wifi_init_sta();
    if(err) 
    {
        ESP_LOGE(TAG, "WiFi Init failed: %d\n", err);
        return;
    }

   	start_webserver();
    
    //initialize mDNS service
    err = mdns_init();
    if (err) 
    {
        ESP_LOGE(TAG, "MDNS Init failed: %d\n", err);
        return;
    }

    //set hostname
    mdns_hostname_set("my-esp32");
    //set default instance
    mdns_instance_name_set("Jhon's ESP32 Thing");
    
	init_spiffs();
    read_station();
}
  • 216行からプログラムが開始します。
  • 223行:err = wifi_init_sta(); STA接続する関数です。
  • 153行:esp_err_t wifi_init_sta(void) 関数本体
    • 158から161行でWiFi接続する為の初期設定をします。
    • 163行:ここでWiFiイベントの登録
    • 168行:IPイベントの登録
    • 174行:ここでWiFi関係のパラメータを設定しています。
      • .ssid: WiFiのSSID
      • .password: WiFIのpassword
      • .threshold.authmode:接続方法
    • これらのパラメータを持って、182,183,184でWiFiと接続を開始します。
    • 190行:WiFi接続処理が終了するまでここでウエートなります。
    • ウエートしている間に接続イベントが起こると、123行の関数が起動します。
    • 126行:ルータ側がOKを出した場合、実際の接続動作128行に入ります。
    • 130行:ルータがNGを出した場合、リトライしそれでもダメならFailとします。
    • 144行:IPアドレスが送られて来たらそれを表示します。
    • 197から199行で登録を解除
    • 200行以下で接続の結果を表示
  • メインプログラムに戻って、230行でHTTPサーバーの開始
  • 69行:httpd_handle_t start_webserver(void) 関数の本体
    • 75行:このパラメータを指定するとワイルドカードが使用出来ます。詳細は後ほど
    • 79行:ここでサーバが起動します。
    • 82行:URIの処理の指定を行います。URIの処理にはGETとPOSTが有りますが今回はGETのみ使用します。
      • 83行:ここでワイルドカードを使用しています。”/*”の意味はサーバーへのアクセス全てと言う意味です。
      • 84行:アクセス方法は、”GET”
      • 85行:処理する関数の指定
    • 89行で上記の設定を行う。これで、サーバーにGETでアクセスしたら全て、処理用の関数が実行される事になります。
  • 再び、メインプログラムに戻って、240行からHostNameの設定を行っています。
    • 241から243行で、HostNameを、”my-esp32″と設定しています。
  • 残りは前回実装したSPIFFS用のコードです。
  • プログラムを実行してサーバーにGETでアクセスすると、60行のesp_err_t get_handler(httpd_req_t *req)が実行されます。今回は、”Hello Secure World!”と画面に表示するだけです。

次は、Kconfig.projbuild。このファイルはmain.c で使用するWiFISSID,Passwordの情報を持っています。

Kconfig.projbuild

menu "Example Configuration"

    config ESP_WIFI_SSID
        string "WiFi SSID"
        default "myssid"
        help
            SSID (network name) for the example to connect to.

    config ESP_WIFI_PASSWORD
        string "WiFi Password"
        default "mypassword"
        help
            WiFi password (WPA or WPA2) for the example to use.

    config ESP_MAXIMUM_RETRY
        int "Maximum retry"
        default 5
        help
            Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
endmenu

menuconfigで設定出来ますが、ここでSSIDとPasswordを設定して置けばmenuconfigを立ち上げる必要がなくなるのでここで設定しています。今回のプロジェクトをここに保存します。

コンパイルと実行

ESP32を使用するので、モニタから、”idf.py -p [usb port] flash monitor” でコンパイル実行されます。モニター表示の最後に

I (2114) example: Starting server on port: ’80’
I (2124) example: Registering URI handlers

と表示されればサーバーは立ち上がっています。今回はHostName”my-esp32”を設定しているので、ブラウザに”my-esp32.local”と入力すればサーバーにアクセス出来ます。

次回は

今回はHTTPサーバーを立ち上げる事が出来ました。次回はデータ編集用のサーバー側のプログラムを書いて行きます。