Monday, December 4, 2017 4:12 pm

Part I: WebSockets for Real-Time Web and IoT Applications

Written by
Rate this item
(1 Vote)
Part I: WebSockets for Real-Time Web and IoT Applications Photo Credit: Carlos Muza

Have you ever wanted to display your real-time sensor data on a webpage that can be viewed on any device with a modern web browser? How about a web page that serves as a dashboard for controlling and monitoring your embedded device in real-time? With WebSockets, you can do exactly that! In this article, we will briefly review what a WebSocket is, how it works, its benefits, and dive into a system-monitoring, dashboard-type application tutorial.

I. What is a WebSocket?

A WebSocket is a low-latency (real-time), full-duplex (bidirectional), long-running (persistent), single connection (TCP) between a client and server. WebSockets provide a huge benefit for real-time, event-driven web applications and are becoming more common to power real-time data updates and synchronization, live text chat, video conferencing, VOIP, IoT control and monitoring. They are used for a variety of apps in gaming, social and collaborative networks, logistics, finance and home, vehicle and industrial automation to name a few. Most major web browsers currently support their use.

The full-duplex aspect of WebSockets allows a server to initiate communication with a client whenever it would like, which is contrary to other protocols such as HTTP and AJAX in which the client must initiate communication. For us embedded folks, we can liken the WebSocket protocol to an interrupt based application instead of a polled application. In web-speak we are breaking from an HTTP based paradigm and trappings where a client must periodically initiate a connection to the server and then place a request to “pull” new data or state information. Furthermore, in HTTP, the server cannot “push” new data to the client as states change, but must first wait for a request from the client. This can be detrimental for real-time applications especially as client request frequencies scale.

WebSockets takes a different approach by establishing a persistent, bidirectional connection that allows the server to update the client application without an initiating request from the client. This not only allows for low-latency communication between a server and client, but it also reduces network traffic by eliminating the need for a client to send a packet just to request data from the server – The server can just send data as soon as it’s available or as states have changed. No request necessary. That’s a good reason why WebSockets are commonly used for applications like updating real-time stock prices, traffic reports, browser based multiplayer games, etc.

The WebSocket protocol was designed to work well with the legacy web infrastructure. It uses an HTTP-compatible handshake to establish a connection between the client and server. The process to start a WebSocket connection begins with the client sending an HTTP GET with the WebSocket "Upgrade" field. After a connection is initialized communication switches to a bidirectional binary protocol unlike the HTTP protocol.

GET /INDEX HTTP/1.1
Host: 192.168.1.220
Connection: Upgrade
Upgrade: websocket
Origin: http://192.168.1.220

If the server supports WebSockets, it responds with a HTTP UPGRADE response.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

At this point the TCP socket used for the HTTP communication is recycled to be used for the WebSocket connection, and either party can begin sending data. WebSockets use the same ports as HTTP – 80 for HTTP, and 443 for HTTPS. To list yet another benefit of WebSockets, a WebSocket header is only 2 to 6 bytes long.

Websockets data stream

As a comparison, Google’s SPDY research reports HTTP request headers can vary in size from ~200 bytes to over 2KB. Think of all the network traffic and communication latency you can reduce by switching from an HTTP-based implementation to a WebSocket implementation!

II. Display Real-Time DIP Switch States with JavaScript and WebSockets

We have several examples included in our NetBurner Network Development Kit (NNDK) tools to help you get started with WebSockets. We’ve just added the following example to help you display a local volatile variable on a webpage in real-time – All hosted on your NetBurner. This example also uses JavaScript to establish a WebSocket connection from the web page, and JSON (JavaScript Object Notation) to communicate the value of the local variable from the server (NetBurner) to the client (web browser).

To get started, you’ll need to download the example from the NetBurner GitHub repository, create a new project in the NBEclipse IDE, and import the source files that you downloaded. This process is described in detail in the NBEclipse Getting Started Guide that can be found on our webpage or in /nburn/docs/Eclipse of your NNDK install.

