mock-devices-de v10 (Docker Edition)

April 27, 2021 ยท View on GitHub

mock-devices is a simulation engine that manages and runs simulated devices that connect to an Azure Iot Hub. When hosted in the Azure IoT Edge runtime, the engine will simulate Edge modules too. The simulated devices and modules implement D2C/C2D scenarios i.e telemetry, twin and commands as supported by the Azure IoT Device SDK

Each configured device/module acts independently of other devices/modules running within the engine. Each has its own model (capabilities), configuration and connection details. Devices/modules running on the same simulation engine can be a mix of connection strings, DPS, SaS, Edge modules. The engine has additional scenarios like cloning, bulk, templates and acknowledgements. See internal help

The Docker edition is a Docker container of the running simulation engine. It exposes a REST endpoint for control and data plane operations. Use this edition to run the Edge modules configured in a mock-devices state file (and deploy as an Edge module) It is also useful where a headless simulation experience per state file is required. It is part of a suite of mock-devices tools

Other editions

  • The mock-devices edition of the tool is the single install UX + engine experience. It provides an interactive experience for running and controlling the simulation as well as managing the devices/modules. It can be used to create a state file which can be used to run any mock-devices engine of the same version

  • The mock-devices-edge edition is a Docker container configured as an Edge module that can be used to manage basic operations for running instances of mock-devices-de within the same Edge runtime. Clients can interact with the simulation engine using Twin Desired and Direct Commands making it an alternative to doing REST

  • The mdux edition is a Docker container build of the desktop edition. It is a fully functional UX + simulation engine mock-devices instance and is useful for dynamic module scenarios. It is feature limited to run inside containers with no access to file system. It can also be used as a "terminal" UX experience to see other running mock-devices engines connecting via IP or DNS

State file

See the desktop edition for details about the state file

Getting started

General - Get and run the public Docker Hub version

Install Docker and execute the following commands. Default port is 9000. The following example remaps to 8989

docker pull codetunez/mock-devices-de

docker run -p 8989:9000 codetunez/mock-devices-de

Developer - Build and run a local version

Clone this repo and from the command line

Node

Install and Build

npm ci && npm run build

node _dist/start.js

Docker

Building the container
docker build -t <namespace>/mock-devices-de .

Run the container (changing port)
docker run -p 8989:9000 -d <namespace>/mock-devices-de

Loading data and running the engine

Use a state file

Once the container is running, load a state file by using one generated by the desktop edition or by using APIs below

See running containers
docker ps

Load your state (or reload new state)
curl -d "@<state file>" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/state

Start all devices
curl http://127.0.0.1:8989/api/devices/start

See the console
docker logs -f <container id>

Stop all devices
curl http://127.0.0.1:8989/api/devices/stop

API

These docs do not have the complete updates for Plan mode, Edge modules, SSE, Simulation and changes to response payloads. Doc will incrementally update

mock-devices DE has several resource endpoints to manage the simulation

The following endpoints deal with the core of creating, running and updating devices as well as adding capabilities to send and receive data.

  • /api/device - CRUD for a device including start/stopping the device
  • /api/devices - Gets the list of current devices and starts/stops all devices

The following endpoints deal with the service state and the misc simulation variables

  • /api/state - Gets or sets the current state machine and simulation config
  • /api/simulation - Gets or sets the simulation configuration. All devices restart required

The following endpoint deals with reference data

  • /api/sensors - Gets a list of fake sensors that can be added to a device to send simulated data

Objects

state object

propertytypeusage
devicesarrayThe list of device objects in the state machine
simulationobjectThe current configuration for the simulation

EXAMPLE

{
    "devices" : []
    "simulation" : {}
}

POST

state object
<devices> curl -d "<state>" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/state
only simulation object
<devices> curl -d "<simulation>" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/simulation

GET

state object
<state> curl http://127.0.0.1:8989/api/state
only devices array
<devices> curl http://127.0.0.1:8989/api/devices
only simulation object
<simulation> curl http://127.0.0.1:8989/api/simulation

Transport - All Devices

GET

<devices> curl http://127.0.0.1:8989/api/devices/start
<devices> curl http://127.0.0.1:8989/api/devices/stop


device object

