通信
感谢网友 YODA 的翻译


通信

leJOS NXJ 支持使用蓝牙和USB进行通信。NXJ通信相关的类设计的很巧妙,能够让你自己的代码不必过多依赖特定的通信方式 - 无论是通过蓝牙还是通过USB通信,你也可以编写同时支持这两种通信方式的应用(见下面示例),甚至还可使用更加灵活易用的Java流对象。

USB方式具有传输速率方面的优势,但是使用USB方式只能把NXT连接到PC。蓝牙方式传输速率相对较慢,但是它支持多种设备之间的通信,例如NXT到NXT,PC到NXT,移动电话到NXT,NXT到远程蓝牙设备等。

通信的第一步是建立连接,一个连接拥有一个发起者和一个接收者。接收者等待发起者发起连接,发起者也只能连接到正在等待连接的某个设备。一旦连接建立,参与连接的双方就可以使用该连接打开输入和输出流并进行数据读写。虽然蓝牙方式允许NXT作为发起者而PC作为接收者,但是本教程并不打算讨论这种情况,因为在大部分PC或者移动电话到NXT的通信中,通常是NXT作为接收者,而PC作为发起者。

发起者程序可能运行在PC,也可能是另外一台NXT、移动电话或者其他支持蓝牙串口规范(Serial Port Profile ,SPP)的设备。一些诸如GPS蓝牙接收器之类的设备只能作为通信接收者,所以在NXT与这些设备通信时,NXT必须作为通信的发起者。需要注意的是,这种设备必须实现串口规范,这是唯一一种NXT支持的规范。

接收者

通过调用Bluetooth或者USB类的waitForConnection()方法可以让NXT的接收者程序处于等待连接的状态:

  • BTConnection waitForConnection();

  • USBConnection waitForConnection();

虽然Bluetooth类返回BTconnection对象,USB类返回USBConnection对象,由于这两个对象都实现了NXTConnection接口,因此只要所声明的变量实现了NXTConnection接口,则BTConnection对象和USBConnection对象都可以赋值到该变量。

蓝牙示例:


  NXTConnection connection = Bluetooth.waitForConnection();
      		

在调用此方法之前,需要确保蓝牙电源已经接通并且该蓝牙设备处于可见状态。可以使用leJOS NXJ启动菜单设置蓝牙选项。

USB示例:


  NXTConnection connection = USB.waitForConnection();
      		

在调用此方法之前需要确保USB线已经正确连接。

下面演示了如何在运行期间选择使用USB还是蓝牙进行通信:


import lejos.nxt.*;

import lejos.nxt.comm.*;

import java.io.*;

/**
* sample of selecting channel at run time
*/

public class CommTest 
{
  public static void main( String[] args) { 
    LCD.drawString("right BT",0, 0);
    NXTConnection connection = null;

    if(Button.waitForPress() == 4){
      LCD.drawString("waiting for BT", 0,1 );
      connection = Bluetooth.waitForConnection();
    } else {
      LCD.drawString("waiting for USB", 0,1 );
      connection = USB.waitForConnection();
    }

    DataOutputStream dataOut = connection.openDataOutputStream();
    try {dataOut.writeInt(1234);} 
      catch (IOException e ) {System.out.println(" write error "+e);} 
    }
  }   
}
      		

返回顶部

一旦连接建立,可以调用NXTConnection接口的下列方法之一来打开输入输出流:

  • InputStream openInputStream() throws IOException;

  • OutputStream openOutputStream() throws IOException;

  • DataInputStream openDataInputStream() throws IOException; (the example above did this)

  • DataOutputStream openDataOutputStream() throws IOException;

使用下列语句从DataInputStream读取数据:

  • int read(byte b[]) throws IOException

  • int read(byte b[], int off, int len)throws IOException

  • boolean readBoolean() throws IOException

  • byte readByte() throws IOException

  • short readShort() throws IOException

  • readInt() throws IOException

  • char readChar() throws IOException

  • float readFloat() throws IOException

  • String readLine() throws IOException

注意:输入流的read方法是阻塞方法,也就是说,直到读取到数据,该方法才会返回。如果在读取数据的同时,你的程序要还需要处理其他任务,则必须在单独的线程中进行read方法调用。

使用下列语句向DataOutputStream写入数据:

  • void write(byte b[], int off, int len) throws IOException

  • void writeBoolean(boolean v) throws IOException

  • void writeByte(int v) throws IOException

  • void writeShort(int v) throws IOException

  • void writeChar(int v) throws IOException

  • void writeInt(int v) throws IOException

  • void writeFloat(float v) throws IOException;

  • void writeChars (String value) throws IOException

