MQTTでLチカ -Node.jsとGo実装比較-

こんにちは,のぶです.最近Web周りを触り始めました.今回はMQTTというプロトコルを使ってLEDをチカチカしたいと思います.

MQTT

最近,IoT(Internet of Things)という言葉がありますね.家電屋に行くとスマート家具やらなんやら出てるあれです.インターネットにモノを繋いでスマートフォンやパソコンで遠隔から操作できるようになります.MQTTは情報が通るトンネルみたいなものです.あなたのスマホと家電とをつなぐ.

MQTTはhttpよりも軽量と言われています.実際に検証している記事がこちらになります.接続を維持できるMQTTは頻繁に通信が行われるIoTに適しています.

今回はESP32に接続されたLEDをチカチカさせます.

ESP32をarduinoベースでコーディングしたものが以下になります.MQTTにはBrokerと呼ばれるサーバーが必要になります.いわばスマホと家電をつなぐ中継器みたいなものです.ここではtest.mosquitto.orgという誰でも無料に使えるテストサーバを利用します.

今回はmqttを使って遠隔でLEDをチカチカさせるため,subscribeのみを使います.つまり,esp32は送られてくる信号を監視し続け,信号が送られてきたら解析して反映します.LEDTestというtopicを設定し,送られてきた値が0ならLEDをoff,1ならonします.ビルド環境はVScode+Platform IOです.

#include <Arduino.h>

#include <WiFiClient.h>

#include <WiFiClientSecure.h>

#include <WiFi.h>

#include <PubSubClient.h>

#include <ArduinoJson.h>

#define LED 5

//MQTT
#define mqtt_server "test.mosquitto.org"
#define PORT 1883


//wifi設定//////////////////////////////
WiFiClient httpClient;
PubSubClient mqttClient(httpClient);

//自宅wifi設定
const char* ssid = "hoge";
const char* password = "fuga";

unsigned long checkTimes = 0;

///////////////////////////////////////

//グローバル変数
String subscribe_url = "LEDTest";
String deviceID_str;

//MQTTによるデータの受け渡し
void callback(char* topic, byte* payload, unsigned int length) {
 int command;
 Serial.print("Received. topic=/");
 Serial.println(topic);
 for (int i = 0; i < length; i++) {
   Serial.print((char)payload[i]);
   //48→0 49→1 50→2
   command = (int)payload[i];
   Serial.println("送信されたコマンド");
   Serial.println(command);
 }

 //LEDをoff
 if (command == 48) {
   Serial.println("Off");
   digitalWrite(LED, LOW);
 }
 //LEDをon
 if (command == 49) {
   Serial.println("On");
   digitalWrite(LED, HIGH);
 }

}

void checkWiFi() {
 if (millis() - checkTimes > 5000) {
   checkTimes = millis();
   Serial.print("check wifi status");
   Serial.println(WiFi.status());
   if (WiFi.status() == WL_DISCONNECTED || WiFi.status() == WL_CONNECT_FAILED || WiFi.status() == WL_CONNECTION_LOST) {
     Serial.println(" After 10 sec, this system will start reboot process");
     delay(10000);
     ESP.restart();
   }
 }

}

void setup() {
 Serial.begin(115200);

 //LEDのピン設定
 pinMode(LED, OUTPUT);

 //MQTTの設定
 mqttClient.setServer(mqtt_server, PORT);
 mqttClient.setCallback(callback);

 //WiFiの設定
 WiFi.mode(WIFI_STA);
 WiFi.begin(ssid, password);  //  Wi-Fi APに接続 ----A
 int cnt = 0;
 while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
   delay(1000);
   Serial.print(".");
   cnt++;
   if (cnt > 10) { //一定時間たってもコネクトできなかったらリブートする
     Serial.println("start reboot");
     ESP.restart();
   }
 }
 Serial.print("WiFi connected\r\nIP address: ");
 digitalWrite(LED,LOW);
 delay(10000);
 Serial.println(WiFi.localIP());


}

void loop() {
 if (!mqttClient.connected()) {
   Serial.println("MQTT In Disconnect");
   Serial.println(mqttClient.state());
   if (mqttClient.state() != -1) {
     Serial.println("mqtt error");
     Serial.println(" After 10 sec, this system will start reboot process");
     delay(10000);
     ESP.restart();
   }

   if (mqttClient.connect(subscribe_url.c_str())) {
     Serial.println("MQTT Connected.");
     Serial.println(subscribe_url);
     mqttClient.subscribe(subscribe_url.c_str(), 0);
     Serial.println("Subscribed.");
   }
 } else {
   mqttClient.loop();
 }
}

次に,LEDに命令を出すpublisherの方を作ります.今回の記事はこちらがメインになります.

Node.js

Node.jsでpublisherを作ります.client_mqtt.publish(topic,"1")でLEDTestトピックに1という値を送ります.そしてGETメソッドでURLを叩くとMQTTを通して命令をおくるようにしています.
例えば,