The device object defines all of the device's or module's inputs/outputs and it's connection configuration.

propertytypeusage
_idstringDevice id in IoT Hub or DeviceId + ModuleId
commsarrayThe list of comm objects
configurationobjectThe connection information of a device
planarrayThe list of plan objects (TBD)

To represent a module, format the id using <deviceId>moduleId

Example for device

{
    "_id: "a-device",
    "configuration" : { _type: 'dps'}
    "comms" : [],
    "plan" : []    
}

Example for module

{
    "_id: "<a-device>a-module",
    "configuration" : { _type: 'module'},
    "comms" : [],
    "plan" : []
}

POST
<device> curl -d "<configuration>" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/new

DELETE
<devices> curl -X DELETE http://127.0.0.1:8989/api/device/<device id>

PUT
can only update configuration
<device> curl -d "<configuration>" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/<device id>

GET
<device> curl http://127.0.0.1:8989/api/device/<device id>

Transport - This device only

GET
<device> curl http://127.0.0.1:8989/api/device/<device id>/start
<device> curl http://127.0.0.1:8989/api/device/<device id>/stop

configuration object

Defines the connection configuration of the device and the initial create profile. Changes here require device restart

propertytypeusage
_kindstring'dps' | 'template' | 'hub' | 'module | 'edge'
deviceIdstringDevice id in IoT Hub or DeviceId + ModuleId
scopeIdstringDPS scope ID
dpsPayloadobjectJSON payload as supported by DPS/Hub build
sasKeystringDevice SaS key
isMasterKeyboolean'true' |'false'. If true, device id is ignored and issued during DPS registration
mockDeviceNamestringFriendly name used by the GUI
mockDeviceCountstring?Device number to start from in bulk create
mockDeviceCountMaxstring?Device number to end on in bulk create
connectionStringstring?Uses Azure IoT Hub connection string if _kind is hub
planModebooleanSwitch this device to use plan mode
modulesarrayThe list of _ids that are modules for the Edge device
{
 "_kind": "dps"
 "deviceId" : "any-device-id"
 "scopeId": "00SED0045",
 "dpsPayload": {},
 "sasKey": "3249cxvnkj43579fdsg+=DFS90832",
 "isMasterKey": false,
 "mockDeviceName": "My Device",
 "mockDeviceCount": 1,
 "mockDeviceCountMax": 1,
}

This object can only be updated with the device object POST and PUT API

comm object

A communication or comm object is used to define the type of input/output the device will do. There are 3 kinds of comm objects. Reported (send data), Desired (receive data) and Method (execute). The device does not have to be stopped to add or remove comm objects except methods

This is how to use the comm object to define a data send property

propertytypeusage
_idstringA unique id for this property. Needs to be unique for the simulation i.e. a GUID
_typestring'property' |'method'. Msg/Events and Twins are properties. Primarily used by the GUI
colorstring?Only used by GUI
enabledbooleanOnly honored when device is in a runloop. Updating a device will send value immediately
interfacestringName the comm property. Not supported
stringbooleantrue for values to be treated as strings i.e. text and dates. false for all other types i.e. object, arrays and booleans
valueanyThe current value of the property. Update this to send a new value (PUT) for the device. Supports AUTO_* in a runloop
sdkstringDetermine how to send the device data. msg for SDK event/message API or twin for SDK Device Twin API
versionnumberReadonly. Lifecycle count of Twin updates for this device (only for Desired fields)
mockobject?See later
propertyObjectobjectSee later
typeobjectSee later
runloopobject?See later

mock object

To use a mock sensor, assign a <sensor> to the mock property and change the mock value in the type object

GET
<sensors> curl http://127.0.0.1:8989/api/sensors
<sensor> curl http://127.0.0.1:8989/api/sensors/<type>

projectObject object

The propertyObject defines if the value field is literal or a JSON object. The latter is the second property in this object should must be stringified. Supports AUTO_*

propertytypeusage
typestring'default' |'templated'
templatedstringJSON object (optional)

type object

The type defines how to comm object binds to the SDK

propertytypeusage
mockanyuse false to switch off mock sensor. Value is a sensor when mock sensor is on
directionstring'd2c' |'c2d'. Used to bind the comm object to the SDK. Honors sdk type and sets up the property to be read/desired or send/reported

