Remote device control

mDash provides two remote control mechanisms:

  • Device shadow: state management mechanism. Device shadow is a document that lives on a cloud. Shadow keeps device state, thus changing shadow makes a real device synchronise with a shadow and change its state.
  • RPC - remote procedure call: request-response mechanism. A well-known, traditional approach. A device implements a set of named functions, and those functions could be called via HTTP/REST.

Depending on a task, one or another mechanism suits better. Lets take a smart light for example: it could be turned "on" or "off".

  • Shadow mechanism. We define a variable in a shadow, call it light. When a shadow variable light is set to true, a real device gets a notification, synchronises and switches on. When light is set to false, a device is switched off. Important note: when a device reboots and connects to mDash, it synchronises with a shadow, so device state persists reboots. It is stored in a cloud.
  • RPC mechanism. We define two functions on a device, name them LightOn and LightOff. We can call either and device will react. Important note: after reboot or power off, a device would not "remember" its previous state. Also, we would need an extra RPC function to get the current state, if we want to display it on the mobile app.

In this smart light example, "remembering" the state could be an important requirement. A current state could be also cached on a device's filesystem. A firmware logic could use any of these two mechanisms.


RPC overview

RPC means Remote Procedure Call. This is the way to send commands to devices and receive replies, i.e. call remote procedures. Commands are sent to mDash via REST, and mDash sends commands to devices via JSON-RPC 2.0.

The diagram below describes an example device that implements RPC function Sum that adds numbers a and b. We can call this function on a device via REST API:

RPC call diagram

Using curl utility, the call above looks like this:

$ curl -H 'Content-Type: application/json' -d '{"a":1,"b":2}' \
  https://mdash.net/api/v2/devices/device1/rpc/Sum?access_token=API_KEY
3

You can call RPC functions on devices programmatically using any programming language / framework. Also, mDash UI provides an easy way to do it.

Call RPC via mDash UI

  • Login to mDash
  • Click on the "manage" link. NOTE: a device should be online!
  • A management dialog appears. Click on RPC tab
  • In the dropdown, select a method to call, for example Sys.GetInfo
  • Click on "Call" button, see the response

mDash device library provides many built-in RPC functions - for managing files, rebooting, getting information, OTA, etc. It is possible to create any number of custom functions using mDash library: see Library API.

List device's RPC functions

To list device's RPC functions, call a built-in function RPC.List:

$ curl https://mdash.net/api/v2/devices/DEVICE_ID/rpc/RPC.List?access_token=API_KEY
[
  "Sys.Reboot",
  "Sys.GetInfo",
  ...
]

Node.js device simulator

You can use this Node.js device simulator program to better understand how JSON-RPC works, and also to experiment with mDash. Note that built-in functions for filesystem and OTA are absent in this simulator. You could extend it with your own custom functions and manage this simulator via mDash:

// mdash.net device simulator. Run it like this:
// node mdash_device.js DEVICE_PASSWORD

const Websocket = require('ws');  // npm install -g ws
const u = 'wss://mdash.net/api/v2/rpc?access_token=' + process.argv[2];
const conn = new Websocket(u, {origin: u});
conn.on('message', msg => {
  let req = JSON.parse(msg), resp = {};  // request and response RPC frames
  if (req.method == 'Sys.GetInfo') {
    resp = {id: req.id, result: {arch: 'nodejs', app: 'app1'}};
  } else if (req.method == 'RPC.Describe') {
    resp = {id: req.id, result: null};
  } else if (req.method == 'RPC.List') {
    resp = {id: req.id, result: ['Sys.GetInfo', 'RPC.List', 'RPC.Describe']};
  } else {
    resp = {id: req.id, error: {code: -32601, message: 'method not found'}};
  }
  console.log('RPC:', req, '->', resp);
  conn.send(JSON.stringify(resp));  // Send reply to mDash
});

Shadow overview

Device shadow is a JSON object that is associated with a device. It lives on a cloud and keeps device state and custom metadata. Device shadow is always available via library and REST API, regardless whether an associated device is online or offline. Device shadow JSON object has the following structure:

  • state.desired - this is the state you want your device to have
  • state.reported - this is the state your device actually has
  • state.delta - automatically generated difference between desired and reported
  • tags - arbitrary metadata invisible to the device but visible to the cloud user
{
  "state": {
    "desired": {
       "switched_on": true   // State you want your device to have
                             // Should be changed by the cloud user
    },
    "reported": {
       "switched_on": false, // State your device actually has
       "ram": 12473          // Should be changed by a device
    },
    "delta": {
      "switched_on": false  // Difference between desired and reported
                            // ONLY FOR KEYS THAT ARE PRESENT IN DESIRED 
                            //  Automatically generated on each change
    }
  },
  "tags": {
    "answer": 42  // Arbitrary cloud metadata
  }
}

The structure of the state.desired, state.reported and tags sub-objects is arbitrary - create whatever structure you wish.

Idiomatic shadow usage

  • When a device is connected to the mDash,
    • Subscribe to the shadow delta topic DEVICE_ID/shadow/delta
    • Report current state by sending a message {"state": {"reported":{...}}} to a DEVICE_ID/shadow/update topic
  • When a message is received on a delta topic, handle it and then report current state like on a previous step

Automatic shadow values

mDash creates and maintains several shadow values automatically:

  • state.reported.online - a boolean value, showing device online state
  • state.reported.ota - an object that contains device information, specifically, a result of the Sys.GetInfo RPC call

Example:

{
  "state": {
    "reported": {
      "online": true,                   // created automatically
      "ota": {                          // created automatically
        "app": "my-product",            // created automatically
        "arch": "esp32",                // created automatically
        "fw_id": "20190613-141510",     // created automatically
        "fw_version": "1.0.28-idf",     // created automatically
        "ram_free": 45172,              // created automatically
        "reboot_reason": "power-on"     // created automatically
      }
    }
  },
  "tags": {
    "labels": "prod devkit"
  },
  "version": 3331,
  "timestamp": 1560498134
}

How to delete shadow keys

In order to create new keys, simply send a shadow update with a new key and its value. In order to delete a key, send a shadow update where a key is set to null:

{"state": {"reported": {"my_key": null}}}