Use device management to initiate a device firmware update (Node/Node)

In the Get started with device management tutorial, you saw how to use the device twin and direct methods primitives to remotely reboot a device. This tutorial uses the same IoT Hub primitives and provides guidance and shows you how to do an end-to-end simulated firmware update. This pattern is used in the firmware update implementation for the Intel Edison device sample.

Note

The features described in this article are only available in the standard tier of IoT hub. For more information about the basic and standard IoT Hub tiers, see How to choose the right IoT Hub tier.

This tutorial shows you how to:

  • Create a Node.js console app that calls the firmwareUpdate direct method in the simulated device app through your IoT hub.
  • Create a simulated device app that implements a firmwareUpdate direct method. This method initiates a multi-stage process that waits to download the firmware image, downloads the firmware image, and finally applies the firmware image. During each stage of the update, the device uses the reported properties to report on progress.

At the end of this tutorial, you have two Node.js console apps:

dmpatterns_fwupdate_service.js, which calls a direct method in the simulated device app, displays the response, and periodically (every 500ms) displays the updated reported properties.

dmpatterns_fwupdate_device.js, which connects to your IoT hub with the device identity created earlier, receives a firmwareUpdate direct method, runs through a multi-state process to simulate a firmware update including: waiting for the image download, downloading the new image, and finally applying the image.

