All communication over the WebSocket connection is done through Messages, which are passed back and forth between the WebSocket client and the robot using a consistent JSON format. They have three fields, namespace
, type
, and payload
.
namespace
is used to route messages on the robot/server side, and to allow clients to specify what information they are interested in (through subscription). This allows for separation of concerns.
The type
field is used to specify what the message means. Types are scoped to namespaces, so you can safely reuse them between namespaces.
The payload
field optionally contains arbitrary text (often escaped JSON). The libraries will handle performing the escaping for you.
Messages can be sent either as responses to a specific client, or broadcast to an entire namespace (all connections that have subscribed to the namespace will receive the message).
The entirety of the Java WebSocket library lives in the org.firstinspires.ftc.robotserver.internal.webserver.websockets
package.
Class names referenced here are located therein.
The Java library allows you to set up namespace handlers and send broadcasts. All namespace handlers need to extend the WebSocketNamespaceHandler
abstract class.
There are two ways to handle messages, which can be used together within a namespace handler:
-
You can override the
onMessage()
method, which will be called for every received message that is in the relevant namespace. -
You can override the
registerMessageTypeHandlers()
method, which will provide a map that you can use to register handlers for specific message types. This allows you to avoid long switch statements or else if chains in a complex namespace.
The handler callbacks will provide you with an FtcWebSocketMessage
instance (the received message) and an an FtcWebSocket
instance (the WebSocket connection that sent it). FtcWebSocket
has a send()
method that you can use to reply to the sender if necessary.
WebSocketNamespaceHandler
also has onSubscribe()
and onUnsubscribe()
methods that you can override to respond to the coming and going of clients, should you have a need for that.
Once you've created a namespace handler, you need to register it. I recommend registering namespace handlers from a the setState
method of a WebHandlers class.
Make sure you use WebServer
's new getWebSocketManager()
method to provide the WebSocketManager
instance to your handler's
constructor if you will be sending broadcasts.
To send a broadcast, call broadcastToNamespace()
on the WebSocketManager
instance. This will let you send a message to all WebSocket connections that have subscribed to your namespace. A namespace must be registered before you can send a broadcast to it. If you don't need to receive messages from the namespace you want to broadcast to, you can call registerNamespaceAsBroadcastOnly(namespace)
on the WebSocketManager
instance.
The js library consists of two files, websocket-core.js
and websocket-iframe.js
. Both of them live in RobotServer/src/main/assets/js
.
The js library supports the idea of having multiple pieces of UI on the screen that can remain separate from each other and subscribe to different namespaces. Doing this through iframes is especially easy. Every iframe that wants to use the WebSocket connection includes the websocket-iframe.js
file. This file sets up a WEBSOCKET_LIB
object that contains the WebSocketMessage
constructor and a webSocketManager
object, which is scoped to the iframe.
WebSocket traffic from different iframes uses the same underlying WebSocket connection. The connection is only initiated when the websocket-iframe.js
file is loaded. If the connection is severed, a reconnection attempt will be made when a ping sent by frame.html
succeeds.
The websocket-iframe.js
file includes documentation about the correct setup sequence for using the WebSocket connection. The documentation for the webSocketManager
methods lives in websocket-core.js
, where the WebSocketManager constructor is.
webSocketManager
has these methods: sendMessage
, registerConnectionStateListeners
, subscribeToNamespace
, registerNamespaceHandler
, and registerTypeHandler
.