A Quick and Dirty Guide to Getting Started with the Pinoccio API.

In case I haven't told you, Pinoccio is an Ardunio-esque micro-controller board, that ships 'with wings': Each board has built-in mesh networking, and the kit comes with a Wifi board.

What's particularly unique about Pinoccio is that they're not leaving it just at that. They're providing a simple scripting language (based on the phenomenally cool Bitlash project), as well as an associated web-service and API provider (called HQ).

In Pinoccio parlance, a single board is called scout and a group of scouts connected by a mesh are called a troop. Cute eh?

Today, in the spirit of taking time off from thesis writing, and getting my head away from Transactional Memory, I figured I'd spend some time digging into the API they provide. I thought I'd write up some example code here, from my perspective (a relative novice to web-services entirely).

We're going to create two scripts: issue_command.py and sync_stream.py. The first will allow issuing a scout-script command through the API, and the second, will allow you to watch the API's sync stream, which watches all the events which hit HQ.

Before those though, I'll show you api_explorer.py which will allow exploring the read only elements of the API.

Authentication:

First step is to login and get an authenticated token, which is a string the API gives you as a temporary password which you attach to all your subsequent requests. This ensures that you, and only you, can control your Pinoccio boards.

For the purposes of this blog post, I'm going to skip the right way to do this, in order to get up and running faster, by showing you the dirty way, straight from the Pinoccio API Documentation.

On the command line, you can ask the API for a token using curl:

curl -X POST -v --data '{"email":"youremail","password":"boats"}' https://api.pinocc.io/v1/login

Take note of the reply:

{"data":{"token":"ahv0onrd0fiio8813llqjo5sb1","account":19}}

You'll need the token shortly.

HTTP Requests

I've been quite impressed with the python library Requests, which makes it reasonably easy to communicate with a service, bidirectionally.

You can install it, if you don't already have it, with pip:

pip install requests

api_explorer.py

While you could do this directly with curl, it's instructive to see how requests are constructed before going on further. To that end: here's api_explorer.py

import requests
import sys

API_Root="https://api.pinocc.io/v1/"
token = "your token here!"
auth_arg = {"token" : token} 

def get(url, **kwargs): 
    """
    Issue a get request to the Pinnocio API, authenticated by the token
    """
    if "data" in kwargs: 
        kwargs["data"] = dict(kwargs["data"].items() + auth_arg.items())
    else:
        kwargs["data"] = auth_arg
    return requests.get(API_Root + url, **kwargs)


if __name__  == '__main__': 
    if len(sys.argv) > 1: 
        r = get(sys.argv[1])
        print r.status_code
        print r.text
    else:
        print "Need a command" 

This simple script allows you to provide an endpoint on the command line, and print out the API's response. The function get above constructs the appropriate URL given the endpoint name, and makes sure that we have the token on the request. The kwargs is so that we can provide arguments to the call, which we will need soon...

troops is an interesting endpoint, as it will list the troops associated with the account you used to generate the token.

SolidALumbook:matt_pinoccio mgaudet$ python api_explore.py troops
200
{"data":[{"account":1112,"token":"<token data-preserve-html-node="true" elided>","name":"Scooters","id":1,"online":true}]}

Reading this JSON output, you can see I have one troop, "Scooters", with id 1. So, we can now ask the API about the scouts in that troop:

SolidALumbook:matt_pinoccio mgaudet$ python api_explore.py 1/scouts
200
{"data":[{"name":"Razer","updated":"1397008385993","id":1,"time":1397008385992},{"name":"Rascal","updated":"1397008855488","id":2,"time":1397008855488}]}

So I have two scouts in the troop: Razer (id 1) and Rascal (id 2).

These ID's are useful when talking about issuing commands.

issue_command.py

According to the API documents, to issue a ScoutScript command to a scout, you need to know its troop id and scout id. After which, you can issue a command with HTTP GET on the end point {troop id}/{scout id}/command. Using api_explorer.py we found out that Razer is scout 1 in troop 1. So if we want to issue a command to Razer, we can make a request to the 1/1/command endpoint.

Here's a script which allows you to specify a troop id, scout id, and a ScoutScript command. As you can see it's largely identical to api_explorer.py, except that "command" is provided as an argument to the get-request.

import requests
import sys

API_Root="https://api.pinocc.io/v1/"
token = "your token here"
auth_arg = {"token" : token} 

def get_token(username, password): 
    pass

def get(url, **kwargs): 
    if "data" in kwargs: 
        kwargs["data"] = dict(kwargs["data"].items() + auth_arg.items())
    else:
        kwargs["data"] = auth_arg
    return requests.get(API_Root + url, **kwargs)

if __name__  == '__main__': 
    if len(sys.argv) > 3: 
        command = " ".join(sys.argv[3:])
        r = get("{}/{}/command/".format(sys.argv[1],sys.argv[2]),data={"command": command}) 
        print r.status_code
        print r.text
    else:
        print "Need a troop, a scout, and a command" 

So we can turn on the led of Razer:

