Mozilla IoTにESP32をつなぐ

Mozillaが提供するWoTの実装であるWebThingsを触ってみます。（いろんな呼び方があってややこしい。。。）

Mozilla IoT：WebThings
　https://iot.mozilla.org/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/609e1bcc-56fa-6da5-8938-c32b7df79bdd.png)

Mozillaが、WebThings Gatewayと、WebThingsに対応させるためのFrameworkを用意してくれています。
Gatewayは、定期的にWebThingsに問合せして状態を取得したり、リクエストを送信したり、状態の変化をロギングしてくれるサーバです。Webサーバを同梱しているため、ブラウザから状態を見たり、操作したりできるので、Goodです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/a7ce0faf-29b7-1a9d-08e9-b0d2efbcbf9e.png)

接続先のWebThingsとして、Google HomeやESP32Homeなど、有志の方がアダプターを作ってくれています。

Frameworkには、Node.jsやPython、Java、Rustに加えて、Arduinoも提供してくれていますので、それを今回使います。

mozilla-iot/webthing-arduino
　https://github.com/mozilla-iot/webthing-arduino

MozillaのIoTはどんなものかは、以下を参考にさせていただきました（ありがとうございました！）
　[Mozilla IoT(WoTの実装)をさわってみました](https://qiita.com/uhidekita/items/f3ed0bd9c16174c1df99)

# WebThings Gatewayのインストール

Raspberry PiやLinuxなど、いくつかの方法がありますが、一番簡単なDockerを利用しました。

WebThings Gateway
　https://hub.docker.com/r/mozillaiot/gateway

いったん以下で実行し、

```
  docker run \
      -d \
      -p 8080:8080 \
      -p 4443:4443 \
      -e TZ=Asia/Tokyo \
      -v /path/to/shared/data:/home/node/.mozilla-iot \
      --log-opt max-size=1m \
      --log-opt max-file=10 \
      --name webthings-gateway \
      mozillaiot/gateway:latest
```

上記を実行した後、ネットワークとしてbridgeをやめてhostにするため、以下のファイル(local.json)を編集し、

```/path/to/shared/data/config/local.json
{
  "ports": {
    "https": 8081,
    "http": 8080
  }
}
```

再度実行します。(hostの方がhighly recommendedだそうです)

```
  docker run \
      -d \
      -e TZ=Asia/Tokyo \
      -v /path/to/shared/data:/home/node/.mozilla-iot \
      --network="host" \
      --log-opt max-size=1m \
      --log-opt max-file=10 \
      --name webthings-gateway \
      mozillaiot/gateway:latest
```

ブラウザで以下を開きます。

http://【dockerインスタンスのURL】:8080

とりあえず、choose a secure web address for your gateway. はSkipします。
ユーザ登録は、適当に登録しましょう。次回以降、ブラウザから開くときに、入力したEmailとパスワードを聞かれます。

#ESP32をWebThingsにする

Arduino IDEを使いました。PlatformIOでも大丈夫のようですが、私はだめでした。。。

Arduino IDEで、ライブラリマネージャから以下の2つのライブラリを登録します。

・webthing-arduino
・ArduinoJson

それから、以下のサイトからZIPをダウンロードして、ZIP形式としてライブラリインストールします。

・ESPAsyncWebServer
　https://github.com/me-no-dev/ESPAsyncWebServer

・AsyncTCP
　https://github.com/me-no-dev/AsyncTCP

# ESP32のソースコード

まずはソースコードを載せちゃいます。

```c:Webthing_test.ino
#include <Arduino.h>

#define M5STICKC

#ifdef M5STICKC
#include <M5StickC.h>
#endif

#define ARDUINOJSON_USE_LONG_LONG 1
#define LARGE_JSON_BUFFERS 1
#include <Thing.h>
#include <WebThingAdapter.h>
#include <ArduinoJson.h>

// TODO: Hardcode your wifi credentials here (and keep it private)
const char *ssid = "【WiFiアクセスポイントのSSID】";
const char *password = "【WiFiアクセスポイントのパスワード】";

#if defined(LED_BUILTIN)
const int ledPin = LED_BUILTIN;
#else
const int ledPin = 10; // manually configure LED pin
#endif

#define CPUTEMP_INTERVAL  1000

void LedOnChanged(ThingPropertyValue value);

const char *thingTypes[] = {"OnOffSwitch", "TemperatureSensor", nullptr};
ThingDevice thing("urn:dev:ops:mything", "ESP32 WebThing", thingTypes);
ThingProperty ledOn("on", "", BOOLEAN, "OnOffProperty", LedOnChanged);
ThingProperty cpuTemp("temperature", "", NUMBER, "TemperatureProperty");

ThingActionObject *action_generator(DynamicJsonDocument *);
StaticJsonDocument<256> dispInput;
JsonObject dispInputObj = dispInput.to<JsonObject>();
ThingAction disp("display", "Display", "Display text message", "DisplayAction", &dispInputObj, action_generator);

ThingEvent pushed("pushed", "Button pushed", NUMBER, "PushedEvent");

WebThingAdapter *adapter;

void do_display(const JsonVariant &input) {
  JsonObject inputObj = input.as<JsonObject>();
  const char* text = inputObj["text"];
  Serial.println(text);

  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print(text);  
}

ThingActionObject *action_generator(DynamicJsonDocument *input) {
  return new ThingActionObject("display", input, do_display, nullptr);
}

void LedOnChanged(ThingPropertyValue value){
  boolean on = value.boolean;
  Serial.print("led: ");
  Serial.println(on);
  digitalWrite(ledPin, on ? LOW : HIGH); // active low led
}

void setup(void) {
#ifdef M5STICKC
  M5.begin();
  M5.IMU.Init();
  M5.Axp.ScreenBreath(9);
  M5.Lcd.setRotation(3);
  M5.Lcd.setTextSize(2);

  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print("WebThing\n");
  delay(500);
#endif
  
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);
  Serial.begin(9600);
  Serial.println("");
  Serial.print("Connecting to \"");
  Serial.print(ssid);
  Serial.println("\"");
#if defined(ESP8266) || defined(ESP32)
  WiFi.mode(WIFI_STA);
#endif
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
#ifdef M5STICKC
  M5.Lcd.println(WiFi.localIP());
#endif

  ThingPropertyValue initialOn = {.boolean = digitalRead(ledPin)== LOW ? true : false };
  ledOn.setValue(initialOn);
  thing.addProperty(&ledOn);

  cpuTemp.unit = "degree celsius";
  cpuTemp.readOnly = "true";
  thing.addProperty(&cpuTemp);

  dispInputObj["type"] = "object";
  JsonObject dispInputProperties = dispInputObj.createNestedObject("properties");
  JsonObject textInput = dispInputProperties.createNestedObject("text");
  textInput["type"] = "string";
  thing.addAction(&disp);

  thing.addEvent(&pushed);

  adapter = new WebThingAdapter("my-thing", WiFi.localIP());
  adapter->addDevice(&thing);
  adapter->begin();
  
  Serial.println("HTTP server started");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.print("/things/");
  Serial.println(thing.id);
#ifdef M5STICKC
  M5.Lcd.println(thing.id);
#endif
}

void processCpuTemperature(){
  uint8_t temp = temperatureRead();
  Serial.print("temperature: ");
  Serial.println(temp);

  ThingPropertyValue value;
  value.number = temp;
  cpuTemp.setValue(value);
}

void sendEvent(uint8_t number){
  ThingDataValue val;
  val.number = number;
  ThingEventObject *ev = new ThingEventObject("pushed", NUMBER, val);
  thing.queueEventObject(ev);    
}

long last_tick = millis();

void loop(void) {
#ifdef M5STICKC
  M5.update();
#endif
    
  adapter->update();

  long cur_tick = millis();
  if( (cur_tick - last_tick) >= CPUTEMP_INTERVAL ){
    processCpuTemperature();
    last_tick = cur_tick;
  }
  
#ifdef M5STICKC
  if ( M5.BtnA.wasPressed() ){
    Serial.println("Pushed");
    sendEvent(1);
  }
#endif
}
```


以下の部分は環境に合わせてください。

・【WiFiアクセスポイントのSSID】
・【WiFiアクセスポイントのパスワード】

また、以下のDefineを先頭の方に追加してください。

```
#define ARDUINOJSON_USE_LONG_LONG 1
#define LARGE_JSON_BUFFERS 1
```

#ソースコード解説

ソースコードを解説します。
（とはいっても、各引数の意味はあまりわからずに使っていまして、解説になっていないところが多々あります。。。）

先に知っておくべきこととして、PropertyとActionとEventがあります。

* Propertyは、Thingsデバイスが持っている情報の単位です。今回は、CPU温度とLED点灯状態の2つを設定します。Gatewayは、設定された間隔でThingsのPropertyを取得しに来ます。

* Actionは、Thingsデバイスに対して処理を依頼したい時に呼び出すためのインタフェース定義です。Propertyでも、状態の変更ができますが、あくまで、状態の値の直接操作です。Actionであれば、変更依頼時の入力パラメータの解釈をThings側ででき、Actionが来たことを検知して独自処理を実装することができます。今回は、M5StickCのLCDに文字を表示させてみました。

* Eventは、Things側でのボタン押下などのイベントをGatewayに伝えるためのインタフェース定義です。（今回ボタン押下を実装しましたが、うまく動きませんでした。）

細かな処理は、webthing-arduinoが提供してくれているライブラリがよろしくやってくれていますが、実際には、以下で示す電文を送受信しています。

W3C：Web Thing API
　https://iot.mozilla.org/wot/

〇Thingsの宣言

```
const char *thingTypes[] = {"OnOffSwitch", "TemperatureSensor", nullptr};
ThingDevice thing("urn:dev:ops:mything", "ESP32 WebThing", thingTypes);
```

GatewayのブラウザページからThingsを登録するときに、デフォルトのThingsの表示形式を選択することができます。
このthingTypesで指定したタイプが、選択肢として表示されます。

〇Thingsの起動

```
  adapter = new WebThingAdapter("my-thing", WiFi.localIP());
  adapter->addDevice(&thing);
  adapter->begin();
```

〇Propertyの設定

LED点灯のPropertyの設定はこちら。LedOnChanged というコールバック関数を指定しています。これは、Gateway側からLEDのPropertyの状態変更を受け付けたときをトリガにして、LEDの点灯を変更するためのものです。
初期値も設定しています。

```
ThingProperty ledOn("on", "", BOOLEAN, "OnOffProperty", LedOnChanged);

  ThingPropertyValue initialOn = {.boolean = digitalRead(ledPin)== LOW ? true : false };
  ledOn.setValue(initialOn);
  thing.addProperty(&ledOn);
```

CPU温度のPropertyの設定はこちら。
CPUの温度のPropertyは、loop()の中で1秒おきに更新しています。

```
ThingProperty cpuTemp("temperature", "", NUMBER, "TemperatureProperty");

  cpuTemp.unit = "degree celsius";
  cpuTemp.readOnly = "true";
  thing.addProperty(&cpuTemp);
```

上記を、Thingsの起動前に設定します。

〇Actionの設定

Actionの設定です。ArduinoJsonを存分に使っているようです。
M5StickCのLCDに表示したい文字列としてStringを受け付けるようにしています。

```
ThingActionObject *action_generator(DynamicJsonDocument *);
StaticJsonDocument<256> dispInput;
JsonObject dispInputObj = dispInput.to<JsonObject>();
ThingAction disp("display", "Display", "Display text message", "DisplayAction", &dispInputObj, action_generator);

  dispInputObj["type"] = "object";
  JsonObject dispInputProperties = dispInputObj.createNestedObject("properties");
  JsonObject textInput = dispInputProperties.createNestedObject("text");
  textInput["type"] = "string";
  thing.addAction(&disp);
```

〇Eventの設定

うーん、動きませんでした。なので、解説は省略。

〇loop()の処理

おきまりのloop()の中では、以下を呼び出しています。

```
adapter->update();
```

前述しましたが、1秒おきにCPU温度を更新しています。
（ボタン押下を契機に、Event送信しようとしていますが、うまく動いていません）

ESP32に書き込んで起動させると、以下のように表示されれば成功です。

```
Connecting to "XXXXXXXX"
...............
Connected to XXXXXXXX
IP address: XXX.XXX.XXX.XXX
MDNS responder started
HTTP server started
http://XXX.XXX.XXX.XXX/things/urn:dev:ops:mything
temperature: 52
temperature: 52
temperature: 52 
```

# ブラウザでGatewayにThingを登録する

まずは、アドオンを登録します。
ブラウザからGatewayのページを開き、左上のメニューから環境設定を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/8bc20697-11d0-ffb5-3faa-bcc9fbcde2d5.png)

