Follow

Can the SmartServer IoT use WebSockets for Custom Web Pages (KB1397 )

WebSockets are supported in SmartServer 2.75.020+.

This document show how to create SmartServer hosted Web page that uses Web sockets to get datapoint updates. 

How WebSockets Work:

A WebSocket is a persistent connection between your Web page and the SmartServer and allows the Web page to receive live datapoint updates for subscribed datapoints. The CMS Web pages use WebSockets.

If you do an ondemand GET request using max_age , the GET response will return the cached value and sometime later you will see a WebSocket update for this datapoint. If you weren't using WebSockets you would have to do a second GET request to see a datapoint update. Thus WebSockets speeds up the process for getting datapoint updates. WebSocket updates are typically live datapoint values and are not cached values (i.e., the SmartServer just went on the wire to get the latest datapoint values). Ondemand GET request use a max_age query parameter that specifies max age time. If the last datapoint value is older than the time specified by max_seconds the SmartServer will go on the wire to read the datapoint value.

WebSockets also eliminates the problem of databack feedback flickering for some applications when you use two datapoints for a single Web page HTML element like image swappers or textfields (one input datapoint for writing and one output datapoint for feedback). When not using WebSockets, after changing the value for a multi-datapoint HTML element on the Web page, the value may seem to flicker between new value, the old value and then the new value again. This is known as datapoint feedback flickering and is caused by using the GET response cached output datapoint value. WebSockets eliminate this issue as the HTML elements values are only updated on live WebSocket data and not the cached GET response data.

To use WebSockets, you first need to open a WebSocket connection to the SmartServer. Next subscribe to a list of datapoints (using dpQualifiers) that is needed for the Web page. WebSocket updates will occur if you do a periodic ondemand GET request or if datapoint values is updated by some other process (another Web page, periodic monitoring or other processes doing ondemand GETs for that datapoint). Do an initial GET request for all datapoint used on the Web page so that the datapoint values on the Web page can be populated and then do a periodic GET request for only those datapoints that will change value.

The WebSocket update format is different then the GET response format so you need to design your code to support both.

Please read the Important Design Considerations before starting your own development.

Important Design Considerations: Read before continuing

1. For a single User Login, only one Web page that uses WebSockets (CMS or Custom Web pages) can be opened at a time, otherwise only one Web page may see datapoint updates.

A subscribe GET request is used to receive WebSocket datapoint updates for a specific list of datapoints. If more than one WebSocket Web page is open for a give User Login then all of these Web pages will see the same list of datpoint updates. Since there is only one subscribe list per user login the last Web page that sent the subscribe request will control which datapoint are updates are sent. This issue also applies to CMS webpages. For example, if you have two CMS Web pages open and the Datapoint Browser Widget on both Web pages show different datapoints, the last Web page that was opened or the last Datapoint Browser Widget that had its datapoint list change will control which datapoints have WebSocket updates.

2. You need to make sure your Web page doesn't use too much CPU bandwidth

The fastest update rate should be poll interval of every 2 seconds with 5 datapoints per poll request using a max_age of 2 seconds.

This corresponds to a EPS of 2.5 and you should subtract this number from the total EPS supported for each software version. That is, if the total EPS specified is 20 EPS then subtracting 2.5 means that 17.5 EPS can be used for other functions (like periodic monitoring). If you can try, to use a slower poll interval. If a Web page has more than 5 datapoints (most Web pages do) then each poll interval will be doing a GET request with a different list of datapoints.

Other Design Considerations:

1. Only do periodic datapoint GET requests for devices that are UP (deviceHealth = normal)

You should design your Web page to take into account that some devices may go down before and during accessing your Web page. When a device is down you should eliminate all datapoints in your datapoint GET request. Not doing so will cause a big CPU hit. When a down device goes back up you should add it back to the list. Do a periodic device status check, every 5 to 10 minutes, using a PUT request listing device IDs.

2. Reduce the number of datapoints in your peridodic polling.

Some UI graphics (clickable imageswappers, SVG and some html inputs) use a output datapoint for current value and an input datapoint for changing the value. To reduce the number of datapoints in a Web socket GET request, try to minimize the number of datapoints are polled. The intial GET request typically includes a list of all datapoints. After that, periodic requests use a limited list (input datapoints for multi-datapoint HTML elements or datapoints never updated should be taken out of the periodic GET request).

