enebularを使った勉強会を主催したときの失敗談

[enebular Advent Calendar 2021](https://qiita.com/advent-calendar/2021/enebular)の12日目を担当します、ニアムギです。今回は自分の失敗談を共有したいと思います。

先日初めてのIoTの勉強会をオンラインで主催しました。
経験値は高くないのですが、とりあえずやる気だけで催してみました。
で、案の定上手く進行出来ずグダグダでした・・・参加者の方には申し訳なかったなと反省しています・・・(それでも勉強会終了時に前向きな意見ばかりだったので嬉しかったです)

同じ過ちを繰り返さないよう、備忘録的にどんなことがあったのかまとめたいと思います。

# 勉強会の目的
**「IoTて身近で面白いね！」**と体感してもらうことを目的としまして、
M5StickCでセンサーの値を取得してMQTT送信、Node-REDで受信してSpreadSheetに書き込む といった基本的な流れを最終目標としました。Node-REDは当然[enebular](https://www.enebular.com/ja/)で編集します！
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/1046b6cd-d0b1-9607-a98f-7744d933dcc2.png)

# 開催してみて・・・
説明資料も十分用意して、まっさらな環境でアプリケーションのインストールやコンパイルを試して問題なし！としていたのですが、序盤からつまづいてしまいました。つまづきポイントを以下に載せていきます。

## [shiftr](https://www.shiftr.io/) でデータを受信できない
MQTTのブローカーのひとつである[shiftr](https://www.shiftr.io/)はデータの流れが可視化できて分かりやすいと思い、勉強会に使うことにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/899e2f26-706c-55ba-8e3f-293f4bba2722.png)

グループを作り、インスタンスを作り、M5StickCにトークンを書いて、データを飛ばすと、、、飛ばない。。。
インスタンス名に「大文字は使えない」らしいです。また、インスタンス名にアンダースコア"_"を使うと勝手にハイフン"-"に変換されて気づかないというトラップもありました。
(上が自分でつけたインスタンス名で、下が設定に使うインスタンス名)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/32ac256b-6163-d6be-9002-3f4df2727cc5.png)
調べてもネーミングルールは存在しませんでした。
そういう仕様なのだと思うしかないですね。
勉強会では「インスタンス名をhogehoge-xxxx にしましょう」などと指定したほうが良さそうです。

## M5StickCからSpreadSheetに書き込みできない
Node-REDを使わなくても直接データをSpreadSheetに書き込めるよ！(でもデータの編集とかはNode-REDを介すると便利だよね！)というのを体験してもらいたく、GASを使ったのですが、接続できない。。

原因は凡ミスで、本来GASをデプロイした時に生成されたデプロイIDを入力しなければならないところを、SpreadSheetのIDを入力していまして繋がりませんでした。他の手順と混同してしまったためで、説明資料の振り返りが必要でした。

SpreadSheetのIDではなく、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/f7b5183f-0bb3-570c-278a-ab3c24054cec.png)


こちらのデプロイIDにしなければいけなかった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/f564f907-01da-ab60-d9c7-c274066d3805.png)

ちなみにサンプルソースはこちらです。
※SpreadSheetに"list"・"work"シートを用意しておきます。

``` txt:GASの設定
function doPost(e) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName('list');
  var sheetWork = ss.getSheetByName('work');
  sheetWork.getRange(1,2).setValue('True');
 
  var postjsonString = e.postData.getDataAsString();
  var postdata = JSON.parse(postjsonString);
  console.log(postjsonString);
  
  var baseData = null;
  var date = null;
  var now = null;
  var nowTs = null;
   
  // データ入力
  baseData = postdata.data;
 
  date = new Date();
  now =  Utilities.formatDate(date, 'Asia/Tokyo', 'yyyyMMdd,HHmmss');
  nowTs = date.getTime();

  var values = sheet.getDataRange().getValues();
  values.splice(0, 0, [nowTs, now + "," + baseData]);
  const MAX_SIZE = 20000;
  if(values.length > MAX_SIZE) values = values.splice(0, MAX_SIZE);
  sheet.getRange(1, 1, values.length, values[0].length).setValues(values);

  sheetWork.getRange(1,2).setValue('False');
```

