MIRA
Domain Nexus - Documentation


Contents

This unit provides a webserver that allows access to MIRA using a proprietary protocol over websockets. Each websocket connection can act as a remote authority.

The protocol

The protocol entirely builds up on the JSON-format. Every message that is exchanged must be a JSON-object that contains always a member "Command".

Messages from client to server

The checkin command

In order to gain access to MIRA each websocket connection must first checkin (like a normal Authority) using namespace and id. This can be done either by opening the websocket passing namespace and id of the created Authority e.g. ws://127.0.0.1:8080/namespace1/namespace2/AuthorityName or by sending the checkin message that must have the following syntax:

{ "Command" : "checkin", "Namespace" : "/myNamespace", "ID" : "MyID" }

Namespace + id of an authority must form a unique identifier in the framework.

Optionally, a unique id can be generated for an authority automatically during checkin by appending a unique string to the given id, this is called an 'anonymous' authority. The Nexus checkin command can be extended by a parameter 'Anonymous' to use this:

{ "Command" : "checkin", "Namespace" : "/myNamespace", "ID" : "MyID", "Anonymous" : true/false }

This allows to checkin multiple authorities with the same checkin command and parameters, without the responsibility to choose unique names on the client side. It can be helpful e.g. when a remote authority is created by an HTML/Javascript page running in a web browser (see mira.js in Nexus:etc/).

The read command

To read data from a channel, use the following message. The last published channel data will be returned by a callback message to the client (see The callback message).

{ "Command" : "read", "Channel" : "channelName",
["Codec" : "codecName",
"Compression" : "compressionLevel",
"Scale" : "pixelScale" ] }

Reading image data

The read command returns a JSON representation of channel data. For e.g. image and map channel types, the JSON serialization only contains meta data like image size and type, but not the actual image data (it would not be very handy to show this e.g. in a text view). However, the Nexus provides an extension to allow reading the actual image data from channels of certain types (mira::Img<...>, mira::maps::GridMap<...>). To enable this, the read command must contain an additional 'Codec' parameter, which will describe an encoding in an image file format (supported codecs are 'png' or 'jpeg', or see below for 'raw' encoding). If this is specified, the returned message will contain an 'Img' element. This 'Img' element in turn will contain an 'Img64' element, holding a base64-encoded string that represents a buffer with the encoded image. E.g. an image.png file can be created by first applying base64decode to the 'Img64' value, then writing the result to a binary file of that name.

2 optional parameters can be used to further control the image encoding: 'Compression' can control the compression level. Valid values depend on the codec: for 'png', the compression (result size) can be selected by values 0 (weak compression) to 9 (strongest compression, default). For 'jpeg', the quality/size can be selected between 0 and 100 (default = 95). The 'Scale' parameter can be used to scale individual pixel values (i.e., increase contrast). The scale can either be a numeric value (integer or floating point), or 'auto' ('auto' only works for 1-channel images, the scale is determined such that the brightest image pixel will have value 255). If no 'Scale' is set, the default is selected based on the channel type: images with uint8 pixel values are not scaled, images with floating point values are auto-scaled.

By specifying 'Codec': 'raw', the image data is NOT encoded into an image file format, but the returned data is a raw buffer of image bytes (copied row by row), which must be interpreted by the client according to the image size and pixel format description (the normal JSON description of the image).

Multi-message response

Messages sent by the Nexus have a size limit, which could be exceeded when a very large image is read. In this case, the messages are automatically split into multiple smaller messages. Only the image data is distributed, all other data is retained in each message. In case of a multi-message response, the 'Img' element will contain additional elements 'ID', 'Seq' and 'Fragments'. 'ID' will be the same for each message belonging to the same image, and unique compared to all other image responses (currently ID is a counter increased with each image that is read, but it could also be e.g. a random string). 'Fragments' is the number of messages for this image, and 'Seq' is the number of the current fragment. Each fragment still contains an 'Img64' element, but these are only parts of the complete base64-encoded buffer. Each message is constructed to not exceed the message size limit. The complete image data can be retrieved by collecting the 'Img64' elements of all fragment messages with the same 'ID', and assembling them in the order of their 'Seq' number. This will result in the same data as a single message 'Img64' field (which can then be base64-decoded and treated as png/jpg data).

The subscribe command

To subscribe to a channel and getting notifications (see The callback message) on channel updates the following message format can be used:

{ "Command" : "subscribe", "Channel" : "channelName" }

Subscribing to image/map channels uses the same mechanism as The read command.

If delays while processing the channel updates are likely, the following message format can be used to receive all unprocessed messages within a certain time interval (specified in milliseconds):

