Musicbox by Jin-Yo Mok (2004)
The music box is connected to a composition program over the internet using a serial-to-Ethernet module. The composition program changes the lights on the music box and the sounds it will play. Real-time communication between the two gives the player feedback. Photo courtesy of Jin-Yo Mok.

5

MAKE: PROJECTS

Communicating in (Near) Real Time

So far, most of the networked communications you’ve seen worked through a web browser. Your device made a request to a remote server, the server ran a program, and then it sent a response. The client made a connection to the web server, exchanging some information, and then the connection was broken. In this chapter, you’ll learn how to keep that connection open. You’ll write two different server programs that allow you to maintain the connection in order to facilitate a faster and more consistent exchange between the server and client.

Supplies for Chapter 5

DISTRIBUTOR KEY

A Arduino Store (store.arduino.cc)

AF Adafruit (adafruit.com)

D Digi-Key (www.digikey.com)

F Farnell (www.farnell.com)

J Jameco (jameco.com)

RS RS (www.rs-online.com)

SF SparkFun (www.sparkfun.com)

SS Seeed Studio (www.seeedstudio.com)

PROJECT 8 & 9: Video Controller

  • 1 Arduino MKR1000 AF 3156, RS 124-0657, A ABX00004, GBX00011 (Africa and EU), D 1659-1005-ND
    Alternatively, an Uno-layout board with the WiFi101 shield or WINC1500-based board. AF 3033 with 2891
  • 1 WiFi connection to the internet If you access the internet at home with WiFi, you should be all set. Make sure you know the network name and password.
  • 1 solderless breadboard D 438-1045-ND, J 20723 or 20601, SF PRT-12615 or PRT-12002, F 4692810, AF 64, SS 319030002 or 319030001
  • 5 220-ohm resistors D 220QBK-ND, J 690700, F 9339299, R 707-7612
  • 1 perforated printed circuit board AF 1609, D V2018-ND, J 616673, F 4903213, RS 159-5420
  • 4 3/4" hex standoffs, 4-40 threads female-female You may need different part numbers to match your enclosure’s particular height, but these will get you started on a search. D 36-2204-ND, RS 123-6835, F 2301244
  • 8 1/4" 4-40 thread pan head machine screws D 36-9300-ND, RS 274-5086, F 2500400
  • 3-5 LEDs D 160-1144-ND or 160-1665-ND, J 34761 or 94511, F 1855510, RS 228-5972 or 826-830, SF COM-09592 or COM-09590
  • 1 rotary encoder with pushbutton AF 377, SF COM-10982 with BOB-11722, SS 311130001
  • 1 pushbutton D GH1344-ND or SW400-ND, J 2231822 or 119011, SF COM-09337, F 1634684, RS 718-2213
  • Male headers D A26509-20-ND, J 103377, SF PRT-00116, F 1593411
  • Female headers D ED7102-ND, F 1122344, SF PRT-00115
  • 1 project enclosure See text.

Interactive Systems and Feedback Loops

In every interactive system, there’s a feedback loop: you take action, the system responds, you see the response—or a notification of it—and you take another action. In some systems, the timing of that loop can be very loose, with substantial delays between action and response. In other applications, the timing must be tight.

For example, in the cat bed application in Chapter 3, there’s no need for the system to respond in less than a few seconds, because your reaction is not very time-sensitive. As long as you get to see the cat while he’s on the bed (which may be true for several minutes or hours), you’re happy. Monski Pong in Chapter 2 relies on a reasonably tight feedback loop in order to be fun. If it took a half-second or longer for the paddles to move when you move Monski’s arms, it would be no fun. The timing of the feedback loop depends on the shortest time that matters to the participant.

Any system that requires coordination between action and reaction needs a tight feedback loop. Consider remote control systems, for example, such as a robot operated over a network. In that case, you’d need not only a fast network for the control system, but also a fast response from the camera or sensors on the robot (or in its environment) that are giving you information about what’s happening. You need to be able to both control it quickly and see the results quickly. Networked action games also need a fast network. It’s no fun if your game console reacts slowly, allowing other players with a faster network connection to get the jump on you. For applications like this, an exchange protocol that’s constantly opening and closing connections (like HTTP does) wouldn’t be very effective.

When there’s a one-to-one connection between two networked objects, it’s easy to establish a tight feedback loop. When multiple objects are involved, though, it gets harder. To begin with, you have to consider how the network of connections between all the objects will be configured. Will it be a star network, with all the participants connected through a central server? Will it be a ring network? Will it be a many-to-many network, where every object has a direct connection to every other object? Each of these configurations has different effects on the feedback loop timing. In a star network, the objects on the edge of the network aren’t very busy, but the central one is. In a ring network, every object shares the load more or less equally, but it can take a long time for a message to reach objects on opposite sides of the ring. In a direct many-to-many network, the load is distributed equally, but each object needs to maintain a lot of connections.

In most cases where you have a limited number of objects in conversation, it’s easiest to manage the exchange using a central server. The most common program example of this is a text-based chat server like the text-based chat in videoconferencing applications like Skype or Google Hangout. Server programs that accept incoming clients and manage text messages between them in real time are often referred to as chat servers. The server programs you’ll write in this chapter are variations on the chat server model. These servers will listen for new connections and exchange messages with all the clients that connect. Because there’s no guarantee how long messages take to pass through the internet, the exchange of messages can’t be instantaneous. As long as you’ve got a fast network connection for both clients and server, though, the feedback loop will be faster than human reaction time.
X

Transmission Control Protocol: Sockets & Sessions

Each time a client connects to a web server, the connection that’s opened uses a protocol called Transmission Control Protocol, or TCP. TCP specifies how objects on the internet open, maintain, and close a connection that will involve multiple exchanges of messages. The connection made between any two objects using TCP is called a socket. A socket is like a pipe joining the two objects. It allows data to flow back and forth between them as long as the connection is maintained. Both sides need to keep the connection open in order for it to work.

For example, think about the exchanges between a web client and server that you saw in the previous two chapters. The pipe is opened when the server acknowledges the client’s contact, and it remains open until the server has finished sending the file. If there are multiple files needed for a web page, such as images and style sheets, then multiple socket connections are opened and closed.

There’s a lot going on behind the scenes of a socket connection. The exchange of data over a TCP connection can range in size anywhere from a few bytes to a few terabytes or more. All that data is sent in discrete packets, and the packets are sent by the best route from one end to the other.

NOTE: “Best” is a deliberately vague term: network hardware calculates the optimal route is differently, which involves a variety of metrics (such as the number of hops between two points, as well as the available bandwidth and reliability of a given path).

The period between the opening of a socket and the successful close of the socket is called a session. During the session, the program that maintains the socket tracks the status of the connection (open or closed) and the port number; counts the number of packets sent and received; notes the order of the packets and sees to it that packets are presented in the right order, even if the later packets arrive first; and accounts for any missing packets by requesting that they be resent. All of that is taken care of for you when you use a TCP/IP stack like the Net library in Processing or the firmware on the WiFi modules that you first saw in Chapter 4.

