M5StickCの書き換えが面倒だったので、Node.jsでArduinoっぽくしてみた



M5StickCのように、ESP32にGroveが付いているのが多いで、いろんな周辺デバイスをいじりたいのですが、そのたびに、リコンパイル＆書き込みをするのはすごく面倒で時間がかかるので、MQTTを使ってリモートから操作できるようにして、クライアント側はNode.jsでたたけるようにしました。

M5StickC側では、I2C、Serial、Lcd、Gpioを操作できるようにしましたので、一度バイナリを書き込んでしまえばあとは、クライアント側のNode.jsで周辺デバイスのドライバを書くことができます。
ですが、動作はそこまで速くない（特にLcd描画系）ので、お遊び程度に思ってください。

で、Node.jsになって開発しやすくなったので、手元にあったI2Cデバイスを一気に実装しました。

・M5Stack ENVユニット
　https://www.switch-science.com/catalog/5690/
・M5Stack TVOC/eCO2ユニット
　https://www.switch-science.com/catalog/6619/
・Grove OLED Display 0.96”
　https://www.switch-science.com/catalog/829/
・Grove Digital Light Sensor
　https://www.switch-science.com/catalog/1174/

以下のGitHubに上げておきました。

poruruba/RemoteArduino
　https://github.com/poruruba/RemoteArduino

#MQTTトピック名

2つのMQTTトピックを使います。
1つは、Node.js側からM5StickCに要求するコマンド送信のためのトピック
もう一つは、M5StickCから処理結果をNode.jsに返すためのトピック

とりあえず、
前者は、m5lite/cmd【M5StickCのMacアドレスの16進数表記】、
後者は、m5lite/rsp【M5StickCのMacアドレスの16進数表記】
にしています。

# Arduino側

以下のライブラリを使わせていただいています。

tanakamasayuki/ESP32LitePack
　https://github.com/tanakamasayuki/ESP32LitePack

もちろん、それに含まれる以下も利用させていただきました。
lovyan03/LovyanGFX
　https://github.com/lovyan03/LovyanGFX

これらのおかげで、描画系が整備され、マルチデバイスで動くようになりましたっ！！

また、以下も利用させていただいています。

knolleary/pubsubclient
　https://github.com/knolleary/pubsubclient

ArduinoJson
　https://arduinojson.org/

##MQTTで受信されるデータフォーマット

こんなJSONがM5StickCに届きます。

```
{
　“client_id”: “【クライアントID】”,
　“tx_id”: 【トランザクションID】,
　“device_type”: “【デバイスタイプ】”,
　“cmd”: “【コマンド名】”,
　“params”: {
　 “param1”: 【1番目のパラメータ】,
　 “param2”: 【2番目のパラメータ】,
　 “param3”: 【3番目のパラメータ】,
　 “param4”: 【4番目のパラメータ】,
　 “param5”: 【5番目のパラメータ】,
　 “param6”: 【6番目のパラメータ】,
　 “param7”: 【7番目のパラメータ】
　}
}
```

クライアントIDとトランザクションIDは、送信側（Node.js側）が自由に付けて送ります。M5StickC側はそのまま同じ値をレスポンスに返します。トランザクションIDを毎回インクリメントすれば、送ったレスポンスかどうかがわかることになります。
デバイスタイプは今のところ以下の4種類です。

* Serial
* Gpio
* Lcd
* Wire、Wire1

コマンド名は、デバイスタイプごとにサポートする名前が異なります。
整理する意味でざっと上げてみました。

|デバイス名|コマンド名|
|:--|:--|
|Serial|begin|
|^|end|
|^|available|
|^|read|
|^|peek|
|^|flush|
|^|print|
|^|println|
|^|write|
|^|write_str|
|^|write_buf|
|Gpio|pinMode|
|^|digitalWrite|
|^|digitalRead|
|^|analogRead|
|^|analogReadResolution|
|Wire|begin|
|^|requestFrom|
|^|beginTransmission|
|^|endTransmission|
|^|write|
|^|write_str|
|^|write_buf|
|^|available|
|^|read|
|^|read_buf|
|Lcd|setRotation|
|^|setTextColor|
|^|setBrightness|
|^|drawPixel|
|^|drawLine|
|^|drawRect|
|^|fillRect|
|^|fillScreen|
|^|drawTriangle|
|^|fillTriangle|
|^|drawCircle|
|^|fillCircle|
|^|drawEllipse|
|^|fillEllipse|
|^|drawBmpData|
|^|getRange|

およそArduinoに合わせてあるので、イメージしやすいかと思います。
M5StickCでは、受け取ったMQTTパケットを解析し、ArduinoのAPI呼び出しに渡す処理をしているだけです。
処理結果は、MQTTトピックにPublishして戻しています。

レスポンスのフォーマットは以下の通りです。

