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 devicedevice_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:
Field | Type | Required? | Description |
---|---|---|---|
device_id | string | No1 | The ID of an existing device. |
device | object | No1 | JSON 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_type | object | No1 | JSON specifying a device type to be used for a single request only. |
command_id | string | No2 | The ID of an existing command to test (using its associated translator, if required). |
command | object | No2 | JSON specifying a command to be used for a single request only. The translator_id must match the id of the specified translator . |
command_params | object | No2 | JSON specifying params to be merged into the command (simulating what would be passed to the command execute endpoint). |
translator | object | No2 | JSON 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,#
Updated almost 3 years ago