[ラズパイ] MQTTをローカルネットワーク間で通信してみる 〜 対話型コンソールの導入


2020/11/02

以前の記事の内容でnode.jsのmqttクライアントモジュールであるmqtt.jsを使って簡単なMQTT接続を試しました。

mqtt.jsを使う際に単純にコマンドを叩くだけではブローカーに接続を切断する時にプロセスが走ったまま終了しない状態になります。

今回はコマンドプロンプトから対話型の操作を導入して、通信状態を管理するやり方を整えてみます。


prompts.js

標準入出力用のnpmパッケージであるprompts.jsから対話型のスクリプトを作成します。

前回のmqttサブスクライバを設定した項目から以降の内容に置き換えて話をしていきます。

            $ npm i prompts -s
#OR
$ yarn add prompts -S
        
公式にあるようにサンプルのコードを走らせてみます。prompt.jsというファイル名で以下のコードの内容で保存します。

            const prompts = require('prompts');

(async () => {
    const response = await prompts({
        type: 'number',
        name: 'value',
        message: 'How old are you?',
        validate: value => value < 18 ? `Nightclub is 18+ only` : true
    });
    console.log(response); // => { value: 24 }
})();
        
このコードを動かしてみると、自前で実装せずともvalidate関数で判定条件を定義すると簡単に対話型のアプリにすることができます。

            $ node prompt.js
? How old are you? › 13
› Nightclub is 18+ only
✔ How old are you? … 45
{ value: 45 }
        


mqttクライアントで利用する

ではselectタイプの質問形式でプログラムを選択するようなプログラムの雛形を作成してみます。先程のprompt.jsの中身を以下のコードの内容に変更します。

            const prompts = require('prompts');

function sleep(_ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, _ms)
    });
}

async function planA() {
    console.log('Plan-A has begun.');
    await sleep(1000);
    console.log('Plan-A finished.');
}

async function planB() {
    console.log('Plan-B has begun.');
    await sleep(2000);
    console.log('Plan-B finished.');
}

async function main() {
    console.log('Start');

    while (true) {
        const question = [
            {
                type: "select",
                name: "plan",
                message: "Order?",
                choices: [
                    { title: "Plan A", value: "a" },
                    { title: "Plan B", value: "b" },
                    { title: "Quit", value: "q" }
                ]
            }
        ];

        const response = await prompts(question);
        console.log(response);
        if (!Object.keys(response).length || response.plan == "q") {
            console.log('Done!');
            break;
        } else if (response.plan == "a") {
            await planA();
        } else if (response.plan == "b") {
            await planB();
        }
    }
}

main();
        
Plan A`Plan A`を選択すると、responseで返るオブジェクトのplanプロパティにaが返り、同期的にplanA関数が処理されます。Plan Bも同様です。

また
Quitが選択されると、response.planqとなり処理が終了します。Cntl + cキーで終了したい場合には、空のオブジェクトを判定して終了処理になります。

ということでこれを実行すると、

            $ node prompt.js
start
✔ Order? › Plan A
{ plan: 'a' }
Plan-A has begun.
Plan-A finished.
✔ Order? › Plan B
{ plan: 'b' }
Plan-B has begun.
Plan-B finished.
✔ Order? › Quit
{ plan: 'q' }
Done!
        
のような対話的に処理が流れるようなプログラムに仕上がりました。


mqttクライアントの動作を組み込む

本題であったmqttクライアントの処理を先程の雛形prompt.jsを拡張して作成します。

今回はMQTTブローカーに接続する、サブスクリプションを登録する、サブスクリプションを止める、程度の処理を行います。

コードを
index_sync.jsとして以下の内容で保存します。なお、ブローカーのアドレスは192.168.0.200で起動したとします。

            const prompts = require('prompts');
const mqtt = require('mqtt');
let mqttClient;

async function mqttConnect() {
    if (!mqttClient) {
        mqttClient = mqtt.connect('mqtt://192.168.0.200:1883');
        mqttClient.on('connect', () => {
            console.log('Connected.');
        });
        mqttClient.on('message', (topic_, message) => {
            console.log('subscriber received topic:', topic_, 'message:', message.toString());
        });
    } else {
        console.log('Already Has Connected.');
    }
}

async function mqttSubscrive() {
    if (mqttClient) {
        const topic = 'hoge/piyo/fuga';
        mqttClient.subscribe(topic, (err, granted) => {
            console.log('Subscribed.');
        });
    } else {
        console.log('MQTT Client is an empty.');
    }
}

async function mqttUnsubscrive() {
    if (mqttClient) {
        const topic = 'hoge/piyo/fuga';
        mqttClient.unsubscribe(topic, (err, granted) => {
            console.log('Unsubscribed.');
        });
    } else {
        console.log('MQTT Client is an empty.');
    }
}

async function main() {
    console.log('Start');

    while (true) {
        const question = [
            {
                type: "select",
                name: "plan",
                message: "Order?",
                choices: [
                    { title: "Connect", value: "c" },
                    { title: "Subscrive", value: "s" },
                    { title: "Unsubscrive", value: "u" },
                    { title: "Quit", value: "q" }
                ]
            }
        ];
        const response = await prompts(question);
        console.log(response);
        if (!Object.keys(response).length || response.plan == "q") {
            console.log('Done!');
            if (mqttClient) {
                mqttClient.end();
            }
            break;
        } else if (response.plan == "c") {
            await mqttConnect();
        } else if (response.plan == "s") {
            await mqttSubscrive();
        } else if (response.plan == "u") {
            await mqttUnsubscrive();
        }
    }
}

main();
        
実行したら以下のように動きます。

合同会社タコスキングダム|蛸壺の中の工作室

この画像で左の窓がサブスクライバ(mqttクライアント)側で、右の窓がブローカー(mqttサーバー)側です。狙い通り、対話型でmqttを接続から切断まで可能なコンソールアプリに仕上がっています。


まとめ

前回はmqtt.jsをただ動かすだけのプログラムになっていましたが、対話型にすることでよりリッチな操作が可能になりました。今回の内容としては、簡単のためサブスクライバとしてテストしましたが、パブリッシャ用のプログラムとしても同様の実装で作成できると思います。


参考サイト

prompts : node.js用のコマンドラインからの値を受け取る時に便利なライブラリ

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

電子工作を身近に知っていただけるように、材料調達からDIYのハウツーまで気になったところをできるだけ細かく記事にしてブログ配信してます。