Skip to content
Frank Weinberg edited this page Nov 3, 2024 · 4 revisions

Javascript library

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.

Initialization

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();

Registering Channels

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.

WS.Register( "channel" );

Simply registers the channel for listening.

WS.Register( "ScoreBoard.CurrentGame.Clock(Period).Time");

WS.Register( [ "channel1", "channel2", "channel3" ]);

Registers a group of channels.

WS.Register( ["ScoreBoard.CurrentGame.Clock(Period).Time", 
              "ScoreBoard.CurrentGame.Clock(Jam).Time"
            ]);

WS.Register( "channel", function( key , value ) { callback function });

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);

WS.Register( [ "channel1", "channel2", "channel3" ], function( key, value) { callback function });

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.

Child Properties

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.

Wildcards

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.

Enriched keys

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.

Get State Values

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"];
}

Set State Values

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.

Change State Value

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).

Commands

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);

Low level Websocket interface

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

Actions are sent by the browser, and look like {"action": "Action Name", ...}.

Register

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

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 flags 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.

StartNewGame

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.

Ping

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

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",
  }
}

Pong

A pong is sent by the backend as reply to a ping. It looks like:

{ "Pong": "" }

error

An error is sent by the backend if a command contained a wrong action. It looks like:

{ "error": _errorMessage_ }
Clone this wiki locally