初めてのIoTをESP32とMQTTで。2020年度版

# 1. はじめに
 
「職場改善活動しろと言われた。みんなで話し合ったら、IoTをやってみようという話になった。ESP32は購入してみたけど、どうやってIoTをしたら良いの？」という方向けです。
厳密な意味でのIoTは置いておいて、「とにかく、ESP32で、Wi-Fiを使って、情報を転送し、蓄積したい」人向けです。

# 2. 参考図書
今回の内容を説明すると、よく「これらの内容がまとまった本、ありますか？」と聞かれます。私が把握している限り、同じ内容のものは無いと思います、今のところ。

ただ、参考になる本は多いです。それぞれ、プログラム例が載っており、すぐに取りかかるのに丁度良い内容です。これらの本では、クラウドに上げるというところまでを扱うため、利用するサービスは、AmbientやAWS,Azureなどを挙げています。個人で行うには、とても便利なサービスですが、職場で行うには極めてハードルが高くなります。それは、データを外部に送信するため決裁が必要になるためです。

今回のような「なんちゃってIoT」について、全てを網羅する本がまだ無いという状況です。そもそも、IoTじゃないという点で、本にしづらいのかもしれません。

- 下島 健彦、IoT開発スタートブック　── ESP32でクラウドにつなげる電子工作をはじめよう！、技術評論社、2019/8/13
- 藤本 壱、ESP32&Arduino 電子工作 プログラミング入門、技術評論社、2020.4/15

# 3. 準備するもの
- ESP32
- ESP32のプログラム開発環境
- ラズパイ(or MQTTブローカ)
- Wi-FiのSSID、パスフレーズ（パスワード）
- サーバのIPアドレス

ものが準備されてない場合は、まずは、 https://qiita.com/dzonesasaki/items/3e2bedfd03e435b52834 などを見て、購入などを済ませましょう。

# 4. サーバとWi-Fiの準備

サーバはラズパイを前提に記載します。それ以外の場合はそれぞれに合わせて読み替えて下さい。ラズパイを社内のLANに接続します。有線か無線どちらかで接続します。Wi-Fi のSSIDとパスフレーズは、管理者に聞きましょう。

ラズパイにmosquittoとnode-redとinfluxdb v1などセッティングしてください。https://qiita.com/dzonesasaki/items/ab904d70fa4b3d4ce7ce などを参考にしてください。

次にラズパイのIPアドレスの固定化については、 https://qiita.com/dzonesasaki/items/7942b13ab9a799f652ff などを参考にしてください。 ここまでで、次の情報が整っているはずです。

- Wi-FiのSSID
- Wi-Fiのパスフレーズ（パスワード）
- サーバ（ラズパイ）のIPアドレス

あとは、MQTTのトピック名を決めましょう。
仮置きするなら、myTopicなどで良いと思いますが、何の情報を集取するのかを決めてその名称にします。

例えば、NCの1番の電源ON/OFFを情報として取得する場合は、ncno1onoff のようにです。
次の情報も整いました。

- ＭＱＴＴのトピック名

# 5. ESP32のプログラムをしましょう

ここでは、Arduino-IDEでESP32のコードを開発することを前提にします。
次のプログラムをコピー＆ペースト使います。ただし、次の情報を自分で書き換えて下さい。ここでは仮のものを入力しています。

- Wi-FiのSSID : SSID
- Wi-Fiのパスフレーズ（パスワード）:PASSWORD
- サーバ（ラズパイ）のIPアドレス : 192.168.1.1
- MQTTのトピック名 : myTopic
- MQTTクライアントID : myMqttSub
- 入力ピン : 26番
- 測定周期 : 2秒 (=2000ミリ秒)
- ウオッチドッグタイマー : 6x10秒

<details><summary>esp32_mqtt_pub.ino</summary><div>

