Thursday, 2 October 2014

Design notes on making your own IR remote for Pentax DSLRs

If you feel like saving yourself a few dollars and don't mind spending some time making a camera remote yourself, you might find this to be a handy resource. I mention Pentax specifically in the title, because that is what brand I built mine for, but you could set this remote up for whatever DSLR (or other IR controlled device) that you have the shutter code for. First up, here's some pictures of the finished IR remote and external footswitch:









The Electronics

I made a few objectives for myself when designing the hardware, and I think I met them all reasonably well. They were to:
  1. Use the absolute minimum amount of power possible in standby mode. I wanted this remote to be able to sit on the shelf for at least 2 years, and still be ready to go;
  2. Be as small as possible, while still using a standard battery size and just protoboard;
  3. Be relatively cheap - at least a good margin cheaper than the $50 off-the-shelf remotes sold by the big brands; and
  4. Have an interface for an external footswitch and an external device using 5V logic. The external device could then control this module and use it for timelapses, etc.
Here is the schematic I developed for the project (click the image for a larger version):


Looking at that schematic in more detail, there's a few important notes to be made about some of the component selections:
  • The voltage regulator I used was this Pololu regulator. It was probably the most expensive component on the board, at around $5. It can step up/down anywhere between 0.5-5.5V to 5V. Given the AAA battery would be 1.5V (or 1.2V for rechargeable), this regulator would do a swell job of keeping the voltage high for the rest of the electronics.
  • The two BS170 MOSFETs are being used as switches. Q3 is in line with the normal switch, such that when pulsed with a 5V input, the remote will fire, just as if the button was pushed. Q1 is being used in a slightly different way though. Given I wanted this remote to have the lowest possible power consumption, that meant I would have to disconnect all of the electronics from the battery and only apply power when the button is pushed (or Q3 is 'opened'). To do this then means that when the button goes up, power will stop being applied again, even if the AVR still has some computing to do. So, Q1 is controllable by the AVR such that once power is applied to the AVR, the first thing the AVR does is pull pin 6 high so that it can then control when it wants to disconnect the battery. This also proves to effectively debounce the push button as well.
  • The 2N3904 BJT is just being used to amplify the signal over the IR LED. The IR LED I used was capable of withstanding spikes of about 1W, so adding in the BJT allowed me to increase the range of the remote a bit. I've only tested it to about 6m, but I think it could go further. Tuning that base resistor would allow for more current to flow through the LED, if you feel the need for even more power. It's necessary to use this boosting BJT because the pins of the AVR can only supply up to 25mA before being at risk of damage.
  • The 8MHz oscillator is not really necessary for this application. I started off using it, thinking that the more precise PWM signal would be better, but really, it's overkill. I've now reverted back to the internal 1MHz oscillator, and it works just fine. Given this is such a short pulse and IR communication in most consumer products tends not to be super precise, using the internal oscillator will save you 50 cents and will lower the probability of something going wrong with that extra component.

To actually put together the board there was quite a bit of tricky protoboard soldering and precise component leg bending involved. I would have liked to have just gone surface mount everything and set up a PCB for the project, but due to time constraints, that didn't happen. If you would be interested in a full tutorial and some documentation on how I set up and soldered the protoboard, let me know in the comments. If there's enough interest, I'll make another post detailing the build process. For now, here's a pretty timelapse of me soldering one up:


The Code

Developing the code for the project was pretty simple, once the IR shutter code for the Pentax DSLRs was found. I found the below shutter code on this forum:


From there, coding it up on the AVR was pretty straight forward. I used Atmel Studio for generating the hex file and a $10 avr-isp off eBay to program the AVR. You can find my code, the hex file, and a bunch of other useful resources in my Google Drive folder for the project. Here's the code:

/*
 * PentaxRemote.c
 *
 * A very basic program that simply fires the 'take photo' IR command for 
 * Pentax cameras, then switches itself back off again. Use case in mind is 
 * that the power source will be directly connected to a button and a FET  
 * switch. When the button is pressed, all the AVR does is turn on the FET  
 * switch (to keep power until it's ready to switch off), send the IR code
 * and then turn itself off again when it's finished sending the code.
 *
 * Created: 10/08/2014 10:16:17 AM
 * Author: Anthony Stancombe
 */ 

#define F_CPU 1000000UL  // 1 MHz
#define PWM_FREQ 38000 // 38kHz
#define INFO_LED PB2 // An LED for letting the user know the avr is running
#define PWR_SWITCH PB2 // FET switch, directly controlling the power source
#define IR_LED PB0 // The IR LED for outputting the remote signal

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/delay.h>

