-
Notifications
You must be signed in to change notification settings - Fork 56
WebSocket Commands
CRG comes with a bundled library to allow frontend screens to set or retrieve information in the backend state via the Websocket protocol. This page will document the basic syntax of the commands used for this purpose.
If you can't use this library, e.g. because you are using a different programming language, see the section on the low level Websocket commands below.
In order to use the library, the following scripts must be loaded. This will usually be via a <script>
command in the HTML file.
For a list of available channels see WebSocket Channels.
/external/jquery/jquery.js
/json/core.js
Note that as JavaScript commands, all of the WS.*
commands are case sensitive. WS.Register()
will work, ws.register()
will not.
To initialize a connection with the server, the script must contain the following two lines. No parameters are required. If these two commands are NOT called, any attempt to query the state will return "undefined".
WS.Connect();
WS.AutoRegister();
The WS.Register()
command is used to establish a listener for a given channel or set of channels. In order to access a value within the backend state, a corresponding Register
command must be executed first.
Simply registers the channel for listening.
WS.Register( "ScoreBoard.CurrentGame.Clock(Period).Time");
Registers a group of channels.
WS.Register( ["ScoreBoard.CurrentGame.Clock(Period).Time",
"ScoreBoard.CurrentGame.Clock(Jam).Time"
]);
Registers a channel for listening and sets a callback function which is executed every time the value of the channel changes. For example, this could be used to update a scoreboard display every time a score value changes:
WS.Register( "ScoreBoard.CurrentGame.Period(1).Jam(13).TeamJam(2).Score", function(k, v) {
updateScore(k,v);
})
This example would call the updateScore
function each time team 1's score changed. The first variable, usually called k
or key
, passes the name of the channel that was updated. The second variable, usually called v
or value
, passes the value the channel was changed to. The key and value will be passed into the function using whatever names are declared in the "function" definition.
In this example, if Team 2's jam score in Period 1 Jam 13 changed to 3, the following command would be executed:
updateScore( "ScoreBoard.CurrentGame.Period(1).Jam(13).TeamJam(2).Score", 3);
Registers a listener to execute the same callback function if ANY of the channels in the list change.
WS.Register([ "ScoreBoard.CurrentGame.Clock(Period).Running",
"ScoreBoard.CurrentGame.Clock(Jam).Running",
"ScoreBoard.CurrentGame.Clock(Intermission).Running"
], function(k,v) {
manageClocks(k,v);
});
The k
or key
parameter is used here to allow the callback function to determine which channel changed to trigger execution of the function.
Any properties which are children of a registered channel can also be accessed without a further registration. For example,
WS.Register( "ScoreBoard.CurrentGame.Team(1)");
will allow the script to access the property ScoreBoard.CurrentGame.Team(1).Score
.
An asterisk may be used as a wildcard for values in parentheses.
WS.Register( "ScoreBoard.CurrentGame.Team(*).Name");
will allow the script to acces either ScoreBoard.CurrentGame.Team(1).Name
or ScoreBoard.CurrentGame.Team(2).Name
.
If you register multiple keys, keys with wildcards or keys with child properties to a callback function, you will commonly have to access parts of the key in your callback funtion. In order to do so without lots of string parsing, you can access these parts via the key variable as shown:
WS.Register( "ScoreBoard.CurrentGame.Team(*).Skater(*)", function(k,v) {
updateSkater(k,v);
});
function updateSkater(k,v){
var team = k.Team;
var skater = k.Skater;
var penalty = k.Penalty;
}
The variable team
now contains the value of the first wildcard, the variable skater
contains the value of the second wildcard. If the update concerns a penalty, penalty
will contain the id of the penalty otherwise it will be undefined.
k.field
will contain the part of the key after the last period and k.parts
will contain an array of all the parts separated by periods without the parentheses and their content. So parts[0]
will be ScoreBoard
, part[1]
will be CurrentGame
, part[2]
will be 'Team'
, part[3]
will be 'Skater'
and part[4]
, and following will vary between updates.
As an example, for the key ScoreBoard.CurrentGame.Team(1).Skater(abcdefg).Penalty(2).Code
the values accessible through the enriched key k
would be:
-
k.ScoreBoard
:''
-
k.CurrentGame
:''
-
k.Team
:'1'
-
k.Skater
:'abcdefg'
-
k.Penalty
:'2'
-
k.Code
:''
-
k.field
:Code
-
k.parts
:['ScoreBoard', 'CurrentGame', 'Team', 'Skater', 'Penalty', 'Code']
Note that the ids will always be returned as strings, even if they represent a numeric value.
And remember that javascript is case sensitive! k.team
would not have worked in this case nor would k.Field
have.
Once a channel is registered, its value can be retrieved. This includes from functions that are NOT callback functions for the channel. This is accompished via the WS.state
command.
WS.state[ _channel_ ]
This returns the value of a registered channel. It returns undefined
for unregistered channels. It also returns undefined
if the WS.Connect()
and WS.AutoRegister()
commands have not been issued. Note the capitalization and use of square brackets rather than parentheses.
Example:
WS.Register( "ScoreBoard.CurrentGame.Team(1).Score");
manageScore(k, v) {
var score = WS.state["ScoreBoard.CurrentGame.Team(1).Score"];
}
Values in the state can be set using the WS.Set()
command.
WS.Set( _channel_, _value_ );
Channels do NOT need to be registered in advance to use the WS.Set()
command.
Example:
WS.Set("ScoreBoard.CurrentGame.Team(1).TripScore", 4);
This will set the score for team 1's current scoring trip to 4 (and update all dependent values like the team's jam score and total score).
Note that you can only set one channel at a time and have to give the full channel name. Wildcards will NOT work.
For channels with numeric values you can also use WS.Set()
to change the value by some amount instead of setting it directly.
Example:
WS.Set("ScoreBoard.CurrentGame.Team(1).TripScore", -1, 'change');
This will reduce the score of team 1's current scoring trip by 1 (and also update all dependent values).
There are also some channels that do not have values but are used to issue commands. These commands are executed by setting their value to true. For example:
WS.Set('ScoreBoard.CurrentGame.StartJam', true);
CRG exposes a WebSocket endpoint at ws://localhost:8000/WS/
, which is used to gain access to and change CRG state. This section documents it.
Actions are sent by the browser, and look like {"action": "Action Name", ...}
.
The register action specifies state that the client is interested in knowing and getting updates on. These are provided as paths, which represent subtrees of events. For example:
{
"action": "Register",
"paths": [
"ScoreBoard.CurrentGame.Team(*).Skater",
"ScoreBoard.CurrentGame.Clock(Jam).Number"
]
}
would register the skaters on both teams and the jam number.
Registrations are cumulative - adding a new path does not cancel prior registrations. On registration each registered channel will get an initaial update and further updates will be provided as the state of the channel changes.
Set allows the browser to store state. For example:
{
"action": "Set",
"key": "ScoreBoard.CurrentGame.Team(1).TripScore",
"value": -1,
"flag": "change"
}
The key
must be the full name of a channel, and values must be valid for the given channel or the literal null
which will clear/delete the given element. If the backend doesn't know the channel or the value is invalid for the given channel, an error will be recorded on the backend's output.
Valid flag
s are change
and reset
. change
can be used for any numerical value and will cause the element to be changed by the given value instead of set to it. reset
currently only has an effect for the time on a clock which will be reset it to its initial value.
If the value changes and a WS client (including the one sending the request) is registered for ScoreBoard.CurrentGame.Team(1).Score
, it will be informed about the new value. If the command does not result in a changed value for any reason no notifications will be sent out.
This action is used to start a new ad-hoc game. It looks like:
{
"action": "StartNewGame",
"data": {
"Team1": _PreparedTeamId_,
"Team2": _PreparedTeamId_,
"Ruleset": _RulesetId_,
"IntermissionClock": _number_,
"Advance": true|false,
"TO1": _number_,
"TO2": _number_,
"OR1": _number_,
"OR2": _number_,
"Points1": _number_,
"Points2": _number_,
"Period": _number_,
"Jam": _number_,
"PeriodClock": _number_
}
}
The fields after Advance
are only evaluated if the latter is set to true
. In that case they are used to fast forward the game to a given state.
The ping action returns a pong response. This is intended as a heartbeat/keepalive to prevent quiet connections getting closed. Clients are recommended to use this every 30s. It usually looks like:
{ "action": "Ping" }
State is sent by the backend and will contain a set of channel-value pairs informing the frontend about changed values. For example:
{
"state": {
"ScoreBoard.Settings.Setting(ScoreBoard.Clock.Sync)": "true",
"ScoreBoard.Settings.Setting(ScoreBoard.View_BoxStyle)": "box_flat_bright",
}
}
A pong is sent by the backend as reply to a ping. It looks like:
{ "Pong": "" }
An error is sent by the backend if a command contained a wrong action. It looks like:
{ "error": _errorMessage_ }