MQTT接続のアラーム装置作成

●概要

今回はネットーワーク接続されたアラーム装置を Raspberry Pi で作成する例を紹介したいと思います。アラーム装置はネットワーク接続されていて、アプリケーションプログラム等からエラーの状態をランプの点灯やブザーで知らせることができます。

ネットワーク接続のアラーム装置は市販されていますが、今回作成するものは MQTT プロトコルで接続している点が特徴です。MQTT 接続を行うことで、同時に複数のアラーム装置を同期してアプリケーションから簡単に操作できます。また、アラーム装置を利用するアプリケーションの増設も簡単に行えます。

MQTT の Will メッセージ送信対象を今回のアラーム装置に合わせると、運用中のアプリケーションを一切変更することなく、接続エラーやアプリケーションダウンを知らせることができるようになります。

●構成図

下記が、アラーム装置のネットワーク構成図です。

今回のアラーム装置は Raspberry Pi 上に作成しています。Raspberry Pi の GPIO に3つのLED (赤色、黄色、緑色) とブザーを接続して警報を知らせることが出来ます。また、スイッチを一つ GPIO に接続して、警報を止めるためのリセットスイッチとして使用します。

アラーム装置として利用する Raspberry Pi には MQTT クライアント機能をインプリメントしています。また、アラーム利用側のプログラムでも MQTT クライアント機能が必要になります。MQTT ブローカはアラーム装置と利用側アプリケーションの両方からアクセス可能なコンピュータに1つ設置します。今回はアラーム装置の Raspberry Pi に MQTT ブローカソフトのmosquitto をインストールします。

今回紹介するMQTT アラーム装置は複数台設置することも可能です。詳しい設定方法は後述します。

●ハードウエア構成(必要な部品)

MQTT 接続のアラーム装置1台につき下記の部品が必要になります。このほかにも電源やネットワークケーブル、ジャンパ等が必要ですが省略しています。。

* Raspberry Pi (ver1,ver2,ver3 のどれでもOK)   x1

* LED(なるべく高輝度のもの,赤、黄、青など好きな色) x3 (LED1,LED2,LED3)

* 抵抗 (LEDのVfに合わせて 100~2KΩ程度で調整) x3  (R1,R2,R3)

* ブザー(電源接続だけ音がなる自己発振タイプ、圧電スピーカはダメ) x1 (BZ1)

* タクトスイッチ x1 (SW1)

* コンデンサ 0.1μF x1 (C1) 省略しても構いません

* ドライバIC TD62083AP x1 (IC1)

* ブレッドボード x1

結線図は下記になります。図中の Raspberry Pi では version3 を使用していますが、Raspberry Pi version1 等を使用する場合でもピンヘッダの位置は各バージョンで共通な部分のみを使用していますので、接続する GPIO のピン位置は同じです。

今回作成するアラーム装置では警報目的のため、高輝度LED を使用したいのですがRaspberry Pi の GPIO では出力電圧・電流とも不足します。このため、シンクタイプのドライバIC を利用して Raspberry Pi Vcc 5V ピンからLED とブザーの電源を供給しています。

Raspberry Pi ver1 上で配線した様子が下記になります。GND ピンやタクトスイッチ配線で一部上記の配線図と異なっていますが電気的には同一です。

●ソフトウエア構成

MQTT 接続のアラーム装置で動作させるソフトウエアについて説明します。Raspberry Pi には標準 OS の Raspbian の最新バージョンをインストールしておきます。

Raspberry Pi のハードウエア(GPIO,I2C,SPI) を操作したり、MQTT クライアント機能、Lua スクリプトの実行環境のために、オールブルーシステムの abs_agent プログラムを使用します。abs_agent のインストールキットと詳しいマニュアルは、こちらから Raspberry Pi 用のバイナリアーカイブをダウンロードできます。個人目的であればフリー版ライセンスが同梱されていますので、期間の制限なく直ぐに使用するこができます。

今回のアラーム装置では Raspberry Pi のGPIOにアクセスしますが、abs_agent ではメモリマップされたプロセッサのレジスタに直接アクセスしています。このため Raspberry Pi にセットアップした Raspbian OS に、追加のデバイスドライバやカーネルモジュールをインストールする必要はありません。(もちろん、インストールした状態でも動作します)

アラーム装置として動作させるメインロジックや、MQTT ブローカから PUBLISH メッセージを受信した時の動作、タクトスイッチを操作したときに動作するイベントハンドラは全て Lua スクリプトで記述します。詳しいスクリプトの内容は後述します。

●MQTT ブローカのインストール

最初に、MQTT ブローカを用意します。既存の MQTT ブローカが設置されていて Raspberry Pi からネットワークアクセス可能な場合には、その MQTT ブローカを使用できます。MQTT ブローカはLAN 内にあっても、ルーター装置の外のインターネット上にあっても構いません。

既存の MQTT ブローカが無い場合にはアラーム装置として使用する Raspberry Pi に オープンソースの mosquitto をインストールします。最新版の Raspbian OS 上でインストールするのは簡単で、コンソールから下記のコマンドを実行するだけで終了します。

$sudo apt-get install mosquitto

$sudo apt-get install mosquitto-clients

上記のコマンドを実行すると、MQTT ブローカとコマンドラインから使用可能な MQTT クライアントプログラムがインストールされます。 mosquitto MQTT ブローカとクライアントプログラムの詳しい内容については mosquitto のホームページを参照してください。また、過去の記事でも mosquitto について説明していますので是非ご覧ください。

●abs_agent プログラムのインストール

オールブルーシステムのホームページのダウンロードページから最新の abs_agent インストールキットを Raspberry Pi にダウンロードしてください。このときユーザーマニュアルも同時にダウンロードすると、詳しいインストール手順や詳細仕様を事前に確認できます。インストールキットは Raspberry Pi 用のバイナリキットを選択してください。

ダウンロードしたキットファイル(tar + gzip 形式) をRaspberry Pi 上の任意のディレクトリに配置してください。ここでは例としてデフォルトユーザー “pi” のホームディレクトリ /home/pi に配置してインストールを行います。

インストール作業はコンソールから “tar zxvf <キットファイル名>” コマンドでファイルを展開するだけで終了します。上記はコマンドを実行したときの様子です。

MQTT 接続のアラーム装置として使用するためには各種設定が必要なのですが、詳しくは後述しますのでここでは早速 abs_agent サーバーを起動してみます。動作中の詳しい情報を確認するためには、ログサーバーを Windows PC にインストールする必要があるのですが、設置しなくても abs_agent は動作可能です。ログサーバーの設置方法については、インストールキットと同じページからダウンロード可能な abs_agent ユーザーマニュアルのインストールの章をご覧ください。

コンソールから abs_agent をインストールしたトップディレクトリ(今回は /home/pi/abs_agent )に移動して、abs_agent プログラムを root 特権付きで起動します。”sudo ./abs_agent” コマンドで実行します。

上記の例では abs_agent を起動した後、”agent_stat” コマンドで abs_agent の動作ステータス情報を表示しています。

ログサーバーを設置している場合には、”-l <ログサーバーホスト名またはIP アドレス>” オプションを付けて “sudo ./abs_agent -l 192.168.100.45″ の様に指定します。192.168.100.45 はログサーバーとログクライアントプログラムをインストールした Windows PC の IP アドレスです。

ログサーバーを設置した Windows コンピュータでログコンソール画面を表示していると下記の様な abs_agent 起動メッセージが表示されます。

複数のアラーム装置を設置する場合でも、同一のログサーバーを指定することで全てのログを集約して表示・管理できます。

abs_agent プログラムは、起動されると Linux のデーモンプロセスとして OS に常駐します。このため、起動後はコンソールからログアウトしても構いません。”-f” オプション付きで abs_agent を起動するとデーモンではなくフォアグランドで動作します。この場合にはログサーバーに出力するのと同じメッセージをコンソール上でも表示します。詳しくはユーザーマニュアルをご覧ください。

●MQTT エンドポイントの設定

abs_agent プログラムでは、MQTT ブローカへの接続条件や自動で購読を開始するトピック設定などの情報(MQTT エンドポイント)をコンフィギュレーションファイルに記述します。 コンフィギュレーションファイルは abs_agent をインストールしたディレクトリに abs_agent.xml ファイルとして保存されていますので、これを編集して新規の MQTT エンドポイントを追加します。

abs_agent プログラムが実行中の場合でも abs_agent.xml 設定ファイルは何時でも編集することができます。ただし、新しい設定ファイルを有効にするためには abs_agent プログラムを再起動させる必要があります。今回接続する MQTT ブローカ(mosquitto) はアラーム装置の Raspberry Pi で動作していますので、これに接続するための設定を追加します。

以降は、アラーム装置(Raspberry Pi) の IP アドレスが 192.168.100.14 になっているものとして説明しますので、御自分の環境に合わせて適宜設定を変更してください。また、MQTT ブローカを動作させるコンピュータには必ず、LAN 内または Internet 上の固定 IP アドレスまたはアクセス可能なホスト名を割り当てる必要があります。MQTT クライアントとして動作させるだけのアラーム装置には固定 IP を割り当てる必要はなく DHCP で割り振っても問題ありません。これらは、常に MQTT クライアントがMQTT ブローカ側へのソケット接続を行うためです。今回の作成例では、MQTT ブローカとMQTT クライアントのアラーム装置は同じ Raspberry Pi に設置しています。

下記が、コンフィギュレーションファイル例です。

<?xml version="1.0" encoding="utf-8"?>
<Document xmlns="http://www.allbluesystem.com/xasdl">
  <Description>ABSAgent コンフィギュレーション</Description>
  <ServiceMain>
    <PortNumber type="integer">27101</PortNumber>
    <DefaultRemoteHost type="string">127.0.0.1</DefaultRemoteHost>
    <TimeStampMargin type="integer">0</TimeStampMargin>
    <AllowFileUpload type="boolean">True</AllowFileUpload>
    <UseMACProtection type="boolean">False</UseMACProtection>
  </ServiceMain>
  <Class>
    <Basic>
      <ServerKey type="string"></ServerKey>
      <LicenseKey type="string">..... この部分のライセンス文字列は使用中のファイルのものを使用します ....</LicenseKey>
      <AllowFileOperation type="boolean">False</AllowFileOperation>
    </Basic>
    <Convert>
      <AutoOnline type="boolean">True</AutoOnline>
    </Convert>
    <Masters>
      <AutoOnline type="boolean">True</AutoOnline>
      <MasterFile type="string">/home/pi/abs_agent/masters.xml</MasterFile>
      <XMLSessionPool type="integer">4</XMLSessionPool>
    </Masters>
    <Session>
      <AutoOnline type="boolean">True</AutoOnline>
    </Session>
    <Script>
      <AutoOnline type="boolean">True</AutoOnline>
      <ScriptFolder type="string">/home/pi/abs_agent/scripts</ScriptFolder>
      <SessionPool type="integer">16</SessionPool>
      <UsePeriodicTimer type="boolean">True</UsePeriodicTimer>
    </Script>
    <WebProxy>
      <AutoOnline type="boolean">True</AutoOnline>
    </WebProxy>
    <Serial>
      <AutoOnline type="boolean">True</AutoOnline>
      <DeviceList/>
    </Serial>
    <MQTT>
      <AutoOnline type="boolean">True</AutoOnline>
      <KeepAliveTimer type="integer">60</KeepAliveTimer>
      <EndPointList>
        <Item>
          <Title>アラーム装置MQTT接続</Title>
          <ClientID>alarm_123456</ClientID>
          <BrokerHostName>192.168.100.14</BrokerHostName>
          <PortNumber>1883</PortNumber>
          <AutoSubscribeTopicList>/alarm/#</AutoSubscribeTopicList>
          <AutoSubscribeQoSList>0</AutoSubscribeQoSList>
          <UserName/>
          <Password/>
          <WillTopic/>
          <WillMessage/>
          <WillQoS>0</WillQoS>
          <WillRetain>False</WillRetain>
          <RecvBuffInit>0</RecvBuffInit>
          <DetailLog>True</DetailLog>
        </Item>
      </EndPointList>
    </MQTT>
    <RASPI>
      <AutoOnline type="boolean">True</AutoOnline>
    </RASPI>
  </Class>
</Document>

設定ファイル中の <MQTT> タグ内の <EndPointList>..</EndPointList> で囲まれた <Item>..<Item> タグ内にMQTT ブローカへの接続条件(MQTTエンドポイント情報)を記載します。使用するタグの名前とデータ値の詳しい説明は abs_agent ユーザーマニュアルに記載されています。

MQTT エンドポイント設定の <ClientID>alarm_123456</ClientID> タグに書かれた内容は、同一 MQTTブローカに接続する MQTT クライアント毎にユニークな任意の文字列で置き換えてください。また、<BrokerHostName>192.168.100.14</BrokerHostName>部分は、使用する MQTT ブローカのホスト名または IP アドレスを設定します。<Title>アラーム装置MQTT接続</Title> 部分は、abs_agent の Lua スクリプトから MQTT エンドポイントを指定するときの名前になります。このタイトル名をパラメータに指定して、MQTT ブローカにデータを送信するライブラリ関数をコールします。

abs_agent 設定ファイルでは、その他のサーバー設定やシリアルデバイスの設定を行うこともできます。今回はMQTT のエンドポイントを1つ追加するだけで設定は完了します。abs_agent をインストールしたディレクトリの docs ディレクトリには、設定ファイルの記述例がテキストファイルで保存されていますので、これをコピー&ペーストで利用すると簡単に設定ファイルを編集できます。

●MQTT PUBLISH メッセージ受信時のイベントハンドラ設定

次に、MQTT ブローカからPUBLISH メッセージを受信したときの処理を記述します。abs_agent プログラムは前項で設定したエンドポイント設定に従って、起動時に MQTT ブローカへのソケット接続、MQTT-CONNECT 処理、MQTT-SUBSCRIBE リクエストが実行されます。自動購読設定では今回 “/alarm/#” トピックを指定していますので、先頭に “/alarm/” の文字列から始まるトピック名で作成された、下記の様なメッセージが MQTT ブローカに渡されたときにそのメッセージが配信されてきます。

(1) アラーム状態を更新するメッセージ例

トピック名   /alarm/signal/<任意の文字列>     メッセージ {“alarm_red”:”on” }

(2) アラーム装置の警報を停止させるメッセージ例

トピック名 /alarm/switch/<任意の文字列>    メッセージ {“sw1″:”on”}

上記(1) はアラーム装置のLED とブザーの出力ステートを変更するための JSON フォーマットの文字列メッセージです。今回のアラーム装置では幾つかのキー・値ペアのエントリを列挙することでアラーム装置を操作できる様に設計しました。

キー値はアラーム装置の3色の LED (“alarm_red”, “alarm_yellow”, “alarm_green”) とブザー(“alarm_buzz”)に対応します。キーに対応する値は LED の場合には 点灯”on” , 消灯 “off”, 点滅 “blink” の何れかを指定できます。ブザーの場合には停止 “off”, ピピピ音 “beep1″, ピーピー音 “beep2″ の値を指定できます。もし受信した JSON メッセージ中に各ハードウエアに該当するキー・値ペアが無い場合には現在の出力状態のまま変更しません。

MQTT ブローカから PUBLISH パケットを受信すると、MQTT_PUBLISH イベントハンドラスクリプトが自動的に実行されます。このイベントハンドラ中に上記の動作を行う様に処理を記述します。今回作成する MQTT_PUBLISH イベントハンドラの内容は abs_agent インストール時のデフォルトイベントハンドラスクリプト中にコメント文として書き込まれていますので、コメントアウトするだけで編集は完了します。スクリプトファイル名は <abs_agentをインストールしたディレクトリ>/scripts/MQTT_PUBLISH.lua です。