int main(void)
{
    // Set up the two LEDs and FET switch as outputs.
    DDRB = _BV(INFO_LED) | _BV(IR_LED) | _BV(PWR_SWITCH);
    // Switch the info LED and FET switch on
    PORTB |= _BV(INFO_LED) | _BV(PWR_SWITCH); 
    // Switch the IR LED off
    PORTB &= ~_BV(IR_LED); 
    
    // Set up the PWM freq
    OCR0A = ((F_CPU / PWM_FREQ - 1)/ 2);
    
    // Start transmitting the code. The code is 50% duty cycle PWM at 38kHz, 
    // following the pattern -
    //  1)  13ms on, 3ms off, 
    //  2)  1ms on, 1ms off,
    //  3)  1ms on, 1ms off,
    //  4)  1ms on, 1ms off,
    //  5)  1ms on, 1ms off,
    //  6)  1ms on, 1ms off,
    //  7)  1ms on, 1ms off,
    //  8)  1ms on, 1ms off
    transmit_IR_segment(13, 3);
    for (int i = 0; i < 7; i++) {
        transmit_IR_segment(1, 1);
    }

    PORTB &= ~_BV(PWR_SWITCH); // Cut the power off by turning off FET switch
}

// transmit_IR_segment: Transmits a segment of an IR code, by turning on 50%, 
//                      38kHz PWM for a specified on time, then turning it off
//                      again for a specified off time. Uses pin 5 (PB0).
// params:              OnTime [int] => How long to pluse 50%, 38kHz PWM, in 
//                      milliseconds.
//                      OffTime [int] => How long to wait before returning, 
//                      with the PWM off, in milliseconds.
void transmit_IR_segment(int OnTime, int OffTime) {
    // Reset the timer and flags
    TCNT0 = 0;
    TIFR = 0;

    // Set up timer 0 to generate a 38kHz carrier
    TCCR0A =_BV(COM0A0) | _BV(WGM01);
    TCCR0B = _BV(CS00);

    // Leave the PWM on for the on time
    delay_ms(OnTime);
    
    // Now, switch off the PWM and the IR LED
    TCCR0A = 0;
    TCCR0B = 0;
    PORTB &= ~_BV(IR_LED);

    // Wait for off time
    delay_ms(OffTime);
}

// delay_ms:    Implementation of delay function that gives _delay_ms from 
//              delay.h a value at compile time to make it happy. The timing
//              does get slightly off by doing this, but only by some 
//              minuscule amount that doesn't cause problems for this code. 
// params:      Time [int] => How long to delay. (milliseconds)
void delay_ms(int Time) {
    while (Time--) {
        _delay_ms(1);
    }
}

I found Adafruit's TV-B-Gone remote kit code to be a helpful resource for me when I was putting together my code.

The Case

Making the case was a lot of fun, given I have a friend with a 3D printer. To develop the case, I used OpenSCAD. I find OpenSCAD to be incredibly awesome for creating 3D models, because I'm more comfortable with the programmatic approach. I can never seem to get the fancy double-click, mouse-swipe-left, press-A-three-times tricks that all of the graphical CAD programs require.

All of the SCAD files and STLs can be downloaded from my Google Drive folder for the project. Here are a few pictures of the case and the external footswitch I made:







A few things to note about the box:
  • There is an insert inside the box that isn't in the STL. That insert is some soft cushioning foam I put in there to make the electronics fit in nice and cozy.
  • The pictured button is a clearish-white button that I bought and glued to some clear, stiff plastic, so that I could achieve a nice, tactile feel from the push button switch on the board, even if it was slightly misaligned.
  • The lid is snap fitting, and works really well. It will easily hold shut when dropped, but is not too difficult to get off with your fingernails as well. I did have to whittle it a bit with a utility knife though, just to get the fit exactly right.

Final thoughts...

If I were to do this project again, I would probably aim to make the whole remote smaller. It feels a tad bit clunky, having it be so big. This could be achieved by changing the power supply to something a bit more expensive and small, like a LiPo battery, and PCBifying the electronics.


And that's really all there was to it! If you have any suggestions or questions, feel free to comment below and I'll respond as soon as possible. Thanks for taking the time to read this, and I hope it helps you out if you're thinking about making your own IR remote.


Wednesday, 20 August 2014

Upgrading the RN171 WiFi chip to v4.41 and enabling the access point

Finally, I came across a good reason to enable the access point on the RN171 chip, so I did just that by upgrading it from the stock firmware - v2.38.3 - to v4.41. Setting it up the first time took me an hour or so, mostly looking around for which version of software I should upgrade to, etc. So hopefully, this guide will provide you with enough information to quickly and easily upgrade your own RN171.

