Reverse Engineering Sphero R2D2 with JavaScript
Tags: javascript, tutorial, beginners, node
Published: 2019-12-24 13:11:19

I bought my Sphero R2D2 two years ago, it was a cool toy for a Star Wars fans like me and a great pal for my cat.. anyway after some time I started thinking to make some programming project with this beautiful cluster of electronics! I wanted to find a way to script my robot but I didn’t found anything well documented and maintained.

The only thing I knew about R2D2 is that it works using BLE technology and you can drive it using the official Sphero Droids app (link). I found only [this article](http://www.synack.net/~bbraun/spherodroid/), a good starting point and on Sphero’s website there’s [some documentation about protocol communication](https://sdk.sphero.com/docs/api_spec/general_api/#protocol-definition) but it wasn’t enough, the article and the script attached looked unfinished and documentation had no specification about the messages that make R2D2 moving and dancing.

That’s why I decided to write some Javascript code to discover how to communicate with R2D2! In this article I’ll show you my personal experience on reverse engineering this droid nut you can apply this approach to any BLE device you want to hack.

### TL;DR

You can jump to [this repository](https://github.com/astagi/freer2) and use the code to communicate with your R2D2\. The final result is in this video 📺

[![Watch the video](https://img.youtube.com/vi/J3hy3zDM3PI/0.jpg)](https://youtu.be/J3hy3zDM3PI)


<figcaption class="imageCaption">Scripting my Sphero R2D2 droid</figcaption>

</figure>

### Setup

For this experiment is necessary:

*   Basic knowledge of BLE protocol (a [tutorial for beginners](https://devzone.nordicsemi.com/nordic/short-range-guides/b/bluetooth-low-energy/posts/ble-services-a-beginners-tutorial#:~:targetText=The%20Bluetooth%20Core%20Specification%20defines%20a%20characteristic%20like%20this%3A,value%20is%20displayed%20or%20represented.%E2%80%9D))
*   A computer with BLE support (I’m using a MacBook Pro)
*   An Android phone (I’m using an old Motorola with an Android 6)
*   A Sphero R2D2 droid! ([Amazon](https://www.amazon.com/Sphero-R201ROW-R2-D2-App-Enabled-Droid/dp/B071KSR86B/ref=sr_1_2?crid=H8EUUHJQ878R&keywords=sphero+r2d2&qid=1577097323&sprefix=sphero+r2%2Caps%2C278&sr=8-2) 📦)

The first thing to do is installing Wireshark and Android Developer tools on the PC:

*   Wireshark is a network protocol analyzer useful for inspecting Bluetooth messages and can be downloaded [from the official site](https://www.wireshark.org/). 
*   Android Developer tools contain **adb executable** to communicate with your Android phone from the PC, visit [the official site](https://developer.android.com/studio/releases/sdk-tools) for more informations.

On the Android phone install [Sphero Droids app](https://play.google.com/store/apps/details?id=com.sphero.starwars&hl=en) and enable **Bluetooth HCI Spoofing** feature under Developer options.

<div class="aspectRatioPlaceholder is-locked" style="max-width: 360px; max-height: 641px;">![](https://cdn-images-1.medium.com/max/800/0*NxTQanavr9Csoj61.png)</div>

Using this feature, I’m able to obtain a file with all Bluetooth communication packets sent and received between devices. 

### Capturing data

Now, with BLE HCI Spoofing enabled, open the Sphero Droids app, connect R2D2 and play with it for some time. 

After that, close the app and download the file generated on your disk using **adb**.

```
adb pull /sdcard/btsnoop_hci.log /dest/path
```

This file is generally saved under **_/sdcard/btsnoop_hci.log_** and can be opened with Wireshark.

### Wireshark inspection

This is the most interesting part of the project: opening the file with Wireshark reveals a lot of useful information for reverse engineering the droid. This is what I got after my first session: there are a lot of _information request packets_ sent between the Android device (_localhost_) and the droid (mine is labeled with the address _d7:1b:52:17:7b:d6_) and, after some scrolling, there’s **_the first write request_**!

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 156px;">![](https://cdn-images-1.medium.com/max/800/1*QJELJr_8dBbe2JRywj_yIQ.png)</div>

<figcaption class="imageCaption">The “usetheforce. ..band” message</figcaption>

As you can see in the bytes inspector the payload is quite eloquent: **“usetheforce. ..band”**. Sounds good :)

Another useful information is _Service UUID_ and _Characteristic UUID (handle 0x0015)_, annotate them to know where to send “usetheforce. ..band” message!

Now it’s time to read some documentation, starting from the Packet structure. This is the schema of a packet in Sphero’s protocol:

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 139px;">![](https://cdn-images-1.medium.com/max/800/1*09fPMfFXr8hL8zTbHZtYEw.png)</div>

Every packet has a SOP (Start of packet) byte and an EOP (End of packet) byte, both equal to 0x8D and 0xD8, so it’s necessary to search for all those packets starting with SOP and ending with EOP.

Other interesting bytes are:

**SEQ (Sequence Number)**: The token used to link commands with responses  
**DATA (Message Data)**: Zero or more bytes of payload data  
**CHK (Checksum)**: The sum of all bytes (excluding SOP and EOP) mod 256, bit-inverted

The first packet sent from the app is this:

```
| 0x8D | 0x0A | 0x13 | 0x0D | 0x00 | 0xD5 | 0xD8 |
```

The **SEQ** byte here is **0x00** according to the packet structure schema: this is the first packet the app sends to the droid! Let’s call it the **Init packet**

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 248px;">![](https://cdn-images-1.medium.com/max/800/1*DffYrGfnsKmoKvj5AB6OSg.png)</div>

<figcaption class="imageCaption">The first packet: Init packet</figcaption>

As you can see, there’s _another Service UUID and another Characteristic UUID_ (handle 0x001c) that will receive the next messages.

Another useful message to get is the last one at the end of the log file, sent from the app before closing, **the packet to turn off the droid**:

```
| 0x8D | 0x0A | 0x13 | 0x01 | 0x20 | 0xC1 | 0xD8 |
```

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 242px;">![](https://cdn-images-1.medium.com/max/800/1*dlckIDsVXYia78KGr_PaNg.png)</div>

<figcaption class="imageCaption">The Turn off packet</figcaption>

It’s time to annotate services, characteristics and messages (without SOP, EOP and other bytes) in some constants.

```js
const CONNECT_SERVICE = "00020001574f4f2053706865726f2121";
const CONNECT_CHAR = "00020005574f4f2053706865726f2121";

const MAIN_SERVICE = "00010001574f4f2053706865726f2121";
const MAIN_CHAR = "00010002574f4f2053706865726f2121";

const MSG_CONNECTION = [0x75,0x73,0x65,0x74,0x68,0x65,0x66,0x6F,0x72,0x63,0x65,0x2E,0x2E,0x2E,0x62,0x61,0x6E,0x64];
const MSG_INIT = [0x0A,0x13,0x0D];
const MSG_OFF = [0x0A,0x13,0x01];
```

### Let’s write some code

The final script will be composed by:

*   a function to **build a packet**
*   a function to **connect R2D2 droid**
*   a functionto **write packets and wait for a response**
*   a function to **turn off the droid**

#### Building a packet

Building a packet is very straightforward because it’s just an array of bytes, starting with a SOP byte and ending with an EOP byte. There are two bytes that must be generated at runtime:

*   **SEQ byte:** it’s just a variable initialized to 0x00 and incremented by 1 everytime a packet is built.
*   **CHK byte:** according to the documentation, CHK byte is _the sum of all bytes (excluding SOP & EOP) mod 256, bit-inverted,_ so it’s really easy to generate.

```js
let calculateChk = (buff) => {
  let ret = 0x00;
  for (let i = 0 ; i < buff.length ; i++) {
    ret += buff[i];
  }
  ret = ret & 255;
  return (ret ^ 255);
}
```

There are other special bytes used in communication beyond SOP and EOP:

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 453px;">![](https://cdn-images-1.medium.com/max/800/1*o4GUHpNiQ0TZT5ITO3JsNQ.png)</div>

When the ESC, SOP, or EOP bytes are needed in the payload, they are encoded into two-byte escape sequences as follows:

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 230px;">![](https://cdn-images-1.medium.com/max/800/1*O6mqYoyE6F4Ki4OY8NJp2w.png)</div>

This is the final code to build a valid packet for R2D2:

```js
const ESC = 0xAB;
const SOP = 0x8D;
const EOP = 0xD8;
const ESC_ESC = 0x23;
const ESC_SOP = 0x05;
const ESC_EOP = 0x50;

let seq = 0;

let buildPacket = (init, payload=[]) => {
  let packet = [SOP];
  let body = [];
  let packetEncoded = [];

  body.push(...init);
  body.push(seq);
  body.push(...payload);

  body.push(calculateChk(body));

  for (let i = 0 ; i < body.length ; i++) {
    if (body[i] == ESC) {
      packetEncoded.push(...[ESC, ESC_ESC]);
    }
    else if (body[i] == SOP) {
      packetEncoded.push(...[ESC, ESC_SOP]);
    }
    else if (body[i] == EOP) {
      packetEncoded.push(...[ESC, ESC_EOP]);
    }
    else {
      packetEncoded.push(body[i])
    }
  }

  packet.push(...packetEncoded);
  packet.push(EOP);
  seq++;

  return packet;
}
```

#### Connect our droid

In this example to connect R2D2 with the PC using BLE technology I use [Noble library](https://github.com/noble/noble). I installed two special forks to make `Noble` and `node-xpc-connection` working on MacOS Catalina (for more info have a quick glance at the [README](https://github.com/astagi/freer2/blob/master/README.md))

```
npm install git://github.com/taoyuan/node-xpc-connection.git
npm install git://github.com/lzever/noble.git
```

With Noble is really easy implementing a function to get the main characteristic used to communicate with the droid.

```js
const noble = require('noble');

let connectTheDroid = (address) => {
  return new Promise((resolve, reject) => {
    noble.on('discover', (peripheral) => {
      if (peripheral.address === address) {
        noble.stopScanning();
        peripheral.connect((e) => {
          peripheral.discoverServices([CONNECT_SERVICE], (error, services) => {
            services[0].discoverCharacteristics([HANDLE_CHAR], (error, characteristics) => {
              characteristics[0].notify(true);
              characteristics[0].subscribe(async (error) => {

              });
              services[0].discoverCharacteristics([CONNECT_CHAR], (error, characteristics) => {
                characteristics[0].write(Buffer.from(MSG_CONNECTION), true, (error) => {
                  peripheral.discoverServices([MAIN_SERVICE], (error, services) => {
                    services[0].discoverCharacteristics([MAIN_CHAR], (error, characteristics) => {
                      resolve(characteristics[0]);
                    });
                  });
                });
              });
            });
          });
        });
      }
    });

    noble.on('stateChange', (state) => {
      if (state === 'poweredOn') {
        noble.startScanning();
      } else {
        noble.stopScanning();
      }
    });
  });
}
```

This script starts scanning all the devices around and select the device with the specific address provided, gets the **connection service** and sends **“usetheforce. ..band”** (`MSG_CONNECTION`) message to its characteristic (`CONNECT_CHAR`). After that, it’s time to get the “Main characteristic” to send commands to the droid! To do that, it’s better creating some code for writing and reading because I need to wait for some responses.

#### Write packets and read responses

This is the core part of the experiment: create a function to write commands and… read the response! When the app sends a message to the droid, it receives one or more **response packets**, as you can see from logs and/or read from the documentation:

> Responses echo the DID, CID, and SEQ to help the sender fully identify the corresponding command packet.

Inspecting the Wireshark log you can see that there are **some _commands that receive another response after the echo response_** and other **_commands that require a timeout_** (e.g. the bipod/tripod transformation). 

To satisfy all these cases, the final write function have to work in this way:

*   Receives the characteristic, the command, a boolean to specify if it receives another response beyond the echo and a timeout
*   Sends the command to the characteristic
*   Waits for the response, check if there’s some errors and then resolve a promise (after some time if timeout is greater than 0)

To enable _‘data’ receive handler,_ the function needs to subscribe to the main characteristic and read from it. The data packet has the same structure of a packet used to send commands, but now we have to check if there are some errors in the Error byte.


```js
let writePacket = (characteristic, buff, waitForNotification = false, timeout = 0) => {
  return new Promise(function (resolve, reject) {

    let dataRead = [];
    let dataToCheck = [];
    let eopPosition = -1;

    let checkIsAValidRequest = (dataRead) => {
      if (dataRead[5] != 0x00) {
        characteristic.removeListener('data', listenerForRead);
        reject(dataRead[5]);
      }
    }

    let finish = () => {
      dataRead = [];
      setTimeout(() => {
        characteristic.removeListener('data', listenerForRead);
        resolve(true);
      }, timeout);
    }

    let listenerForRead = (data) => {
      dataRead.push(...data);
      eopPosition = dataRead.indexOf(EOP);
      dataToCheck = dataRead.slice(0);
      if (eopPosition !== dataRead.length - 1) {
        dataRead = dataRead.slice(eopPosition + 1);
      } else {
        dataRead = [];
      }
      if (eopPosition !== -1) {
        if (waitForNotification) {
          if (dataToCheck[1] % 2 == 0) {
            finish();
          } else {
            checkIsAValidRequest(dataToCheck);
          }
        } else {
          checkIsAValidRequest(dataToCheck);
          finish();
        }
      }
    };
    characteristic.on('data', listenerForRead);
    characteristic.write(Buffer.from(buff));
  });
}
```

### Supported types for payload data

Following the same process, I tried to know how to rotate the top. There are a lot of messages of this type to make the top rotating

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 237px;">![](https://cdn-images-1.medium.com/max/800/1*kfhF9iW8wI8QgqGQlq-72A.png)</div>

<figcaption class="imageCaption">90° top rotation message</figcaption>

I tried to rotate the top to ~90° and I got 32 bit of payload with no value representing a number near to 90\. That’s not completely true: “90” may be not represented as an integer! Following the documentation there are other types supported for the payload data

<div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 371px;">![](https://cdn-images-1.medium.com/max/800/1*uQBpME-SK85RQwhdl5v8KA.png)</div>

32 bits payload **0x42b23198** is very similar to a number encoded using IEEE754! Converting this value with [an online tool](https://www.h-schmidt.net/FloatConverter/IEEE754.html) I get 89.09686. 

This is the final code to rotate R2D2 top:

```js
const MSG_ROTATE = [0x0A,0x17,0x0F];


let convertDegreeToHex = (degree) => {
  var view = new DataView(new ArrayBuffer(4));
  view.setFloat32(0, degree);
  return Array
    .apply(null, { length: 4 })
    .map((_, i) => view.getUint8(i))
}


let droidAddress = 'd7:1b:52:17:7b:d6';


connectTheDroid(droidAddress).then(characteristic => {
  characteristic.subscribe(async(error) => {
    if (error) {
      console.error('Error subscribing to char.');
    } else {
      console.log("Wait for init!");
      await writePacket(characteristic, buildPacket(MSG_INIT), true, 5000);

      console.log('Rotate the droid!');
      for (let degrees = -160 ; degrees <= 180 ; degrees+=5) {
        await writePacket(
          characteristic,
          buildPacket(MSG_ROTATE, convertDegreeToHex(degrees)),
          false,
        );
      }
    }
  });
});
```

I tried to make a full rotation of the top but it’s not possible, I get error 0x07 (`data parameter invalid`, check [this link](https://sdk.sphero.com/docs/api_spec/general_api/#error-codes) for more errors).

[In the next episode](https://dev.to/astagi/reverse-engineering-sphero-r2d2-i-like-to-move-it-51ni) I’ll try to move R2D2. 

You can check [this repository](https://github.com/astagi/freer2) containing some other functions like animations and bipod/tripod transformations.


Cover image: artwork by [snowmarite](https://www.deviantart.com/snowmarite)