作成日: 2013年12月28日
最終更新日: 2014年1月23日

Arduino YunのBridgeメモ

Arduino YunはATMega32U4とLinux(Linino)とのインターフェースのために「Bridge」という仕組みが用意されている。では具体的にどのようにやりとりをしているのか、特にKey-Valueのやりとりについての確認。

パケットの構造

基本的にBridgeの通信はシリアル。32U4の場合はシリアル通信のインスタンスであるSerial1を使って通信している。Linino側ではコンソールの入出力でやりとりを行う。
通信は「パケット」の単位で通信を行う。
パケットの構造は以下のとおり。

内容 バイト数 備考
マーク 1 0xFF固定
インデックス 1 送信順に0から加算される
データ長 2 ペイロードのバイト数。Hi, Loの順
ペイロード n  
CRC 2  

これらはBridge.cppのtransferメソッドで送信、受信される。
各ペイロードは、先頭1バイトがタグ、2バイト目以降はタグに依存したデータ。タグはアルファベット1文字。例えば
process.run();
で送信されるコマンドは「R」で、それに対するレスポンスは「r」。タグに応じてLinino側がデータの処理を行う。

32U4側の処理

32U4側でパケットを送信する最も低レベルな関数がBridge.transfer()。ペイロード(のポインタと長さ)を渡す事によって上記パケットの送信、レスポンスの受信を行う。
その上位の関数として基本的なデータの受け渡しをするものがBridge.put() および Bridge.get() 。例えばBridge.put関数を呼ぶと、そのパラーメーターであるKeyとValueがつなぎ合わされてひとつのデータが作成され(KeyとValueの間は0xFEで分けられる)、さらにタグ「D」が追加され、それがBridge.transfer関数でlinino側に送信される。

Linino側の処理

Linino側は、/usr/bin/run-bridge というスクリプトでBridgeのサーバーに対応するプロセスが起動する。このプロセスは32U4側の、Bridge.begin() によって起動される。
run-bridgeはbridge.pyというスクリプトを起動している。このスクリプトは/usr/lib/python2.7/bridge/以下にある。
このスクリプトでは各コマンドに対応するモジュールをインポートし、コマンドとして動作するよう登録している。例えば32U4のprocess.run() (「R」タグ)を処理するスクリプトはprocesses.py。ここで以下のようにコマンドを登録している。

def init(command_processor):
  command_processor.register('R', RUN_Command(processes))   
    

パケットの受信は packet.py の中の PacketReader クラス内 process 関数にで行われている。

class PacketReader:
(中略)    
  def process(self):
    if self.processor.finished:
      return False
      
    # Do a round of runners
    self.processor.run()
    
    # Wait for Start Of Packet
    while True:
      c = self.t_read()
      if c is None:
        return None
      if ord(c) == 0xFF:
        break
          
    # Read index and len
    index = self.t_read()
    if index is None:
      return None
    len_hi = self.t_read()
    if len_hi is None:
      return None
    len_lo = self.t_read()
    if len_lo is None:
      return None
      
    crc = CRC(None)
    crc.write(c)
    crc.write(index)
    crc.write(len_hi)
    crc.write(len_lo)
      
    len_t = (ord(len_hi) << 8) + ord(len_lo)
    
    # Read payload
    data = ''
    for x in range(len_t):
      c = self.t_read()
      if c is None:
        return None
      data += c
      crc.write(c)
    
    # Read and check CRC
    crc_hi = self.t_read()
    if crc_hi is None:
      return None
    crc_lo = self.t_read()
    if crc_lo is None:
      return None
      
    crc_hi = ord(crc_hi)
    crc_lo = ord(crc_lo)
    if not crc.check((crc_hi << 8) + crc_lo):
      return None
(以下略)

受信したペイロードの処理は bridge.py 内 CommandProcesser クラスの process メソッド内で、タグに応じたクラスのrunメソッドが実行される(processメソッドはpacket.py内 PacketRunner クラスの process メソッド内から呼ばれる)。

cmd = self.commands[data[0]]
return cmd.run(data[1:])

この commands はcommand_processor.registerにて予め登録されている。

ここで32U4側からput関数が呼ばれた場合、mailbox.py内 DATASTORE_PUT_Command クラスの run メソッドが呼ばれる。ここでデータが Key と Value に分割され、それが Mailbox クラス内の data_store ディクショナリに保存される。

Linino側での他のアプリとのデータのやりとり

Mailbox クラス内 data_store ディクショナリのデータは、Linino側では TCPJSONServer を経由してやりとりを行う。。
Mailbox では TCPJSONServer が起動される。TCPJSONServer に接続するために TCPJSONClient クラスが用意されている。さらに TCPJSONClient クラスを使ってデータをやりとりするために BridgeClient クラスが用意されている(bridgeclient.py の中にある)。
例えば BridgeClient.get(key) メソッドを呼ぶと、その中で TCPJSONClient インスタンスが作成され、key を指定した get コマンドが発行され、その結果が返る。

サンプル(Pythonの場合)