First of all, the specific chip I was upgrading was the Wifi Bee v2.0 (pictured below, in a Bee Socket v0.9b. These sockets aren't sold anymore, and have been replaced by the Grove), but this guide should apply to any of the many XBee RN171 variants out there.


The thing that took me the most time was finding the latest firmware version and the manual for that version. Really and truly, this should have been quick, but I derped out and forgot to look in the most obvious place. The manufacturer's website. So, here you will find Microchip's own page of info about the RN171, which contains both the latest firmware version and the manual for v4.41. As of the time of me posting this, v4.41 is the latest stable version, so of course, that's what version I'll talk about upgrading to.

To talk to the chip, you'll need an FTDI chip (like this one) and a serial application (like PuTTY), or some similar setup you've already got going. I'm not going to go into detail about this, as there are plenty of posts out there that will give you a full tutorial on how to speak to USART devices from a terminal.

To upgrade the chip to v4.41, the chip first needs to be connected to a network that has an internet connection. To do this, I used the following commands:

CMD    // Typing '$$$' starts command mode
set wlan ssid YourAccessPoint
AOK
<2.38.3> set wlan passphrase YourAccessPointPassword
AOK
<2.38.3> save
Storing in config
<2.38.3> reboot

Next, the update over FTP can be initiated:

CMD
ftp update wifly7-441.img    // If a timeout error occurs with the ftp download, try rebooting the chip and wait to see that it connects to your network, before trying the ftp command again
<2.38.3> FTP connecting to 198.175.253.161
FTP file=35
................................................................
FTP OK.
UPDATE OK
// MAKE SURE you run this factory reset and reboot straight after the update!
factory R
Set Factory Defaults
<2.38.3> reboot
*Reboot**CFG-ERR*
wifly-EZX Ver: 4.41 Build: r1057, Jan 17 2014 10:23:54 on RN-171
MAC Addr=00:06:66:51:0b:0c
*READY*

Now, you’ll need to connect to your network again because it needs to download some more files, so put the chip in ad hoc mode and set up the network:

CMD
set wlan join 1    // Turns on ad hoc mode
AOK
<4.41> set wlan ssid YourAccessPoint
AOK
<4.41> set wlan passphrase YourAccessPointPassword
AOK
<4.41> save
Storing in config
<4.41> reboot
*Reboot*wifly-EZX Ver: 4.41 Build: r1057, Jan 17 2014 10:23:54 on RN-171
MAC Addr=00:06:66:51:0b:0c
*READY*

Download the extra system files (for more info, see p80 of the manual):

CMD
ftp update wifly7-441.mif
<4.41> FTP connecting to 198.175.253.161
FTP file=58:................................................................
FTP file=59:..................................
FTP file=60:................................................
FTP file=61:.........................................................
FTP file=62:....................................
FTP file=63:...
FTP file=64:.......
UPDATE OK
*Reboot*wifly-EZX Ver: 4.41 Build: r1057, Jan 17 2014 10:23:54 on RN-171
MAC Addr=00:06:66:51:0b:0c
*READY*

Finally, set the chip up to act as an access point. The values below are just the defaults, apart from the ssid and passphrase (more info on this on p47 of the manual):

CMD
set wlan join 7    // Turns on access point mode
AOK
<4.41> set apmode ssid SomeSSID
AOK
<4.41> set apmode passphrase SomePassword
AOK
<4.41> set ip dhcp 4
AOK
<4.41> set ip address 192.168.1.1
AOK
<4.41> set ip net 255.255.255.0
AOK
<4.41> set ip gateway 192.168.1.1
AOK
<4.41> save
Storing in config
<4.41> reboot
*Reboot*wifly-EZX Ver: 4.41 Build: r1057, Jan 17 2014 10:23:54 on RN-171
MAC Addr=00:06:66:51:0b:0c
*READY*
Using WPA2-PSK
AP mode as SomeSSID on chan 1
Listen on 2000
DHCP Server Init

All done! Now you can test your access point. Try seeing if it is in your available networks on your computer and connect to it. Assuming that goes smoothly, use Netcat (or something similar) to open up a TCP connection to 192.168.1.1 on port 2000. You should now be able to type into your Netcat or serial window and see what you are typing appear in the other window, like in the picture below.


Thursday, 10 July 2014

CSSE3010 final project task - Seeed Shiled Bot control via Android app

Anyone looking to have some fun with the Seeed Shield Bot, the Seeed Wifi Bee, or writing an app that spits data down a UDP socket in Android might find this article of interest. Specifically the second two, given that they're the parts I can talk about in detail, as the setup of the Shield Bot was not covered by me.

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 -


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 -

  1. Managing a wifi access point for the Wifi Bee to connect to; and
  2. 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 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.