MQTT を使って IoT センサーデータを MotionBoard に表示する手順

BI技術者の雑記
今回はIoT センサーデータをMotionBoardに取り込んでみようという企画です。
1.概要
- M5Stick-C(ESP32)を使ってモーションセンサー(MPU6886)とバッテリー電圧を5秒に1回Publishします。
- MQTTブローカーはOracle CloudのAlways Free EditionのVMにインストールします。
- MotionBoardでSubscribeした情報をダッシュボードに表示します。
- M5StickCはArduinoでプログラミングします。
2.構成図
- M5Stick-CからMQTT BrokerへPublish
- MotionBoard CloudからSubscribe
3.完成イメージ
M5Stick-Cを腕に装着して腕の傾きや方向を変えると値が変わります。
4.材料
- MotionBoard IoTエディション
- Oracle Cloud
- M5Stick-C(プログラムの修正が必要になりますが、IMUが搭載されていればM5Stack FireやCore2でも代用できます)
- Arduino
5.手順
①Oracle CloudにVMを用意
VMを用意してUbuntu Linux Minimal 20.04をインストールしてします。
今回はIoTの数が少ないのでAlways Freeの対象となるVMを建てます。
1. Always Free Edition対象のインスタンスを選択
2. 起動イメージをUbuntuに変更
3. ssh公開鍵をアップロード
4. ユーザー「ubuntu」でログイン
5. アップデートを実行
$ sudo apt update $ sudo apt upgrade
②VMにMQTTブローカーをインストール
MQTT通信を仲介してくれるサーバーMosquittoをインストールします。
1.Mosquittoをインストール
$ sudo snap install mosquitto
2.Iotクライアントのユーザーとパスワードを設定
下記を実行してIoTのユーザーを作成します。
$ sudo mosquitto_passwd -c -b /var/snap/mosquitto/common/passwordfile m5stickc01 PasswordForIotClient
3.MotionBoardのユーザーとパスワードを設定
下記を実行してMotionBoardのユーザーを作成します。
$ sudo mosquitto_passwd -b /var/snap/mosquitto/common/passwordfile motionboard PasswordForMotionBoard
4.Mosquittoの設定変更
設定ファイル/var/snap/mosquitto/common/mosquitto.confに下記を追加
・Listner設定を追加
listener 1883 0.0.0.0
・Passwordファイルを指定
password_file /var/snap/mosquitto/common/passwordfile
5.疎通確認(サブスクライブを用意)
$ mosquitto_sub -t 'sensor/axis' -u 'm5stickc01' -P 'PasswordForIoTClient'
6.疎通確認(パブリッシュ)
別ターミナルでログインしてパブリッシュ(MQTTメッセージを送信)し、上記5のターミナルで「Hello.」と表示されれば確認が完了。
$ mosquitto_pub -t 'sensor/axis' -u 'm5stickc01' -P 'PasswordForIoTClient' -m 'Hello.'
7.Oracle Cloud VMのポートを開放(ポート1883)
「Virtual Cloud Network:」→「Subnets」→「Security Lists」をクリックして「Ingress Rules」を追加する
③M5StickCにプログラミング
ArduinoやUSBドライバー、ライブラリのインストール方法は割愛します。
MotionBoardでMQTTメッセージを受け取り、リアルタイム連携機能を利用するにはMotionBoard側で決められたフォーマットのJSONを送信する必要があります。
#include <M5StickC.h> #include <ArduinoJson.h> #include <PubSubClient.h> #include <WiFi.h> #define LCD_BRIGHTNESS 8 #define LED_PIN 10 #define LED_ON 248 #define LED_OFF 256 #define LEDC_CHANNEL 0 #define ACCELEROMETER_THRESHOLD -0.7f #define CHAR_SIZE_X 4 #define CHAR_SIZE_Y 8 // Wi-Fi SSID const int networks = 4; const char* ssid[networks] = {"SSID-1", "SSID-2", "SSID-3","SSID-4"}; const char* password[networks] = {"PassWord1" , "PassWord2", "PassWord3", "PassWord4"}; // NTP const long gmt_offset = 3600 * 9; // JST-9 const int daylight = 3600 * 0; // No daylight time const char* ntp_server = "pool.ntp.org"; // MQTT const char* MQTTT_ENDPOINT = "Address for Oracle Cloud VM"; const int MQTT_PORT = 1883; const char* ClientID = "m5stickc"; const char* MQTT_TOPIC = "sensor/axis"; const int MQTT_CONNECT_RETRY = 1; const char* MQTT_USER = "m5stickc01"; const char* MQTT_PASS = "PasswordForIoTClient"; // MQTT MotionBoard Template const int MQTT_BUF_SIZ = 1024; const int MQTT_DOC_SIZ = 2048; const char* MQTT_MB_LOGINID = "bi-sol@local"; const char* MQTT_MB_TEMPLATENAME = "Acceleration"; const char* MQTT_MB_LATITUDE = "35.4462079"; const char* MQTT_MB_LONGITUDE = "139.6489123"; WiFiClient httpsClient; PubSubClient mqttClient(httpsClient); bool ConnectWiFi() { if (WiFi.status() == WL_CONNECTED) { return true; } M5.Lcd.fillScreen(0); M5.Lcd.setCursor(0, 0); //connect to WiFi for(int j = 0; j < networks; j++) { Serial.printf("nConnecting to %s ", ssid[j]); M5.Lcd.printf("nConnecting to %s ", ssid[j]); WiFi.begin(ssid[j], password[j]); delay(500); for(int i = 0; i < 9; i++) { if (WiFi.status() == WL_CONNECTED) { Serial.println(" CONNECTED"); M5.Lcd.println(" CONNECTED"); break; } delay(500); Serial.print("."); M5.Lcd.print("."); } if (WiFi.status() != WL_CONNECTED) { Serial.println(" can't CONNECT!"); M5.Lcd.println(" can't CONNECT!"); WiFi.disconnect(true); WiFi.mode(WIFI_OFF); } else { return true; } } return false; } bool ConnectMqtt() { if ( !ConnectWiFi() ) { return false; } if(mqttClient.connected()) { return true; } mqttClient.setServer(MQTTT_ENDPOINT, MQTT_PORT); mqttClient.setBufferSize(MQTT_BUF_SIZ); for(int i = 0; i < MQTT_CONNECT_RETRY; i++) { if(mqttClient.connected()) { Serial.printf("MQTT Connected. Endpooint:%s User:%sn", MQTTT_ENDPOINT, MQTT_USER); M5.Lcd.println("MQTT Connected."); return true; } if (!mqttClient.connect(ClientID, MQTT_USER, MQTT_PASS)) { //if (!mqttClient.connect(ClientID)) { Serial.printf("Error: MQTT state=%dn", mqttClient.state()); M5.Lcd.printf("Error: MQTT state=%dn", mqttClient.state()); delay(1000); } } return false; } void setup() { M5.begin(true, true, true); M5.Axp.ScreenBreath(LCD_BRIGHTNESS); M5.Lcd.setRotation(1); M5.Lcd.setTextColor(0xC618, 0x0000); Serial.begin(115200); Serial.println("M5StickC_axis_mqtt"); M5.Lcd.println("M5StickC_axis_mqtt"); if (!ConnectWiFi()) { return; } configTime(gmt_offset, daylight, ntp_server); M5.MPU6886.Init(); ledcSetup(LEDC_CHANNEL, 12800, 8); ledcAttachPin(LED_PIN, LEDC_CHANNEL); ledcWrite(LEDC_CHANNEL, LED_OFF); if(!ConnectMqtt()) { return; } } void SendMqttAxisJson() { int x = 0; int y = 0; float accX = 0; float accY = 0; float accZ = 0; float gyroX = 0; float gyroY = 0; float gyroZ = 0; M5.MPU6886.getAccelData(&accX,&accY,&accZ); M5.MPU6886.getGyroData(&gyroX,&gyroY,&gyroZ); float temp = M5.Axp.GetTempInAXP192(); float vbat = M5.Axp.GetBatVoltage();; struct tm td; char buffer[MQTT_BUF_SIZ]; while (!getLocalTime(&td)) { Serial.println("Error: NTP"); M5.Lcd.fillScreen(0); M5.Lcd.println("Error: NTP"); delay(1000); } char now[26]; strftime(now, sizeof(now), "%Y/%m/%d %H:%M:%S", &td); M5.Lcd.setCursor(0, 0); M5.Lcd.printf(" %s ", now); M5.Lcd.setCursor(0, 1 * CHAR_SIZE_Y); M5.Lcd.printf(" Temperature : %6.3f c ", temp); M5.Lcd.setCursor(0, 2 * CHAR_SIZE_Y); M5.Lcd.printf(" VBat : %6.3f v ", vbat); M5.Lcd.setCursor(0, 3 * CHAR_SIZE_Y); M5.Lcd.print(" Accel Gryo "); M5.Lcd.setCursor(0, 4 * CHAR_SIZE_Y); M5.Lcd.print(" X n Y n Z "); M5.Lcd.setCursor(3 * CHAR_SIZE_X, 4 * CHAR_SIZE_Y); M5.Lcd.printf("%8.2f %8.2f", accX * 1000, gyroX); M5.Lcd.setCursor(3 * CHAR_SIZE_X, 5 * CHAR_SIZE_Y); M5.Lcd.printf("%8.2f %8.2f", accY * 1000, gyroY); M5.Lcd.setCursor(3 * CHAR_SIZE_X, 6 * CHAR_SIZE_Y); M5.Lcd.printf("%8.2f %8.2f", accZ * 1000, gyroZ); if ( accY < ACCELEROMETER_THRESHOLD ) { ledcWrite(0, LED_ON); } else { ledcWrite(0, LED_OFF); } DynamicJsonDocument doc(MQTT_DOC_SIZ); JsonObject root = doc.to<JsonObject>(); root["loginId"] = MQTT_MB_LOGINID; root["template"] = MQTT_MB_TEMPLATENAME; JsonArray locationsarray = root.createNestedArray("locations"); JsonObject locationsobject = locationsarray.createNestedObject(); sprintf(now, "%d000", mktime(&td)); locationsobject["time"] = String(now); locationsobject["uptime"] = String(now); locationsobject["lat"] = MQTT_MB_LATITUDE; locationsobject["lon"] = MQTT_MB_LONGITUDE; JsonArray statusarray = root.createNestedArray("status"); JsonObject statusobject = statusarray.createNestedObject(); statusobject["time"] = String(now); JsonArray values = statusobject.createNestedArray("values"); JsonObject value = values.createNestedObject(); value["name"] = "AccelerationX"; value["type"] = "3"; sprintf(now, "%.3f", accX); value["value"] = String(now); value = values.createNestedObject(); value["name"] = "AccelerationY"; value["type"] = "3"; sprintf(now, "%.3f", accY); value["value"] = String(now); value = values.createNestedObject(); value["name"] = "AccelerationZ"; value["type"] = "3"; sprintf(now, "%.3f", accZ); value["value"] = String(now); value = values.createNestedObject(); value["name"] = "GyroX"; value["type"] = "3"; sprintf(now, "%.3f", gyroX); value["value"] = String(now); value = values.createNestedObject(); value["name"] = "GyroY"; value["type"] = "3"; sprintf(now, "%.3f", gyroY); value["value"] = String(now); value = values.createNestedObject(); value["name"] = "GyroZ"; value["type"] = "3"; sprintf(now, "%.3f", gyroZ); value["value"] = String(now); value = values.createNestedObject(); value["name"] = "VBat"; value["type"] = "3"; sprintf(now, "%.4f", vbat); value["value"] = String(now); serializeJson(root, buffer, sizeof(buffer)); root.clear(); doc.shrinkToFit(); if (ConnectMqtt()) { mqttClient.publish(MQTT_TOPIC, buffer); Serial.printf("Send Mqtt! (%s)n", MQTTT_ENDPOINT); M5.Lcd.setCursor(1 * CHAR_SIZE_X, 7 * CHAR_SIZE_Y); M5.Lcd.printf("MQTT Connected. n (%s)", MQTTT_ENDPOINT); } else { Serial.printf("MQTT Not connected. (%s)n", MQTTT_ENDPOINT); M5.Lcd.setCursor(1 * CHAR_SIZE_X, 7 * CHAR_SIZE_Y); M5.Lcd.printf("MQTT Not connected.n (%s)", MQTTT_ENDPOINT); } Serial.print("json : "); Serial.println(buffer); } void loop() { SendMqttAxisJson(); delay(5000); }
ArduinoJSONを使っている位で特別な内容はないかと思います。
MQTTのメッセージが大きくなる場合はbuff変数だけでなく.setBufferSize() でバッファサイズを大きくしておく必要があります。
参考ページ
④MotionBoardにMQTT Subscribeを設定
MotionBoardでMQTTメッセージを受け取れるように設定します。
1. リアルタイム連携の設定
- 右上の設定アイコンからリアルタイム連携の設定画面を開きます。
「システム管理」→「接続/認証」→「リアルタイム連携」 - 「MQTT:利用する」チェックボックスを有効にします。
- URLを設定します。
SSLを利用する場合は「ssl://~」、利用しない場合は「tcp://~」と設定します。 - サブスクライブするトピックを指定します。
「#」や「-」のワイルドカードも利用できます。 - ブローカーで設定したユーザー/パスワードを設定します。
「ステータス」タブの設定
「ステータス」タブでIoTから受け取る値の受け取り方を設定します。
- 「新規作成」ボタンを押下します。
- 「ステータス名」にはJSONで送られるステータスの名前(”name”で設定された値)を指定します。
- 「MB IoT Agent」は未設定のままにしておきます。
- データ型を設定して「OK」ボタンを押下します。
⑤ダッシュボード作成
- データソースは「ClientStatus」と「StatusHistory」を利用
- 加速度、ジャイロ、バッテリー電圧は「ClientStatus」から取得
バッテリー電圧グラフは「StatusHistory」から取得 - 自動リフレッシュのトグルボタンを用意
選択時アクションと非選択時アクションは下図の通り
6.おわりに
すでに稼働していてMQTTを利用している製品もあるかと思います。
その場合は、MotionBoard側でプラグインを作成して各センサーデータを取得する必要があるかと思います。
今回はIoT側のプログラムを作るのでJSONフォーマットはMotionBoardに合わせる形でコーディングしました。