Do More With Sphero's BB-8 - Part One

Posted on Jan 21, 2016 in Sundry
james portrait
James Jacoby
Chief Technology Officer, Founder
 
BB-8 Project - Part 1

We were first in line to get our hands on the Sphero BB-8 toy when it launched last year. Since then, we've been smitten with its cuteness and high-tech bluetooth coolness. After you've driven it around a little, watched it patrol, and played with the adorable puppeteering features, some people might feel quite content with their purchase.

Not me. Owning this toy has only made me want a real BB-8. You know- one that hangs out with you, providing comic relief throughout the work day. I don't have time for an insanely complex project, so that dream is a ways out. 

However, I think we can get more enjoyment out of the toy that already exists by making it smarter.

Here's some goals I have in mind:

  • It would be great if you could enjoy BB-8 without constantly running the app on a phone
  • BB-8's personality would be even more endearing if he had more autonomy
  • Let's hook this thing up to the internet so it can receive input from anything

How it Works

There's a lot going on behind the scenes in this demo. Here's breakdown of the parts:

Node.js App

The app running on my laptop is a small Node app that uses a community variant of the Sphero SDK called Node Sphero Pwn. The SDK handles all the low-level bluetooth communication with BB-8. The package also provides a macro language for controlling the motors and lights, allowing us to design our own animations. In part two, this code will be moved to a Raspberry Pi Zero that lives in BB-8's charging base.

Here's a Gist of the main source file if you're curious.

var sphero = require('sphero-pwn'),
    exec = require('exec'),
    macros = require('sphero-pwn-macros'),
    keypress = require("keypress");

var orb = null,
    id = 'ble://##:##:##:##:##:##';

var awsIot = require('aws-iot-device-sdk');

// MACROS

var actions = {};

var src = "motor 2 80 2 80\n" +
          "delay 50\n" +
          "motor 1 80 1 80\n" +
          "delay 50\n" +
          "motor 2 80 2 80\n" +
          "delay 50\n" +
          "motor 1 80 1 80\n" +
          "delay 50\n" +
          "motor 0 0 0 0\n";

actions['fast_nod'] = macros.compile(src);

src = "rgb 0 255 0\n" +
      "delay 200\n" +
      "rgb 0 0 255\n" +
      "delay 200\n" +
      "rgb 255 0 0\n" +
      "delay 200\n" +
      "rgb 0 0 0\n";

actions['flash'] = macros.compile(src);

var src = "motor 2 80 1 80\n" +
          "delay 200\n" +
          "motor 0 0 0 0\n" +
          "delay 1000\n" +
          "motor 1 80 2 80\n" +
          "delay 200\n" +
          "motor 0 0 0 0\n";

actions['look_around'] = macros.compile(src);

// HELPERS

var playSound = function(name) {
  exec('afplay "sounds/bb8 - ' + name + '.mp3"', function() {});
};

var playAction = function(name) {
  var macro = actions[name];
  orb.loadMacro(0xFF, new Buffer(macro.bytes)).
    then(function() {
      orb.runMacro(0xFF);
    });
};

var onKeyPress = function(ch, key) {
  console.log('pressed: ' + key.name);

  if (key.ctrl && key.name === "c") {
    process.stdin.pause();
    process.exit();
  }

  if (key.name === "y") {
    animateYes();
  }

  if (key.name === "f") {
    flashColors();
  }
}

var handleMessage = function(topic, action) {
  ensureConnected().then(function(response) {
    switch (action) {
      case 'flash':
        return flashColors();
      case 'yes':
        return animateYes();
      case 'look':
        return lookAround();
    }
  });
};

var ensureConnected = function() {
  // This is an area of great frustration
  // For now, closing and re-opening connection every time
  orb.channel().close();

  return sphero.Discovery.findChannel(id).then(function(channel) {

    orb = new sphero.Robot(channel);
  });
};

// ACTIONS

var animateYes = function() {
  playSound('be he do');
  playAction('fast_nod');
};

var lookAround = function() {
  playSound('up down');
  playAction('look_around');
};

var flashColors = function() {
  playSound('beep beep');
  playAction('flash');
};

// CONNECT!

console.log('start');

// Connect to IoT on AWS

var device = awsIot.device({
   keyPath: 'private.pem.key',
  certPath: 'certificate.pem.crt',
    caPath: 'root-CA.crt',
  clientId: '###',
    region: 'us-west-2'
});

device
  .on('connect', function(foo) {
    console.log('connected to IoT');
    device.subscribe('bb8');
  });

device
  .on('message', function(topic, payload) {
    var action = payload.toString();

    console.log('message', topic, action);

    handleMessage(topic, action);
  });

sphero.Discovery.findChannel(id).then(function(channel) {
  // new orb
  orb = new sphero.Robot(channel);
  console.log('connected to bb8');
});

Amazon IoT

Amazon's Internet of Things service allows WiFi connected devices to communicate securely with cloud applications. Devices can easy publish, subscribe, and respond to state change requests- all with a very small code footprint.

Slack

Slack seemed like a fun integration to start with. Everyone in the office uses it and it's super simple to integrate with other services. In the demo, anyone on our Slack team can send /bb8 commands and BB-8 will respond, wherever it is.

Amazon Lambda

For this proof of concept, I needed something to act as the public interface for BB-8. Using Amazon Lambda, I quickly created an API backed by a single file. By using Lambda, I didn't have to think about setting up servers, deployment, or even source control. My barebones API accepts commands from Slack and communicates them to the IoT service. In part two, this will be replaced by a proper web app.

That's it for now. In part two, we'll show off the enhanced web app and publish the plans and code so you can make your own!

What Distance Running Teaches Us About Web Design Unpacking the (HTML5) Holiday Tree