runloop object

Use the runloop object to send the value on a timed loop.

propertytypeusage
includebooleanfalse to not include this property in a runloop
unitstring'secs' |'mins'
valueintegerHonors unit

EXAMPLE

{
    "_id": "b07f4260-df7d-4d79-8f2d-02cf9d8c5b1f",
    "_type": "property",
    "name": "d2cProperty_1d57",
    "color": "#333333",
    "enabled": false,
    "interface": "(single interface only)",
    "string": false,
    "value": 0,
    "sdk": "msg",
    "propertyObject": {
        "type": "default"
    },
    "version": 0,
    "type": {
        "mock": false,
        "direction": "d2c"
    },
    "runloop": {
        "include": false,
        "unit": "secs",
        "value": 15
    }
}

POST
create the comm with defaults
<device> curl -d "{type:'d2c'}" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/<device id>/property/new
create the default mock sensor and assign to comm
<device> curl -X POST http://127.0.0.1:8989/api/device/<device id>/property/<property id>/mock/new

PUT
<device> curl -d "{comm}" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/<device id>/property/<property id>

DELETE
<devices> curl -X DELETE http://127.0.0.1:8989/api/device/<device id>/property/<property id>

GET
<devices> curl http://127.0.0.1:8989/api/device/<device id>/property/<property id>

This is how to use the comm object to define a data receive property

runloop not required

EXAMPLE

{
    "_id": "61cdc84c-20e8-4066-9e39-22a3868719bf",
    "_type": "property",
    "enabled": true,
    "name": "c2dProperty_a8e3",
    "color": "#383a1e",
    "interface": "(single interface only)",
    "string": false,
    "value": 0,
    "sdk": "twin",
    "propertyObject": {
        "type": "default"
    },
    "version": 0,
    "type": {
        "mock": false,
        "direction": "c2d"
    }
}

POST
create the comm with defaults
<device> curl -d "{type:'c2d'}" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/<device id>/property/new\

PUT
update comm and not value
<device> curl -d "{comm}" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/<device id>/property/<property id>
update comm and value
<device> curl -d "{comm}" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/<device id>/property/<property id>/value

DELETE
<devices> curl -X DELETE http://127.0.0.1:8989/api/device/<device id>/property/<property id>

GET
<devices> curl http://127.0.0.1:8989/api/device/<device id>/property/<property id>

This is how to use the comm object to define a method property

propertytypeusage
_idstringA unique id for this property. Needs to be unique for the simulation i.e. a GUID
_typestring'property' |'method'. Msg/Events and Twins are properties. Primarily used by the GUI
colorstring?Only used by GUI
enabledbooleanOnly honored when device is in a runloop. Updating a device will send value immediately
interfacestringName the comm property. Not supported
statusintegerHTTP status code
receivedParamsstringThe parameters sent to the device from the caller
asPropertybooleanSend the result as a reported Twin reported as well
payloadstringThe result JSON

EXAMPLE

{
    "_id": "27dfec16-128a-4cf8-bbba-89263c51f4d3",
    "_type": "method",
    "enabled": true,
    "name": "method47a2",
    "color": "#3a1e1e",
    "interface": "(single interface only)",
    "status": 200,
    "receivedParams": null,
    "asProperty": false,
    "payload": "{\n  \"result\": \"OK\"\n}"
}

POST
create the comm with defaults
<device> curl -X POST http://127.0.0.1:8989/api/device/<device id>/method/new

PUT
<device> curl -d "{comm}" -H "Content-Type: application/json" -X POST http://127.0.0.1:8989/api/device/<device id>/method/<method id>

DELETE
<devices> curl -X DELETE http://127.0.0.1:8989/api/device/<device id>/method/<method id>

GET
<devices> curl http://127.0.0.1:8989/api/device/<device id>/<method>/<method id>

Sample device object JSON

