こんにちは、Mr. ZigBeeです。
前回のエントリで開発環境を構築し仕様書を集め終えたので、いよいよZigBee的Hello Worldプログラムの作成に取りかかります。今回、作成するZigBee的Hello Worldは、Coordinator側のXBeeから、End Device側のXBeeへのユニキャスト、ZigBeeの64ビットアドレスを指定してパケットを送信するシンプルなプログラムです。ZigBeeのトポロジやプロトコルの詳細は、ひとまず忘れて、ZigBeeの無線を動作させてみます。
ZigBee的Hello World!のゴール
Coordinator側からEnd Deviceに、APIモードでHello World!のバイト列を送信し、正しく受信できている事を確認する。
XBeeチップの設定
ZigBeeの通信には、シリアルポートへRead/Writeする事で透過的にPAN(Personal Area Network)内のZigBeeと通信できる、Serial Transparentモードと、より柔軟な制御が可能なAPIモードがあります。今回は、後者のAPIモードを利用して2つのXBeeチップ間で通信を行います。(実際には、APIモードでは、データをエスケープするかどうかにより、2つのモードにわかれています。)
工場出荷時の状態では、Serial Transparentモードで通信を行うように設定されているので、APIモードへとXBeeの設定を切り替える必要があります。XBeeの設定は、シリアルからコマンドモードと呼ばれる設定モードに入り、コマンドを発行する事でXBeeの設定を更新する事ができます。Windowsなら、
X-CTUや
Tera Term、Macなら
Z-Term等のターミナルソフトウェアが利用できます。
デフォルトでは、XBeeのシリアル接続のボーレートは9600bpsに設定されてます。ここでは、19200bpsに変更します。
はエンターキーです。OKは、XBeeからのレスポンスで、入力する必要はありません。ここでの設定項目は、以下の4つです。最後にATWRコマンドで設定を永続化する必要があります。このコマンドを発行しないと、電源を落とした時に設定が消えてしまいます。
- ボーレートを19200bpsに変更
- APIモードへ切り替え
- ノードIDの設定
- PAN(Personal Area Network) IDの設定
- コーディネーターとして設定 (コーディネーターのみ)
設定例
+++OK // +++と入力後、しばし待つとOKと表示される
ATBD 4<cr> // ボーレートを19200bpsに変更
OK
ATNI COORDINATOR<cr>// ノードID(20文字以内の任意のASCII文字列)
OK
ATAP 2<cr>// APIモード(バイト列が必要に応じてエスケープされる)に設定
OKCR
ATID 0x1a 0xaa<cr>// PAN IDの設定 (0-0xFFFFの間で設定する。デフォルトは、0x3332)
ATWR<cr>// 設定の書き込み
OK
ATCE 1<cr>
ATFR // 再起動
2台のXBeeの設定は、次のようになります。XBeeは、同一のPAN ID間で通信を行います。
Coordinator
- ボーレート: 19200bps
- ノードID: COORDINATOR
- モード: API
- PAN ID: 0x1a 0xaa
End Device
- ボーレート: 19200bps
- END_DEVICE_1
- モード: API
- ノードID: 0x1a 0xaa
PCとの接続
今回、XBee単体での制御とArduino/XBee Shield経由での制御を学ぶために、一台をSparkfunより販売されている
XBee USB Explolerに、もう一台をXBee Shield上にセットアップします。XBee USB Exploler側に、Coordinator、ArduinoにEnd Deviceをそれぞれ配置します。状態とエラーの確認のために、ブレッドボード上に緑色と赤色のLEDを設置しています。緑色のLEDをArduinoの13番ピンと、赤色のLEDを12番ピンと結線しています。