```c++
#include <WiFi.h>
#include <PubSubClient.h> // https://github.com/knolleary/pubsubclient

char *ssidC="SSID";//ここを書き換える
char *passC="PASSWORD";//ここを書き換える
char* servC="192.168.1.1";//ここを書き換える

const char* topic = "myTopic";//ここを書き換える
const String clientId = "myMqttPub" ;//ここを書き換える

#define PIN_IN 26 //ここを書き換える

WiFiClient myWifiClient;
const int mqttPort = 1883;
PubSubClient mqttClient(myWifiClient);

uint32_t gu32Count=0;

#define NUM_1SEC_MICROSEC (1000000)
#define NUM_SEC_TIMER (10)
#define NUM_TIMER_MICROSEC (NUM_SEC_TIMER*NUM_1SEC_MICROSEC)
hw_timer_t * handleTimer = NULL; 

portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 

volatile int gviCountWatchDog=0;
#define NUM_MAX_WATCHDOG (6) // 60sec //ここを書き換える


void IRAM_ATTR irq_Timer01()
{
  portENTER_CRITICAL_ISR(&timerMux);
  gviCountWatchDog++;
  if (gviCountWatchDog > NUM_MAX_WATCHDOG)
  {
    selfRestartProcess();
  }

  portEXIT_CRITICAL_ISR(&timerMux);
}//irq_Timer01()

void selfRestartProcess(void)
{
  //memory copy to flash
  ESP.restart(); // reboot
}//selfRestartProcess()

void init_TimerInterrupt()
{
  gviCountWatchDog =0;
  handleTimer = timerBegin(0, 80, true); // Num_timer ,  counter per clock 
  timerAttachInterrupt(handleTimer, &irq_Timer01, true);
  timerAlarmWrite(handleTimer, NUM_TIMER_MICROSEC, true); //[us] per 80 clock @ 80MHz
  timerAlarmEnable(handleTimer);
}//init_TimerInterrupt()

void initWifiClient(void){
  Serial.print("Connecting to ");
  Serial.println(ssidC);
  uint16_t tmpCount =0;
  
  WiFi.begin( ssidC, passC);
  while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
  tmpCount++;
  if(tmpCount>128)
  {
    //gFlagWiFiConnectFailure = true;
    Serial.println("failed  ");
    return;
  }
}
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void connectMqtt()
{
  mqttClient.setServer(servC, mqttPort);
  while( ! mqttClient.connected() )
  {
    Serial.println("Connecting to MQTT Broker...");
    //String clientId = "myMqttPub" ;
    if ( mqttClient.connect(clientId.c_str()) )
    {
      Serial.println("connected"); 
    }
  }
}

void doPubMqtt(char * cValue)
{
  if(!mqttClient.connected())
  { 
    if (mqttClient.connect(clientId.c_str()) )
    {
      Serial.println("reconnected to MQTT broker"); 
    }
    else
    {
      Serial.println("failure to reconnect to MQTT broker");
      return;
    }
  }
  //mqttClient.loop();
  boolean bFlagSucceed=mqttClient.publish(topic,cValue);
  //free(cValue);
  if(bFlagSucceed)
   {
    Serial.print("done: ");
    Serial.println(cValue);
  }
  else
  {
    Serial.println("failure");
  // run to check: mosquitto_sub -t myTopic
  }
}

void setup() {
  Serial.begin(115200);
  gviCountWatchDog=0; 
  init_TimerInterrupt();
  initWifiClient();
  connectMqtt();
  pinMode(PIN_IN,INPUT);

}

void loop(){
 Serial.println("start loop");
  portENTER_CRITICAL(&timerMux);
    gviCountWatchDog=0; //clear Counter
  portEXIT_CRITICAL(&timerMux);

  float fSensValue=analogRead(PIN_IN);

  //char cValue[11];
  char *cValue;
  cValue = (char *)malloc(12);
  sprintf(cValue, "%1.5e", fSensValue);

  doPubMqtt(cValue);
  free(cValue);
  
  delay(2000);//ここを書き換える

}
```

</div></details>

Arduino-IDEを起動して、ファイル → 新規作成をクリックします。
初めに出てくる内容を全て削除して、上のコードをコピーし、Arduino-IDE上にペースト します。
そして、自分の状態に合わせて、上の書き換える部分を修正しましょう。

測定周期の変更は、最後の```delay(2000);```にある2000を書き換えます。

聞き慣れないウオッチドッグという単語があります。
これは、プログラムが途中で予期しない停止があった場合に、ESP32自身が自分をリセットするための機能(プログラム)です。
たとえば、測定周期を10秒にした場合は、ウオッチドッグタイマーは60秒のままで良いと思いますが、測定周期が60秒の場合は、ウオッチドッグタイマーは3分などにした方が良いでしょう。そうしないと、測定せずに自己リセットされてしまいます。

