Building a Voice-Driven TV Remote - Part 6: Starting to Migrate from HTTP to MQTT

This is part six of the Building a Voice-Driven TV Remote series:

  1. Getting The Data
  2. Adding Search
  3. The Device API
  4. Some Basic Alexa Commands
  5. Adding a Listings Search Command
  6. Starting to Migrate from HTTP to MQTT
  7. Finishing the Migration from HTTP to MQTT
  8. Tracking Performance with Application Insights

After a much longer hiatus than I'd hoped to take from this project, I'm finally getting a chance to dive back in!

One of the main things I've been wanting to do is deprecate the device API introduced in part 3 in favor of leveraging Azure's IoT Hub and the MQTT protocol. If you're unfamiliar with MQTT, it's a very lightweight publish/subscribe messaging protocol designed for IoT scenarios. Azure's IoT Hub has a free tier that allows for 8,000 messages per day, which fits right in with my goal of this thing being as cheap as possible. I like free.

This would come with several nice benefits. For one, I could eliminate the public-facing HTTP API entirely which would feel good from a security perspective, and also get rid of a lot of moving parts like NGINX, LetsEncrypt, and No-IP. Additionally it should ultimately result in a nice performance improvement as well since each command wouldn't require separate HTTP calls with all the overhead that comes with that. It should also help from a monitoring perspective as well, to be able to detect if the device goes offline and things like that.

Bridging the IoT Hub with the Harmony API

Once I had the hub set up, I quickly realized a real issue with using Azure IoT Hubs with my Harmony API's support of MQTT: the topics. Messages sent to devices through Azure IoT Hubs will always go to a MQTT topic specific to IoT Hubs, which doesn't play well with how the Harmony API is set up, since it has a variety of different topics for sending different commands. I spent some time trying to figure out a way around this, but ultimately the easiest solution was to just build a bridge between the two that would run on the Raspberry Pi alongside the API.

To do this, I built a simple Node app that spins up a MQTT broker, and also connects to IoT Hub as a device. When a message comes in to that device from IoT Hub it will parse and relay it to a topic known by the Harmony API. To get started, I pulled in two npm dependencies:

npm i --save aedes azure-iot-device-mqtt  

With those in place I needed to create the MQTT broker, and then connect to Azure. First, the broker:

const broker = require('aedes')();  
const server = require('net').createServer(broker.handle);

server.listen(1883, () => console.log('MQTT broker listening'));  

The aedes package makes it really simple to create a MQTT server. Now we need to connect to Azure:

const Azure = require('azure-iot-device');  
const Mqtt = require('azure-iot-device-mqtt').Mqtt;

const azureConnectionString = 'get-this-from-the-azure-portal';  
const azureClient = Azure.Client.fromConnectionString(azureConnectionString, Mqtt);

function onAzureConnect(err) {  
    if (err) {
        console.error('Could not connect to Azure', err);
        return;
    }

    console.log('Connected to Azure');

    azureClient.on('error', console.error);
    azureClient.on('disconnect', () => {
        azureClient.removeAllListeners();
        azureClient.open(onAzureConnect);
    });

    azureClient.on('message', msg => {
        const [topic, payload] = msg.data.toString().split(';');

        broker.publish({ topic, payload });
    });
}

azureClient.open(onAzureConnect);  

Most of this is boilerplate code and there's really not much going on. When a message comes in it parses out a topic and payload, assuming a message format of topic;payload, and publishes that to the MQTT broker.

To get the connection string, first we need to create a device in the IoT Hub. It's possible to do this via the SDK itself, but since this is just one static device it was easier to just create it in the Azure portal. Once you create the device you can just copy the connection string out of the portal to use here.

Finally, we just need to tell the Harmony API to connect to the MQTT broker when it starts up in its config.json file:

{
  "mqtt_host": "mqtt://127.0.0.1",
  "mqtt_options": {
      "port": 1883,
      "clientId": "harmony"
  }
}

With these running we can now use the Azure portal to try sending a message to the device:

Sending a message

Clicking Send Message here quickly resulted in my TV being muted. There's still a lot more work to do here to migrate over to MQTT entirely, but this is an encouraging start. For the commands, I'll need to update the Azure Functions code to publish those messages via the Azure IoT Hub instead of making HTTP calls, which will be easy enough. The more complicated part will be migrating the GET calls since it will require some type of persistent storage, but one step at a time.

To be continued!


Next post in series: Finishing the Migration from HTTP to MQTT

comments powered by Disqus
Navigation