ZigBeeスタック
XBeeの仕様は、
前回のエントリより無料で入手できるので、ZBeeスタックのライブラリを自作する事ができますが、今回は、オープンソースで作成されている以下の2つのプロダクトを利用します。
xbee-apiが、シリアルからXBeeを制御するためのライブラリで、
xbee-arduinoがArduino上でXBeeを制御するためのライブラリです。
Coordinator側の実装
Coordinator側は、先程紹介したxbee-apiライブラリを用いてScalaで実装しています。始めにPAN内に存在するノードリストを取得するために、ATNDコマンドを発行します。プログラム中では、nodeDiscoverメソッド内でATNDコマンドを発行し、送信先End DeviceのシリアルID(64bit)を取得します。この64bitのシリアルIDは、全世界でユニークな不変値です。PAN内でユニークなID(16bit)で通信する事もできます。この16bit IDは、ノードがPANに参加した時や抜けた時などに動的に変更されます。
取得した送信先End Deviceの64bitアドレス(シリアルID)を宛先に指定して、Hello, World!のバイト列を送信します。XBeeのシリアルIDは、コマンドモードにてATSH, ATSLコマンドを使用して取得する事もできます。それぞれ、上位32ビット、下位32ビットを取得します。
import com.rapplogic.xbee.api.{ApiId, XBee, XBeeResponse, AtCommand, AtCommandResponse}
import com.rapplogic.xbee.api.{XBeeAddress64, XBeeAddress16, XBeeException, XBeeRequest, AtCommandResponse}
import com.rapplogic.xbee.api.zigbee.{ZNetTxRequest, ZNetTxStatusResponse, ZNetRemoteAtRequest, ZNetRxResponse}
import com.rapplogic.xbee.api.zigbee.{ZNetRemoteAtResponse, AssociationStatus}
import com.rapplogic.xbee.util.ByteUtils
import com.rapplogic.xbee.api.wpan.{RxResponse, TxRequest16, TxRequest64, TxRequestBase}
import org.apache.log4j.PropertyConfigurator
import scala.collection.jcl.Conversions.convertList
object CoordinatorTest {
def nodeDiscover(xbee: XBee) = {
var response: XBeeResponse = null
response = xbee.sendAtCommand(new AtCommand("NT", Array(NODE_DISCOVER_TIME)))
if(response.isError) {
System.err.println("[ERROR] ATNT: " + response + "\n")
System.exit(1)
}
response = xbee.sendAtCommand(new AtCommand("ND"))
if(response.isError) {
System.err.println("[ERROR] ATND: " + response + "\n")
System.exit(1)
}
val value = response.asInstanceOf[AtCommandResponse].getValue
Array(value.slice(2, 10))
}
def main(args: Array[String]) = {
PropertyConfigurator.configure("log4j.properties")
val xbee = new XBee
xbee.open("/dev/tty.usbserial-A8008iws", 19200)
sendData(xbee)
}
def toHexString(bytes: Seq[Int]) = bytes.map(b => "0x" + b.toHexString).mkString(" ")
def sendData(xbee: XBee) = {
val nodeId = nodeDiscover(xbee).first
println("Found Node: " + toHexString(nodeId))
val addr64 = new XBeeAddress64(nodeId(0), nodeId(1), nodeId(2), nodeId(3),
nodeId(4), nodeId(5), nodeId(6), nodeId(7))
val payload = "Hello, World!".map(_.toInt).toArray
while(true) {
val request = new TxRequest64(addr64, xbee.getNextFrameId, payload)
xbee.sendSynchronous(request, 3000)
println("sent: " + request)
Thread.sleep(3000)
}
}
}
End Device側の実装 (on Arduino)
Arduino上のXBeeは、Coordinatorから送信されたデータを受信するだけのプログラムです。Coordinatorからデータを受信した際に、response.isAvailableがtrueになり、レスポンスを解析します。始めに、APIリクエストの種類を検出し、種類に応じた処理に振りわけます。ここでは、RX 16bitとRX 64bitのパケットを処理し、どちらでも無い場合は、エラー用のLEDを点灯させます。状態確認用のLEDを13番ピンに、エラー用のLEDを、12番ピンに接続しています。
ビルド方法についてビルドには、Arduinoに付属しているMakefileを利用できます。Makefileは、/path/to/arduino-xxx/hardware/cores/arduino/Makefileです。XBee Shieldへの書き込みは、make uploadで実行できます。(※ Jumberピンを二つともUSB側に指しておく必要があります)
#include <XBee.h>
static const unsigned int BAUD_RATE = 19200;
XBee xbee = XBee();
XBeeResponse response = XBeeResponse();
Rx16Response rx16 = Rx16Response();
Rx64Response rx64 = Rx64Response();
static uint8_t stLed = 13;
static uint8_t errLed = 12;
extern "C" void __cxa_pure_virtual(){}
void setup() {
pinMode(stLed, OUTPUT);
pinMode(errLed, OUTPUT);
digitalWrite(13, HIGH);
Serial.begin(19200);
swserial.begin(9600);
swserial.println("SWSERIAL started");
xbee.begin(BAUD_RATE);
}
void loop() {
xbee.readPacket();
response = xbee.getResponse();
if(response.isAvailable()) {
uint8_t apiId = response.getApiId();
if(apiId == RX_16_RESPONSE) {
response.getRx16Response(rx16);
uint8_t dataLength = rx16.getDataLength();
Serial.print("rcv(16): addr16=");
Serial.print((int)rx16.getRemoteAddress16());
Serial.print(", data=");
for(int i = 0; i < dataLength; ++i) {
uint8_t data = rx16.getData(i);
Serial.print("0x");
Serial.print(data, HEX);
Serial.print(" ");
}
Serial.println("");
} else if(apiId == RX_64_RESPONSE) {
response.getRx64Response(rx64);
uint8_t dataLength = rx64.getDataLength();
Serial.print("rcv(64): data=");
for(int i = 0; i < dataLength; ++i) {
uint8_t data = rx64.getData(i);
Serial.print("0x");
Serial.print(data, HEX);
Serial.print(" ");
}
Serial.println("");
} else {
digitalWrite(errLed, HIGH);
delay(1000);
digitalWrite(errLed, LOW);
}
}
}
動作確認
動作確認時には、プログラムの書き込み時に移動したJumperピンをXBEE側に戻します。
Coordinator側 (Sender)
% sbt run
[info] Building project xbee 1.0 using XBeeProject
[info]
[info] == compile ==
[info] Source analysis: 0 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling main sources...
[info] Nothing to compile.
[info] Post-analysis: 5 classes.
[info] == compile ==
[info]
[info] == run ==
[info] Running Test ...
Experimental: JNI_OnLoad called.
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version = RXTX-2.1-7
Found Node: 0x0 0x13 0xa2 0x0 0x40 0x31 0x9f 0x11
sent: apiId=TX_REQUEST_64 (0x00),frameId=1,option=DEFAULT_OPTION,payload=0x48 0x65 0x6c 0x6c 0x6f 0x2c 0x20 0x57 0x6f 0x72 0x6c 0x64 0x21,remoteAddress64=0x00 0x13 0xa2 0x00 0x40 0x31 0x9f 0x11
sent: apiId=TX_REQUEST_64 (0x00),frameId=2,option=DEFAULT_OPTION,payload=0x48 0x65 0x6c 0x6c 0x6f 0x2c 0x20 0x57 0x6f 0x72 0x6c 0x64 0x21,remoteAddress64=0x00 0x13 0xa2 0x00 0x40 0x31 0x9f 0x11
sent: apiId=TX_REQUEST_64 (0x00),frameId=3,option=DEFAULT_OPTION,payload=0x48 0x65 0x6c 0x6c 0x6f 0x2c 0x20 0x57 0x6f 0x72 0x6c 0x64 0x21,remoteAddress64=0x00 0x13 0xa2 0x00 0x40 0x31 0x9f 0x11
End Device側 (Receiver)
rcv(16): addr16=1, data=0x48 0x65 0x6C 0x6C 0x6F 0x2C 0x20 0x57 0x6F 0x72 0x6C 0
x64 0x21
rcv(16): addr16=1, data=0x48 0x65 0x6C 0x6C 0x6F 0x2C 0x20 0x57 0x6F 0x72 0x6C 0
x64 0x21
rcv(16): addr16=1, data=0x48 0x65 0x6C 0x6C 0x6F 0x2C 0x20 0x57 0x6F 0x72 0x6C 0
x64 0x21
Coordinatorから送信されたデータを、End Device側で受信できました。
次回は、CoordinatorとEnd Device間のアソシエーションの確立や、APIモード、ZigBeeのアドレッシングについてより深く見ていきます。