今回は、SCRATCH プログラムからセンサーネットワークのデータを利用する方法について紹介したいと思います。
SCRATCH は MITで開発された教育用のプログラミング環境で、ビジュアルにブロックを並べるようにしてアプリケーションを簡単に作成できます。(SCRATCH ホームページ)
SCRATCH プログラムから、PCに接続した (PicoBoard) という専用の I/O デバイスを利用してセンサー値を取得したり、I/O を操作するプログラムも作成できます。また、標準では隠されていますが “共有機能” を有効にすると、ネットワーク経由で任意のセンサー値を受信して SCRATCH プログラム内で利用できるようになります。(SCRATCH共有機能)
ここではこの共有機能を使用して、リモートに設置したXBee-ZB のセンサー値を SCRATCH から利用してみます。
上のキャプチャ画像は、XBee-ZB エンドデバイスやルータデバイスから定期的に送信される I/O データ値を SCRATCHから利用している様子です。SCRATCH では取得したセンサー値に応じて、アニメーション表示や音を鳴らすことが簡単にできます。また、自分で作成した画像をアニメーション表示できますので、簡単にセンサーの監視盤を作成できます。
この記事では XBee-ZB の I/O データについて説明していますが、DeviceServer で監視している他のセンサーデータ値を、SCRATCH で利用するように応用することも簡単にできます。
XBee-ZB エンドデバイスは下記の様にブレッドボードで作成しています。XBee-ZB にスイッチと光センサ(CDS) を接続しています。
DIO#11 をデジタル入力用に設定してタクトスイッチを接続します。ADC#3 には光センサを接続します。Vcc(3.3V) を光センサ(CDS) と抵抗で分圧した値が、XBee-ZB ADC#3 のリファレンス電圧(1.2V) に収まるように抵抗値を調整します。手持ちの CDS では+(左)側が1.5K,GND(下)側が 1K になりました。その他の配線は、前回の記事中で使用したものと同じですのでこちらを参考にしてください。
次に、XBee-ZB の詳細パラメータを設定します。DIO#11 の設定は以下のようになります。
DIO#11 を Digital input に設定しています。同時にプルアップとChange Detect も有効にしています。今回は定期的にサンプリングデータを送信しますので Change Detect は無効にしても正常に動作します。Change Detect を有効にしておくことで、スイッチを “押した” 時と “離した” 時に DIO のみが格納されたサンプリングデータが追加で送信されますので、スイッチの入力にすばやく反応して、SCRATCH 側のセンサー値を変化させることができます。
次に、光センサ(CDS) を接続した ADC#3 の設定を行います。
ADC#3 (DIO#3) を Analog input に設定します。設定画面上部の IO Sampling Rate を 300(ms) に設定して、0.3 秒ごとに I/O 値を Coordinator(DeviceServer)に送信するようにします。
これらの設定が完了すると、XBee-ZB デバイスから定期的に I/O データが送信されてきて DeviceServer の ZB_IO_DATA イベントハンドラがその都度実行されます。 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 local shared_key for key,val in orderedPairs(g_params) do ------------------------------------------------------------------------------- -- I/O サンプリングデータを共有メモリに格納する -- <NodeIdentifier>_<"DIO"|"ADC">_<Bit#> のキー名でサンプリングデータを格納する ------------------------------------------------------------------------------- if string.match(key,"SAMPLE_1") then shared_key = g_params["NodeIdentifier"] .. string.sub(key,9,-1) if not set_shared_data(shared_key,val) then error() end ---------------------------------------------------------------------------------- -- 共有メモリに格納したサンプリングデータ項目のリストを共有メモリリストに格納して -- SCRATCH への送信対象とする ---------------------------------------------------------------------------------- if not add_shared_strlist("XBeeZBサンプリング項目",shared_key,true) then error() end end end ------------------------------------------------------------- -- SCRATCH にソケット通信でセンサーデータを送信するタスクに、 -- 送信すべきデータが揃ったことを通知する ------------------------------------------------------------- if not event_set("SensorDataExist") then error() end
このイベントハンドラでは XBee-ZB から送信されてきた複数のI/O データを DeviceServer の共有メモリに格納しています。その後、このイベントハンドラとは別に動作している送信用のスクリプトに対して、送信対象のデータが共有メモリに設定したことを知らせるために、event_set() 関数でイベントを発生させます。
このアプリケーションでは、センサーデータの取得と処理(SCRATCH に送信)を2つのタスクに分けて別々に動作するようにしています。実は、この XBee-ZB のデータを受信したときのイベントハンドラ中から直接 SCRATCH プログラムに TCP/IP で送信しても動作するのですが、あえて2つに分けているのは以下の理由からです。
XBee-ZB のイベントデータは 300ms 間隔で定期的に送信されてきます。その間隔で SCRATCH プログラムにメッセージを通信するのは問題ないのですが、もし同様の XBee-ZB エンドデバイスを同時に複数使用する場合や、サンプリング間隔をもっと短くした場合を考えます。このときもたぶん動作しますが、SCRATCH プログラムを手動で停止させたときに問題が発生します。SCRATCH プログラムを終了させると、もちろんTCP/IP でメッセージを送信する部分でエラーが発生します。このエラー(タイムアウト)を検出するまでの間にこのイベントハンドラがいくつも同時に実行されたままになって、リソース(DeviceServer のLua スクリプトエンジンの最大同時実行数デフォルト値 15) を超えてしまい、他の監視機能に影響を与えてしまう恐れがあります。
動作を分けると、SCRATCH との通信に時間がかかって同一のI/O データ項目が複数回更新されたときでも、最後に到着したデータを送信するだけの動作になりますので効率的に通信をすることができるようになります。
下記はSCRATCH に送信する SCRATCH_SEND_TASK.lua スクリプトの内容です。
file_id = "SCRATCH_SEND_TASK" ------------------------------------------------------------------------------------------ -- SCRATCH プログラムの Mesh サーバーにセンサーデータを送信するためのタスクです。 -- イベント "SensorDataExist" がセットされたタイミングでセンサーデータを送信します。 -- 送信するセンサーデータは共有変数に格納されていて、共有変数の名前(キー名)が、 -- 共有文字列リスト "XBeeZBサンプリング項目"に格納されています。 -- -- このスクリプトは無限ループになっていて、終了させる場合には、"タスクリスト"プログラム -- (TaskList.exe)を使用して強制終了します。また、SCRATCH プログラムへのソケット通信で -- エラーが発生した場合(SCRATCH プログラムを終了した場合など)にも、このスクリプトは -- エラーを発生させて終了します ------------------------------------------------------------------------------------------ log_msg("start..",file_id) local stat,strlist,data while true do -- エラー発生または強制終了されるまでバックグランドで実行する ------------------------------------------------------------------ -- 既にこのタスクが実行中であるかを確認するためのフラグを更新する ------------------------------------------------------------------ if not set_shared_data("SCRATCH_SEND_TASK_RUNNING","1") then error() end ------------------------------------------------------------------ -- 送信対象のデータが揃うまでイベントを待つ -- 100ms ごとにタイムアウトして、上記のタスク実行中を示すフラグを更新するタイミング -- に利用する。 ------------------------------------------------------------------ if event_wait("SensorDataExist",100) then ------------------------------------------------------------------ -- センサーデータ項目が格納された共有文字列リストの内容を取得する -- リストの内容を取得すると同時に文字列リストをクリアしておくことで、 -- SCRATCH に送信するデータを更新分だけにできる ------------------------------------------------------------------ stat,strlist = get_shared_strlist("XBeeZBサンプリング項目",true) if not stat then error() end ------------------------------------------------------------------ -- SCRATCH に送信する "sensor-update" メッセージを組み立てる ------------------------------------------------------------------ local msg = "sensor-update" for key,val in ipairs(strlist) do stat,data = get_shared_data(val) if not stat then error() end msg = msg .. ' "' .. val .. '" ' .. data end log_msg(msg,file_id) ----------------------------------------------------------------------------- -- sensor-update メッセージを送信 ----------------------------------------------------------------------------- if not scratch_send("localhost",msg) then error() end ----------------------------------------------------------------------------- -- broadcast メッセージを送信する -- このメッセージを送信しなくても SCRATCH 側でセンサーデータ項目を利用 -- できますが、センサーデータ更新タイミングでSCRATCH 側のスクリプトを書き易い -- ように送信しておく ----------------------------------------------------------------------------- if not scratch_send("localhost",'broadcast "my_data_update"') then error() end end end log_msg("end.",file_id)
このスクリプトは起動されると無限ループに入って、強制的に終了させるかまたは通信エラーが発生するまでイベントの到着を待ち続けます。XBee-ZB の I/O データを受信したイベントハンドラでイベントがセットされると、共有データに格納されている I/O データの最新値を取得して、SCRATCH に送信するメッセージを組み立てます。
SCRATCH のメッセージは以下のような内容です。”sensor-update” の後にセンサー項目名とセンサー値がペアで続きます。詳しい仕様はこちらをご覧ください。
sensor-update “Node1_DIO_11″ 0 “Node1_ADC_3″ 200 …..
また、全てのセンサーデータを送信した後に ”broadcast” メッセージを送信しています。
broadcast “my_data_update”
これは、SCRATCH 側でプログラムを作るときに、”my_data_update” という名前のメッセージが届いたときに、最新のセンサーデータを利用した動作を簡単に作成できるようにするために送信しています。送信には専用の API 関数 scratch_send() 関数を使用しています。この関数はパラメータで指定された文字列を TCP ポート番号42001 で送信します。文字列の先頭にはデータ長を示す 4 バイトのデータを追加して送信します。
上記の送信スクリプトは無限ループに入ってしまうので、直接クライアントプログラムから起動せずに以下の SCRATCH_SEND_TASK_SUBMIT.lua スクリプトを実行して、間接的に SCRATCH_SEND_TASK.lua を起動させます。
file_id = "SCRATCH_SEND_TASK_SUBMIT" ------------------------------------------------------------------------------------- -- SCRATCH_SEND_TASK を別スレッドで起動する ------------------------------------------------------------------------------------- log_msg("SCRATCH_SEND_TASK が実行中であるかをチェックします.. 2秒かかります",file_id) if not set_shared_data("SCRATCH_SEND_TASK_RUNNING","") then error() end ------------------------------------------------------------------------------------- -- SCRATCH_SEND_TASK が実行中と仮定して、センサーデータの確認イベント待ちでタイムアウト -- が確実に発生するまでウェイトする。(100ms + SCRATCH へ送信中の場合にはそれが完了するまでの時間) -- もしタイムアウトが発生すると、共有変数 "SCRATCH_SEND_TASK_RUNNING" が "1" に設定 -- されるのでSCRATCH_SEND_TASK スクリプトタスクが実行中であることが判る ------------------------------------------------------------------------------------- wait_time(2000) local stat,val = get_shared_data("SCRATCH_SEND_TASK_RUNNING") if not stat then error() end if val == "1" then log_msg("SCRATCH_SEND_TASK は既に実行中です",file_id) return end log_msg("SCRATCH_SEND_TASK を別スレッドで実行します",file_id) local stat,taskid = script_fork_exec("SCRATCH_SEND_TASK",{}) if not stat then error() end
このスクリプトでは送信用のスクリプトを2重に起動しないような仕組みも記述しています。詳しくはスクリプト中のコメントを参照してください。
これで センサーデータ送信側の準備が整いましたので、SCRATCH プログラムを起動します。SCRATCH プログラムは、ここからダウンロードできます。今回の記事で紹介している内容は SCRATCH ver1.4 のプログラムを対象にしています。オンライン版(ver2.x) の SCRATCH も公開されていますが、これには対応していませんので注意してください。
SCRATCH プログラムを起動した状態では外部からのネットワーク経由のセンサーデータ受信には対応していませんので、このサイトを参考に共有機能メニューを有効にする必要があります。
詳しい手順は上記のサイトに書かれていて、SCRATCH プログラム自身のソースをSystem Browser で変更しています。一度設定を変更してデフォルト値を保存しておくと、次回からはこれらの操作を行う必要はありません。次に、共有メニューを “SHIFT” キーを押しながら選択すると “HOST Mesh” が選択できるようになりますので、これを実行します。
またこの方法とは別に、簡単に共有設定を行う方法があります。SCRATCH コンポーネントの “xxxのセンサー値 ” をマウスで長押しすることで表示される ”遠隔センサーを有効にする” をチェックすると同様の機能が使えるようになります。
これで SCRATCH プログラムでソケットサーバーが起動されて外部からの接続を受け付ける状態になっています。ここで先に設定した、”センサー値を送信するスクリプト” を起動するためのスクリプト SCRATCH_SEND_TASK_SUBMIT.lua を実行します。
実行すると、XBee-ZB から定期的に送信されているセンサーデータが SCRATCH にも送られるようになります。このときのログは以下のように出力されます。
この状態で SCRATCH からは XBee-ZB のI/O データをセンサー値として利用できるようになっています。SCRATCH コンポーネントの “xxxのセンサー値 ” のプルダウンメニューに、XBee-ZB から送信された I/O データが表示されていると思います。
複数の XBee-ZB エンドデバイスから送信されている場合には、センサー名の頭に NodeIdentifier がついていますので簡単に区別できます。XBee-ZB デバイスの詳細設定でI/O 設定を変更して I/O を増やした場合には自動的に新規のメニュー項目が追加されます。
“xxxのセンサー値 ” コンポーネントのチェックボックスにチェックを付けると、SCRATCH 画面の右上に監視盤が表示されて、現在のセンサー値がダイナミックに表示されるようになります。
上のキャプチャ画像では監視盤に加えて、スクラッチ側で簡単なスクリプトを作成しています。XBee-ZB に接続した光センサに手をかざして暗くなると、キャラクタの色が変化するようにしています。このスクリプトはセンサーデータと同時に SCRATCH に送信した broadcast メッセージを受信したタイミングで実行します。
SCRATCH プログラムを終了させると、DeviceServer 側で動作している送信用のスクリプト SCRATCH_SEND_TASK.lua 内で通信エラーが発生して自動的にスクリプトが終了します。手動でスクリプトを停止させる場合には DeviceServer アプリケーションに付属のタスク管理プログラムから “削除” ボタンを押してスクリプトを強制終了できます。
ここまでの操作を動画にまとめてありますので是非ご覧ください。(音量注意)
それではまた。