Each Ingestor will have a specific inbound translator with which it is associated,
and each Command will have a specific command translator with which it is associated. The inbound translator's purpose is to convert the data received via the ingestor listener and handler to the EdgeIQ report JSON format. The command translator's purpose is to convert a command represented as JSON into the device's native command format.
Cloud vs Edge Translators
Translators can be run either in the cloud or on a downstream device such as a gateway running EdgeIQ's Edge service. If the cloud_translator
parameter is true, the translator will be installed in the EdgeIQ Cloud ingest engine. If it is false, the translator will be passed to the associated Edge Gateway.
Translator Types (inbound "ingest")
Translator Type | Attributes | Example |
---|---|---|
anko | type, name, script | { "type" : "anko", "name" : "Anko Translator 01", "value" : "...escaped anko script..." } |
javascript | type, name, script | { "type" : "javascript", "name" : "JS Translator 02", "script" : "...escaped javascript script..." } |
csv | type, name | See detailed example below. |
The anko
type is only valid for edge translators (i.e., cloud_translator=false
).
Translator Types (command)
Translator Type | Attributes | Example |
---|---|---|
anko_cmd | type, name, script | { "type" : "anko_cmd", "name" : "Anko Translator 01", "script" : "...escaped anko script..." } |
javascript_cmd | type, name, script | { "type" : "javascript_cmd", "name" : "JS Translator 02", "script" : "...escaped javascript script..." } |
template_cmd | type, name, script | { "type" : "template_cmd", "name" : "Template Translator 02", "script" : "...escaped golang template..." } |
csv_cmd | type, name, options | { "type" : "csv_cmd", "name" : "CSV Translator 03", "options": { "delim": " |
shell_cmd | type, name | { "type" : "shell_cmd", "name" : "Shell Translator" } |
The anko_cmd
type is only valid for edge translators (i.e., cloud_translator=false
).
Translator Variables
Inside the translator, for translators of type template
, anko
, and javascript
, certain variables are available based on the type of listener
configured for the ingestor
.
All Polling Listeners
Name | Description |
---|---|
gateway_unique_id | Unique ID of Gateway device where Ingestor is running |
device_unique_id | Unique ID of device that Ingestor is associated with. If device is attached to a gateway, than gateway_unique_id and device_unique_id will be different. They will be identical when the Ingestor is associated with the gateway. |
unique_id | alias for device_unique_id |
device | The Device resource that is associated with the Ingestor. All data fields in Device are available |
device_type | The Device resource that is associated with the Ingestor. All data fields in DeviceType are available |
HTTP Client Listener
Name | Description |
---|---|
output | The HTTP response body. If the Content Type is application/json , then the output variable will support JSON querypaths, i.e. output.foo for { "foo": "bar" } |
body | alias for output |
status_code | The HTTP Response status code value as an integer. |
ICMP Client Listener
Name | Description |
---|---|
output | success/fail |
status | alias for output |
error | if status is fail , then the error value as a string. if status is success , than an empty string. |
time_in_ms | latency in milliseconds |
SNMP Client Listener
Name | Description |
---|---|
output | A JSON map where key is the SNMP Object ID and its value as either a string or integer |
error | Error information as a string |
Script Example
This is an example of an inbound anko
Edge Translator that translates a fixed-length binary message to the EdgeIQ JSON report format. The script follows a pattern
The anko script:
# anko "package" imports
time = import("time")
math = import("math")
json = import("encoding/json")
# vars that will be set and/or read from go
incomingMsg = nil
dataSourceId = ""
errMsg = ""
func isValid() {
if len(incomingMsg) == 21 {
return true
}
return false
}
func getMfrId() {
return toHexString(incomingMsg[2:8])
}
func translate() {
now = time.Now().UTC()
battery = readIntFromBytes(incomingMsg[16:17])
onExtPower = battery >= 128
level = battery
if onExtPower {
level -= 128
}
tempUint32 = readUint32FromBytes(incomingMsg[17:21])
tempFloat = math.Float32frombits(tempUint32)
retMap = {
"device_id": dataSourceId,
"device_datetime": now.Format("2006-01-02T15:04:05.999Z07:00"),
"payload": {
"device_name": getMfrId(),
"function_code": readIntFromBytes(incomingMsg[0:2]),
"interval": readIntFromBytes(incomingMsg[12:16]),
"external_power": onExtPower,
"battery_level": level,
"temperature": tempFloat
},
"raw_data": toHexString(incomingMsg)
}
marshaledBytes, err = json.Marshal(retMap)
return toString(marshaledBytes)
}
The full translator JSON (including above script):
{
"name": "XW110 Sensor Translator",
"type": "anko",
"cloud_translator": false,
"script": "# anko \"package\" imports\ntime = import(\"time\")\nmath = import(\"math\")\njson = import(\"encoding/json\")\n\n# vars that will be set and/or read from go\nincomingMsg = nil\ndataSourceId = \"\"\nerrMsg = \"\"\n\nfunc isValid() {\n\tif len(incomingMsg) == 21 {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc getMfrId() {\n\treturn toHexString(incomingMsg[2:8])\n}\n\n\nfunc translate() {\n\tnow = time.Now().UTC()\n\tbattery = readIntFromBytes(incomingMsg[16:17])\n\tonExtPower = battery >= 128\n\tlevel = battery\n\tif onExtPower {\n\t\tlevel -= 128\n\t}\n\ttempUint32 = readUint32FromBytes(incomingMsg[17:21])\n\ttempFloat = math.Float32frombits(tempUint32)\n\t\tretMap = {\n\t\t\"device_id\": dataSourceId,\n\t\t\"device_datetime\": now.Format(\"2006-01-02T15:04:05.999Z07:00\"),\n\t\t\"payload\": {\n\t\t\t\"device_name\": getMfrId(),\n\t\t\t\"function_code\": readIntFromBytes(incomingMsg[0:2]),\n\t\t\t\"interval\": readIntFromBytes(incomingMsg[12:16]),\n\t\t\t\"external_power\": onExtPower,\n\t\t\t\"battery_level\": level,\n\t\t\t\"temperature\": tempFloat\n\t\t},\n\t\t\"raw_data\": toHexString(incomingMsg)\n\t}\n\n\tmarshaledBytes, err = json.Marshal(retMap)\n\treturn toString(marshaledBytes)\n}\n"
}
CSV Example
The next example shows a translator for a CSV message from a fictitious temperature sensor. The
message contains the following fields:
- MAC Address
- Timestamp
- Message type identifier
- Temperature value
- Temperature Unit (F or C)
- Newline char (all CSV records must end with a newline)
An example message:
cd53e1825a01,170113132307,TMP,041.27,C\n
Handler
options are configured in the handler (see Ingestors example). Whereas the handler
controls how CSV records are read, the options
field in the JSON below specifies characteristics about the message type and individual fields. The options
field is an array and can contain multiple different CSV message formats. This is useful for dealing with devices that emit multiple types of messages over the same connection.
The following table enumerates properties contained within an element of the options
array. Note that in the following descriptions, "property" refers to a JSON property, and "field" refers to a value within the CSV message.
Field | Required? | Description |
---|---|---|
mfr_id_field_num | Y | This property indicates the position of the field value that uniquely identifies the device. This value is used to look up the device, so this value must match the device name or unique_id property. |
msg_id_field_num | N | This property is required when the options array contains more than one element, and indicates the position of the field that identifies the message type. A single device might emit multiple CSV message types, and this field is used to distinguish them. |
msg_id_field_val | N | This property is required when the options array contains more than one element, and indicates the expected contents of the msg_id_field_num field for this message type. |
timestamp_field_num | N | This property indicates the position of the field containing a message timestamp. If no field in the message contains a timestamp, a current timestamp will be generated during translation. |
timestamp_format | N | If timestamp_field_num is specified, this property specifies the format of the timestamp so it can be correctly parsed. This property must be specified according to the rules specified in the Golang time package. |
field_metadata | Y | This array is required. Each element in the array specifies how to handle the field within the message at the same position. |
This table lists the properties of each field_metadata
element:
Field | Required? | Description |
---|---|---|
name | Y | The name to be given to the property within the JSON report. To omit the corresponding field from the report, specify "SKIPME" as the name. |
type | N | The type of data contained in the field value (defaults to "string" if not specified).. Valid property values are "string", "integer", "float", and "boolean". When "integer" is specified, the value will be parsed as a base 10 64-bit signed integer. A "float" will be parsed as a 64-bit floating point number. A "boolean" will use the default boolean parse function. |
Here is the complete JSON for this CSV translator example:
{
"name": "CSV Translator Example",
"type": "csv",
"cloud_translator": false,
"options": {
"mfr_id_field_num": 0,
"msg_id_field_num": 2,
"msg_id_field_val": "TMP",
"timestamp_field_num": 1,
"timestamp_format": "060102150405",
"field_metadata": [
{
"name": "device_name"
},
{
"name": "SKIPME"
},
{
"name": "report_type"
},
{
"name": "temperature",
"type": "float"
},
{
"name": "temperature_unit"
}
]
}
}
And for the example message above, the translated report would look like this:
{
"id": "587971121ab7161c37bbcd57",
"device_datetime": "2017-01-13T13:23:07Z",
"created_at": "2017-01-14T00:30:10Z",
"payload": {
"device_name": "cd53e1825a01",
"report_type": "TMP",
"temperature": 41.27,
"temperature_unit": "C"
},
"device_id": "587971121ab7161c37bbcd54",
"raw_data": "cd53e1825a01,170113132307,TMP,041.27,C",
"seq": 0
}