書き換えたらESP32に書き込みます。
その前に、何に書き込むのかを設定します。

使っているのがESP32開発ボードの場合は、

ツール → ボード:" @@@@@@ " → ESP32 Arduino → ESP32 Dev Module 

にします。@@@@は、前回の内容が入ってます。初期の場合は、Arduino-Unoになっていると思います。

使っているのがM5StickCやM5StickC-plusの場合は、

ツール → ボード:" @@@@@@ " → ESP32 Arduino → M5Stick-C

にします。
あとは、

ツール → シリアルポート → COM数字

にします。使っている状況で、COMの後ろの数字が異なります。書き込みでエラーが出たら、別の数字のものにします。

数字を確認する場合、ESP32とPCをUSBケーブルで接続する前に、ツール → シリアルポートで内容を確認し、USBケーブルを接続した時に、ツール → シリアルポートで増えたものが、ESP32であることが判ります。

これらが終わったら、 スケッチ → マイコンボードに書き込む をクリックします。



# 6. サーバ側のプログラムをしましょう

ここでは、サーバをラズパイとします。ラズベリーのスタートボタンから、プログラム → node-redをクリックすると、node-redが起動します。

node-redの右上に 三 のようなボタンを押して、「読み込み」をクリックすると、jsonをペーストするウインドウが出ますので、そこに次のコードをペーストし、貼り付けます。最後にデプロイボタンを押します。変更したらその都度デプロイボタンを押します。

![node-red_read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/9d183040-d296-3919-4c37-d36799929fd8.png)

右上に、テントウ虫のようなアイコンがあるのでクリックします。
そこに、測定周期ごとに数字が現れるはずです。これで、ESP32からの数値を受け取っていることが判ります。


<details><summary>node-red_mqtt_sub.json</summary><div>


```json
[
    {
        "id": "9b3687a0.66a128",
        "type": "mqtt in",
        "z": "f61cf7aa.e627d",
        "name": "",
        "topic": "myTopic",
        "qos": "2",
        "datatype": "auto",
        "broker": "4e38b730.53a58",
        "x": 140,
        "y": 100,
        "wires": [
            [
                "53dd59d2.d3dad8",
                "9b3f417c.243948"
            ]
        ]
    },
    {
        "id": "212a6d2.4d54f92",
        "type": "debug",
        "z": "f61cf7aa.e627d",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 100,
        "wires": []
    },
    {
        "id": "53dd59d2.d3dad8",
        "type": "csv",
        "z": "f61cf7aa.e627d",
        "name": "",
        "sep": ",",
        "hdrin": "",
        "hdrout": "none",
        "multi": "one",
        "ret": "\\n",
        "temp": "",
        "skip": "0",
        "strings": true,
        "include_empty_strings": "",
        "include_null_values": "",
        "x": 310,
        "y": 100,
        "wires": [
            [
                "391eec55.13d354"
            ]
        ]
    },
    {
        "id": "391eec55.13d354",
        "type": "function",
        "z": "f61cf7aa.e627d",
        "name": "",
        "func": "var Sens=msg.payload.col1;\nvar myValues = {Sens:Sens}\nmsg.payload=myValues\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 480,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "9b3f417c.243948",
        "type": "debug",
        "z": "f61cf7aa.e627d",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 340,
        "y": 160,
        "wires": []
    },
    {
        "id": "4e38b730.53a58",
        "type": "mqtt-broker",
        "name": "",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": false,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    }
]
```

</div></details>

この先、データ蓄積を行いたい場合は、 https://qiita.com/dzonesasaki/items/319173274f0af448b2dc などを参考にしてください。


# 7. おわりに
細かい説明抜きに、まずはIoTを試してみる、という内容を記載しました。判らなければgoogleるという習慣をつけてください。そして、15分悩んで解決しなければ、誰かに聞くのが良いでしょう。1時間かけて、何も進展が無ければ、それは何かが間違っています。それ以上時間をかけるのは望ましくないかもしれません。
なお、今回は同一のLAN上にあるラズパイに接続する前提でした。Node-redを使っていますので、クラウドにあるNode-redでも同じことができます。そういった意味で、IoTの入り口となる内容だと思います。