Pivotal Tracker, Phidgets, and Nerf Guns

javascript product_management 
15 Feb 2012
↞ See all posts



Update:

It seems that the good folks on Hacker News also like the internet and nerf guns (who knew!?). There is a growing collection of other folk’s hacks regarding testing, project management, and foam weaponry **


In previous posts, I have talked about how to use node.js to talk to a phidget board. I have even talked about how to run node.js ON a phidget board. Now you may be wondering what kind of projects you might do with a small embedded computer that has a solid http I/O stack.

Here is a "suggestion".

At ModCloth we used pivotal tracker, an awesome agile project management tool from the folks over at Pivotal Labs. I have fallen in love with the tool for a number of reasons, but the most important reason for this article is how they really "get" the modern web-development workflow, and the tool fits nicely. They have tons of third-party integrations (Jira, GitHub, ect) and a great API.

As a Product Manager with a remote team, I was always thinking about ways for our team to keep in touch better. While I was working on the phidget library, I thought I might use it and the tracker API to ring a small gong (or something similar) whenever a story was accepted as a proxy for me being able to quickly yell "thanks" (a story is a "small unit of work" for the uninitiated, like an item on your to-do list). I never found a gong, but I did find a motorized nerf gun… and you can imagine where this is going :D Unfortunately, I didn’t get this project done in time before I left ModCloth, but I’m sure that I’ll find a use for it in the future!

The first step was to hack the gun so that I could control it "digitally" without needing to squeeze the trigger. As it turns out, children’s toys are REALLY well made these days, and it took about an hour to pry everything apart. I forgot to take detailed photos, but what I ended up doing was soldering 2 new contact wires to the bridge on the on/off switch, and the motor itself. This was done in series with the trigger’s gate so it still worked and would complete the same circuit. I drilled a small hole in the base of the handle for the new wires to come out


The gun used the trigger to transfer the current rather than be a relay for the motor, and this meant that my new cables would be carrying a significant amount of amperage from its 6 "D" Batteries. I couldn’t just complete the circuit with the phidget board (it would fry the computer, or at least be grounded out). Luckily, I had a 10-amp relay board which worked with the phidget board.

Now with everything wired up, I needed a way to talk to the pivotal tracker API. Wizcorp had made a basic polling node.js wrapper the Tracker V3 API which did what I needed. Thanks WizCorp!

The application logic is simple:

  • Connect to the Phidget board
  • Authenticate with Pivotal Tacker
  • Poll your project every so often and look for story state-changes
  • "fire" if a story you own was rejected.

You can also use a similar mechanism for story acceptance. The next steps in this project are obviously to pour your engineers a drink automatically when a story is accepted :D

Here is the application (written for node v0.6.x). This code is pretty terrible (as I limited myself to 4 hours for this project, including the construction), and lacks any failure / error handling, but hopefully it is easy enough to understand.

1var phidgets = require("phidgets").phidgets; 2var pivotal = require("pivotal"); 3 4// curl -d username=$USERNAME -d password=$PASSWORD -X POST https://www.pivotaltracker.com/services/v3/tokens/active 5var apiToken = "XXXXXXXXXXXXXXXXXXX"; 6var projectID = 12345; 7var OwnedBy = "Evan Tahler"; 8var checkTimerMS = 500; 9var newer_than_version = 0; 10var PhidgetHost = "phidgetsbc.local"; 11var phidgetsReady = false; 12var phidgetData = {}; 13 14var init = function (next) { 15 phidgets.on("data", function (type, id, value) { 16 if (phidgetsReady) { 17 console.log("phidgets >> " + type + " #" + id + " now at @ " + value); 18 } 19 }); 20 21 phidgets.on("log", function (data) { 22 if (typeof data != "object") { 23 console.log("phidgets log >> " + data); 24 } 25 }); 26 27 phidgets.on("error", function (e) { 28 console.log("phidgets error >> " + e); 29 }); 30 31 phidgets.connect( 32 { 33 host: PhidgetHost, 34 }, 35 function (p) { 36 phidgetsReady = true; 37 phidgets.setOutput(0, false); 38 phidgets.setOutput(1, false); 39 phidgetData = p; 40 pivotal.useToken(apiToken); 41 pivotal.getProjects(function (err, resp) { 42 if (err != null) { 43 console.log(err.desc); 44 process.exit(); 45 } else { 46 var found = false; 47 for (var i in resp.project) { 48 var project = resp.project[i]; 49 if (project.id == projectID) { 50 found = true; 51 break; 52 } 53 } 54 if (found == false) { 55 console.log( 56 "This user does not have access to project #" + projectID, 57 ); 58 process.exit(); 59 } else { 60 checkForTrackerStatus(); 61 next(); 62 } 63 } 64 }); 65 }, 66 ); 67}; 68 69var checkForTrackerStatus = function () { 70 var filters = { 71 project: projectID, 72 limit: 1, 73 newer_than_version: newer_than_version, 74 }; 75 pivotal.getActivities(filters, function (err, activities) { 76 if (err != null) { 77 console.log(err.desc); 78 process.exit(); 79 } else { 80 if (activities.activity != null) { 81 if ( 82 activities.activity.event_type == "story_update" && 83 newer_than_version > 0 84 ) { 85 pivotal.getStory( 86 projectID, 87 activities.activity.stories.story.id, 88 function (err, story) { 89 if (err != null) { 90 console.log(err.desc); 91 process.exit(); 92 } else { 93 if (story.owned_by == OwnedBy) { 94 if (story.current_state == "rejected") { 95 fireGun(); 96 } else if (story.current_state == "accepted") { 97 happyness(); 98 } 99 } 100 } 101 }, 102 ); 103 } 104 newer_than_version = activities.activity.version; 105 } 106 setTimeout(checkForTrackerStatus, checkTimerMS); 107 } 108 }); 109}; 110 111var fireGun = function (next) { 112 console.log("BANG!"); 113 phidgets.setOutput(0, true); 114 setTimeout(phidgets.setOutput, 1000, 0, false); 115 if (typeof next == "function") { 116 next(); 117 } 118 100; 119}; 120 121var happyness = function (next) { 122 console.log("Yay!"); 123 phidgets.setOutput(1, true); 124 setTimeout(phidgets.setOutput, 1000, 1, false); 125 if (typeof next == "function") { 126 next(); 127 } 128}; 129 130init(function () { 131 console.log("READY!"); 132 111; 133});

And here is a video of everything working

Enjoy!

Hi, I'm Evan

I write about Technology, Software, and Startups. I use my Product Management, Software Engineering, and Leadership skills to build teams that create world-class digital products.

Get in touch