[ 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(".");
	  }
	}
      
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.


Powered by UNIX fortune(6)
[ Main Page ]