Playing with the opendevicelab.com API

How about promoting the ODL movement by showing off some worldwide statistics?opendevicelab.com has a simple API which returns all data given by the participating ODLs. To show that we are part of a greater whole, we decided to fetch some of this data and display it on our site.

Getting the data and displaying it on our page is fairly simple, not the least because our site is driven by (passion and) Kirby, a wonderful small but powerfull file based content management system perfect for those who are not intimidated by the prospect of writing HTML and PHP code.

1) Fetching the data

The API is responding at the URI api.opendevicelab.com and returns JSON formatted data. The returned JSON is pretty straight forward: Each ODL is a object in the top level array and has several entries.
For example, here's the part describing our ODL:


{
    "id": 200,
    "name": "Open Device Lab Frankfurt",
    "date": 1356346805,
    "open": 1,
    "closed": 0,
    "rating": 5,
    "description": "",
    "type": "resident",
    "status": "just_established",
    "loc": {
        "city": "Frankfurt am Main",
        "organization": "BNT.DE Gesellschaft für interaktive Medien mbH",
        "street_adress": "Löwengasse 27E",
        "state": "Hessen",
        "zip": "60385",
        "country": "Germany",
        "latlng": {
            "lat": 50.12867,
            "lng": 8.71376
        }
    },
    "number_of_devices": 20,
    "brands_available": [
        "Apple",
        "BlackBerry",
        "Dell",
        "Google",
        "HTC",
        "Huawei",
        "Nextbook",
        "Nokia",
        "RIM",
        "Samsung"
    ],
    "urls": [
        {
            "url": "http://frankfurt.opendevicelab.net/",
            "type": "Website"
        },
        {
            "url": "http://twitter.com/odl_ffm",
            "type": "Twitter"
        },
        {
            "url": "https://www.facebook.com/FrankfurtOpenDeviceLab",
            "type": "Facebook"
        },
        {
            "url": "https://plus.google.com/104330008144213271171",
            "type": "GooglePlus"
        }
    ],
    "comments": [
        {
            "author": "Markus Spannknebel",
            "gravatar": "http://www.gravatar.com/avatar/1284e097fcfde7e2c4ce848111986dee",
            "date": 1375799662,
            "content": "Good device selection and very nice people.",
            "openclosed": "open",
            "rating": 5
        }
    ]
}

Similar data is in there for every other ODL listed on opendevicelab.com.

Since we wanted to display some more general data, like the total number of devices available in all ODLs, we need to play with this data-set, and to do this, we first need a local copy of it.
We could have build some Javascript-driven AJAX thingie which polls the API in real time and places it on our site, but we thought it better to make it server side, fetching the data only thrice a day to limit the load on the opendevicelab.com server.
I'm sure there are plenty of ways to do this, and our way is relatively quick and presumely dirty as well, but that's how we roll for now. :-)

To get a local copy of the JSON data, we build a small shell-script which pulls the data via wget and writes it into a text file on our server:


#!/bin/bash
wget http://api.opendevicelab.com -O ./odlapi.json

Pretty and simple. This script is called by a cron job and lives in a directory readable by the webserver but not accessible from the web, in which we now have a text file named 'odlapi.json'.

2) Let's make a simple Kirby plugin

Kirby has a nice plugin architecture; every PHP file you'll put into the site/plugin directory will get parsed on runtime and so you can access the data you'd like to have in the site's snippets or templates.

So all we need to do is:

  • parse the json to something php understands
  • loop over the data and collect the bits of data we're interested in
  • return these in a variable for usage in the template

Again, pretty straight-forward (better version of this see upddate below):


    function odlapi(){
        $path_to_json = '/opendevicelab.net/ffm/cron/';
        $json_file_name = 'odlapi.json';
        $json = $path_to_json.$json_file_name;
        if( is_file( $json ) ){
            $tmp = file_get_contents( $json );
            $odls = json_decode( $tmp,true );
            /* number of manufactures
             * number of devices
             * number of countries
             */
            $odldevices = '';
            $odlbrands = '';
            $odlcountries = array();
            foreach( $odls as $odl) {
                if( $odl['number_of_devices'] > 0 ){
                    $odldevices = ($odldevices + $odl['number_of_devices'] );
                }
                $brands = count($odl['brands_available']);
                if( $brands > 0){
                    $odlbrands = $odlbrands + $brands;
                }
                $odlcountries[] = $odl['loc']['country'];
            }
            $odlc = array_unique($odlcountries);
            // count of odls
            $odlcount = count($odls);

            // return info
            $odlinfo = array();
            $odlinfo['count'] = $odlcount;
            $odlinfo['devices'] = $odldevices;
            $odlinfo['brands'] = $odlbrands;
            $odlinfo['countries'] = count($odlc);
            return $odlinfo;
        }
        return false;
    }

