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というものを使って,ローカルホストのサーバを公開します.参考はこちら
ngrokを起動させたときの画面になります.で,スマホに公開されたURL+/light-onのURLを入力すると,
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チカ実装比較でした.