Create and test a new simulated device

The Remote Monitoring solution accelerator lets you define your own simulated devices. This article shows you how to define a new simulated lightbulb device and then test it locally. The solution accelerator includes simulated devices such as chillers and trucks. However, you can define your own simulated devices to test your IoT solutions before you deploy real devices.

Note

This article describes how to use simulated devices hosted in the device simulation service. If you want to create a real device, see Connect your device to the Remote Monitoring solution accelerator.

This how-to guide shows you how to customize the device simulation microservice. This microservice is part of the Remote Monitoring solution accelerator. To show the device simulation capabilities, this how-to guide uses two scenarios in the Contoso IoT application:

In the first scenario, you add a new telemetry type to Contoso's existing Chiller device type.

In the second scenario, Contoso wants to test a new smart lightbulb device. To run the tests, you create a new simulated device with the following characteristics:

Properties

Name Values
Color White, Red, Blue
Brightness 0 to 100
Estimated remaining life Countdown from 10,000 hours

Telemetry

The following table shows the data the lightbulb reports to the cloud as a data stream:

Name Values
Status "on", "off"
Temperature Degrees F
online true, false

Note

The online telemetry value is mandatory for all simulated types.

Methods

The following table shows the actions the new device supports:

Name
Switch on
Switch off

Initial state

The following table shows the initial status of the device:

Name Values
Initial color White
Initial brightness 75
Initial remaining life 10,000
Initial telemetry status "on"
Initial telemetry temperature 200

To complete the steps in this how-to guide, you need an active Azure subscription.

If you don't have an Azure subscription, create a free account before you begin.

Use Azure Cloud Shell

Azure hosts Azure Cloud Shell, an interactive shell environment that you can use through your browser. You can use either Bash or PowerShell with Cloud Shell to work with Azure services. You can use the Cloud Shell preinstalled commands to run the code in this article without having to install anything on your local environment.

To start Azure Cloud Shell:

Option Example/Link
Select Try It in the upper-right corner of a code block. Selecting Try It doesn't automatically copy the code to Cloud Shell. Example of Try It for Azure Cloud Shell
Go to https://shell.azure.com, or select the Launch Cloud Shell button to open Cloud Shell in your browser. Launch Cloud Shell in a new window
Select the Cloud Shell button on the menu bar at the upper right in the Azure portal. Cloud Shell button in the Azure portal

To run the code in this article in Azure Cloud Shell:

  1. Start Cloud Shell.

  2. Select the Copy button on a code block to copy the code.

  3. Paste the code into the Cloud Shell session by selecting Ctrl+Shift+V on Windows and Linux or by selecting Cmd+Shift+V on macOS.

  4. Select Enter to run the code.

Prerequisites

To follow this how-to guide, you need:

Prepare your development environment

Complete the following tasks to prepare your development environment:

  • Download the source for the device simulation microservice.
  • Download the source for the storage adapter microservice.
  • Run the storage adapter microservice locally.

The instructions in this article assume you're using Windows. If you're using another operating system, you may need to adjust some of the file paths and commands to suit your environment.

Download the microservices

Download and unzip the remote monitoring microservices from GitHub to a suitable location on your local machine. The article assumes the name of this folder is remote-monitoring-services-dotnet-master.

Download and unzip the device simulation microservice from GitHub to a suitable location on your local machine. The article assumes the name of this folder is device-simulation-dotnet-master.

Run the storage adapter microservice

Open the remote-monitoring-services-dotnet-master\storage-adapter folder in Visual Studio Code. Click any Restore buttons to fix any unresolved dependencies.

Open the storage-adapter/WebService/appsettings.ini file and assign your Cosmos DB connection string to the documentDBConnectionString variable.

To run the microservice locally, click Debug > Start Debugging.

The Terminal window in Visual Studio Code shows output from the running microservice including a URL for the web service health check: http://127.0.0.1:9022/v1/status. When you navigate to this address, the status should be "OK: Alive and well".

