Skip to content

Electron から MQTT.js SDK を使ってデプロイメントに接続する

本記事では、Electron プロジェクトでの MQTT の利用方法を主に紹介し、シンプルな MQTT デスクトップクライアントの作成、およびクライアントと MQTT ブローカー間の接続、サブスクライブ、サブスクライブ解除、メッセージ送受信などの機能を実装する方法を解説します。

事前準備

MQTT ブローカーのデプロイ

  • EMQX が提供する 無料のパブリック MQTT ブローカー を利用できます。このサービスは EMQX プラットフォーム をベースに構築されています。ブローカーへのアクセス情報は以下の通りです:
    • ブローカー:broker.emqx.io
    • TCP ポート:1883
    • SSL/TLS ポート:8883
    • WebSocket ポート:8083
    • WebSocket TLS/SSL ポート:8084
  • また、独自の MQTT ブローカーを作成することも可能です。デプロイメントが稼働状態になったら、デプロイメント概要ページで接続情報を確認できます。後のクライアント接続段階で必要となるユーザー名とパスワードは、アクセス制御 -> 認証 で設定してください。

Electron アプリケーションの作成

Electron は GitHub によって開発・保守されているオープンソースのソフトウェアフレームワークです。Chromium レンダリングエンジンと Node.js ランタイムを組み合わせ、Web 技術を用いたデスクトップ GUI アプリケーションの開発を可能にします。Electron は Atom、GitHub Desktop、Light Table、Visual Studio Code、WordPress Desktop などの主要なオープンソースプロジェクトの GUI フレームワークとして使われています。^1

基本的な Electron プロジェクトは、package.json(メタデータ)、main.js(コード)、index.html(GUI)の3ファイルで構成されます。Electron の実行ファイル(Windows では electron.exe、macOS では electron.app、Linux では electron)がフレームを提供し、開発者はフラグの追加、アイコンのカスタマイズ、実行ファイルの名前変更や編集を自由に行えます。

新規プロジェクトの構築方法は多様ですが、ここではいくつかの簡単な例を挙げます:

  • 手動で作成する場合、プロジェクトディレクトリで以下を実行します

    shell
    cd your-project
    
    npm init
    
    npm i -D electron@latest

    詳細な手順は以下のドキュメントも参照してください。

    アドレス: https://www.electronjs.org/docs/tutorial/first-app

  • 公式テンプレートプロジェクト electron-quick-start を使った迅速な開発

    アドレス: https://github.com/electron/electron-quick-start

    shell
      # リポジトリをクローン
      git clone https://github.com/electron/electron-quick-start
      # リポジトリに移動
      cd electron-quick-start
      # 依存関係をインストール
      npm install
      # アプリを起動
      npm start
  • React.js を使った開発が可能なテンプレートプロジェクト electron-react-boilerplate を使った迅速な開発

    アドレス: https://github.com/electron-react-boilerplate/electron-react-boilerplate

    shell
    git clone --depth 1 --single-branch https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name
    cd your-project-name
    yarn
  • Vue.js を使った開発が可能な electron-vue によるプロジェクトの迅速な構築は、vue-cli ツールを用いたプロジェクト初期化と連携します。

    アドレス: https://github.com/SimulatedGREG/electron-vue

    shell
    # vue-cli をインストールし、ボイラープレートを作成
    npm install -g vue-cli
    vue init simulatedgreg/electron-vue my-project
    
    # 依存関係をインストールし、アプリを起動
    cd my-project
    yarn # または npm install
    yarn run dev # または npm run dev

本記事では、公式の electron quick start プロジェクトテンプレートを使ってプロジェクトを初期化し、例題プロジェクトを迅速に構築します。

依存関係のインストール

コマンドラインから以下を実行します。

shell
npm install mqtt --save

依存関係のインストール後、デバッグ用にコンソールを開きたい場合は、main.js のコードを修正し、win.webContents.openDevTools() のコメントアウトを解除してください。

javascript
// DevTools を開く
mainWindow.webContents.openDevTools()

この場合、ローカルにインストールした MQTT.js モジュールは、フロントエンドビルダーでフロントエンドページをパッケージングしない限り、renderer.js に直接読み込めません。ビルドツールを使う方法以外に、以下の2つの解決策があります:

  1. webPreferences 内で nodeIntegration を true に設定する方法。これにより、webview 内で Node 統合が有効になり、requireprocess などの Node API を使って低レベルのシステムリソースにアクセスできます。Node 統合はデフォルトで無効です。

    javascript
    const mainWindow = new BrowserWindow({
      width: 800,
      height: 600,
      webPreferences: {
        nodeIntegration: true,
        preload: path.join(__dirname, 'preload.js'),
      },
    })
  2. preload.js 内で MQTT.js モジュールをインポートする方法。Node 統合が無効でも、このスクリプトはすべての Node API にアクセス可能です。ただし、このスクリプトの実行完了後、Node 経由で注入されたグローバルオブジェクトは削除されます。

  3. MQTT.js モジュールをメインプロセスでインポートし接続する方法。Electron では、ipcMainipcRenderer モジュールを使い、開発者が定義した「チャネル」を介してプロセス間通信を行います。これらのチャネルは任意の名前を付けられ、双方向に通信可能です。使用例は IPC チュートリアル を参照してください。

接続

より直感的に説明するため、例題の主要な接続コードは renderer.js ファイルに記述します。セキュリティ面を考慮し、インストールした MQTT モジュールは Node.js API の require メソッドを使い、preload.js ファイル内で読み込みます(上記の方法2)。また、この方法ではグローバルな window オブジェクトに注入します。