{ "Command" : "subscribeInterval", "Channel" : "channelName", "Interval": "interval" }

The subscribePeriodic command

Subscribing to particularily busy channels using subscribe (see The subscribe command) will incurr a high networking overhead as every channel update is transmitted in its own callback (see The callback message). The following command will restrict the callback frequency to the specified interval (in milliseconds):

{ "Command" : "subscribePeriodic", "Channel" : "channelName", "Interval": "interval" }

In cases where channel updates are supposed to be processed in larger batches rather than as individual messages, the following command will return all data since the last periodic callback as a single batch:

{ "Command" : "subscribePeriodicInterval", "Channel" : "channelName", "Interval": "interval" }

The unsubscribe command

In order to cancel a previous subscription one needs to send:

{ "Command" : "unsubscribe", "Channel" : "channelName" }

The publish command

To publish a channel in order to write data to it the following syntax is used:

{ "Command" : "publish", "Channel" : "channelName", "Type" : "channelType" }

As type the C++ typename of the desired type must be passed (e.g. int, mira::robot::RangeScan)

The unpublish command

In order to cancel a previous publication one needs to send:

{ "Command" : "unpublish", "Channel" : "channelName" }

The write command

To write data to a formerly published channel the following syntax is used:

{
"Command" : "write",
"Channel" : "channelName",
"Data" : jsonValue,
"Timestamp" : 12345,
"FrameID" : "FrameID",
"SequenceID" : 1234
}

Timestamp is the time in nanoseconds since 00:00:00 1970/01/01 and is optional. If not provided Time::now() will be used. FrameID and SequenceID are also optional members of "Data".

The waitForService command

To wait for a service to become available, the client can use the following message syntax:

{
"Command" : "waitForService",
"Service" : "serviceName",
"Timeout" : 1000
}

The Timeout member is the timeout in milliseconds for the server to wait for the RPC call to become available. It is optional and by default is set to infinity.

The server will answer with a waitForService message when the service is ready or the timeout occurs: The waitForService message.

The callService command

To issue a RPC method call the client can use the following message syntax:

{
"Command" : "callService", "ID" : 1234,
"Service" : "serviceName", "Method" : "methodName",
"Args" : jsonArgsAsArray, "Timeout" : 1234
}

The ID can be used to associate the server response with the call. The Args member is optional and if not supplied [] is used. The Timeout member is the timeout in milliseconds for the server to wait for the RPC call to finish. It is also optional and by default is set to infinity.

If the call is successful or if an error occurred the server will answer with a serviceResponse: The serviceResponse message.

The getChannelList command

To get all available channels from the server the client can use the following syntax:

{
"Command" : "getChannelList"
}

The ID can be used to associate the server response with the call.

If the call is successful, the server will answer with a response message: The channelListResponse message.

The getAuthorities command

To get all available authorities from the server the client can use the following syntax:

{
"Command" : "getAuthorities"
}

The ID can be used to associate the server response with the call.

If the call is successful, the server will answer with a response message: The authoritiesResponse message.

subscribeTransform and getTransform commands

To access the transformation framework, one can use these methods to either receive continuous callbacks whenever a transformation changes, or to query for a certain transform at a certain time.

{
"Command" : "subscribeTransform",
"ID" : 1234,
"Target" : "/path/to/some/target/frame",
"Source": "/path/to/the/source/frame",
"Dimension": 2/3
}

The subscribeTransform command will deliver continuous The transform message callbacks for every change in the transformation chain between /path/to/some/target/frame and /path/to/the/source/frame. The callbacks will be identified by the ID passed to this method. The Dimension parameter allows to select 2D or 3D transformation, it is optional and defaults to 2D.

When invoking this command, Nexus will perform an initial query of the transformation between these frames and deliver the result via the callback mechanism. This guarantees that even static transforms (e.g. published at framework creation) can be 'accessed' this way.

{
"Command" : "getTransform",
"ID" : 1234,
"Target" : "/path/to/some/target/frame",
"Source": "/path/to/the/source/frame",
"Time": 1404980602249830000,
"Dimension": 2/3,
"Interpolate" : true/false
}

Similar to the subscribeTransform command, this command will query the transformation framework for the specified transformation, and deliver the result using the The transform message response with the specified ID. The Time parameter is optional, it allows querying the transform at a specific point in time (unix timestamp in nanoseconds). The Interpolate parameter is also optional, it defaults to true. Without interpolation, the transformation is defined by the transformation published closest to the query timestamp (or last transformation if no specific timestamp is given). With interpolation, it will be interpolated/extrapolated to the exact query timestamp (or 'now', without timestamp)

