M5StackとNode-REDをMQTTで連携するメモ

M5StackとNode-REDをMQTTで連携するメモです。

明星和楽2018でIoT&MixedReality展示した記事で使っていたものを今後も自分で使いやすくするため、まとめた形です。

修正ポイント 2018/07/19

M5StackのLCDディスプレイの色をRGBで指定する。 – Qiita

こちらの記事を見て、カラー指定が間違っていたことに気づいたのでソースコードを修正しています。

今回の仕組み

展示でも「扱いやすかったなー!」と思った部分をメインに、M5StackとNode-REDをMQTTでつなげてディスプレイ操作指示をする流れです。

image

たとえば、メッセージで文言は「RED」、背景色は R 255 , G 0 , B 0 (赤色)と指示すれば、図のように反映されます。

image

一応、疎通確認ということで、M5Stack側からもNode-REDのほうに、送信カウントを送っています。

Node-REDの設定

Node-REDの設定です。MQTTブローカーおよびMQTT連携はChoregraphe入っているPCにNode-REDを入れてPepperとMQTT連携するメモをベースにしています。

Node-REDでMQTTブローカーも立ち上げられるので、実質Node-REDを立ち上げるだけで連携できる環境をつくれます。

MQTTブローカー部分

MQTTブローカーを立ち上げます。

image

JSONファイルはこちらです。

[{"id":"fd6fc6a.23ada38","type":"mosca in","z":"b5b5500a.cbb08","mqtt_port":1883,"mqtt_ws_port":8080,"name":"","username":"","password":"","dburl":"","x":124,"y":88,"wires":[["c2ed7722.45ce78"]]},{"id":"20305bcb.029334","type":"comment","z":"b5b5500a.cbb08","name":"MQTTブローカー","info":"","x":114,"y":48,"wires":[]},{"id":"c2ed7722.45ce78","type":"debug","z":"b5b5500a.cbb08","name":"","active":false,"console":"false","complete":"payload","x":366,"y":87,"wires":[]}]

M5Stackやりとりする仕組み

MQTTブローカーが立ち上がれば、あとはディスプレイへの指示を作ります。

image

このように /sub/M5Stack のほうはディスプレイへの指示を担当していて、injectノードで、青・赤・緑・OFFの指示が出せるようになっています。下の /pub/M5Stack は送信カウントを受信してデバッグノードを動作させてます。

image

たとえば、赤にする指示をするinjectノードの中身です。

image

このようにinjectノードでJSONデータを投げ込んでいます。

image

データの中身はこのようになってます。ledがON/OFF、rgbが背景色のRGB値、messageが文字を担当してます。

M5StackやりとりするNode-REDフローのJSONファイルはこちらです。

[{"id":"b2a18e0a.19d3b","type":"inject","z":"5d1c73a0.e0106c","name":"red 255,0,0","topic":"","payload":"{\"led\":1,\"r\":255,\"g\":0,\"b\":0,\"message\":\"red\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":240,"wires":[["3eddc425.b41aac"]]},{"id":"3eddc425.b41aac","type":"mqtt out","z":"5d1c73a0.e0106c","name":"","topic":"/sub/M5Stack","qos":"","retain":"","broker":"32a2c535.945fea","x":610,"y":280,"wires":[]},{"id":"41d66ed8.e9647","type":"inject","z":"5d1c73a0.e0106c","name":"blue 0,0,255","topic":"","payload":"{\"led\":1,\"r\":0,\"g\":0,\"b\":255,\"message\":\"blue\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":160,"wires":[["3eddc425.b41aac"]]},{"id":"3156f09b.327de","type":"inject","z":"5d1c73a0.e0106c","name":"green 0,255,0","topic":"","payload":"{\"led\":1,\"r\":0,\"g\":255,\"b\":0,\"message\":\"green\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":340,"wires":[["3eddc425.b41aac"]]},{"id":"9433885e.b6de28","type":"inject","z":"5d1c73a0.e0106c","name":"off","topic":"","payload":"{\"led\":0,\"r\":0,\"g\":0,\"b\":0,\"message\":\"close\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":390,"y":420,"wires":[["3eddc425.b41aac"]]},{"id":"3311f1ec.4eca5e","type":"mqtt in","z":"5d1c73a0.e0106c","name":"","topic":"/pub/M5Stack","qos":"2","broker":"32a2c535.945fea","x":330,"y":540,"wires":[["76e3689c.df5728"]]},{"id":"76e3689c.df5728","type":"debug","z":"5d1c73a0.e0106c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":580,"y":600,"wires":[]},{"id":"32a2c535.945fea","type":"mqtt-broker","z":"","broker":"127.0.0.1","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willPayload":"","birthTopic":"","birthQos":"0","birthPayload":""}]

