The main aim of this project was to control a Shield Bot using my Nexus 5's accelerometer. There were some other aims also, but I'll leave those to be UQ's secrets for now, as they'll probably want to pull them out on some unsuspecting students next year. This was by far the coolest feature of the project anyway, so don't worry - you're not missing out.
First of all, here's a block diagram of the hardware and communications. Everyone loves block diagrams, right?
Note that I lumped together the last nRF24L01+, NetDuino Plus 2, and Seeed Shield Bot because I did no coding or setting up of those modules - they were supplied for the project as a full package as part of the assessment for CSSE3010. I was just given a spec that detailed commands I could send to the Shield Bot via RF, then it would perform the actions itself. Here are some links for more info on each of the pieces of hardware involved -
- Seeed Wifi Bee V2.0, mounted on a Seeed Bee Socket (Thanks for letting me borrow this, Matt!)
- Seeed Shield Bot V1.0
- NetDuino Plus 2 (NP2) running FreeRTOS, developing in C99.
- Any nRF24L01+ based RF chip. I used this one from SparkFun.
So, breaking the problem down, I started off by developing the app to send UDP packets to the wifi chip. It was a very basic Android app, all developed using the ADT. I felt that only one view was necessary, seeing as the app really didn't do all that much. Here are a couple of screenshots of that view -
The screenshot on the left is the app in its initial state, just after it has been opened. In the second screenshot, the 'Start Access Point' and the 'Start Sending Data' buttons have been pressed.
This app performs two main functions -
That code should speak for itself, for the most part. The 100ms delay is probably the only part I found confounding. I ended up adding that because sometimes, the access point information would just not be available yet, by the time the code to get that info would get hit. So the SSID, password, and IP address fields would just sometimes never get filled with data. It was unpredictable as to when it would and wouldn't work, adding that delay seemed to solve the issue. If you know a better way of getting around this issue, I'd love to hear it in the comments.
The other main function of this app was the sending and receiving of UDP packets over wifi. Here are some code snippets for that -
You've probably noticed that my socket needs to receive data before it sends anything. This meant that I could make my NP2 send a 'Give me some accelerometer values' packet whenever it was interested in getting back some accelerometer data, as opposed to the app just constantly puking out accelerometer values left, right, and center.
Now that the app has been explained, the next step was to get the Wifi Bee talking to my phone. This command reference for the wifi module - the Wifly RN-171 - was very useful for this step. Taking a look at that reference, you'll see that these Wifly chips are comprised of pure awesome. Send it a few commands via serial, and you're all set up to do whatever wifi related thing you could ever want. To set the Wifi Bee up initially, I used an FTDI chip (this one from DFRobot) and talked to the module using PuTTY. Here are the commands I used to set it up -
At this point, I would also like to acknowledge this blog, as it helped me out greatly with the initial setup of the Wifi Bee and proved to me that the idea of connecting an Android phone to these Wilfy modules was feasible.
The next step I took in getting this all to work was coding up the freeRTOS task to handle the requesting of accelerometer data via USART, receiving of accelerometer data via USART, and the forwarding of commands to the Shield Bot based on the accelerometer data via RF. Unfortunately, this code is not appropriate for me to post in the public domain, due to the fact it is what my assessment for CSSE3010 will be based on. Honestly though, setting this up was the easy part. Take any microcontroller you like for this part - the Wifi Bee just plugs in to the USART pins. From there, you send the Wifi Bee a character over USART, and it handles the transmission to the phone. If the phone transmits a packet to the Wifi Bee, the Wifi Bee handles all of the UDP/IP stack business, and just sends you the payload back to the microcontroller via USART. It's that simple.
And that is it! Well, at least all of it that I can post about. From this point on, I just sent commands to the Shield Bot and it handled the driving. Hope this post made for an interesting read and may be of use to you in the future.
The screenshot on the left is the app in its initial state, just after it has been opened. In the second screenshot, the 'Start Access Point' and the 'Start Sending Data' buttons have been pressed.
This app performs two main functions -
- Managing a wifi access point for the Wifi Bee to connect to; and
- Using a UDP socket to send the accelerometer data to the Wifi Bee.
In terms of setting up and closing down the wifi access point, here are some code snippets -
public class MainActivity extends ActionBarActivity {
WifiP2pManager wifiManager;
Channel wifiChannel;
...
static Button apBtn;
static ProgressBar APBar;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create a manager and channel for the hotspot
wifiManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
wifiChannel = wifiManager.initialize(this, getMainLooper(), null);
...
}
/* Just to be safe, let's remove the access point as soon as the app loses focus.
* Don't want to end up eating up all the battery! */
@Override
protected void onPause() {
// Check if the access point exists and if it is valid for us to remove
if (wifiManager != null && wifiChannel != null) {
wifiManager.requestGroupInfo(wifiChannel, new GroupInfoListener() {
@Override
public void onGroupInfoAvailable(WifiP2pGroup group) {
if (group != null && wifiManager != null && wifiChannel != null
&& group.isGroupOwner()) {
// Remove the access point group
wifiManager.removeGroup(wifiChannel, null);
}
}
});
}
finish();
super.onPause();
}
...
/* Called when user clicks the APButton */
public void toggleAccessPoint(View view) {
// Need to either disable the AP or enable the AP, depending on its current status
final Button apBtn = (Button)view;
String btnText = apBtn.getText().toString();
apBtn.setEnabled(false); // Disable the button while processing request
// Get the progress bar and make it visible
final ProgressBar apBar = (ProgressBar) findViewById(R.id.APProgressBar);
apBar.setVisibility(View.VISIBLE);
if (btnText.equals(getResources().getString(R.string.AP_button_off))) { // It's time to turn the AP on
wifiManager.createGroup(wifiChannel, new ActionListener() { // Create an access point
@Override
public void onSuccess() {
Log.d("AP Connect", "Successfully created AP group.");
// Give the wifi manager a little time to settle down, so we can get info from it
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Print out the access point ssid and password
wifiManager.requestGroupInfo(wifiChannel, new GroupInfoListener() {
@Override
public void onGroupInfoAvailable(WifiP2pGroup group) {
if (group != null && wifiManager != null && wifiChannel != null) {
TextView ssid = (TextView)findViewById(R.id.ssid);
TextView password = (TextView)findViewById(R.id.password);
ssid.setText(group.getNetworkName());
password.setText(group.getPassphrase());
}
}
});
// Print out the access point ip address
wifiManager.requestConnectionInfo(wifiChannel, new ConnectionInfoListener() {
@Override
public void onConnectionInfoAvailable(WifiP2pInfo info) {
if (info != null && wifiManager != null && wifiChannel != null &&
info.isGroupOwner) {
TextView ip = (TextView)findViewById(R.id.ipAddr);
ip.setText(info.groupOwnerAddress.toString());
}
}
});
// Change the button text to reflect the new state
apBtn.setText(getResources().getString(R.string.AP_button_on));
apBar.setVisibility(View.INVISIBLE); // hide the progress bar again
apBtn.setEnabled(true); // Re-enable button
}
@Override
public void onFailure(int reason) {
Log.d("AP Connect", "Failed to create AP group - " + reason);
}
});
} else { // It's time to turn the AP off
// Check if the access point exists and if it is valid for us to remove
if (wifiManager != null && wifiChannel != null) {
wifiManager.requestGroupInfo(wifiChannel, new GroupInfoListener() {
@Override
public void onGroupInfoAvailable(WifiP2pGroup group) {
if (group != null && wifiManager != null && wifiChannel != null
&& group.isGroupOwner()) {
// Remove the access point group
wifiManager.removeGroup(wifiChannel, new ActionListener() {
@Override
public void onSuccess() {
Log.d("AP Disconnect", "Successfully removed AP group.");
// Change the button text to reflect the new state
apBtn.setText(getResources().getString(R.string.AP_button_off));
apBar.setVisibility(View.INVISIBLE); // Hide the progress bar again
apBtn.setEnabled(true); // Re-enable the button
}
@Override
public void onFailure(int reason) {
Log.d("AP Disconnect", "Failed to remove AP group - " + reason);
}
});
}
}
});
}
}
}
...
}
That code should speak for itself, for the most part. The 100ms delay is probably the only part I found confounding. I ended up adding that because sometimes, the access point information would just not be available yet, by the time the code to get that info would get hit. So the SSID, password, and IP address fields would just sometimes never get filled with data. It was unpredictable as to when it would and wouldn't work, adding that delay seemed to solve the issue. If you know a better way of getting around this issue, I'd love to hear it in the comments.
The other main function of this app was the sending and receiving of UDP packets over wifi. Here are some code snippets for that -
public class MainActivity extends ActionBarActivity {
...
static String accXVal;
static String accYVal;
...
static DatagramSocket socket;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
// Set up to get accelerometer data
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// Get accelerometer data and keep track of the last value received
sensorManager.registerListener(new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
TextView accX = (TextView)findViewById(R.id.accX);
TextView accY = (TextView)findViewById(R.id.accY);
// Store the values for use later, multiplied by 100 for easier computation when
// they are sent to the NP2 over wifi.
accXVal = String.format("%.0f", event.values[0] * 100);
accYVal = String.format("%.0f", event.values[1] * 100);
// Print the values to the screen, if available
if (accX != null && accY != null) {
accX.setText(accXVal);
accY.setText(accYVal);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
...
/* Called when user clicks the Send/Stop Data Button */
public void toggleSendData(View view) {
// Need to either start sending or stop sending data, depending on the buttons's current status
Button sendDataBtn = (Button)view;
String btnText = sendDataBtn.getText().toString();
if (btnText.equals(getResources().getString(R.string.send_data_button_off))) { // Time to start sending data
new ServerAsyncTask(MainActivity.this).execute();
sendDataBtn.setText(getResources().getString(R.string.send_data_button_on));
} else { // Time to stop sending data
new ServerAsyncTask(MainActivity.this).cancel(true);
socket.close(); // Close the socket
sendDataBtn.setText(getResources().getString(R.string.send_data_button_off));
}
}
public static class ServerAsyncTask extends AsyncTask {
private Activity act;
public ServerAsyncTask(Activity _act) {
this.act = _act;
}
@Override
protected Void doInBackground(Void... params) {
try
{
// Set up some default parameters for the socket
int port = 8888;
String msg = "";
byte[] sendData = msg.getBytes();
// Create the socket
socket = new DatagramSocket(port);
for(;;)
{
DatagramPacket packet = new DatagramPacket(sendData, sendData.length);
// Receive a packet, blocking until something comes through
socket.receive(packet) ;
// Doesn't matter what was received, just set up the response such that it gives back the accelerometer x and y values.
msg = accXVal + " " + accYVal + "\n";
sendData = msg.getBytes();
packet.setData(sendData);
socket.send( packet ) ;
}
} catch (Exception e) {
Log.d("UDP sending", "Failed to send UDP packet - " + e.getMessage());
}
return (Void)null;
}
}
}
You've probably noticed that my socket needs to receive data before it sends anything. This meant that I could make my NP2 send a 'Give me some accelerometer values' packet whenever it was interested in getting back some accelerometer data, as opposed to the app just constantly puking out accelerometer values left, right, and center.
Now that the app has been explained, the next step was to get the Wifi Bee talking to my phone. This command reference for the wifi module - the Wifly RN-171 - was very useful for this step. Taking a look at that reference, you'll see that these Wifly chips are comprised of pure awesome. Send it a few commands via serial, and you're all set up to do whatever wifi related thing you could ever want. To set the Wifi Bee up initially, I used an FTDI chip (this one from DFRobot) and talked to the module using PuTTY. Here are the commands I used to set it up -
$$$
set wlan ssid DIRECT-wB-Android_19d3
set wlan passphrase ********
save
reboot
set wlan passphrase ********
save
reboot
$$$
set ip host 192.168.49.1 (The address of the phone)
set ip remote 8888 (The port the phone is listening on)
set ip proto 1 (Use UDP)
set ip host 192.168.49.1 (The address of the phone)
set ip remote 8888 (The port the phone is listening on)
set ip proto 1 (Use UDP)
save
reboot
At this point, I would also like to acknowledge this blog, as it helped me out greatly with the initial setup of the Wifi Bee and proved to me that the idea of connecting an Android phone to these Wilfy modules was feasible.
The next step I took in getting this all to work was coding up the freeRTOS task to handle the requesting of accelerometer data via USART, receiving of accelerometer data via USART, and the forwarding of commands to the Shield Bot based on the accelerometer data via RF. Unfortunately, this code is not appropriate for me to post in the public domain, due to the fact it is what my assessment for CSSE3010 will be based on. Honestly though, setting this up was the easy part. Take any microcontroller you like for this part - the Wifi Bee just plugs in to the USART pins. From there, you send the Wifi Bee a character over USART, and it handles the transmission to the phone. If the phone transmits a packet to the Wifi Bee, the Wifi Bee handles all of the UDP/IP stack business, and just sends you the payload back to the microcontroller via USART. It's that simple.
And that is it! Well, at least all of it that I can post about. From this point on, I just sent commands to the Shield Bot and it handled the driving. Hope this post made for an interesting read and may be of use to you in the future.


