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) 。

それではまた。