使用流对象读取和写入整数的示例: (disdos 为已经打开的DataInputStream和DataOutputStream对象):


for(int i=0;i <100;i++) {
  int n = dis.readInt();
  LCD.drawInt(n,7,0,1);
  dos.writeInt(-n); 
  dos.flush();
}
     		

注意:你必须显式的调用输出流的flush方法来确保完成实际的数据传输。 另外,有可能在不抛出异常的情况下发生传输失败的情况。

可以使用close()方法来关闭DataInputStream,DataOutputStream和NXTConnection对象。

使用蓝牙进行通信的完整代码如下:


public class BTReceive {
  public static void main(String [] args) throws Exception {
    String connected = "Connected";
    String waiting = "Waiting...";
    String closing = "Closing...";

    while (true) {
      LCD.drawString(waiting,0,0);
      NXTConnection connection = Bluetooth.waitForConnection(); 
      LCD.clear();
      LCD.drawString(connected,0,0);

      DataInputStream dis = connection.openDataInputStream();
      DataOutputStream dos = connection.openDataOutputStream();

      for(int i=0;i<100;i++) {
        int n = dis.readInt();
        LCD.drawInt(n,7,0,1);
        dos.writeInt(-n);
        dos.flush();
      }
      dis.close();
      dos.close();

      LCD.clear();
      LCD.drawString(closing,0,0);

      btc.close();
      LCD.clear();
    }
  }
}
      		

把Bluetooth.waitForConnection()修改为USB.waitForconnection(),则可以使上面示例代码运行在USB连接模式下。

返回顶部

NXT 发起者

要从一台NXT发起到另外一台NXT的蓝牙连接,首先需要把接收者NXT加入到发起者NXT的蓝牙设备列表中。

leJOS NXJ启动菜单,蓝牙子菜单,选择“Search”,打开作为接收者的NXT蓝牙电源并使其可见,就会找到该NXT,然后选择“Add”将其加入到发起者的蓝牙设备列表中。

从发起者NXT的蓝牙菜单,选择“Devices”,查看是否已经正确完成添加。

然后,在发起者NXT上,创建BTRemoteDevice对象:

示例:


  BTRemoteDevice btrd = Bluetooth.getKnownDevice(name);
      		

可以通过下面方法得到远程设备的地址:

  • public byte[] getDeviceAddr()

然后调用Bluetooth类的connect()方法通过远程设备的地址进行连接:

  • BTConnection connect(BTRemoteDevice remoteDevice)

  • BTConnection connect(byte[] device_addr)

  • BTConnection connect(byte[] device_addr, byte[] pin)

示例:


BTRemoteDevice btrd = Bluetooth.getKnownDevice(name);

if (btrd == null) {
  LCD.clear();
  LCD.drawString("No such device", 0, 0);
  Button.waitForPress();
  System.exit(1);
}

BTConnection btc = Bluetooth.connect(btrd);

if (btc == null) {
  LCD.clear();
  LCD.drawString("Connect fail", 0, 0);
  Button.waitForPress();
  System.exit(1);
}
      		

在得到BTConnection对象之后,可以按照上述示例的方法打开数据输入流和输出流。

下面是完整的BTConnectTest示例代码,可以作为发起者程序和BTReceive接收者程序联合使用:


public class BTConnectTest {
  public static void main(String[] args) throws Exception {
    String name = "NXT";
    LCD.drawString("Connecting...", 0, 0);
    BTRemoteDevice btrd = Bluetooth.getKnownDevice(name);

    if (btrd == null) {
      LCD.clear();
      LCD.drawString("No such device", 0, 0);
      Button.waitForPress();
      System.exit(1);
    }

    BTConnection btc = Bluetooth.connect(btrd);

    if (btc == null) {
      LCD.clear();
      LCD.drawString("Connect fail", 0, 0);
      Button.waitForPress();
      System.exit(1);
    }
  
    LCD.clear();
    LCD.drawString("Connected", 0, 0);

    DataInputStream dis = btc.openDataInputStream();
    DataOutputStream dos = btc.openDataOutputStream();

    for(int i=0;i<100;i++) {
      try { 
        LCD.drawInt(i*30000, 8, 0, 2);
        dos.writeInt(i*30000);
        dos.flush(); 
      } catch (IOException ioe) {
        LCD.drawString("Write Exception", 0, 0);
      }
    
      try {
        LCD.drawInt(dis.readInt(),8, 0,3);
      } catch (IOException ioe) {
        LCD.drawString("Read Exception ", 0, 0);
      }
    }
  
    try {
      LCD.drawString("Closing... ", 0, 0);
      dis.close();
      dos.close();
      btc.close();
    } catch (IOException ioe) {
      LCD.drawString("Close Exception", 0, 0);
    }
  
    LCD.drawString("Finished",3, 4);
    Button.waitForPress();
  }
}
      		

