工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

PythonアプリからブラウザアプリへMQTTで送信

概要

PythonアプリからブラウザアプリへMQTTでメッセージ送信するための簡単なテストプログラムを作成し、動作確認した。

背景と目的

Raspberry Pi上のPythonアプリ側から、そのUI系として用いているクライアントサイドWebアプリ(以下、ブラウザアプリ)に対してプッシュ通信をする必要が出た。なので、MQTTでPython側からパブリッシュし、Webアプリ側でサブスクライブして受け取るというのを実装し、動作確認する。


詳細

0.構成、動作確認したいこと

  • Rasbperry Pi上にMQTTブローカーを立てる
  • Rasbperry Pi上のPythonアプリケーションからパブリッシュする
  • (Raspberry Pi上のWebサーバーでホスティングし)別デバイスで表示しているブラウザアプリのjavascriptで、サブスクライブしてメッセージを受信


1.MQTTブローカー

Raspberry Piにブローカーをインストールするには、mosquittoというアプリがあるので、それを使う。

sudo apt-get install mosquitto

とする。起動の前に、WebSocketで通信できるようにするため、/etc/mosquitto/mosquitto.confに以下の2行を追記。これは、後述のjavascript用MQTTクライアントライブラリを動かすために必要。(WebSocketを使わないのであれば不要)

listener 1883
protocol websockets

編集完了したら、サービスを起動。

sudo systemctl start mosquitto


2.Pythonアプリ上でパブリッシュ

PythonでMQTTクライアントを扱うには、こちらを参考に、

pip3 install paho-mqtt

でインストール。

パブリッシュのサンプルスクリプトは、以下。1秒ごとにパブリッシュする。大事なのは、WebSocketで通信するため、Clientインスタンス作成時に引数transport="websockets"を与える部分。

client.loop_start()は、重要!2020/06/05追記

あるプログラムで、比較的長い周期でpublishする部分があったのだが、どうもしばらくすると動かなくなってしまった。接続自体は切れていないのにおかしいなと思ったら、どうやらclient.loop_start()をやるのを忘れていた。なぜ重要かというと、こちらのKQTT Keep Aliveの説明にあるように、PINGをブローカーと送りあっていて、その周期がKeepAliveIntervalすなわちconnectの第3引数なのだが、loop_startをさせないとどうやらこのping送信がされないようだ。すると、ある程度の時間たつとpublishできなくなってしまう。(内部的には切断のような状態なのだろう。) というわけで、loop_startは忘れないこと。 (なお、以下のサンプルは短い周期でpublishしているので、最悪なくても動いてしまう)

import paho.mqtt.client as mqtt
import time

def on_connect(client, userdata, flag, rc):
    """
    ブローカーに接続できたときの処理
    """
    print("Connected with result code " + str(rc))

def on_disconnect(client, userdata, flag, rc):
    """
    ブローカーが切断したときの処理
    """
    if rc != 0:
        print("Unexpected disconnection.")

def on_publish(client, userdata, mid):
    """
    publishが完了したときの処理
    """
    print("publish: {0}".format(mid))

if __name__ == '__main__':

    client = mqtt.Client(transport="websockets") # クラスのインスタンス(実体)の作成
    client.on_connect = on_connect       # 接続時のコールバック関数を登録
    client.on_disconnect = on_disconnect   # 切断時のコールバックを登録
    client.on_publish = on_publish       # メッセージ送信時のコールバック

    client.connect("localhost", 1883, 60)  # 接続先は自分自身

    # 通信処理スタート
    client.loop_start()  # subはloop_forever()だが,pubはloop_start()で起動だけさせる

    # 永久に繰り返す
    while True:
        client.publish("topic/test", "Hello, World!")
        time.sleep(1)


3.ブラウザアプリ(javascript)でサブスクライブ

https://projects.eclipse.org/projects/iot.paho/downloadsから、javascript用のライブラリをダウンロード。

サブスクライブ動作確認用のコードを書いた。参考にしたのは、ダウンロード先のURLの下のほうについていたもの。HTML部分は省略するが、id="message"というdiv要素のテキストを、メッセージを受け取ってその内容に書き換えている。

// クライアントのインスタンスを作成
client = new Paho.MQTT.Client(RaspberryPiのIPアドレス, Number(1883), "ClientId");

// ハンドラのセット
client.onConnectionLost = onConnectionLost; // 接続切断時
client.onMessageArrived = onMessageArrived; // メッセージ受信時

// 接続する
client.connect({
    onSuccess: onConnect
});

// 接続完了時のコールバック
function onConnect() {
    $("#status").text("onConnect");
    // サブスクライブ開始
    client.subscribe("topic/test");
}

// 接続切断時
function onConnectionLost(responseObject) {
    if (responseObject.errorCode !== 0) {
        console.log("onConnectionLost:" + responseObject.errorMessage);
    }
}

// メッセージ受信時
function onMessageArrived(message) {
    $("#status").text("onMessageArrived");
    $("#message").text(message.payloadString);
}

4.動作確認

以下の通り、受け取ったメッセージが表示され、正しく通信できた。


まとめと今後の課題

Pythonアプリからのプッシュをブラウザアプリで受け取ることができた。