ProcessingでXBeeを扱いたい用があり、設定例及び使用例を示す。Processingの実体はJavaのクラスライブラリなので、Java用のAPIを探すことになる。
XBeeのJava用APIは何種類か開発されているが、いくつかはメンテナンスが終了されており、
現在比較的良く使えるのは、andrewrapp/xbee-apiと思われる。
RXTX シリアルライブラリ及びlog4jに依存しており、バイナリを適切にインストールする必要がある。
Processingのディレクトリにコピーする方法が簡単。RXTXは、Windowsの場合、
processing-3.3\java\bin\rxtxSerial.dll (環境により32bitか64bitを適切に選択)
processing-3.3\java\lib\ext\RXTXcomm.jar
の2つをコピーする。log4jは最新の2.x系でなく1.x系を使用しているので、processing-3.3\java\lib\ext\log4j-1.2.17.jarにコピーする。xbee-api-*.jarも同じくjava\lib\ext\にコピーする。
log4jとxbee-apiに関してはjarをdrag and dropする方法でも良い。古いバージョンのprocessingにはrxtxライブラリが入っているので、確認する必要がある。
mirror:
rxtx-2.2pre2.zip (src)
rxtx-2.2pre2-bins.zip (binary) install.txt
log4j-1.2.17.jar (Maven Repository: log4j - log4j - 1.2.17)
xbee-api-0.9.3.jar (Maven Repository: com.rapplogic - xbee-api - 0.9.3)
設定は、3.3VのUSB-serial変換基板を使って行う。他のサイトでも示されている通り、TX/RX以外にRTS/CTS/DTRのフローコントロールの配線をする必要がある。以下は接続例。
FT232HL (AE-FT232HL)
FT231X (AE-FT231X)


XBeeは、ネットワーク上にCoordinatorが一つと、RouterまたはEnd Deviceが複数個必要なので、そのように設定する。 PAN IDはネットワーク内で同一にする(例:1010)。xbee-apiの文書の通り、APは2(API mode with escaping)でescapingを有効にしないとxbee-apiが動作しない。 XBee S2B/S2/Series2であれば、Coordinator APIファームウエアを書き込む。 XBee S2CからはCoordinator/Router/End Deviceファームウエアが統合されているので、ファームウエアを変えるのでなく、APでAPIを有効にし、CEでCoordinatorを有効にする。 End Deviceは、SM(sleep mode)で設定する。


子機では、後でリモートに設定できる部分が多いので、APIファームウエアを焼き込むのと、PAN IDを設定すること以外は特に設定しなくても良く、AP(escaping)もデフォルトのままで良いが、念のためこちらもAPは2にしておく。 上記では、PAN IDを親機と同一に設定してからはリモートで設定できるので、その状態で表示されている。
XBeeモジュール自体にADCが付いているので、10Hz程度までなら外付けマイコンなしでADCデータをストリーミングできる。 S2BがEMBER EM250 ZigBee/802.15.4 SoC (16-bit XAP2b microprocessor / 128kb of Flash / 5kb of SRAM)、 S2CではEMBER EM357 ZigBee/802.15.4 SoC (ARM Cortex-M3 / 192kB flash / 12kB RAM)でかなり性能がアップしているが、 S2B Pro (Programmable module)とS2C Programmableでは、HCS08 / 50.33MHz / 32KB Flash / 2kB RAM(マニュアルの回路ではFreescale MC9S08QE32CFT)が追加されており、 新たにHCS08用にファームウエアを開発すればさらに複雑な処理もできる。
下記では0x2c4fが子機、0x0000はCoordinatorである。 64-bit addressを使って通信しても良いが、ネットワーク内のすべての子機の把握は大変であり、IPで言えばDHCPに近い物である16-bit addressを使うのが容易と思われる。 16-bit addressを使う時、64-bit addressが未知であれば、0xFFFFFFFFFFFFFFFFに設定する。

APIでADCの設定、IRでサンプリング周期を設定すると、ADCデータを一定間隔で垂れ流してくれる。IRを0にすれば停止する。(フレームリストXML)





D0 D1 ST IRの順に設定し、データが受信される。IR=0で停止する。