file_id = "MQTT_PUBLISH"

--[[

******************************************************************************
* イベントハンドラスクリプト実行時間について                                 *
******************************************************************************

一つのスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
処理に時間がかかると、イベント処理の終了を待つサーバー側でタイムアウトが発生します。

また、同時実行可能なスクリプトの数に制限があるため、他のスクリプトの実行開始が
待たされる原因にもなります。

頻繁には発生しないイベントで、処理時間がかかるスクリプトを実行したい場合は
スクリプトを別に作成して、このイベントハンドラ中から script_fork_exec() を使用して
別スレッドで実行することを検討してください。

******************************************************************************

MQTT_PUBLISH スクリプト起動時に渡される追加パラメータ

---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
ClientID		エンドポイントの ClientID 文字列						"abs9k:2222-eagle"

Title			エンドポイントに設定されたタイトル文字列。
				タイトル文字列が設定されていない場合には、"" 空文字列
				が入ります												"センサーデバイス#1"

MessageType		MQTT プロトコルで定義されたメッセージタイプが入ります。	"3"
				PUBSLIH メッセージの場合には常に "3"が設定されます

MessageID		Brokerから送信するときに使用された MQTT メッセージID が
				入ります。(QoS = 1 または QoS = 2 の場合) 値は "1" から
				"65535" の整数値をとります。
				QoS = 0 の場合には常に "0" が設定されます。				"1234"

Dup				MQTT 固定ヘッダ中の Dup フラグの値が設定されます。
				"0" または "1" の値をとります。							"0"

QoS				MQTT 固定ヘッダ中の QoS フラグの値が設定されます。
				"0", "1", "2" の何れかの値をとります。					"0"

Retain			MQTT 固定ヘッダ中の Retain フラグの値が設定されます。
				"0" または "1" の値をとります。							"0"

PublishTopic	MQTT ブローカから受信した PUBLISH メッセージ中の Topic
				文字列。												"センサー/ノード1"

PublishData		MQTT ブローカから受信した PUBLISH メッセージ中のペイロー
				ドデータ。
				バイナリデータを16進数文字列に変換したものが格納されます
				ペイロードデータに格納されたデータが UTF-8 文字列の場合
				には文字列コードのバイト列が格納されています。
				イベントハンドラ中でこれらの文字列データをデコードする処
				理がデフォルトで記述されていますので、UTF-8 文字列を扱う
				場合にはデコード後の変数を利用することができます。		"010203414243"

PublishDataで渡されたペイロードデータを解析して作成される文字列変数

PublishString	PublishData に格納されたペイロードデータ部分のサイズが
				2048 Bytes以内の場合に、データバイト列を UTF-8形式で
				文字列にデコードした結果を PublishString に格納します。
				変換対象のバイト列のサイズを変更したいときには該当する
				スクリプト部分を変更して下さい。

]]

------------------------------------------------------------------------------------------
-- 受信したペイロードデータのサイズが 2048 bytes 以内の場合には
-- バイナリデータ列を UTF-8 文字列としてデコードしたものを PublishString 変数に格納する
------------------------------------------------------------------------------------------
local PublishString = ""
local pub_len = string.len(g_params["PublishData"]) / 2
if pub_len < 2048 then
	PublishString = readUTF_hex(bit_tohex(pub_len,4) .. g_params["PublishData"])
end

log_msg(g_params["Title"] .. "[" .. g_params["ClientID"] .. "] msg:" .. g_params["MessageID"] .. " dup:" .. g_params["Dup"]  ..
" retain:" .. g_params["Retain"]  .. " qos:" .. g_params["QoS"]  .. " topic:" .. g_params["PublishTopic"]  .. " " .. PublishString,file_id)

-- MQTTアラーム装置のメッセージ解析
json = require('json')
local flag,sender = string.match(g_params["PublishTopic"],"/alarm/(.+)/(.+)")
if flag == "switch" then
	------------------------------------------------------------------------------------------------------------------
	-- トピックが "/alarm/switch/<送信側ホスト名または任意の文字列>" の場合にメッセージを JSON デコードして、
	-- "sw1" キーの値が "on" の場合にはグローバル共有変数 alarm_red, alarm_yellow, alarm_green, alarm_buzz を削除する
	------------------------------------------------------------------------------------------------------------------
	local msg = json.decode(PublishString)
	if msg.sw1 == "on" then
		set_shared_data("alarm_red","")
		set_shared_data("alarm_yellow","")
		set_shared_data("alarm_green","")
		set_shared_data("alarm_buzz","")
	end
end

if flag == "signal" then
	---------------------------------------------------------------------------------------------------------
	-- トピックが "/alarm/signal/<送信側ホスト名または任意の文字列>" の場合にメッセージを JSON デコードして、
	-- 得られたオブジェクトのキーと値をそのままグローバル共有変数に設定する。
	---------------------------------------------------------------------------------------------------------
	local msg = json.decode(PublishString)
	for k,v in pairs(msg) do
		set_shared_data(k,v)
	end
end

このイベントハンドラは MQTT ブローカから購読中の全てのトピックに対応する PUBLISH パケットを受信したときに共通で実行されます。このため、最初に下記の正規表現を利用したパターンマッチ関数でトピック名の部分文字列取り出して、アラーム装置の処理対象メッセージであるかを判断します。

local flag,sender = string.match(g_params["PublishTopic"],”/alarm/(.+)/(.+)”)

アラーム装置操作用のトピック文字列を指定してメッセージが配信されてきた場合には、flag 変数には “signal” または “switch” の文字列が格納されて、sender 変数には送信元ホスト名や任意の文字列が格納されます。パターンマッチに失敗するような、これ以外のトピック名の場合には以降の処理は行いません。

アラーム装置の状態を更新する “/alarm/signal/<任意の文字列>” のトピック名の場合には、JSON メッセージをデコードした後、列挙されたキーと値のペアをそのまま abs_agent のグローバル共有変数のキーと値にそれぞれセットします。Raspberry Pi の GPIO(LED、ブザー) を操作する部分は別スクリプトとして別途作成することにして、ここではフラグ(グローバル共有変数)の更新のみを行うようにしています。この様な構成にすることで機能を拡張しやすくなり、プログラム(スクリプト)の内容を簡単にすることができます。

アラーム装置をリセットする “/alarm/switch/<任意の文字列>” のトピック名のメッセージを受信した場合には、JSON メッセージをデコードした後、キー値が “sw1″, 値が “on” の時に上記のグローバル共有変数を全て削除します。

●アラーム装置として動作するためのスクリプトファイル作成

次に、上記の MQTT_PUBLISH イベントハンドラで更新されたアラーム状態を示すグローバル共有変数の値を定期的に監視して、Raspberry Pi の GPIO を操作するスクリプトを作成します。

スクリプト名は “RASPI/ALARM_TASK” で、<abs_agentをインストールしたディレクトリ>/scripts/RASPI/ALARM_TASK.lua に格納されていますので作成する必要はありません。スクリプトの内容は以下の様になっています。

--[[

●機能概要

グローバル共有変数にセットされたアラーム状態を示すフラグ値に対応して、
Raspberry Pi の GPIO に接続された LEDの点滅やブザーを鳴らす。

●参照するグローバル共有変数

---------------------------------------------------------------------------------
キー値            値                              操作するGPIO# と設定値
---------------------------------------------------------------------------------
"alarm_red"        "off" or "" (変数未定義)    GPIO#4  Low
                "on"                           GPIO#4  High
                "blink"                        GPIO#4  Low-High 繰り返し

"alarm_yellow"    "off" or "" (変数未定義)     GPIO#17 Low
                "on"                           GPIO#17 High
                "blink"                        GPIO#17 Low-High 繰り返し

"alarm_green"   "off" or "" (変数未定義)       GPIO#18  Low
                "on"                           GPIO#18  High
                "blink"                        GPIO#18  Low-High 繰り返し

"alarm_buzz"    "off" or "" (変数未定義)       GPIO#27  Low
                "beep1"                        GPIO#27  Low-High 早い繰り返し
                "beep2"                        GPIO#27  Low-High 遅い繰り返し

●備考

このスクリプトは無限ループに入って終了しないので、必ず別スレッドで起動してください。

●変更履歴

2016/08/23    初版作成

copyright(c) All Blue System

]]

-- 2重起動防止用チェック
if not exclusive_check(g_script) then
    log_msg("*ERROR* exclusive_check() failed. script = " .. g_script,file_id)
    return
end
log_msg("start..   TaskID = " .. g_taskid,g_script)

-- アラーム状態出力用の Raspberry Pi GPIO ピン番号設定
local RED_pin = 4
local YELLOW_pin = 17
local GREEN_pin = 18
local BUZZ_pin = 27
local SW_pin = 23
local short_timer_interval = 80      -- beep1 ブザー音間隔(ms)
local long_timer_repeat_count = 8    -- beep2 ブザー音間隔、LED 点滅間隔、グローバル共有変数のチェック間隔
                                      -- short_timer_interval の繰り返し回数で指定する

-- グローバル共有変数の最新の値のキャッシュ, beep2 間隔でグローバル共有変数から最新の値をロードして更新される
local alarm_red = ""
local alarm_yellow = ""
local alarm_green = ""
local alarm_buzz = ""

local led_stat = false            -- LEDとブザー(beep2) High - Low 繰り返しの現在のステート値
local buzz_stat = false           -- ブザー(beep1) High - Low 繰り返しの現在のステート値

-- LED と スイッチを接続する GPIO のモードを初期設定する。
if not raspi_gpio_config(RED_pin,"output","off") then error() end
if not raspi_gpio_config(YELLOW_pin,"output","off") then error() end
if not raspi_gpio_config(GREEN_pin,"output","off") then error() end
if not raspi_gpio_config(BUZZ_pin,"output","off") then error() end
if not raspi_gpio_config(SW_pin,"input","pullup") then error() end

-- スイッチ入力を検知するために RASPI_CHANGE_DETECT イベントを有効にする
if not raspi_change_detect(SW_pin,true) then error() end

-- short_timer_interval 間隔で実行
function short_cyc_task()

    -- ブザー(beep1) の状態を反転する
    buzz_stat = not buzz_stat

    -- ブザー(beep1)の GPIO 出力
    if alarm_buzz == "beep1" then
        if not raspi_gpio_write(BUZZ_pin,buzz_stat) then error() end
    elseif (alarm_buzz == "") or (alarm_buzz == "off") then
        if not raspi_gpio_write(BUZZ_pin,false) then error() end
    end

end

-- short_timer_interval * long_timer_repeat_count 間隔で実行
function long_cyc_task()
    local stat

    -- 最新のグローバル共有変数の値をキャッシュにロード
    stat,alarm_red = get_shared_data("alarm_red")
    stat,alarm_yellow = get_shared_data("alarm_yellow")
    stat,alarm_green = get_shared_data("alarm_green")
    stat,alarm_buzz = get_shared_data("alarm_buzz")

    -- LED の状態を反転する
    led_stat = not led_stat

    -- LED とブザー(beep2)の GPIO 出力
    if alarm_red == "on" then
        if not raspi_gpio_write(RED_pin,true) then error() end
    elseif alarm_red == "blink" then
        if not raspi_gpio_write(RED_pin,led_stat) then error() end
    else
        if not raspi_gpio_write(RED_pin,false) then error() end
    end

    if alarm_yellow == "on" then
        if not raspi_gpio_write(YELLOW_pin,true) then error() end
    elseif alarm_yellow == "blink" then
        if not raspi_gpio_write(YELLOW_pin,led_stat) then error() end
    else
        if not raspi_gpio_write(YELLOW_pin,false) then error() end
    end

    if alarm_green == "on" then
        if not raspi_gpio_write(GREEN_pin,true) then error() end
    elseif alarm_green == "blink" then
        if not raspi_gpio_write(GREEN_pin,led_stat) then error() end
    else
        if not raspi_gpio_write(GREEN_pin,false) then error() end
    end

    if alarm_buzz == "beep2" then
        if not raspi_gpio_write(BUZZ_pin,led_stat) then error() end
    elseif (alarm_buzz == "") or (alarm_buzz == "off") then
        if not raspi_gpio_write(BUZZ_pin,false) then error() end
    end

end

-- メインタスク、グローバル変数の値を定期的に監視してアラーム出力を行う
local cntr_long_timer = 0
while true do -- 無限ループを停止させる場合には script_kill() または "agent_task -k <taskid>" コマンドを使用する
    wait_time(short_timer_interval)
    if cntr_long_timer >= (long_timer_repeat_count - 1) then
        cntr_long_timer = 0
        long_cyc_task() -- 約 640ms 間隔で実行
    else
        cntr_long_timer = cntr_long_timer + 1
    end
    short_cyc_task() -- 約 80ms 間隔で実行
end

このスクリプトの先頭では2重起動のチェックを下記の文で行っています。

-- 2重起動防止用チェック
if not exclusive_check(g_script) then
    log_msg("*ERROR* exclusive_check() failed. script = " .. g_script,file_id)
    return
end

このスクリプトは直ぐに無限ループに入って、共有変数の値の監視と GPIO 操作を繰り返す動作になります。起動時には別スレッドで実行させて、スクリプト実行スレッドが常駐する形になります。このため、間違って複数回このスクリプトをバックグランドで実行しないようにチェックしています。

次に、下記の部分で Raspberry Pi の GPIO ポートの初期化を行っています。LED とブザーを接続している GPIO ポートは出力モードでプルアップ抵抗を切り離しています。タクトスイッチを接続している GPIO ポートは入力モードにして、プルアップを有効にします。

-- LED と スイッチを接続する GPIO のモードを初期設定する。
if not raspi_gpio_config(RED_pin,"output","off") then error() end
if not raspi_gpio_config(YELLOW_pin,"output","off") then error() end
if not raspi_gpio_config(GREEN_pin,"output","off") then error() end
if not raspi_gpio_config(BUZZ_pin,"output","off") then error() end
if not raspi_gpio_config(SW_pin,"input","pullup") then error() end

-- スイッチ入力を検知するために RASPI_CHANGE_DETECT イベントを有効にする
if not raspi_change_detect(SW_pin,true) then error() end

タクトスイッチを操作したときに GPIO 値が変化するので、これを検出して abs_agent の RASPI_CHANGE_DETECT イベントハンドラを自動実行するように設定しています。このイベントハンドラの内容も後で編集します。

下記の部分がメインループになります。80ms 間隔で short_cyc_task() 関数を実行して、640ms 間隔で long_cyc_task() 関数を実行します。long_cyc_task() 関数内では現在のグローバル共有変数にセットされたアラーム状態を示すフラグ値をローカル変数にロードして、LED の点滅やブザーのON-OFF のタイミングを判断しています。詳しい処理内容は前述の全体のスクリプト中のコメントをご覧ください。

-- メインタスク、グローバル変数の値を定期的に監視してアラーム出力を行う
local cntr_long_timer = 0
while true do -- 無限ループを停止させる場合には script_kill() または "agent_task -k <taskid>" コマンドを使用する
    wait_time(short_timer_interval)
    if cntr_long_timer >= (long_timer_repeat_count - 1) then
        cntr_long_timer = 0
        long_cyc_task() -- 約 640ms 間隔で実行
    else
        cntr_long_timer = cntr_long_timer + 1
    end
    short_cyc_task() -- 約 80ms 間隔で実行
end

この RASPI/ALARM_TASK スクリプトは、abs_agent 起動時に別スレッドで自動実行するように後で設定します。

●タクトスイッチ操作時のイベントハンドラ設定

