Scripting Sphero's Star Wars Droids


For Christmas, I got an R2-D2 Sphero Droid, and wanted to script it. The droid has a number of builtin "animations" where it will make reactionary sounds, some of them involve R2 tapping his feet, wobbling from side to side, and a reenactment of being hit with an ion blast. Sitting on my desk, R2 would make a great little notification system with these animations, so I started looking into not just how to program the thing, but doing it in a way that can tie in with my existing desktop workflow.

Available Interfaces

Sphero provides a couple different programming options:

Interfacing with R2

The Sphero Star Wars droids use a BLE interface. The plan was to sniff the BLE traffic, and then replay the commands needed to play the animations. So, step 1: sniffing the BLE traffic.
The Sphero EDU app is great because you can have it send a single command to the droid. So firing up the Sphero EDU app to send one command at a time, and sniffing that command seemed ideal. So, how to sniff BLE on iOS?

Sniffing BLE traffic on iOS

Apple provides a number of debugging profiles that can be installed on iOS devices including a Bluetooth profile that includes BLE logging. Follow the instructions to install the profile, run Sphero EDU with the command you want to sniff, then sync the logs off with iTunes. The bluetooth packet log comes out in a 'pkl' file that can be read with Apple's Bluetooth Packet Logger briefly discussed here, and is available in Xcode -> Open Developer Tool -> More Developer Tools menu.

The protocol is a BLE UART interface, so basically sending serial commands to the droid. The commands look something like this:
0x8D,0x0A,0x17,0x05,0x41,0x00,0x0F,0x89,0xD8
All the commands begin with 0x8D and end with 0xD8. From the Older Packet Format docs, the old format included an 8bit checksum at the end of the packet, and sure enough this one does too. It's a simple inverted 8bit sum of all bytes in the packet (excluding 0x8d and 0xd8).

However, you can't just send the command and the droid will do it. The droid will disconnect you and ignore any input unless you send the following sequence to a separate BLE characteristic handle:
0x75,0x73,0x65,0x74,0x68,0x65,0x66,0x6F,0x72,0x63,0x65,0x2E,0x2E,0x2E,0x62,0x61,0x6E,0x64
This sequence is the ascii string "usetheforce...band", a reference to their Force Band remote control product.

After sending that string, you can start talking to the BLE UART. Sometimes the droid will be responsive to other commands right away, but sometimes not. It seems as though the droid can be in a low power mode where it's not performing any actions, and the following packet seems to wake it up:
0x8D,0x0A,0x13,0x0D,0x00,0xD5,0xD8
After that, it should be responsive to normal commands. When done, if the BLE connection is severed, the droid will sit looking confused for a timeout period before giving up on the client and going back to sleep mode. So it seems like the polite thing to do is to put the droid back to sleep when you're done talking with it:
0x8D,0x0A,0x13,0x01,0x17,0xCA,0xD8

Sending data to the droid

Linux's bluez package includes a very useful tool for this sort of thing: gatttool. This is a commandline tool for interacting with BLE GATT. The first step though is to figure out the address of the droid with hcitool lescan. This will list all the BLE devices the machine can see, including something like:
E3:61:5F:C0:FF:EE D2-FFEE
The address being E3:61:5F:C0:FF:EE, and D2-FFEE being the name of the droid, the last four digits being the last four of the address, in order to distinguish between multiple droids of the same model.

Here's an example session with gatttool to talk to R2:
sudo gatttool -I -t random -b E3:61:5F:C0:FF:EE
[E3:61:5F:C0:FF:EE][LE]> connect
Attempting to connect to E3:61:5F:C0:FF:EE
Connection successful
# usetheforce...band this must be entered quickly, so have it in a paste buffer
[E3:61:5F:C0:FF:EE][LE]> char-write-req 15 757365746865666F7263652
Characteristic value was written successfully
# wake from sleep
[E3:61:5F:C0:FF:EE][LE]> char-write-req 1c 8D0A130D00D5D8
Characteristic value was written successfully
# turn on holoprojector LED at maximum (0xFF) intensity
[E3:61:5F:C0:FF:EE][LE]> char-write-req 1c 8D0A1A0E1C0080FF32D8
Characteristic value was written successfully
So, now we know how to talk to the droid in at least a rudimentary fashion. The gatttool arguments are all hexadecimal, and the values taken from the BLE captures done earlier. This is a great way to experiment with the values observed in the capture, but not the most convenient way to script. gatttool allows issuing one-shot commands from the command line, but doesn't accept chains of commands for the same session as is required here. Unfortunately, the linux bluetooth stack, bluez, does not have official API for BLE and GATT interfacing, although being open source, you can look at the calls gatttool is making, and call them yourself.

Surely someone has made some python bindings or something? Well, not really. BLE in Linux seems to be a bit of a wasteland at the moment outside of the excellent gatttool. However, someone has made a python wrapper around gatttool. Less than ideal, but hey. Whatever works. So, here's my python script that makes R2 run his animations.

Updated January 23 2018