Skip to content
This repository was archived by the owner on Oct 11, 2023. It is now read-only.

Device Models

Devis Lucato edited this page Aug 22, 2017 · 5 revisions

Each Simulated Device belongs to a specific Device Type, which defines the simulation behavior, for example how frequently telemetry is sent, what kind of messages to send, which methods are supported, etc.

Device types are defined using a set of JSON configuration files, (one file for each device type), and a set of Javascript files containing the functions to generate the random data. Normally a simulation service should have:

  • One JSON file for each device type (e.g. elevator.json)
  • One Javascript file for each device type (e.g. elevator-state.js)
  • One Javascript file for each device method (e.g. elevator-go-down.js)

The DeviceTypes folder contains some examples of these files, showing how to define a device type and how to simulate a device behavior.

Device Type JSON example

{
    "SchemaVersion": "1.0.0",
    "Id": "8dd272c0-514e-4a87-a1ad-be12a0b2809c",
    "Version": "0.0.1",
    "Name": "Elevator",
    "Description": "Simulated Elevator with floor number sensor",
    "Protocol": "AMQP",
    "DeviceState": {
        "Initial": {
            "floor": 1
        },
        "SimulationInterval": "00:00:15",
        "SimulationScript": {
            "Type": "javascript",
            "Path": "elevator-state.js"
        }
    },
    "Telemetry": [
        {
            "Interval": "00:00:01",
            "MessageTemplate": "{\"current_floor\": ${floor}}",
            "MessageSchema": {
                "Name": "elevator;v1",
                "Format": "JSON",
                "Fields": {
                    "current_floor": "integer"
                }
            }
        }
    ],
    "CloudToDeviceMethods": {
        "Start": {
            "Type": "javascript",
            "Path": "TBD.js"
        }
    }
}

Device Type Javascript simulation example

var state = {
    floor: 1,
    direction: "up"
};

function main(context, previousState) {
    restoreState(previousState);
    if (typeof(state.direction) === "undefined" || state.direction === null
        || (state.direction !== "up" && state.direction !== "down")) {
        state.direction = "up";
    }
    if (state.direction === "up") state.floor++;
    if (state.direction === "down") state.floor--;
    if (state.floor < 1) {
        state.floor = 1;
        state.direction = "up";
    }
    if (state.floor > 10) {
        state.floor = 10;
        state.direction = "down";
    }
    return state;
}


function restoreState(previousState) {
    if (typeof(previousState) !== "undefined" && previousState !== null) {
        log("Using default state");
        state = previousState;
    }
}

Thanks to these files, it is possible to add new device types and customize the simulation behavior, without rebuilding the service. To add new types or to change the simulated behavior, edit the relevant *.json and *.js files and redeploy the device simulation microservice.

Each device type file contains the definition of the simulated device type, including the following information:

  • Device type name: string
  • Protocol: AMQP | MQTT | HTTP
  • The initial device state
  • How often to refresh the device state
  • Which Javascript file to use to generate the device state
  • A list of telemetry messages to send, each with a specific frequency
  • The schema of the telemetry messages, used by backend application to parse the telemetry received
  • A list of supported methods and the Javascript file to use to simulate the method.

Check the DeviceTypes folder for some examples.

Device Type files

File schema

The schema version is always "1.0.0" and is specific to the format of this file:

"SchemaVersion": "1.0.0"

Device type description

The following properties describe the device type. Each type has a unique identifier, a semantic version, a name and a description:

"Id": "12345678-1234-1234-1234-123456789012",
"Version": "0.0.1",
"Name": "Chiller",
"Description": "Simulated Chiller with temperature and humidity sensors"

IoT Protocol

IoT devices can connect using different protocols. The simulation allows to use either AMQP, MQTT or HTTP:

"Protocol": "AMQP"

Simulated device state

Each simulated device has an internal state, which needs to be defined. The state also defines the properties that can be reported in telemetry. For example an elevator might have an initial state like:

"Initial": {
    "floor": 1
}

while a room with multiple sensors might have more properties, like:

"Initial": {
    "temperature": 50.5,
    "temperature_unit": "F",
    "humidity": 50,
    "humidity_unit": "%",
    "lights_on": false
}

The device state is kept in memory by the simulation service, and provided in input to the Javascript function. The Javascript function can decide to ignore the state and generate some random data, or to update the device status in some realistic way, given a desired scenario.

The function generating the state receives in input also the Device Id, and the current time, so it is possible to generate different data by device and by time.

Generating telemetry messages

The simulation service can send multiple messages for each device, and each message can be sent at a different frequency. Typically, a telemetry will send a message including some data from the device state. For example, a simulated room might send information about temperature and humidity every 10 seconds, and lights status once per minute. Note the placeholders, which are automatically replaced with values from the device state:

"Telemetry": [
    {
        "Interval": "00:00:10",
        "MessageTemplate":
            "{\"t\":${temperature},\"t_u\":\"${temperature_unit}\",\"h\":\"${humidity}\"}",
        "MessageSchema": {
            "Name": "RoomComfort;v1",
            "Format": "JSON",
            "Fields": {
                "t": "double",
                "t_u": "text",
                "h": "integer"
            }
        }
    },
    {
        "Interval": "00:01:00",
        "MessageTemplate": "{\"lights\":${lights_on}}",
        "MessageSchema": {
            "Name": "RoomLights;v1",
            "Format": "JSON",
            "Fields": {
                "lights": "boolean"
            }
        }
    }
],

The placeholders use a special syntax ${NAME} where NAME is a key from the device state object returned by the Javascript main() function.

Message schema

Each message type must have a well defined schema. The message schema is also published to IoT Hub, so that backend applications reuse this information to interpret the incoming telemetry.

The schema supports JSON format, which allows for easy parsing, transformation and analytics, across several systems and services.

The fields listed in the schema can be of the following types:

  • Object - serialized using JSON
  • Binary - serialized using base64
  • Text
  • Boolean
  • Integer
  • Double
  • DateTime

Supported methods

Simulated devices can also react to method calls, in which case they will execute some logic and provide some response. Similarly to the simulation, the method logic is stored in a Javascript file, and can interact with the device state. For example:

"CloudToDeviceMethods": {
    "Start": {
        "Type": "javascript",
        "Path": "truck-start.js"
    }
}

Function script files

Functions are stored in Javascript files, which are loaded and executed at runtime, using Jint, a Javascript interpreter for .NET.

The Javascript files must have a main function, and accept two parameters:

  1. a context object which contains two properties:
    1. currentTime as a string with format yyyy-MM-dd'T'HH:mm:sszzz
    2. deviceId, e.g. "Simulated.Elevator.123"
    3. deviceType, e.g. "Elevator"
  2. a state object, which is the value returned by the function in the previous call. This is device state maintained by the simulation service, and used to generate telemetry messages.

The main function returns the new device state. Example:

function main(context, state) {

    // Use context if the simulation depends on
    // time or device details.
    // Execute some logic, updating 'state'

    return state;
}

Debugging script files

While it's not possible to attach a debugger to the Javascript interpreter, it's possible to log information in the service log. For convenience, the application provides a log() function which can be used to save information useful to track and debug the function execution. In cases of syntax errors, the interpreter will fail, and the service log will contain some information about the Jint.Runtime.JavaScriptException exception occurred.

Logging example:

function main(context, state) {

    log("This message will appear in the service logs.");

    log(context.deviceId);

    if (typeof(state) !== "undefined" && state !== null) {
        log("Previous value: " + state.temperature);
    }

    // ...

    return state;
}

Clone this wiki locally