注意: Context isolation (contextIsolation) は Electron 12 以降デフォルトで有効になっています。preload スクリプトはアタッチ先の renderer と window グローバルを共有しますが、contextIsolation のため preload スクリプトから直接 window に変数をアタッチできません。

そのため、webPreferencescontextIsolation: false を設定して無効化します。

js
const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: false, // バージョン12.0.0以上はデフォルトで有効
    }
  })

これにより、読み込んだモジュールを renderer.js で直接アクセス可能になります。

  • MQTT モジュールのインポート例
javascript
// preload.js
const mqtt = require('mqtt')
window.mqtt = mqtt

TCP ポートで接続

クライアント ID、ユーザー名、パスワードを以下のコードで設定します。クライアント ID は一意である必要があります。

js
const clientId = 'emqx_vue3_' + Math.random().toString(16).substring(2, 8)
const username = 'emqx_test'
const password = 'emqx_test'

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

js
const client = mqtt.connect('mqtt://broker.emqx.io:1883', {
  clientId,
  username,
  password,
  // ...その他のオプション
})

TCP セキュアポートで接続

TLS/SSL 暗号化が有効な場合、接続のパラメーターオプションは TCP ポート接続時と同様です。プロトコルを mqtts に変更し、正しいポート番号を指定するだけで構いません。

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

js
const client = mqtt.connect('mqtts://broker.emqx.io:8883', {
  clientId,
  username,
  password,
  // ...その他のオプション
})

WebSocket ポートで接続

MQTT WebSocket は接続パスとして /path を統一的に使用します。接続時に指定が必要で、EMQX ブローカーは /mqtt をパスとして使います。

そのため、WebSocket 接続時はポート番号の変更とプロトコルを ws に切り替えるだけでなく、/mqtt パスを追加する必要があります。

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

js
const client = mqtt.connect('ws://broker.emqx.io:8083/mqtt', {
  clientId,
  username,
  password,
  // ...その他のオプション
})

WebSocket セキュアポートで接続

TLS/SSL 暗号化が有効な場合、接続のパラメーターオプションは WebSocket ポート接続時と同様です。プロトコルを wss に変更し、正しいポート番号を指定するだけで構いません。

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

js
const client = mqtt.connect('wss://broker.emqx.io:8084/mqtt', {
  clientId,
  username,
  password,
  // ...その他のオプション
})

サブスクライブとパブリッシュ

トピックのサブスクライブ

サブスクライブするトピックと対応する QoS レベル(任意)を設定し、MQTT.js の subscribe メソッドを呼び出してサブスクライブ操作を行います。

javascript
function onSub() {
  if (client.connected) {
    const { topic, qos } = subscriber
    client.subscribe(
      topic.value,
      { qos: parseInt(qos.value, 10) },
      (error, res) => {
        if (error) {
          console.error('Subscribe error: ', error)
        } else {
          console.log('Subscribed: ', res)
        }
      }
    )
  }
}

トピックのサブスクライブ解除

サブスクライブ解除時は、不要になったトピックと対応する QoS(任意)を渡す必要があります。

https://github.com/mqttjs/MQTT.js#mqttclientunsubscribetopictopic-array-options-callback

javascript
function onUnsub() {
  if (client.connected) {
    const { topic } = subscriber
    client.unsubscribe(topic.value, (error) => {
      if (error) {
        console.error('Unsubscribe error: ', error)
      } else {
        console.log('Unsubscribed: ', topic.value)
      }
    })
  }
}

メッセージのパブリッシュ

メッセージをパブリッシュする際は、MQTT ブローカーに対して対応するトピックとメッセージ内容を通知する必要があります。

https://github.com/mqttjs/MQTT.js#mqttclientpublishtopic-message-options-callback

javascript
function onSend() {
  if (client.connected) {
    const { topic, qos, payload } = publisher
    client.publish(topic.value, payload.value, {
      qos: parseInt(qos.value, 10),
      retain: false,
    })
  }
}

メッセージの受信

javascript
// onConnect 関数内
client.on('message', (topic, message) => {
  const msg = document.createElement('div')
  msg.setAttribute('class', 'message-body')
  msg.innerText = `${message.toString()}\nOn topic: ${topic}`
  document.getElementById('article').appendChild(msg)
})

MQTT ブローカーからの切断

クライアントによるアクティブな切断

https://github.com/mqttjs/MQTT.js#mqttclientendforce-options-callback

javascript
function onDisconnect() {
  if (client.connected) {
    client.end()
    client.on('close', () => {
      connectBtn.innerText = 'Connect'
      console.log(options.clientId + ' disconnected')
    })
  }
}

接続のテスト

ここまでで、Electron で作成した MQTT 5.0 クライアントツール - MQTTX(こちらも Electron で開発)を使い、メッセージの送受信をテストします。

MQTTX からクライアントにメッセージを送信すると、メッセージが正常に受信されていることが確認できます:

electronmessage.png

自作クライアントから MQTTX にメッセージを送信すると、MQTTX 側でも正常にメッセージを受信していることが確認できます:

mqttx.png

まとめ

ここまでで、Electron を使ってシンプルな MQTT デスクトップクライアントを作成し、クライアントと MQTT ブローカー間の接続、メッセージ送受信、サブスクライブ解除、切断のシナリオをシミュレーションできました。完全なサンプルソースコードは MQTT Client Electron ページからダウンロード可能です。また、MQTT Client Example ページでは他言語のデモ例も多数公開されていますので、ぜひご覧ください。