Leave the storage adapter microservice running in this instance of Visual Studio Code while you complete the next steps.

Modify the chiller

In this section, you add a new Internal Temperature telemetry type to the existing Chiller device type:

  1. Create a new folder C:\temp\devicemodels on your local machine.

  2. Copy the following files to your new folder from the downloaded copy of the device simulation microservice:

    Source Destination
    Services\data\devicemodels\chiller-01.json C:\temp\devicemodels\chiller-01.json
    Services\data\devicemodels\scripts\chiller-01-state.js C:\temp\devicemodels\scripts\chiller-01-state.js
    Services\data\devicemodels\scripts\Reboot-method.js C:\temp\devicemodels\scripts\Reboot-method.js
    Services\data\devicemodels\scripts\FirmwareUpdate-method.js C:\temp\devicemodels\scripts\FirmwareUpdate-method.js
    Services\data\devicemodels\scripts\EmergencyValveRelease-method.js C:\temp\devicemodels\scripts\EmergencyValveRelease-method.js
    Services\data\devicemodels\scripts\IncreasePressure-method.js C:\temp\devicemodels\scripts\IncreasePressure-method.js
  3. Open the C:\temp\devicemodels\chiller-01.json file.

  4. In the InitialState section, add the following two definitions:

    "internal_temperature": 65.0,
    "internal_temperature_unit": "F",
    
  5. In the Telemetry array, add the following definition:

    {
      "Interval": "00:00:05",
      "MessageTemplate": "{\"internal_temperature\":${internal_temperature},\"internal_temperature_unit\":\"${internal_temperature_unit}\"}",
      "MessageSchema": {
        "Name": "chiller-internal-temperature;v1",
        "Format": "JSON",
        "Fields": {
          "temperature": "double",
          "temperature_unit": "text"
        }
      }
    },
    
  6. Save the C:\temp\devicemodels\chiller-01.json file.

  7. Open the C:\temp\devicemodels\scripts\chiller-01-state.js file.

  8. Add the following fields to the state variable:

    internal_temperature: 65.0,
    internal_temperature_unit: "F",
    
  9. Update the main function as follows:

    function main(context, previousState, previousProperties) {
    
        // Restore the global state before generating the new telemetry, so that
        // the telemetry can apply changes using the previous function state.
        restoreSimulation(previousState, previousProperties);
    
        // 75F +/- 5%,  Min 25F, Max 100F
        state.temperature = vary(75, 5, 25, 100);
    
        // 70% +/- 5%,  Min 2%, Max 99%
        state.humidity = vary(70, 5, 2, 99);
    
        // 65F +/- 2%,  Min 15F, Max 125F
        state.internal_temperature = vary(65, 2, 15, 125);
    
        log("Simulation state: " + state.simulation_state);
        if (state.simulation_state === "high_pressure") {
            // 250 psig +/- 25%,  Min 50 psig, Max 300 psig
            state.pressure = vary(250, 25, 50, 300);
        } else {
            // 150 psig +/- 10%,  Min 50 psig, Max 300 psig
            state.pressure = vary(150, 10, 50, 300);
        }
    
        updateState(state);
        return state;
    }
    
  10. Save the C:\temp\devicemodels\scripts\chiller-01-state.js file.

Create the lightbulb

