2015-11-24 Creating an Azure IoT Device Explorer in node.js, express and jade
After playing a bit with Azure IoT hub and building a webcam system with a RaspberryPi 2 running Linux in my previous article, I've decided to continue developing a bit in node.js to build a simple equivalent of the Device Explorer but in node.js. I'm not a node.js expert so there may be more efficient way to write some of the code.
Code is available on GitHub: https://github.com/Ellerbach/nodejs-webcam-azure-iot/tree/master/DeviceExplorer. The code on GitHub does include more than what is explained in this article.
Setup the dev environement
I'll use Windows as my development environment. Visual Studio 2015 can support node.js development and debugging. So first I needed to setup Visual Studio 2015:
- You can get for free Visual Studio Community 2015 here.
- Then you need to the free node.js tools for Visual Studio, download them here.
- And you need of course to install the node.js framework from here. Once those 3 steps done, you're good to go!
Creating a node.js project in Visual Studio
This is quite straight forward, you have new project type which is node.js. I've created a Start Node.js Express 3 Application project. It does come with a simple MVC project containing couple of example pages which makes it easy to learn and understand how all is working.
Views are using jade. This is a part where I had quite some difficulties to use. I do recommend to read the Language Reference as well as the examples from https://jade-lang.com/. The most important is to keep in mind the indentation must be respected and this is what makes groups and makes all the code logic. When you got that, the rest is quite easy and really nice to use.
It will create a full web site. I do recommend to create TypeScript file and not Javascript. this will allow to have a better support and maintainability over time. Javascript file are generated once compiled. And you can choose which version of Javascript you want to generate.
Listing devices
The idea is to have a page that will allow to enter the connection key:
Once the key is entered, it will list the devices plus their keys and couple of other info (in real, keys are displayed instead of the blue box):
So to build this, we will need to:
- Create a view which is about adding a jade file in the views directory
- Add a function to handle requests on the page
- Add a route so the traffic will be correctly redirected to the page I will first explain how the principle of views and code is working.
Adding the view
Right click on Views then Add and New Items…
select jade file and create one call devices.jade. Let start by replacing what is generated by this code:
extends layout
block content
h2 #{title}
h3 #{message}
div.connbox
p please enter your connection string like HostName=XXX.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=XXX
form(name="connection", action="/devices", method="post")
input(type="text", name="constr")
input(type="submit", value="Connect")
In jade, it will create a page where the #{title} will be replaced by the default text rendering of the title object which will be given to the page. It can be a string or any object. This will allow to manipulate those objects and we will see later how to do that.
The second part will create a form which will post the value of the text box to the page names /devices
Adding a function to handle the code
In the routes/index.js file, add this code:
export function devices(req: express.Request, res: express.Response) {
var cnxstr = req.body['constr'];
res.render('devices', { title: 'Devices', message: 'this is the connection string: ‘ + cnxstr });
};
This simple code just find the value of ‘constr' which has been send by the post form and ask to render the page by sending back the info in the message object. Now, we still don't have everything as the function has not been declared as a route when the page /devices is called.
Adding the route for a page
Find the file app.ts and add the lines after the line "app.get(‘/', routes.index);"
app.get('/devices', routes.devices);
app.post('/devices', routes.devices);
Basically, the function we've just wrote is now linked to a get or post request on the /devices page.
All together, if you click F5, go on the /devices pages, you'll be able to fill the text box, send the data to the page and see what you've posted. This is a very basic example but it does allow to understand how express and jade are working together.
Requesting Azure IoT Hub devices list in node.js
You'll need to add the ‘azure-iothub""' module. Using Visual Studio, makes it super easy. Just right click on "npm" in your project then select "Install new npm Packages…"
This will open this window where you can search for the packages.
Here is the code to request all devices present in the IoT hub. It is quite straight forward as the node.js SDK is really nicely done:
var iothub = require('azure-iothub');
var cnxString = '';
export function devices(req: express.Request, res: express.Response) {
var cnxstr = req.body['constr'];
if ((cnxstr != undefined) || (cnxString)) {
if (cnxString) {
if ((cnxstr == undefined)||(cnxstr ==''))
cnxstr = cnxString;
}
var registry = iothub.Registry.fromConnectionString(cnxstr);
registry.list(function (err, deviceList) {
if (!err) {
cnxString = cnxstr;
res.render('devices', { title: 'Devices', year, message: 'Getting list of devices', devicelist: deviceList });
} else
res.render('devices', { title: 'Devices', year, message: 'Error getting list of devices', devicelist: null });
});
} else
res.render('devices', { title: 'Devices', year, message: 'Please give a valid connection key', devicelist: null });
};
First part of the code is really here to check if a connection string has already been provided. If yes, it will reuse the connection string. The key 2 lines are:
var registry = iothub.Registry.fromConnectionString(cnxstr);
registry.list(function (err, deviceList) {
this will return in devicelist an array of devices. If no error, then it is passed to the devices view. So very straight forward.
Modifying the jade view to render devices list
Go back to the devices.jade file and add the following code:
div.text
if(devicelist!=null)
table
thead
tr
th
| DeviceId
th
| Prim Key
th
| Sec key
th
| Last upd
th
| Status
th
| Msg waiting
tbody
each device in devicelist
tr
td
a(href='/devicedetail/' + device.deviceId) #{device.deviceId}
td
| #{device.authentication.SymmetricKey.primaryKey}
td
| #{device.authentication.SymmetricKey.secondaryKey}
td
| #{device.lastActivityTime}
td
| #{device.status}
td
| #{device.cloudToDeviceMessageCount}
p
a(href='/adddevice/') Add a new device
This part took me quite a lot of time. The reason is jade and the way indentation is working. It is really super important to respect it and the alignment almost drives the behavior of what will be generated. the good news is that you can add code in the jade file like testing if you have a devicelist object. and do for each in the code. The code will generate a simple table which contains some of the device properties. I've created as well a page for details as well as a page to add a new device.
This code will render exactly as in the screen capture from the first part of this article. Now, let see how to generate the detailed page for devices as well as creating a new device.
Listing devices properties
Similar to the previous part, add a devicedetail jade, here is the code very similar to the previous page to generate the details in a table:
extends layout
block content
h2 #{title}
p #{message}
div.text
if(device!=null)
table
thead
tr
th
| Details
th
| Values
tbody
tr
td
| Device Id
td
| #{device.deviceId}
tr
td
| Primary Key
td
| #{device.authentication.SymmetricKey.primaryKey}
tr
td
| Secondary Key
td
| #{device.authentication.SymmetricKey.secondaryKey}
tr
td
| Last Activity
td
| #{device.lastActivityTime}
tr
td
| Generation Id
td
| #{device.generationId}
tr
td
| Messages waiting
td
| #{device.cloudToDeviceMessageCount}
tr
td
| etag
td
| #{device.etag}
tr
td
| Status
td
| #{device.status}
tr
td
| Status Reason
td
| #{device.statusReason}
tr
td
| Connection State
td
| #{device.connectionState}
tr
td
| connectionStateUpdatedTime
td
| #{device.connectionStateUpdatedTime}
tr
td
| statusUpdatedTime
td
| #{device.statusUpdatedTime}
We'll need to create a function that will handle the request and return the device object:
export function devicedetail(req: express.Request, res: express.Response) {
var devId = req.params.deviceId;
var strcnx = getHostName(cnxString);
if (strcnx == '')
res.render('devicedetail', { title: 'Device detail', year, message: 'Error getting device details. Connection string was: ' + cnxString + ' and deviceId: ' + devId });
strcnx += ';DeviceId=' + devId;
var registry = iothub.Registry.fromConnectionString(cnxString);
var msg = 'No device found';
registry.get(devId, function (err, device) {
if (!err) {
strcnx += ';SharedAccessKey=' + device.authentication.SymmetricKey.primaryKey;
res.render('devicedetail', { title: 'Device detail', year, message: 'Those are the device details. Connection string: ' + strcnx, device: device });
} else
res.render('devicedetail', { title: 'Device detail', year, message: 'Error connecting' });
});
};
function getHostName(str)
{
var txtchain = str.split(';');
for (var strx in txtchain) {
var txtbuck = txtchain[strx].split('=')
if (txtbuck[0].toLowerCase() == 'hostname')
return txtchain[strx];
}
return '';
}
As you'll see in the jade page, the link to the page is /devicedeatil/name_of_a_device. In order to catch it, we'll need to declare it in the route and it will allow to have it thru the req.params function. I will name it deviceid. so add this line in the app.ts file:
app.get('/devicedetail/:deviceId', routes.devicedetail);
First part of the code is about getting the list of devices and making sure the device exists. then it's about getting the device and returning it. As sometimes you need the device connection string, this string is built and returned as well. As a result, you'll get a detailed page like:
Those properties are the ones available for every device. The status shows is the device is allow or not to connect. If not, you'll have a reason (128 bit max) displayed in the Status reason. If you send messages to your device, you'll see as well if messages are waiting.
Adding a device to Azure IoT hub
Very similar to the previous part, we'll just add a jade file adddevice. Here is the code:
extends layout
block content
h2 #{title}
p #{message}
div.connbox
if(deviceId==null)
p please enter your device name
form(name="adddevice", action="/adddevice", method="post")
input(type="text", name="deviceId")
input(type="submit", value="Add")
Simple code, very similar to the first example. For the main function code, it's quite easy as well:
export function adddevice(req: express.Request, res: express.Response) {
var devId = req.params.deviceId;
if (devId == undefined) {
devId = req.body['deviceId'];
if (devId == undefined)
res.render('adddevice', { title: 'Add device', year, message: 'Error, no device ID' });
}
if (cnxString == '')
res.render('adddevice', { title: 'Add device', year, message: 'Error, no connection string' });
else {
var registry = iothub.Registry.fromConnectionString(cnxString);
//create a new device
var device = new iothub.Device(null);
device.deviceId = devId;
registry.create(device, function (err, deviceInfo, response) {
if (err)
res.render('adddevice', { title: 'Add device', year, message: 'Error, creating device' + err.toString() })
else
if (deviceInfo)
res.render('adddevice', { title: 'Add device', year, message: 'Device created ' + JSON.stringify(deviceInfo) });
else
res.render('adddevice', { title: 'Add device', year, message: 'Unknown error creating device ' + devId });
});
}
}
All up, first part of the code is just to check if there is a device name either thru get as a param, either thru post. Second part is about adding a device:
var registry = iothub.Registry.fromConnectionString(cnxString);
//create a new device
var device = new iothub.Device(null);
device.deviceId = devId;
registry.create(device, function (err, deviceInfo, response) {
Again, very simple, very straight forward. we're connecting to the Azure IoT Hub registry, then create an empty device, set the deviceId name and ask for creation.
Don't forget to add the route as well. the "?" in "/adddevice/:deviceId?" is to make the param as optional.
app.get('/adddevice/:deviceId?', routes.adddevice);
app.post('/adddevice', routes.adddevice);
Once created, if there is no error, the device is sent back. I'm just converting it into a JSON and display it in the message:
You can do a very similar code to delete a device. You can as well send a message to the device and monitor the results. I have to say the Azure IoT SDK in node.js is really great and working perfectly. And please note that this website can be deployed on Azure, Windows or Linux or anything else that can run one of the latest node.js version (see restriction in the Azure Iot SDKs on GitHub here)
More examples on my GitHub! Enjoy and feedback welcome.