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.構成図

be11_01.png

  1. M5Stick-CからMQTT BrokerへPublish
  2. MotionBoard CloudからSubscribe

3.完成イメージ

be11_02.png

M5Stick-Cを腕に装着して腕の傾きや方向を変えると値が変わります。

be11_03.jpg

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対象のインスタンスを選択

be11_04.png

2. 起動イメージをUbuntuに変更

be11_05.png

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」を追加する

be11_06.png

③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. リアルタイム連携の設定

be11_07.png

  1. 右上の設定アイコンからリアルタイム連携の設定画面を開きます。
    「システム管理」→「接続/認証」→「リアルタイム連携」
  2. 「MQTT:利用する」チェックボックスを有効にします。
  3. URLを設定します。
    SSLを利用する場合は「ssl://~」、利用しない場合は「tcp://~」と設定します。
  4. サブスクライブするトピックを指定します。
    「#」や「-」のワイルドカードも利用できます。
  5. ブローカーで設定したユーザー/パスワードを設定します。

「ステータス」タブの設定

「ステータス」タブでIoTから受け取る値の受け取り方を設定します。

be11_08.png

be11_09.png

  1. 「新規作成」ボタンを押下します。
  2. 「ステータス名」にはJSONで送られるステータスの名前(”name”で設定された値)を指定します。
  3. 「MB IoT Agent」は未設定のままにしておきます。
  4. データ型を設定して「OK」ボタンを押下します。

⑤ダッシュボード作成

  1. データソースは「ClientStatus」と「StatusHistory」を利用be11_10.png
  2. 加速度、ジャイロ、バッテリー電圧は「ClientStatus」から取得
    バッテリー電圧グラフは「StatusHistory」から取得
  3. 自動リフレッシュのトグルボタンを用意
    選択時アクションと非選択時アクションは下図の通りbe11_11.pngbe11_12.png

6.おわりに

すでに稼働していてMQTTを利用している製品もあるかと思います。
その場合は、MotionBoard側でプラグインを作成して各センサーデータを取得する必要があるかと思います。
今回はIoT側のプログラムを作るのでJSONフォーマットはMotionBoardに合わせる形でコーディングしました。

BI_banner01.png