A WebSocket is a persistent TCP connection between the browser page and the DataHub data engine. It uses the DHTP command set, giving it similar performance characteristics to a DataHub tunnel. The result is that the web page can interact with the DataHub data at high speeds with very low latencies.
A WebSocket connection requires you to write some JavaScript in your web page to create and maintain the connection to a DataHub instance. The DataHub installation contains some JavaScript files to help with this:
/scripts/DHWebSocket.js
/scripts/lispparse.js
These files are located here (32-bit and 64-bit versions):
C:\Program Files (x86)\Cogent\Cogent DataHub\Plugin\WebServer\html\
scripts
C:\Program Files\Cogent\Cogent DataHub\Plugin\WebServer\html\scriptsLoad these two files using <script/> tags in your
HTML file. They provide the WebSocket implementation and the message parser
respectively.
The WebSocket connection is asynchronous, meaning that your JavaScript page will send messages due to events on the page, typically user interactions, timers, or changes to the connection state, and will not wait for a response to those messages. Similarly, messages from the DataHub server will arrive at any time. Your JavaScript code is therefore designed as a collection of event handlers that respond to different events from the connection or from the web page.
A typical implementation of a WebSocket connection to the DataHub server looks like this:
Add the two JavaScript references as listed above.
Create a <script> tag that contains the
initialization for the WebSocket.
Create a new DataHubWebSocket().
Optionally set a user name and password.
Optionally set a connection heartbeat and timeout.
Define callbacks for connection events:
onConnectionSuccess
onConnectionFailure
Define handlers for data point change events:
addPointHandler
Start the background connection loop:
connect(host, port, isSSL)
Once the connection callbacks and data point change event handlers are
defined, the DataHubWebSocket implementation will trigger
them based on events on the connection or the arrival of data from the DataHub
server.
DataHubWebSocket
classsend (string)
Transmits a LISP-formatted command to the DataHub server. Any command listed in the DataHub command set can be transmitted, so long as the user connecting on this WebSocket has sufficient permission to execute it. For example:
ws.send("(cset \"DataPid:PID1.Sp\"50)");setAuth(username, password)
Sets the user name and password to be used to authenticate on this connection. This will automatically be sent to the server when the connection is first established. Note that the password is transmitted in plain text, so it is advisable to use an HTTPS connection. For example:
ws.setAuth("admin", "admin");setHeartbeat(heartbeat, timeout)
Sets the heartbeat and timeout of the connection in milliseconds. Setting both of these to zero will disable the heartbeat. If the heartbeat is disabled it could take a long time to detect some kinds of connectivity failure. The heartbeat should be less than half the timeout. For example:
ws.setHeartbeat(5000,15000);
connect(hostname, port, is_ssl)
Starts the connection process. The connection process continues in
the background after this call, and is not complete until either
onConnectionSuccess or
onConnectionFailure is called. If the
connection fails it will automatically re-try every 5 seconds. For
example:
ws.connect(location.hostname, location.port,
location.protocol == "https:");disconnect()
Terminates the WebSocket connection and stops the 5-second re-try
timer. The DataHubWebSocket will not attempt
to connect again until connect() is called again. For
example:
ws.disconnect();
addPointHandler(pointName, handlerFunction)
Adds a function to the point change handler table for the
specified point name. The universal point name,
*, indicates all points. If multiple handlers are
defined for a point then all handlers will be called each time the
value of the point changes. If a universal handler is defined, it
will be called in addition to any specific handlers. The universal
handler is called after all specific handlers complete.
The handler function is a JavaScript function accepting a single
argument. The argument is an array of the tokens from a
(point) message, which contains the same
tokens as the (write) message. All tokens are
delivered as strings, and should be converted to numbers where
appropriate. For example:
ws.addPointHandler("DataPid:PID1.Sp", function(tokens)
{
console.log (tokens[1] + " = " + tokens[3]);
});
removePointHandler(pointName, handlerFunction)
Removes a point value change handler from the handler table. The
handlerFunction must compare as equal
(using == comparison) to the
handlerFunction originally provided to
addPointHandler.
registerPoint(pointName)
Sends a message to the DataHub server to register to be notified of value changes for this point. The point name must be fully qualified, including the domain: prefix. If the WebSocket is not currently connected, the point name will be stored, and it will automatically be registered when the connection is subsequently established. If the point is already registered, this call is ignored. For example:
ws.registerPoint("DataPid:PID1.Mv");registerDomain(domainName, onceOnly)
Sends a message to the DataHub server to register to be notified for
all points in a data domain. If onceOnly is
true, the domain is registered such that exactly one value message
will be transmitted for each point. If a new point is added to the
domain after this call is made, that point will be automatically
registered. If onceOnly is false, the DataHub
server will transmit the current values of all points immediately,
and then transmit any subsequent value changes for each point until
the connection is lost or the point is unregistered. If the domain
is already registered, this call is ignored. For example:
e.g., ws.registerDomain("DataPid", false);unregisterPoint(pointName)
Sends a message to the DataHub server indicating that it should not transmit value change notifications for this point. If the point is not currently registered then this call does nothing. For example:
ws.unregisterPoint("DataPid:PID1.Mv");unregisterDomain(domainName)
Sends a message to the DataHub server indicating that it should not transmit value change notifications for any point in this data domain. For example:
ws.unregisterDomain("DataPid");escaped(string, isQuoted)
Adds escape sequences to the provided string such that it can be
correctly parsed by the DataHub server. If
isQuoted is true, the returned string
will start and end with double-quote characters. If
isQuoted is false, the caller is
responsible for adding the double-quote characters to the message
being sent to the DataHub server. For example:
ws.escaped('The dog said "woof"!', false) > The dog said \"woof\"!
ws.escaped('The dog said "woof"!', true) > "The dog said \"woof\"!"
ISOlocaltimeStr(date)
,
ISOlocaldateStr(date)
,
ISOlocalDTstr(date)
Accepts an argument of type Date, and returns a string representing the ISO-formatted time string, date string, and date+time string respectively representing the local time representation of the provided date. For example:
ws.ISOlocaltimeStr(new Date()) > '09:41:02' ws.ISOlocaldateStr(new Date()) > '2021-10-26' ws.ISOlocalDTstr(new Date()) > '2021-10-26 09:41:02'
StampTime(seconds)
Accepts a floating point number of seconds in the Unix epoch (seconds since January 1, 1970) and returns a the local ISO time representation with millisecond accuracy. For example:
ws.StampTime(Date.now()/1000.0) '09:41:02.452'
The DataHubWebSocket class exposes an array called
handlers that allows you to define custom code that will
run when any command message arrives from the DataHub server. By default, two
handlers are defined, for point and echo
messages respectively. You can replace these with your own handlers if you do
not want to use the default point handling mechanism.
A command handler is a function taking a single argument. The argument is an array of the tokens in the command produced by parsing the incoming string. The first token in the array is the name of the command.
To add a custom event handler for any message, assign a function to
handlers[command]. For example, to add a custom handler
for a (domains name1 name2) message arriving from the DataHub
server, do:
ws.handlers["domains"] = function (data) { /* do something */ };A handler can be disabled by setting its function to null.
<html>
<head>
<title>Simple WebSocket connection to DataHub</title>
<!-- These two scripts are necessary to make a WebSocket
connection to the DataHub server -->
<script src="/scripts/DHWebSocket.js"></script>
<script src="/scripts/lispparse.js"></script>
</head>
<body>
<h1>Simple WebSocket connection to DataHub</h1>
<h3>Connection messages:</h3>
<div id="connlog"></div>
<h3>Data Values:</h3>
<div id="datalog"></div>
<script>
// Collect a user name and password somehow from
// the user, if necessary.
var username = "";
var password = "";
ws = new DataHubWebSocket();
ws.setAuth(username, password);
ws.setHeartbeat(5000, 30000);
// This function will be called if the connection succeeds.
// It will be called every time the connection is established or
// re-established after a connection loss. You should treat all
// connections as being fresh - the DataHub server will not
// maintain state after a connection loss. This means that any
// data point registrations and other setup commands must be
// re-transmitted each time.
ws.onConnectionSuccess = function (host, port)
{
logConnection("Connection succeeded");
// This code will register for the current values of all
// points in the DataPid domain, but not for future changes.
ws.registerDomain("DataPid", true);
// Request a list of all data domains. The return will be
// handled in the handler for the response message,
// (domains domain1 domain2 ...)
ws.send("(domains)");
}
// This function will be called each time a connection attempt
// fails, and each time that a previously successful connection
// is disconnected.
ws.onConnectionFailure = function (host, port)
{
logConnection("Connection failed to "
+ host + ":" + port);
}
// During onConnectionSuccess above we sent a (domains)
// command. This handles the response when it arrives.
ws.handlers[ "domains"] = function (data)
{
logConnection("Domains: " + data);
// This code will register for point changes from all
// domains, including all newly created points and
// all new values of every point as they change.
for (var i = 1; i < data.length; i++)
{
ws.registerDomain(data[i], false);
}
}
// When a message arrives and does not have a specific handler,
// it will execute the special AsyncMessage handler instead.
ws.handlers[ "AsyncMessage"] = function (data)
{
logConnection("AsyncMessage: " + data);
}
// Ignore (alive) messages. They are produced when the
// connection is idle and a heartbeat time has been set.
ws.handlers[ "alive"] = function (data)
{
logConnection("Received alive message");
};
// Create a point change handler that logs the point value.
function pointHandler (data)
{
logData("Point: " + data);
}
// Add the point change handler to all points. We could use a
// fully-qualified point name (like "DataPid:PID.Mv") instead
// of "*" here to call the above handler only for that point.
// If there is a "*" handler it will be also be called after
// any point-specific handlers.
ws.addPointHandler("*", pointHandler);
// Logging functions that distinguish connection messages
// from data value messages. They limit the size of the
// message history to 4096 bytes.
var maxMessageSize = 4096;
function log(elementId, message)
{
var logTag = document.getElementById(elementId);
var str = logTag.innerHTML;
str += "[" + ws.StampTime(Date.now() / 1000) +
"] " + message + "<br/>\n";
if (str.length > maxMessageSize)
{
var pos = str.indexOf("\n", str.length - maxMessageSize);
str = str.substring(pos);
}
logTag.innerHTML = str;
}
function logConnection(message)
{
log("connlog", message);
}
function logData(message)
{
log("datalog", message);
}
// Finally, start the connection process. The connection is not
// actually made during this call. Instead an asynchronous con-
// nection attempt is made, and we will be notified of its success
// or failure in onConnectionSuccess or onConnectionFailure.
ws.connect(location.hostname, location.port,
location.protocol == "https:");
</script>
</body>
</html>