Overview
The TCP Server plugin hosts configurable TCP connections to which networked client computers can connect and communicate with running JADE systems. Clients open a connection to the hosted IP address and port as defined in the TCP Server configuration. Multiple simultaneous connections are supported and handled concurrently, and the maximum number of connections allowed is configurable (-1 means no limit; see configuration details below). Connected clients can then send requests to the TCP Server, to perform actions such as:
- fetch data from any plugins the TCP Server has subscribed to (configurable, of course)
- send control messages to the Worker instance (ex. run or shut down a plugin instance; see The Worker documentation for details)
- send messages to any plugins which support control messages (see documentation for specific plugins for details)
User Interface
The TCP Server interface displays both the Latest Message
(latest subscription message to arrive) and Merged Messages
(an aggregate of all subscription messages under a unique key for each subscription source - a plugin or the Worker).

Subscription Data Handling
The TCP Server plugin accepts published JSON messages and displays both the Latest Message
and Merged Messages
. As data arrives, the TCP Server plugin aggregates that data into the Merged Messages
object, which can then be queried by external applications (more on this later). Let’s look at an example to help build our mental model for how subscription data is handled. Suppose the TCP Server plugin subscribes to two plugins named MySerialPublisher1
and MySerialPublisher2
which publish temperature and pressure data respectively.
Suppose MySerialPublisher1
publishes:
{
"instanceName": "MySerialPublisher1",
"temperature": 22.4,
"unit": "Celcius"
}
Suppose MySerialPublisher2
publishes:
{
"instanceName": "MySerialPublisher2",
"pressure": 148.7,
"unit": "PSI"
}
When those published messages arrive, the TCP Server will look for the special key instanceName
which uniquely identifies the source of the data and use its value as a top level key name for storing the incoming data. It does this “namespacing” of sorts to avoid naming collisions in the Merged Messages
object. Don’t worry, you can configure the list of such “special keys” in the TCP Server’s messageSourceKeyNames
configuration option, but almost all publishing plugins use instanceName
. In this case, the incoming data results in the following Merged Messages
object:
{
"MySerialPublisher1": {
"instanceName": "MySerialPublisher1",
"temperature": 22.4,
"unit": "Celcius"
},
"MySerialPublisher2": {
"instanceName": "MySerialPublisher2",
"pressure": 148.7,
"unit": "PSI"
}
}
So as long as the TCP Server plugin also supports requests to get data from the Merged Messages
object, external applications can get data from any plugin which the TCP Server has subscribed to (more on this later).
Subscription Data Special Cases
Ok, so we know we can handle messages published from plugins who use a special key to identify themselves. Let’s cover some special cases:
-
What happens if data comes in without the special
instaneName
key?
In that case, the TCP Server will simply put that data under a top level key named__UNKNOWN_MESSAGE__
. -
I know the Worker can publish data such as the statuses of all plugins. How do I subscribe to that data and how does it get set in the
Merged Messages
object?
First, the TCP Server would need to subscribe to__PLUGIN_INFO__
(literally just add__PLUGIN_INFO__
to thesubscribesTo
array in configuration). Then the question becomes, what special key does the Worker use to identify itself. The answer isworkerName
. So as long as themessageSourceKeyNames
array in the TCP Server’s configuration hasworkerName
in it, you’re all set. -
What if I want my plugin data to go under a different top level key in
Merged Messages
?
Well, the list of special keys are be configured inmessageSourceKeyNames
. ThemessageSourceKeyNames
(string array) defaults to["instanceName", "workerName"]
to cover the common/standard cases right “out of the box”, but you have full control over this just in case. -
What if my plugin publishes both an
instanceName
as well as auniqueId
key, but I want to use theuniqueId
instead of theinstanceName
for the top level key inMerged Messages
?
In this case, you’d just adduniqueId
to the front of themessageSourceKeyNames
array. When incoming messages are processed, the order of the strings inmessageSourceKeyNames
determines the order of precedence for which special key gets used. In other words, the first one in the array to be found in the incoming subscription message gets used.
Request Handling
Now that we know how subscription data is handled, let’s take a look at how the TCP Server handles requests (one of which will, of course, allow us to fetch subscription data). Let’s first understand the required structure of a request, see an example, and then do the same for a response.
Requests
When a request is received, the TCP Server will read and interpret the first 4 bytes (the header) as a signed 32-bit integer (big endian byte order) whose value must be equal to the size of the rest of the reuqest (the body). The body is expected to be a serialized JSON object with the following top level keys:
target
(string, one of:__SERVER__
,__WORKER__
, or any plugin instance name): the target of the message; i.e. who the message is intended formessage
(type depends on the request, often a JSON object): the message to process or forward along to the specified target
Here’s an example (4-byte header not shown, but must preceed the JSON object):
{
"target": "__SERVER__",
"message": {
"operation": "Get Data",
"data": {
"path": "MySerialPublisher1.tempature"
}
}
}
Notice the target
seems to specify the TCP Server itself (__SERVER__
). Also notice the operation
is Get Data
. That’s a supported operation by the TCP Server, which will get data from the Merged Messages
object at the specified path
. Here the path
is MySerialPublisher1.temperature
. Looking back at our subscription data handling example, this would appear to be a request to get the temperature
published by MySerialPublisher1
(which in that example would have a value of 22.4
). Now the question is: what does the response look like?
Responses
All responses from the TCP Server use the same header concept (first 4 bytes are a number represented as a signed 32-bit integer, big endian byte order, whose value is equal to the size of the rest of the response) and the body is a serialized JSON object with the following top level keys:
value
(a valid JSON type: boolean, string, number, array, or object): the value specifiederror
(object with booleanstatus
, integercode
, and stringsource
)
The error
will contain error or warning information, if any, but will always be returned as an object with it’s elements. The status
element will be true if an error occurred and false otherwise. The code
element will be non-zero for errors or warnings (the distinction between an error and a warning is simply that for warnings the status
is false) and will be 0 for no error or warning. The source
element will contain human readable text describing the error or warning, or an empty string if there is no error or warning to report.
So what would the response to our request example above look lik (assuming the data from our Subscription Data Handling section above)? Here’s the answer (4-byte header not shown, but must preceed the JSON object):
{
"value": 22.4,
"error": {
"status": false,
"code": 0,
"source": ""
}
}
Now that we have an idea for how to send requests and read responses, let’s take a look at the supported requests.
Messages Routing
The TCP Server plugin routes messages contained in requests based on the specified target
. In the example above we saw the __SERVER__
target, which refers to the TCP Server itself, which currently only supports the Get Data
message noted in our example above. But we also noted that the target
could be __WORKER__
or any plugin instance name. If the target
is __WORKER__
then the message will be routed to the Worker; this is how control messages can be sent to the Worker from an external application. Similarly, if the target
is some plugin instance name, the message will be sent to the corresponding plugin. So now the question becomes, what messages are supported by other plugins? And the Worker? Well, each plugin determines if and what messages are supported, and each plugin should have documentation for all its supported messages. Similarly, the Worker documents all its supported messages, which can be found in The Worker documentation.
One important question is: what can I expect back from the Worker or plugins when my message is routed to them? The answer is, you’ll get a standard acknowledgement from the TCP Server that your request has been received, as shown below:
{
"value": "Message received.",
"error": {
"status": false,
"code": 0,
"source": ""
}
}
At this time, the TCP Server does not wait for a response from the Worker or any plugin, and in fact those components do not respond at all to their control messages, rather they simply take the action defined in the message. Since these plugins or components communicate over internally managed queues (not dependent on a network connection), there is essentially no chance that such messages will be lost. If an invalid message is sent, plugins will generally generate an error which can be seen in the Worker (or seen in the Worker’s published __PLUGIN_INFO__
data).
Configuration Example
Configuration Details
[]
[ "workerName", "instanceName" ]