[ Main Page ]

XBee xbee-api (Java) for Processing (3)

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)

親機 (Coordinator)

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)で設定する。

子機 (RouterまたはEnd Device)

子機では、後でリモートに設定できる部分が多いので、APIファームウエアを焼き込むのと、PAN IDを設定すること以外は特に設定しなくても良く、AP(escaping)もデフォルトのままで良いが、念のためこちらもAPは2にしておく。 上記では、PAN IDを親機と同一に設定してからはリモートで設定できるので、その状態で表示されている。

ADC ストリーミング

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 ADC Vref

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 Client

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(".");
	  }
	}
      
Q:	What do Winnie the Pooh and John the Baptist have in common?
A:	The same middle name.

We have lots of FogBugz customers who have high-priced Remedy, Rational, or
Mercury products sitting on the shelves after investments of well over
$100,000, because that software isn't good enough to actually use. Then they
buy a couple of thousand dollars worth of FogBugz and that's the product they
really use. The Rational salesperson is laughing at me, because I have $2000
in the bank and he has $100,000. But I have far more customers than he does,
and they're all using my product, and evangelizing it, and spreading it, while
Rational customers either (a) don't use it or (b) use it and can't stand it.
But he's still laughing at me from his 40 foot yacht while I play with rubber
duckies in the bathtub. Like I said, all three methods work fine. But cheaper
prices is like buying advertising and as such is an investment in the future.

    -- Joel Spolsky
    -- "Camels and Rubber Duckies" ( http://www.joelonsoftware.com/articles/CamelsandRubberDuckies.html )


Powered by UNIX fortune(6)
[ Main Page ]