Example - User Defined Command

Create and execute Command example

The following example illustrates how to implement a command for a device that can receive a command string via TCP. The command will cause the device to immediately send a report containing all of its current sensor data. Note that in this example, we are assuming the existence of the translator referenced by translator_id, which will translate the payload into the exact bytes that will be sent to the device.

First, we will create a command via POST /api/v1/platform/commands, with most of the fields necessary to process and send the command included at the time of creation. Here's the JSON request body:

{
    "name": "Acme REPORT_NOW command",
    "sender_type": "tcp_sender",
    "sender": {
        "port": 1234,
        "timeout": 5
    },
    "translator_id": "59d69e228cd4c5aa22000001",
    "long_description": "Acme REPORT_NOW.  Requires device_id and sender.host fields.",
    "payload": {
        "command_string": "REPORT_NOW\n"
    }
}

The response to that POST will contain the assigned command ID (e.g., 59d69ff98cd4c5aa91000002).

Next, we execute the command for a specific host 192.168.1.201 via the POST /api/v1/platform/commands/:id/execute endpoint, where :id is a path parameter that we replace with
59d69ff98cd4c5aa91000002 (the assigned command ID). Since we already saved everything about the command that doesn't change from one execution to the next, this POST request body contains only two fields:

{
    "device_id": "71:1b:5e:b3:0b:8f",
    "sender": {
        "host": "192.168.1.201"
    }
}

Note that here we are providing the device's unique_id value (a MAC address) to uniquely identify the recipient device. Either the assigned unique_id or the BSON ObjectId of the device can be provided in this field.

The execute endpoint will then lookup the saved command, and merge in the received request body. The merged command would look like this:

{
    "name": "Acme REPORT_NOW command",
    "device_id": "71:1b:5e:b3:0b:8f",
    "sender_type": "tcp_sender",
    "sender": {
        "host": "192.168.1.201",
        "port": 1234,
        "timeout": 5
    },
    "translator_id": "59d69e228cd4c5aa22000001",
    "long_description": "Acme REPORT_NOW.  Requires device_id and sender.host fields.",
    "payload": {
        "command_string": "REPORT_NOW\n"
    }
}

The merged command now contains everything needed for the command to be translated and delivered to the device via a TCP socket connection on 192.168.2.201:1234.

Same example using query params

The same result as above can also be accomplished using an HTTP GET with query params. The query params must be specified in "dot notation" to merge nested values.

GET /api/v1/platform/commands/59d69ff98cd4c5aa91000002/execute?device_id=71:1b:5e:b3:0b:8f&sender.host=192.168.1.201

Same example using form params

The same result as above can also be accomplished using an HTTP GET with form params. The form params must be specified in "dot notation" to merge nested values.

POST /api/v1/platform/commands/59d69ff98cd4c5aa91000002/execute
Content-Type: application/x-www-form-urlencoded
Accept: application/json

device_id=71:1b:5e:b3:0b:8f
sender.host=192.168.1.201

REST Command forwarder example

This example will demonstrate how to achieve the same as above with a REST Command forwarder. First, we still need to create the command via via POST /api/v1/platform/commands:

{
    "name": "Acme REPORT_NOW command",
    "sender_type": "rest_fwd_sender",
    "sender": {
        "uri": "http://my.example.com:8080/my/command/endpoint",
        "method": "POST",
        "timeout": 5,
        "headers": {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }
    },
    "long_description": "Acme REPORT_NOW via REST.  Requires device_id and options.host fields.",
    "options": {
        "port": 1234,
        "timeout": 5
    },
    "payload": {
        "command_string": "REPORT_NOW\n"
    }
}

The response to that POST will contain the assigned command ID (e.g., 59d8220db68b7867f3000001). Whereas in the first example we populated the TCP host, port, and timeout in the sender object, in this example we stick these into the options object, which is intended for metadata used to process or send the command.

Next (just as above), we execute the command for a specific host 192.168.1.201 via the
POST /api/v1/platform/commands/:id/execute endpoint, where :id is a path parameter that we replace with 59d8220db68b7867f3000001 (the assigned command ID).

{
    "device_id": "71:1b:5e:b3:0b:8f",
    "options": {
        "host": "192.168.1.201"
    }
}

The merged command JSON will look like the following:

{
    "device_id": "59db9d6a8cd4c5efc2000002",
    "device_unique_id": "71:1b:5e:b3:0b:8f",
    "device_name": "Acme sensor 37108",
    "name": "Acme REPORT_NOW command",
    "sender_type": "rest_fwd_sender",
    "sender": {
        "uri": "http://my.example.com:8080/my/command/endpoint",
        "method": "POST",
        "timeout": 5,
        "headers": {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }
    },
    "long_description": "Acme REPORT_NOW via REST.  Requires device_id and options.host fields.",
    "options": {
        "host": "192.168.1.201",
        "port": 1234,
        "timeout": 5
    },
    "payload": {
        "command_string": "REPORT_NOW\n"
    }
}