{
    "comms": [
        {
            "_id": "b07f4260-df7d-4d79-8f2d-02cf9d8c5b1f",
            "_type": "property",
            "name": "d2cProperty_1d57",
            "color": "#333333",
            "enabled": false,
            "interface": "(single interface only)",
            "string": false,
            "value": 0,
            "sdk": "msg",
            "propertyObject": {
                "type": "default"
            },
            "version": 0,
            "type": {
                "mock": true,
                "direction": "d2c"
            },
            "runloop": {
                "include": true,
                "unit": "secs",
                "value": 15
            },
            "mock": {
                "_id": "2400337d-46dd-4885-857f-306bf4761e75",
                "_hasState": false,
                "_type": "random",
                "_value": 0,
                "variance": 3,
                "init": 0,
                "_resx": {
                    "init": "Initial",
                    "variance": "Length"
                }
            }
        },
        {
            "_id": "61cdc84c-20e8-4066-9e39-22a3868719bf",
            "_type": "property",
            "enabled": true,
            "name": "c2dProperty_a8e3",
            "color": "#383a1e",
            "interface": "(single interface only)",
            "string": false,
            "value": 0,
            "sdk": "twin",
            "propertyObject": {
                "type": "default"
            },
            "version": 0,
            "type": {
                "mock": false,
                "direction": "c2d"
            }
        },
        {
            "_id": "27dfec16-128a-4cf8-bbba-89263c51f4d3",
            "_type": "method",
            "enabled": true,
            "name": "method47a2",
            "color": "#3a1e1e",
            "interface": "(single interface only)",
            "status": 200,
            "receivedParams": null,
            "asProperty": false,
            "payload": "{\n  \"result\": \"OK\"\n}"
        }
    ],
    "configuration": {
        "_kind": "dps",
        "deviceId": "any-device-id",
        "scopeId": "00SED0045",
        "dpsPayload": {},
        "sasKey": "3249cxvnkj43579fdsg+=DFS90832",
        "isMasterKey": false,
        "mockDeviceName": "My Device",
    },
    "_id": "any-device-id",
    "running": false
}

Macros and AUTO values

AUTO values are macros that are replaced with real (random) values when the device sends its data. They can be used in value fields and complex payloads (for sending random data on the leaf nodes) Replace AUTO string with a static value if the feature is not required.

Complex values must be authored using JSON so use the macro as a string (see examples). When using AUTOs, the property's type will be replaced. See the following.

  • AUTO_STRING - Use a random word from 'random-words' library
  • AUTO_BOOLEAN - Use a random true or false
  • AUTO_INTEGER - Use a random number
  • AUTO_LONG - Use a random number
  • AUTO_DOUBLE - Use a random number with precision
  • AUTO_FLOAT - Use a random number with precision
  • AUTO_DATE - Use now() ISO 8601 format
  • AUTO_DATETIME - Use now() ISO 8601 format
  • AUTO_TIME - Use now() ISO 8601 format
  • AUTO_DURATION - Use now() ISO 8601 format
  • AUTO_GEOPOINT - Use a random lat/long object
  • AUTO_VECTOR - Use a random x,y,z
  • AUTO_MAP - Use empty HashMap
  • AUTO_ENUM/* - Use a random Enum value (See below)
  • AUTO_VALUE - Use the last user supplied or mock sensor value. Honors String setting.

Enum support is possible by extending the macro to include the list of values. Values can only be integers or strings. Enums use the JavaScript style arrays i.e. [1,0] or ['foo','bar'] Append this to the end of an Enum AUTO like ...

AUTO_ENUM/['foo','bar']

The default geo location is London, UK. This can be changed in the Simulation object

Complex Examples A JSON with AUTO values and statics

{
  "complexObject": {
    "nestedObj": {
      "nestedObj": {
        "param3": "AUTO_BOOLEAN"
      },
      "param1": "AUTO_INTEGER",
      "param2": "Hello World!"
    },
    "param1": "AUTO_VALUE",
    "param2": "AUTO_STRING",
    "param3": true
  }
}

An X,Y,Z object

{
  "x": "AUTO_INTEGER",
  "y": "AUTO_INTEGER",
  "z": 150
}

An Array

['AUTO_INTEGER', 5, 'AUTO_DOUBLE', 999]

A Map - Needs static keys (Beta)

{
  "map": {
    "mapKey1": "AUTO_STRING",
    "mapKey2": "AUTO_STRING",
    "mapKey3": "AUTO_STRING"
  }
}