app.get('/light-on', (req, res) => {
 client_mqtt.publish(topic, "1");

の部分でhttp://localhost:8080/light-onというURLをブラウザで入力すると,MQTTを通して,テストサーバー上をsubscribeしているESP32が反応し,値を勝手に拾って,LEDを点灯させます.ミニマムのコードがこちらになります.

"use strict"


const express =require('express'),
 app = express(),
 mqtt = require('mqtt'),
 client_mqtt = mqtt.connect('mqtt://test.mosquitto.org');

app.set("port", process.env.PORT || 8080);

app.use(
 express.urlencoded({
   extended: false
 })
);
app.use(express.json());

var topic = "LEDTest";

client_mqtt.on('connect', function(){
 console.log('publisher.connected.');
});

app.get('/light-on', (req, res) => {
 client_mqtt.publish(topic, "1");
 res.json({ message: 'A open command was sended!' });
 res.end();
});
app.get('/light-off', (req, res) => {
 client_mqtt.publish(topic, "0");
 res.json({ message: 'A open command was sended!' });
 res.end();
});

app.listen(app.get("port"), () => console.log(`Listening on port ${app.get("port")}`));

Go

次に,Go言語でpublishする場合です.
Go言語ではHandlerFuncを使ってURLごとに送る値を変えています.現在,絶賛勉強中なので正直コードがイケてないです.
例えば,ONの指令を出す場合こうなります.

	if token := c.Publish("LEDTest", 0, false, "1"); token.Wait() && token.Error() != nil {
		log.Fatalf("Mqtt error: %s", token.Error())
	}

tokenを出して失敗すればエラーを吐きます.全体のコードは以下になります.

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"

	mqtt "github.com/eclipse/paho.mqtt.golang"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, HTTPサーバ")
	defer r.Body.Close()
}

func lightOn(w http.ResponseWriter, r *http.Request) {
	opts := mqtt.NewClientOptions()
	opts.AddBroker("mqtt://test.mosquitto.org:1883")
	opts.SetDefaultPublishHandler(f)

	c := mqtt.NewClient(opts)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		log.Fatalf("Mqtt error: %s", token.Error())
	}
	fmt.Printf("MQTT server connected")

	if token := c.Publish("LEDTest", 0, false, "1"); token.Wait() && token.Error() != nil {
		log.Fatalf("Mqtt error: %s", token.Error())
	}

}

func lightOff(w http.ResponseWriter, r *http.Request) {
	opts := mqtt.NewClientOptions()
	opts.AddBroker("mqtt://test.mosquitto.org:1883")
	opts.SetDefaultPublishHandler(f)

	c := mqtt.NewClient(opts)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		log.Fatalf("Mqtt error: %s", token.Error())
	}
	fmt.Printf("MQTT server connected\n")

	if token := c.Publish("LEDTest", 0, false, "0"); token.Wait() && token.Error() != nil {
		log.Fatalf("Mqtt error: %s", token.Error())
	}

}

var f mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
	fmt.Printf("TOPIC: %s\n", msg.Topic())
	fmt.Printf("MSG: %s\n", msg.Payload())
}

func main() {
	var addr = flag.String("addr", ":8080", "アプリケーションのアドレス")
	flag.Parse()

	http.HandleFunc("/", handler)
	http.HandleFunc("/light-on", lightOn)
	http.HandleFunc("/light-off", lightOff)

	// Webサーバを開始します
	log.Println("Webサーバを開始します.ポート:", *addr)
	if err := http.ListenAndServe(*addr, nil); err != nil {
		log.Fatal("ListeningAndServe:", err)
	}

}

URLごとにLEDのON-OFFを切り替えたかったので,LightOn/LightOff関数を作って,毎度MQTTを初期化して繋げる処理をしています.

テスト

実際に動かしてみます.
ESP32に先程のコードを書き込みます.次に,Node.jsまたはGoのコードを実行し,ローカルホストにWebサーバを起動させます.そして,ngrokというものを使って,ローカルホストのサーバを公開します.参考はこちら

画像3

ngrokを起動させたときの画面になります.で,スマホに公開されたURL+/light-onのURLを入力すると,

画像2

画像3

3秒ほど待つと,,,無事にLED(緑)が点きました!ちなみに遅いのはブローカーにtest.mosquitto.orgを使わせてもらっているためです.ローカルまたはherokuやAWSを使えば一瞬で点きます.

所感

Node.jsとGoでMQTTを使ってLEDを点灯させました.Nodeでは,app.getでコードの記述が少ないのに対して,Goでは関数ごとにMQTTを初期化する必要がありました.もちろん,実際にはLEDのON-OFFは一つのURLで管理する必要があると思いますし,なんならルート"/"だけで,制御できるのでURLを用意する必要はありません.大きな違いとしては,NodeではGETメソッドを,Goではhandlerで制御できる点です.個人的にはGoのHandlerFuncのように,URLごとに関数を記述できたほうが直感的でAPIの設計をしやすいのではないかと思います.まあ,初心者にとってはの話なんでしょうが...
Goでの強みはpublishではなくsubscribeなのではないかと思います.何百とあるセンサーからアクセスがあっても強力な並列処理ができるので,IoTにはもってこいの機能であると思います.

以上,最近勉強したNode.jsとGoによるMQTTを用いたLチカ実装比較でした.


この記事が気に入ったらサポートをしてみませんか?