User Tools

Site Tools


Oak: Using a servo

tl;dr: The Arduino Servo library seems to work just fine.

Example: NextBus meter

The San Francisco public transit system (MUNI) makes realtime prediction data available through NextBus. For this example, we will mount a small servo with an arrow connected to it in a box, making a prediction display suitable for the home.


qty. part
1 Oak (with soldered headers)
1 small servo motor
3 jumper wires


Oak VCC → servo red
Oak Gnd → servo black
Oak P5 → servo white

NOTE: I chose to use a servo small enough to be safely powered by the 3.3v provided by the Oak. If you need a larger servo, you will need a few more components (like an optoisolator or a logic-level converter) to allow the 3.3v Oak to control a servo which requires higher voltage.

Oak code

We will code with the Arduino environment (because that is what's available today).

#include <Servo.h>

 * A servo based count-down display for transit times.

#define SERVO_PIN 5
#define SERVO_MIN 700
#define SERVO_MAX 2400

#define MINUTE(x) (x * 60 * 1000)

#define POS_GONE 180
#define POS_3_MINUTE 140
#define POS_6_MINUTE 30
#define POS_9_MINUTE 6

Servo s;
int servo_pos = POS_GONE;

long end_time = 0;

void setup() {
  Particle.function("countdown", set_remaining_ms);

void loop() {

int set_remaining_ms(String arg) {
  long remaining_ms = arg.toInt();
  end_time = millis() + remaining_ms;
  return 0;  // success (-1 means failure to Particle).

void update_servo_pos() {
  long remaining_ms = end_time - millis();
  if (remaining_ms < 0) {
    remaining_ms = 0;

  // Do linear interpolations between labels on the physical device.
  if (remaining_ms > MINUTE(9)) {
    servo_pos = POS_GONE;
  } else if (remaining_ms > MINUTE(6)) {
    servo_pos = interpolate(remaining_ms, MINUTE(6), MINUTE(9), POS_6_MINUTE, POS_9_MINUTE);
  } else if (remaining_ms > MINUTE(3)) {
    servo_pos = interpolate(remaining_ms, MINUTE(3), MINUTE(6), POS_3_MINUTE, POS_6_MINUTE);
  } else {
    servo_pos = interpolate(remaining_ms, 0, MINUTE(3), POS_GONE, POS_3_MINUTE);

  // Move the servo for 1s, then detach to avoid a prolonged buzzing noise.

// Return a linear interpolation between outMin and outMax, given the input data and bounds.
long interpolate(long input, long inMin, long inMax, long outMin, long outMax) {
  long i = max(min(input, inMax), inMin);
  return outMin + (i - inMin) * (outMax - outMin) / (inMax - inMin);

Updating our meter

In order for this meter to be useful, we need to call it every once in a while with updated data.

export DEVICE_ID="<your_device_id>"
export PARTICLE_ACCESS_TOKEN="<your_access_token>"
curl${DEVICE_ID}/countdown \
  -d access_token="${PARTICLE_ACCESS_TOKEN}" \
  -d "args=360000"

It is left as an exercise to the reader to figure out how to call this periodically. One method is to use a cron task on Google AppEngine to check with NextBus and update the device.

oak/tutorials/servo.txt · Last modified: 2016/04/10 18:12 by sir_buckyball