32U4側からLinino側に対して、Bridge (Mailbox) を使用してデータを受け渡す例。
まずは32U4側のスケッチ。「count」というキーに、0から1秒間隔でカウントアップされるデータをセットする。

Sketch
#include <Bridge.h>

unsigned int counter = 0;

void setup()
{
    Bridge.begin();
}

void loop()
{
    Bridge.put("count", String(counter++));
    delay(1000);
}
    

Linino側のpythonスクリプト

example.py
#!/usr/bin/python
#
# Bridge client test (get)

import sys
sys.path.insert(1,'/usr/lib/python2.7/bridge')

from bridgeclient import BridgeClient

bridgeClient = BridgeClient()

while True:                          
  value = bridgeClient.get('count') 
  print value

これでコンソールには1秒ごとに更新される数字が次々に表示される。

注:インポートするディレクトリを指定する時は通常であれば sys.path.append を使うが、 /usr/lib/python2.7/bridge 内のモジュールの中で json というモジュールをインポートしている。これは /usr/lib/python2.7/bridge/json.py をインポートすることを意図したものであるが、 /usr/lib/python2.7 の中に json というディレクトリがあるため、bridge ディレクトリ内の json.py モジュールがインポートされなくなってしまう。そこで、/usr/lib/python2.7 より先に /usr/lib/python2.7/bridge 内を探すよう、sys.path リストの先のほうに insert している(ちなみに sys.path[0] は空文字、つまりカレントディレクトリ。なので、このスクリプトを /usr/lib/phthon2.7/bridge ディレクトリの中に入れれば sys.path.insertが無くても動く)。

逆にLinino側から32U4への通信の例。
32U4側。「count」というキーに対する値を取り出し、それを表示する。

Sketch
#include <Bridge.h>

char buffer[255];

void setup()
{
    Serial.begin(57600);
    Bridge.begin();
}

void loop()
{
    Bridge.get("count", buffer, 255);
    Serial.println(buffer);
    delay(100);
}

Linino側。1秒間隔にカウントされる値を「count」というキーに対する値として設定する。

#!/usr/bin/python
#
# Bridge client test (put)

import sys
import time
sys.path.insert(1,'/usr/lib/python2.7/bridge')

from bridgeclient import BridgeClient

bridgeClient = BridgeClient()
count = 0

while True:                          
  value = bridgeClient.put('count',str(count)) 
  count += 1
  time.sleep(1)
  

これで32U4側でシリアルモニタを開けば、1秒毎にカウントアップされる値が0.1秒毎に表示される。

http(REST)の場合

Pythonを直接使用しない場合はHTTP(REST) によりやりとりできる。例えば key の値を取り出したければ、http://localhost/data/get/key/ で取り出せる(得られる結果はJSON)。
これを使えばJavascriptを用いてAjaxで値のやりとりももちろん可能。このサンプルはhttp://arduino.local/keystore_manager_example/ でアクセスできる。
独自のWebページをmicroSD上に置き、80以外のポート番号でアクセスする場合などは、microSD上のドキュメントルートとなるディレクトリに /www/cgi-bin 以下をコピーしておけば、例えば http://localhost:81/data/get/key/ 等でアクセスできる。
下は、/www/keystore_manager_example/ 以下をmicroSDにコピーし、さらに一部を日本語に書き換え、それをhttpsでアクセスした例(ポート番号は8443とした)。

PHPの場合

PHPをインストールすればもちろんPHPでやりとりすることも可能。TCPJSONServerから取り出すことができる。
以下のスクリプトは32U4側からcountの値を送信し、それを受信する例。

<?php
print "<html><body>";
$fp = stream_socket_client("tcp://localhost:5700", $errno, $errstr, 10);
fwrite($fp,'{"command":"get","key":"count"}\n');
$readArray = array($fp);
$nullDummy = NULL;
$readData = "";
while(stream_select($readArray,$nullDumy,$nullDummy,10)) {
$readData .= fread($fp, 4096);
if(!($record = json_decode($readData))) continue;
print $record->{'value'};
break;
}
fclose($fp);

print "</body></html>";

これで、Webブラウザでこのスクリプトにアクセスすれば、count の値が表示される。

外部から直接JSONServerにアクセス

Linino上のTCPJSONServerはlocalhost(127.0.0.1)以外からアクセスできないようになっているが、もしセキュリティー上問題ない環境であればこれをNetwork経由でアクセスできるようにすれば、他のマシンから直接アクセスできる。Yunにて得たセンサー等のデータを他のマシンでなんらかの処理をする時などはhttpでアクセスするよりも便利かもしれない。
そのためには /usr/lib/python2.7/bridge/mailbox.py を書き換える。

bridge.py
(前略)
from tcp import TCPJSONServer
from collections import deque
import json

json_server = TCPJSONServer('0.0.0.0', 5700)
(後略)

元は TCPJSONServar('127.0.0.1', 5700) となっているが、このIPアドレスを 0.0.0.0 に書き換える。
これで例えば上のPHPスクリプトを他のマシンで動作させても(もちろん tcp://localhost:5700 の部分はLinimo のIPアドレスに書き換える)目的の値を取り出すことができる。




home | Elec top

inserted by FC2 system