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:- Sphero EDU is an app that runs on iOS and Android and allows you to use a Scratch-like interface, or a javascript style interface, and one of my favorite features is you can start with scratch and have it show you the javascript it generates. This is a lot of fun, and I think it's a great app, but it's isolated in a sandbox. You can't have Mail, for instance, run a Sphero EDU program every time mail is received, or Messages when a new text arrives.
- Similarly, Sphero offers bindings in Swift Playgrounds on iPads. Again, isolated in a sandbox with no ability to integrate with anything outside of it.
- Sphero also provides an SDK. Unfortunately, as of the time of this project, the SDK and all related documentation hasn't been updated for the Star Wars droids. The Star Wars droids all use BLE, where the older Sphero orbs used classic bluetooth with a different protocol.
- There's also a number of open source scripting language bindings for the Spheros, but everything I came across was also based on the classic bluetooth interface.
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,0xD8All 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,0x64This 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,0xD8After 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-FFEEThe 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 successfullySo, 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