To complete this tutorial, you need the following:

  • Node.js version 4.0.x or later,
    Prepare your development environment describes how to install Node.js for this tutorial on either Windows or Linux.
  • An active Azure account. (If you don't have an account, you can create a free account in just a couple of minutes.)

Follow the Get started with device management article to create your IoT hub and get your IoT Hub connection string.

Create an IoT hub

Create an IoT hub for your simulated device app to connect to. The following steps show you how to complete this task by using the Azure portal.

  1. Sign in to the Azure portal.

  2. Select Create a resource > Internet of Things > IoT Hub.

    Azure portal Jumpbar

  3. In the IoT hub pane, enter the following information for your IoT hub:

    • Subscription: Choose the subscription that you want to use to create this IoT hub.

    • Resource group: Create a resource group to host the IoT hub or use an existing one. For more information, see Use resource groups to manage your Azure resources.

    • Region: Select the closest location to you.

    • Name: Create a name for your IoT hub. If the name you enter is available, a green check mark appears.

    Important

    The IoT hub will be publicly discoverable as a DNS endpoint, so make sure to avoid any sensitive information while naming it.

    IoT Hub basics window

  4. Select Next: Size and scale to continue creating your IoT hub.

  5. Choose your Pricing and scale tier. For this article, select the F1 - Free tier if it's still available on your subscription. For more information, see the Pricing and scale tier.

    IoT Hub size and scale window

  6. Select Review + create.

  7. Review your IoT hub information, then click Create. Your IoT hub might take a few minutes to create. You can monitor the progress in the Notifications pane.

  8. When your new IoT hub is ready, click its tile in the Azure portal to open its properties window. Now that you have created an IoT hub, locate the important information that you use to connect devices and applications to your IoT hub. Click Shared access policies.

  9. In Shared access policies, select the iothubowner policy. Copy the IoT Hub Connection string---primary key to use later. For more information, see Access control in the "IoT Hub developer guide."

    Shared access policies

Create a device identity

In this section, you use a Node.js tool called iothub-explorer to create a device identity for this tutorial. Device IDs are case sensitive.

  1. Run the following in your command-line environment:

    npm install -g iothub-explorer@latest

  2. Then, run the following command to login to your hub. Substitute {iot hub connection string} with the IoT Hub connection string you previously copied:

    iothub-explorer login "{iot hub connection string}"

  3. Finally, create a new device identity called myDeviceId with the command:

    iothub-explorer create myDeviceId --connection-string

    Important

    The device ID may be visible in the logs collected for customer support and troubleshooting, so make sure to avoid any sensitive information while naming it.

Make a note of the device connection string from the result. This device connection string is used by the device app to connect to your IoT Hub as a device.

Refer to Getting started with IoT Hub to programmatically create device identities.

Trigger a remote firmware update on the device using a direct method

In this section, you create a Node.js console app that initiates a remote firmware update on a device. The app uses a direct method to initiate the update and uses device twin queries to periodically get the status of the active firmware update.

  1. Create an empty folder called triggerfwupdateondevice. In the triggerfwupdateondevice folder, create a package.json file using the following command at your command prompt. Accept all the defaults:

    npm init
    
  2. At your command prompt in the triggerfwupdateondevice folder, run the following command to install the azure-iot-hub package:

    npm install azure-iothub --save
    
  3. Using a text editor, create a dmpatterns_getstarted_service.js file in the triggerfwupdateondevice folder.
  4. Add the following 'require' statements at the start of the dmpatterns_getstarted_service.js file:

    'use strict';
    
    var Registry = require('azure-iothub').Registry;
    var Client = require('azure-iothub').Client;
    
  5. Add the following variable declarations and replace the placeholder values:

    var connectionString = '{device_connectionstring}';
    var registry = Registry.fromConnectionString(connectionString);
    var client = Client.fromConnectionString(connectionString);
    var deviceToUpdate = 'myDeviceId';
    
  6. Add the following function to find and display the value of the firmwareUpdate reported property.

    var queryTwinFWUpdateReported = function() {
        registry.getTwin(deviceToUpdate, function(err, twin){
            if (err) {
              console.error('Could not query twins: ' + err.constructor.name + ': ' + err.message);
            } else {
              console.log((JSON.stringify(twin.properties.reported.iothubDM.firmwareUpdate)) + "\n");
            }
        });
    };
    
  7. Add the following function to invoke the firmwareUpdate method to reboot the target device:

    var startFirmwareUpdateDevice = function() {
      var params = {
          fwPackageUri: 'https://secureurl'
      };
    
      var methodName = "firmwareUpdate";
      var payloadData =  JSON.stringify(params);
    
      var methodParams = {
        methodName: methodName,
        payload: payloadData,
        timeoutInSeconds: 30
      };
    
      client.invokeDeviceMethod(deviceToUpdate, methodParams, function(err, result) {
        if (err) {
          console.error('Could not start the firmware update on the device: ' + err.message)
        } 
      });
    };
    
  8. Finally, Add the following function to code to start the firmware update sequence and start periodically showing the reported properties:

    startFirmwareUpdateDevice();
    setInterval(queryTwinFWUpdateReported, 500);
    
  9. Save and close the dmpatterns_fwupdate_service.js file.

Create a simulated device app

In this section, you:

  • Create a Node.js console app that responds to a direct method called by the cloud
  • Trigger a simulated firmware update
  • Use the reported properties to enable device twin queries to identify devices and when they last completed a firmware update
  1. Create an empty folder called manageddevice. In the manageddevice folder, create a package.json file using the following command at your command prompt. Accept all the defaults:

    npm init
    
  2. At your command prompt in the manageddevice folder, run the following command to install the azure-iot-device and azure-iot-device-mqtt Device SDK packages:

    npm install azure-iot-device azure-iot-device-mqtt --save
    
  3. Using a text editor, create a dmpatterns_fwupdate_device.js file in the manageddevice folder.

  4. Add the following 'require' statements at the start of the dmpatterns_fwupdate_device.js file:

    'use strict';
    
    var Client = require('azure-iot-device').Client;
    var Protocol = require('azure-iot-device-mqtt').Mqtt;
    
  5. Add a connectionString variable and use it to create a Client instance. Replace the {yourdeviceconnectionstring} placeholder with the connection string you previously made a note of in the "Create a device identity" section previously:

    var connectionString = '{yourdeviceconnectionstring}';
    var client = Client.fromConnectionString(connectionString, Protocol);
    
  6. Add the following function that is used to update reported properties:

    var reportFWUpdateThroughTwin = function(twin, firmwareUpdateValue) {
      var patch = {
          iothubDM : {
            firmwareUpdate : firmwareUpdateValue
          }
      };
    
      twin.properties.reported.update(patch, function(err) {
        if (err) throw err;
        console.log('twin state reported: ' + firmwareUpdateValue.status);
      });
    };
    
  7. Add the following functions that simulate downloading and applying the firmware image:

    var simulateDownloadImage = function(imageUrl, callback) {
      var error = null;
      var image = "[fake image data]";
    
      console.log("Downloading image from " + imageUrl);
    
      callback(error, image);
    }
    
    var simulateApplyImage = function(imageData, callback) {
      var error = null;
    
      if (!imageData) {
        error = {message: 'Apply image failed because of missing image data.'};
      }
    
      callback(error);
    }
    
  8. Add the following function that updates the firmware update status through the reported properties to waiting. Typically, devices are informed of an available update and an administrator defined policy causes the device to start downloading and applying the update. This function is where the logic to enable that policy should run. For simplicity, the sample waits for four seconds before proceeding to download the firmware image:

    var waitToDownload = function(twin, fwPackageUriVal, callback) {
      var now = new Date();
    
      reportFWUpdateThroughTwin(twin, {
        fwPackageUri: fwPackageUriVal,
        status: 'waiting',
        error : null,
        startedWaitingTime : now.toISOString()
      });
      setTimeout(callback, 4000);
    };
    
  9. Add the following function that updates the firmware update status through the reported properties to downloading. The function then simulates a firmware download and finally updates the firmware update status to either downloadFailed or downloadComplete:

    var downloadImage = function(twin, fwPackageUriVal, callback) {
      var now = new Date();   
    
      reportFWUpdateThroughTwin(twin, {
        status: 'downloading',
      });
    
      setTimeout(function() {
        // Simulate download
        simulateDownloadImage(fwPackageUriVal, function(err, image) {
    
          if (err)
          {
            reportFWUpdateThroughTwin(twin, {
              status: 'downloadfailed',
              error: {
                code: error_code,
                message: error_message,
              }
            });
          }
          else {        
            reportFWUpdateThroughTwin(twin, {
              status: 'downloadComplete',
              downloadCompleteTime: now.toISOString(),
            });
    
            setTimeout(function() { callback(image); }, 4000);   
          }
        });
    
      }, 4000);
    }
    
  10. Add the following function that updates the firmware update status through the reported properties to applying. The function then simulates applying the firmware image and finally updates the firmware update status to either applyFailed or applyComplete:

    var applyImage = function(twin, imageData, callback) {
      var now = new Date();   
    
      reportFWUpdateThroughTwin(twin, {
        status: 'applying',
        startedApplyingImage : now.toISOString()
      });
    
      setTimeout(function() {
    
        // Simulate apply firmware image
        simulateApplyImage(imageData, function(err) {
          if (err) {
            reportFWUpdateThroughTwin(twin, {
              status: 'applyFailed',
              error: {
                code: err.error_code,
                message: err.error_message,
              }
            });
          } else { 
            reportFWUpdateThroughTwin(twin, {
              status: 'applyComplete',
              lastFirmwareUpdate: now.toISOString()
            });    
    
          }
        });
    
        setTimeout(callback, 4000);
    
      }, 4000);
    }
    
  11. Add the following function that handles the firmwareUpdate direct method and initiates the multi-stage firmware update process:

    var onFirmwareUpdate = function(request, response) {
    
      // Respond the cloud app for the direct method
      response.send(200, 'FirmwareUpdate started', function(err) {
        if (!err) {
          console.error('An error occured when sending a method response:\n' + err.toString());
        } else {
          console.log('Response to method \'' + request.methodName + '\' sent successfully.');
        }
      });
    
      // Get the parameter from the body of the method request
      var fwPackageUri = request.payload.fwPackageUri;
    
      // Obtain the device twin
      client.getTwin(function(err, twin) {
        if (err) {
          console.error('Could not get device twin.');
        } else {
          console.log('Device twin acquired.');
    
          // Start the multi-stage firmware update
          waitToDownload(twin, fwPackageUri, function() {
            downloadImage(twin, fwPackageUri, function(imageData) {
              applyImage(twin, imageData, function() {});    
            });  
          });
    
        }
      });
    }
    
  12. Finally, add the following code that connects to your IoT hub:

    client.open(function(err) {
      if (err) {
        console.error('Could not connect to IotHub client');
      }  else {
        console.log('Client connected to IoT Hub.  Waiting for firmwareUpdate direct method.');
      }
    
      client.onDeviceMethod('firmwareUpdate', onFirmwareUpdate);
    });
    

Note

To keep things simple, this tutorial does not implement any retry policy. In production code, you should implement retry policies (such as an exponential backoff), as suggested in the MSDN article Transient Fault Handling.

Run the apps

You are now ready to run the apps.

  1. At the command prompt in the manageddevice folder, run the following command to begin listening for the reboot direct method.

    node dmpatterns_fwupdate_device.js
    
  2. At the command prompt in the triggerfwupdateondevice folder, run the following command to trigger the remote reboot and query for the device twin to find the last reboot time.

    node dmpatterns_fwupdate_service.js
    
  3. You see the device response to the direct method in the console.

Next steps

In this tutorial, you used a direct method to trigger a remote firmware update on a device and used the reported properties to follow the progress of the firmware update.

To learn how to extend your IoT solution and schedule method calls on multiple devices, see the Schedule and broadcast jobs tutorial.