Since the API's JSON does not provide cumulative data like "all devices" or "total number of manufacturers", we need to extract these bits from the data.
We use PHP's json_decode() function, and since we set the second parameter to "true", we will get a PHP array out of the JSON data. Set to "false" (which is default), it would have returned an object.
We wanted to use four "over all" numbers:

  1. count of all ODLs: This is easy, since every ODL is a entry in the top-level array, so a simple count() on that array returns our number.
  2. Number of devices available: Again, simple, since every ODL has a "number_of_devices" entry: We only need to loop over all ODL entries and add each ODL's "number_of_devices" number.
  3. Number of brands: This is a bit trickier, since in each ODL data set there is an array "brands_available", so we need to count these and then add each ODL's count. This is quick and dirty since the resulting total number will have duplicates (i.e. if ten ODLs have "Nokia", we'll have ten manufacturers in the total number. The next version of this script will be more exact. See update below.
  4. Number of countries: Assuming that every ODL is in one country only, we take the "['loc']['country']" entry and push it into a new array "$odlcountries". After the loop over all the ODLs, we strip out the duplicate countries with PHP's array_unique() function and make a count() on the resulting array.

Finally we take these numbers and fill our "$odlinfo" array, which will act as a container that is returned by this plugin for usage in the site's template.

3) Display the goodness in the template

To get the numbers, we now can use the "$odlinfo" variable returned by the plugin. Since Kirby is cool, we don't need to do anything, the variable is magically there and ready to use as soon as we call our plugin's function:


$odlinfo = odlapi();
$out = sprintf('Weltweit sind momentan <span><b>%d</b> ODLs</span> in <span><b>%d</b> Ländern</span> mit <span><b>%d</b> Geräten</span> von <span><b>%d</b> Herstellern</span> am Start.',$odlinfo['count'],$odlinfo['countries'],$odlinfo['devices'],$odlinfo['brands']);
if( c::get('lang.current') == 'en' ){
            $out = sprintf('Currently there are <span><b>%d</b> ODLs</span> across <span><b>%d</b> countries</span> featuring <span><b>%d</b> devices</span> by <span><b>%d</b> manufacturers</span> worldwide.',$odlinfo['count'],$odlinfo['countries'],$odlinfo['devices'],$odlinfo['brands']);
        }
echo $out;

As you can see, we used PHP's sprintf() function to make two localized strings for German and English with our oh-so-dynamically fetched data numbers injected at the right points.

Have a look at the result on our page, right under the text block "Not in Frankfurt?".

Update 2013-11-13

While writing the above, I noticed that my logic to get the number of manufacturers was wrong.
Here is the new version of the plugin, which should display the correct number of different manufacturers across all ODLs:


    function odlapi(){
        $path_to_json = '/opendevicelab.net/ffm/cron/';
        $json_file_name = 'odlapi.json';
        $json = $path_to_json.$json_file_name;
        if( is_file( $json ) && filesize( $json ) > 0 ){
            $tmp = file_get_contents( $json );
            $odls = json_decode( $tmp,true );

            $odldevices = '';
            $odlbrands = array();
            $odlcountries = array();
            foreach( $odls as $odl) {
                if( $odl['number_of_devices'] > 0 ){
                    $odldevices = ($odldevices + $odl['number_of_devices'] );
                }
                if( $odl['brands_available'] != '' ){
                    if( count($odl['brands_available']) > 0){
                        foreach( $odl['brands_available'] as $brand){
                            $odlbrands[] = $brand;
                        }
                    }
                }
                $odlcountries[] = $odl['loc']['country'];
            }
            $odlb = array_unique($odlbrands);
            $odlc = array_unique($odlcountries);

            // return info
            $odlinfo = array();
            $odlinfo['count'] = count($odls);
            $odlinfo['devices'] = $odldevices;
            $odlinfo['brands'] = count($odlb);
            $odlinfo['countries'] = count($odlc);
            return $odlinfo;
        }
        return false;
    }