SimonBirrell/luxserver

Websockets server for Robotlux

★ 0Forks 0JavaScriptGitHub ↗Compare

README

LuxServer

The websockets server that intermediates between Lux Agent (that lives on the robot) and the users' browsers. It subscribes to changes in the robot state sent from the agent and maintains a copy in memory. It sends the changes on to any attached browsers that have access to the robot. It also receives commands from the browser (such as 'launch a node' and sends them on to the agent).

Multiple browsers can view the same ROS instance by connecting to the server. The server will only receive one set of updates from the agent, regardless of how many browsers are viewing.

Communication is via websockets. Currently messages are in JSON or optionally BSON (at least ebtween server and agent) and will eventually be compressed.

Running

	node --harmony luxserver.js

The server can be run locally or deployed to heroku with

	git push heroku master	

Tests

    mocha --harmony -w
    mocha --harmony -w -g 'string for matching test names'

Protocol

Error condition signaled by mbody containing error key:

	{...., error: 'message', ...}

General

A full RosInstanceId looks like this:

"<org_id> <network_id> <base_ros_instance_id>"

A full machineId looks like this: "<org_id> <network_id> <base_ros_instance_id> <machine_id>"

Graph Structure transmitted from agent is a superset of the ROS computational graph, extended to include installed packages. This is stored on the server and forwarded to browsers but is not interpreted as such by the server. So far, at least.

Protocol

The protocol by default consists of JSON messages like the following:

{
	mtype: 	<command>,
	mbody:	<data>
}

Where "" is a simple string and "" is a further set of JSON data that comprises the payload.

This protocol may be optionally converted to BSON (at least between server and agent) and should eventually be compressed, perhaps with LZW or with command-specific compressions schemes (e.g. streaming JPEG). Encryption should be added, or at least some secure authentication method.

Currently, all updates generated by the ROS instance are passed to the server, without regard for bandwidth.

General

keepAlive

mtype: keepAlive

Dummy message sent by any party to keep the connection alive. Should be ignored.

Browser -> Server

These are the commands sent from the browser to the server.

browserConnenct

Sent by the browser on connection to the server's websocket to request access to a ROS instance owened by an organization. The userName and secret is the (primitive) authentication method for the time being.

mtype: browserConnect
mbody: {rosinstance: rosInstanceBase, org: orgId, user: userName, secret: secret}

Reply:

mtype: browserConnected
mbody: instanceN

The reply acknowledges a successful connection.

subscribeRosInstances

mtype: subscribeRosInstances

Reply:

mtype: rosInstancesUpdate
mbody: [{add: {rosInstanceId: fullRosInstanceId1, rosInstanceHumanId: "foo instance"}}, {add: {rosInstanceId: fullRosInstanceId2, rosInstanceHumanId: "bar instance"}}}... ]

and as changes occur:

mtype: rosInstancesUpdate
mbody: [{add: fullRosInstanceId1}, {del: fullRosInstanceId2}... {add: fullRosInstanceIdN}]

subscribeRosInstance

mtype: subscribeRosInstance
mbody: rosInstance: fullRosInstanceId

Reply:

mtype: rosInstanceGraph
mbody: {rosInstance: fullRosInstanceId, graph: graph}

unsubscribeRosInstance

mtype: unsubscribeRosInstance
mbody: {rosInstance: fullRosInstanceId}

Reply:

mtype: unsubscribedRosInstance
mbody: {rosInstance: fullRosInstanceId}

rosrun

mtype: rosrun
mbody: {rosmachine: fullMachineId, args: args}

Reply:

mtype: rosrunSent
mbody: {rosmachine: fullMachineId, args: args}

roslaunch

Performs a 'roslaunch' command on a machine in the ROS instance.

mtype: roslaunch
mbody: {rosmachine: fullMachineId, args: args}

Reply:

mtype: roslaunchSent
mbody: {rosmachine: fullMachineId, args: args}

kill

Kills a process on one of the hosts in the ROS instance.

mtype: kill
mbody: {rosmachine: fullMachineId, args: args}

Reply:

mtype: kill
mbody: {rosmachine: fullMachineId, args: args}

TODO: Incorporate PID identifier.

message

Sends message to a ROS topic, as if it had been published by a regular ROS node.

mtype: message
mbody: {rosInstance: rosInstanceId, topic: topicName, message: <JSON message>}

Server -> Browser

rosInstanceGraphAdd

mtype: rosInstanceGraphAdd
mbody: {rosInstance: rosInstanceId, graph: hash}

rosInstanceGraphDel

mtype: rosInstanceGraphAdd
mbody: {rosInstance: rosInstanceId, graph: ['key1', 'key2'...]}

rosInstanceGraphUpd

mtype: rosInstanceGraphUpd
mbody: {rosInstance: rosInstanceId, graph: [ [node1Name, node1Value], [node2Name, node2Value], ... ]}

node1Value becomes the 'data' attribute of the uiGraph node.

Agent -> Server

agentConnect

mtype: agentConnect
mbody: {rosinstance: rosInstanceBase, org: orgId, user: userName, secret: secret}

Reply:

mtype: agentConnected
mbody: instanceN

or

mtype: agentRefused
mbody: "Human readable error message"

graphAdd

mtype: graphAdd 
mbody: [[key1, value1], [key2, value2], ... [keyN, valueN]]

graphUpd

mtype: graphUpd
mbody: [[key1, value1], [key2, value2], ... [keyN, valueN]]

graphDel

mtype: graphDel
mbody: [key1, key2, ... keyN] ??? Is it that or [[key, value], [key, value] ...]

MAYBE ****************

rosrunResult

mtype: rosrunResult
mbody: ?

END NEW *****************

Server -> Agent

subscribeGraph

mtype: subscribeGraph

rosrun

mtype: rosrun
mbody: {args: args}

roslaunch

mtype: roslaunch
mbody: {args: args}

kill

mtype: kill
mbody: {args: args}

message

Publish a ROS message to a topic.

mtype: message
mbody: {topic: topicName, message: {...JSON encoded version of ROS message..}}

if an error then will get:

mtype: messageStatus
error: 

TODO

  • Add general compression to protocol. Have tried LZW but without success so far (apparent incompatibilities between JS and Python implementations).
  • Add specific, efficient compression for images and streams of images.
  • Add more robust authentication, perhaps involving Rails server.
  • Improve robustness, error trapping in sockets. One connection failure should not bring down other clients. A general failure should crash out directly to let Heroku reboot.
  • Ideally, agents and browsers should be able to invisibly reconnect after server reboot.
  • Test scaling to many simulataneous clients with heavey messaging loads. How big / how many servers needed?

More generally, should meta-information on ROS instances, messages etc. be added here or on the Rails server? Right now, luxserver has no persistent state which is kind of nice.

License

(c) 2015 Robot Lux. All Rights Reserved.

Contributors

manacoa

Issues