``` Cpp:main.ino 
#include <M5StickC.h>>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "intervalMs.h"
#include "envWifi.h"
#include "env.h"

// Wifi
bool isConnect;

// date
#include <time.h>
#define JST 3600 * 9

// LoopTime
const int LOOPTIME_POST = 10 * 1000;

// Post SpreadSheet
WiFiClientSecure client;
String header = "";

bool Connect()
{
    WiFi.begin(ssid, pw);
    int count = 0;
    M5.Lcd.fillRect(0, 0, M5.Lcd.width(), M5.Lcd.height(), BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("Wi-Fi connection");
    M5.Lcd.println("SSID : " + String(ssid));
    while (count < 30)
    {
        if (WiFi.status() == WL_CONNECTED)
        {
            M5.Lcd.printf("\nConnected!\n");
            M5.Lcd.println(WiFi.localIP());
            delay(1500);
            return true;
        }
        delay(350);
        M5.Lcd.print(".");
        count++;
    }
    M5.Lcd.println("Timed out.");
    return false;
}

void setup()
{
    // M5Stick C
    M5.begin();
    M5.Lcd.setTextColor(YELLOW);
    M5.Lcd.setRotation(3);
    delay(100);

    // Wifi
    isConnect = Connect();

    // date
    configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

    // SpreadSheet
    client.setInsecure();
    // Header
    header = "POST " + url + " HTTP/1.1\r\n";
    header += "Host: " + String(host) + ":443\r\n";
    header += "Content-Type: application/json\r\n";
    header += "Connection: Keep-Alive\r\n";
}

void loop()
{
    if (!isConnect)
    {
        isConnect = Connect();
    }
    if (M5.BtnA.wasPressed())
    {
        esp_restart();
    }

    if (M5.BtnB.wasPressed())
    {
        M5.Axp.PowerOff();
    }
    M5.update();

    // time
    interval<LOOPTIME_POST>::run([]
    {
      if (!isConnect) return;
    
      time_t sendTs = time(NULL);
      struct tm *tm;
      tm = localtime(&sendTs);
      char getTime[8] = "";
      sprintf(getTime, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);

      M5.Lcd.fillRect(0, 0, M5.Lcd.width(), M5.Lcd.height(), BLACK);
      M5.Lcd.setTextColor(ORANGE);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.println("Host : " + String(host));
        
      // upload
      if (!client.connect(host, 443))
      {
        M5.Lcd.println("Connection Failed...");
        return;
      }
        
      String postData = "{\"data\":\"" + String(getTime) + "\"}";
      M5.Lcd.println("Connecting...");
      M5.Lcd.println("Data : " + postData);  
   
      client.print(header);
      client.print("Content-Length: " + String(postData.length()) + "\r\n");
      client.print("\r\n");
      client.print(postData + "\r\n");

      unsigned long timeout = millis();
      while (client.available() == 0)
      {
          if (millis() - timeout > 10000)
          {
              Serial.println(">>> Client Timeout !");
              client.stop();
              return;
          }
      }

      unsigned int cnt = 0;
      while (client.available())
      {
          String line = client.readStringUntil('\r');
          Serial.println(line);
          if(cnt == 0) {
            M5.Lcd.println(line);
          }
          cnt++;
      }

      Serial.println("closing connection");
      client.stop();
      M5.Lcd.println("Closed");     

   });
}
```

``` Cpp:env.h
#ifndef ENV_H
#define ENV_H

// SpreadSheet
const char *host = "script.google.com";

// アクセスするSpreadSheetのURLを設定
// GASをデプロイした時の”デプロイID” を設定
// ex) HogeHogeXXX_tjgoDmI7buD5IBTG4_smrOpm233C15vubeAeh15Qx3oqbagVf-5N3H_OFr2H
// "/macros/s/[ここのIDを書き換え]/exec"

const String url = "/macros/s/***/exec";

#endif
```

``` Cpp:envWifi.h
#ifndef ENV_WIFI_H
#define ENV_WIFI_H

const char *ssid = "xxx";
const char *pw = "xxx";

#endif
```

``` Cpp:intervalMs.h
template <uint32_t time>
class interval
{
  uint32_t next_run = 0;

  template <class T>
  void _run(T func)
  {
    uint32_t now = millis();
    if (next_run < now)
    {
      func();
      next_run = now + time;
    }
  }

  interval() {}

public:
  template <class T>
  static void run(T func)
  {
    static interval<time> instance;
    instance._run(func);
  }
};
```

## enebular でパレットの管理が表示されない
これは予想外でした。パレットの管理はあるものと考えていたのですが、参加者から「表示されません」と。
原因はパソコンにインストールされているNode.jsのバージョン違いによるものでした。
詳しくは[@kitazaki](https://qiita.com/kitazaki)さんの[Raspberry Piでenebular editorをインストールするメモ (※パレットの管理メニューが表示されない問題を解決)](https://qiita.com/kitazaki/items/b4e81812cd50bce304bd)を参照ください。[@kitazaki](https://qiita.com/kitazaki)さんありがとうございます！


## コンパイルすると"「WiFi.h」に対して複数のライブラリが見つかりました" というエラーになる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/b0c7f621-c75a-b963-b170-6648d5f2e6f3.png)

これはWifi.hに問題があるわけではなく、コーディングに問題があるそうです。
参考URLはこちら [ESP32 ( ESP-WROOM-32 , M5Stack )自分的 トラブルシューティング まとめ](https://www.mgo-tec.com/blog-entry-trouble-shooting-esp32-wroom.html/3)
例えば上記の場合、エラーメッセージから分かるように'TOKEN' という変数が定義されていないよ！というものでWifi.hとは無関係です。
しかしながら勉強会中にエラーが出ると慌てますね…何かトラブルが起きたら画面共有する or エラーメッセージを送ってもらう などの初動が大切だと思いました。


# まとめ
色々とトラブルがありながらも付き合ってくれた参加者に感謝しつつ、私も勉強になりました。

* 準備は大切。説明資料も出来る限り充実させる。
* つまづいたことは記事にすることで他の方の参考になること。
* [enebular](https://www.enebular.com/ja/)は勉強会向けのツールとして使いやすい！

今後も[enebular](https://www.enebular.com/ja/)を使った勉強会を度々開いていきたいと思っています。またはまりポイントがあれば記事にしたいです。

何か参考になれば幸いです。
ではでは。

# 参考

[Raspberry Piでenebular editorをインストールするメモ (※パレットの管理メニューが表示されない問題を解決)](https://qiita.com/kitazaki/items/b4e81812cd50bce304bd)
[ESP32 ( ESP-WROOM-32 , M5Stack )自分的 トラブルシューティング まとめ](https://www.mgo-tec.com/blog-entry-trouble-shooting-esp32-wroom.html/3)