アラーム装置のタクトスイッチが押されたときの動作を記述します。 RASPI/ALARM_TASKスクリプト最初の部分で、下記の文が実行されると GPIO#23 ピンが変化する度に、RASPI_CHANGE_DETECT イベントハンドラスクリプトが実行されます。

raspi_change_detect(SW_pin,true)

この RASPI_CHANGE_DETECT スクリプトを編集してタクトスイッチを操作したときに、MQTT ブローカに対してトピック名  /alarm/switch/<ホスト名>  でメッセージ {“sw1″:”on”} または{“sw1″:”off”} を送信するようにします。

このイベントハンドラの内容は abs_agent インストール時のデフォルトイベントハンドラスクリプト中にコメント文として書き込まれていますので、コメントアウトするだけで編集は完了します。スクリ プトファイル名は <abs_agentをインストールしたディレクトリ>/scripts/RASPI_CHANGE_DETECT.lua です。

file_id = "RASPI_CHANGE_DETECT"

--[[

******************************************************************************
* イベントハンドラスクリプト実行時間について                                 *
******************************************************************************

一つのスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
処理に時間がかかると、イベント処理の終了を待つサーバー側でタイムアウトが発生します。

また、同時実行可能なスクリプトの数に制限があるため、他のスクリプトの実行開始が
待たされる原因にもなります。

頻繁には発生しないイベントで、処理時間がかかるスクリプトを実行したい場合は
スクリプトを別に作成して、このイベントハンドラ中から script_fork_exec() を使用して
別スレッドで実行することを検討してください。

******************************************************************************

RASPI_CHANGE_DETECT スクリプト起動時に渡される追加パラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------

BitNumList		GPIO の監視対象ビット中で変化したGPIO ピン番号が入る
				複数同時に変化した場合にはカンマ区切りのリストになる。 18,22,23
				0 から 53 までの整数が入る

BitValList		BitNumList に格納されている GPIO ピンの変化後の現在の値。
				カンマ区切りのリストで表される場合にはBitNumList のピン番号
				並びと値の並びが対応しています。						0,1,1
				0 または 1 の値が入る

このスクリプト中で作成される配列
---------------------------------------------------------------------------------
変数名			説明
---------------------------------------------------------------------------------

change_bit[] 	数値配列。キーが GPIO ピン番号の整数で、配列の値は
				GPIO の現在値を示す。 0(low) または 1(high) の整数値
				たとえば上記の BitNumList, BitValList の "値の例" の場合には
				change_bit[] 配列には下記の値が格納されます。

				change_bit[18] = 0
				change_bit[22] = 1
				change_bit[23] = 1

				** メモ **
				Lua では存在しないキーの配列エントリにアクセスすると、
				値は nil が返ります。上記の例では change_bit[0] = nil です。

]]

log_msg("start..",file_id)

--------------------------
-- change_bit[] 配列作成
--------------------------
local arr_pos = csv_to_tbl(g_params['BitNumList'])
local arr_val = csv_to_tbl(g_params['BitValList'])
local change_bit = {}
for i,v in ipairs(arr_pos) do
	change_bit[tonumber(v)] = tonumber(arr_val[i])
end

----------------------------------------------
-- change_bit[] 配列内容確認のためログ出力
----------------------------------------------
for key,val in pairs(change_bit) do
	log_msg(string.format("change_bit[%d] = %d",key,val),file_id)
end

if change_bit[23] then -- SW を操作した?
	local msg
	local clientid = "アラーム装置MQTT接続"
	local topic = "/alarm/switch/" .. g_hostname
	if change_bit[23] == 0 then
		msg = '{"sw1":"on"}'
	else
		msg = '{"sw1":"off"}'
	end
	if not mqtt_publish(clientid,topic,msg,0) then error() end -- MQTTブローカに SW の状態を送信
end

このスクリプトでは GPIO#23 が操作されたかを判断した後、タクトスイッチを押し込んだ状態と離した状態の2つのタイミングで MQTT ブローカにメッセージを送信しています。先に説明した MQTT_PUBLISH イベントハンドラでは、押し込んだ状態のメッセージ {“sw1″:”on”} のみを選択してアラーム状態のフラグをクリアしています。

実は、このスクリプト中で MQTT ブローカにメッセージを送信しなくても、スイッチを操作したアラーム装置の状態のみをクリアするのであれば直接フラグの値を削除しても同じ動作になります。

今回の様に  MQTT ブローカ経由でスイッチ操作の処理を行うことで、複数のアラーム装置のリセットを連動させることが可能になります。送信するトピック名を工夫すると、アラーム操作やリセットで連動するアラーム装置を限定したグループに分けることもできます。

●アラーム装置タスクの自動起動設定

abs_agent 起動時に、自動的に RASPI/ALARM_TASK スクリプトを別スレッドで実行するように設定します。abs_agent が起動されてデバイスやサービスの準備が完了すると、SERVER_START イベントハンドラが1回だけ自動実行されます。

ここではその SERVER_START イベントハンドラを編集して RASPI/ALARM_TASK  スクリプトを起動するようにします。このイベントハンドラの内容は abs_agent インストール時のデフォルトイベントハンドラスクリプト中にコメント文として書き込まれていますので、コメントアウトするだけで編集は完了します。スクリ プトファイル名は <abs_agentをインストールしたディレクトリ>/scripts/SERVER_START.lua です。

file_id = "SERVER_START"

--[[
******************************************************************************
このスクリプトは abs_agent 起動時にコールされます
このスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
******************************************************************************
]]

log_msg("start..",file_id)

--[[
******************************************************************************

MQTT モジュールが有効な場合にエンドポイントの接続を開始させる

******************************************************************************
]]
local mstat,module_stat = service_module_status()
if not mstat then error() end
if module_stat["MQTT"] then
	script_fork_exec("MQTT_CONNECT_ALL","","")
end

-- Raspberry Pi GPIO アラームタスク起動
script_fork_exec("RASPI/ALARM_TASK","","")

script_fork_exec() ライブラリ関数は、abs_agent に設置された任意のスクリプトを別スレッドで起動するためのライブラリ関数です。スクリプトの前半部分には、MQTT ブローカへの接続処理がデフォルトで記述されていますが、この部分はこのまま変更しないでください。

●abs_agent の再起動

abs_agent の設定と、イベントハンドラ、ユーザースクリプトの準備が完了しましたので、abs_agent プログラムを再起動させます。

コンソールから下記のコマンドを入力して、 abs_agent を再起動させます。

agent_shutdown コマンドを使用して abs_agent プログラムを停止させます。実行中のスクリプトがある場合には全て停止させた後、全てのサービスモジュールが終了します。abs_agent が完全に停止まで数秒かかりますので、agent_shutdown コマンド実行後少し待ってから、agent_stat プログラムを実行してサーバーから応答が無いこと(ソケットエラーが発生)を確認しています。ログコンソールを表示しているとサーバー停止状態をメッセージで確認できます。その後、abs_agent プログラムを手動で起動します。

Raspberry Pi の電源を入れたときに abs_agent プログラムを自動起動させる場合には、OS の /etc/rc.local シェルスクリプトに起動コマンドを記述します。詳しい設定方法は abs_agent ユーザーマニュアルをご覧ください。/etc/rc.local はシステム起動プロセスで実行されますので、abs_agent プログラム起動時に “sodo” コマンドを併用する必要はありません。また、日本語をabs_agent プログラム内で扱うために LANG 環境変数をセットしている点に注意して下さい。

abs_agent が起動すると、ログコンソールには下記の様なメッセージが表示されます。

abs_agent 設定ファイルに記載した MQTT エンドポイント設定に従って、MQTT ブローカに接続する様子がログメッセージに出力されます。また、アラーム装置として動作させる RASPI/ALARM_TASK スクリプトが別スレッドで自動起動されています。別スレッドで実行中のスクリプトは agent_task コマンドで確認できます。agent_task コマンドでは、実行中のスクリプト・タスクを強制終了させることもできます。詳しくは abs_agent ユーザーマニュアルをご覧ください。

これで MQTT 接続のアラーム装置が完成しました。

●アラーム装置で警報ランプやブザーを鳴らす

早速アラーム装置を操作してみましょう。Raspberry Pi にインストールした MQTT ブローカのクライアントプログラム mosquitto_pub コマンドを使用して、アラーム装置を操作するメッセージをMQTT PUBLISH パケットに格納して送信してみます。

このコマンド例では、赤色 LED と 緑色 LED をそれぞれ点滅状態にセットしています。

メッセージ中の “blink” 文字列部分を “on” や “off” に変えると常時点灯や消灯になります。ブザーを鳴らす場合には、メッセージ中に下記の何れかのエントリを指定します。

“alarm_buzz”:”beep1″     短い繰り返し音(ピピピピ…)

“alarm_buzz”:”beep2″  長い繰り返し音(ピーピーピー…)

“alarm_buzz”:”off”     ブザー停止

ブザーはかなり大きな音がしますので、最初はマスキングテープなどで穴を塞いでおくことをお勧めします。

MQTT ブローカに接続可能な様々なクライアントからメッセージを送信してみてください。ちなみに以前の記事で紹介した Windows 版のMQTT クライアントプログラムから送信している様子は下記になります。

●リセットスイッチで警報を停止させる

アラームを停止させる場合には、アラーム装置のタクトスイッチを押します。複数のアラーム装置を設置している場合でも、何れか1つのアラーム装置のタクトスイッチを押すだけで、全てのアラーム装置のLED と ブザーが停止することを確認してください。

アラーム装置のLED とブザーの停止は、MQTT クライアントプログラムからメッセージを送信することでも操作できます。このとき2つのタイプのメッセージで操作できます。

1つ目の方法は、先ほど LED の点滅や点灯時に指定した様に、全ての LED とブザーに対して “off” を指定したメッセージを送信する方法です。

もう一つの方法はタクトスイッチを押し込んだ時に自動送信されるメッセージを、他のMQTT クライアントから同様に送信する方法です。下記は、mosquitto_pub プログラムでリセット操作用のメッセージを送信している様子です。

●複数のアラーム装置を設置する

今回作成した MQTT 接続のアラーム装置は、同一 MQTT ブローカに接続するように複数台設置することができます。この場合には、abs_agent の設定ファイル中に記述する MQTT エンドポイントの設定項目中の下記のタグの内容を変更してください。

<ClientID>alarm_123456</ClientID>

MQTT エンドポイント毎にユニークな文字列であれば何でも構いません。その他のスクリプトやイベントハンドラの内容は一切変更する必要はありません。

複数設置した場合でも、アラーム装置を利用する側のメッセージ送信は全く同じです。これはアラーム装置の追加や削除、入れ替えなどの作業を、クライアントプログラム側の設定を一切変更なく運用できることを示しています。

複数のアラーム装置を同時にリセットする場合でも、他のアラーム装置に影響なくどれか1台のタクトスイッチを操作するだけで自動で全てのアラーム装置が連動してリセットされる点も特徴です。

●Will メッセージの送信対象にアラーム装置を指定する

MQTT ブローカに接続するときには、遺言メッセージ(Will Message)を指定する機能があります。この Will Message を今回設置したアラーム装置操作用のメッセージにすると、MQTT ブローカへの接続が切れたときに自動的にアラームを鳴らすことができます。

下記は、以前の記事で紹介した Windows 版の MQTT プログラムで、CONNECT リクエストを実行するときに Will Message の設定をアラーム装置用に合わせている様子です。

上記は、クライアントプログラムの “Open” ボタンを押して、MQTT ブローカとソケット接続した後、CONNECT リクエストを2回繰り返して強制的にブローカとのソケットを切断させて Will メッセージをテスト送信している様子です。

MQTT ブローカを利用したアプリを作成する場合に、CONNECT コマンド送信部分の Will Topic と Will Message パラメータを、今回設置したアラーム装置のメッセージに合わせるだけで簡単にサーバーダウン時に警報を出す機能を追加することができます。

●応用・拡張

今回作成したアラーム装置は、お手持ちの Raspberry Pi に LEDとブザーを接続するだけで簡単に作成することができます。使用している abs_agent プログラムは、個人目的であればフリーライセンスとして期間の制限なく使用できますので是非ご利用ください。

Raspberry Pi の余っている GPIO にセンサーを接続したり、Wi-Fi 付きの安価なマイコンと組み合わせると、戸締り警報装置や、雨降りお知らせ装置なども簡単に実現できます。また、インターネット上の MQTT ブローカと組み合わせると、遠い場所で暮らしている家族を見守ることもできます。

アラーム装置で使用するLED の種類や発光パターンを増やすのも、スクリプトファイルを少し変更するだけで簡単に対応できます。また、Raspberry Pi に LCD 表示器を取り付けると、メッセージ表示機能付きのアラーム装置に改造することもできます、ぜひチャレンジしてみてください。

ご意見や質問がありましたら、お気軽にメールをお寄せください(contact@allbluesystem.com) 。

それではまた。

 

TOCOS TWE無線タグのセンサデータを MQTT Broker に送信、TWE-MQTT Gateway

●概要

リモートに設置した TOCOS TWE無線タグ・デバイスのセンサデータを MQTT Broker に送信するシステム構築例を紹介します。このシステムは TWE-Lite や TWE-Lite-2525A 等で動作する無線タグアプリ・ファームウエア(Samp_Monitor) を動作させて、これらのリモートデバイスに接続したセンサデータを MQTT Broker に送信します。

●子機の配線

リモートに設置する無線タグデバイス・子機には TWE-Lite DIP を使用しています。東京コスモス電機のホームページで公開されている TWE-Zero アプリの “無線タグアプリ(Samp_Monitor)” を書き込んで使用します。

無線タグファームウエアに設定するセンサ種別は、デフォルト設定のアナログセンサモードで使用しています。アナログ入力AI3(a2タグ) ピンには光センサを接続しています。

A/D の入力電圧が 0-2.4V なので、これを超えないようにフォトダイオードの負荷抵抗を調整してください。上記の回路では2つの5.6K の抵抗で分圧したものを AI3 に接続しています。

子機は複数同時に使用することもできます。また、ファームウエアに設定するセンサー種別を変えて、 I2C 接続の温度センサや気圧センサなどを使用することもできます。この場合には、自動的に MQTT Broker に送信する JSON フォーマットがのタグ名が対応する名前に変更されます。

●親機の配線

子機からのセンサデータを受信する親機を準備します。子機と同様に TWE-Lite DIP デバイスを使用しています。PC の USB ポートに直接接続できる スティックタイプの TWE デバイスや TWE ライタデバイスを親機として使用できます。ここでは、TWE-Lite DIP をブレッドボード上に配置して、USB シリアルアダプタ(秋月電子: AE-UM232R)経由で PC に接続しています。

子機側の TWE-Lite DIP ファームウエアの書き換えや、コンフィギュレーション設定にも上記の回路を使用します。このため、リセットボタンとプログラム書き込み用のボタンも配置しておきます。TWE-Lite DIP デバイスの電源は USB シリアルアダプタから供給される 3.3V を使用しています。

●親機の設定

最初に親機側の TWE-Lite DIP デバイスを設定します。購入直後の TWE-Lite DIP ファームウエアには “超簡単!TWEアプリ” が入っていますが、今回はこれを書き換えて “無線タグ専用の親機ファームウエア” を書き込みます。ファームウエアは東京コスモス電機の TWE-Zero アプリのページで公開されていますのでこれを使用します。詳しい書き込み方法はファームウエアのページを参照して下さい。

ここでは、ファームウエア書き込み後の親機側のコンフィギュレーション設定を説明します。親機の TWE-Lite DIP を USB シリアルアダプタに接続した状態で、シリアル端末ソフト(TeraTerm 等)で仮想 COM ポートに接続します。