M5Stackのソースコード

M5Stackで書き込んだソースコードはこちらです。ソースコード内の設定は以下の通りなので、お使いになる場合は、現場に合わせて書き換えて下さい。

  • Wi-FiのSSID、Wi-Fiのパスワード
    • 接続したいWi-Fiの設定を書く
  • MQTTの接続先のIP、MQTTのポート
    • 今回のNode-RED MQTTブローカーへのIPアドレス書く
  • デバイスID
    • デバイスIDは機器ごとにユニークにします
  • メッセージを知らせるトピック
    • M5Stack → Node-RED
    • 送信カウントを伝える
  • メッセージを待つトピック
    • Node-RED → M5Stack
    • ディスプレイ操作を受け付けるトピック名
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <M5Stack.h>
#include <ArduinoJson.h>

// Wi-FiのSSID
char *ssid = "---------ssid---------";
// Wi-Fiのパスワード
char *password = "---------password ---------";
// MQTTの接続先のIP
const char *endpoint = "---------endpoint---------";
// MQTTのポート
const int port = 1883;
// デバイスID
char *deviceID = "M5Stack";  // デバイスIDは機器ごとにユニークにします
// メッセージを知らせるトピック
char *pubTopic = "/pub/M5Stack";
// メッセージを待つトピック
char *subTopic = "/sub/M5Stack";

////////////////////////////////////////////////////////////////////////////////
  
WiFiClient httpsClient;
PubSubClient mqttClient(httpsClient);
  
void setup() {
    Serial.begin(115200);
    
    // Initialize the M5Stack object
    M5.begin();

    // START
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(10, 10);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.printf("START");
    
    // Start WiFi
    Serial.println("Connecting to ");
    Serial.print(ssid);
    WiFi.begin(ssid, password);
  
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    // WiFi Connected
    Serial.println("\nWiFi Connected.");
    M5.Lcd.setCursor(10, 40);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.printf("WiFi Connected.");
    
    mqttClient.setServer(endpoint, port);
    mqttClient.setCallback(mqttCallback);
  
    connectMQTT();
}
  
void connectMQTT() {
    while (!mqttClient.connected()) {
        if (mqttClient.connect(deviceID)) {
            Serial.println("Connected.");
            int qos = 0;
            mqttClient.subscribe(subTopic, qos);
            Serial.println("Subscribed.");
        } else {
            Serial.print("Failed. Error state=");
            Serial.print(mqttClient.state());
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}
  
long messageSentAt = 0;
int count = 0;
char pubMessage[128];
int led,red,green,blue;
  
void mqttCallback (char* topic, byte* payload, unsigned int length) {

    String str = "";
    Serial.print("Received. topic=");
    Serial.println(topic);
    for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
        str += (char)payload[i];
    }
    Serial.print("\n");

    StaticJsonBuffer<200> jsonBuffer;
    
    JsonObject& root = jsonBuffer.parseObject(str);
  
    // パースが成功したか確認。できなきゃ終了
    if (!root.success()) {
      Serial.println("parseObject() failed");
      return;
    }
    // JSONデータを割りあて
    const char* message = root["message"];
    led = root["led"];
    red = root["r"];
    green = root["g"];
    blue = root["b"];

    Serial.print("red = ");
    Serial.print(red);
    Serial.print(" green = ");
    Serial.println(green);
    Serial.print(" blue = ");
    Serial.println(blue);

    if( led == 1 ){
      // RGBカラー uint16_t に変換
      // uint16_t RGB = red << 11 | green << 5 | blue;
      uint16_t RGB = ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);
      // 背景カラー反映
      M5.Lcd.fillRect(0, 0, 320, 240, RGB);
      // テキスト反映
      M5.Lcd.setCursor(10, 120);
      M5.Lcd.setTextColor(WHITE);
      M5.Lcd.setTextSize(5);
      M5.Lcd.printf(message);
    } else {
      // 消灯
      M5.Lcd.fillScreen(BLACK);
    }

    delay(300);
    
}
 
