Skip to content

xkcd plugin case study

jdeananderson edited this page Jan 10, 2019 · 46 revisions

This article is meant as a "cheat sheet" example to quickly get a new plugin up and running. This example modifies the "simple_text.php" plugin to display the latest xkcd comic using the xkcd API. The xkcd API was chosen for this example because it does not require an API key of any kind. This exercise was completed in well under an hour by copying and pasting code, and writing a few lines of new code.

Copy existing plugin and rename a few things inside the new xkcd.php plugin

  1. This plugin started by simply copying the simple_text plugin. cp simple_text.php xkcd.php

  2. At the top of the new xkcd.php file, rename the class from "simpleTextPlugin" to the name of the new plugin, which in this case is going to be "xkcdPlugin.php". The new line should appear as class xkcdPlugin implements iPlugin() {

  3. Now the plugin needs a unique "plugin" index in the public function "getIndex". The number 5 has not been used yet, so we are going to replace return 4 with return 5. Make sure there are no plugins with an index of 5 on your system before using "5".

  4. Our new plugin needs a relevant name returned by the getName function. Edit the getName function to return "xkcd" instead of return "Static Images".

  5. Change the isActive function to include a new config parameter that will live in the settings.cfg file. In this case, we will rename simpleTextPluginActive to xkcdPluginActive. We will add the "xkcdPluginActive" parameter to settings.cfg file in a later step.

  6. At the bottom of the script, there are a few lines of code that check to make sure that the new plugin is active, and adds your new plugin to the array of plugins as defined in iPlugin.php. These lines need to be altered to include the new plugin name. Change simpleTextPluginActive to xkcdPluginActive, $simpleText to $xkcd, and simpleTextPlugin to xkcdPlugin.

It should now read:

if ($config->xkcdPluginActive == "true") {
    $xkcd= new xkcdPlugin;
    $plugins[ $xkcd->getIndex() ] = $xkcd;
}

Making a new getResources function

The getResources function needs to return a list of the resource options that this plugin will have. This function is used by the device manager to populate the resources drop down list. This plugin will have two statically created resource options, "Today's Comic" and a "Random Comic". Other plugins are more complex, and return a list of resources queried from an API, such as the google calendar plugin that gets a list of all of the calendars that a user has available. In this case, we are just assigning two predetermined options. To do this, we remove all code from the existing getResources plugin and add the following lines below. This function returns a simple array that has two options; Option 1, which is Today's Comic, and Option 2, which is a random comic.

//include list of resources.  
//This plugin will only have two staticly defined resources
$xkcdOptions = array("1"=>"Today's Comic","2"=>"Random Comic");
return $xkcdOptions;

Making a new getImage function in xkcd.php plugin

Now it is time to alter the getImage function. This is typically the "hard part". In this example we want to use the xkcd API to get the URL of an xkcd comic, download the comic, and send it to a wall-ink device. To get today's comic, is pretty simple, we use the following two lines of code to get the URL of today's xkcd comic:

//get today's comic json object
$json_object = json_decode(`curl https://xkcd.com/info.0.json`);
//pull the 'img' URL out of the json object 
//and put it in the $comic_image_url variable
$comic_image_url = $json_object->{'img'};

$comic_image_url gets the url of today's comic in png format such as

https://imgs.xkcd.com/comics/popper.png

With this URL, we can download the png comic file, and pass it through the same process as the static images plugin to put the png file on the wall-ink device.

We use the following code to download the png file comic to a temporary location for ImageMagick to convert it as necessary:

$remote_image = file_get_contents($comic_image_url);
$temp_file_name = "/tmp/xkcd" . $device ["mac_address"] . ".png";
file_put_contents($temp_file_name,$remote_image);
$source_image = "/tmp/xkcd" . $device["mac_address"] . ".png";

We make sure that the the temp file has a unique name (it contains the MAC address of the requesting device) in case multiple wall-ink devices are all using the xkcd plugin simultaneously so images don't get mixed up.

We now steal the ImageMagick "convert" command line code from the static_images plugin to convert and resize the png comic file into a pbm file. This line of code will take the temp file downloaded above, and create the necessary pbm file. That pbm file will be processed like any other plugin to create a wink file.

`convert $source_image -rotate $angle -resize $size\! $pbm`;

Adding the random comic

Adding the random comic requires a bit more API work. We first use the xkcd API to get today's comic json object and look at the comic number. We then choose a random number between 1 and today's comic number. That randomly chosen number is then used with the xkcd API to get that day's comic. This is a slightly different API call than getting today's comic.

We use an if statement to run the random comic code if the resource_id is "2" (as defined in our array in getResources), and if the resource_id is anything but "2", just return today's comic. The part of getImage that returns the $comic_image_url looks like this now:

 //get today's comic json object
        $json_object = json_decode(`curl https://xkcd.com/info.0.json`);

        if ($device['resource_id'] == 2) {
                //get today's comic number
                $today_num = $json_object->{'num'};
                //choose a random number comic between 1 and today
                $random_comic_num = rand(1,$today_num);
                //get url for random comic number https://xkcd.com/1/info.0.json
                $json_url = "https://xkcd.com/" . $random_comic_num . "/info.0.json";
                $json_command_line = `curl $json_url`;
                //retrieve json object of random comic
                $json_object = json_decode($json_command_line);
                //get url of image for this random comic
                $comic_image_url = $json_object->{'img'};
        } else {  //if anything else, just deliver today's comic
                $comic_image_url = $json_object->{'img'};
        }

Altering getDeviceType in xkcd.php plugin

The getDeviceType function is what controls how often the wall-ink device refreshes. It can also control how plugins display information to the device. For instance, a device layout could be included that displays the title of the comic, or the date of the comic on the screen. The plugins created to date all use radio boxes to choose layout types. This is not a requirement. The code here simply needs to allow choosing a layout. It is a good idea to include some code to specify a default, or the device manager will appear not to work. The device manager page will not show a preview until a valid option with all parameters is chosen.

Each device type option in the getDeviceType function in the simpleText plugin has radio box code that looks like this:

$getDeviceType .= "<li>";
    $getDeviceType .= "<label for=\"60\">1 hour refresh cycle</label>";
    $getDeviceType .= "<input type=\"radio\" id=\"type_60\" name=\"new_device_type\" value=\"60\"";
    if ($device['device_type'] == 60 && $device['plugin'] == $this->getIndex()) {
        $getDeviceType .= " checked";
    }
    $getDeviceType .= ">";
$getDeviceType .= "</li>";

In this example, we are adding a couple of new layouts to what the simpleText plugin had. We are going to add a 3 hour option, and a 1 day option. We simply take the text above, and replace all instances of "60" with "180" to make the 3-hour option, and replace "60" with "1440" for the 1-day option. We also have to replace the text "1 hour" with "3 hour" and "1 day" as needed.

Edit settings.cfg to include xkcdPluginActive

Edit the settings.cfg file. to include the necessary text to declare the plugin "active" or not. Append the text below at the bottom of the settings.cfg. The comments starting with # are not necessary, but make the config file much easier to peruse when changing settings.

# ****************************************
# ****      xkcd plugin settings       ***
# ****************************************
xkcdPluginActive="true"
#

settings_check.sh

The web/config/settings_check.sh file is used to ensure that all required settings for a plugin are in the settings.cfg file when make is run. The plugin will function without editing this check script, but it can alleviate hassles when a new plugin is used with an old config file that doesn't contain the required parameters.

Run make and test the plugin

Go to the root of your wall-ink-server directory and run "make". If everything is in the right place, the plugin should work now. If it doesn't work, check the instructions above and perhaps see troubleshooting to figure out what went wrong.

Full xkcd.php code for reference

If you want to use this code, don't forget you will need to edit the settings.cfg file and run "make" before this will work.

<?php
require_once("$_SERVER[DOCUMENT_ROOT]/plugin_dependencies/iPlugin.php");
require("$_SERVER[DOCUMENT_ROOT]/config/dbconfig.php");

class xkcdPlugin implements iPlugin {

    public function getIndex() {
        // The number returned here for this plugin must be unique to this wall-ink-server
        return 5;
    }
    public function getName() {
        // This is the name of the plugin as it will appear in the device manager
        return "xkcd";
    }
    public function isActive($config) {
        // Used to see if the plugin is active
        return $config->xkcdPluginActive;
    }
    public function getResources($config) {
        //include list of resources.  This plugin will only have two staticly defined resources
        $xkcdOptions = array("1"=>"Today's Comic","2"=>"Random Comic");
        return $xkcdOptions;

    }
    public function getImage($config, $device) {
        //get today's comic json object
        $json_object = json_decode(`curl https://xkcd.com/info.0.json`);

        if ($device['resource_id'] == 2) {
                //get today's comic number
                $today_num = $json_object->{'num'};
                //choose a random number comic between 1 and today
                $random_comic_num = rand(1,$today_num);
                //get url for random comic number https://xkcd.com/1/info.0.json
                $json_url = "https://xkcd.com/" . $random_comic_num . "/info.0.json";
                $json_command_line = `curl $json_url`;
                //retrieve json object of random comic
                $json_object = json_decode($json_command_line);
                //get url of image for this random comic
                $comic_image_url = $json_object->{'img'};
        } else {  //if anything else, just deliver today's comic
                $comic_image_url = $json_object->{'img'};
        }




        //get height and width of image from wall-ink device firmware
        $width = $device['width'];
        $height = $device['height'];

        //The size of the image created by ImageMagick below must be reduced by the size of the margin added by the "border"
        $size = ($width . "x" . $height);

        //if device is set to upside-down, make it so
        $angle = 0;
        if ($device['orientation'] == 1) {
                $angle = 180;
        }

        //use device type as the number of minutes to sleep for.  If device type not set, assume 1 hour
        $timeIncrement = 3600;
        if (isset($device['device_type'])) {
                $timeIncrement = $device['device_type'] * 60;
        }

        //calculate the next time the wall-ink device is to check in
        $nextRefreshTime = $timeIncrement - ($_SERVER['REQUEST_TIME'] % $timeIncrement) +30;
        $pbm = "$_SERVER[DOCUMENT_ROOT]/image_data/" . $device["mac_address"] . "." . "pbm";
        $raw = "$_SERVER[DOCUMENT_ROOT]/image_data/" . $device["mac_address"];
        $comic_image = "$_SERVER[DOCUMENT_ROOT]/image_data/" . $device["mac_address"] . "." . "wink";

        $remote_image = file_get_contents($comic_image_url);
        $temp_file_name = "/tmp/xkcd" . $device ["mac_address"] . ".png";
        file_put_contents($temp_file_name,$remote_image);
        $source_image = "/tmp/xkcd" . $device["mac_address"] . ".png";

        `convert $source_image -rotate $angle -resize $size\! $pbm`;
        `$_SERVER[DOCUMENT_ROOT]/pbmToRaw.sh $pbm $raw`;
        `$_SERVER[DOCUMENT_ROOT]/rawToWink $raw $comic_image $width $height $nextRefreshTime $device[mac_address]`;

        return $comic_image;
    }

    public function getDeviceType($device) {
        //if necessary, set default
        $validDeviceTypes = array(30,60,180,1440);
        if (!in_array($device["device_type"], $validDeviceTypes)) {
            $device["device_type"] = 60;
        }
        $getDeviceType = "";
        $getDeviceType .= "<script language='javascript'>";
        $getDeviceType .= "defaults[" . $this->getIndex() . "]=" . $device["device_type"] . ";";
        $getDeviceType .= "</script>";






        $getDeviceType .= "<fieldset class=\"field getdevicetype";
        if ($device['plugin'] != $this->getIndex()) {
            $getDeviceType .= " hidden";
        }
        $getDeviceType .= "\" data-pluginid=\"";
        $getDeviceType .= $this->getIndex();
        $getDeviceType .= "\">";
            $getDeviceType .= "<legend>Device Type</legend>";
            $getDeviceType .= "<ul>";
                //first option
                $getDeviceType .= "<li>";
                    $getDeviceType .= "<label for=\"30\">30 minute refresh cycle</label>";
                   $getDeviceType .= "<input type=\"radio\" id=\"type_30_" . $this->getIndex() . "\" name=\"new_device_type\" value=\"30\"";
                    if ($device['device_type'] == 30 && $device['plugin'] == $this->getIndex()) {
                        $getDeviceType .= " checked";
                    }
                    $getDeviceType .= ">";
                $getDeviceType .= "</li>";

                $getDeviceType .= "<li>";
                    $getDeviceType .= "<label for=\"60\">1 hour refresh cycle</label>";

                   $getDeviceType .= "<input type=\"radio\" id=\"type_60_" . $this->getIndex() . "\" name=\"new_device_type\" value=\"60\"";
                    if ($device['device_type'] == 60 && $device['plugin'] == $this->getIndex()) {
                        $getDeviceType .= " checked";
                    }
                    $getDeviceType .= ">";
                $getDeviceType .= "</li>";

                $getDeviceType .= "<li>";
                    $getDeviceType .= "<label for=\"180\">3 hour refresh cycle</label>";
                    $getDeviceType .= "<input type=\"radio\" id=\"type_180_" . $this->getIndex() . "\" name=\"new_device_type\" value=\"180\"";
                    if ($device['device_type'] == 180 && $device['plugin'] == $this->getIndex()) {
                        $getDeviceType .= " checked";
                    }
                    $getDeviceType .= ">";
                $getDeviceType .= "</li>";

                 $getDeviceType .= "<li>";
                    $getDeviceType .= "<label for=\"1440\">1 day refresh cycle</label>";
                    $getDeviceType .= "<input type=\"radio\" id=\"type_1440_" . $this->getIndex() . "\" name=\"new_device_type\" value=\"1440\"";
                    if ($device['device_type'] == 1440 && $device['plugin'] == $this->getIndex()) {
                        $getDeviceType .= " checked";
                    }
                    $getDeviceType .= ">";
                $getDeviceType .= "</li>";

            //end of last option
            $getDeviceType .= "</ul>";
        $getDeviceType .= "</fieldset>";
        return $getDeviceType;

    }


}

if ($config->xkcdPluginActive == "true") {
    $xkcd= new xkcdPlugin;
    $plugins[ $xkcd->getIndex() ] = $xkcd;
}
?>