返回顶部

PC 发起者

可以使用程序从PC上发起到NXT的连接,并且打开Java数据流。

在PC上发起连接的API和NXT API有所不同,见PC API文档。

要从PC连接到NXT,需要从NXTCommFactory类获取一个NXTComm对象:

  • static NXTComm createNXTComm(int protocol)

蓝牙发起者

可以使用下列语句获取通过蓝牙进行连接的NXTComm对象


NXTComm nxtComm = NXTCommFactory.createNXTComm(NXTCommFactory.BLUETOOTH);
      		

之所以使用工厂方法获取连接对象,是因为在PC上可能存在多种适用蓝牙和USB的连接驱动实现方式,具体使用何种驱动取决于你所用的操作系统以及nxj.properties文件的内容。

可以通过地址连接NXT或者进行蓝牙查找来连接NXT:

要通过地址连接,需要使用构造函数创建一个NXTInfo对象:

  • public NXTInfo(String name, String address)

示例:


NXTInfo nxtInfo = new NXTInfo("NXT", "00:16:53:00:78:48");
      		

要查找可用的蓝牙NXT,需要:


NXTInfo[] nxtInfo = nxtComm.search("NXT",NXTCommFactory.BLUETOOTH)
      		

返回顶部

USB 发起者

使用USB实现发起者连接的过程和蓝牙类似,只需把代码中的Bluetooth替换为USB即可。由于USB设备不提供地址信息,可以使用NXT名称进行连接。


NXTComm nxtComm = NXTCommFactory.createNXTComm(NXTCommFactory.USB);
      		

要查找可用的NXT,需要:


nxtComm.search("MYNXT", NXTCommFactory.BLUETOOTH);
      		

如果PC上仅连接了一个NXT设备,可以将search()方法的name参数设置为null。

返回顶部

使用NXTInfo对象

一旦获取NXTInfo对象,就可以调用NXTComm对象的open()方法来连接到NXT:

  • public boolean open(NXTInfo nxt) throws NXTCommException;

一旦NXT被打开,可以通过调用NXTComm对象的getInputStream()和getOutputStream()方法获取INputStream和OutputStream对象:

  • public OutputStream getOutputStream();

  • public InputStream getInputStream();

然后可以构造DataInputStream和DataOutputStream对象来向接收者NXT发送数据。

在samples文件夹有完整的BTSend示例代码。

返回顶部

高级通信

通过本节内容,你将学会如何:

  • 通过蓝牙从一台NXT控制另外一台NXT
  • 控制外部蓝牙设备,例如GPS接收器
  • 在NXT和RCX之间通信
控制远程NXT

通过使用RemoteNXT类可以在一台运行leJOS NXJ的NXT上控制另外一台NXT,远程NXT可以是运行NXJ的,也可以是运行标准LEGO固件的。它基于蓝牙使用LEGO通信协议控制远程NXT。

目前,RemoteNXT类的功能有限,不支持I2C和RCX传感器,并且因为(远程控制模式下)无法使用调节进程,所以只能以一种简单的方式使用马达。(原文:the motors must be used in a simple way as the regulation thread is not used)

要访问远程NXT,使用如下构造函数:

  • public RemoteNXT(String name) throws IOException

示例:


try {
  LCD.drawString("Connecting...",0,0);
  nxt = new RemoteNXT("NXT");

  LCD.clear();
  LCD.drawString("Connected",0,0);
} catch (IOException ioe) {
  LCD.clear();
  LCD.drawString("Conn Failed",0,0);
  Button.waitForPress();
  System.exit(1);
}
      		

远程NXT的名称必须是已经存在于发起者NXT已知设备列表中的。可以通过leJOS NXJ蓝牙菜单进行蓝牙设备查找,然后完成添加动作。

构造函数打开连接,并且创建远程马达和传感器端口对象实例:

然后,可以使用下列方法访问远程NXT的相关信息:

  • public String getBrickName()

  • public String getBluetoothAddress()

  • public int getFlashMemory()

  • public String getFirmwareVersion()

  • public String getProtocolVersion()

示例:


LCD.drawString(nxt.getBrickName(), 0, 6);
LCD.drawString(nxt.getFirmwareVersion(), 0, 7);
LCD.drawString(nxt.getProtocolVersion(), 4, 7);
LCD.drawInt(nxt.getFlashMemory(), 6, 8, 7);
      		

同时,还有一些方法在远程NXT上执行:

  • public byte deleteFlashMemory()

可以通过远程Battery对象获取远程NXT电池的电压信息,使用方法和本地Battery对象相同。

示例:


LCD.drawString( "Battery: " + nxt.Battery.getVoltageMilliVolt() 0,4);
      		

同样,也可以通过S1,S2,S3和S4来访问远程NXT的传感器端口对象。

使用远程传感器端口对象和本地传感器端口对象类似。

示例:


LightSensor light = new LightSensor(nxt.S1);
LCD.drawString("Light: " + light.readValue(),0,5);
      		

同时,命名为A,B和C的远程Motor对象也已经被创建。

可以以常规方式访问,例如:


nxt.A.setSpeed(360);
nxt.A.forward();
nxt.A.stop();
nxt.A.backward();
      		

返回顶部

外部蓝牙设备

NXT可以连接到支持串口规范的外部蓝牙设备。

可以从蓝牙菜单搜索蓝牙设备并将其加入到已知设备列表。

例如,可以通过蓝牙GPS接收器获取机器人的所在地理位置。

leJOS支持外部蓝牙GPS接收器,使用GPS和NMEASentence类可以支持NMEA协议。NMEASentence类不能直接访问,而是被GPS类作为工具类使用。

Holux M-1200设备已经通过了和leJOS NXJ的测试。

大部分外部蓝牙设备都需要提供连接PIN码才可以正确连接,默认PIN码有可能是“0000”。

要连接到GPS设备,需要:


final byte[] pin = {(byte) '0', (byte) '0', (byte) '0', (byte) '0'};
BTRemoteDevice btGPS = Bluetooth.getKnownDevice(name); 

if (btrd == null) {
  LCD.drawString("No such device", 0, 0);
  Button.waitForPress();
  System.exit(1);
}

btGPS = Bluetooth.connect(btrd.getDeviceAddr(), pin);

if(btGPS == null)
  LCD.drawString("No Connection", 0, 1);
  Button.waitForPress();
  System.exit(1)
}

LCD.drawString("Connected!", 0, 1);

GPS gps = null;
InputStream in;

try {
  in = btGPS.openInputStream();
  gps = new GPS(in);
  LCD.drawString("GPS Online", 0, 6);
} catch(Exception e) {
  LCD.drawString("GPS Connect Fail", 0, 6);
  Button.waitForPress();
  System.exit(1);
}
      		

如上例所示,GPS类的构造函数含有一个输入流参数,该参数可以从蓝牙连接对象中获取。

  • public GPS(InputStream in)

GPS类开启一个线程,并使用NMEASentence类来处理来自蓝牙设备的消息,例如经度、纬度、海拔高度等。

使用下列方法读取经度、维度以及海拔高度信息:

  • public float getLatitude()

  • public float getLongitude()

  • public float getAltitude()

也可以获取上述信息对应的时间戳:

  • public int getTime()

返回顶部

和RCX通信

通过Mindsensors NRLink RCX IR适配器可以和RCX进行IR通信,对应的软件实现是RCXLink类。构造函数如下:

  • public RCXLink(I2CPort port)

示例:


RCXLink link = new RCXLink(SensorPort.S1);

      		

NRLink-Nx的ROM和EEPROM中含有一组宏指令,可以用来通过LEGO RCX IR协议向RCX发送消息。用户可以通过重写EEPROM宏来定义自己的宏指令。

可以使用下面方法运行宏:

  • public void runMacro(int addr)

同时,还有一些方法用来运行ROM宏:

  • public void beep()

  • public void runProgram(int programNumber)

  • public void forwardStep(int id)

  • public void backwardStep(int id)

  • public void setRCXRangeShort()

  • public void setRCXRangeLong()

  • public void powerOff()

  • public void stopAllPrograms()

使用下列语句在EEPROM地址范围中定义新的宏:

  • public void defineMacro(int addr, byte[] macro)

可以使用下面的方法定义和运行宏:

返回顶部