“+” キーを3回連続で押してコンフィギュレーション画面に入ります。

最初に “Application ID” を設定します。この例では 0×11223344 を設定しています。この “Application ID” は接続する全ての子機側でも同じ値に設定します。

“Option Bits” に 0×00020000 フラグを既存の設定値と ”OR” した0×00020001 に設定しています。このフラグ設定によって、親機自身から出力されるカウンタ?(::ts=xxxx)データを抑止しています。リモートの無線タグ・デバイスから送信されるセンサデータ以外のデータレコードが親機自身から送信されて、シリアル出力のサイズが増えないようにしています。後で作成するMQTT ブローカに送信する部分の Lua スクリプト中で、これらの不要なデータレコードを取り除く処理を行いますので、このフラグを設定しなくても正常に動作します。

その他のフラグやコンフィギュレーション項目はデフォルトのままで大丈夫です。

●子機の設定

次に子機の TWE-Lite DIP デバイスを設定します。ファームウエアを書き換えますので、親機の設定で使用したブレッドボードから親機用のTWE-Lite DIP デバイスを抜いて、子機の TWE-Lite DIP デバイスを代わりに差し込みます。これらの作業をするときには、PC と接続している USB ケーブルを抜いた状態でデバイスの入れ替えをすると安全に操作できます。

親機と同様にメーカのホームページから”無線タグ専用の子機ファームウエア” を書き込みます。その後、子機のファームウエアを設定します。子機のコンフィギュレーション設定をするときだけは、TWE-Lite DIP の M2 ピンを GND に接続する必要があります。ブレッドボード上でジャンパケーブルを M2(26pin) と GND に接続してください。その後、シリアル端末ソフト(TeraTerm 等)でUSBシリアルアダプタに接続した後、ブレッドボード上のリセットボタンを押して子機ファームウエアのコンフィギュレーション画面に入ります。

“Application ID” には親機と同じ 0×11223344 を設定します。”Option Bits” にはOTA(無線経由での設定)を停止するためのフラグ 0×400 を有効にします。また、ここではセンサデータの送信間隔(Sleep Dur) を 300ms に設定していますが任意の値に設定しても構いません。

子機の設定が終了したら “save Configulation” で設定値を保存します。

全ての子機の設定を同様におこなったら、子機と親機の TWE-Lite DIP デバイスをそれぞれの本来の回路に戻しておきます。このとき、親機のブレッドボード上に配線した M2 <-> GND 間の配線を取り除くのを忘れないようにします。これで、無線タグデバイスのセットアップは完了しました。

●TWE-Lite タグデバイスの動作確認

MQTT broker への送信設定を行う前に、TWE 無線タグ・デバイスが動作しているかどうかを確かめてみます。

親機の USB シリアルアダプタにシリアル端末で再び接続してください。子機の無線タグの電源を入れると、下記の様なデータレコードが親機からシリアル送信されていればリモート側の無線タグは正常に動作しています。

*** Samp_Monitor (Parent) 1.05-5 ***
* App ID:11223344 Long Addr:81004152 Short Addr 0152 LID 00
::rc=80000000:lq=66:ct=0001:ed=81002A1E:id=0:ba=3310:a1=1922:a2=2467:p0=000:p1=000
::rc=80000000:lq=69:ct=0002:ed=81002A1E:id=0:ba=3280:a1=1845:a2=2467:p0=000:p1=000
::rc=80000000:lq=69:ct=0003:ed=81002A1E:id=0:ba=3400:a1=1891:a2=2467:p0=000:p1=000
::rc=80000000:lq=72:ct=0004:ed=81002A1E:id=0:ba=3440:a1=1975:a2=2467:p0=000:p1=000
::rc=80000000:lq=69:ct=0005:ed=81002A1E:id=0:ba=3400:a1=2035:a2=2467:p0=000:p1=000
::rc=80000000:lq=72:ct=0006:ed=81002A1E:id=0:ba=3440:a1=2105:a2=2467:p0=000:p1=000
::rc=80000000:lq=72:ct=0007:ed=81002A1E:id=0:ba=3440:a1=2120:a2=2467:p0=000:p1=000

もし、親機のファームエア設定で 0×00020000 フラグを設定していないときには、下記のようなシリアル出力になります。

::rc=80000000:lq=90:ct=0078:ed=81002A1E:id=0:ba=3440:a1=1435:a2=2467:p0=000:p1=000
::ts=435
::rc=80000000:lq=90:ct=0079:ed=81002A1E:id=0:ba=3460:a1=1442:a2=2467:p0=000:p1=000
::rc=80000000:lq=93:ct=007A:ed=81002A1E:id=0:ba=3470:a1=1435:a2=2467:p0=000:p1=000
::rc=80000000:lq=90:ct=007B:ed=81002A1E:id=0:ba=3440:a1=1418:a2=2467:p0=000:p1=000
::ts=436
::rc=80000000:lq=93:ct=007C:ed=81002A1E:id=0:ba=3450:a1=1418:a2=2467:p0=000:p1=000
::rc=80000000:lq=93:ct=007D:ed=81002A1E:id=0:ba=3460:a1=1440:a2=2467:p0=000:p1=000
::rc=80000000:lq=93:ct=007E:ed=81002A1E:id=0:ba=3460:a1=1430:a2=2467:p0=000:p1=000
::ts=437
::rc=80000000:lq=93:ct=007F:ed=81002A1E:id=0:ba=3440:a1=1420:a2=2467:p0=000:p1=000

これで、親機と子機の無線タグが正常に動作しているのを確認できました。複数の子機を同時に動作させることもできますので、この場合にはそれぞれのデバイスからのデータも表示されます。

動作確認が終了したら、シリアル端末のプログラムを終了させて COM ポートを開放してください。以降の設定で、親機からの出力される上記のシリアルデータを変換して MQTT Broker に JSON 文字列を送信します。

●TWE無線タグ・親機のシリアルデバイスを DeviceServer に登録

TWE無線タグ・親機から出力されるリモートセンサデータを取り込むために、DeviceServer にシリアルデバイスを登録します。”サーバー設定プログラム” を起動して、”SERIAL” タブを選択します。

親機が接続されている仮想 COM ポート(ここでは “COM10″) にデバイスタイプ “TWE”でシリアルデバイスを登録した様子です。デバイスの詳細設定は以下の様になっています。

デバイスタイプは “TWE” を選択します。シリアルデバイスのタイトル文字は適当な名前をつけて下さい。シリアルポート通信条件はファームウエアのデフォルト値 (15200baud, 8bit, 1stop-bit, no parity) に合わせます。

これで、親機からセンサデータを受信する度に、DeviceServer 側ではイベントハンドラスクリプト SERIAL_TWE.lua が実行されるようになります。

●MQTT エンドポイント作成

シリアルデバイスの作成に続いて、サーバー設定プログラムの “MQTT” タブを選択して、MQTT Broker との接続(エンドポイント)を作成します。

ここでは、2つのエンドポイントを作成しています。1つめが、TWE リモートセンサデータを MQTT Brokerに登録するための接続で、後の1つが、MQTT Broker からセンサデータを受信して、ログに出力したり LED 表示器に A/D 変換値を出力するための接続です。

今回は、構成をわかり易くするために MQTT Broker 接続のエンドポイントを2つに分けていますが、センサーデータ登録用と受信用のエンドポイントを1つで共有して運用しても構いません。

“サーバー設定プログラム”の “MQTT” タブを押してエンドポイントリストを表示した状態です。ここでは既にデータ登録用のエンドポイントとして、タイトル名 “センサーデータ登録” と データ取得用のエンドポイント、タイトル名 “センサーデータ取得” の2つのエンドポイントが登録されています。

“センサーデータ登録” のエンドポイントの詳細設定は以下の様になっています。

接続先の MQTT Broker は、IP アドレス192.168.100.14、ポート番号1883 で動作している RaspberryPi 上の mosquitto にしています。mosquitto MQTT ブローカについては、こちらの記事も参照してください。

“センサーデータ取得” のエンドポイントの詳細設定は以下の様になっています。

MQTT Broker の IP アドレスや ポート番号は同じですが、このエンドポイントでは起動時に購読するトピックを複数設定しています。

TWE 無線タグ・デバイスのセンサーデータは、下記のトピック名で送信されています。

”/twe/<TWE 子機のMACアドレスを表す16進数文字列>/Samp_Monitor”

全ての無線タグ子機から送信されるセンサーデータを購読するために、トピック名 “/twe/+/Samp_Monitor” を購読するように設定します。

”/+/+/io” や “/+/+/tdcp” のトピック購読は、リモートに設置した XBee デバイスやマイコンボード等からのセンサーデータ購読のために指定しています。今回はこれらからのトピック・データは使用していません。

これで、DeviceServer 側の親機のシリアル設定と MQTT エンドポイント設定は完了しました。サーバー設定プログラムの “次へ” ボタンを押して設定を反映させると DeviceServer が再起動してTWE 無線タグ親機へのシリアル接続と MQTT Broker へのエンドポイント接続が自動的に開始されます。

●MQTT Broker にTWE無線タグセンサ・データを JSON 形式で送信する

次に、DeviceServer 側で実行されているデフォルトのイベントハンドラスクリプトをエディタで変更して、MQTT Broker にセンサーデータを JSON 形式で送信するようにします。

TWE 無線タグ親機からセンサーデータを受信する毎に、SERIAL_TWE.lua イベントハンドラが実行されますので、この内容を変更して下記のようにします。

(ここで紹介しているイベントハンドラの内容はコメントアウトされた状態でインストールキットに含まれています。そのため、この記事の内容を試す場合にはイベントハンドラ中の該当部分のコメント指定を外して直ぐに使用できます)

file_id = "SERIAL_TWE"

--[[

******************************************************************************
* イベントハンドラスクリプト実行時間について                                 *
******************************************************************************

一つのスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
処理に時間がかかると、イベント処理の終了を待つサーバー側でタイムアウトが発生します。

また、同時実行可能なスクリプトの数に制限があるため、他のスクリプトの実行開始が
待たされる原因にもなります。

頻繁には発生しないイベントで、処理時間がかかるスクリプトを実行したい場合は
スクリプトを別に作成して、このイベントハンドラ中から script_fork_exec() を使用して
別スレッドで実行することを検討してください。

******************************************************************************

SERIAL_TWE スクリプト起動時に渡される追加パラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
COMPort			イベントを送信したシリアルデバイスの COMポート名		"COM10"

Title			イベントを送信したシリアルデバイスのタイトル名			"TWE-Lite PAN#1"
				タイトルが設定されていない場合にはこのパラメータは
				設定されません

TWE_DATA		COM ポートから入力されたアスキー形式のデータパケット全体を格納しています。

	値の例

	タイプ(1)	":01890902010A010203040405060708092F"
	タイプ(2)	"::rc=80000000:lq=84:ct=0001:ed=81002A1E:id=1:ba=3320:a1=0009:a2=0009:p0=001:p1=000"
	タイプ(3)	";116;00000000;054;001;1002ecd;3330;0007;0042;0007;0007;S;"
	タイプ(4)	"!INF TOCOS TWELITE DIP APP V1-00-2, SID=0x81000038, LID=0x78"
	タイプ(5)	"*** Samp_Monitor (Parent) 1.03-3 *** Title = TWE-Zero"

	文字列は、下記の何れかのデータで終端されたものです。
	ヌル文字(0x00),CR(0x0D),LF(0x0A),CR-LF(0x0D,0x0A)
	TWE_DATA パラメータには、終端文字を含まない文字列部分が格納されています

TWE_DATAを解析してこのスクリプト中で作成されるテーブルと文字列変数
---------------------------------------------------------------------------------
テーブルまたは文字列変数名		説明									値の例
---------------------------------------------------------------------------------
byte_arr	TWE_DATA が ":" 1文字から始まっている場合に、16進数文字列部分を
			1バイト毎に数値に変換して配列に格納したものが入る
			上記 TWE_DATA データ例タイプ(1)を参照

			byte_arr[1] = 0x01
			byte_arr[2] = 0x89
			byte_arr[3] = 0x09
			..

key_val		TWE_DATA が "::" 2文字から始まっている場合に、続く ":" 文字毎にカラムを
			分けて "<key>=<val>" で記述された部分を連想配列に格納したものが入る。
			上記 TWE_DATA データ例タイプ(2)を参照

			key_val["rc"] = "80000000"
			key_val["lq"] = "84"
			key_val["ct"] = "0001"
			key_val["ed"] = "81002A1E"
			..

tag_arr		TWE_DATA が ";" 1文字から始まっている場合に、続く ";" 文字毎にカラムを
			分けたものを文字列形式で配列に格納したものが入る。
			上記 TWE_DATA データ例タイプ(3)を参照

			tag_arr[1] = "116"
			tag_arr[2] = "00000000"
			tag_arr[3] = "054"
			tag_arr[4] = "001"
			..

comment		TWE_DATA が ":"または ";" 文字以外から始まっている場合に、
			データパケット全体の文字列を格納したものが入る
			上記 TWE_DATA データ例タイプ(4),(5)を参照

			COMMENT = "!INF TOCOS TWELITE DIP APP V1-00-2, SID=0x81000038, LID=0x78"

TWE_DATAを解析してこのスクリプト中で作成されるグローバル共有変数と共有文字列リスト
---------------------------------------------------------------------------------
グローバル共有変数名						説明
または、共有文字列リストChannel名
---------------------------------------------------------------------------------

TWE_<COMPort>_<ChildID>

			TWE_DATA が ":" 1文字から始まっていて、かつコマンド種別を示すバイト値が
			0x81(状態通知)の場合に、メッセージ内容を解析した値がカンマ区切りで
			グローバル共有変数に格納される。この変数の値は常に最後のイベント発生時
			の内容で更新されます。

			<COMPort>は、イベントを送信したシリアルデバイスの COMポート名になります
			<ChildID>は、メッセージ中の送信元論理デバイスIDを10進数にしたものになります

			変数の内容に設定されるカンマ区切りの文字列は下記のフォーマットになります。

			<LQI>,<Batt>,<DI1>,<DI2>,<DI3>,<DI4>,<AD1>,<AD2>,<AD3>,<AD4>

			<LQI> にはLQI値フィールドの値を10進数に変換したものが入ります
			<Batt> には電源電圧[mV]フィールドの値を10進数に変換したものが入ります
			<DI1>..<DI4> にはDI の状態ビットが Lowの場合に1, High の場合に 0 が入ります
			<AD1>..<AD4> にはAD変換値の値を10進数に変換したものが入ります

TWE_<COMPort>_CHILD_LIST (共有文字列リストChannel名)
			TWE_DATA が ":" 1文字から始まっているパケットデータを受信したときの
			<ChildID> 部分を文字列リストに保存します。文字列リストにはメッセージ中の
			送信元論理デバイスIDを10進数表現にしたものを重複なく保存しています。
]]

local str = ""
for key,val in pairs(g_params) do
	str = str .. key .. " = " .. val .. " "
end
log_msg(str,file_id)

------------------------------------------------------------
-- g_params["TWE_DATA"] データパケット文字列をデコードする
------------------------------------------------------------
local data = g_params["TWE_DATA"]
local byte_arr,key_val,tag_arr,comment
if string.match(data,"^::") then		-- タイプ(2)
	key_val = key_val_to_tbl(string.sub(data,3,-1))
