婉兮清扬

案上诗书杯中酒之快意人生

[实验] Sun SPOT无线通讯专题

发表时间:2008-06-11 21:13:14
一、基本概念

IEEE扩展Mac地址可以形象地比喻为Sun SPOT的IP地址。在一个Sun SPOT网络中可以用IEEE扩展Mac地址来标志一台特定的Sun SPOT,除非发生重大变化不会变成其他的Sun SPOT。

radiostream是 一种类似于套接字(socket)的peer-to-peer通讯协议。当两台Sun SPOT之间需要进行可靠的数据传输时,它们之间通过无线网络建立起一个稳定的、带缓冲的数据数据链。与电话网络相类似,这种数据链是点对点的,通讯的双 方则通过这条数据链来回传输数据。在这条稳定的数据链的基础上,radiostream通讯协议通过信息校验能够保证接收方所接收到的数据和发送方所发送的数据在内容和顺序上是完全一致的,从而实现了数据的可靠传输。

radiogram不是一种基于稳定连接的通讯协议。radiogram通讯协议将独立的数据包从一台Sun SPOT传输到另外一台Sun SPOT,但是并不保证接受方能够接收到该数据包,也不保证接收方所接收到的数据和发送方所发送的数据在内容和顺序上是完全一致的。因此,radiogram通讯协议更类似于普通邮政服务,寄信人不能够保证所寄出去的信能够被收信人及时收到,后发出的信也许会比先发出的信更早到达。

为了让多个应用程序能够共享一个无线通讯模块,一个无线通讯模块可以提供多达255 个通讯端口,不同的应用程序可以使用不同的端口进行通讯。需要注意的事,虽然这个端口号的值可以在0 到255之间,但是0 并不是一个可以正常使用的端口。

二、使用radiostream通讯协议

两台Sun SPOT之间可以通过radiostream通讯协议建立起一个稳定可靠的数据链。具体方法如下:

RadiostreamConnection conn = (RadiostreamConnection)
Connector.open("radiostream://<destinationAddr>:<portNo>");

其中,destinationAddr是对方的IEEE扩展Mac地址,portNo则是通讯中使用的端口号。需要注意的是,通讯双方需要使用同样的端口号。

当radiostream连接被建立起来之后,可以使用DataInputStream和DataOutputStream来接收和发送数据:

DataInputStream dis = conn.openDataInputStream();

DataOutputStream dos = conn.openDataOutputStream();


关闭一个RadioStreamConnection对象:

conn.close();

下面的程序片断演示了两台Sun SPOT之间如何通过radiostram通讯协议进行通讯:

package org.sunspotworld;

import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.io.j2me.radiostream.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.*;

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {

/**
* starApp()方法启动一个Sun SPOT应用程序。
*/

protected void startApp() throws MIDletStateChangeException {

/**
* 启动一个线程,检测USB电缆是否被连接上。
*/

new BootloaderListener().start();

/**
* 获得本机的IEEE扩展Mac地址。
*/

IEEEAddress ourAddr = new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());

/**
* 打印本机的IEEE扩展Mac地址。
*/

System.out.println("Our radio address = " + ourAddr.asDottedHex());

/**
* 声明程序中用到的对象
* RadiostreamConnection -- 通讯连接
* DataInputStream -- 数据输出流,接收数据
* DataOutputStream -- 数据输出流,发送数据
* input_message -- 对方发送过来的数据
* i -- 计数器
*/

RadiostreamConnection conn;
DataInputStream dis;
DataOutputStream dos;
String input_message;
int i = 0;


try
{

/**
* 创建网络连接。
* 0014.4F01.0000.081F对方的IEEE扩展Mac地址。
* 100是通讯双方约定的端口号。
*/

conn = (RadiostreamConnection) Connector.open("radiostream://0014.4F01.0000.081F:100");

/**
* 获得输入输出流。
*/

dis = conn.openDataInputStream();
dos = conn.openDataOutputStream();


/**
* 进入一个无限循环。
*/

while (true)
{


/**
* 将需要发送的内容写入发送缓冲区。
* 发送的内容为本机的IEEE扩展Mac地址,以及当前消息的编号。
*/

dos.writeUTF("Sun SPOT " + ourAddr.asDottedHex() + " Message number " + i + ".");

/**
* 正式发送数据。
*/

dos.flush();

/**
* 接收一条信息。
*/

input_message = dis.readUTF();

/**
* 打印接收到的信息内容。
*/

System.out.println(input_message);

/**
* 休息3 秒钟,计数器加1,进入下一个循环。
*/

Utils.sleep(3000);
i = i + 1;
}
} catch (IOException e)
{
System.out.println("No route to the destination Sun SPOT.");
}


}

protected void pauseApp() {
// This will never be called by the Squawk VM
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
// Only called if startApp throws any exception other than MIDletStateChangeException
}
}