The complexity of TCP is worthwhile when you’re exchanging critical data. For example, in an email, every byte is a character in the message. If you drop a couple of bytes, you could lose crucial information. The error-checking of TCP does slow things down a little, though, and if you want to send messages to multiple receivers, you have to open a separate socket connection to each one.

There’s a simpler type of transmission protocol that’s also common on the internet: User Datagram Protocol (UDP). Whereas TCP communication is based on sockets and sessions, UDP is based only on the exchange of packets. You’ll learn more about it in Chapter 7.

TCP Sockets vs. WebSockets

When you used telnet to connect to a web server in Chapter 3, you were opening a TCP socket connection to the server. HTTP works over TCP sockets. If you’re familiar with web development at all, you may have heard of a new protocol introduced with HTML5 called webSockets. The webSocket protocol allows HTTP clients and servers to establish longer sessions for sustained exchanges, such as you might see in a game or other application that requires a tight interactive loop.

The second server program you’ll write in this chapter is a webSocket server. The first is a TCP socket server. Understanding the first will make the second easier to comprehend.
X

Project 8

A Video Control Application

Media control is a great way to learn about real-time connections. This project is a networked video application that you can control from a physical controller. The server will be a Processing program, and the client will be a physical device that is using a WiFi-connected microcontroller. The clients and the server’s screen have to be physically close so that the operator can see the screen. In this case, you’re using a network for its flexibility in handling multiple connections, not for its ability to connect remote places.

There are a few well-known controls on any video controller: a play/pause button, fast-forward and rewind buttons, and perhaps a knob to allow you to shuttle frame-by-frame through the video. The controller you make for this video server will be even simpler. It will have a play/pause button, a shuttle knob, and a button to connect or disconnect from the server.

Here’s how the system will work:

  • The video server shows a video.
  • The controller clients connect to the server through a TCP connection.
  • When a player connects, the server replies with a greeting string.
  • The client can send the following commands:
    • start or stop
    • scroll forward or back n frames
    • disconnect
  • The server doesn’t send any messages to the client other than the hello message.

The communications protocol doesn’t define anything about the physical form of the client object. As long as the client can make a TCP connection to the server and can send and receive the appropriate ASCII messages, it can work with the server. You can attach any type of physical inputs to the client, or you can write a client that sends all these messages automatically, with no physical input from the world at all (though that would be boring). Your client doesn’t have to use pushbuttons and a scroll knob if you prefer to use other sensors, as long as you send the messages.

You’ll need to define the specific bytes you want to use for these messages, however, and the format in which you want to send them. Since you’ve learned about key-value pairs, let’s use them, with colons to separate the key and the value. By doing so, it will be easy to use standard programming tools that can parse that format. The messages will look like this:

  • Server greeting: connect: n where n is client’s number on the server
  • Client video play: playing: n where n is 1 or 0 (for true or false)
  • Client scroll: position: n where n is a positive or negative frame count. Positive numbers scroll forward, negative numbers scroll back.
  • Client disconnect: exit:n if n=1, this 1 tells the server to disconnect the client
  • All messages will be terminated with a newline character ( ).

A Test Chat Server

You need a server to get started. You don’t need code to control the video right now; you just want to confirm that the clients can connect and send messages. Following is a server written in Processing with all the elements to handle network communications. It will let you listen for new clients, and then send them messages by typing in the applet window that appears when you run the program.
X

Try It

Start with the includes and variable declarations and definitions. Include the net library, which gives you the ability to make socket connections, The main variables are an instance of the Server class, a port number on which to serve, and an ArrayList to keep track of the clients. You can make the new Server and ArrayList instances here when you initialize the variables, or you can do it in the setup() function.

/*
  Test Server Program
 Context: Processing
 
 Creates a server that listens for clients and prints
 what they say.  It also sends the last client anything that's
 typed on the keyboard.
 */
  
// include the net library:
import processing.net.*;
  
int port = 8080;                         // the port the server listens on
Server myServer = new Server(this, port); // the server object
ArrayList clients = new ArrayList();       // list of clients 

The setup() method sets the window size.

void setup() {
  size(640, 360);                    // set the window size
}

The draw() method listens for new messages from clients and calls a function called readMessage() to deal with them.

void draw() {
  // listen for clients:
  Client currentClient = myServer.available();
  
  // if a client sends a message, read it:
  if (currentClient != null ) {
    readMessage(currentClient);
  }
}

The readMessage() function handles incoming messages. It skips empty messages (strings with just a newline, for example) and prints the message and the sender’s IP address. If a client sends any message containing the substring “exit,” the server disconnects it and removes it from the list of clients.