elseif string.match(data,"^:%x") then	-- タイプ(1)
	byte_arr = hex_to_tbl(string.sub(data,2,-1))
	local id = tostring(byte_arr[1])
	if not add_shared_strlist("TWE_" .. g_params["COMPort"] .. "_CHILD_LIST",id,true) then error() end -- 子機のIDリストを更新
	if byte_arr[2] == 0x81 then -- 状態通知の場合には共有変数に現在のセンサ値を保存
		local di1,di2,di3,di4,ad1,ad2,ad3,ad4
		if bit_and(byte_arr[17],0x01) ~= 0 then di1 = "1" else di1 = "0" end
		if bit_and(byte_arr[17],0x02) ~= 0 then di2 = "1" else di2 = "0" end
		if bit_and(byte_arr[17],0x04) ~= 0 then di3 = "1" else di3 = "0" end
		if bit_and(byte_arr[17],0x08) ~= 0 then di4 = "1" else di4 = "0" end
		if byte_arr[19] == 0xFF then ad1 = "-1" else ad1 = tostring((byte_arr[19]*4 + bit_and(byte_arr[23],0x03))*4) end
		if byte_arr[20] == 0xFF then ad2 = "-1" else ad2 = tostring((byte_arr[20]*4 + bit_and(bit_rshift(byte_arr[23],2),0x03))*4) end
		if byte_arr[21] == 0xFF then ad3 = "-1" else ad3 = tostring((byte_arr[21]*4 + bit_and(bit_rshift(byte_arr[23],4),0x03))*4) end
		if byte_arr[22] == 0xFF then ad4 = "-1" else ad4 = tostring((byte_arr[22]*4 + bit_and(bit_rshift(byte_arr[23],6),0x03))*4) end
		local val = tostring(byte_arr[5]) .. "," .. tostring(byte_arr[14]*256 + byte_arr[15])
				.. "," .. di1 .. "," .. di2 .. "," .. di3 .. "," .. di4
				.. "," .. ad1 .. "," .. ad2 .. "," .. ad3 .. "," .. ad4
		if not set_shared_data("TWE_" .. g_params["COMPort"] .. "_" .. id,val) then error() end
		-- リレーサーバーに最新データの配信を依頼する場合には下記のコメントを削除する
		script_exec("RELAY_SERVER_UPLINK","COM,TYPE,NodeID,LQI,Batt,DI1,DI2,DI3,DI4,AD1,AD2,AD3,AD4",g_params["COMPort"] .. ",TWE_UPDATE," .. id .. "," .. val)
	end
elseif string.match(data,"^;") then		-- タイプ(3)
	tag_arr = ssv_to_tbl(string.sub(data,2,-2))
else									-- タイプ(4),(5)
	comment = data
end

--[[
********************************************************************

TWE(Samp_Monitor)->MQTT Gateway 機能の実装例

Samp_Monitor ファームウエアで動作中のリモート TWEデバイスから送信されたタグ・データを MQTT に登録します。
Samp_Monitor ファームウエアのオプション設定によって、送信されるタグの項目は変わります。詳しくは
ファームウエアのドキュメントを参照して下さい。このスクリプトでは送信されたタグのキーと値をそのまま
文字列ペアとして JSON に変換しています。

トピック名: "/twe/<Samp_Monitor フレーム中の "ed"(MACアドレス)タグの値>/Samp_Monitor"
メッセージ文字列: JSON キー名部分は下記スクリプト中の "tag_name" テーブルに設定することで変更できます

{"id":"0","ct":"0007","ed":"81002A1E","ba":"2840","rc":"80000000","lq":"57","p0":"000","a2":"2113","a1":"11..(キーと値のペアが続く)

送信先 MQTT ブローカは、サーバー設定プログラムを使用して
エンドポイントを登録して下さい。下記スクリプト中の end_point 変数に
エンドポイントのタイトル文字列または ClientID を設定します。

********************************************************************
]]

-- データが文字列 "::" から始まるデータタイプ(2)の場合で、且つ "ed"タグが含まれているものだけを、MQTTブローカ送信対象にする
if key_val and key_val["ed"] then
	local dev = key_val["ed"]
	local end_point = "センサーデータ登録"
	local topic = "/twe/" .. dev .. "/Samp_Monitor"
	local qos = 1

	-- TWE のタグ名を変更して JSON 形式にする場合は、変更したいタグを下記テーブルに指定する
	local tag_name = {}
	tag_name["ba"] = "battery"
	tag_name["ct"] = "count"
	tag_name["lq"] = "LQI"
	tag_name["te"] = "temperature"
	tag_name["hu"] = "humidity"
	tag_name["at"] = "pressure"
	tag_name["x"] = "acc_x"
	tag_name["y"] = "acc_y"
	tag_name["z"] = "acc_z"

	local json_str = '{'
	local first = true
	for key,val in pairs(key_val) do
		if first then
			first = false
		else
			json_str = json_str .. ","
		end
		if tag_name[key] then
			json_str = json_str .. string.format('"%s":"%s"',tag_name[key],val)
		else
			json_str = json_str .. string.format('"%s":"%s"',key,val)
		end
	end
	json_str = json_str .. '}'
	mqtt_publish(end_point,topic,json_str,qos)
end

イベントハンドラの最初の部分では、TWE-Zero アプリ(ファームウエア) で使用される幾つかの種類のレコードフォーマットを解析するための共通ルーチンが定義されています。

無線タグアプリ(Samp_Monitor) は上記コメント中の タイプ(2) のレコードフォーマットとして解析されて、Lua テーブル key_val[] にタグのキーと値が対応付けられて作成されます。

スクリプトの後半部分で、MQTT Brokerに送信するための JSON 形式の文字列を作成しています。key_val[] テーブルのキーと値は、TWE無線タグ親機から送信されるタグの、キーと値にそのまま対応しています。ここではこのキー名と値を利用して下記のような JSON 形式の文字列に変換します。

{“id”:”0″,”ct”:”0007″,”ed”:”81002A1E”,”ba”:”2840″,”rc”:”80000000″,”lq”:”57″,”p0″:”000″,”a2″:”2113″,”a1″:”11″,”a2″:”334″}

“ba” や ”lq” などのタグ名を、JSON データ利用側で判りやすいように “battery”, “LQI” などの名前に変換するための機能がスクリプト中に記述されています。詳しくはスクリプト中のコメントをご覧ください。

mqtt_publish() ライブラリ関数をコールすることで、JSON 形式に変換したリモート無線タグから受信したセンサデータを MQTT Broker に送信します。

これで、リモートに設置した全てのTWE 無線タグのセンサデータが MQTT Broker に送信されるようになります。TWE無線タグ・子機のオプション設定を変更して、接続するセンサーデバイスを変更した場合でも、出力されるデータのタグ名に対応した JSON 形式に変換されて MQTT Broker に送信されます。

●MQTT Broker からTWE無線タグ・センサ・データを受信する

次に、DeviceServer 側から MQTT Broker のセンサーデータの購読する部分のスクリプトを作成します。MQTT Broker に送信したセンサデータを再び DeviceServer 側に取り込んで利用する形になります。今回のシステム構成では、MQTT Broker に送信する前にセンサデータを処理できるのであくまでも、MQTT Broker からのデータ取得の一例として紹介します。

インストール直後のデフォルトスクリプトは、MQTT Broker から購読したデータ列を文字列に変換してログに出力しているだけですが、この部分を変更して子機無線タグのAI3 (“a2″タグ) に接続した光センサの値をログに出力します。MQTT_PUBLISH.lua イベントハンドラの内容に下記の記述を追加します。

file_id = "MQTT_PUBLISH"

--[[

******************************************************************************
* イベントハンドラスクリプト実行時間について                                 *
******************************************************************************

一つのスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
処理に時間がかかると、イベント処理の終了を待つサーバー側でタイムアウトが発生します。

また、同時実行可能なスクリプトの数に制限があるため、他のスクリプトの実行開始が
待たされる原因にもなります。

頻繁には発生しないイベントで、処理時間がかかるスクリプトを実行したい場合は
スクリプトを別に作成して、このイベントハンドラ中から script_fork_exec() を使用して
別スレッドで実行することを検討してください。

******************************************************************************

MQTT_PUBLISH スクリプト起動時に渡される追加パラメータ

---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
ClientID		エンドポイントの ClientID 文字列						"abs9k:2222-eagle"

Title			エンドポイントに設定されたタイトル文字列。
				タイトル文字列が設定されていない場合には、"" 空文字列
				が入ります												"センサーデバイス#1"

MessageType		MQTT プロトコルで定義されたメッセージタイプが入ります。	"3"
				PUBSLIH メッセージの場合には常に "3"が設定されます

MessageID		Brokerから送信するときに使用された MQTT メッセージID が
				入ります。(QoS = 1 または QoS = 2 の場合) 値は "1" から
				"65535" の整数値をとります。
				QoS = 0 の場合には常に "0" が設定されます。				"1234"

Dup				MQTT 固定ヘッダ中の Dup フラグの値が設定されます。
				"0" または "1" の値をとります。							"0"

QoS				MQTT 固定ヘッダ中の QoS フラグの値が設定されます。
				"0", "1", "2" の何れかの値をとります。					"0"

Retain			MQTT 固定ヘッダ中の Retain フラグの値が設定されます。
				"0" または "1" の値をとります。							"0"

PublishTopic	MQTT ブローカから受信した PUBLISH メッセージ中の Topic
				文字列。												"センサー/ノード1"

PublishData		MQTT ブローカから受信した PUBLISH メッセージ中のペイロー
				ドデータ。
				バイナリデータを16進数文字列に変換したものが格納されます
				ペイロードデータに格納されたデータが UTF-8 文字列の場合
				には文字列コードのバイト列が格納されています。
				イベントハンドラ中でこれらの文字列データをデコードする処
				理がデフォルトで記述されていますので、UTF-8 文字列を扱う
				場合にはデコード後の変数を利用することができます。		"010203414243"

PublishDataで渡されたペイロードデータを解析して作成される文字列変数

PublishString	PublishData に格納されたペイロードデータ部分のサイズが
				2048 Bytes以内の場合に、データバイト列を UTF-8形式で
				文字列にデコードした結果を PublishString に格納します。
				変換対象のバイト列のサイズを変更したいときには該当する
				スクリプト部分を変更して下さい。

]]

------------------------------------------------------------------------------------------
-- 受信したペイロードデータのサイズが 2048 bytes 以内の場合には
-- バイナリデータ列を UTF-8 文字列としてデコードしたものを PublishString 変数に格納する
------------------------------------------------------------------------------------------
local PublishString = ""
local pub_len = string.len(g_params["PublishData"]) / 2
if pub_len < 2048 then
	PublishString = readUTF_hex(bit_tohex(pub_len,4) .. g_params["PublishData"])
end

log_msg(g_params["Title"] .. "[" .. g_params["ClientID"] .. "] msg:" .. g_params["MessageID"] .. " dup:" .. g_params["Dup"]  ..
" retain:" .. g_params["Retain"]  .. " qos:" .. g_params["QoS"]  .. " topic:" .. g_params["PublishTopic"]  .. " " .. PublishString,file_id)

if g_params["Title"] == "センサーデータ取得" then

	---------------------------------------------------------------------------------------
	-- 過去データ確認用に時系列データベースに Publish されてきた文字列(JSON)を保管しておく
	---------------------------------------------------------------------------------------
	if not add_series_str(g_params["PublishTopic"],PublishString) then error() end

	------------------------------------------------
	-- その他のアクション定義
	------------------------------------------------
	json = require('json')
	local data_tbl = json.decode(PublishString) -- JSON デコードして Lua テーブル構造に変換

	if data_tbl.a2 then -- JSON データ中に "a2" キー名に対応するデータが存在するときだけ実行
		local a2_num = tonumber(data_tbl.a2)
		log_msg("A2 = " .. tostring(a2_num),file_id)
		------------------------------------------------
		-- 7セグLED表示器にメッセージ表示
		------------------------------------------------
		local tbl = {}
		tbl["com"] = "Arduino実験ボード#1"
		tbl["data"] = tostring(a2_num)
		tbl["width"] = "8"
		if not script_exec("ARDUINO/DEVICE/SPI_7SEGLED",tbl) then error() end
	end

end

add_series_str() をコールしている部分は、後で過去のデータをグラフ表示する時などに利用できるように、DeviceServer 内蔵のデータベースに取得した JSON 文字列をそのまま格納しています。

次の部分では、JSON 文字列をデコードして Lua のテーブル構造に変換しています。テーブルの形にしておくとで、センサデータの内容を取り出すことが簡単にできるようになります。data_tbl.a2 には JSON 文字列中の { …. “a2″:”xxxx” } の部分が入っていますので、これを数値に変換することで AI3 の A/D 変換値が得られます。ここでは、この値をログに出力しています。

動作中のログを下記に示します。MQTT Broker にセンサーデータを登録して、同時にそのセンサーデータを MQTT から購読した後上記スクリプト中で A/D 変換値を出力するまでがログに出力されています。

●おまけ

MQTT_PUBLISH.lua イベントハンドラスクリプト中からは、Arduino に接続した 7セグLED 表示器(秋月電子 M-06681) に現在の A/D 変換値を表示しています。

7セグLED 表示器は SPI 接続で簡単にスクリプト中から操作できますので、最新のデータを確認するのに便利です。

Arduino と DeviceServer 間はシリアル接続されいて、Firmata プロトコルで SPI 通信コマンドをやり取りしています。(Firmata プロトコルで Arduino をDeviceServerから操作するアプリ例はこちらの記事を参照してください)

動作の詳細はインストールキットに含まれている下記のスクリプトをご覧ください。また、Arduino で動作させるスケッチ(SensorControlModule.ino)もインストールキットに含まれていますのでご利用下さい。

Arduino に SPI 接続した LED表示器を DeviceServer から操作するスクリプト

C:\Program Files (x86)\AllBlueSystem\Scripts\ARDUINO\DEVICE\SPI_7SEGLED.lua

C:\Program Files (x86)\AllBlueSystem\Scripts\ARDUINO\DEVICE\SPI_WRITE.lua

それではまた。

 

MQTT から WebSocket 経由で IO データを受信してグラフ表示、XBee-MQTT Gateway (その2)

●概要

この記事では、前の記事で説明した  MQTT ブローカに登録されている XBee IO サンプリングデータを、WebSocket 経由で取得してグラフ表示をする Webアプリケーションを作成します。

リモート XBee デバイスから JSON 形式で MQTT ブローカに送信された IO サンプリングデータを、Web アプリケーション中から購読(MQTT Subscribe) します。MQTT からメッセージを受信したら、メッセージ内容からデータを取り出してグラフ表示します。MQTT ブローカからは連続してメッセージを受信しますので、グラフは最新のデータで更新されてスクロール表示されます。

下記は、XBee (Device5) のAD0 に接続した光センサの値をグラフ表示した様子です。

●MQTT ブローカ mosquitto を WebSocket 対応版にする

前回の記事までで使用していた MQTT ブローカ (mosquitto) は、RaspberryPi 上でバイナリパッケージをインストールしたものを使用していました。mosquitto の最新バージョン ver1.4.2 では WebSocket をサポートしているのですが、残念ながら記事を作成していた時点では最新バージョンのバイナリパッケージをインストールできませんでした。そのため、mosquitto のホームページから直接ソースをダウンロードしてコンパイルすることにします。

mosquitto-1.4.2.tar.gz ファイルを RaspberryPi にダウンロードして適当な場所に展開します。

デフォルトのビルド設定では WebSocket が有効にされていないので、config.mk ファイルを修正して使えるようにします。ファイル中でコメントアウトされた下記の部分を

#WITH_WEBSOCKETS:=no

以下の様に変更します

WITH_WEBSOCKETS:=yes

その後、make コマンドを実行するとコンパイルされて最新のバージョンの mosquitto が作成されます。実行する RaspberryPi の環境によっては、幾つかの足りないライブラリやパッケージを RaspberryPi にインストールする必要がでてくると思います。この場合でも コンパイルエラーで表示されるメッセージを手がかりにネットで検索すると、必要なパッケージやライブラリが簡単に見つかります。あきらめずにコンパイルに挑戦して下さい。(ライブラリをインストールしたときにはライブラリキャッシュの更新を忘れずに!)

無事にコンパイルが完了したら、既存の mosquitto パッケージをアンインストールしてプロセスを削除してください。その後、”src” ディレクトリに移動して先ほどコンパイルして作成した mosquitto を起動します。

起動時にはデフォルトの TCP/IP ポート 1883 の他に、WebSocket を使用するコンフィギュレーションファイルを指定します。下記の内容のファイルをホームディレクトリにmosquitto.conf の名前で作成しました。ここではWebSocket のポート番号を 9090 にしていますが別の値を指定しても構いません。

以前のバージョンの mosquitto で使用していたユーザーアカウントファイルを、 password_file パラメータで再利用しています。MQTT アカウントの認証を省略する場合には、このパラメータは削除してください。

listener 1883

listener 9090 127.0.0.1
protocol websockets

password_file /etc/mosquitto/password_file

作成したコンフィギュレーションファイルを使用して mosquitto MQTT ブローカを起動するまでの様子は以下になります。

mosquitto を起動したときのメッセージを確認してください。TCP/IP のポート 1883 と WebSocket のポート 9090 がそれぞれ ”listen” 状態になっていれば大丈夫です。

これで WebSocket も使用可能な MQTT ブローカ(mosquitto) の準備が完了しました。DeviceServer を再起動して MQTTT エンドポイントを接続してください。その後、前のバージョンの時と同様に XBee からの IO サンプリングデータを mosquitto_sub コマンドで確認してください。

●Webアプリケーション作成

ここからは、Web アプリケーションを作成します。最初に index.html ファイルを下記の内容で作成します。ここで紹介する Web アプリのファイルは、最新の DeviceServer インストールキットに含まれています。DeviceServer が動作している PC の “C:\Program Files (x86)\AllBlueSystem\WebRoot\web_api_sample\mqtt_xbee_chart” フォルダに Webアプリケーションに必要なファイルが格納されています。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>MQTT-XBee ゲートウェイ ADCグラフ</title>
		<link rel="stylesheet" href="libs/css/themes/default/jquery.mobile-1.4.5.min.css" />
		<link rel="stylesheet" href="libs/css/jquery.jqplot.css" />
		<script src="libs/js/jquery.js"></script>
		<script src="libs/js/jquery.mobile-1.4.5.js"></script>
		<script src="libs/js/mqttws31.js"></script>
		<script src="libs/js/jquery.jqplot.js"></script>
	</head>
	<body>

		<div data-role="page" id="setup_page">
			<div data-role="header" data-position="inline">
				<h3>MQTTブローカ接続設定</h3>
			</div>
			<div role="main" class="ui-content">
				<label for="host_name">MQTT broker host name</label>
				<input id="host_name" value="127.0.0.1" type="text" placeholder="hostname or IP address (WebSocket)" data-clear-btn="true"/>
				<label for="port_number">MQTT broker WebSocket port</label>
				<input id="port_number" value="9090" type="number" placeholder="port number (WebSocket)" data-clear-btn="true"/>
				<label for="mqtt_topic">Subscribe topic</label>
				<input id="mqtt_topic" value="/+/+/io" type="text" placeholder="subscribe topic ex: /xbee/NodeIdentifier/io" data-clear-btn="true"/>
				<div><h3>&nbsp</h3></div>
				<div class="ui-grid-b">
					<div class="ui-block-b">
							<a class="ui-btn ui-btn-inline ui-icon-check ui-btn-icon-left " id="connect_btn" >Connect</a>
					</div>
				</div>
			</div>
			<div data-role="footer">
				<h3>Copyright(c) 2015 All Blue System</h3>
			</div>
		</div>

		<div data-role="page" id="chart_page">
			<div data-role="header" data-position="inline">
				<a data-icon="back" id="chart_view_prev_btn">接続設定</a>
				<h3>グラフ表示</h3>
			</div>
			<div id="chart1" style="height:500px;width:100%;"></div>
			<div data-role="footer">
				<h3>Copyright(c) 2015 All Blue System</h3>
			</div>
		</div>

		<div data-role="page" id="error_quit_dialog">
			<div data-role="header" data-theme="b">
				<h1>*MQTT BROKER ERR*</h1>
			</div>
			<div role="main" class="ui-content">
				<h2>エラーが発生しました</h2>
				<p>MQTT ブローカ接続処理中にエラーが発生しました。接続設定を見直して下さい</p>
				<p><a data-role="button" id="error_ok_btn" class="ui-btn ui-shadow ui-btn-a ui-icon-check ui-btn-icon-left">OK</a></p>
			</div>
		</div>

		<script src="main.js"></script>

    </body>
</html>

index.html ファイルでは jQuery-mobile フレームワークを使用して Webアプリの画面とメッセージダイアログを定義しています。

Webアプリで最初に表示される “設定画面ページ”では MQTT ブローカ WebSocket に接続するためのパラメータを入力します。”Connect” ボタンを押すと MQTT ブローカに接続します。

MQTT ブローカ接続後に表示されるのが “グラフ表示ページ” です。グラフ表示には jplot ライブラリを使用しています。

index.html ファイル中から参照している mqttws31.js は Paho プロジェクトで公開されている JavaScript 版の MQTT クライアント・ライブラリです。これを利用して WebSocket 経由で MQTT ブローカに接続します。

次に、メインのアプリケーションロジックを実現している JavaScript ファイル(main.js)は以下になります。

//////////////////////////////////////
// グラフ表示ページ
//////////////////////////////////////

var plot1;
var ad0 = new Array();
var ad1 = new Array();
var ad2 = new Array();
var ad3 = new Array();
var ad4 = new Array();
var ad5 = new Array();

// グラフを右端から開始するために、初期データをシリーズ変数に格納する
for (var i = 0; i <= 120; i++){
	ad0.push(null);
	ad1.push(null);
	ad2.push(null);
	ad3.push(null);
	ad4.push(null);
	ad5.push(null);
}

// グラフ表示フォーマット
var chartOptions = {
	legend: { show: true, location: "nw" },
	title: "-- no data --",
	series: [{label: "AD0" },{label: "AD1" },{label: "AD2" },{label: "AD3" },
				{label: "AD4" },{label: "AD5" }],
	seriesColors: ["#000000","#A64F08","#FF0000","#FF8000","#FFFF00","#04B404"],
	seriesDefaults: {
		rendererOptions: { smooth: true },
    	pointLabels: { show: true },
		lineWidth:3,
		showMarker:false,
    	breakOnNull: true
	},
	axes: {
		xaxis: {
			label: "(past) sample",
			min: 0,
			max: 120,
			ticks: [[1,"120"],[11,"110"],[21,"100"],[31,"90"],[41,"80"],[51,"70"],
					[61,"60"],[71,"50"],[81,"40"],[91,"30"],[101,"20"],[111,"10"],[121,"0"]],
			pad: 0
		},
		yaxis: {
			label: "XBee-ADC",
			min: 0,
			max: 1023,
			tickOptions: { formatString: '%d'},
			ticks: [0,100,200,300,400,500,600,700,800,900,1000,1023]
		},
	}
};

// MQTTブローカとの接続が途中で切れた
function onConnectionLost(responseObject) {
	if (responseObject.errorCode !== 0) {
		console.log("onConnectionLost:"+responseObject.errorMessage);
		$("body").pagecontainer("change","#error_quit_dialog",{ transition: "pop",role:"dialog" });
	}
}

// MQTTブローカからメッセージを受信した
function onMessageArrived(message) {
	console.log("onMessageArrived:"+ message.destinationName + " " + message.payloadString);
	// 既存のシリーズデータ(プロットデータ)を左シフトする
	if(ad0.length > chartOptions.axes.xaxis.max) {
		ad0.shift();
		ad1.shift();
		ad2.shift();
		ad3.shift();
		ad4.shift();
		ad5.shift();
	}
	// MQTT ブローカから WebSocket 経由で受信した JSON 文字列をシリーズ変数に格納する
	// メッセージフォーマット:
	//      {"ad0":<ad0_value>, .. ,"ad1":<ad1_value>, ... ,"dio0":<dio0_value>,"dio1":<dio1_value>,... }
	// 該当チャンネルのデータが存在しないときには Null が格納されてグラフには表示されない
	var recv_data;
	try {
		recv_data = $.parseJSON(message.payloadString);
		ad0.push(recv_data.ad0);
		ad1.push(recv_data.ad1);
		ad2.push(recv_data.ad2);
		ad3.push(recv_data.ad3);
		ad4.push(recv_data.ad4);
		ad5.push(recv_data.ad5);
	}catch(e){
		console.log("invalid JSON :" +message.payloadString);
	}
	// 再描画時のメモリリーク対策用 jqplot.replot()使用NG
	if (plot1) {
		plot1.destroy();
	}
	// グラフにシリーズデータをプロット
	chartOptions.title = "XBee-MQTT Gateway ADC samples [ " + message.destinationName + " ]";
	plot1 = $.jqplot("chart1",[ad0,ad1,ad2,ad3,ad4,ad5],chartOptions);
}

// グラフ表示ページが表示された
$(document).on("pageshow","#chart_page",function(event){
	// 再描画時のメモリリーク対策用 jqplot.replot()使用NG
	if (plot1) {
		plot1.destroy();
	}
	// 既存のシリーズデータまたは、初期値の空白グラフを表示する
	plot1 = $.jqplot("chart1",[ad0,ad1,ad2,ad3,ad4,ad5],chartOptions);
});

// グラフ表示ページの "接続設定" ボタンが操作された
$("#chart_view_prev_btn").on( "click",function(event, ui){
	$("body").pagecontainer("change","#setup_page", { transition: "none" });
});

//////////////////////////////////////
// セットアップページ
//////////////////////////////////////

var mqtt_client;
var topic;

// MQTTクライアント接続毎にユニークなID を生成
function get_clientid() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
  }
  return 'mqtt-' + s4() + '-' + s4() + '-' + s4();
}