In this section, you define a new Lightbulb device type:

  1. Create a file C:\temp\devicemodels\lightbulb-01.json and add the following content:

    {
      "SchemaVersion": "1.0.0",
      "Id": "lightbulb-01",
      "Version": "0.0.1",
      "Name": "Lightbulb",
      "Description": "Smart lightbulb device.",
      "Protocol": "MQTT",
      "Simulation": {
        "InitialState": {
          "online": true,
          "temperature": 200.0,
          "temperature_unit": "F",
          "status": "on"
        },
        "Interval": "00:00:20",
        "Scripts": [
          {
            "Type": "javascript",
            "Path": "lightbulb-01-state.js"
          }
        ]
      },
      "Properties": {
        "Type": "Lightbulb",
        "Color": "White",
        "Brightness": 75,
        "EstimatedRemainingLife": 10000
      },
      "Tags": {
        "Location": "Building 2",
        "Floor": "2",
        "Campus": "Redmond"
      },
      "Telemetry": [
        {
          "Interval": "00:00:20",
          "MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\",\"status\":\"${status}\"}",
          "MessageSchema": {
            "Name": "lightbulb-status;v1",
            "Format": "JSON",
            "Fields": {
              "temperature": "double",
              "temperature_unit": "text",
              "status": "text"
            }
          }
        }
      ],
      "CloudToDeviceMethods": {
        "SwitchOn": {
          "Type": "javascript",
          "Path": "SwitchOn-method.js"
        },
        "SwitchOff": {
          "Type": "javascript",
          "Path": "SwitchOff-method.js"
        }
      }
    }
    

    Save the changes to C:\temp\devicemodels\lightbulb-01.json.

  2. Create a file C:\temp\devicemodels\scripts\lightbulb-01-state.js and add the following content:

    "use strict";
    
    // Default state
    var state = {
      online: true,
      temperature: 200.0,
      temperature_unit: "F",
      status: "on"
    };
    
    // Default device properties
    var properties = {};
    
    /**
     * Restore the global state using data from the previous iteration.
     *
     * @param previousState device state from the previous iteration
     * @param previousProperties device properties from the previous iteration
     */
    function restoreSimulation(previousState, previousProperties) {
      // If the previous state is null, force a default state
      if (previousState) {
        state = previousState;
      } else {
        log("Using default state");
      }
    
      if (previousProperties) {
        properties = previousProperties;
      } else {
        log("Using default properties");
      }
    }
    
    /**
     * Simple formula generating a random value around the average
     * in between min and max
     *
     * @returns random value with given parameters
     */
    function vary(avg, percentage, min, max) {
      var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
      value = Math.max(value, min);
      value = Math.min(value, max);
      return value;
    }
    
    /**
     * Simple formula that sometimes flips the status of the lightbulb
     */
    function flip(value) {
      if (Math.random() < 0.2) {
        return (value == "on") ? "off" : "on"
      }
      return value;
    }
    
    /**
     * Entry point function called by the simulation engine.
     * Returns updated simulation state.
     * Device property updates must call updateProperties() to persist.
     *
     * @param context             The context contains current time, device model and id
     * @param previousState       The device state since the last iteration
     * @param previousProperties  The device properties since the last iteration
     */
    function main(context, previousState, previousProperties) {
    
      // Restore the global device properties and the global state before
      // generating the new telemetry, so that the telemetry can apply changes
      // using the previous function state.
      restoreSimulation(previousState, previousProperties);
    
      state.temperature = vary(200, 5, 150, 250);
    
      // Make this flip every so often
      state.status = flip(state.status);
    
      updateState(state);
    
      return state;
    }
    

    Save the changes to C:\temp\devicemodels\scripts\lightbulb-01-state.js.

  3. Create a file C:\temp\devicemodels\scripts\SwitchOn-method.js and add the following content:

    "use strict";
    
    // Default state
    var state = {
      status: "on"
    };
    
    /**
     * Entry point function called by the method.
     *
     * @param context        The context contains current time, device model and id
     * @param previousState  The device state since the last iteration
     * @param previousProperties  The device properties since the last iteration
     */
    function main(context, previousState) {
      log("Executing lightbulb Switch On method.");
      state.status = "on";
      updateState(state);
    }
    

    Save the changes to C:\temp\devicemodels\scripts\SwitchOn-method.js.

  4. Create a file C:\temp\devicemodels\scripts\SwitchOff-method.js and add the following content:

    "use strict";
    
    // Default state
    var state = {
      status: "on"
    };
    
    /**
     * Entry point function called by the method.
     *
     * @param context        The context contains current time, device model and id
     * @param previousState  The device state since the last iteration
     * @param previousProperties  The device properties since the last iteration
     */
    function main(context, previousState) {
      log("Executing lightbulb Switch Off method.");
      state.status = "off";
      updateState(state);
    }
    

    Save the changes to C:\temp\devicemodels\scripts\SwitchOff-method.js.

