title | description | author | manager | ms.service | services | ms.topic | ms.date | ms.author |
---|---|---|---|---|---|---|---|---|
Provision Raspberry Pi to Remote Monitoring in Node.js - Azure | Microsoft Docs |
Describes how to connect a Raspberry Pi device to the Remote Monitoring solution accelerator using an application written in Node.js. |
dominicbetts |
timlt |
iot-accelerators |
iot-accelerators |
conceptual |
01/24/2018 |
dobett |
[!INCLUDE iot-suite-selector-connecting]
This tutorial shows you how to connect a physical device to the Remote Monitoring solution accelerator. In this tutorial, you use Node.js, which is a good option for environments with minimal resource constraints.
A desktop computer to enable you to connect remotely to the command line on the Raspberry Pi.
Microsoft IoT Starter Kit for Raspberry Pi 3 or equivalent components. This tutorial uses the following items from the kit:
- Raspberry Pi 3
- MicroSD Card (with NOOBS)
- A USB Mini cable
- An Ethernet cable
You need SSH client on your desktop machine to enable you to remotely access the command line on the Raspberry Pi.
- Windows does not include an SSH client. We recommend using PuTTY.
- Most Linux distributions and Mac OS include the command-line SSH utility. For more information, see SSH Using Linux or Mac OS.
If you haven't done so already, install Node.js version 4.0.0 or later on your Raspberry Pi. The following steps show you how to install Node.js v6 on your Raspberry Pi:
-
Connect to your Raspberry Pi using
ssh
. For more information, see SSH (Secure Shell) on the Raspberry Pi website. -
Use the following command to update your Raspberry Pi:
sudo apt-get update
-
Use the following commands to remove any existing installation of Node.js from your Raspberry Pi:
sudo apt-get remove nodered -y sudo apt-get remove nodejs nodejs-legacy -y sudo apt-get remove npm -y
-
Use the following command to download and install Node.js v6 on your Raspberry Pi:
curl -sL https://deb.nodesource.com/setup_6.x | sudo bash - sudo apt-get install nodejs npm
-
Use the following command to verify you have installed Node.js v6.11.4 successfully:
node --version
Complete the following steps using the ssh
connection to your Raspberry Pi:
-
Create a folder called
remotemonitoring
in your home folder on the Raspberry Pi. Navigate to this folder in your command line:cd ~ mkdir remotemonitoring cd remotemonitoring
-
To download and install the packages you need to complete the sample app, run the following commands:
npm install async azure-iot-device azure-iot-device-mqtt
-
In the
remotemonitoring
folder, create a file called remote_monitoring.js. Open this file in a text editor. On the Raspberry Pi, you can use thenano
orvi
text editors. -
In the remote_monitoring.js file, add the following
require
statements:var Protocol = require('azure-iot-device-mqtt').Mqtt; var Client = require('azure-iot-device').Client; var ConnectionString = require('azure-iot-device').ConnectionString; var Message = require('azure-iot-device').Message; var async = require('async');
-
Add the following variable declarations after the
require
statements. Replace the placeholder value{device connection string}
with value you noted for the device you provisioned in the Remote Monitoring solution:var connectionString = '{device connection string}'; var deviceId = ConnectionString.parse(connectionString).DeviceId;
-
To define some base telemetry data, add the following variables:
var temperature = 50; var temperatureUnit = 'F'; var humidity = 50; var humidityUnit = '%'; var pressure = 55; var pressureUnit = 'psig';
-
To define some property values, add the following variables:
var temperatureSchema = 'chiller-temperature;v1'; var humiditySchema = 'chiller-humidity;v1'; var pressureSchema = 'chiller-pressure;v1'; var interval = "00:00:05"; var deviceType = "Chiller"; var deviceFirmware = "1.0.0"; var deviceFirmwareUpdateStatus = ""; var deviceLocation = "Building 44"; var deviceLatitude = 47.638928; var deviceLongitude = -122.13476; var deviceOnline = true;
-
Add the following variable to define the reported properties to send to the solution. These properties include metadata to describe the methods and telemetry the device uses:
var reportedProperties = { "Protocol": "MQTT", "SupportedMethods": "Reboot,FirmwareUpdate,EmergencyValveRelease,IncreasePressure", "Telemetry": { "TemperatureSchema": { "Interval": interval, "MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", "MessageSchema": { "Name": temperatureSchema, "Format": "JSON", "Fields": { "temperature": "Double", "temperature_unit": "Text" } } }, "HumiditySchema": { "Interval": interval, "MessageTemplate": "{\"humidity\":${humidity},\"humidity_unit\":\"${humidity_unit}\"}", "MessageSchema": { "Name": humiditySchema, "Format": "JSON", "Fields": { "humidity": "Double", "humidity_unit": "Text" } } }, "PressureSchema": { "Interval": interval, "MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}", "MessageSchema": { "Name": pressureSchema, "Format": "JSON", "Fields": { "pressure": "Double", "pressure_unit": "Text" } } } }, "Type": deviceType, "Firmware": deviceFirmware, "FirmwareUpdateStatus": deviceFirmwareUpdateStatus, "Location": deviceLocation, "Latitude": deviceLatitude, "Longitude": deviceLongitude, "Online": deviceOnline }
-
To print operation results, add the following helper function:
function printErrorFor(op) { return function printError(err) { if (err) console.log(op + ' error: ' + err.toString()); }; }
-
Add the following helper function to use to randomize the telemetry values:
function generateRandomIncrement() { return ((Math.random() * 2) - 1); }
-
Add the following generic function to handle direct method calls from the solution. The function displays information about the direct method that was invoked, but in this sample does not modify the device in any way. The solution uses direct methods to act on devices:
function onDirectMethod(request, response) { // Implement logic asynchronously here. console.log('Simulated ' + request.methodName); // Complete the response response.send(200, request.methodName + ' was called on the device', function (err) { if (err) console.error('Error sending method response :\n' + err.toString()); else console.log('200 Response to method \'' + request.methodName + '\' sent successfully.'); }); }
-
Add the following function to handle the FirmwareUpdate direct method calls from the solution. The function verifies the parameters passed in the direct method payload and then asynchronously runs a firmware update simulation:
function onFirmwareUpdate(request, response) { // Get the requested firmware version from the JSON request body var firmwareVersion = request.payload.Firmware; var firmwareUri = request.payload.FirmwareUri; // Ensure we got a firmware values if (!firmwareVersion || !firmwareUri) { response.send(400, 'Missing firmware value', function(err) { if (err) console.error('Error sending method response :\n' + err.toString()); else console.log('400 Response to method \'' + request.methodName + '\' sent successfully.'); }); } else { // Respond the cloud app for the device method response.send(200, 'Firmware update started.', function(err) { if (err) console.error('Error sending method response :\n' + err.toString()); else { console.log('200 Response to method \'' + request.methodName + '\' sent successfully.'); // Run the simulated firmware update flow runFirmwareUpdateFlow(firmwareVersion, firmwareUri); } }); } }
-
Add the following function to simulate a long-running firmware update flow that reports progress back to the solution:
// Simulated firmwareUpdate flow function runFirmwareUpdateFlow(firmwareVersion, firmwareUri) { console.log('Simulating firmware update flow...'); console.log('> Firmware version passed: ' + firmwareVersion); console.log('> Firmware URI passed: ' + firmwareUri); async.waterfall([ function (callback) { console.log("Image downloading from " + firmwareUri); var patch = { FirmwareUpdateStatus: 'Downloading image..' }; reportUpdateThroughTwin(patch, callback); sleep(10000, callback); }, function (callback) { console.log("Downloaded, applying firmware " + firmwareVersion); deviceOnline = false; var patch = { FirmwareUpdateStatus: 'Applying firmware..', Online: false }; reportUpdateThroughTwin(patch, callback); sleep(8000, callback); }, function (callback) { console.log("Rebooting"); var patch = { FirmwareUpdateStatus: 'Rebooting..' }; reportUpdateThroughTwin(patch, callback); sleep(10000, callback); }, function (callback) { console.log("Firmware updated to " + firmwareVersion); deviceOnline = true; var patch = { FirmwareUpdateStatus: 'Firmware updated', Online: true, Firmware: firmwareVersion }; reportUpdateThroughTwin(patch, callback); callback(null); } ], function(err) { if (err) { console.error('Error in simulated firmware update flow: ' + err.message); } else { console.log("Completed simulated firmware update flow"); } }); // Helper function to update the twin reported properties. function reportUpdateThroughTwin(patch, callback) { console.log("Sending..."); console.log(JSON.stringify(patch, null, 2)); client.getTwin(function(err, twin) { if (!err) { twin.properties.reported.update(patch, function(err) { if (err) callback(err); }); } else { if (err) callback(err); } }); } function sleep(milliseconds, callback) { console.log("Simulate a delay (milleseconds): " + milliseconds); setTimeout(function () { callback(null); }, milliseconds); } }
-
Add the following code to send telemetry data to the solution. The client app adds properties to the message to identify the message schema:
function sendTelemetry(data, schema) { if (deviceOnline) { var d = new Date(); var payload = JSON.stringify(data); var message = new Message(payload); message.properties.add('$$CreationTimeUtc', d.toISOString()); message.properties.add('$$MessageSchema', schema); message.properties.add('$$ContentType', 'JSON'); console.log('Sending device message data:\n' + payload); client.sendEvent(message, printErrorFor('send event')); } else { console.log('Offline, not sending telemetry'); } }
-
Add the following code to create a client instance:
var client = Client.fromConnectionString(connectionString, Protocol);
-
Add the following code to:
- Open the connection.
- Set up a handler for desired properties.
- Send reported properties.
- Register handlers for the direct methods. The sample uses a separate handler for the firmware update direct method.
- Start sending telemetry.
client.open(function (err) { if (err) { printErrorFor('open')(err); } else { // Create device Twin client.getTwin(function (err, twin) { if (err) { console.error('Could not get device twin'); } else { console.log('Device twin created'); twin.on('properties.desired', function (delta) { // Handle desired properties set by solution console.log('Received new desired properties:'); console.log(JSON.stringify(delta)); }); // Send reported properties twin.properties.reported.update(reportedProperties, function (err) { if (err) throw err; console.log('Twin state reported'); }); // Register handlers for all the method names we are interested in. // Consider separate handlers for each method. client.onDeviceMethod('Reboot', onDirectMethod); client.onDeviceMethod('FirmwareUpdate', onFirmwareUpdate); client.onDeviceMethod('EmergencyValveRelease', onDirectMethod); client.onDeviceMethod('IncreasePressure', onDirectMethod); } }); // Start sending telemetry var sendTemperatureInterval = setInterval(function () { temperature += generateRandomIncrement(); var data = { 'temperature': temperature, 'temperature_unit': temperatureUnit }; sendTelemetry(data, temperatureSchema) }, 5000); var sendHumidityInterval = setInterval(function () { humidity += generateRandomIncrement(); var data = { 'humidity': humidity, 'humidity_unit': humidityUnit }; sendTelemetry(data, humiditySchema) }, 5000); var sendPressureInterval = setInterval(function () { pressure += generateRandomIncrement(); var data = { 'pressure': pressure, 'pressure_unit': pressureUnit }; sendTelemetry(data, pressureSchema) }, 5000); client.on('error', function (err) { printErrorFor('client')(err); if (sendTemperatureInterval) clearInterval(sendTemperatureInterval); if (sendHumidityInterval) clearInterval(sendHumidityInterval); if (sendPressureInterval) clearInterval(sendPressureInterval); client.close(printErrorFor('client.close')); }); } });
-
Save the changes to the remote_monitoring.js file.
-
To launch the sample application, run the following command at your command prompt on the Raspberry Pi:
node remote_monitoring.js
[!INCLUDE iot-suite-visualize-connecting]