// MQTTブローカに接続した
function onConnect() {
	console.log("connect to MQTT broker");
	// センサーデータのトピック購読
	mqtt_client.subscribe(topic);
	// グラフ画面ページに移動する
	$("body").pagecontainer("change","#chart_page", { transition: "pop" });
}

// MQTTブローカとの接続に失敗した
function onConnectFailure(responseObject) {
	console.log("onConnectFailure:"+responseObject.errorMessage);
	$("body").pagecontainer("change","#error_quit_dialog", { transition: "pop",role:"dialog" });
}

// セットアップページの "Connect" ボタンを押した
$( "#connect_btn" ).on( "click", function(event, ui){
	var mqtt_host = $("#host_name").val();
	var mqtt_port = parseInt($("#port_number").val());
	topic = $("#mqtt_topic").val();
	// MQTT Client 作成
	mqtt_client = new Paho.MQTT.Client(mqtt_host,mqtt_port,"/mqtt",get_clientid());
	mqtt_client.onConnectionLost = onConnectionLost;
	mqtt_client.onMessageArrived = onMessageArrived;
	// MQTT ブローカ WebSocket に接続
	mqtt_client.connect({ onSuccess:onConnect, onFailure:onConnectFailure });
});

// セットアップページが表示された
$(document).on("pageshow","#setup_page",function(event){
	// 既存の MQTT Client オブジェトを削除する(もしあれば)
	if (mqtt_client) {
		try{
			// ConnectionLost 発生時には disconnect() で例外が発生するが無視する。
			mqtt_client.disconnect();
		}catch(e){
			console.log("disconnect() failed");
		}
		delete mqtt_client;
	}
});

// エラーダイアログから復帰する場合はセットアップページに戻る
$( "#error_ok_btn" ).on( "click", function(event, ui){
	session_token = "";
	$("body").pagecontainer("change","#setup_page", { transition: "pop" });
});

設定画面で入力されたパラメータで MQTT ブローカに接続します。

接続に成功すると、設定画面で指定したトピックを購読します。デフォルトでは “/+/+/io” になっていますので XBee, XBee-ZB から送信される全てのリモート IO サンプリングデータを受信することになります。

MQTT ブローカからトピック・メッセージを受信すると、JSON 文字列をオブジェクトに変換した後、個々の AD サンプリング値を取り出して配列に格納します。配列はグラフ表示するときの X 軸方向の目盛り分のサイズを確保しています。もし最新のサンプリングデータを格納するときに、配列のサイズを超える場合には古いデータを順に削除します。これによってグラフ表示したときに左スクロールするように見えます。

最新のサンプリングデータを配列に格納したら、jqplot ライブラリを使用してグラフ表示します。MQTT ブローカからメッセージを受信する毎にこれらの動作を繰り返します。

●Webアプリを動作させる

早速 Web アプリを起動してみます。DeviceServer をインストールしていた場合には “http://<IPアドレス>/web_api_sample/mqtt_xbee_chart/index.html” をWebブラウザで開いて下さい。

この Webアプリは DeviceServer 側の Web API 機能を一切使用しませんので、”mqtt_xbee_chart” フォルダごと他の Webサーバーに配置して動作させることもできます。WebSocket では Webブラウザで実行中の JavaScript からのネットワークアクセスを、配信元サーバーと同一ドメインに制限する “Same Origin Policy” から除外されています。このためMQTT ブローカが動作している RaspberryPi 以外のWebサーバーに配置しても大丈夫です。”index.html” ファイルをダブルクリックして直接 “file” 形式のURI でアクセスしても動作します。

Web アプリを起動すると最初に設定画面が表示されます。

MQTT ブローカのホスト名(IP アドレス) と WebSocket ポート番号を入力します。ここで指定するポート番号は DeviceServer 側から XBee IO サンプリングデータを Publish している TCP/IP ポート番号(1883) ではありませんので注意してください。必ず、この記事の最初で mosquitto.conf に設定した WebSocket ポート番号を指定します。

“Connect” ボタンを押すと、MQTT ブローカに接続してトピック購読を開始します。

グラフ画面です。この画面では IO サンプリングデータ中に含まれる AD0 .. AD5 までの IOデータをプロットします。存在しない ADxデータ項目はグラフ表示されません。実際にデータが到着すると下記の様なグラフが表示されます。

複数のリモート XBee からのデータを同時に受信すると、グラフで表示するデータ内容が混在してしまいます。その場合には “接続設定” ボタンを押して設定画面に戻って、トピック文字列を設定し直してください。たとえばトピックを “/xbee/Device5/io” にすると、XBee(Series1) でかつ NodeIdentifier が “Device5″ のサンプリングデータのみをグラフ表示させることができます。

●動作例の動画

MQTT ブローカから受信した IO サンプリングデータをグラフ表示しているときの動画を載せましたのでご覧ください。(音量注意)

それではまた。

 

リモート XBee IO データを MQTT Broker に送信、XBee-MQTT Gateway (その1)

●概要

今回の記事では、リモートから送信したXBee IO サンプリングデータを MQTT ブローカに自動送信するシステムを構築します。

XBee(Series1) や XBee-ZB(Series2) デバイスには A/D や DIO の値を定期的に送信するIOサンプリング機能が予め備わっています。このシステムでは XBee から送信された IO サンプリングデータを JSON 形式に変換して、MQTT ブローカに送信するシステムを構築します。

リモートに設置した複数の XBee(Series1) や XBee-ZB(Series2) デバイスから IO サンプリングパケットは、DeviceServer に接続したXBee デバイスで受信します。