从上面的程序片断中,我们注意到如下几个要点:

DataOutputStream.writeUTF(String output_content)方法将打算发送出去的内容output_content写入发送缓冲区。

DataOutputStream.flush ()方法将发送缓冲区中的内容真正发送出去。如果一直没有执行DataOutputSteam.flush()方法,无线通讯模块会在发送缓冲区已经被用 完的时候自动将等待发送的内容发送出去,然后清空发送缓冲区。如果发送缓冲区没有被用完,并且程序没有调用DataOutputStream.flush ()方法,那么数据一直都不会被真正发送出去。

String input_content = DataInputStream.readUTF()方法阻塞当前的进程,等待对方发送的内容。当接收到一个数据包之后,这个数据包的内容被返回到字符串input_content里面。

需要注意的是,如果通讯双方无法找到对方,则会抛出异常NoRouteException。在我们的应用程序中需要捕获和处理这个异常。

三、使用radiogram通讯协议

radiogram是一种基于服务器/客户端的数据包通讯协议。

创建一个RadiogramConnection链接:

RadiogramConnection conn = (RadiogramConnection)
Connector.open("radiogram://<serverAddr>:<portNo>");

其中,serverAddr是服务器端的IEEE扩展Mac地址,portNo是通讯中使用的端口号。需要注意的是,通讯双方需要使用同样的端口号。

当通讯双方建立起连接之后,可以通过Datagram进行数据交换。

Datagram dg_send = conn.newDatagram(conn.getMaximumLength());

向一个Datagram对象dg写入数据:

dg_send.writeUTF("Hello");

通过RadiogramConnection conn将一个Datagram对象发送出去:

conn.send(dg_send);


通过RadiogramConnection conn接收一个Datagram对象:

Datagram dg_receive = conn.newDatagram(conn.getMaximumLength());
conn.receive(dg_receive);

从一个Datagram对象中读取数据:

String input_content = dg_receive.readUTF();

关闭一个RadiogramConnection对象:

conn.close();

下面的程序片断演示了两台Sun SPOT之间如何通过radiostram通讯协议进行通讯:

package org.sunspotworld;

import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.io.j2me.radiostream.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.*;

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {

/**
* starApp()方法启动一个Sun SPOT应用程序。
*/

protected void startApp() throws MIDletStateChangeException {

/**
* 启动一个线程,检测USB电缆是否被连接上。
*/

new BootloaderListener().start();

/**
* 获得本机的IEEE扩展Mac地址。
*/

IEEEAddress ourAddr = new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());

/**
* 打印本机的IEEE扩展Mac地址。
*/

System.out.println("Our radio address = " + ourAddr.asDottedHex());

/**
* 声明程序中用到的对象
* RadiogramConnection -- 通讯连接
* dg_send -- 发送出去的数据包
* dg_receive -- 接收到的数据包
* input_message -- 对方发送过来的数据
* i -- 计数器
*/

RadiogramConnection conn;
Datagram dg_send, dg_receive;
String input_message;
int i = 0;


try
{

/**
* 创建网络连接。
* 0014.4F01.0000.081F对方的IEEE扩展Mac地址。
* 100是通讯双方约定的端口号。
*/

conn = (RadiogramConnection) Connector.open("radiogram://0014.4F01.0000.081F:100");

/**
* 进入一个无限循环。
*/
while (true)
{


/**
* 创建两个空的数据包,一个用来发送数据,一个用来接收数据。
*/

dg_send = conn.newDatagram(conn.getMaximumLength());
dg_receive = conn.newDatagram(conn.getMaximumLength());


/**
* 将需要发送的内容写入需要发送的数据包。
*/
dg_send.writeUTF("Sun SPOT " + ourAddr.asDottedHex() + " Message number " + i + ".");

/**
* 发送数据包。
*/
conn.send(dg_send);

/**
* 接收数据包。
*/
conn.receive(dg_receive);

/**
* 从数据包中提取接收到的消息。
*/
input_message = dg_receive.readUTF();

/**
* 打印接收到的消息。
*/
System.out.println(input_message);

/**
* 休息三秒钟。
*/
Utils.sleep(3000);

/**
* 计数器加一,进入下一个循环。
*/

i = i + 1;
}
} catch (IOException e)
{
System.out.println("No route to the destination Sun SPOT.");
}


}

protected void pauseApp() {
// This will never be called by the Squawk VM
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
// Only called if startApp throws any exception other than MIDletStateChangeException
}
}
四、使用radiogram进行广播

radiogram还可以用于向所有的Sun SPOT进行广播。为了进行广播,您首先需要在广播服务器端创建一个特殊的DatagramConnection

DatagramConnection conn =
(DatagramConnection) Connector.open("radiogram://broadcast:<portNo>");

其中,portNo是需要使用的广播端口。

