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に合わせる形でコーディングしました。
