Sunday, 13 March 2016

Integrating the Particle Internet Button with Github

When I bought my photon I also bought an Internet Button at the same time. I hadn't come up with a good use for it until recently, when I thought it would make a neat desk notification device for my work Github repositories.

With 4 developers in my team and 10 or so repositories we regularly commit to, I found I was not keeping on top of the pull requests or noticing when people commented on mine. To make this activity more noticeable so I could act on it more quickly I used my Internet Button to flash in different colours according to different triggers.



              

Push to master triggers a green notification, Pull requests trigger purple notifications

Moving Parts


Github  -> AWS API Gateway  -> AWS Lambda  -> Particle Cloud  -> Internet Button


The event triggers come from web hooks you can attach to your repositories in Github. As we've seen in a previous blog post we can export cloud functions to trigger behaviour on your photon. We will use one of these functions as the target of the Github web hook. However we cannot call our function directly as we need to provide the access token to our particle account along with the request payload and the web hooks do not allow you to add custom data so we will need a middle layer. I chose AWS to host this middle layer.

There are many ways to skin this cat but as I have already been playing around in AWS recently I thought I would use this as a learning opportunity to get more familiar with different services available. AWS allows you to quickly set up an API endpoint using the API Gateway and attach it to a back end lambda function written in either Java, Python or NodeJs. Using this setup I created a new endpoint for the web hook to POST to and then a Python lambda to take the request from github and translate it into a payload to send to my particle cloud function.

So unfortunately we have quite a few moving parts to get this working but each part is relatively easy to understand. Let's work backwards from the Internet Button to Github to see how the parts fit together.

You can download all the code you need from here.


1. The Internet Button


The program to flash onto the Button consists of the 3 files main.cpp, InternetButton.h and InternetButton.cpp in the root directory of the project. The InternetButton library originally comes from here but this particular version is my personal fork which has a new function to produce a spinning notification pattern.

The key bit of code to look at here is the exported cloud function:

 Rgb getColour(String type) {  
   if(type == "PullRequest") {  
     // purple  
     return {255,0,255};  
   } else if(type == "Push") {  
     // green  
     return {0, 255, 0};  
   } else {  
     return {0,0,0};  
   }  
 }  

 int notification(String type) {  
   Rgb colour = getColour(type);  
   for(int i=0 ; i<8 ; i++) {  
     b.spin(colour.r, colour.g, colour.b, 100);  
   }  
   b.allLedsOff();  
   return 0;  
 }  

Here we are taking in a String that will decide what colour notification we want, calling spin 8 times and then turning all the LEDs off again. The notification types accepted here are 'PullRequest' and 'Push', we'll see where they come from in a minute. Take a look in InternetButton.cpp for the implementation of spin. It triggers your chosen colour to light up each LED in turn for a single revolution of the disc. The number you pass into spin is the delay between LEDs so if you want your spin to be faster then enter a smaller delay. Likewise if you want to change the length of the notification, change the number of spins from 8 to whatever you want.


2. Particle Cloud

In order to make our function available in the Particle Cloud all we need to do is ensure our Button is switched on and connected to the internet. When the program starts it exports to function into the cloud with:

 Particle.function("notify", notification);  

Easy peasy.


3. AWS Lambda

Our cloud function will now run if we make a POST request to the appropriate URL with the correct arguments.

E.g.

   $ curl https://api.particle.io/v1/devices/12341234/notify -d access_token=12341234 -d "args=PullRequest"   

We now need a small program that will make this request for us and inject the arguments. This program needs to be hosted somewhere on the internet to make sure it is always available and running. AWS lambdas are a good way of doing this as it allows you to run a small chunk of code off a trigger without having to worry about any infrastructure.

We will create a lambda with the contents of call_particle.py. I used this tutorial to help, which also covers the API Gateway we will see in the next section.

The python handler receives an event object containing the metadata sent from Github. From this object we can work out whether the event was a push or a pull request. If it was a push we only continue processing if it was a push to master as we are not interested in other branches. Once we know what our notification type should be we create a new POST request and send our particle access token along with our function argument off to the Particle Cloud API.

If you are not committing your code somewhere public you can just include your particle access token and device ID directly in the python code instead of reading them out of the event. However as I wanted to make this code public I have taken steps to inject my data in the next step.


4. AWS API Gateway

Now we have our lambda that will call our particle function, we just need a way of exposing it on the internet so it can be accessed by Github. We are going to do this using the API Gateway. Continue following the tutorial from the previous step to create a new resource with a POST method.

This is what my setup looked like:


I added some extra config in the Integration Request section to inject my particle access token and device ID into the request before it hits the lambda. If you want to do the same, add a new mapping template of type 'application/json' with the following content:

 {  
   "particle_access_token" : "your_access_token",  
   "particle_device_id" : "your_device_id",  
   "body" : $input.json('$')  
 }  

This will perform a transformation of the request before it is passed to the lambda via the event object. The original payload will be nested inside the 'body' value and 2 new keys will be added for the particle credentials. If you do not want to perform this mapping step you will need to modify the python script to remove the 'body' nesting as the original request will now be at the root of the event object.

Follow through the tutorial until you have deployed your endpoint to a stage to give you an invoke URL. We're almost done!


5. Github web hook

We will now attach a web hook to our repository to make a POST request to our new endpoint when someone pushes or makes a pull request.
  • Go to your repository and open the Settings
  • Click on 'Webhooks & Services'
  • Click 'Add webhook'
  • Enter your invoke URL from the API Gateway along with the full path to your resource.
          e.g. 
            https://something.execute-api.us-west-2.amazonaws.com/myStage/particle/internetbutton        
  • Under 'which events would you like to trigger this webhook' choose 'Let me select individual events'
  • Select 'Push', 'Pull request', 'Pull request review comment' and 'Issue comment'
  • Save your webhook
Underneath your webhook configuration is a section called 'Recent Deliveries'. This will show you the last few requests that were sent to your endpoint. This is really useful for seeing the exact structure of the payload so you know how to pick out the relevant data in your lambda. You can also resend particular payloads for faster turnaround time when testing.


We're done!

Plug in your Internet Button and push some code to the master branch of your repository. The Button should do a green spin. Commit to a branch and create a pull request for a purple spin. Any comments on the pull request will also trigger a purple spin. Conversation comments are a separate event to comments on the diff which is why we included 'Issue Comment' in our trigger configuration.

Have a play and add more notification types by attaching more triggers, updating the event_type mapping in the lambda and choosing a colour mapping in the particle code.