===== Oak: Using a servo ===== **tl;dr:** The [[https://www.arduino.cc/en/Reference/Servo|Arduino Servo]] library seems to work just fine. ==== Example: NextBus meter ==== The San Francisco public transit system ([[https://www.sfmta.com/|MUNI]]) makes realtime prediction data available through [[http://www.nextbus.com/|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. {{:oak:tutorials:oak_servo_build.jpg|}} === Components === ^qty. ^ part ^ | 1 | Oak (with soldered headers) | | 1 | small servo motor | | 3 | jumper wires | === Wiring === Oak VCC -> servo red\\ Oak Gnd -> servo black\\ Oak P5 -> servo white {{:oak:tutorials:oak_servo_wiring.png|}} 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 [[https://en.wikipedia.org/wiki/Opto-isolator|optoisolator]] or a [[https://www.sparkfun.com/products/12009|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 /* * 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); } === 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="" export PARTICLE_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 [[https://cloud.google.com/appengine/|Google AppEngine]] to check with NextBus and update the device.