Datapoint Format:

Custom Web pages define datapoints using the following datapoint path format

        <deviceName>/<blockName>/<blockIndex>/<datapointName>

for example:

        Sensor1/Lamp/0/nvoValueFb

To use Web sockets you need to convert the datapoint path to a dpQualifier pathname used by the Web socket subscribe and for the Web socket GET request. The dpQualifer is the same one returned when getting the Datapoint value.

dpQualifer format:

 <SmartServerSID>/<Protocol>/<DeviceDID>/<BlockName>/<BlockIndex>/<DatapointName>

for example:

17q3awh/lon/5/Lamp/0/nvoValueFb

Ondemand GET request with a list of datapoints

/iap/devs/*/if/*/*/*+qualifier=-<dpQualifier list>/value?max_age=5&noxs=true

dpQualifiers in a GET URL must have the the "/" replaced with a "%2F%"

For example, to do an ondemand GET request for two datapoints

https://10.1.128.82/iap/devs/*/if/*/*/*+qualifier=-17q3awh/lon/F5/Lamp/0/nviValue,17q3awh/lon/5/Lamp/F1/nvoValueFb/value?max_age=0&noxs=true

What is sent on the wire: notice how the trailing "/value?max_age" still has the "/".

https://10.1.128.82/iap/devs/*/if/*/*/*+qualifier=-17q3awh%2Flon%2F5%2FLamp%2F0%2FnviValue,17q3awh%2Flon%2F5%2FLamp%2F1%2FnvoValueFb/value?max_age=0&noxs=true

There are many types of WebSocket updates, datapoint updates have an action of "UPD:DATAPOINT"

WebSocket Instructions:

1. Make a web sockets connection

let socketRequest = "wss://"+ window.location.hostname + ":8443/iap/ws";

2. Get a device list so you can convert the Web page datapoint path to the dpQualifer

Url:
        https://10.1.128.82/iap/devs?short=true

3. subscribe to all datapoints

Url:
        https://10.1.128.82/iap/dp/updates/subscribe

payload:
["17q3awh/lon/5/Lamp/0/nviValue","17q3awh/lon/5/Lamp/0/nvoValueFb","17q3awh/lon/5/LightSensor/0/nvoLuxLevel"]

4. Do an initial Web socket GET request for all datapoints using max_age 0

Use one of the following formats:

a. GET request format: get all properties for a datapoint -- needed if you need the datapoint SNVT type

/iap/devs/*/if/*/*/*+qualifier=-<dpQualifier list>/*?max_age=0&noxs=true

b. GET request format: get only datapoint value -- typically you would use this one

/iap/devs/*/if/*/*/*+qualifier=-<dpQualifier list>/value?max_age=0&noxs=true

Url: Before replacing the qualifier path "/" with "%2F"

https://10.1.128.82/iap/devs/*/if/*/*/*+qualifier=-17q3awh/lon/5/Lamp/0/nviValue,17q3awh/lon/5/Lamp/1/nvoValueFb,17q3awh/lon/5/LightSensor/0/nvoLuxLevel/value?max_age=0&noxs=true

Url: What is sent on the wire

https://10.1.128.82/iap/devs/*/if/*/*/*+qualifier=-17q3awh%2Flon%2F5%2FLamp%2F0%2FnviValue,17q3awh%2Flon%2F5%2FLamp%2F1%2FnvoValueFb,17q3awh%2Flon%2F5%2FLightSensor%2F0%2FnvoLuxLevel/value?max_age=0&noxs=true

 Use GET response (cached values) to populate Web page

5. Perioically issue a limited Web socket GET request using a non-zero max_age for devices that are UP.

/iap/devs/*/if/*/*/*+qualifier=-<dpQualifier list>/value?max_age=5&noxs=true

https://10.1.128.82/iap/devs/*/if/*/*/*+qualifier=-17q3awh%2Flon%2F5%2FLamp%2F1%2FnvoValueFb,17q3awh%2Flon%2F5%2FLightSensor%2F0%2FnvoLuxLevel/value?max_age=5&noxs=true

   DON'T use GET response (cached values) to update the Web page , use only WebSocket updates.