アドオンを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/bb8cb069-d567-5efb-647c-6e421f48bb6c.png)

そして、表示されたページの右下の「＋」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/e33a4221-db6c-2231-9301-2d9ced23f5ad.png)

この中に、Web Thingがありますので、「＋追加」ボタンを押下して追加します。

戻って、Web Thingの設定変更ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/bb8d9157-e496-1622-39d0-f5bbbeeec005.png)

urlsに、ESP32のURLを指定し、適用ボタンを押下します。例：http://192.168.1.1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/46826f45-d818-6530-a95b-35c54e5f9cac.png)

それでは、さっそく、ESP32のThingを追加します。
戻って、左上のメニューからThingを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/539e3657-8198-57d5-5e90-a74fc2d4bb8b.png)

右下の「＋」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/4369310a-ef97-92a6-576e-b349b65218a2.png)

「ESP32 WebThing」という名前のデバイスが見つかるはずです。
保存ボタンを押下して完了させましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/6e5c1728-c3ca-b3ff-aa04-418a6920690d.png)

電源マークのボタンを押してみてください。ESP32のLEDが付いたり消えたりするかと思います。

次に、右上の雪っぽいマークをクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/2901d5c1-c240-2b9e-b29a-8ab249ea114d.png)

さきほどは、電源マークだけでしたが、スイッチやCPUの温度が表示されています。
真ん中のボタンだけでなく、左下のスイッチでも、LEDの点灯をOn/Offできます。

今度は、左上のDISPLAYボタンを押下してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/91f1afa3-3db9-e7f7-cf1e-a92acab3932c.png)

textを入力するダイアログが表示されました。
適当な文字列を入力し、送信ボタンを押下します。（日本語は文字化けします！）

そうすると、ESP32のLCDに入力した文字列が表示されたかと思います。

以上