This is an old revision of the document!
tl;dr: The Arduino Servo library seems to work just fine.
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.
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() {
update_servo_pos();
delay(10000);
}
int set_remaining_ms(String arg) {
long remaining_ms = arg.toInt();
end_time = millis() + remaining_ms;
update_servo_pos();
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.
s.attach(SERVO_PIN, SERVO_MIN, SERVO_MAX);
s.write(servo_pos);
delay(1000);
s.detach();
}
// 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);
}
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 https://api.particle.io/v1/devices/${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.