You've now created a customized version of the Chiller device type and created a new Lightbulb device type.

Test the devices

In this section, you test the device types you created in the previous sections locally.

Run the device simulation microservice

Open the device-simulation-dotnet-master folder you downloaded from GitHub in a new instance of Visual Studio Code. Click any Restore buttons to fix any unresolved dependencies.

Open the WebService/appsettings.ini file and assign your Cosmos DB connection string to the documentdb_connstring variable and also modify the settings as follows:

device_models_folder = C:\temp\devicemodels\

device_models_scripts_folder = C:\temp\devicemodels\scripts\

To run the microservice locally, click Debug > Start Debugging.

The Terminal window in Visual Studio Code shows output from the running microservice.

Leave the device simulation microservice running in this instance of Visual Studio Code while you complete the next steps.

Set up a monitor for device events

In this section, you use the Azure CLI to set up an event monitor to view the telemetry sent from the devices connected to your IoT hub.

The following script assumes that the name of your IoT hub is device-simulation-test.

# Install the IoT extension if it's not already installed
az extension add --name azure-iot

# Monitor telemetry sent to your hub
az iot hub monitor-events --hub-name device-simulation-test

Leave the event monitor running while you test the simulated devices.

Create a simulation with the updated chiller device type

In this section, you use the Postman tool to request the device simulation microservice to run a simulation using the updated chiller device type. Postman is a tool that lets you send REST requests to a web service. The Postman configuration files you need are in your local copy of the device-simulation-dotnet repository.

To set up Postman:

  1. Open Postman on your local machine.

  2. Click File > Import. Then click Choose Files.

  3. Navigate to the device-simulation-dotnet-master/docs/postman folder. Select Azure IoT Device Simulation solution accelerator.postman_collection and Azure IoT Device Simulation solution accelerator.postman_environment and click Open.

  4. Expand the Azure IoT Device Simulation solution accelerator to the requests you can send.

  5. Click No Environment and select Azure IoT Device Simulation solution accelerator.

You now have a collection and environment loaded in your Postman workspace that you can use to interact with the device simulation microservice.

To configure and run the simulation:

  1. In the Postman collection, select Create modified chiller simulation and click Send. This request creates four instances of the simulated chiller device type.

  2. The event monitor output in the Azure CLI window shows the telemetry from the simulated devices, including the new internal_temperature values.

To stop the simulation, select the Stop simulation request in Postman and click Send.

Create a simulation with the lightbulb device type

In this section, you use the Postman tool to request the device simulation microservice to run a simulation using the lightbulb device type. Postman is a tool that lets you send REST requests to a web service.

To configure and run the simulation:

  1. In the Postman collection, select Create lightbulb simulation and click Send. This request creates two instances of the simulated lightbulb device type.

  2. The event monitor output in the Azure CLI window shows the telemetry from the simulated lightbulbs.

To stop the simulation, select the Stop simulation request in Postman and click Send.

Clean up resources

You can stop the two locally running microservices in their Visual Studio Code instances (Debug > Stop Debugging).

If you no longer require the IoT Hub and Cosmos DB instances, delete them from your Azure subscription to avoid any unnecessary charges.

Next steps

This guide showed you how to create a custom simulated device types and test them by running the device simulation microservice locally.

The suggested next step is to learn how to deploy your custom simulated device types to the Remote Monitoring solution accelerator.