AtomS3 から HiveMQ MQTT につないでディスプレイ ON / OFF 遠隔操作の仕組みを作るメモ

AtomS3 から HiveMQ MQTT につないでディスプレイ ON / OFF 遠隔操作の仕組みを作るメモ

AtomS3 から HiveMQ MQTT につないでディスプレイ ON / OFF 遠隔操作の仕組みを作るメモです。

背景

ATOMS3 — スイッチサイエンス

AtomS3 から HiveMQ MQTT につないでディスプレイ ON / OFF 遠隔操作の仕組みをまとめておきます。

最近では MCP サーバーで IoT 連携をするときに、このようなシンプルなディスプレイ ON / OFF 遠隔操作の仕組みがあるとデモしやすいので重宝しています。

今回のプログラム

今回のプログラムです。

#include "M5AtomS3.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

// ———— ファームウェア情報 ————
#define FW_NAME    "AtomS3_HiveMQ_DisplaySwitch"
#define FW_VERSION "1.0.5"

// ———— Wi-Fi 設定 ————
const char* ssid     = "YOUR_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// ———— MQTT ブローカー設定 ————
const char* mqttServer   = "*******************************.s1.eu.hivemq.cloud";
const int   mqttPort     = 8883;
const char* mqttUser     = "mqttUser";
const char* mqttPassword = "mqttPassword";

// ———— トピック定義 ————
char subTopic[64];
char pubTopic[64];

// ———— MQTT クライアント ————
WiFiClientSecure netClient;
PubSubClient    mqttClient(netClient);

// ———— スイッチ状態 ————
bool switchState = false;

// ———— MQTT 接続/再接続 ————
void connectMQTT() {
    // 表示: MQTT ...
    AtomS3.Display.clear();
    AtomS3.Display.setTextSize(1);
    AtomS3.Display.setCursor(0, 0);
    AtomS3.Display.println("MQTT ...");
    delay(200);

    while (!mqttClient.connected()) {
        // クライアント ID ランダム生成
        String mac = WiFi.macAddress();
        mac.replace(":", "");
        String rnd = String(random(0x1000000), HEX);
        String cid = String(FW_NAME) + "-" + mac + "-" + rnd;
        char clientId[80];
        cid.toCharArray(clientId, sizeof(clientId));

        if (mqttClient.connect(clientId, mqttUser, mqttPassword)) {
            mqttClient.subscribe(subTopic);
            // 表示: MQTT OK!
            AtomS3.Display.clear();
            AtomS3.Display.setTextSize(1);
            AtomS3.Display.setCursor(0, 0);
            AtomS3.Display.println("MQTT OK!");
            delay(2000);
            // 状態の発行
            StaticJsonDocument<128> doc;
            doc["command"] = "displaySwitch";
            doc["state"]   = switchState;
            char buf[128];
            size_t len = serializeJson(doc, buf);
            mqttClient.publish(pubTopic, buf);
        } else {
            delay(5000);
        }
    }
}

// ———— MQTT 受信コールバック ————
void mqttCallback(char* topic, byte* payload, unsigned int length) {
    StaticJsonDocument<128> doc;
    DeserializationError err = deserializeJson(doc, payload, length);
    if (err != DeserializationError::Ok) return;

    if (!doc.containsKey("command")) return;
    String cmd = doc["command"].as<const char*>();
    if (cmd == "displaySwitch" && doc.containsKey("state")) {
        switchState = doc["state"].as<bool>();
        // 表示の更新
        if (switchState) {
            AtomS3.Display.fillScreen(WHITE);
        } else {
            AtomS3.Display.fillScreen(BLACK);
        }
    }
}

// ———— MQTT ループ処理 ————
void mqttLoop() {
    if (!mqttClient.connected()) {
        connectMQTT();
    }
    mqttClient.loop();
}

void setup() {
    // AtomS3 初期化
    auto cfg = M5.config();
    AtomS3.begin(cfg);
    // テキストサイズ / 表示配置を一度設定
    AtomS3.Display.setTextDatum(TL_DATUM);
    AtomS3.Display.setTextSize(1);

    // 表示: FW_NAME / FW_VERSION
    AtomS3.Display.clear();
    AtomS3.Display.setCursor(0, 0);
    AtomS3.Display.println(FW_NAME);
    AtomS3.Display.println(FW_VERSION);
    delay(400);

    Serial.begin(115200);
    delay(100);

    // ———— Wi-Fi 接続 ————
    AtomS3.Display.clear();
    AtomS3.Display.setCursor(0, 0);
    AtomS3.Display.println("Wi-Fi ...");
    delay(200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
    }
    AtomS3.Display.clear();
    AtomS3.Display.setCursor(0, 0);
    AtomS3.Display.println("Wi-Fi OK");
    delay(2000);

    // トピック生成(MAC ベース)
    String mac = WiFi.macAddress();
    mac.replace(":", "");
    snprintf(subTopic, sizeof(subTopic), "atoms3/%s/displaySwitch", mac.c_str());
    snprintf(pubTopic, sizeof(pubTopic), "atoms3/%s/displaySwitchState", mac.c_str());
	
    // TLS 証明書検証スキップ(自己署名 OK)
    netClient.setInsecure();
	
    // MQTT 初期設定
    mqttClient.setServer(mqttServer, mqttPort);
    mqttClient.setCallback(mqttCallback);
    connectMQTT();

    // 表示の更新
    if (switchState) {
        AtomS3.Display.fillScreen(WHITE);
    } else {
        AtomS3.Display.fillScreen(BLACK);
    }
}

void loop() {
    mqttLoop();

    AtomS3.update();
    if (AtomS3.BtnA.wasPressed()) {
        switchState = !switchState;
        // 表示の更新
        if (switchState) {
            AtomS3.Display.fillScreen(WHITE);
        } else {
            AtomS3.Display.fillScreen(BLACK);
        }
        // 状態の発行
        StaticJsonDocument<128> doc;
        doc["command"] = "displaySwitch";
        doc["state"]   = switchState;
        char buf[128];
        size_t len = serializeJson(doc, buf);
        mqttClient.publish(pubTopic, buf);
    }

    delay(10);
}

こちらを持ってきて、

// ———— Wi-Fi 設定 ————
const char* ssid     = "YOUR_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

つなげたい Wi-Fi への接続設定をして、

// ———— MQTT ブローカー設定 ————
const char* mqttServer   = "mqttServer";
const int   mqttPort     = 1883;
const char* mqttUser     = "mqttUser";
const char* mqttPassword = "mqttPassword";

つなげたい MQTT のブローカーの設定をしてください。

こちらを AtomS3 に書き込みます。

動かしてみる

電源入れて動かしてみます。

Wi-Fi 接続開始して、うまく Wi-Fi がつながって、

MQTT OK! で接続完了です。

接続時点で # トピックや atoms3/# などで、接続開始をとらえておくと atoms3/{MAC Address}/displaySwitchState で初期状態が届くので、以降のつなげたいデバイスの {MAC Address} が分かります。

MQTT 側で atoms3/{MAC Address}/displaySwitch のトピックに対して { "command":"displaySwitch", "state": true} という JSON を送ると ON になります。

MQTT 側で atoms3/{MAC Address}/displaySwitch のトピックに対して { "command":"displaySwitch", "state": false} という JSON を送ると OFF になります。

また、ディスプレイボタンをクリックすると、こちらでも ON/OFF できるので、ON にしたり

OFF にすると、

このように atoms3/{MAC Address}/displaySwitchState で、現在の状態が飛んできます。