SolidALumbook:matt_pinoccio mgaudet$ python issue_command.py 1 1 led.on
200
{"data":{"type":"reply","from":1,"id":"t44947","end":true,"reply":"","_cid":13,"basetime":469,"commandsPending":0,"messagesQueued":0,"account":"1112","tid":"1","_t":1397421971331,"output":"","result":false}}

Or peek at the wifi status:

SolidALumbook:matt_pinoccio mgaudet$ python issue_command.py 1 1 "wifi.report()"
200
{"data":{"type":"reply","from":1,"id":"t44948","end":true,"reply":"{\"type\":\"wifi\",\"connected\":true,\"hq\":true}\r\n","_cid":14,"basetime":400,"commandsPending":0,"messagesQueued":0,"account":"1112","tid":"1","_t":1397421998605,"output":"{\"type\":\"wifi\",\"connected\":true,\"hq\":true}\r\n","result":false}}

sync_stream.py

One of the interesting elements of the Pinnocio API is that the scouts themselves will autonomously report information back to HQ. This data can be accessed through the API from the sync endpoint.

Here's a simple script which starts listening to the sync stream until killed:

import requests
import sys
import json

API_Root="https://api.pinocc.io/v1/"
token = "your API key here!"
auth_arg = {"token" : token} 

def get_token(username, password): 
    pass

def get(url, **kwargs): 
    if "data" in kwargs: 
        kwargs["data"] = dict(kwargs["data"].items() + auth_arg.items())
    else:
        kwargs["data"] = auth_arg
    return requests.get(API_Root + url, **kwargs)

if __name__  == '__main__': 
    received = 0
    r = get("sync",stream=True)
    print r.status_code

    # Chunk size required to avoid buffering output too long!
    for line in r.iter_lines(chunk_size=1): 
        received = received + 1 
        if line: 
            print "({}) {}".format(received, json.loads(line))
        else:
            print "({}) keepalive".format(received) 

Here's what it looks like in action:

SolidALumbook:matt_pinoccio mgaudet$ python sync_stream.py 
200
(1) {u'data': {u'account': u'1112', u'type': u'connection', u'troop': u'1', u'value': {u'status': u'online', u'ip': u'<ip data-preserve-html-node="true" elided>'}, u'time': 1397420589928}}
(2) {u'data': {u'account': u'1112', u'value': {u'state': [-1, -1, -1, -1, -1, -1, -1, -1], u'_t': 1397420590333.001, u'type': u'analog', u'mode': [-1, -1, -1, -1, -1, -1, -1, -1]}, u'scout': u'1', u'troop': u'1', u'time': 1397420590333.001, u'type': u'analog'}}
(3) {u'data': {u'account': u'1112', u'value': {u'available': 1, u'_t': 1397420590149.001, u'scout': 1, u'type': u'available'}, u'scout': u'1', u'troop': u'1', u'time': 1397420590149.001, u'type': u'available'}}
(4) {u'data': {u'account': u'1112', u'value': {u'available': 1, u'_t': 1397421532918.001, u'scout': 2, u'type': u'available'}, u'scout': u'2', u'troop': u'1', u'time': 1397421532918.001, u'type': u'available'}}
(5) {u'data': {u'account': u'1112', u'value': {u'list': [], u'_t': 1397420590332.001, u'type': u'backpacks'}, u'scout': u'1', u'troop': u'1', u'time': 1397420590332.001, u'type': u'backpacks'}}
(6) {u'data': {u'account': u'1112', u'value': {u'state': [-1, -1, -1, -1, -1, -1, -1], u'_t': 1397420590333, u'type': u'digital', u'mode': [-1, -1, -1, -1, -2, -2, -2]}, u'scout': u'1', u'troop': u'1', u'time': 1397420590333, u'type': u'digital'}}
(7) {u'data': {u'account': u'1112', u'value': {u'led': [0, 255, 0], u'torch': [0, 255, 0], u'type': u'led', u'_t': 1397421971069}, u'scout': u'1', u'troop': u'1', u'time': 1397421971069, u'type': u'led'}}
(8) {u'data': {u'account': u'1112', u'value': {u'led': [0, 34, 255], u'torch': [0, 34, 255], u'type': u'led', u'_t': 1397421537370}, u'scout': u'2', u'troop': u'1', u'time': 1397421537370, u'type': u'led'}}
(9) {u'data': {u'account': u'1112', u'value': {u'scoutid': 1, u'power': u'3.5 dBm', u'troopid': 1, u'_t': 1397420590334, u'rate': u'250 kb/s', u'routes': 0, u'type': u'mesh', u'channel': 20}, u'scout': u'1', u'troop': u'1', u'time': 1397420590334, u'type': u'mesh'}}
(10) {u'data': {u'account': u'1112', u'value': {u'vcc': True, u'battery': 73, u'_t': 1397422079321, u'voltage': 392, u'type': u'power', u'charging': False}, u'scout': u'1', u'troop': u'1', u'time': 1397422079321, u'type': u'power'}}

Download code:

All this code has been uploaded to github here, as PinoccioApiTools.

Conclusion

Pinoccio is providing some very powerful tools for building some interesting projects, minimizing a lot of pain points that previous solutions suffered from. I'm really excited by this.