IoT何をいまさら(83) M5Stack ATOMLite、MQTTでnode-RED接続

Joseph Halfmoon

先週の投稿で、M5Stack社の超小型デバイスATOM Liteのスタンドアロンでのお試しプログラムを動作させました。本日は、余勢をかって?WiFiネットワークに接続し、Node-REDダッシュボードにMQTT接続いたします。他のデバイスで何度となくやっているので新味はありませんが、これをやらないとATOM Liteの意味がない?

(今回使用のATOM Lite用のArduino環境用ソース全文は末尾にあります)

まずATOM Liteを接続するNode-REDのサーバーについて簡単におさらいしておきます。

  • ハードウエアは Raspberry Pi 3 model B+ (Raspbian OS)
  • MQTTブローカはMosquittoが走っている

接続するATOM Liteの拡張端子には何も取り付けていないので(デバッグ用にUSBシリアルに仮想端末がぶら下がってはいる)、とりあえずNode-REDダッシュボードと情報のやり取りできそうなお道具は以下の2つです。

  1. ボタン1個
  2. フルカラーLED1個

そこでATOM Lite側からは、テスト用に以下の2つの情報をサーバに接続させることといたしました。

  1. ボタンのON/OFFの状態を「時々」パブリッシュする
  2. LEDに表示すべき色の指示をサブスクライブして点灯色を変更する

時々というのは、デバッグのし易さから「2秒」というお手頃な頻度といたしました。サブスクライブしている情報はWiFiの成行で直ぐにATOM Liteに伝わってまいりますが、色を変更するのもこれまた「2秒」に1回という設定。おいおいATOM Liteの背面端子などに通信機能を持たないデバイスをいろいろ取り付けてATOM Liteに「仲介の労」を取ってもらおうという目論見ですが、まだまだ先は長いです。

Node-REDのフロー

まずはラズパイ上で走っているNode-REDのエディタ画面にパソコンのブラウザから接続し、Node-REDの簡単なフローを記述いたします。このフローは、ダッシュボードに新設したタブ「ATOM Lite」内の以下のウイジットと結合しています。

  • ATOM Liteが接続したことを表示するための Status (テキスト)
  • ATOM LiteのボタンのON/OFFを表示するための Button (テキスト)
  • LEDの表示色をATOM Liteに指示するための Color (プルダウンリスト)

NodeRedFlow_ATOMLite現状、Statusは、ATOM Lite側からパブリッシュされる文字列をただ表示するだけのものなので、”Running”と言われればATOM Lite側とコネクションが切れてもそのままのオトボケ仕様です。ちゃんとハートビート信号でも捉えて、切れたら、切れたと表示するのはまた後で。

それに対して Button の方は、ATOM Lite側からは JSON形式で キー、バリュー型のデータを送ってくる想定です。後で ボタンのON/OFFだけでなく、他の目的にも拡張できるようにするためです。そこでATOM Lite側からPUBLISHされたJSONデータの中の特定のキー(ここではBUTTON)に反応し、JSONデータから対応のバリュー部分を取り出すようにしてみました。

最後のLED表示色の設定ですが、24ビットで指定可能なLED色をカッコいいインタフェースで指定できたらなどと一瞬考えたのですが、メンドイ。フルカラーLEDといいつつも、LEDの色は、それほどな表現力があるようには思われません。とりあえず赤、青、緑の単色指定の選択を「1文字に込めて」パブリッシュするだけといたしました。

Node-REDダッシュボードの様子

これを受けたNode-REDダッシュボードの様子(ColorのところでREDを指定している動作時)は以下のようです。色指定は、変更したときのみ送信されるような設定です。

ブラウザ上でREDと指定されて、赤く光っているATOM Liteの様子がこちら。

ATOMLite_mqttConnected

ATOM Liteのプログラム

さて肝心のATOM Liteに載せたプログラムのソースコード、全文は末尾にあります。ただちょっと読みづらいです。その一つの原因が、

USBシリアルへの”printfデバッグ用”垂れ流し出力

にあります。なにせATOM Liteは「見かけ上手足のほとんど無い」デバイスなので、何かとりつけておかないと今どうなっているのだか外からその様子を知る手段に乏しいです。一たびネットワークに接続して動いてしまえばネットワーク上で「知る」ことはできますが、接続前にトラブルと困ります。やはりprint文は必須ですかね。

それから細かい話ですが、以下のコメントアウトを外すとビルド途中でエラーになります。ボード的には「M5StickC」として設定しているので念のためインクルードしておくか(重複してもインクルードガードに跳ね返されるだろうし)と想像したのですが良くありませんでした。

//#include <M5StickC.h>

M5Stack社「伝統」の M5 オブジェクト、M5StickCとの共通部分も含めて、M5Atom.h の中で始末されているようです(まだソース見てません。)ボード定義はM5StickCだけれども、ソース上では M5Atom.h のみインクルードすれば良いみたいです。

