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 のようにです。
次の情報も整いました。
- MQTTのトピック名
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秒
esp32_mqtt_pub.ino
#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);//ここを書き換える
}
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をペーストするウインドウが出ますので、そこに次のコードをペーストし、貼り付けます。最後にデプロイボタンを押します。変更したらその都度デプロイボタンを押します。
右上に、テントウ虫のようなアイコンがあるのでクリックします。
そこに、測定周期ごとに数字が現れるはずです。これで、ESP32からの数値を受け取っていることが判ります。
node-red_mqtt_sub.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": ""
}
]
この先、データ蓄積を行いたい場合は、 https://qiita.com/dzonesasaki/items/319173274f0af448b2dc などを参考にしてください。
7. おわりに
細かい説明抜きに、まずはIoTを試してみる、という内容を記載しました。判らなければgoogleるという習慣をつけてください。そして、15分悩んで解決しなければ、誰かに聞くのが良いでしょう。1時間かけて、何も進展が無ければ、それは何かが間違っています。それ以上時間をかけるのは望ましくないかもしれません。
なお、今回は同一のLAN上にあるラズパイに接続する前提でした。Node-redを使っていますので、クラウドにあるNode-redでも同じことができます。そういった意味で、IoTの入り口となる内容だと思います。