This JSON will be sent as the request body to the specified sender uri. Note the following:

  • device_id field now contains the assigned BSON ID of the device
  • device_unique_id field now contains the manufacturer unique ID (i.e., MAC address in this example)
  • device_name field contains the friendly name for the device

Command "Dry Run" Example

The "dry run" endpoint is a stateless API that allows a developer to execute a command against a device. It's intended as a mechanism for developers to test and debug commands and translators.

The following table defines the fields of the posted JSON:

FieldTypeRequired?Description
device_idstringNo1The ID of an existing device.
deviceobjectNo1JSON specifying a device to be used for a single request only. The device_type_id must match the id of the specified device_type.
device_typeobjectNo1JSON specifying a device type to be used for a single request only.
command_idstringNo2The ID of an existing command to test (using its associated translator, if required).
commandobjectNo2JSON specifying a command to be used for a single request only. The translator_id must match the id of the specified translator.
command_paramsobjectNo2JSON specifying params to be merged into the command (simulating what would be passed to the command execute endpoint).
translatorobjectNo2JSON specifying a translator to be used for a single request only.

1 Either device_id or both device_type and device are required.

2 Either command_id or commander and translator are required.

Some further clarification might be useful. This endpoint was designed to allow developers to test commands in the most flexible way. A developer can build up the pieces as they go, locking things down that work. If the device type and device have already been defined, they can be used by specifying the device_id. Otherwise, they can be fully specified via the device_type and device fields. Similarly, if the command and translator are already defined, they can be used by specifying just the command_id. Otherwise, they can be fully specified via the command and translator fields.

To pull all these pieces together, the following example would execute a command using a device type, device, command, and translator that are all created and used for this request only. Once again, the command specifies everything except the device_id and sender.host fields, which are provided within the command_params.

{
  "device": {
    "id": "59dba7398cd4c5f59170e81e",
    "name": "Acme sensor d05fb84d40de",
    "device_type_id": "59dba7398cd4c5f59170e81d",
    "unique_id": "d05fb84d40de"
  },
  "device_type": {
    "id": "59dba7398cd4c5f59170e81d",
    "name": "Acme sensor",
    "model": "S-100",
    "manufacturer": "Acme, Inc."
  },
  "command": {
    "id": "59dba7398cd4c5f59170e821",
    "name": "test cmd",
    "sender_type": "tcp_sender",
    "sender": {
      "port": 5050,
      "timeout": 5
    },
    "translator_id": "59dba7398cd4c5f59170e820",
    "long_description": "Test command for dry run endpoint, requires `device_id` and `sender.host`, and `payload` fields."
  },
  "command_params": {
    "device_id": "d05fb84d40de",
    "sender": {
      "host": "192.168.1.220"
    },
    "payload": {
      "device_unique_id": "D05FB84D40DE",
      "ph_above_caution_value": 9.5,
      "ph_above_critical_value": 10,
      "ph_below_caution_value": 4.5,
      "ph_below_critical_value": 4,
      "ph_controller_activate": "A",
      "ph_scale": 3,
      "ph_set_value": 7
    }
  },
  "translator": {
    "id": "59dba7398cd4c5f59170e820",
    "name": "AnkoCmdTest",
    "type": "anko_cmd",
    "script": "\n# anko \"package\" imports\nstrings = import(\"strings\")\nfmt = import(\"fmt\")\ntime = import(\"time\")\nencodingJson = import(\"encoding/json\")\n\nincomingMsg = nil\nnow = time.Now().UTC()\n\nfunc translate() {\n\tjson = {}\n\terr = encodingJson.Unmarshal(incomingMsg, \u0026json)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tresult = make([]string,0)\n\tresult = appendString(result, \"@\")\n\tresult = appendString(result, toString(json.device_unique_id))\n\tresult = appendString(result, now.Format(\"060102150405\"))\n\tresult = appendString(result, \"PHS\")\n\tresult = appendString(result, toString(json.ph_scale))\n\tresult = appendString(result, fmt.Sprintf(\"%06.3f\", json.ph_set_value))\n\tresult = appendString(result, fmt.Sprintf(\"%06.3f\", json.ph_below_caution_value))\n\tresult = appendString(result, fmt.Sprintf(\"%06.3f\", json.ph_below_critical_value))\n\tresult = appendString(result, fmt.Sprintf(\"%06.3f\", json.ph_above_caution_value))\n\tresult = appendString(result, fmt.Sprintf(\"%06.3f\", json.ph_above_critical_value))\n\tresult = appendString(result, toString(json.ph_controller_activate))\n\tresult = appendString(result, \"#\")\n\n\treturn strings.Join(result, \",\")\n}\n"
  }
}

This command will result in the following translated command string being sent via TCP to 192.168.1.220:5050:

@,D05FB84D40DE,171009190943,PHS,3,07.000,04.500,04.000,09.500,10.000,A,#