Series1 の場合にはスター接続の中央に配置されたコントローラデバイスで、Series2 の場合には Coordinator デバイスが DeviceServer に接続されています。DeviceServer で受信したIO サンプリングパケットはイベントハンドラ中で解析して、JSON 文字列に変換した後 MQTT ブローカに Publish メッセージとして配信します。

複数のリモート XBee デバイスからの IO サンプリングデータを、 MQTT ブローカの購読側で簡単に選別できるように、トピック名をデバイス名(NodeIdentifier) を含んだ毎にユニークな文字列 “/<”xbee” | “zb”>/<NodeIdentifier>/”io” にして送信します。またメッセージ内容は JSON 形式で下記のフォーマットで送信します

{“ad0″:<ad0_value>, .. ,”ad1″:<ad1_value>, … ,”dio0″:<dio0_value>,”dio1″:<dio1_value>,… }

JSON 形式にすることで、IO サンプリングデータを MQTT メッセージ購読側のアプリで利用しやすくなります。また、XBee デバイスからは複数の IO 値が1つのサンプリングパケットにまとめて送信されますので、MQTT ブローカに送信するときも IO ポート毎にトピックを分解しないほうが元のデータを正しく表現することができると思います。

●MQTT ブローカを準備

センサデータを配信する MQTT ブローカをセットアップします。今回は RaspberryPi にインストールした mosquitto を使用します。

セットアップの方法は此方の記事を参照にしてください。

これ以外にも LAN やインターネット上に設置された MQTT ブローカを使用できます。送信するセンサデータは JSON 文字列で数十バイト程度なので、もし利用する MQTT ブローカでペイロードサイズの制限があったとしても引っかかることはないと思います。

ただ、XBee  IO サンプリングデータの送信頻度を百ミリ秒未満にする場合にはMQTT ブローカ間とのネットワーク遅延などを考慮してください。

●リモート側XBee デバイスを設置

ここではリモート側の XBee デバイスに XBee(Series1) を使用していますが、XBee-ZB(Series2)を使用することもできます。光センサ(フォトダイオード) を XBee デバイスの AD0 に接続しています。

フォトダイオードは 浜松ホトニクス S9648 を 負荷抵抗 10KΩで使用しています。またフィルタ用に 0.1μ コンデンサを並列に接続しています。XBee の Vcc と GND の他に、Vref 端子を忘れないように配線してください(Series1 の場合)。

●DeviceServer 側のエンドポイントを作成

DeviceServer 側に MQTT ブローカとの接続を管理するためのエンドポイントを作成します。今回のアプリケーションでは、エンドポイントを XBee から受信した IO サンプリングデータを MQTT ブローカに送信(Publish) する時のみに使用しています。

DeviceServer のサーバー設定プログラムを起動します。

“次へ” ボタンを押して DeviceServer の設定を行います。

MQTT タブを選択して、MQTT クライアント機能の設定とエンドポイントの作成を行います。ここで “MQTT 機能を有効にする” にチェックを付けます。KeepAliveTimer はデフォルトの 60秒のままにしておいて下さい。ここで設定した間隔で、DeviceServer 側から MQTT ブローカに対して PINGREQ メッセージを定期的に送信します。また、エンドポイントのソケット接続でエラーを検出したときには再接続処理が自動で行われます。

“追加” ボタンを押して、新規のエンドポイントを作成します。

エンドポイントのタイトルを上記のように設定します。Broker のホスト名と TCP/IP ソケット・ポート番号をここで入力します。その他の設定は空白のままで構いません。

このエンドポイントは XBee のIO サンプリングデータを MQTT ブローカに送信するためだけに使用する予定なので、”起動時に購読するトピック” 項目などは空白のままで大丈夫です。

“OK” を押してエンドポイントを作成した後、サーバー設定プログラムの “次へ” ボタンを押すとDeviceServer が再起動します。同時に、エンドポイントに設定した MQTT ブローカへの接続が自動的に開始されます。

●XBee のイベントハンドラスクリプト設定

XBee デバイスから IO サンプリングデータを受信したときに、DeviceServer 側で実行されるイベントハンドラスクリプトを設定します。XBee(Series1) の場合には XBEE_IO_DATA.lua が自動起動される Lua スクリプトです。

このスクリプトを修正して、MQTT ブローカに JSON 形式で IO データを送信するようにします。スクリプトの内容は以下になります。最新の DeviceServer インストールキットでインストールすると、下記の内容が既にコメントで囲まれた状態で記述されていますので簡単に設定できます。

file_id = "XBEE_IO_DATA"

--[[

******************************************************************************
* イベントハンドラスクリプト実行時間について                                 *
******************************************************************************

一つのスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
処理に時間がかかると、イベント処理の終了を待つサーバー側でタイムアウトが発生します。

また、同時実行可能なスクリプトの数に制限があるため、他のスクリプトの実行開始が
待たされる原因にもなります。

頻繁には発生しないイベントで、処理時間がかかるスクリプトを実行したい場合は
スクリプトを別に作成して、このイベントハンドラ中から script_fork_exec() を使用して
別スレッドで実行することを検討してください。

******************************************************************************

XBEE_IO_DATA スクリプト起動時に渡される追加パラメータ

---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------

APIType			フレームデータ中のAPI Type(16進数2桁)					83

SourceAddress	フレームデータ中のSourceAddress
				16bit アドレスの場合(16進数4桁)							0A01
				64bit アドレスの場合(16進数16桁)						0013A200404AC397

SerialNumber	XBee デバイスの SerialNumber
				DeviceServer に保持されたマスターファイルを使用して、
				SourceAddress から変換した値が設定される。				0013A200404AC397

NodeIdentifier	XBee デバイスの NodeIdentifier。
				DeviceServer に保持されたマスターファイルを使用して、
				SourceAddress から変換した値が設定される。				Device1
				マスターにNodeIdentifier未登録の場合は"" が設定される

RSSI			フレームデータ中のRSSI(16進数2桁)						45

Options			フレームデータ中Options(16進数2桁)						00

SAMPLE_COUNT	I/O データのサンプル数									1

SAMPLE_DIO		I/O データ中のサンプル対象となったDIOビット番号リスト
				(10進数、カンマ区切り)									0,1,4

SAMPLE_ADC		I/O データ中のサンプル対象となったADCビット番号リスト
				(10進数、カンマ区切り)									2,3

SAMPLE_<Sample#>_<"DIO"|"ADC">_<Bit#>

				I/O サンプルデータ値。
				DIO の場合は、High で "1"、Low で "0"。					1
				ADC の場合は 10進数。									1023

				<Sample#> には 最大、SAMPLE_COUNT まで 1から順番に
				インクリメントされた値が入る。

				<"DIO"|"ADC">は、I/O サンプルデータが ADC
				もしくは、DIO のどちらであるかを示す。

				<Bit#>は、サンプルデータのビット番号。10進数。

		例:"SAMPLE_1_ADC_0" は、第一サンプルデータ中の #0番ポートのADC変換値を示す。

]]

--[[
log_msg("start..",file_id)
for key,val in pairs(g_params) do
	log_msg(string.format("g_params[%s] = %s",key,val),file_id)
end
]]

--[[
********************************************************************

XBee->MQTT Gateway 機能の実装例

リモート XBee から送信された I/O パケットデータを MQTT に登録します。
トピック名: "/xbee/<NodeIdentifier>/io"
メッセージ文字列:
 {"ad0":<ad0_value>, .. ,"ad1":<ad1_value>, ... ,"dio0":<dio0_value>,"dio1":<dio1_value>,... }

XBee の IO 設定で有効になっている A/D, DIO ポート値のみが上記のフォーマット
で格納されます。

送信先 MQTT ブローカは、サーバー設定プログラムを使用して
エンドポイントを登録して下さい。下記スクリプト中の end_point 変数に
エンドポイントのタイトル文字列または ClientID を設定します。

XBee デバイスの "Sample Before TX" の設定値を1以上にした場合には
最後にサンプリングしたデータのみを MQTT に送信しています

********************************************************************
]]

local end_point = "センサーデータ登録"
local qos = 1
local topic = "/xbee/" .. g_params["NodeIdentifier"] .. "/io"

local ad_list = csv_to_tbl(g_params["SAMPLE_ADC"])
local dio_list = csv_to_tbl(g_params["SAMPLE_DIO"])
local json_str = '{'
local first = true
for i,v in ipairs(ad_list) do
	if first then
		first = false
	else
		json_str = json_str .. ","
	end
	json_str = json_str .. string.format('"ad%s":%s',v,
				g_params["SAMPLE_" .. g_params["SAMPLE_COUNT"] .. "_ADC_" .. v])
end
for i,v in ipairs(dio_list) do
	if first then
		first = false
	else
		json_str = json_str .. ","
	end
	json_str = json_str .. string.format('"dio%s":%s',v,
				g_params["SAMPLE_" .. g_params["SAMPLE_COUNT"] .. "_DIO_" .. v])
end
json_str = json_str .. '}'
mqtt_publish(end_point,topic,json_str,qos)

上記スクリプト中の “end_point” 変数に設定している名前が、先にサーバー設定プログラムで作成したエンドポイントのタイトル名と一致していることを確認してください。

XBEE_IO_DATA イベントハンドラスクリプトでは、リモートの XBee(Series1) デバイスから送信された IO サンプリングパケットは自動的に解析されて、既にスクリプトパラメータに格納されtた状態で起動されます。

そのため、このスクリプトパラメータを利用してMQTT に送信する JSON 文字列を作成します。XBee デバイスの ADC, DIO ポートが有効になっていると、各々のポート値がスクリプトパラメータに設定されていますので、これを連結して JSON 形式に変換します。変換するときの詳しいフォーマットはスクリプト中のコメントを参照してください。

JSON 形式になったIO サンプリングデータは mqtt_publish() ライブラリ関数を使用して、MQTT ブローカに送信(Publish) します。このとき、トピック名には “/xbee/<NodeIdentifier>/io” を使用することで、購読側でデバイスを選択し易くしています。

続いて、XBee-ZB(Series2) のイベントハンドラスクリプト(ZB_IO_DATA.lua) も同様に設定します。

file_id = "ZB_IO_DATA"

--[[

******************************************************************************
* イベントハンドラスクリプト実行時間について                                 *
******************************************************************************

一つのスクリプトの実行は長くても数秒以内で必ず終了するようにしてください。
処理に時間がかかると、イベント処理の終了を待つサーバー側でタイムアウトが発生します。

また、同時実行可能なスクリプトの数に制限があるため、他のスクリプトの実行開始が
待たされる原因にもなります。

頻繁には発生しないイベントで、処理時間がかかるスクリプトを実行したい場合は
スクリプトを別に作成して、このイベントハンドラ中から script_fork_exec() を使用して
別スレッドで実行することを検討してください。

******************************************************************************

ZB_IO_DATA スクリプト起動時に渡される追加パラメータ

---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
FrameType		フレームデータ中のFrame Type
				(16進数2桁)												92

SourceAddress	フレームデータ中のSourceAddress
				64bit アドレス(16進数16桁)								0013A200404AC397

NetworkAddress	フレームデータ中の SourceNetworkAddress
				16bit アドレス(16進数4桁)								D565

NodeIdentifier	XBee デバイスの NodeIdentifier。
				DeviceServerのマスターファイルを検索して設定される。	Node1
				マスターにNodeIdentifier未登録の場合は"" が設定される

DeviceType		XBee デバイスの Device Type
				DeviceServerのマスターファイルを検索して設定される。	01
				マスターにDeviceType未登録の場合は"" が設定される
				8bit値(16進数2桁)
				00: coordinator
                01: router
                02: end device

DeviceTypeID	XBee デバイスの Device Type Identifier
				DeviceServerのマスターファイルを検索して設定される。
				マスターにDeviceTypeID未登録の場合は"" が設定される
				32bit値(16進数8桁)										00030000

ReceiveOptions	フレームデータ中 ReceiveOptions							01
				8bit値(16進数2桁)

SAMPLE_COUNT	I/O データのサンプル数									1

SAMPLE_DIO		I/O データ中のサンプル対象となったDIOビット番号リスト
				(10進数、カンマ区切り)									0,1,4

SAMPLE_ADC		I/O データ中のサンプル対象となったADCビット番号リスト
				(10進数、カンマ区切り)									2,3

SAMPLE_<Sample#>_<"DIO"|"ADC">_<Bit#>

				I/O サンプルデータ値。
				DIO の場合は、High で "1"、Low で "0"。					1
				ADC の場合は 10進数。									1023

				<Sample#> には 最大、SAMPLE_COUNT まで 1から順番に
				インクリメントされた値が入る。

				<"DIO"|"ADC">は、I/O サンプルデータが ADC
				もしくは、DIO のどちらであるかを示す。

				<Bit#>は、サンプルデータのビット番号。10進数。

		例:"SAMPLE_1_ADC_0" は、第一サンプルデータ中の #0番ポートのADC変換値を示す。

]]

--[[
log_msg("start..",file_id)
for key,val in orderedPairs(g_params) do
	log_msg(string.format("g_params[%s] = %s",key,val),file_id)
end
]]

--[[
********************************************************************

XBee->MQTT Gateway 機能の実装例

リモート XBee から送信された I/O パケットデータを MQTT に登録します。
トピック名: "/zb/<NodeIdentifier>/io"
メッセージ文字列:
 {"ad0":<ad0_value>, .. ,"ad1":<ad1_value>, ... ,"dio0":<dio0_value>,"dio1":<dio1_value>,... }

XBee の IO 設定で有効になっている A/D, DIO ポート値のみが上記のフォーマット
で格納されます。

送信先 MQTT ブローカは、サーバー設定プログラムを使用して
エンドポイントを登録して下さい。下記スクリプト中の end_point 変数に
エンドポイントのタイトル文字列または ClientID を設定します。

********************************************************************
]]

local end_point = "センサーデータ登録"
local qos = 1
local topic = "/zb/" .. g_params["NodeIdentifier"] .. "/io"

local ad_list = csv_to_tbl(g_params["SAMPLE_ADC"])
local dio_list = csv_to_tbl(g_params["SAMPLE_DIO"])
local json_str = '{'
local first = true
for i,v in ipairs(ad_list) do
	if first then
		first = false
	else
		json_str = json_str .. ","
	end
	json_str = json_str .. string.format('"ad%s":%s',v,
				g_params["SAMPLE_" .. g_params["SAMPLE_COUNT"] .. "_ADC_" .. v])
end
for i,v in ipairs(dio_list) do
	if first then
		first = false
	else
		json_str = json_str .. ","
	end
	json_str = json_str .. string.format('"dio%s":%s',v,
				g_params["SAMPLE_" .. g_params["SAMPLE_COUNT"] .. "_DIO_" .. v])
end
json_str = json_str .. '}'
mqtt_publish(end_point,topic,json_str,qos)

スクリプトの内容は XBee(Series1) とほぼ同じですが、MQTT ブローカに送信するときのトピック名を “/zb/<NodeIdentifier>/io” に変更しています。

イベントハンドラスクリプトが完成したら、今回の XBee-MQTT Gateway の機能はほぼ実現していることになります。

●リモート側XBee デバイスの IO 設定

次に、光センサを接続したリモート側 XBee デバイスの IO 設定を変更して、IO サンプリングデータが送信されるようにします。

DeviceServer のクライアントを起動してログインします。

ログインしたら、”XBee” ツールボタンを押してPAN 内の XBee デバイス一覧を表示させます。

光センサを接続しているリモート側XBee (Device5) をダブルクリックするか、または選択した後に “設定変更” ツールボタンを押します。現在の XBee 設定値がリモートからロードされた後、詳細設定画面が表示されます。