在接收端,则需要创建一个DatagramConnection,指定其监听相对应的端口。

RadiogramConnection conn = (RadiogramConnection) Connector.open("radiogram://:<portNo>");

所有打开同样portNo接收数据的Sun SPOT均可以接收到从广播端发送的数据。

下面的程序片断演示了两台Sun SPOT之间如何通过radiostram通讯协议进行通讯:

数据广播端:

package org.sunspotworld;

import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.io.j2me.radiostream.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.*;

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {

/**
* starApp()方法启动一个Sun SPOT应用程序。
*/

protected void startApp() throws MIDletStateChangeException {

/**
* 启动一个线程,检测USB电缆是否被连接上。
*/

new BootloaderListener().start();

/**
* 获得本机的IEEE扩展Mac地址。
*/

IEEEAddress ourAddr = new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());

/**
* 打印本机的IEEE扩展Mac地址。
*/

System.out.println("Our radio address = " + ourAddr.asDottedHex());

/**
* 声明程序中用到的对象
* RadiogramConnection -- 通讯连接
* dg_send -- 发送出去的数据包
* i -- 计数器
*/

RadiogramConnection conn;
Datagram dg_send;
int i = 0;


try
{

/**
* 创建广播。
* 100是通讯双方约定的端口号。
*/

conn = (RadiogramConnection) Connector.open("radiogram://broadcast:100");

/**
* 进入一个无限循环。
*/
while (true)
{


/**
* 创建用来发送数据的数据包。
*/
dg_send = conn.newDatagram(conn.getMaximumLength());

/**
* 将需要发送的内容写入需要发送的数据包。
*/
dg_send.writeUTF("Sun SPOT " + ourAddr.asDottedHex() + " Message number " + i + ".");

/**
* 发送数据包。
*/
conn.send(dg_send);

/**
* 休息三秒钟。
*/
Utils.sleep(3000);

/**
* 计数器加一,进入下一个循环。
*/

i = i + 1;
}
} catch (IOException e)
{
System.out.println("No route to the destination Sun SPOT.");
}


}

protected void pauseApp() {
// This will never be called by the Squawk VM
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
// Only called if startApp throws any exception other than MIDletStateChangeException
}
}

数据接收端:

package org.sunspotworld;

import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.io.j2me.radiostream.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.*;

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {

/**
* starApp()方法启动一个Sun SPOT应用程序。
*/

protected void startApp() throws MIDletStateChangeException {

/**
* 启动一个线程,检测USB电缆是否被连接上。
*/

new BootloaderListener().start();

/**
* 获得本机的IEEE扩展Mac地址。
*/

IEEEAddress ourAddr = new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());

/**
* 打印本机的IEEE扩展Mac地址。
*/

System.out.println("Our radio address = " + ourAddr.asDottedHex());

/**
* 声明程序中用到的对象
* RadiogramConnection -- 通讯连接
* dg_receive -- 接收到的数据包
* input_message -- 对方发送过来的数据
*/

RadiogramConnection conn;
Datagram dg_receive;
String input_message;


try
{

/**
* 创建连接。
* 100是通讯双方约定的端口号。
*/

conn = (RadiogramConnection) Connector.open("radiogram://:100");

/**
* 进入一个无限循环。
*/
while (true)
{


/**
* 创建用来接收数据的数据包。
*/

dg_receive = conn.newDatagram(conn.getMaximumLength());

/**
* 接收数据包。
*/
conn.receive(dg_receive);

/**
* 从数据包中提取接收到的消息。
*/
input_message = dg_receive.readUTF();

/**
* 打印接收到的消息。
*/
System.out.println(input_message);
}
} catch (IOException e)
{
System.out.println("No route to the destination Sun SPOT.");
}


}

protected void pauseApp() {
// This will never be called by the Squawk VM
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
// Only called if startApp throws any exception other than MIDletStateChangeException
}
}

当 一只Sun SPOT打开某个端口进行数据广播时,它还可以打开同一个端口监听来自其他Sun SPOT的广播数据,但是无法接受到来自自己的广播数据。也就是说,假如有一组Sun SPOT需要相互交换信息,那么这些Sun SPOT可以约定使用同样的端口通过广播的方式进行通讯。下面的程序演示了如何通过这个方式进行数据交换:

package org.sunspotworld;