```
{
　“client_id”: “【クライアントID】”,
　“tx_id”: 【トランザクションID】,
　“device_type”: “【デバイスタイプ】”,
　“rsp”: “【コマンド名】”,
　“status”: “【処理結果】”,
　“params”: {
　　“param1”: 【1番目のパラメータ】,
　　“param2”: 【2番目のパラメータ】,
　　“param3”: 【3番目のパラメータ】,
　　“param4”: 【4番目のパラメータ】,
　　“param5”: 【5番目のパラメータ】,
　　“param6”: 【6番目のパラメータ】,
　　“param7”: 【7番目のパラメータ】
　}
}
```

cmdがrspに代わっただけです。
クライアントIDとトランザクションIDは、コマンドにあったものをそのまま返しています。
処理結果には、”OK”か”NG”が入ります。NGの場合には、reasonも一緒に理由が入って帰ります。
paramsは処理結果です。デバイスタイプとコマンド名で決まる処理内容によって異なります。

複雑な処理はしておらず、ただただ、各コマンドをArduinoのAPIに変換する処理をえんえんと記述しています。

#Node.js側

以下のファイル構成になっています。

arduino.js
　これがメインとなるクラスです。これをrequireすると、Serial、Gpio、Lcd、WireおよびWire1が一緒にインスタンス化されます。

arduino_device/Gpio.js
　Gpioのコマンド送信のためのクラスです。

arduino_device/Serial.js
　Serialのコマンド送信のためのクラスです。

arduino_device/Lcd.js
　Lcdのコマンド送信のためのクラスです。

arduino_device/Wire.js
　Wireのコマンド送信のためのクラスです。
　周辺デバイスはI2Cデバイスが多いので、こちらを多用しました。

#使い方(Node.js側)

まずは、メインとなるArduinoクラスをインスタンス化します。

```js:index.js
const Arduino = require('./arduino');
const arduino = new Arduino(MQTT_CLIENT_ID, MQTT_HOST_URL, MQTT_TOPIC_CMD, MQTT_TOPIC_RSP);
```


その際に、接続するMQTTのクライアントID、MQTTブローカのURL、接続するMQTTトピック名の送信用と受信用を指定します。トピック名は、接続するM5StickCの設定に合わせます。

MQTTブローカのURLは以下のような感じに指定します。
　tcp://【MQTTブローカのホスト名】:1883

そして、以下のようにしてMQTTに接続し、内部でLcdの画面サイズを取得しています。

```js:index.js
　await arduino.connect();
```

あとは、Wireを使いたい場合は、arduino.Wireにあります。

```js:index.js
  var wire = arduino.Wire;
  await wire.begin();
```

#周辺デバイスの制御

手持ちにある、I2Cデバイスの周辺デバイスを一通り実装しました。
I2Cアドレスが違うのであれば、I2Cハブを介して同時に接続できるかと思います。以下では4つ同時につないでいます。SSD 1308のOLED Displayは少々遅いですが、それ以外はあまり遅さは気になりませんかね。

・M5Stack ENVユニット
　　device/DHT12.js
　　device/BME280.js
・M5Stack TVOC/eCO2ユニット
　　device/SGC30.js
・Grove OLED Display 0.96”
　　device/SSD1308.js
・Grove Digital Light Sensor
　　device/TSL2561.js

M5StackやAdafruitやseeedが提供するサンプルを参考にさせていただきました。っていうより、ほぼそのままNode.jsにポーティングです。
以下のようにして使います。

```js:index.js
const SGC30 = require('./device/SGC30');
const DHT12 = require('./device/DHT12');
const TSL2561 = require('./device/TSL2561');
const BME280 = require('./device/BME280');
const SSD1308 = require('./device/SSD1308');

  var bme280 = new BME280(wire);
  await bme280.begin();
  var ret = await bme280.readTemperature();
  console.log(ret);
  var ret = await bme280.readPressure();
  console.log(ret);

  var dht12 = new DHT12(wire);
  var ret = await dht12.readTemperature();
  console.log(ret);
  var ret = await dht12.readHumidity();
  console.log(ret);

  var tsl2561 = new TSL2561(wire);
  await tsl2561.init();
  var ret = await tsl2561.readVisibleLux();
  console.log(ret);

  var sgc30 = new SGC30(wire);
  var ret = await sgc30.begin();
  console.log(ret);
  await sgc30.IAQmeasure();
  console.log(sgc30.TVOC);
  console.log(sgc30.eCO2);

  var ssd1308 = new SSD1308(wire);
  await ssd1308.init();
  await ssd1308.clear();
  await ssd1308.put_pixel(1, 1, true);
  await ssd1308.update();
```

#終わりに

自分はNode.jsに慣れているので、周辺デバイスのドライバ作成のデバッグが非常にはかどりました。修正・再実行が一瞬(数秒)なので。

ただ、動いたっぽく見えただけなので、いろいろバグがあるかもしれません。
さらに、I2C以外は動作確認していません。ほんと機械的な実装なので。。。

以上