明示的に取り込んだのは以下の3ライブラリであります。

どれも以前から「お世話になっている」ライブラリばかりであります。今回のテーマ的にはPubSubClientライブラリ(作Nicholas O’Leary氏、MITライセンス)でありましょう。ライブラリのExampleの中で「参考」にさせていただいたのは

mqtt_basic

であります。これは WiFiでなく Ethernetベースのサンプルで、特にESP系のデバイス専用というわけではないです。他にも WiFi、ESP8266ベースのExampleもありますが、一番シンプルで分かり易いサンプルに思われました。無駄なところがない。そう考えると下に掲げたコードは、ハードや「成行」に引きづられているところがありありです。

次回は、いよいよ ATOM Lite の拡張端子を触って行きたいと思います。

IoT何をいまさら(82) M5Stack、M5ez、簡易ファイラー? へ戻る

IoT何をいまさら(84) Eddystoneパケット、Wiresharkで表示フィルタ へ進む

ATOM Lite、MQTTでボタンとLED制御、Serial Debug用コード入り
#define SERUSB_DEBUG

#define XMQTT_NOT_START      (0)
#define XMQTT_CONNECTED      (1)
#define XMQTT_NOT_CONNECTED  (101)
#define WIFI_NOT_CONNECTED  (100)
#define RETRY_MAX           (10)

#include <WiFi.h>
#include <PubSubClient.h>
//#include <M5StickC.h>
#include <ArduinoJson.h>
#include "M5Atom.h"

const bool LCDEnable = false;
const bool PowerEnable = false;
const bool SerialEnable = true;
CRGB ledColor;
unsigned long lastMsg = 0;

char *ssid = "Your SSID";
char *password = "Your PASSWORD";
const char *brokerAdr = "Your MQTT IP address";
const int  brokerPort = 1883;
int mqttStatus;

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

char buf[100];
StaticJsonDocument<200> doc;

CRGB dispColor(uint8_t g, uint8_t r, uint8_t b) {
  return (CRGB)((g << 16) | (r << 8) | b);
}

void reconnect() {
  int retryCount = 0;
  while (WiFi.status() != WL_CONNECTED) {
#ifdef SERUSB_DEBUG
    Serial.println("Waiting WiFi.");
#endif
    mqttStatus = WIFI_NOT_CONNECTED;
    if (++retryCount > RETRY_MAX) {
      return; 
    }
    delay(500);  
  }
  retryCount = 0;
  while(!mqttClient.connected()) {
#ifdef SERUSB_DEBUG
    Serial.println("Waiting MQTT.");
#endif
    mqttStatus = XMQTT_NOT_CONNECTED;
    if (++retryCount > RETRY_MAX) {
      return; 
    }
    if (!mqttClient.connect("ATOMLite")) {
      delay(500);
    }
  }
  mqttClient.subscribe("ATOMLite/Color");
  mqttStatus = XMQTT_CONNECTED;
}

void callback(char* topic, byte* payload, unsigned int length) {
#ifdef SERUSB_DEBUG
  Serial.print(topic);
  Serial.print(" ");
  for (int i=0;i<length;i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
#endif
  if (length > 0) {
    char com = (char)payload[0];
    if (com == 'G') {
      ledColor = 0x5F0000;
    } else if (com == 'R') {
      ledColor = 0x005F00;
    } else if (com == 'B') {
      ledColor = 0x00005F;
    } else {
      ledColor = 0x1F1F1F;      
    }
  }
}

void mqttPub(char* topic, char* msg) {
  if (mqttStatus == XMQTT_CONNECTED) {
    mqttClient.publish(topic, msg);
#ifdef SERUSB_DEBUG
  } else {
    Serial.println("ERROR: MQTT Publish status.");
#endif
  }  
}

void sendBUTTON(char* datV) {
  doc["mesType"] = "BUTTON";
  doc["data"] = datV;
  serializeJson(doc, buf, 100);
  mqttClient.publish("ATOMLite/Button", buf);
}

void setup() {
  M5.begin(LCDEnable, PowerEnable, SerialEnable); //M5StickC::begin
#ifdef SERUSB_DEBUG
  Serial.begin(115200);
  while(!Serial) {};
  Serial.println("M5Atom Lite w/MQTT");
#endif
  ledColor = 0x010101;
  M5.dis.drawpix(0, 0);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
#ifdef SERUSB_DEBUG
    Serial.println("Waiting WiFi.");
#endif
    delay(500);
  }
  mqttClient.setServer(brokerAdr, brokerPort);
  mqttClient.setCallback(callback);
  reconnect();
  mqttPub("ATOMLite/Status","Running");
}

void loop() {
  if (!mqttClient.connected()) {
    reconnect();
  }
  mqttClient.loop();

  unsigned long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    M5.dis.drawpix(0, ledColor);
    M5.update();
    if (M5.Btn.wasPressed())
    {
      sendBUTTON("ON");
    } else {
      sendBUTTON("OFF");
    }
  }
}