Introduction
JSON (JavaScript Object Notation) is a text-based data representation format that can be used to exchange pieces of data between two systems. JSON is human-readable and the data is structured as key-value pairs. JSON is used extensively in web-based systems especially in REST API, AJAX (as a replacement for XML), Databases (Eg: Mongo DB), etc… The simple human-readable format and wide popularity of JSON among Web and application developers make it an attractive protocol interface for hardware devices as well. Traditionally, automation devices used protocols such as Modbus or proprietary protocols which usually are designed by hardware developers for hardware developers. Such protocols are usually in binary format and are not human readable. They also tend to be cryptic. With the accelerated unification web and desktop development through JavaScript, Node JS, Electron JS, and other similar technologies, it has become very important for hardware manufacturers to offer more in terms of protocol interfaces to the new generation of developers. Numato Lab is now offering JSON based protocol interface on several products and thus enabling developers to send requests and receive responses as JSON objects.
Supported Devices
JSON Primer
JSON represents data in a human-readable string format. Each piece of data is represented as a key-value pair. A set of key-value pairs within a pair of curly braces makes a JSON object. Each key-value pair within an object is separated by a comma. Let’s see how this is done with an example. In this example, we will represent the following data in JSON format.
Name - John Doe Age - 49
We start by organizing the data into Key-Value pairs as below.
"Name" : "John Doe" "Age" : 49
As you may notice, the keys are always strings enclosed in double quotes. Values can be Strings, Numbers or certain other types such as arrays. Numbers are not enclosed in double quotes. The Key and Value in a pair are separated by a colon character. Let’s now put together a JSON object with this data.
{ "Name" : "John Doe", "Age" : 49 }
JSON objects are enclosed in a pair of curly braces. Each Key-value pair inside the object is separated by a comma. Simple! that is all it takes to build a simple JSON object.
To interact with certain Numato Lab products, we will need to learn how to use arrays in JSON. The concept is very simple, create a key and put all values in square brackets separated by commas. Let’s take a look at the following data.
Name - John Doe Age - 49 Order Numbers - 6380, 5402, 7832
This data can be represented in JSON as
{ "Name" : "John Doe", "Age" : 49, "OrderNumbers" : [6380, 5402, 7832] }
Elements in the array can be numbers, strings or other objects as well.
We are skipping the concept of nested objects and other advanced topics since Numato Lab products does not use those as of writing this article. But the reader is encouraged to learn more to get a solid understanding of JSON.
JSON Request Object
Numato Lab products that accept JSON expect the request to be sent as a null-terminated ASCII string. A request can contain only one object and there can be only one outstanding request at any given point in time. When a complete request is received, the device will process the request and respond with a JSON object containing the response. The host (application communicating with the device) must wait until a complete response is received before sending another request. All requests sent to the device must be in the following format.
{ "action":"", "resource":"/<resourcename>", Optional arguments... }
The request object must always have an action and a resource name. Certain action/resource combinations may require additional arguments to process the request. Currently, the following actions are supported currently.
Action | Comments |
---|---|
get | Reads information about specified resource. A get action executed on a resource will not change the state of the resource unless such change is a part of the resources expected behavior (for example a read counter). Alternatively action name "read" can be used in place of "get". |
post | Updates a specified resource. A post action executed on a resource may change the state of the resource. Alternatively action name "write" can e bused in place of "post". Certain resources may not support updating the state and this may not support post action. |
Readers may find similarities between the action names and HTTP GET/POST methods. This is intentional but unlike HTTP, USB communication does not have the concept of GET/POST methods. So the action is a part of the JSON object itself.
Let’s take a look at how to put together a request object in order to read the device information. Reading device information requires sending an object with action set to “get” and resource name set to “/info”. No additional arguments needed. With this information, we can construct a JSON request object as below.
{ "request":"get", "resource":"/info" }
This object can be converted into a string as below and sent to the device over the USB Serial interface.
'{"request":"get","resource":"/info"}'
Depending on the programming language of choice, this can be done in different ways (For example, json.dumps(JSON object).encode() in Python). Also, a NULL character may need to be added to the end of the string manually in certain programming languages.
JSON Response Object
In response to a JSON request, the device will return a string containing the JSON response object. The format for the response object is as below.
{ "status": "<status>", Optional additional information... }
All responses will have a status member that indicates the status resulted from processing the request. A successful request will be responded with status set to “success”. A failed request shall be responded with status set to one of the status values mentioned in JSON command reference.
For example, a response to “get” action sent to “\info” resource will return the following string containing the JSON response object.
'{"status":"success","vid":"10777","pid":"9473","hwminver":"1","hwmajver":"0","fwminver":"1","fwmajver":"1"}'
This string can be formatted as below using methods built into certain programming languages (Eg. json.loads() method in Python).
{ "fwmajver": "1", "fwminver": "1", "hwmajver": "0", "hwminver": "1", "pid": "9473", "status": "success", "vid": "10777" }
In this example, the “status” member is set to “success” which means the request is successful. Other members provide additional information as requested by the request object.
Working with Relays
Reading the state of a relay or setting relay state is straightforward as well. To read a relay state, send a request object with “get” action to resource “/relay”. To change a relay state, send a request to “/relay” resource with a “post” action. Let’s how this is done with some examples.
The request example below will request the device to return the current state of relay index 0. This request can be modified to read states of more relays by using a “count” larger than 1. For example, if the count is set to 5, the device will respond with states of five relays starting at index 0.
{ "action":"get", "resource":"/relay", "index" :0, "count":1 }
If the request is successful, the device will respond with a JSON object similar to the one below.
{ "data": [ 0 ], "status": "success" }
The status member reflects the status of the request and the data member has an array of values. Each item in the array represents the state of each relay starting at the index requested.
Changing the state of a relay is very similar. Use “post” action instead of the “get” action and supply the value to be written to the relay. the example request object below will set the state of Relay 0 to 1 (ON).
{ "request":"post", "resource":"/relay", "index" :0, "count":1, "value":1 }
If the operation is successful, the response object will have the status member set to “success”. When a count larger than 1 is used, the “value” member of the request must be an array of numbers.
Working with GPIOs
Working with GPIOs is exactly the same as for Relays. To read a GPO pin state, send a request object with “get” action to resource “/gpo”. To change a GPO state, send a request to “/gpo” resource with a “post” action. Let’s how this is done with some examples.
The request example below will request the device to return the current state of GPO index 0 (any pin number available on a GPIO module can be used as an index). This request can be modified to read states of more GPOs by using a “count” larger than 1. For example, if the count is set to 5, the device will respond with states of five GPOs starting at index 0.
{ "action":"get", "resource":"/gpo", "index" :0, "count":1 }
If the request is successful, the device will respond with a JSON object similar to the one below.
{ "data": [ 0 ], "status": "success" }
The status member reflects the status of the request and the data member has an array of values. Each item in the array represents the state of each GPO starting at the index requested.
Changing the state of a GPO is very similar. Use “post” action instead of the “get” action and supply the value to be written to the GPO. the example request object below will set the state of GPO 0 to 1 (ON).
{ "request":"post", "resource":"/gpo", "index" :0, "count":1, "value":1 }
If the operation is successful, the response object will have the status member set to “success”. When a count larger than 1 is used, the “value” member of the request must be an array of numbers.
A GPI state can be read by sending a similar “get” request to “/gpi” resource as shown below. “post” action is not supported by “gpi” resource.
{ "action":"get", "resource":"/gpi", "index" :0, "count":1 }
More information
For more information about all features supported by Numato Lab products and see more articles with specific examples in various programming languages, please visit the Knowledge Base at https://numato.com/kb/