I have recently been lucky enough to borrow a set of very cool Sun SPOTs, so I immediately began thinking of all the amazingly clever and brilliant things I could do with them. However, with the exception of making the LEDs light up like a Cylon’s eyes (or Knightrider’s Kit as per your preference), all my ideas required one spot to be able to talk to another.
So what I figured the first thing to do would be to create a simple way to do this. Basically, each SPOT you want to talk to another instantiates a Buddy object and asks it to look for another buddy (myBuddy.lookForPair()) this will then start the buddy broadcasting and looking for a fellow.
It’s probably important to note at this point that I have not tested this code with more than two SPOTs, I certainly haven’t tested any edge cases where multiple SPOTs start up and all start looking for buddies on different ports. This is code is for my own use, I’m very happy for anyone to download, modify and use it (it is GPL’d) as long as you remember that it only works for the nicest of use cases.
Once two buddies (on different SPOTs) have connected you can send a message from one to the other by typing;
myBuddy.sendMessage("my message");
Recieved messages are handled by a BuddyListener which your SPOT application should have given to it’s Buddy instance. Recieved messages come in from the following method on that interface;
void messageRecieved(String message);
There’s lots of ways this code could be improved, for example the initial broadcast port is hardcoded into the Buddy code itself, this should really be parameterised. Also, the sleeps and waits that the Buddy endures while trying to connect to another one and so on. These should probably be pulled out to fields so that they can be tweaked more easily.
The good thing about this code is that it makes paired communication very simple (although you do have to be careful with the threads that are listening to incoming messages). You can also use your own language/protocol that the SPOTs will interpret on top of this Buddy mechanism very easily.
radio.Buddy – This is the meat of the project. To easily connect two SPOTs each must have an instance of one of these. Once connected to each other sending and recieving messages between the two is very easy.
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package radio;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import javax.microedition.io.DatagramConnection;
import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.io.j2me.radiostream.RadiostreamConnection;
import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.util.IEEEAddress;
import com.sun.spot.util.Utils;
import com.sun.squawk.util.StringTokenizer;
public class Buddy {
private static final String NEED_BUDDY = "BUDDY_REQ";
private static final String I_WILL_BE_YOUR_BUDDY = "BUDDY_ACK";
private long thisAddressAsNumber = RadioFactory.getRadioPolicyManager().getIEEEAddress();
private String addyStr;
private RadiostreamConnection listenOn;
private RadiostreamConnection sendOn;
private DataInputStream listenOnInputStream;
private DataOutputStream sendOnOutputStream;
private int portOut;
private int portIn;
private BuddyListener buddyListener = new NullBuddyListener();
private int initialBroadCastPort;
private boolean foundBuddy = false;
private RadiogramConnection broadcastConnectionIn;
private DatagramConnection broadcastConnectionOut;
private int MAX_BROADCAST_SIZE;
public Buddy(int broadcastPort) throws IOException {
this.initialBroadCastPort = broadcastPort;
}
public void lookForPair() throws IOException {
IEEEAddress addy = new IEEEAddress(RadioFactory.getRadioPolicyManager().getIEEEAddress());
addyStr = addy.asDottedHex();
System.out.println("I am " + addyStr);
broadcastConnectionIn = (RadiogramConnection) Connector.open("radiogram://:" + initialBroadCastPort);
MAX_BROADCAST_SIZE = broadcastConnectionIn.getMaximumLength();
broadcastConnectionOut = (DatagramConnection) Connector.open("radiogram://broadcast:" + initialBroadCastPort);
startListenForPartnerResponseThread();
startLookForBuddyThread();
}
public void setBuddyListener(BuddyListener bl) {
if(null != bl) {
buddyListener = bl;
} else {
buddyListener = new NullBuddyListener(); //allows us to reset the currently loaded BuddyListener
}
}
public void sendMessage(String message) {
if (null != sendOn) {
try {
if (null == sendOnOutputStream) {
sendOnOutputStream = sendOn.openDataOutputStream();
}
sendOnOutputStream.writeUTF(message);
} catch (IOException ioe) {
System.out.println("IOException sending message on partner stream");
ioe.printStackTrace();
}
} else {
throw new IllegalStateException(
"Cannot send message. No buddy found yet.");
}
}
private void startLookForBuddyThread() {
Thread t = new Thread(new Runnable() {
public void run() {
lookForBuddy();
}
});
t.start();
}
private void lookForBuddy() {
try {
while (!foundBuddy) {
Datagram dg = broadcastConnectionOut.newDatagram(broadcastConnectionOut.getMaximumLength());
dg.writeUTF(Buddy.NEED_BUDDY + " " + initialBroadCastPort);
broadcastConnectionOut.send(dg);
Utils.sleep(1000);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void startListenForPartnerResponseThread() {
Thread t = new Thread(new Runnable() {
public void run() {
try {
while (true) {
Datagram dg = broadcastConnectionIn.newDatagram(MAX_BROADCAST_SIZE);
broadcastConnectionIn.receive(dg); // blocking call
String message = dg.readUTF();
String otherAddress = dg.getAddress();
long otherAddressAsNumber = IEEEAddress.toLong(otherAddress);
if (thisAddressAsNumber != otherAddressAsNumber) {
handleIncomingBroadcastMessage(dg, message, otherAddress);
}
if (sendOn != null && listenOn != null) {
System.out.println("Buddy status achieved, no longer going to listen to broadcasts");
System.out.println("Partner port to send on: " + sendOn.getLocalPort());
System.out.println("Partner port to listen on: " + listenOn.getLocalPort());
startListenOnPartnerStreamThread();
break;
}
}
} catch (IOException ioe) {
System.out.println("Exception when listening for buddy response");
ioe.printStackTrace();
}
}
});
t.start();
}
private void handleIncomingBroadcastMessage(Datagram dg, String message, String otherAddress) throws IOException {
if (message.startsWith(Buddy.NEED_BUDDY)) {
if (null == sendOn) {
int otherPort = Integer.valueOf(separateStrings(message)[1]).intValue();
Datagram response = offerToBeBuddy(otherPort, otherAddress);
response.setAddress(dg);
broadcastConnectionIn.send(response);
}
} else if (message.startsWith(Buddy.I_WILL_BE_YOUR_BUDDY)) {
buddyInviteAccepted(message, otherAddress);
}
}
private void buddyInviteAccepted(String message, String otherAddress) throws IOException {
foundBuddy = true;
portOut = Integer.valueOf(separateStrings(message)[1]).intValue();
sendOn = (RadiostreamConnection) Connector.open("radiostream://" + otherAddress + ":" + portOut);
portIn = portOut - 1;
listenOn = (RadiostreamConnection) Connector.open("radiostream://" + otherAddress + ":" + portIn);
}
private Datagram offerToBeBuddy(int otherPort, String otherAddress) throws IOException {
foundBuddy = true;
portOut = otherPort;
portIn = portOut + 1;
System.out.println("Offering to be a buddy to " + otherAddress + "\nSending on " + portOut + "\nListening on " + portIn);
sendOn = (RadiostreamConnection) Connector.open("radiostream://" + otherAddress + ":" + portOut);
Datagram response = broadcastConnectionIn.newDatagram(MAX_BROADCAST_SIZE);
response.writeUTF(Buddy.I_WILL_BE_YOUR_BUDDY + " " + portIn);
listenOn = (RadiostreamConnection) Connector.open("radiostream://" + otherAddress + ":" + portIn);
return response;
}
private void startListenOnPartnerStreamThread() {
Thread t = new Thread(new Runnable() {
public void run() {
while (true) {
listenForIncomingMessage();
}
}
});
t.start();
}
private void listenForIncomingMessage() {
if (null != listenOn) {
try {
if (null == listenOnInputStream) {
listenOnInputStream = listenOn.openDataInputStream();
}
String message = listenOnInputStream.readUTF();
buddyListener.messageRecieved(message);
} catch (IOException ioe) {
System.out.println("IOException recieving message on partner stream");
ioe.printStackTrace();
}
} else {
// Maybe we haven't found a partner yet, so don't spin the processor
Utils.sleep(100);
}
}
private String[] separateStrings(String msg) {
StringTokenizer stk = new StringTokenizer(msg, " ");
String[] result = new String[stk.countTokens()];
for (int i = 0; stk.hasMoreTokens(); i++) {
result[i] = stk.nextToken();
}
return result;
}
}
radio.BuddyListener – Classes wishing to have a buddy must have (or be) one of these and give it to the buddy instance
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package radio;
public interface BuddyListener {
void messageRecieved(String message);
}
radio.NullBuddyListener – It might be that two buddies are connected and one could be chatting to the other which doesn’t have a BuddyListener attached, in which case this is used instead.
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package radio;
public class NullBuddyListener implements BuddyListener {
public void messageRecieved(String message) {
System.out.println("Warning! NullBuddyListener being used and messages are getting recieved");
}
}
radio.RadioFun – This is included for completeness. It’s the app that got run when I was testing the Buddy connections. I don’t think it makes a lot of sense for any one else to use this class except to see how to talk/listen to the buddy.
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package radio;
import java.io.IOException;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.util.IEEEAddress;
import com.sun.spot.util.Utils;
public class RadioFun extends MIDlet {
private Buddy buddy;
public RadioFun() {
}
void start() {
try {
buddy = new Buddy(60);
buddy.setBuddyListener(new BuddyListener() {
public void messageRecieved(String message) {
System.out.println("recieved=["+message+"]");
}
});
buddy.lookForPair();
} catch (IOException ioe) {
ioe.printStackTrace();
}
IEEEAddress addy = new IEEEAddress(RadioFactory.getRadioPolicyManager().getIEEEAddress());
String addyStr = addy.asDottedHex();
while (true) {
try {
buddy.sendMessage("Hello from "+addyStr);
} catch (IllegalStateException ise) {
System.out.println("Failed to send message ["+ise.getMessage()+"]");
}
Utils.sleep(500);
}
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
RadioFun rf = new RadioFun();
rf.start();
}
}
January 11, 2009 at 19:21 |
hello! i found your post very interesting since I am new to Sunspot. However when downloading the program into the spot device, the following error was prompted:
symbol : method getRadioPolicyManager()
location: class com.sun.spot.peripheral.radio.RadioFactory
IEEEAddress addy = new IEEEAddress(RadioFactory.getRadioPolicyManager().getIEEEAddress());
Do you have any idea why? and how to solve it?
I have included the following libraries as shown in your code:
import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.io.j2me.radiostream.RadiostreamConnection;
import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.util.IEEEAddress;
import com.sun.spot.util.Utils;
import com.sun.squawk.util.StringTokenizer;
Im using Netbeans 5.5 and Mac OX 10.4.11. Hooe you can help
Thanks,
January 11, 2009 at 20:11 |
Hi, I’m glad you find it useful.
The only thing I can think of is that you have a different version of the SUN Spot SDK loaded. I’m running blue (I think) or it might be purple, I can’t remember right now.
I’d upgrade your spots to the latest version and then try a redeploy of this app. If that still doesn’t work then check the Spot documentation for whatever the current method is to get the IEEEAddress of your spots and change the code to that.
Here’s a tip though. Always upgrade one version at a time, i.e. don’t jump straight from version 1 to version 5 (or whatever the version numbers are) I did that once and it took me hours to get the Spots back to a working state because the upgrade failed spectacularly!
Hope this helps.
Tom
January 16, 2009 at 22:11 |
Thanks!! you were right i was using an older version. One more question now that they are up and running is there a way I can “see” a message going from one spot to another without any wired connection?
In other words how can I test the sending/receiving packets from one spot to another? I know you included radio.RadioFun.java but how do you exactly test this code? Right now, after I downloaded the program to the Spots the wireless led blinks but how exactly do i know if they are communicating with each other
>> its been a while since i’ve played around with netbeans…
Regards,
January 18, 2009 at 21:22 |
Great, I’m glad that it works for you – or that you can at least get the app deployed now.
If you use the SPOT emulator and open the output for each spot the RadioFun app will send a “Hello from xyz” message to each other. I’m afraid that there isn’t any other way to see what’s going on.
I wrote this code simply to allow me to easily connect two SPOTs (albeit in a less than secure and robust way) and send messages back and forth. The protocol of those messages is up to you and your own needs to define. It should be pretty easy to change RadioFun to send names of colours between the SPOTs and maybe blink the LEDs based on what colour gets sent. The changes required I think would be trivial.
“its been a while since i’ve played around with netbeans” Just so you know, NetBeans is not required – although the SDK installers claims that it is. What I do is copy one of the demo apps, do a find-replace on the application name on all the files in that directory to the name of my new application, use Eclipse to write my code and the ant on the command line to deploy it. NetBeans might have a “Sun SPOT Application” project type but I find the clunkiness, slowness and bugginess of the IDE far outweighs those benefits.
(Luckily I’m wearing my asbestos underpants to protect from the flames such a comment might now ignight.)
Cheers.
April 15, 2009 at 16:52 |
After reading through this article, I just feel that I really need more information on the topic. Could you share some resources please?