import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.io.j2me.radiostream.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.*;

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {

/**
* starApp()方法启动一个Sun SPOT应用程序。
*/

protected void startApp() throws MIDletStateChangeException {

/**
* 启动一个线程,检测USB电缆是否被连接上。
*/

new BootloaderListener().start();

/**
* 获得本机的IEEE扩展Mac地址。
*/

IEEEAddress ourAddr = new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());

/**
* 打印本机的IEEE扩展Mac地址。
*/

System.out.println("Our radio address = " + ourAddr.asDottedHex());

/**
* 声明程序中用到的对象
* RadiogramConnection -- 通讯连接
* dg_send -- 发送出去的数据包
* i -- 计数器
*/

RadiogramConnection conn_send;
Datagram dg_send;
int i = 0;


try
{

/**
* 创建广播。
* 100是通讯双方约定的端口号。
*/

conn_send = (RadiogramConnection) Connector.open("radiogram://broadcast:100");

/**
* 启动一个线程来监听其他Sun SPOT发送的数据。
* startListenThread()方法中定义了一个线程,具体参见后面的实现。
*/
startListenThread();

/**
* 进入一个无限循环。
*/
while (true)
{


/**
* 创建用来发送数据的数据包。
*/
dg_send = conn_send.newDatagram(conn_send.getMaximumLength());

/**
* 将需要发送的内容写入需要发送的数据包。
*/
dg_send.writeUTF("Sun SPOT " + ourAddr.asDottedHex() + " Message number " + i + ".");

/**
* 发送数据包。
*/
conn_send.send(dg_send);

/**
* 休息三秒钟。
*/
Utils.sleep(3000);

/**
* 计数器加一,进入下一个循环。
*/

i = i + 1;
}
} catch (IOException e)
{
System.out.println("No route to the destination Sun SPOT.");
}

/**
* startListenThread()定义并启动一个线程。该线程的功能是监听其他Sun SPOT所发送的信息。
*/
public void startListenThread()
{


/**
* 在Java语言中,可以通过创建一个Runnable对象来创建一个线程。
*/
Runnable r = new Runnable(){

/**
* 每个线程都需要定义一个run()方法。一个线程运行的时候,其实就是运行这个方法。
*/
public void run()
{

/**
* 调试语句。
*/
System.out.println("Entering listen thread...");

/**
* 该线程的主要功能,是调用listenForData()方法进行数据监听。
* 该方法在后面有具体的定义。
*/
listenForData();
}
};

/**
* 创建该线程的一个实例,并且通过start()方法启动该线程。
* 在Java语言的线程中,start()方法调用run()方法。
*/
(new Thread(r)).start();
}

/**
* 监听数据广播的方法
*/

public void listenForData()
{

/**
* 定义必要的对象。
*/
RadiogramConnection conn_receive;
Datagram dg_receive;
String input_message;

try
{

/**
* 创建数据监听连接。
*/
conn_receive = (RadiogramConnection) Connector.open("radiogram://:100");

/**
* 创建一个Datagram对象用来接收数据。
*/
dg_receive = conn_receive.newDatagram(conn_receive.getMaximumLength());

/**
* 进入一个无限循环。
*/
while (true)
{
/**
* 调试语句。
*/
System.out.println("Listen for data....");

/**
* 接收数据。
*/
conn_receive.receive(dg_receive);

/**
* 读取数据。
*/
input_message = dg_receive.readUTF();

/**
* 显示数据并进入下一个循环。
*/
System.out.println(input_message);
}
}catch (IOException e)
{
System.out.println(e.toString());
}

}

}

protected void pauseApp() {
// This will never be called by the Squawk VM
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
// Only called if startApp throws any exception other than MIDletStateChangeException
}
}


五、系统相关

编号为0-31之间的端口为系统保留端口,请不要在您的应用程序当中使用这些端口。目前已经被系统使用的保留端口包括下面这些:

5

六、编程练习

在 飞行器中通常需要监测其三轴的加速度、机舱温度和亮度等指标。譬如说,加速度超出预定的范围,则表示飞行器的运动状态可能出现了问题(例如失速)。机舱的 温度过高,可能是由于某些部件过热引起。假设我们的Sun SPOT安装在某型号的小型飞行器上,与一组同型号的小型飞行器编队飞行,这些飞行器之间通过Sun SPOT提供的无线数据链进行通讯。在正常情况下,这些飞行器以每五秒一次的频率报告自己的身份以及OK信号。在加速度、温度或者是亮度超过某个范围的情 况下,以每秒一次的频率向其他的飞行器发送SOS信号以及自身的加速度、温度和亮度数据。

七、参考资料

RadiostreamConnection
RadiogramConnection
Datagram
DataInputStream
DataOutputStream
上一篇 下一篇

发表时间:2008-06-12 22:54:37    评论者:Jerry

第五部分的截图有点问题?小红叉叉。

 
姓名:
评论:

请输入下面这首诗词的作者姓名。

活水还须活火烹,自临钓石取深清。
大瓢贮月归春瓮,小杓分江入夜瓶。
茶雨已翻煎处脚,松风忽作泻时声。
枯肠未易禁三碗,坐听荒城长短更。

答案:

云与清风常拥有,
冰雪知音世难求。
击节纵歌相对笑,
案上诗书杯中酒。

蒋清野
2000.12.31 于 洛杉矶