At In The Pocket, we recently moved to a new office in Dok Noord, Ghent. In the new office, each of our development teams has its own room, equipped with a TV & a Chromecast, so that the teams could easily project their SCRUM board during stand-up, or quickly project some code/scheme/dashboard/diagram to discuss.

That was a nice thing, but it still left the Chromecast idle for most of the day, displaying photos. Wouldn’t it be nice if it automatically would display a slideshow of diverse dashboards? That it would alert the team in case a build failed?

In this first Hack The Office series of posts, we’ll discuss one of the projects that was successfully engineered during our first office hackaton: Chromecast based dashboards.

Chromecast primer

If you’re unfamiliar with a Chromecast: it is a €39 device that can be plugged into the HDMI port of a TV/display and can be used to cast content from your mobile phone or browser to your TV.

What makes it interesting for developers though, is the Cast SDK. As it is engineered, content that is casted to the Chromecast is controlled by a so-called Sender application and played by a Receiver application that runs on the Chromecast.

A Receiver application is actually just a client-side web application that is able to communicate with the Chromecast via a javascript SDK.

The Sender application is either an iOS, Android or Chrome application, for which Google provides SDKs. Communication between a Sender and its Receiver happens via a message bus with different channels. There are a number of predefined channels, but each sender-receiver solution can create their custom channel needed to pass information.

The Idea

Knowing how relatively simple it was to create a Receiver application, it quickly lead us to the following idea:

  • Create a Receiver application that displays the outcome of Jenkins builds in realtime
  • Customize the application per team: the Sender application should be able to tell the receiver application which team it is broadcasting to.
  • Automatically discover Chromecasts
  • Automatically start the receiver app when noone else is casting

The Receiver App

We started our office hackaton by creating a custom receiver app. In our case, the only required information that should be passed from the Sender to the Receiver, was the actual team for which the Receiver should display information. The actual dashboard data would then be fetched by the Receiver via AJAX calls, or be pushed directly to it via Websockets.

This meant that our Receiver app would be fairly simple - in pure hackaton style, it sufficed to display the team’s name to confirm the communication between Sender and Receiver would work.

Using a custom channel

In order to pass the team’s information from the Sender to the Receiver, we had to define a custom channel in the message bus between a Sender and a Receiver. We opted to go for the identifier urn:x-cast:mobi.inthepocket.cast.dashboard and the team information that we would send from the Sender to the Receiver would take this form:

{
  "code": "teamretail",
  "name": "Team Retail & New Tech",
  "castname": "Team Retail"
}

In this payload, code was a unique, normalized name for a team (useful as a CSS class), name was a human-readable name for the team and castname referred to the name of the Chromecast that was owned by that team.

After determining these conventions, the required javascript for the Receiver turned out to be very minimalistic:

window.onload = function() {
  window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();

  // Create a CastMessageBus to handle messages for a custom namespace
  window.messageBus =
    window.castReceiverManager.getCastMessageBus('urn:x-cast:mobi.inthepocket.cast.dashboard');

  // Handle received messages
  window.messageBus.onMessage = function(event) {
    if (event.data != null && event.data.code != null) {
      setTeam(event.data);
    }
  }

  // Initialize the CastReceiverManager with an application status message
  window.castReceiverManager.start({statusText: 'ITP Dashboard is starting'});
};

// Simple function that displays the team's name in the DOM
function setTeam(team) {
  currentTeam = team;
  window.castReceiverManager.setApplicationState('Casting for ' + team.name);
  document.getElementById("message").innerHTML=team.name;
  document.getElementsByTagName('body')[0].className=team.code;
}

Caveat: Receiver app caching

If you’re going to develop receiver apps, be aware of the fact that a Chromecast will cache the receiver application and will cache it agressively. We lost valuable time restarting Chromecasts to clear their cache before we found out that window.location.reload(true); would actually clear the cache. In our case, this still was too much of an overhead and eventually we resolved it by just including an empty cache.manifest that we referred in our root html element:

CACHE MANIFEST