1. Starting an HTTP Server

Let’s begin by reviewing how to start the HTTP server and setup the WebSocket C++ backend.

OSSemInit( &SockReadySem, 0 );
StartHTTP();
TheWSHandler = MyDoWSUpgrade;
OSSimpleTaskCreate(InputTask, MAIN_PRIO - 1);

Starting an HTTP server is as simple as calling the StartHTTP() function. This will allow us to access the webpage defined in the /html directory of our source files by directing our web browser to the IP address of our NetBurner. We will use a semaphore to communicate between tasks that a WebSocket connection has been established. TheWSHandler is a callback function used by the HTTP task whenever a HTTP GET request with the WebSocket "Upgrade" field is received. This callback function oversees handling the WebSocket upgrade process and it must be defined by the user. Last, we create a task, called InputTask, to read from the WebSocket whenever data is available and handle errors.

2. Upgrading to a WebSocket connection with C++

int MyDoWSUpgrade( HTTP_Request *req, int sock, PSTR url, PSTR rxb ){
  if( httpstricmp( url, "INDEX" ) ){
    if( ws_fd < 0 ){
      int rv = WSUpgrade( req, sock );
      if( rv >= 0 ){
        ws_fd = rv;
        NB::WebSocket::ws_setoption( ws_fd, WS_SO_TEXT );
        OSSemPost( &SockReadySem );
        return 2;
      }
      else{
        return 0;
      }
    }
  }
  NotFoundResponse( sock, url );
  return 0;
}

Let’s take a look at the callback function that handles the WebSocket upgrade. This function gets passed in the URL of which the upgrade is being requested. Our HTML webpage, located in the /html directory of the source files, is named index.html. Therefore, we parse the source URL for the “INDEX” string. If you decide to add additional webpages that each use WebSockets, this is where you would parse the separate URLs so that you can have separate WebSockets per webpage. WSUpgrade() takes care of upgrading the HTTP connection. It will return the file descriptor value for the new WebSocket. We store this value, ws_fd, as a global variable so that all tasks can write, read, or close the connection.

3. Using C++ to write a JSON object to a WebSocket

void SendSwitchesReport( int ws_fd )
{
  SMPoolPtr pq;
  ParsedJsonDataSet JsonOutObject;

  // Assemble JSON object
  JsonOutObject.StartBuilding();
  JsonOutObject.AddObjectStart( "dipSwitches" );
  JsonOutObject.Add( "dip1", dipStates[0] );
  JsonOutObject.Add( "dip2", dipStates[1] );
  JsonOutObject.Add( "dip3", dipStates[2] );
  JsonOutObject.Add( "dip4", dipStates[3] );
  JsonOutObject.Add( "dip5", dipStates[4] );
  JsonOutObject.Add( "dip6", dipStates[5] );
  JsonOutObject.Add( "dip7", dipStates[6] );
  JsonOutObject.Add( "dip8", dipStates[7] );
  JsonOutObject.EndObject();
  JsonOutObject.DoneBuilding();

  // If you would like to print the JSON object to serial to see the format, uncomment the next line
  //JsonOutObject.PrintObject( true );

  // Print JSON object to a buffer and write the buffer to the WebSocket file descriptor
  int dataLen = JsonOutObject.PrintObjectToBuffer( ReportBuffer, REPORT_BUF_SIZE );
  writeall( ws_fd, ReportBuffer, dataLen );
}

After getting the state of the DIP Switches from the NetBurner MOD-DEV-70 carrier board and populating the dipStates array, we call SendSwitchesReport() to send the states in a JSON object. Writing to a WebSocket can be done just like writing to any file descriptor. In this case, we use writeall() so that we can send the whole JSON object in one packet. Otherwise, the JSON object might be sent as several smaller packets, which doesn’t play nice with JavaScript on the browser side.

4. Using JavaScript to establish a WebSocket connection and parse JSON