6. Response data structure is different between GET response and the Web socket data

a.WebSocket update

Look for "action":"UPD:DATAPOINT" in the WebSocket update.

For each datapoint:

{\"datapointQualifier\":\"17q4bn7/lon/1/device/0/nviLamp1\",\"value\":{\"value\":0,\"state\":0},\"blockName\":\"device\",\"blockIndex\":0,\"datapointName\":\"nviLamp1\"}

WebSocket event.data for two datapoints

"{\"action\":\"UPD:DATAPOINT\",\"payload\":[{\"datapointQualifier\":\"17q4bn7/lon/1/device/0/nviLamp1\",\"value\":{\"value\":0,\"state\":0},\"blockName\":\"device\",\"blockIndex\":0,\"datapointName\":\"nviLamp1\"},{\"datapointQualifier\":\"17q4bn7/lon/1/device/0/nvoPulseOut\",\"value\":50,\"blockName\":\"device\",\"blockIndex\":0,\"datapointName\":\"nvoPulseOut\"}]}"

b. The GET response

7. Check Device state of all device every 5 to 10 minutes

Use the PUT listByIDs to get the status for only devices used on the Web page. Change the the limited Web Socket GET request if devices go up or down.

Note, devices may be reported down if a single datapoint poll doesn't work.

For example:

Put url:
/iap/devs/listByIDs
Payload:
[3,4]

/*************************************************************************
 * 
 *   ivProcessReadDeviceDpData - Used to send Datapoint WebSocket update
 *      - Create function in your code
 * **********************************************************************/
    let ivSocketRequest = "wss://"+ window.location.hostname + ":8443/iap/ws";
    let ivSocket;
    let ivWsShowConsoleLog = true// set to false to reduce memory usage in Web Browser
    var ivWsMessage, ivWsLastDatapointUpdate;
    var ivWsSocketConnected = false;
    
    
    function ivWsLogout() {
        ivSocket.close();
    }
    function ivWebSocketInit() {
        ivSocket =  new WebSocket(ivSocketRequest);
        ivSocket.onopen = function(event) {
            if(ivWsShowConsoleLog)
                console.log("Web Socket Open");
            ivWsSocketConnected = true;
       }
        ivSocket.onmessage = function(event) {
            //alert('[message] Data received frm server: ' + event.data);
            if(bivNeedToSubscribe)
                return;
            ivWsMessage = JSON.parse(event.data);
            var i, index = -1;
            var ivWsLastDatapointUpdate;
            try
            {
                if(ivWsMessage.action === "UPD:CPU") {
                }
                else if(ivWsMessage.action === "UPD:EPS") {
                }
                else if(ivWsMessage.action === "UPD:DATAPOINT"){
                    
                    ivWsLastMessage = ivWsMessage;
                    if(ivWsShowConsoleLog)
                        console.log(ivWsMessage);
                    json = [];
                    if(typeof ivWsMessage.payload !== 'undefined'){
                        for(i=0; i < ivWsMessage.payload.length; i ++)
                        {
                            index ++;
                            ivWsLastDatapointUpdate = {};
                            ivWsLastDatapointUpdate.datapointQualifier = ivWsMessage.payload[i].datapointQualifier;
                            ivWsLastDatapointUpdate.value = ivWsMessage.payload[i].value;
                            json[index] = ivWsLastDatapointUpdate;
                        }
                        if(json.length > 0) {
                            ivProcessReadDeviceDpData(01"", json)
                        }
                    }
                }
            }
            catch {}
        }
        ivSocket.onclose = function(event) {
            ivWsSocketConnected = false;
            if(event.wasClean) {
                if(ivWsShowConsoleLog)
                    console.log("web Socket close cleanly");
           
            }
            else {
                if(ivWsShowConsoleLog)
                    console.log('Web Socket died, code = ' + event.code + 'reason = ' + event.reason);
            }
        }
        ivSocket.onerror = function(error) {
            if(ivWsShowConsoleLog)
                console.log("error: " + error.message);
        }
    }
Was this article helpful?
0 out of 0 found this helpful
Have more questions? Submit a request

Comments

Powered by Zendesk