void readMessage(Client thisClient) {
  // read available text from client as a String and print it:
  String message = thisClient.readStringUntil('
');
  // if there's no message, skip the rest of this function:
  if (message == null) return;
  // print the message and who it's from
  println(thisClient.ip() + ": " + message);  
  
  if (message.contains("exit")) {    // if it's a disconnect message, 
    myServer.disconnect(thisClient); // disconnect client  
    clients.remove(thisClient);      // delete client from the clientList
  }
}

The serverEvent() message is generated by the server when a new client connects to it. serverEvent() announces new clients and adds them to the client list. It sends the new client the greeting message telling it its place in the clients ArrayList.

// ServerEvent occurs when a new client connects to the server:
void serverEvent(Server myServer, Client thisClient) {
  println("New client: " + thisClient.ip()); // print client's IP
  clients.add(thisClient);                   // add it to the clientList
  thisClient.write("client:" + clients.size() + "
");     // say hello
}

Finally, the keyReleased() method sends any keystrokes typed on the server to all connected clients. This is just for testing.

void keyReleased() {
    myServer.write(key);
}

Run the server and open a telnet connection to it. Remember, it’s listening on port 8080, so if your computer’s IP address is, say, 192.168.1.45, you’d connect like so: telnet 192.168.1.45 8080. If you’re telnetting in from the same machine, you can use: telnet localhost 8080 or telnet 127.0.0.1 8080. (Windows 10 users: telnet may ask for password permission to connect). Whatever you type in the telnet window will show up in Processing’s console pane when you press Enter, and whatever you type in the server’s applet window will show up at the client’s command line. Try sending the messages as described previously: playing, position, and exit. Sending exit:1 will cause the server to close the connection to the client.

Once you understand the server, it’s time to make a microcontroller-based client to connect to it.
X

The Controller Client

The video controller client listens to local input and remote input. The local input is from you, the user. The remote input is from the server. The client is constantly listening to you, but it only listens to the server when it’s connected.

To listen to you, the client needs:

  • An input for instructing it to connect. The same input can be used to send a disconnect message.
  • An input for sending a scroll message.
  • An input for sending a play/pause message.

To let the user know what the client device is doing, add:

  • An output to indicate whether the client is connected to the server.
  • An output to indicate whether the video should be playing or not
  • Outputs to indicate when the buttons or scroll wheel are active

It’s always a good idea to put outputs on the client to give local feedback when it receives input from the user. This is what the last outputs are for. Even if there is no connection to the server, local feedback lets the user know that the client device is responding to her actions. For example, pressing the connect button doesn’t guarantee a connection, so it’s important to separate the output that acknowledges a button push from the one that indicates successful connection. If there’s a problem, this helps the user determine whether the problem is with the connection to the server, or with the client device itself. The circuit for this project includes LEDs triggered directly by the sensors, so no code is needed to activate them.

In the code for this client, you’ll control two other LEDs, which are used to indicate whether the client is connected or disconnected, and whether the remote video should be playing or stopped. If this client had a more complex interaction, you’d need more status indicators.
X

The Circuit

The circuit for the client uses an MKR1000 as its microcontroller. It uses several of the GPIO pins for the pushbuttons, the scroll wheel, and the indicator LEDs. The ESP8266 boards don’t have enough GPIO pins to build this project, so if you want to use one of them, you’ll need to use a second microcontroller connected serially to it in order to control the physical inputs and outputs. You’d also need to break the code into the network code and the physical I/O code as well.

MATERIALS

  • 1 Arduino-compatible, WiFi-enabled microcontroller:
    • MKR1000, or Uno-layout board with WiFi101 shield or WINC1500-based board
    • Features used: WiFi, Digital I/O, UART
  • 1 WiFi connection to the internet
  • 1 solderless breadboard
  • 5 220-ohm resistors
  • 1 perforated printed circuit board
  • 3–5 LEDs (see text)
  • 1 pushbutton
  • 1 rotary encoder with pushbutton
  • 1 project enclosure (see text)

This client uses a rotary encoder as the scroll wheel. A rotary encoder, also called a quadrature encoder, is a sensor that has two output pins and a knob that can turn a full 360°. When the knob turns, the output pins pulse. Their pulsing is out of phase with each other so that by watching the sequence of pulses, you can tell which direction the encoder is turning. The sequence of pulses produced is called gray code. A program to read an encoder must read and interpret gray code. Different encoders have different resolutions. The ones recommended here both produce 24 pulses per revolution, meaning that you’d see 24 changes of the I/O pins every time you turn the encoder one full revolution.

Rotary encoders often incorporate a pushbutton in the shaft, as the ones recommended here do. The pushbutton is handy, as it allows you to combine the play/pause button and the scroll wheel into one physical control. If you’re using the SparkFun encoder, it also has an RGB LED in it, so you could use the red, green, and blue channels of the LED as three of your indicator LEDs if you wish. I used the Adafruit encoder in the illustrations shown here.

This project uses an illuminated pushbutton as well, for the connect button. It’s a convenient way to show the connection state to the server right on the control that affects it. If you’re not using an illuminated pushbutton, you’ll need a separate LED to replace the one inside it.

The pushbuttons are wired differently from a normal switch. First, they’re wired to ground instead of to voltage. When you push one of the buttons, the input pin to which it’s connected will read as LOW. This is called inverted logic, and allows you to read the buttons without a separate pulldown resistor. In the program, you’ll initialize them using pinMode(pinNumber, INPUT_PULLUP). Most microcontrollers have internal pullup resistors on their GPIO pins so that you can wire switches this way.

The pushbuttons also have LEDs connected to them from voltage. This way, when you push the button, the LED lights up, because its cathode connects to ground through the button. There’s no need to add any code to make this happen—the pushbutton will ground the LED circuit automatically, and you’ll get local feedback on when the button is being pressed. The encoder pins also have LEDs wired similarly.

Figures 5-2 and 5-3 show the circuit on a solderless breadboard as usual. Figures 5-4 and 5–5 show the device laid out on a printed circuit prototyping board (known as a perfboard). The perfboard makes it possible for the circuit to be a little smaller and more physically robust. It’s harder to assemble than a solderless breadboard circuit, though. so you should build the circuit on a breadboard first. Both layouts are shown so you can choose the method you like.

You’ll need an enclosure for the circuit. You can buy a project case from your favorite electronics retailer, but you can also get creative. For example, a pencil box from your friendly neighborhood stationery store will work well. Drill holes in the lid for the controls and the LEDs, add a battery inside, and you’re all set. If you’re skilled with a mat knife, you can also make your own box from cardboard or mat board. Figures 5-6 and 5–7 show the box I made, and Figure 5-8 shows the template from which I made it. If you’re lucky enough to have access to a laser cutter, use it; if not, a mat knife and a steady hand can do the job.
X

Figure 5-2
The video controller client, breadboard layout. Although it’s shown on a full-size breadboard for clarity, it will fit on a half-size breadboard.
Figure 5-3
The video controller client schematic. The following page shows the layout for the circuit on a printed circuit prototyping board (a perfboard).
Figure 5-4
The circuit as laid out on a printed circuit prototyping board (perfboard), with all components on board. All the components except the resistors are all attached to the board via socket headers so they can be removed easily.
Figure 5-5
The circuit board layout showing where the solder joints (in gray) connect wires, resistors, and sockets underneath the board.
Figure 5-6
The assembled video controller. The circuit inside the housing was designed to be just tall enough so that the LEDs and encoder would stick through the top. Assemble the circuit first so you know how much space you need for the housing.
Figure 5-7
Detail of the assembled circuit board. Use female headers to mount the LEDs, the encoder, and the pushbutton. Additional extension headers make the job of getting the components to the right height much easier. You can trim the LED legs bit by bit until they are the right height, then stick them in the headers. The hex spacers lift the circuit board from the box, leaving room for a Lithium-poly battery underneath, which plugs conveniently into the MKR1000. Make sure to use at least a 1000mA battery.
Figure 5-8
Template for the housing. This template can be cut out of poster board or mat board and then folded to make a housing for the controller. The dimensions will depend on how you assemble your circuit, so modify them as needed. Although they look precise here, they were measured with a caliper after the fact.

The screw holes in the bottom and the front are drilled for 4-40 size machine bolts, to hold the box on its standoffs.

The first two prototypes were drawn by hand and cut out of paper, then scrap cardboard. I didn’t move to a final version cut out of mat board until I had tested the fit of the sides with a paper version. Score the folds on the outside side of the board so they fold better.

The Code

This sketch uses two new libraries, Encoder, by Paul Stoffregen and Button, by Michael Adams. You can find and install both using the Library Manager as you did for previous libraries. The Encoder library simplifies the reading of a rotary encoder, It reads the changes in the I/O pins of the encoder and returns the number of steps and direction that the encoder has moved. Similarly, the Button library simplifies the reading of a pushbutton attached to an I/O pin. It reads the state of its I/O pin and keeps track of whether it’s toggled, pressed, or released.

Plan It

The video controller client code reads the sensors, and listens for data from the server. If it’s connected to the server, it sends changes from the sensors to the server.

In the code that follows, the sensor reading and sending is broken out into separate functions.

/*
Video client
context: Arduino
*/
  
// include necessary libraries
// initialize global variables
  
void setup() {
  // connect to WiFi
  // initialize inputs and outputs
  // initialize serial communications
}
  
void draw() {
  // read scroll wheel
  // read buttons
  
  // if connected to server,
  //    listen for incoming data from server
  //    if scroll wheel has changed, send change to server
  //    if connect button changed, toggle connect state
  //    if play button changed, toggle playing state
  
  // update status output LEDs
}

Try It

First, make a new file called config.h and store the WiFi access point SSID and password in it as you did for the Arduino programs in Chapter 4.

// config.h
char ssid[] = "ssid";     //  your network SSID (name)
char password[] = "s3c3r3+!"; // your network password

At the beginning of your code, import the libraries you need.

// include required libraries and config files
#include <SPI.h>
#include <WiFi101.h>
#include <Encoder.h>
#include <Button.h>
#include "config.h"     

Set up global variables to hold the server address (your personal computer’s address, where the server runs) and make an instance of the WiFiClient library. This is how you’ll create a TCP socket connection to the server, and read from and write to the server.

const char serverAddress[] = "192.168.0.12"; // server address
int port = 8080;                              // port number
WiFiClient tcpSocket;                         // server socket
  

Then make instances of the Encoder and Button libraries to read the encoder and buttons. Set up constants for the LED pin numbers to make them easier to remember as well. Finally, you need a Boolean variable to track the playing state, and a long integer to hold the last read encoder position.

Encoder myEncoder(0, 1);         // instance of the encoder library
Button playButton(2);            // instances of the button library
Button connectButton(3);
const int playLED = 4;           // pin numbers for the LEDs
const int connectLED = 5;
  
boolean playing = false;         // what the video state should be
long lastPosition  = 0;          // last read position of the encoder

The setup() function sets the states of the I/O pins and starts the button instances. Notice that the pinMode for the encoder pins is INPUT_PULLUP. This tells the microcontroller that you want to use the internal pullup resistors on these pins.

Once the pins are initialized, you’ll connect to your WiFi network, just like you did in the sketches in Chapter 4.

void setup() {
  Serial.begin(9600);             // initialize serial communication
  pinMode(0, INPUT_PULLUP);       // initialize encoder pins
  pinMode(1, INPUT_PULLUP);
  pinMode(connectLED, OUTPUT);    // initialize LED pins
  pinMode(playLED, OUTPUT);
  connectButton.begin();           // initialize buttons
  playButton.begin();
  
  // while you're not connected to a WiFi AP,
  while ( WiFi.status() != WL_CONNECTED) {
    Serial.print("Attempting to connect to Network named: ");
    Serial.println(ssid);           // print the network name (SSID)
    WiFi.begin(ssid, password);     // try to connect
    delay(2000);
  }
  
  // When you're connected, print out the device's network status:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
}

Begin the loop() function by calling two new functions, readEncoder() and readButtons(), which you’ll write shortly. These will do what their names imply. Then check to see if there’s a socket connection to the server, and if so, check if there’s any data available to be read from the server. If there is, read it and print it out to the Serial Monitor.

Finally, update the status LEDs. The Connect LED depends on whether there’s a socket connection, so you can use the value returned by the tcpSocket.connected() function, which returns 1 or 0 for true or false. Likewise, you can turn on or off the Playing LED using the playing variable, which you’ll set to true or false as well.

void loop() {
  // read the sensors:
  readEncoder();
  readButtons();
  
  // check for incoming data from the server:
  if (tcpSocket.connected()) {    // if connected to the server,
    if (tcpSocket.available()) {  // if there is a response from the server,
      String result = tcpSocket.readString();  // read it
      Serial.print(result);       // and print it (for diagnostics only)
    }
  }
  // update the status LEDs:
  digitalWrite(connectLED, tcpSocket.connected());
  digitalWrite(playLED, playing);
}

Start the readEncoder() function by calling myEncoder.read(). It will return the number of steps the encoder’s taken. Positive values indicate clockwise movement and negative values indicate counterclockwise movement. Compare the position to the last position read, and if there’s a socket connection to the server open, send the difference to the server. Then save the current position for comparison next time you read.

void readEncoder() {
  long position = myEncoder.read();       // read the encoder
  long difference = position - lastPosition;  // compare to last position
  if (difference != 0) {                  // if it's changed,
    if (tcpSocket.connected()) {          // if the socket's connected,
      tcpSocket.print("position:");        // send the key
      tcpSocket.println(difference);       // send the value
    }
    lastPosition = position;              // update lastPosition
  }
}
  

Start the readButtons() function by checking if the Connect button is toggled (changed), and if so, read it to see if it’s pressed. If so, and there’s no socket connection to the server, attempt to connect. If you’re connected, send exit:1 and the server will disconnect.

By determining if .toggled() is true and the button is low, you ensure that you’re only taking action when the button changes state. If you only checked if it was pressed, you’d take action repeatedly as long as it’s held down.

void readButtons() {
  if (connectButton.toggled()) {          // if connect button has changed
    if (connectButton.read() == LOW) {    // and it's pressed
      if (!tcpSocket.connected()) {       // if you're not connected,
        connectToServer();                // connect to server
      } else {                           // if you're already connected,
        tcpSocket.println("exit:1");       // disconnect
      }
    }
  }                                       // end of connectButton.toggled
  

Finish the readButtons() function by checking the Play button. Check both toggled and pressed conditions just like you did with the Connect button. If its state has changed, change the variable playing to its opposite, and if there’s a socket connection to the server, send a message with the current state of this variable.

  if (playButton.toggled()) {             // if the play button has changed
    if (playButton.read() == LOW) {       // and it's LOW
      playing = !playing;                 // toggle playing state
      if (tcpSocket.connected()) {        // if you're connected,
        tcpSocket.print("playing:");       // send the key
        tcpSocket.println(playing);        // send the value
      }
    }
  }                                       // end of playButton.toggled
}                                         // end of readButtons()

Finally, you need the connectToServer() function that’s called in the first half of readButtons(). This calls the .connect() function from the WiFiClient library to attempt a connection to the server. This is a blocking function, meaning that it stops your whole program until a connection is made. You’ll probably notice a delay between when you press the button and when the Serial Monitor reports the result.

void connectToServer() {
  Serial.println("attempting to connect");
  // attempt to connect to the server on the given port:
  if (tcpSocket.connect(serverAddress, port)) {
    Serial.println("connected");
  } else {
    Serial.println("failed to connect");
  }
}

Upload the client code to the microcontroller, then run the Processing chat server that you wrote previously. Open the Serial Monitor so you can see the status messages. Once your controller’s connected to WiFi, press the Connect button, and you should see it connect to the server. Click the Play button or turn the encoder, and you should see the messages come through in Processing’s console pane. If you type in the Processing sketch window and hit the Enter key, the message should show up in the Arduino Serial Monitor as well. Congratulations, you’ve got a working client!

Send a few messages to the server by triggering the encoder and the buttons to get a sense how both the server and the controller client device work. Once you’re comfortable, it’s time to add video to the server application.
X

Adding Video to the Server

The main difference between the video server application and the chat server you’ve already written is that the video server parses the messages from the client and uses them to control the video. The network connection management is the same as the chat server, so you can start with the previous Processing code and add to it to make the server. The changes will be as follows:

  • Global variables: add video library and variables to manage the video
  • Setup function: add code to set the window size and initialize the video
  • Draw function: add code to display a frame of video and text overlay
  • serverEvent function: no changes needed
  • readMessage function: add code to parse messages into key-value pairs and take appropriate actions

New functions:

  • movieEvent function: called by sketch every time a new video frame is ready to read. Use it to read the video.
  • scrub function: jumps to new position in the movie

You’ll also need a video for your sketch to run. The video library for playing movies is the same as the one you used to capture video in Chapter 3. It can run a wide variety of video formats, but the Processing foundation recommends encoding your video using the H.264 format. Keep the size small, 640x480 or thereabouts. If you don’t have a video handy, the video library examples provide a few you can use. Save your video in a subdirectory of your sketch’s directory, called data.
X

Code It

At the beginning of your sketch, import the video library and add a few new global variables for managing the video and client messages. New lines are shown in blue.

/*
  Video server
 context: Processing
 */
  
// include necessary libraries
import processing.net.*;
import processing.video.*;
  
// set global variables:
int port = 8080;                     // the port the server listens on
Server myServer;                     // the server object
ArrayList clients = new ArrayList(); // list of clients
Movie myVideo;                       //  movie player
boolean playing = false;             // state if the video: playing/paused
String lastMessage = "";             // last message received from client

In the setup() function, add a call to the size() function to set the window size. The size parameters should be the width and height of your video, in pixels, and the new movie instantiation should use the name of your movie. You’ll add the scrub() function shortly; it will jump to a given frame of the movie.

void setup() { 
  size(640, 360);
  myVideo = new Movie(this, "movie.mov"); // initialize the video
  myServer = new Server(this, port);      // start the server 
  scrub(0.0);                             // set the video to the start
}

In the draw() function you’ll add a line to display the latest frame of the video in the window. After that, you’ll add text overlay as you did in the cat server in Chapter 3, to display the last client message received, and who it’s from.

void draw() {
  // listen for clients:
  Client currentClient = myServer.available();
  
  // if a client sends a message, read it:
  if (currentClient != null ) {
    readMessage(currentClient);
  }
  // display the latest video frame:
  image(myVideo, 0, 0, width, height);
  
  // display the client that sent the last message:
  fill(15);                          // dark gray fill
  text(lastMessage, 11, height-19);  // drop shadow
  fill(255);                         // white fill
  text(lastMessage, 10, height-20);  // main text
}

The two new events you’ll add are movieEvent() and scrub(). The former is called automatically when there’s a new frame of video ready. It just calls the video library’s .read() command to get the new frame.

The scrub() function jumps to a given position in the movie. You have to play or loop first, then use the .jump() command to move the position. If you’re playing state is paused, then use the pause() command to do so.

void movieEvent(Movie myVideo) {
  myVideo.read();
}
  
void scrub(float newPosition) {
  myVideo.loop();                // play the video
  myVideo.jump(newPosition);       // jump to the calculated position
  if (!playing) myVideo.pause(); // if the movie should be paused, pause it
}

Finally, make the following changes to the readMessage() command, which reads incoming client messages. The new code replaces the conditional statement from the chat server sketch that begins with if (message.contains("exit")).

First, use the trim() command to remove any extraneous tabs, newlines, or carriage return bytes from the end of the string. These characters are called whitespace. They’re added by some systems (for example, the Arduino println() statement adds ), and they can interfere with converting a substring to a different data format.

Next use the split() command to split the string at the colon into two substrings. The first substring is the message’s property name, and the second is the property value.

Once you have the property name and value, use three conditional statements (if statements) to take the appropriate action. The position message converts the position value and moves the video. One video frame is approximately 0.033 second. The playing property starts or pauses video. The exit property causes the server to disconnect the client.

At the end of the function, save the client message to a global variable so that the draw() function can use it to display it onscreen.

void readMessage(Client thisClient) {
  // read available text from client as a String and print it:
  String message = thisClient.readStringUntil('
');
  // if there's no message, skip the rest of this function:
  if (message == null) return;
  // print the message and who it's from
  println(thisClient.ip() + "	" + message); 
  
  message = message.trim();                   // trim whitespace 
  String[] decodedMsg = split(message, ":");   // split message at the colon
  String property = decodedMsg[0];            // first part is the key
  int value = int(decodedMsg[1]);              // second part is the value
  
  //    if it's a play/pause message, change the video state:  
  if (property.equals("playing")) {  
    playing = boolean(value);      // convert value to a boolean
    if (playing) {                 // if it's true,
      myVideo.loop();              // play the video
    } else {                       // if not,
      myVideo.pause();             // pause it
    }
  }
  
  // if it's a position message, change the video time:
  if (property.equals("position")) {
    float frames = value * 0.033;
    float videoTime = myVideo.time() + frames;
    scrub(videoTime);      // set the video to the appropriate position
  }
  
  // if it's a disconnect message, disconnect the client:
  if (property.equals("exit") && value == 1) {  
    myServer.disconnect(thisClient); // disconnect client
    clients.remove(thisClient);      // delete client from the clientList
  }
  // save the last client message received for printing on the screen:
  lastMessage = thisClient.ip() + ": " + message;
}

From the client’s point of view, this server will respond exactly the same as the chat server. Run the sketch and try connecting to it using your controller client, or from a telnet connection and you’ll see, However, now it will display and control video in reaction to the client’s messages. Figure 5-9 shows what the server displays, using video shot from a moving train.

You can log into the server with as many clients as you wish. The server will respond to the last client’s message. One side effect of this is that the server’s playing state can get out of sync with the clients’ states. To correct this, you could add code to broadcast the server’s playing state whenever it changes. You’d also need to add client code to receive and act on these messages. See if you can work out how to do this.
X

Figure 5-9
Screenshot from the Processing video server. You can see the client IP address and last message in the lower-left corner.

Project 9

A WebSocket Video Controller

In the server sketch, you probably noticed some lag between when you triggered your sensors and when the video moved in Processing. That’s because video playback is computationally intensive. It would be better if you could separate the server, which manages the network transactions, from the display, which controls the video, so that the server doesn’t have to do the most computation. In this project, you’ll do that by writing a web server in node.js to replace Processing as the server, and a web page to replace it as the display client. Your controller client device will control video on the display client, with the server managing the exchange. You’ll maintain a persistent connection between clients and server using webSockets.

You might think, “Why not make the server an HTTP server like we did in Chapter 4? You can put video in a web page, right?” You can, but controlling the video in the client remotely would be tricky using normal HTTP requests While HTTP is convenient because of its ubiquity, it’s not so effective for real-time or near-real-time applications like this video server, because you’re opening and closing a socket for each exchange between the server and client.

The webSocket protocol addresses the need for realtime client-server interaction in HTTP. It’s an extension to HTTP that allows clients to upgrade their connection so that the socket remains open and the server and client can exchange messages continually. Instead of separate TCP sessions for every transaction, the client can now establish one session with the server. This protocol was designed to support applications requiring a tighter interactive loop between clients and server.

Figure 5-10 shows the system diagram for the new version of this project. The server can support multiple clients, both controller and display clients. With a more complex protocol, each controller client could even target a specific display client.

WebSockets are more complex than normal TCP sockets, however. First a client has to make an HTTP request, and in the header of the request, it must include the following headers:

Upgrade: websocket
Connection: Upgrade

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

The Upgrade and Connection headers tell the server that the client wants to establish a websocket, the Sec-WebSocket-Key is a unique string to handshake with the server, and the Sec-WebSocket-version says what version of the webSocket protocol the client is using. The server will respond like so:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

The Sec-WebSocket-Accept is a version of the client’s key encrypted with a string that’s standard to the webSocket protocol. This allows the client to verify the server.

Once they’re connected, the client and server can exchange bytes continually. However, the server expects the client to encode its messages using a formula specific to the webSocket protocol. There are webSocket clients for most platforms that will take care of the protocol management for you. WebSockets are built into HTML5 and client-side JavaScript now, so it’s easy to use them in an HTML page. There are a few webSocket client and server libraries for node.js as well. In this project, you’ll use one called ws. The Arduino-based library you’ll use is the webSocket class from the ArduinoHttpClient library you used in the previous chapter.
X

Figure 5-10
System diagram for a websocket video server. Server supplies video HTML page and video assets to browsers via HTTP, then connects both controller and display clients’ webSockets via HTTP Upgrade. This server can support multiple clients of both types.

The Server and Browser Client

WebSocket servers are built on top of HTTP servers, so your server for this project will start with a node.js express-based HTTP server like the one you saw in Chapter 4. The browser client will be an HTML page that displays and controls the video using the JavaScript library called p5.js.

Try It

Start with the basics for a server using the express.js library. Save this file in its own project directory as wsExpressServer.js, then add the express.js library using npm as you did before like so:

$ npm install express

You can see this server serves static files from a subdirectory called public like the one in Chapter 4 did. Shortly, you’ll make a new subdirectory in the project directory called public.

The webSocket library will need access to an instance of node.js’s http library, so this program makes that instance from the Express server, then uses it to listen for clients. This server will run now, but it won’t do much so far. You’ll need a web page to serve. To make it, you’re going to use p5.js.

/*
Express-based webSocket Server
context: node.js
*/
  
// Include libraries:
var express = require('express');           // the express library
var http = require("http");                 // the http library
  
var server = express();                     // the express server
var httpServer = http.createServer(server); // the http server
  
// serve static files from /public:
server.use('/',express.static('public'));
  
// start the server:
httpServer.listen(8080);                    // listen for http connections

Adding Interactive Elements with p5.js

In Chapter 1 you got a brief introduction to p5.js, a JavaScript framework for making interactive web pages in a Processing-like style. This will be your first project with it. Make a new subdirectory in your project directory called public, and copy the files from the p5.js empty-example project into it. The files in it should be as follows:

public/

index.html

sketch.js

libraries/

p5.js

p5.dom.js

p5.sound.js

If you installed the command-line tool p5-manager, you can install the whole p5.js project on the command line like so:

$ p5 g -b public

Then change directories into the public directory and get the latest versions of the p5.js libraries like so:

$ p5 update

You’ll see this pattern frequently in the chapters that follow: a node.js project that’s a server, with a public subdirectory that’s a p5.js project. Together, node.js and p5.js are a handy combination.

Try It

When you open the index.html file from the p5.js empty project template, it will look similar to this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script type="text/javascript" src="p5.js"></script>
    <script type="text/javascript" src="libraries/p5.dom.js">
    <script type="text/javascript" src="sketch.js">
    <title></title>
  </head>

The JavaScript code you’ll write will all be in the sketch.js file. Open it and replace the default code with the code to the right.

Copy your video file into the public directory as well, so the server can serve it.

Now you can run your server program and see some results. On the command line, run the server like so:

$ node wsExpressServer.js

Then open a browser and go to http://localhost:8080. You should see your video playing. Change the video name and video size to match the width and height of your particular movie.

/*
  Video client
  context: p5.js
*/ 
var myVideo;                          // movie player
  
function setup() {
  myVideo = createVideo("movie.mov"); // initialize the video
  myVideo.size(640, 360);             // set its size
  myVideo.position(10, 30);            // position it
  myVideo.loop();                     // loop the video
}

Adding WebSocket Functionality

Now that you’ve got the HTTP server and the HTML client started, you need to give both the capability to make webSocket connections. So far, you’ve worked on the center and the right side of the diagram in Figure 5-10. You’ve got steps 1 and 2 of that side working, in that you can serve the index.html page and the movie. Now you’ll add the third step on the right side. It’s best to get the webSocket functionality working in software alone before you add the microcontroller to the system.

The communications protocol for this server is the same as the one in the previous project. The messages will look like this:

  • Server greeting: connect: n where n is client’s number on the server
  • Client video play: playing: n where n is 1 or 0 (for true or false)
  • Client scroll: position: n where n is a positive or negative frame count. Positive numbers scroll forward, negative numbers scroll back.
  • Client disconnect: exit:n if n=1, this 1 tells the server to disconnect the client
  • All messages will be terminated with a newline character ( ).

This time, since the server and the browser client are written in JavaScript, you’ll send the messages in JSON, because it’s easy for JavaScript to parse it that way. That means that you need to surround your messages with curly braces, make sure the key names are in quotation marks, and separate the key-value pairs with commas as needed. Then you can use JSON.parse() and JSON.stringify() to convert them from strings to data objects and back again.

Add It

At the beginning of your server script, you need to include the ws library and make an instance of it. New lines are shown in blue as usual. You need to install the ws library using npm as well, like so:

$ npm install ws

It may seem strange to have three server objects in your code. The first variable, server, is the instance of express.js that handles the server routing and static files. The second, httpServer, is the part of server that handles the HTTP protocol. The third, wss, is the part that handles the webSocket protocol.

/*
Express-based webSocket Server
context: node.js
*/ 
var express = require('express');           // the express library
var http = require("http");                 // the http library
var WebSocketServer = require('ws').Server; // ws library's Server class
  
var server = express();                                // express server
var httpServer = http.createServer(server);             // http server
var wss = new WebSocketServer({ server: httpServer }); // websocket server

At the end of your code, you already start the HTTP server. Add an event listener to the webSocket server to listen for incoming webSocket connections.

// start the servers:
httpServer.listen(8080);                // listen for http connections
wss.on('connection', connectClient);    // listen for webSocket messages

In the body of your script, after you configure the Express server to serve static files from the public directory, you’ll add a new function to handle incoming webSocket connections. connectClient() is the callback function for the event listener you just added on the previous page. When a new connection is made, the event listener will pass the new client to this function.

A webSocket client can generate a few possible events: send a message, close the socket, or trigger an error. You’ll set up three event listeners for those events inside the connectClient() function, because they can only occur if a client connects. You’ll put their callback functions, readMessage(), readError(), and disconnect(), here as well.

At the end of connectClient(), you’ll define what happens when the client first connects: define a greeting message and send it. You’re using .size, not .length, because wss.clients is a data type called a Set. Sets in JavaScript similar to arrays, but each element has to be unique. The ws library stores clients in a Set called wss.clients.

// serve static files from /public:
server.use('/',express.static('public'));
  
function connectClient(newClient) {
  // when a webSocket message comes in from this client:
  function readMessage(data) {
    // you'll see more on this function shortly
  }
  
  // if there's a webSocket error:
  function readError(error){
    console.log(error);
  }
  
  // when a client disconnects from a webSocket:
  function disconnect() {
    console.log('Client ' + newClient.clientName + ' disconnected');
  }
  
  // set up event listeners:
  newClient.on('message', readMessage);
  newClient.on('error', readError);
  newClient.on('close', disconnect);
  
  // when a new client connects, send the greeting message:
  var greeting = {"client": wss.clients.size};
  newClient.send(JSON.stringify(greeting));
}

The readMessage() function is the most complex of the three callback functions in connectClient(), because it has to read incoming messages and decide what to do with them. Replace the earlier stub of code with this function.

When the incoming message has a property called playing or position, you need to broadcast it to the other clients so they can adjust the video. You’ll write the broadcast() function shortly. If the incoming message has an exit property and the value is 1, then close the webSocket to this client.

  // when a webSocket message comes in from this client:
  function readMessage(data) {
    var result = JSON.parse(data);              // parse the JSON
    if (result.hasOwnProperty('clientName')) {  // if there's a clientName,
      newClient.clientName = result.clientName; // name the client
    }
    if (result.hasOwnProperty('playing') ||      // if there's a playing prop
       result.hasOwnProperty('position')) {      // or a position property
      broadcast(newClient, result);             // broadcast it    
    }
    if (result.exit === 1) {                    // if it's an exit message,
      console.log("client " + result.clientName + " logging out");
      newClient.close();                        // close the webSocket
    }
    console.log(result);                        // print the message itself
  }

The broadcast() function goes after connectClient(). It runs through the list of connected webSocket clients and checks to see if they are the client that sent the message. If not, it sends them the message. You’re using the .forEach() function to call the same function, sendToAll(), for each element of the wss.clients Set.

// broadcast data to connected webSocket clients:
function broadcast(thisClient, data) {
  function sendToAll(client) {
    if (client !== thisClient) {
      console.log('broadcasting from:' + client.clientName);
      client.send(JSON.stringify(data));
    }
  }
 // run the sendToAll function on each element of wss.clients:
  wss.clients.forEach(sendToAll);
}

The webSocket server is now different than your Processing server in two ways. First, the webSocket server keeps its own list in a property called clients, unlike Processing, where you had to maintain your own ArrayList of clients. Second, you’re not really doing anything with the incoming messages; you’re just checking that they have useful information and sending them out to the other clients. The web client code for handling incoming messages will look more like the Processing sketch’s readMessage() function, because they both manage the video. In this case, each client controls its own copy of the video. By contrast, the Processing server controlled the video, not the clients.

You’ve added all the code you need for the server to handle webSockets. Make sure you installed the ws library and run this script from the command line. You won’t see any difference when you reload the web page yet, though, since the script in that page doesn’t yet have code to make a websocket connection. Next you’ll add some code to the sketch.js script to make that happen.

Add It

At the beginning of your client-side script, you’ll need a few new global variables to keep track of the socket connection, the last received message, the host’s address, and the outgoing message. In the message variable, set a client property so the server knows what type of client this is. Although the server’s not checking this, it can be useful for diagnostics.

/*
  Video client
  context: p5.js
*/
  
var myVideo;                          // movie player
var socket;                           // the websocket
var lastMessage;                      // last server message received
var host = document.location.host;    // address of the server 
var message = {"client": "browser"};  // what you'll send to the server

In the setup() function, add some code to create a new text division in the HTML using the createDiv() function. You’ll use this to show messages from the server. Then create a new webSocket connection back to the server. Set up two listener functions, one to listen for a successful opening of the socket, and the other to read messages.

function setup() {
  myVideo = createVideo("movie.mov"); // initialize the video
  myVideo.size(640, 360);             // set its size
  myVideo.position(10, 30);           // position it
  myVideo.loop();                     // loop the video
  lastMessage = createDiv('');        // create a div for text messages
  lastMessage.position(10, 10);       // position the div
  socket = new WebSocket('ws://' + host); // connect to server
  socket.onopen = sendIntro;          // socket connection listener
  socket.onmessage = readMessage;     // socket message listener
}

As mentioned above, you won’t have a draw() function, because everything in this script is driven by events generated by the webSocket. The sendIntro() function just sends a greeting to the server when the socket’s open.

The readMessage() function is the heart of the script. It reads incoming messages and converts them to JSON objects. Then it looks for the appropriate properties, playing or position. If those properties are present, the client uses them to control the video, just like the readMessage() function in the Processing server did. At the end of the readMessage() function, it converts the latest message into a string and places it in the text division.

Save these changes to sketch.js, make sure the server is still running, and reload the page in the browser. You should see the video load, and get a message from the server sending the greeting as you see in Figure 5-11. In the server console, you should see the client login and send the message: { client: 'browser' }. Now it’s time to write the controller client.
X

function sendIntro() {
  // convert the message object to a string and send it:
  socket.send(JSON.stringify(message));
}
  
function readMessage(event) {
   // read  text from server:
  var msg = event.data;           // read data from the onmessage event
  var videoTime = myVideo.time(); // get the current video time
  var message = JSON.parse(msg);  // convert incoming message to JSON
  
  // if it's a play/pause message, change the video state:
  if (message.playing) {
    myVideo.loop();
  } else {
    myVideo.pause();
  }
  
  // if it's a position message, change the video time:
  var value = parseFloat(message.position);
  if (!isNaN(value)) {           // if it's a numeric value,
    var frames = value * 0.033;  // 1 frame in seconds at ~30fps
    videoTime += frames;        // add the change to the current time
    myVideo.time(videoTime);     // set the video position
  }
  
  // save the last client message received for printing on the screen:
  lastMessage.html(JSON.stringify(message));
}
Figure 5-11
The video browser client

The WebSocket Controller Client

The video controller client for this project is physically identical to the controller client for the last project. You can use the same circuit; only the firmware on the microcontroller needs to change. The only difference between this microcontroller client and the last one is that this one needs to make webSocket connections instead of TCP socket connections. To do that, you’ll use the ArduinoHttpLibrary that you’ve already been using. That library features a webSocket class that makes it easy to make the connection.

Try It

Start by making a copy of the finished Arduino program from the previous project. You’ll need to add an extra line at the top of the program to include the ArduinoHttpLibrary. You’ll also need to add a line at the top to make a webSocket instance. New lines are shown in blue.

There are no changes needed to the rest of the global variable section or setup() function, so you can leave those as is.

/*
  Video Controller WebSocket Client
  Context: Arduino, with WINC1500 module
*/
#include <SPI.h>
#include <WiFi101.h>
#include <ArduinoHttpClient.h>
#include <Encoder.h>
#include <Button.h>
#include "config.h"     
  
const char serverAddress[] = "192.168.0.12";  // server address
int port = 8080;                              // port number
WiFiClient tcpSocket;                         // server socket
  
// make a websocket instance
WebSocketClient webSocket = WebSocketClient(tcpSocket, serverAddress, port);

The loop() function is very similar. You’re just swapping out the webSocket for the TCP socket; the rest is the same. The webSocket class has a function called parseMessage() that parses the message and returns how many bytes are in the incoming message. Assuming there are bytes available, you can use the usual Stream methods like read() or readString() to get the message.

You’ll also need to change the connectLED update, as it relies on whether the webSocket’s connected, not the TCP socket.

void loop() {
  readEncoder();
  readButtons();
  
  // while websocket is connected, listen for incoming messages:
  if (webSocket.connected()) {
    int msgLength = webSocket.parseMessage();  // get message length
    if (msgLength > 0) {                       // if it's > 0,
      String message = webSocket.readString(); // read it
      Serial.println(message);                 // print it
    }
  }
  // update the status LEDs:
  digitalWrite(connectLED, webSocket.connected());
  digitalWrite(playLED, playing);
}

The readEncoder() function has only one change: instead of checking that the TCP socket’s connected, you’re now checking that the webSocket’s connected. If it is, you’ll call a new function, sendJsonMessage(), to format and send the message out the webSocket, which you’ll see shortly.

void readEncoder() {
  long position = myEncoder.read();            // read the encoder
  long difference = position - lastPosition;   // compare to last position
  if (difference != 0) {                       // if it's changed,
    if (webSocket.connected()) {               // and webSocket's connected,
      sendJsonMessage("position", difference);  // send a JSON message
    }
    lastPosition = position;                   // update lastPosition
  }
}

The readButtons() function’s changes are similar to those for the readEncoder() function. When the connectButton changes, you’ll replace the TCP socket check with the webSocket check, and call connectToServer() if you’re not connected. If you are connected, you’ll send the exit message so the server can disconnect you.

When the play button changes, check the webSocket connection and send the playing state if you have a connection.

void readButtons() {
  if (connectButton.toggled()) {         // if the connect button has changed
    if (connectButton.read() == LOW) {   // and it's LOW
      if (!webSocket.connected()) {       // if you're not connected,
        connectToServer();               // connect to server
      } else {                          // if you're already connected,
        sendJsonMessage("exit", 1);       // disconnect
      }
    }
  }                                     // end of connectButton.toggled
  
  if (playButton.toggled()) {             // if the play button has changed
    if (playButton.read() == LOW) {        // and it's LOW
      playing = !playing;                  // toggle playing state
      if (webSocket.connected()) {         // if you're connected, 
        sendJsonMessage("playing", playing); // send a message
      }
    }
  }                                     // end of playButton.toggled
}                                       // end of readButtons()

The connectToServer() function attempts to connect just like the previous one. However, webSocket.begin() returns an error 1 if it fails to connect, so the logic is reversed from the previous code. This version also sends the client name by way of greeting to the server, using the sendJsonMessage() function that you’ll see next.

void connectToServer() {
  Serial.println("attempting to connect");
  boolean error = webSocket.begin();   // attempt to connect
  if (error) {
    Serial.println("failed to connect");
  } else {
    Serial.println("connected");
    sendJsonMessage("", 0);  // send the client name and nothing else
  }
}

The only new function, called sendJsonMessage(), forms a JSON-encoded message to send to the server. The webSocket class needs to form a message packet before sending, using webSocket.beginMessage(). This function lets you specify the type of message, using the webSocket protocol types (these include text, binary, ping, and others). Then you can print() or println() to the packet. Finally, the webSocket.endMessage() function sends the whole message that you’ve assembled.

// This function forms a JSON message to send,
// from a key-value pair:
void sendJsonMessage(String key, int val) {
  webSocket.beginMessage(TYPE_TEXT);   // message type: text
  webSocket.print("{"clientName":"MKR1000"");
  if (key != "") {          // if there is no key, just send name
    webSocket.print(","");  // comma, opening quotation mark
    webSocket.print(key);   // key
    webSocket.print("":");  // closing quotation mark, colon
    webSocket.print(val);   // value
  }
  webSocket.print("}");
  webSocket.endMessage();
}

Upload this sketch to your board and open the Serial Monitor. Make sure your server’s still running, then press the connect button. On the server side, you should see a message like this: { clientName: 'MKR1000' }. The connect LED should light and in the Serial Monitor, you should see a greeting message like this: {"client":1}. If you’ve got a browser client logged in, turn the encoder or press the play button and you should be able to control the video now.

With this version of the video server, you can have both multiple controller clients and multiple display clients, and the server won’t see a perceptible change in performance.

This version doesn’t allow you to target specific controller clients to specific display clients, but now that you have the idea, see if you can modify the system to make that happen.
X

Figure 5-12
One controller can control many browser clients simultaneously.

Conclusion

The basic structure of the clients and servers in this chapter can be used any time you want to make a system that manages synchronous connections between several devices on the network. The servers’ main jobs are to listen for new clients, keep track of the existing clients, and make sure that the right messages reach the right clients. They must place a priority on listening at all times.

The clients should also place a priority on listening, but they have to balance listening to the server with listening to the physical inputs. They should always give clear and immediate responses to local input, and should indicate the state of the network connection at all times.

When you’re planning the communications between client and server, keep it simple, and leave the system open for more commands, because you never know when you might decide to add a feature. Make sure to build in responses where appropriate, like the “client” and “exit” messages you saw here. Keep the messages unambiguous and, if possible, keep them short as well. Consider using formats that are native to your programming tools, like JSON was used here.

You’ve seen two different approaches to client-server systems with tight interactive loops here. The first used plain TCP sockets connection between the server and its clients, and the second used webSockets. The plain TCP socket is a simpler approach if both your client and your server can make plain TCP socket connections. The webSocket approach, on the other hand, works well with existing HTTP applications and lets you take advantage of the web browser as client.

Now you’ve seen examples of client-server exchanges with loose interactive loops (the HTTP system in Chapter 4) and tight interactive loops, as you saw in this chapter. With those two tools, you can build almost any application in which there’s a central server and a number of clients. For the next chapter, you’ll step away from the internet and take a look at various forms of wireless communication.
X

Jin-Yo Mok’s original sketches of the music box.
The music box composition interface.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.81.206