function MakeDataSocket(){
    if( "WebSocket" in window ){
        if( ( ws == null ) || ( ws.readyState == WebSocket.CLOSED ) ){
            ws = new WebSocket( "ws://" + window.location.hostname + "/INDEX" );
            ws.onopen = function(){};
            ws.onmessage = function( evt ){
                var rxMsg = evt.data;
                report = JSON.parse( rxMsg );

                dip1 = report.dipSwitches.dip1;
                dip2 = report.dipSwitches.dip2;
                dip3 = report.dipSwitches.dip3;
                dip4 = report.dipSwitches.dip4;
                dip5 = report.dipSwitches.dip5;
                dip6 = report.dipSwitches.dip6;
                dip7 = report.dipSwitches.dip7;
                dip8 = report.dipSwitches.dip8;

                updateCheckBox();
            };
            ws.onclose = function(){};
        }
    }
}

The custom JS function MakeDataSocket is called when the web page is loaded. It’s used to establish a WebSocket connection between the browser and the NetBurner server. It defines an event listener, or callback function, that is called upon receiving data from the WebSocket. That is the onmessage event listener. We use this event listener to parse the JSON object that is sent by SendSwitchesReport() in section 3. This allows us to separate the state of each DIP switch into its own variable that can then be used to display the states of each switch.

5. Display and toggle a CSS switch with JavaScript

function updateCheckBox(){
    if( dip1 === "Off" ){
        document.getElementById( "cbox1" ).checked = false;
    }
    else if(dip1 === "On"){
        document.getElementById( "cbox1" ).checked = true;
    }
}

Now that each DIP switch state has been parsed into its own variable, we can use that variable to toggle a CSS switch (a stylized HTML checkbox) as a cool animation on the webpage. The onmessage event listener calls updateCheckBox() upon receiving a message, which then updates the CSS switch that is displayed on the webpage.

<p>
  <input id="cbox1" type="checkbox" class="cbox" disabled />
  <label for="cbox1" class="lbl">Switch 1: </label>
</p>

In the HTML code, each CSS toggle switch has an ID that can be used to change the state of the CSS switch. In the above excerpt of the HTML source code, we define a CSS switch with an ID of “cbox1.” The “cbox1” CSS switch is set to true or false by updateCheckBox() – and updateCheckBox() received the state of the MOD-DEV-70 DIP switches by parsing the JSON object in the onmessage event listener.

After all of that is said and done, you get a webpage that displays the real-time state of the MOD-DEV-70 DIP switches with CSS toggle switches. Go ahead and flip your DIP switches on your hardware and watch the webpage CSS switches toggle away!

websockets tutotial with DIP switch indicator

III. Secure WebSockets

Coming to the next release of the NNDK tools, we’ve added support for WebSockets over an SSL/TLS connection. We’ll also update our blog and documentation to get you comfortable with implementing secure WebSockets with your NetBurner. If you would like an early release of the update, contact us through support and we would be happy to share it with you. A Security Suite License is required, of course.

IV. Wrapping it up

You can see how this makes for a very powerful method for real-time monitoring of your NetBurner. By way of extension we hope it is all clear how you would monitor your sensors, controls, and process that might also be integrated with your NetBurner hardware. But if not, we’d love to see your comments and questions. What is particularly advantageous with the WebSockets approach is that you can update the state of your webpage or dashboard without reloading the page – much like AJAX but in some ways much better for real-time and distributed applications since it also reduces the overhead and burden on the server and clients themselves. Stay tuned for our WebSockets Part II article where we'll provide an example of a monitoring AND control application using a NetBurner Dev Kit as well as more information on support for WebSocket over SSL/TLS!

Read 3669 times Last modified on Wednesday, January 10, 2018 4:22 am

Leave a comment

NetBurner Learn

The NetBurner Learn website is a place to learn faster ways to design, code, and build your NetBurner based product. Sign-up for our monthly newsletter!

Latest Articles

We use cookies to help us provide a better user experience.