ここで DIO0 ポートタブを選択して、ADC にチェックをつけます。これで AD0 が有効になりました。次に “Sample Before TX” を 1にして “Sample Rate” を 200ms に設定します。これによって XBee デバイスが 200ms 毎に IO サンプリングを実行して、その結果を DeviceServer に接続した XBee に送信するようになります。

その後 DeviceServer 側では、先ほど設定したイベントハンドラスクリプト中から MQTT ブローカに IO データが JSON 形式で送信されます。

複数の XBee デバイスからの IO サンプリングデータを、同時に MQTT ブローカに送信することもできます。その場合にはそれぞれの XBee デバイスの IO 設定を同様に変更してください。

“OK” を押すと同時に、リモート側 XBee デバイスの IO サンプリングが開始されて MQTT ブローカに送信されるようになります。

●RaspberryPi のコンソール上で、MQTT メッセージを購読して IO データを確認

ここで、MQTT ブローカに送信されている IO サンプリングを見てみましょう。RaspberryPi にログインして MQTT ブローカからメッセージを購読します。

mosquitto_sub コマンドを使用して、MQTT ブローカからメッセージを受信している様子です。XBee デバイスで有効になっている全ての IO 値が JSON 形式で送信されています。光センサに手をかざすと ad0 の値が変化するのを確認できます。

●動作例の動画

XBee-MQTT gateway が動作しているときの動画を載せましたのでご覧ください。(音量注意)

今回は、XBee データを MQTT に変換する例を紹介しましたが、同様のスクリプトを用意することで DeviceServer で取得した様々なセンサデータを MQTT に送信することが可能になります。

また、今回のシステムのセンサデータとは逆方向の Gateway も簡単に作成できます。DeviceServer 側に作成したエンドポイントで MQTT のブローカから特定のトピックを購読して、そのデータを元にリモート側の XBee のポートを変更させることもできます。この時には、MQTT ブローカからトピック・メッセージを受信するときに自動実行される MQTT_PUBLISH イベントハンドラに処理を記述します。

ここで紹介した DeviceServer の機能は、ABS-9000 DeviceServer インストールキットをダウンロードして、直ぐに使用することができます。イベントハンドラで使用したスクリプトファイルも全て格納されていますので是非お試しください。デモライセンスが添付されていますので直ちに使用可能です。こちらから最新のインストールキットやセットアップマニュアル、ユーザーマニュアルをダウンロードできます。

次の記事 “XBee-MQTT Gateway (その2)” では、今回作成したシステムで MQTT ブローカに配信されているセンサデータを WebSocket 経由で受信して、グラフ表示するWebアプリを紹介する予定です。

それではまた。

 

MQTT クライアントとテスト用プログラムを作成しました

●MQTT を使ってみよう

センサーネットワークなどを構築するときには、センサーで取得したデータを何らかの方法で他のシステムに送信する必要がありますが、もし、センサーデバイスに TCP/IP プロトコルを載せた Ethernet インターフェイスが付いていれば、FTP, HTTPなどのプロトコルや独自のプロトコルを利用してソケット通信で簡単に転送できます。これらの標準的なプロトコルとして注目されているのが MQTT とよばれるプロトコルです。MQTT は IBM社 が開発したプロトコル ( http://mqtt.org/ )で、最近 OASIS Standard にも認定されました。特徴としては、

(1) 送信側と受信側の間に Broker とよばれる仲介サーバーを介してデータのやりとりを行う。このため、センサーデータ配信側と利用側が直接連携をとる必要がない。

(2) TCP/IP のソケット通信を使用する( WebSocket で実装されているものもあります )ので送信データが確実に伝わることが期待できる。

(3) 標準機能を実装した Broker ( http://mosquitto.org/ ) や クライアントライブラリ ( http://eclipse.org/paho/ ) が豊富に用意されている

(4) 仕様が公開されていて( http://mqtt.org/documentation ) 、(3) と相まってアプリケーションへの実装が容易

(5) QoS と呼ばれる送達保障レベルをメッセージや購読項目毎に自由に選択可能。CPU リソースが少ないセンサーノードからの通信時には簡単な送信手順が用意されているなど実装の自由度が高い。

今後、 MQTT プロトコルはセンサーネットワーク等に幅広く利用されていくと思います。

●MQTT テストクライアントを作成しました

今回、DeviceServer 製品に MQTT クライアント機能を追加することを想定して、MQTT クライアントライブラリを作成しました。また、このライブラリの機能試験を行うためにテスト用クライアントプログラム(スタンドアロンで動作します)も同時に作成しました。

このMQTT クライアントプログラムは、Windows 環境(Windows XP SP3,Windows 7 64bit で動作確認済み) で動作します。もし興味がありましたら、こちらのリンクからダウンロードできますので記事の内容を見ながら使用してみて下さい(テスト用に作成したものですが、個人用・商用問わず自由に使用していだだいて構いません)。

ダウンロードすると、プログラム本体と DLLファイル が格納されています。プログラム本体をダブルクリックすると下記の画面が表示されます。DLLファイル はメモリ監視用に組み込んでありますが気にしないでこのまま使用してください。

MQTT テストプログラムでは動作時の詳細ログ情報を ABS-9000 DeviceServer のログサーバーとログコンソールに出力しています。DeviceServer のインストールキットは此方からダウンロードできますので併せてインストールしてください。DeviceServer のライセンスはデモライセンスのままで構いません。ログサーバーとログコンソールはデモライセンス期間に関係なく何時までも使用することができます。

●iot.eclipse.org で公開されている MQTT Broker を使用します

クライアントプログラムだけでは試験できませんので、接続先の MQTT Broker を用意する必要があります。ここでは http://iot.eclipse.org が試験目的で使用可能な Broker を提供していていますの最初はこれを利用すると簡単に MQTT を試せます。この Broker はユーザー登録などは不要で常に利用可能になっているようなので早速接続してみます。

“Broker HostName” に “iot.eclipse.org” を指定して、”Port” には “1883″ を指定します。その他の設定は初期値のままにしておいて下さい。 “Open” ボタンを押すとソケット接続を開始します。このとき、ログコンソールプログラムを起動していると下記の様に表示されます。”connecting…” と表示されている部分がソケット接続時のメッセージになります。

エラー無く接続されたら、以降は MQTT のメッセージをやり取りすることになります。

最初に “CONNECT” ボタンを押して、CONNECT メッセージを送信します。すると直後に Broker 側から CONNACK が返ってくるのをログで確認できます。現状ではユーザー名やパスワードは指定しなくても “iot.eclipse.org” の Broker には接続できるようです。

次に、”定期的にPING送信” にチェックを付けます。チェックをつけなくても、”PINGREQ” ボタンを1分以内に1回程度の頻度で手動で押しても構いません。これによって PINGREQ メッセージをBroker に送信して、クライアントがまだちゃんと動作していることを伝えることができます。送信しない状態で “KeepAlive” 値に設定した秒数+α 経過すると Broker は勝手に接続を切ってしまいます。

次に “SUBSCRIBE” ボタンを押します。これで、”hello/world” と “センサー/ノード1″ という2つのトピックの購読を Broker に伝えます。トピックのメッセージを受け取るときにはそれぞれ QoS の 2 と 0 のプロトコルを使用することを指定しています。ここでは1度に2つのトピックと QoS をカンマ区切りで指定していますが、”SUBSCRIBE” ボタンを複数回押してトピックの購読を後から追加していくこともできます。

最後に、”PUBLISH(str)” ボタンを押して、トピック名 “hello/world” でメッセージを送信します。このときにペイロードに格納されるデータは “PublishData(str)” のテキストエリアに入力されている文字列(UTF8文字列)になります。送信するときに指定する QoS はデフォルトで2になっています。

ログには、クライアントプログラムから送信された PUBLISH データと Broker 側からクライアント側に配信されて戻ってきたデータを確認できます。ログ中の “—- PublishData —-” と表示されている部分が購読しているトピックのデータを受信したときのログになります。Broker 側から配信されたデータが 256Bytes 以内の場合には、データバイト列を UTF8 文字列に変換してログにも表示します。また同時に、クライアントプログラムが配置されているフォルダには受信したデータバイト列がファイルとして保存されます。

“Pub_xxxxxxxxxxxxx.dat” のファイル名で保存されているのが受信した PUBLISH のペイロード部分をファイル書き出したものです。このファイルは PUBLISH メッセージを受信する度に増えていきますので、いらなくなったら削除して下さい。

Brokerとの接続を切るときには、最初に “DISCONNECT” ボタンを押します。その後、”Close” ボタンを押してソケットを閉じます。このとき、”Close” を押さずにプログラムを終了するとMQTT接続で使用しているリソースに関するメモリー情報がダイアログに出力されますが、気にしないで下さい。

●クライアントを複数起動してチャット(LINE?) のような事もできます

MQTT クライアントプログラムは同じ PC 上で複数同時に起動することもできます。下記の画面をご覧ください。

このとき、”CONNECT” ボタンを押すときに “ClientID” エリアに入力されているデフォルト文字列が必ず異なっていることを確認してください。数字部分を乱数で生成していますのでそのままで大丈夫だと思います。

ここで、双方のアプリで “SUBSCRIBE” を押して “PUBLISH(str)” で好きなメッセージを送信することができます。同じトピック “hello/world” を購読していますのでチャットのような動作になります。

“PUBLISH(str)” ボタンを押すときに “PublishQoS” を 0, 1,2 にそれぞれ変更すると、MQTT プロトコルでやり取りされるパケットの種類が変化するのをログで確認できると思います。

●RaspberryPi にMQTT Broker をインストール

ここからは、Will メッセージ送信やデータファイル送信などを試してみたいと思います。強制的にソケットを切断したり、Broker に負荷がかかるデータ送信試験などを行いたいので、パブリックに公開されている Broker を使用しないでここからは、自前で用意した Broker を使用します。

ここでは、RaspberryPi (下記写真) に MQTT Broker をインストールします。

RaspberryPi には Raspbian OS が動作しています。ここに、オープンソースで開発されている MQTT Broker “mosquitto” をインストールします。(蚊?スペルが似てるからですね…)

インストールの方法は http://mosquitto.org/ に詳しく書かれています。RaspberryPi 用には予めコンパイル済みのパッケージを利用できますのでこれを利用するのが簡単です。インストールするときのコマンド例のみを以下に紹介しますが、詳しい手順については http://mosquitto.org/2013/01/mosquitto-debian-repository/ に詳しく書かれていますので確認してください。

(1) レポジトリにパッケージ追加

wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
sudo apt-key add mosquitto-repo.gpg.key

cd /etc/apt/sources.list.d/
sudo wget http://repo.mosquitto.org/debian/mosquitto-stable.list

(2) パッケージアップデート

apt-get update

(3) mosquitto Broker とクライアントプログラムをインストール

apt-get install mosquitto
apt-get install mosquitto-clients

これでインストール完了です。mosquitto Broker をインストールすると自動的に MQTT Broker プロセスが起動している筈ですが、起動していない場合や手動で起動する場合には、下記の様にコマンドを実行します。

これで、自前の Broker が作成できました。ここからはこの Broker を使用して試験をします。

●Will メッセージを受信する

MQTT プロトコルには、予期しないネットワーク切断が発生したときにSubscribe しているクライアントに対してメッセージを出力する機能 “Will Message” が用意されています。先ほど RaspberryPi に作成した Broker を使用してこのメッセージを出力してみます。

最初に、MQTT クライアントプログラムを使用して RaspberryPi (この例ではIP アドレス 192.168.100.14 になっています)に接続します。

“Broker HostName” にRaspberryPi の IP アドレスを設定して “Open” を押します。その後、”WillTopic” と “WillMessage” に文字列が設定されているのを確認した後、”CONNECT” を押して Broker に接続します。ここで、”SUBSCRIBE” を押した後、”PUBLISH(str)” ボタンでデータを送信するたびに、Broker 側から同じ文字列が配信されてくる様子をログで確認してください。

ここで、別のコンソールを使用して RaspberryPi に接続して RaspberryPi 上でも Subscribe を実行します(MQTT クライアントプログラムをもう一つ起動する方法でも構いません)。 コマンド “mosquitto_sub -d -v -t hello/my_will ” を実行してください。ここで指定する “hello/my_will” は、クライアントプログラムで “CONNECT” したときに指定した WillTopic の内容に合わせています。

ここで、クライアントプログラムの “Close” ボタンを押すと RaspberryPi のコンソールには WillMessage (この例では “さようなら”) が表示されると思います。”DISCONNECT” ボタンを押してから “Close” を押した場合には正常な切断方法ですので WillMessage は表示されません。

ちなみに、クライアント側からの切断だけでなく Broker 側からの切断時にも WillMessage が配信されます。クライアントプログラムで “CONNECT” ボタンを押した後にもう一度 “CONNECT” を押してみて下さい。このときには Broker で異常事態を検出してソケットを勝手に切断してしまいます。このときにも RaspberryPi のコンソールにWillMessage が表示されるのを確認できると思います。

●大きなサイズのメッセージを Publish する

MQTT クライアントで送信するデータは、MQTT PUBLISH メッセージフレーム中のペイロードと呼ばれるエリアに格納されます。今までの動作例ではこの部分に UTF-8 エンコードされた文字列データを格納していました。

MQTT の仕様上 PUBLISH ペイロードは格納するデータ内容は自由なので、任意のバイナリデータを格納することもできます。また、ペイロード部分とヘッダ情報を合わせて最大 256MBytes までのデータを PUBLISH メッセージに格納することができる仕様になっています。

もちろん、Broker を動作させるRaspberryPi の搭載メモリやCPUパワー、クライアントプログラムの動作環境に依存しますので、常に 256MBytes のデータをやり取りすることはできないと思います。そこで、どれぐらいのデータサイズまで PUBLISH 可能かを試してみます。

MQTT クライアントアプリには “PUBLISH(file)” ボタンが用意されています。これを利用するとファイルの内容をバイナリ列として PUBLISH フレーム中のペイロードに格納して送信できます。そこで 20MBytes 程度のファイルを転送してみます。(**注意** 必ずローカルに設置した自前の Broker で試験してください。高い負荷がかかると Broker 側がハングする可能性があります!! また、他の人が同じ Topic を購読していると大変なことになります!!)

ここでは、適当な映像ファイル (test2.wmv) ファイルを PUBLISH すると同時に、配信されてきたデータを受信します。このとき Broker 側から受信したデータはMQTTテストクライアントプログラムと同じフォルダにファイルの形で格納されますのでこのファイルの内容を確認します。

Pub_xxxxxxx.dat のファイル拡張子を元のファイルと同じ “.wmv” へ変更すると、内容が正しく配信されているかを再生することで確認できます。一々再生しなくても MD5 ハッシュ値を求めて確認する方法がスマートかもしれません。

私の環境で RaspberryPi を Broker に使用した場合には、実用的な速度で送信可能な最大サイズは 20MBytes ぐらいになりました。そこで、どうしても 100MBytes を転送してみたくなったので、Windowsマシン(Windows7 64Bit,CPU i7-4770,メモリ 16G)に Windows 版 mosquitto Broker をインストールして試してみました。

コマンドプロンプトから Broker を起動して、MQTT クライアントアプリを localhost に接続して試験した様子です。なんとか転送できているのを確認することができます。(同じ環境で 200MBytes までは正常な転送を確認できました!!)

実際の環境では Subscribe を依頼しているエンドポイントの数だけ Broker から送信しますので、実用的には数MBytes が限度かもしれません。

今回紹介した MQTT クライアントプログラムを使っていて、不具合を見つけた場合や要望などがありましたら、遠慮なく contact@allbluesystem.com までメールお寄せ下さい。

それではまた。