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.