# Cache manifest version 1.0

CACHE

#no cache

NETWORK
*

Be sure to read the debugging section of the documentation carefully.

Caveat: DNS

Another caveat to watch out for: the Chromecast uses Google’s DNS servers, not the ones you pass it via DHCP. So although the Chromecast is able to load receiver apps that are only available in the private network, you’ll need to make sure to either use the IP address directly, or to publish the DNS entry to the entire world.

The Sender App

Up until now, we had been testing with a Chrome Sender app. But that wasn’t very suited for our goal to start it automatically. Unfortunately, Google does not provide any server-based SDKs for Sender applications that could help us at this stage.

After performing a bit of research, we discovered the castv2-client NPM package created by @thibauts. It not only offered us a way to create a node.js based Sender application, but also provided great detail on how the Cast protocol worked and how we could discover Chromecasts in our office.

Creating the Sender

Creating a Sender app with the castv2-client library involves a couple of steps.

First, if you need a custom channel to communicate with your receiver app, you’ll need to a controller that inherits from RequestResponseController to handle that communication. In our case, we just need to tell thereceiver app which team it is broadcasting for, so our controller turned out to be quite simple:

// DashboardController.js
var util                      = require('util');
var RequestResponseController = require('castv2-client').RequestResponseController

function DashboardController(client, sourceId, destinationId) {
  // Pass our custom channel identifier
  RequestResponseController.call(this, client, sourceId, destinationId,
    'urn:x-cast:mobi.inthepocket.cast.dashboard');
  }
}
util.inherits(DashboardController, RequestResponseController);

// Send the 'team' message over the custom channel
DashboardController.prototype.setTeam = function(team, callback) {
  var data = {
    team: team
  };

  this.request(data, function(err, response) {
    callback(err, response);
  });
};

module.exports = DashboardController;

Next, you’ll need to create a class for your application that inherits from Application. This class is used to launch the receiver app on the Chromecast and to interact with the receiver app. Again, in our case, the implementation proved quite trivial:

// Dashboard.js
var util                = require('util');
var Application         = require('castv2-client').Application;
// Our Dashboard Controller
var DashboardController = require('./DashboardController');

function Dashboard(client, session) {
  Application.apply(this, arguments);
  this.dashboardController = this.createController(DashboardController);
}
util.inherits(Dashboard, Application);

// See Chromecast Developer Console
Dashboard.APP_ID = '********';

// Use the controller to handle communication
Dashboard.prototype.setTeam = function(team, callback) {
  this.dashboardController.setTeam.apply(this.dashboardController, arguments);
};

module.exports = Dashboard;

Finally, you’ll need to connect to the Chromecast and launch the receiver app. To achieve that, you’ll need to create a Client, connect to the Chromecast’s IP address and then launch the app. Implementing that logic was quite trivial as well:

var Client    = require('castv2-client').Client;
var Dashboard = require('./Dashboard');

// Replace with the Chromecast's IP address
var chromecastIp = 'a.b.c.d';
var team = {
  code: 'teamretail',
  name: 'Team Retail & New Tech',
  castname: 'Team Retail'
};

var client = new Client();

// Connect to the Chromecast
client.connect(chromecastIp, function() {
  // Launch our Dashboard App
  client.launch(Dashboard, function(err, app) {
    if (err) {
      console.error(err);
    } else {
      // Send the 'team' message
      app.setTeam(team);
    }
  });
});

client.on('status', function(event) {
  console.log('Status event', event);
});

client.on('error', function(err) {
  console.error('Error occured', err.message);
  client.close();
});

To be continued

We now have a simple Chromecast receiver app that can be controlled via a server-based sender app. It is a nice start in achieving our goal of having an automated, realtime office dashboard, but we’re not quite there yet.

In the next episode of Hack The Office, we’ll cover how we handled Chromecast discovery and how we were able to detect if the Chromecast was already casting or not. Finally, we’ll also cover how we passed Jenkins build results from our Jenkins server to the dashboard of the responsible team in realtime. So stay tuned!