void mqttLoop() {
    if (!mqttClient.connected()) {
        connectMQTT();
    }
    mqttClient.loop();
}

void loop() {

  // 常にチェックして切断されたら復帰できるように
  mqttLoop();

  // 5秒ごとにメッセージを飛ばす
  long now = millis();
  if (now - messageSentAt > 5000) {
      messageSentAt = now;
      sprintf(pubMessage, "{\"count\": %d}", count++);
      Serial.print("Publishing message to topic ");
      Serial.println(pubTopic);
      Serial.println(pubMessage);
      mqttClient.publish(pubTopic, pubMessage);
      Serial.println("Published.");
  }
}

実は、ここまでやって、RGB値の動的設定をやったことがなかったので、

M5Stack/TFT_Rainbow_one_lib.ino at master · m5stack/M5Stack

を参考に、

      // RGBカラー uint16_t に変換
      uint16_t RGB = ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);
      // 背景カラー反映
      M5.Lcd.fillRect(0, 0, 320, 240, RGB);

を知りました。

明星和楽では、カラーセンサーが結構センシティブなので、一旦、数値を受け取ってから、近しい代表的なカラー定数(BLACKとかREDとかで表現できるやつ)に寄せる判定を書いていたのでした。

これでフルカラー制御イケる。

と思いきや、間違ってた

M5StackのLCDディスプレイの色をRGBで指定する。 – Qiita

こちらの記事にたどり着きまして、16Bit Colorと24Bit Colorの変換式が間違っていたことに気づいたので修正しました。

      // RGBカラー uint16_t に変換
      // uint16_t RGB = red << 11 | green << 5 | blue;
      uint16_t RGB = ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);
      // 背景カラー反映
      M5.Lcd.fillRect(0, 0, 320, 240, RGB);

動かしてみる

ということで動かしてみましょう。

image

設定があっていると、このように、電源を入れると、STARTののち、Wi-FiがつながればWiFi Connectedが表示されます。

MQTTの接続も表示しようと思ったのですが、MQTTブローカーおよび通信品質によって、切断後の再接続がどれくらいの頻度か読めなかったので、演出に加えるとややこしくなりそうなので、今回は割愛してます。

Node-REDを見てみるとデバッグタブにちゃんと送信カウントが送られてます!

image

では、ディスプレイを操作してみましょう。

image

赤→緑→赤→青→OFFでinjectノードを操作します。

かなりいい反応で動いています!

まとめ

ということで、M5StackとNode-REDをMQTTで連携するメモをまとめました。

M5StackはESP32のナレッジがそのまま使えるため、このようにMQTTをつなげるナレッジも引き継げましたし、ディスプレイだけでなくボタン操作や音といったWEB制作で使うようなインタラクティブな操作が、M5Stackのライブラリ内でかなり呼び出しやすくなっています。

これができると「WEBでCSSで背景色を変えるように、電子工作側でどうやるといいんだろう??」といったつまづきが少なくて時間が限られた開発でも使いやすかったです。

またM5Stackはこのコンパクトさのおかげで、実際に動くものをユーザーに手渡せるという行動が起こしやすく、出来上がったものを伝達する上でも楽しかったです。

それでは、よき、M5Stack & Node-RED Life を!