Messages from server to client

The response message

The server will acknowledge each command with a response message, reporting either success or failure:

{
"Command" : "response",
"Message" : either "success" or "failed"
}

The callback message

Whenever data in a subscribed channel changes (or just once for a read command) the server will send the following message:

{
"Command" : "callback",
"Channel" : "channelName",
"Data" : jsonValue,
"Timestamp" : 12345,
"FrameID" : "FrameID",
"SequenceID":1234
}

The exception message

Whenever an exception occurs on the server side caused by a former client message the server sends this message:

{ "Command" : "exception", "Message" : "exception text" }

The waitForService message

Whenever a waitForService succeeds or fails, the server sends this message:

{
"Command" : "waitForService", "Service" : "serviceName",
"Error" : "Timeout"
}

The Error member only exists in case of a timeout. Otherwise, the service is ready to receive calls when this message is sent.

The serviceResponse message

Whenever a service call is ready the server sends this message:

{
"Command" : "serviceResponse", "ID" : 1234,
"Error" : "Error message", "Result" : jsonResult
}

The ID is the same ID as sent with the callService message. So the client can associate the result with the call. The response either contains the Error member if an error occurred on the server side while processing the call or the Result if the call was successful.

The transform message

Whenever a transformation changes that was subscribed to using subscribeTransform, or is queried using getTransform, Nexus will send this message with the changed transform. The ID corresponds to the ID used in the subscribe or get command.

{
"Command" : "transform", "ID" : 1234,
"Data" : { "X": 0.0, "Y": 0.0, "Phi": 0.0 } or
{ "X": 0.0, "Y": 0.0, "Z": 0.0, "Yaw": 0.0, "Pitch": 0.0, "Roll": 0.0 },
"Timestamp": 1404980602249830000
}

The Data field contains the JSON-serialized mira::Pose2/Pose3 which is the result of transforming the target frame into the source frame. The timestamp contains the point in time (unix timestamp in nanoseconds) when the transform was published or for which it was queried.

The channelListResponse message

The server will respond to a The getChannelList command with the list of all known channels with the following syntax:

{
"Command" : "channelListResponse",
"Channels" :
{
"/myNamespace/MyChannel1":
{
"Typename" : "mira::MyType1",
"DataChanges" : 123,
"Subscribers" : 2,
"Publishers" :1
},
"/myNamespace/MyChannel2":
{
"Typename" : "mira::MyType2",
"DataChanges" : 456,
"Subscribers" : 2,
"Publishers" :1
},
...
}
}

The authoritiesResponse message

The server will respond to a The getAuthorities command with the list of all known authorities with the following syntax:

{
"Command" : "authoritiesReponse",
"Authorities" :
{
"/myNamespace/MyUnit":
{
"RunningState" : "Running",
"IsLocal" : true,
"HasUnrecoverableFailure" : false,
"PublishedChannels" : ["/myNamespace/MyPublishedChannel1", "/myNamespace/MyPublishedChannel2"],
"SubscribedChannels" : ["/yourNamespace/YourPublishedChannel1"],
"Interfaces" : ["MyInterface"]
},
...
}
}

Client implementation examples

Python

Checkin an authority and call a service:

#!/usr/bin/env python
import websocket
ws = websocket.WebSocket()
url ='ws://127.0.0.1:8080'
ws.connect(url, header=[])
ws.send('{"Command": "checkin", "Namespace": "/test", "ID": "TestID"}')
ws.send('{"Command": "callService", "ID": 85, "Service": "/RobotAPI", "Method": "dockOff", "Args": [1] }')

Checkin an authority and subscribe to a channel:

Note: the previous example uses websocket for simplicity. This one uses websockets (note the subtle difference) for compatibility with async.

#!/usr/bin/env python
import asyncio
import json
import websockets
async def mira_nexus():
uri = 'ws://10.234.16.31:8080'
async with websockets.connect(uri) as websocket:
await websocket.send(
json.dumps({'Command': 'checkin', 'Namespace': '/namespace', 'ID': 'test', 'Anonymous': 'true'}))
await websocket.recv()
await websocket.send(json.dumps({'Command': 'subscribeInterval', 'Channel': '/robot/can/IncomingCAN',
'Interval': 500 }))
await websocket.send(json.dumps({'Command': 'subscribe', 'Channel': '/navigation/Task'}))
await websocket.recv()
while True:
msg = await websocket.recv()
print(f'{msg}')
asyncio.get_event_loop().run_until_complete(mira_nexus())

JavaScript

An implementation of an Authority wrapper in JavaScript is provided with the Nexus toolbox, in Nexus:etc/mira.js.