ESP32でMongoose(web server用ライブラリ)を使ってみる

# はじめに

ESP32で[Mongoose](https://github.com/cesanta/mongoose)というweb server用のライブラリを使ってみたので、その情報共有です。

以下に載せたのは、簡単なWebサーバーのサンプルプログラムを動作させる手順です。PCからアクセスして「Hello World」とブラウザに表示させることができます。※ESP-IDFで開発しています

きっかけはESP32をMQTTのブローカーにしたいと思ったことでした。調べていて、[この記事](https://www.esp32.com/viewtopic.php?t=109)を見つけてMongooseに辿り着きました。

本当にESP32をブローカーにできるかはまだわかっていませんが、まずは簡単なプログラムでMongooseを使ってみたという感じです。

# 使用したもの

* [ESPr® Developer 32](https://www.switch-science.com/catalog/3210/)
* 開発に使用したOS：Ubuntu18.04 ( Windows7 Pro 64bit をホストOSとするVirtualBoxにインストール ) 

# 手順

※ESP-IDFの開発環境を構築していることを前提としています。（[「ここ」](https://qiita.com/TwDaiki/items/862f61c9d3888e990209#%E6%89%8B%E9%A0%86%EF%BC%92mruby%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B)の手順2の1～5を行った状態に該当します）


### １. プロジェクトを作成する
テンプレートのESP-IDFプロジェクトをgithubからクローンします。
そして、mongoose_exampleというディレクトリ名に変えます

```
$ git clone https://github.com/espressif/esp-idf-template.git
$ mv esp-idf-template mongoose_example
$ cd mongoose_example/
```

### ２. mongooseライブラリをプロジェクトに入れる
mongooseライブラリをgithubからcloneします  

```
$ mkdir -p components
$ cd components/
$ git clone https://github.com/cesanta/mongoose.git
```

その後、componentsディレクトリに出来たmongooseディレクトリに移動して、component.mkファイルを以下の内容のみで作成します。

```
COMPONENT_ADD_INCLUDEDIRS=.
```

### ３. サンプルコードの作成
main.cを以下の「main.cのコード」の内容に置き換えます。

※ただし、コード中の以下の①と②は自分の環境に合わせて変更する必要があります。
①使用環境のWiFi情報

```
#define WIFI_SSID "xxxxx"
#define WIFI_PASS "xxxxx"
```

②アクセス先(ESP32)のIPアドレス

```
IP4_ADDR(&ipInfo.ip, 192,168,10,3);
IP4_ADDR(&ipInfo.gw, 192,168,10,1);
```
　

 + main.cのコード

```
#include <string.h>

#include "freertos/FreeRTOS.h"

#include "esp_event.h"
#include "esp_event_loop.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "nvs_flash.h"

#include "mongoose.h"

#define WIFI_SSID "xxxxx"
#define WIFI_PASS "xxxxx"

#define MG_LISTEN_ADDR "80"

static esp_err_t event_handler(void *ctx, system_event_t *event) {
  (void) ctx;
  (void) event;
  return ESP_OK;
}

static void mg_ev_handler(struct mg_connection *nc, int ev, void *p) {
  static const char *reply_fmt =
      "HTTP/1.0 200 OK\r\n"
      "Connection: close\r\n"
      "Content-Type: text/plain\r\n"
      "\r\n"
      "Hello %s\n";

  switch (ev) {
    case MG_EV_ACCEPT: {
      char addr[32];
      mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
                          MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
      printf("Connection %p from %s\n", nc, addr);
      break;
    }
    case MG_EV_HTTP_REQUEST: {
      char addr[32];
      struct http_message *hm = (struct http_message *) p;
      mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
                          MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
      printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len,
             hm->method.p, (int) hm->uri.len, hm->uri.p);
      mg_printf(nc, reply_fmt, addr);
      nc->flags |= MG_F_SEND_AND_CLOSE;
      break;
    }
    case MG_EV_CLOSE: {
      printf("Connection %p closed\n", nc);
      break;
    }
  }
}

void app_main(void) {
  nvs_flash_init();
  tcpip_adapter_init();

  tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);

  /* IP Address */
  tcpip_adapter_ip_info_t ipInfo;
  IP4_ADDR(&ipInfo.ip, 192,168,10,3);
  IP4_ADDR(&ipInfo.gw, 192,168,10,1);
  IP4_ADDR(&ipInfo.netmask, 255,255,255,0);
  tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo);

  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

  /* Initializing WiFi */
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_wifi_init(&cfg));
  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  wifi_config_t sta_config = {
      .sta = {.ssid = WIFI_SSID, .password = WIFI_PASS, .bssid_set = false}};
  ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
  ESP_ERROR_CHECK(esp_wifi_start());
  ESP_ERROR_CHECK(esp_wifi_connect());

  /* Starting Mongoose */
  struct mg_mgr mgr;
  struct mg_connection *nc;

  printf("Starting web-server on port %s\n", MG_LISTEN_ADDR);

  mg_mgr_init(&mgr, NULL);

  nc = mg_bind(&mgr, MG_LISTEN_ADDR, mg_ev_handler);
  if (nc == NULL) {
    printf("Error setting up listener!\n");
    return;
  }
  mg_set_protocol_http_websocket(nc);

  // print the local IP address
	tcpip_adapter_ip_info_t ip_info;
	ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
	printf("IP Address:  %s\n", ip4addr_ntoa(&ip_info.ip));
	printf("Subnet mask: %s\n", ip4addr_ntoa(&ip_info.netmask));
  printf("Gateway:     %s\n", ip4addr_ntoa(&ip_info.gw));

  /* Processing events */
  while (1) {
    mg_mgr_poll(&mgr, 1000);
  }
}

```

### ４. 動作を確認する

プログラムをビルドしてESP32に書き込み＆モニター開始します

```
$ make
$ make flash
$ make monitor
```
※初回の実行時に設定画面が出てくるが、設定はそのままで良いはずなので、「Exit」を選択してその画面を抜ければよいです（それ以降でCOMポート等のコンフィグを変更したい場合は「make menuconfig」を実行する)
　
その後に、ESP32と同じLAN内のPCのブラウザから指定したIPアドレス(上記コードのままであれば「192.168.10.3」)にアクセスすると、以下の文字列がブラウザに表示されます。(「192.168.10.12」はアクセス元のPCのIPアドレス)

```
Hello 192.168.10.12:49911
```

一方、ESP32のモニター上では以下のような表示が出ます

```
$ make monitor
Toolchain path: /home/daiki/esp/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc
Toolchain version: crosstool-ng-1.22.0-80-g6c4433a
Compiler version: 5.2.0
Python requirements from /home/daiki/esp/esp-idf/requirements.txt are satisfied.
MONITOR
--- idf_monitor on /dev/ttyUSB0 115200 ---
   :（省略）
Starting web-server on port 80
IP Address:  192.168.10.3
Subnet mask: 255.255.255.0
Gateway:     192.168.11.1
I (1567) wifi: new:<11,0>, old:<1,0>, ap:<255,255>, sta:<11,0>, prof:1
I (2557) wifi: state: init -> auth (b0)
I (2557) wifi: state: auth -> assoc (0)
I (2567) wifi: state: assoc -> run (10)
I (2647) wifi: connected with xxxxx, channel 11, bssid = 00:22:aa:bb:cc:dd
I (2647) wifi: pm start, type: 1

I (2647) event: sta ip: 192.168.10.3, mask: 255.255.255.0, gw: 192.168.10.1
Connection 0x3ffb97c0 from 192.168.10.12:49911
HTTP request from 192.168.10.12:49911: GET /
Connection 0x3ffb97c0 closed
Connection 0x3ffc63b8 from 192.168.10.12:49911
HTTP request from 192.168.10.12:49910: GET /favicon.ico
Connection 0x3ffc63b8 closed
```


# 終わりに

次はMongooseを使って、ESP32をMQTTのブローカーにしたいと考えています。

[「ラズパイゼロを使うか、ESP32だったらuMQTTBrokerの方がいいんじゃね？」](https://esp32.com/viewtopic.php?t=7620)という人たちもいるので実はあまり期待はしていませんが、手を動かしていれば色々と発見もあるので、それはそれで良いということでチャレンジしてみます。

見ていただいてありがとうございました。
тнайк чoμ_〆(・ω・｡)			
			
# おまけ

### Mongooseのサンプルプログラム

Mongooseはクロスプラットホームの組込向けライブラリです。ESP32の他にもPICやSTM、mbedやnRFといったマイコンなどへのサンプルプログラムが公開されています。
　
互換性のほどは良く分かりませんが、同じコードを使い回すことができれば、実はすごいやつかもしれません。

詳しくは、[githubのページ](https://github.com/cesanta/mongoose/tree/master/examples)を見ると色々わかりそうです。

# 更新履歴
* 2019-05-24：新規作成