My work schedule can get pretty crazy! Really crazy… I thought it might be cool to come up with a way to communicate to that to folks I work with.
I built a little wifi connected RGB LED that will display whether I’m busy, free or about to be busy inside of a 3d printed case. I’ve got all of the 3d files and code here available for download.
3d files
The construction was in two parts, the 3d Files were sent to Shapeways and printed with a SLS process. (They call it ‘strong and flexible’). The topper (transparent shark piece) was ‘clear detail’. Both the case and the topper aren’t particularly well suited to a home 3d printer because of hard drop offs on the case and the topper is very small with fine detail.
Build Gallery
The wooden base was a pre-cut railing piece from home depot that I drilled holes for the case pegs to push into. I used a lazy method of putting a little paint from a paint pen on the bottom of the pegs, pushed it against the wood for a close approximation of where to drill. This method isn’t precise but in this case it worked well because the slightly imprecise hole placement put a little lateral pressure on the pegs and friction helps keep the case on the wood.
Photon Code
The brains behind the LED is a photon, wifi connected microcontroller. The code is pretty simple, it’s basically waiting for an rgb hex value from the Particle API. When it sees it, it parses it out and sets the three PWM channels appropriately. This is mostly from a quick example I found somewhere on the web.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
#include <math .h> // only for round(); #define MAX_PARAMS 3 // 3 color channels String firstArgs = "15,221,222"; // for testing, ints only String secondArgs = "120.0,121.7,222.9"; // for testing, floats int led[] = {A4,A5,A6}; // You'll need to wire the RGB-LED: int onBoardLED = D7; // This one is the built-in tiny one to the right of the USB jack int sensorReading = 0; const int knockSensor = A7; // the piezo is connected to analog pin 0 const int threshold = 2080; // threshold value to decide when the detected sound is a knock or not void setup() { /* pinMode(A1, OUTPUT); pinMode(A2, OUTPUT); pinMode(A3, OUTPUT); pinMode(A4, OUTPUT); pinMode(A5, OUTPUT); pinMode(A6, OUTPUT); pinMode(A7, OUTPUT); */ for(int i=0;i<max_params ;i++)// 3 color channels; RGB { pinMode(led[i], OUTPUT); } pinMode(onBoardLED, OUTPUT); Spark.function("changeColor", changeColorFunction); } // This routine gets called repeatedly, like once every 5-15 milliseconds. // Spark firmware interleaves background CPU activity associated with WiFi + Cloud activity with your code. // Make sure none of your code delays or blocks for too long (like more than 5 seconds), or weird things can happen. void loop() { } int changeColorFunction(String args) { int length = args.length(); char separator = ','; double allValues[MAX_PARAMS]; // Here 3: there will be 3 values for rgb int allValuesCount = 0; int from = 0; for(int i=0;i<=length;i++)// parse all parts of args between the separators { if(args.charAt(i)==separator || i == length) { String substring = args.substring(from, i); int bufSize = 1+ substring.length(); char buffer[bufSize]; substring.toCharArray(buffer, bufSize); double value = atof(buffer); bool isNumber = true; if(0.0 == value) // there maybe not a number { int inChar = args.charAt(from); if (isDigit(inChar)) { // we have a string isNumber = false; } } if(isNumber) { *(allValues + allValuesCount)=value; allValuesCount++; } // what else could be done? if(i < length) // next substring beyond separator from = i+1; } } // limit the color vaues to 3 , red, green and blue allValuesCount = (allValuesCount > MAX_PARAMS) ? MAX_PARAMS : allValuesCount; for(int i=0; i<allvaluescount ; i++) { double doubleValue = *(allValues +i); int value = round(doubleValue); // manually clamp the value to 0-255 value = (value < 0) ? 0: value; value = (value > 255) ? 255: value; // apply the value on the LED analogWrite(led[i], value) ; //Spark.publish(String(value)); } /* for(int i=0;i<30;i++)// just blink and wait { digitalWrite(onBoardLED, HIGH); delay(100); // Wait for 100mS = 1/10 second digitalWrite(onBoardLED, LOW); delay(100); // Wait for 1/10 second in off mode } */ return 1; } </allvaluescount></max_params></math> |
Color Server
On the server side, I put together a small nodeJS Script that handles colors with an API. Given a server address, it can constantly check for a color from that URL. Or it can accept arbitrary colors through GET requests.
It’s not quite fleshed out here but I thought a fun way to extend this script would be a robust way of keeping track of ‘color devices’, what their current color is, an API to set a new one and an ‘input’ source that would affect it’s color automatically.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
var http = require("http"); var request = require('request'); var Router = require('routes'); var router = Router(); var url = require('url') var handle = null; var current_color = null; handle = setInterval(checkColor, 10000); router.addRoute('/', handleHome); router.addRoute('/color/:red/:green/:blue/:time', handleColor); router.addRoute('/*', handle404); http.createServer(function (req, res) { var path = url.parse(req.url).pathname; var match = router.match(path); match.fn(req, res, match); }).listen(12415) function handleHome(req, res, match) { fs = require('fs') fs.readFile('~/webapps/rgb/index.html', 'utf8', function (err,data) { if (err) { return console.log(err); } res.statusCode = 200; res.write(data); res.end(); }); } function handle404(req, res, match) { res.statusCode = 404; res.write("404, page not found"); res.end(); } function handleColor(req, res, match) { clearInterval(handle); res.statusCode = 200; res.end(); current_color = null; console.log(match); setColor(match.params.red, match.params.green, match.params.blue); handle = setInterval(checkColor, match.params.time * 1000); } function setColor(r,g,b) { console.log("set the color!" + r + "," + g + "," + b); request.post('https://api.spark.io/v1/devices/YOUR_DEVICE_ID/changeColor', { form: { "access_token": 'YOUR_ACCESS_TOKEN', "args": r + "," + g + "," + b } }, function (error, response, body) { if (!error) { // && response.statusCode == 200) { console.log(body) } } ); } function checkColor() { request('BUSY_URL_HERE', function (error, response, body) { if (!error && response.statusCode == 200) { if(current_color != body) { console.log(body) // Show the HTML for the Google homepage. switch(body) { case "yes": setColor(255,1,1); break; case "no": setColor(1,255,1); break; case "almost": setColor(255,255,1); break; } current_color = body; } else { console.log("already set this color"); } } }); } |
Outlook Calendar (Busy/Free) Parser
This server side php file will look at a local .VFB file, and parse it out to figure out, am I busy, free or about to be busy (15 minutes or less from being busy) and returns a JSONP file. Pretty simple, the color server will look for this file at it’s URL and use it to determine color!
Basically, have a look at this link about how to publish your free/busy schedule to an FTP location. Then, put this php script somewhere that can see the file locally.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
< ?php $busyonly = $_REQUEST['busyonly']; // Output one line until end-of-file $myfile = fopen("tom.vfb", "r") or die("Unable to open file!"); $busy = false; date_default_timezone_set('America/New_York'); $now = date(time()); $busy = "no"; $json = 'jsonCallback({"events": ['; $myfile = fopen("tom.vfb", "r") or die("Unable to open file!"); while(!feof($myfile)) { $line = fgets($myfile); if( startswith($line, "FREEBUSY:") ){ $start = strtotime(substr($line, 9, 16)); $startminus = $start - 900; $end = strtotime(substr($line, 26, 16)); if($now > $startminus && $now < $end) { $busy = "almost"; } //date('l dS \o\f F Y h:i:s A', $start) } } fclose($myfile); $myfile = fopen("tom.vfb", "r") or die("Unable to open file!"); while(!feof($myfile)) { $line = fgets($myfile); if( startswith($line, "FREEBUSY:") ){ $start = strtotime(substr($line, 9, 16)); $end = strtotime(substr($line, 26, 16)); if($now > $start && $now < $end) { $busy = "yes"; } //date('l dS \o\f F Y h:i:s A', $start) $json .= '{"start":"' . date('Y-m-d H:i:s', $start) . '","end":"' . date('Y-m-d H:i:s', $end) . '"},'; } } fclose($myfile); $json = rtrim($json, ","); //$busystring = ($busy) ? 'true' : 'false'; $busystring = $busy; $json .= '],"busy": "' . $busystring . '"});'; if($busyonly == "true") { echo $busystring; } else { header('Content-Type: application/javascript'); echo $json; } function startsWith($haystack, $needle) { $length = strlen($needle); return (substr($haystack, 0, $length) === $needle); } ?> |
π