XBee Series 1ではVrefを設定できたが、Series 2以降では1.2V固定(最高1.2V)になった。SoCのADC用の内部Vrefは出力されず、Programmable moduleではHCS08のADC用に設定できるだけである。 中点(0.6V)を設定するのがやや難しいが、ダイオードのVF(順方向降下電圧)に近い電圧なので、 ダイオードをFETで定電流動作させて約0.6Vを作って中点とすると通常の使用用途では十分である。もう少し精密な電圧が必要な時は、TL431か同等品を使うと良いだろう。
Processing 3で、上記の垂れ流しモードを表示・ファイルに保存する例を示す。 controlP5も使用している。 controlP5は、ProcessingでGUIをコントロールするのには使いやすい。下記では、4chのADCのうち、AD0、AD1を指定している。エラー処理はしていない。

使い方は、COM17等でポートを選択し、node discoveryで子機を検索、表示されたら、最後に見つかった子機に対しADCの設定(set ADC)、 IRの設定をしてサンプリング開始(start sampling)、停止(stop sampling)する。Processingで、Applicationとして出力した場合、サンプリング結果が実行ファイルと同じところにoutput.csvが保存される。
import java.io.*;
import processing.serial.*;
import com.rapplogic.xbee.*;
import com.rapplogic.xbee.api.*;
import com.rapplogic.xbee.api.wpan.*;
import com.rapplogic.xbee.api.zigbee.*;
import com.rapplogic.xbee.examples.*;
import com.rapplogic.xbee.examples.wpan.*;
import com.rapplogic.xbee.examples.zigbee.*;
import com.rapplogic.xbee.socket.*;
import com.rapplogic.xbee.test.*;
import com.rapplogic.xbee.util.*;
import controlP5.*;
ControlP5 cp5;
Chart adcChart;
XBee xbee;
XBeeAddress16 sensor_address16 = new XBeeAddress16(0, 0);
public void nodeDiscovery(int theValue) {
if(xbee.isConnected() == false) { return; }
println("nodeDiscovery");
try{
xbee.sendAsynchronous(new AtCommand("ND"));
}
catch(XBeeException xe){ ; }
cp5.get(ScrollableList.class, "sensors").clear();
}
public void setADC(int theValue) {
if(sensor_address16.get16BitValue() == 0) { return; }
println("setADC");
XBeeAddress16 remoteAddress = sensor_address16;
RemoteAtRequest request0 = new RemoteAtRequest(remoteAddress, "D0", 2);
RemoteAtRequest request1 = new RemoteAtRequest(remoteAddress, "D1", 2);
RemoteAtRequest request2 = new RemoteAtRequest(remoteAddress, "D2", 2);
RemoteAtRequest request3 = new RemoteAtRequest(remoteAddress, "D3", 2);
//int[] val = {0x13,0x88};
int[] val = {0xff,0xfe};
RemoteAtRequest request4 = new RemoteAtRequest(remoteAddress, "ST", val);
try{
xbee.sendAsynchronous(request0);
xbee.sendAsynchronous(request1);
//xbee.sendAsynchronous(request2);
//xbee.sendAsynchronous(request3);
xbee.sendAsynchronous(request4);
}
catch(XBeeException xe){ ; }
}
public void startSampling(int theValue) {
if(sensor_address16.get16BitValue() == 0) { return; }
println("startSampling");
XBeeAddress16 remoteAddress = sensor_address16;
//int[] val = {0,0x64}; // = 100ms, 10Hz
//int[] val = {0,0x50}; // = 80ms, 12.5Hz
//int[] val = {0,0x4b}; // = 75ms, 13.33Hz
int[] val = {0,0x32}; // = 50ms, 20Hz ~ actual 12.5Hz
RemoteAtRequest request0 = new RemoteAtRequest(remoteAddress, "IR", val);
try{
xbee.sendAsynchronous(request0);
}
catch(XBeeException xe){ ; }
}
public void stopSampling(int theValue) {
if(sensor_address16.get16BitValue() == 0) { return; }
println("stopSampling");
XBeeAddress16 remoteAddress = sensor_address16;
int[] val = {0,0};
RemoteAtRequest request0 = new RemoteAtRequest(remoteAddress, "IR", val);
try{
xbee.sendAsynchronous(request0);
}
catch(XBeeException xe){ ; }
}
void setup(){
size(1024, 768);
cp5 = new ControlP5(this);
adcChart = cp5.addChart("ADC View")
.setPosition(10, 10)
.setSize(800, 400)
.setRange(0, 1.2)
.setView(Chart.LINE) // use Chart.LINE, Chart.PIE, Chart.AREA, Chart.BAR_CENTERED
.setStrokeWeight(1.5)
.setColorCaptionLabel(color(40));
adcChart.addDataSet("adc0");
adcChart.setColors("adc0", color(200,50,0));
adcChart.setData("adc0", new float[200]);
adcChart.addDataSet("adc1");
adcChart.setColors("adc1", color(0,200,100));
adcChart.setData("adc1", new float[200]);
cp5.addButton("nodeDiscovery")
.setValue(0) .setPosition(220,440) .setSize(200,20);
cp5.addButton("setADC")
.setValue(0) .setPosition(220,470) .setSize(200,20);
cp5.addButton("startSampling")
.setValue(0) .setPosition(220,500) .setSize(200,20);
cp5.addButton("stopSampling")
.setValue(0) .setPosition(220,530) .setSize(200,20);
cp5.addScrollableList("serialPort")
.setPosition(10, 440) .setSize(200,100)
.setBarHeight(20) .setItemHeight(20)
.addItems(Serial.list())
.setType(ScrollableList.LIST) // currently supported DROPDOWN and LIST
;
cp5.addScrollableList("sensors")
.setPosition(430, 440) .setSize(200,100)
.setBarHeight(20) .setItemHeight(20)
.setType(ScrollableList.LIST) // currently supported DROPDOWN and LIST
;
cp5.addTextfield("outputfile")
.setPosition(640, 440)
.setSize(200,40)
.setColor(color(255,0,0))
;
cp5.get(Textfield.class,"outputfile").setText("output.csv");
xbee = new XBee();
}
void serialPort(int n) {
String port = (String)cp5.get(ScrollableList.class, "serialPort").getItem(n).get("text");
print(port);
if(xbee.isConnected() == true) {
xbee.close();
}
try{
xbee.open(port , 9600);
System.out.print(" " + port + ">succeeded");
}
catch(XBeeException xe){
System.out.print(" " + port + ">failed");
}
if(xbee.isConnected() == true) {
xbee.addPacketListener(new XBeeListener());
}
}
public void controlEvent(ControlEvent theEvent) {
println("controlEvent:" + theEvent.getController().getName());
}
public static final int BUFSIZE = 200;
float[] adcbuf0 = new float[BUFSIZE];
float[] adcbuf1 = new float[BUFSIZE];
int ptbuf0 = 0;
int ptcount = 0;
void draw() {
background(140);
if(ptcount != ptbuf0) {
int diff = Math.abs(ptbuf0 - ptcount);
try{
Writer writer = new FileWriter(cp5.get(Textfield.class,"outputfile").getText(),true);
if(diff < BUFSIZE/2) { // without loop back
for(int i = ptcount;i < ptbuf0;i ++) {
print("("+i+")");
adcChart.push("adc0", adcbuf0[i]);
adcChart.push("adc1", adcbuf1[i]);
writer.write(nf(year(),4)+","+nf(month(),2)+","+nf(day(),2)+","+nf(hour(),2) + "," + nf(minute(),2) + "," + nf(second(),2) + ",");
writer.write(String.format("%.3f",adcbuf0[i]) + "," + String.format("%.3f",adcbuf1[i])+"\r\n");
}
} else { // with loop end back
for(int i = ptcount;i < BUFSIZE;i ++) {
print("("+i+")");
adcChart.push("adc0", adcbuf0[i]);
adcChart.push("adc1", adcbuf1[i]);
writer.write(nf(year(),4)+","+nf(month(),2)+","+nf(day(),2)+","+nf(hour(),2) + "," + nf(minute(),2) + "," + nf(second(),2) + ",");
writer.write(String.format("%.3f",adcbuf0[i]) + "," + String.format("%.3f",adcbuf1[i])+"\r\n");
}
for(int i = 0;i < ptbuf0;i ++) {
print("("+i+")");
adcChart.push("adc0", adcbuf0[i]);
adcChart.push("adc1", adcbuf1[i]);
writer.write(nf(year(),4)+","+nf(month(),2)+","+nf(day(),2)+","+nf(hour(),2) + "," + nf(minute(),2) + "," + nf(second(),2) + ",");
writer.write(String.format("%.3f",adcbuf0[i]) + "," + String.format("%.3f",adcbuf1[i])+"\r\n");
}
}
writer.close();
} catch(IOException ex) {
ex.printStackTrace();
}
ptcount = ptbuf0;
}
}
class XBeeListener implements PacketListener{
public void processResponse(XBeeResponse response){
ApiId id = response.getApiId();
print(""+id.toString());
switch(id){
case ZNET_IO_SAMPLE_RESPONSE:
ZNetRxIoSampleResponse ioSample = (ZNetRxIoSampleResponse)response;
print(" AD0=" + String.format("%.3f", (float)ioSample.getAnalog0()*1.2/1024));
print(" AD1=" + String.format("%.3f", (float)ioSample.getAnalog1()*1.2/1024));
//print(" AD2=" + String.format("%.3f", (float)ioSample.getAnalog2()*1.2/1024));
//print(" AD3=" + String.format("%.3f", (float)ioSample.getAnalog3()*1.2/1024));
if(ptbuf0 >= BUFSIZE) { ptbuf0 = 0; }
adcbuf0[ptbuf0] = (float)ioSample.getAnalog0()*1.2/1024.0;
adcbuf1[ptbuf0] = (float)ioSample.getAnalog1()*1.2/1024.0;
ptbuf0 ++;
break;
case ZNET_RX_RESPONSE:
ZNetRxResponse rx = (ZNetRxResponse) response;
println(ByteUtils.toBase16(rx.getRemoteAddress64().getAddress()));
println(ByteUtils.toString(rx.getData()));
break;
case AT_RESPONSE:
AtCommandResponse atResponse = (AtCommandResponse) response;
if (atResponse.isOk()) {
println("At Command successful : " + ByteUtils.toBase16(atResponse.getValue()));
ZBNodeDiscover node = ZBNodeDiscover.parse(atResponse);
println("NodeAddress16 : " + ByteUtils.toBase16(node.getNodeAddress16().getAddress()));
println("NodeAddress64 : " + ByteUtils.toBase16(node.getNodeAddress64().getAddress()));
cp5.get(ScrollableList.class, "sensors").addItem((String)ByteUtils.toBase16(node.getNodeAddress16().getAddress()),0);
if(!sensor_address16.equals(node.getNodeAddress16()) && node.getNodeAddress16().get16BitValue() != 0) {
sensor_address16 = node.getNodeAddress16();
println("Sensor was registered.");
}
} else {
println("At Command failed : " + ByteUtils.toBase16(atResponse.getValue()));
}
break;
case REMOTE_AT_RESPONSE:
RemoteAtResponse remoteAtResponse = (RemoteAtResponse) response;
if (remoteAtResponse.isOk()) {
println("RemoteAt Ok.");
} else {
println("RemoteAt Failed.");
}
break;
default:
print("?");
break;
}
println(".");
}
}
There is no IGLU Cabal!
Writing this sentence followed by an explanation has been patented by Omer Zak
in US patent No. 10943307*2^66452-1. Commenting on Omer's comment has been
patented by Shlomi Fish in US patent No. e^(i*pi). The sentence itself is a
trademark of Moshe Zadka.
The existence of these patents is the only explanation one needs for this
sentence.
Shlomi Fish in Hackers-IL message No. 1515
-- Shlomi Fish
-- Hackers-IL
Message No. 1515 ( http://tech.groups.yahoo.com/group/hackers-il/message/1515 )
Q: Why haven't you graduated yet?
A: Well, Dad, I could have finished years ago, but I wanted
my dissertation to rhyme.