WebSocket on Industruino

example of D21G as WebSocket client

Tom

WebSockets are an easy way to exchange data between a client and server, full-duplex, over a single TCP connection. This example uses the ArduinoWebSockets library, which needs only a few modifications to work with the Industruino's D21G platform.

We will run a websocket server on a laptop (e.g. the node.js/simple.js example included in the above library), and our Industruino will connect over Ethernet to this server.

  • start the websocket server
  • start the Industruino websocket client
  • the Industruino connects to the server and send a hello message
  • the server sends a hello message to the Industruino client
  • whenever an Industruino button is pressed, it sends 'ENTER', 'UP', 'DOWN' to the server
  • when the server disconnects (timeout of 1min), the Industruino displays 'disconnected'
  • the Industruino client connects again when the 'ENTER' button is pressed

Above is the console output of the simple.js websocket server on a Linux Ubuntu laptop.

The library configuration files need 2 small modifications:

  • config.h needs to refer to the W5500 chip because that is the one used in the Industruino Ethernet module
  • platform.h needs to explicitely state that we have an SAMD architecture; Industruino does not define ARDUINO_SAMD_ZERO so we have to include after line 18:
#define PLATFORM_ARCH PLATFORM_ARCHITECTURE_SAMD21
The Serial Monitor has a lot of debugging information in case of trouble.
The code for the client is below; you will need to edit the server IP address as applicable. The code retrieves a unique MAC address from the RTC EEPROM included in the D21G topboard, you can of course set the MAC address manually to any other constant.
/*
   WEBSOCKET DEMO for Industruino
   using library https://github.com/skaarj1989/ArduinoWebSockets
   based on simple-client.ino example

   configure config.h line 24 for W5500 (Ethernet chip used in Industruino Ethernet module)
   edit platform.h to include a line after line 18:
   #define PLATFORM_ARCH PLATFORM_ARCHITECTURE_SAMD21    // include for INDUSTRUINO
   (because ARDUINO_SAMD_ZERO is not defined for Industruino)

   you need a websocket server, e.g. the one included in the library node.js/simple.js (tested on Linux Ubuntu)
   working on Industruino D21G + Ethernet
   > the sketch connects to the server
   > displays a message from the server
   > sends a string message to the server
   > whenever a button is pressed on the Industruino, it sends 'ENTER', 'UP', 'DOWN' to the server
   > displays 'disconnected' when the server closes the connection
   > re-connects again to the server when the Enter button is pressed
   
*/

#include <WebSocketClient.h>                    // includes SPI and Ethernet2
WebSocketClient client;

#include <Wire.h>                               // for RTC EEPROM MAC
byte mac[6];                                    // read from RTC EEPROM
const char *server = "192.168.1.121";
const int port = 3000;

#include <UC1701.h>
static UC1701 lcd;

unsigned long pressed_timestamp;
unsigned long pressed_interval = 200;
boolean ws_connected = false;

/////////////////////////////////////////////////////////////////////////

void onOpen(WebSocket &ws) {
  SerialUSB.println("Connected");
  lcd.setCursor(0, 6);
  lcd.print("connected       ");
  ws_connected = true;

  char message[] = "Hello from your Industruino websocket client!";
  ws.send(TEXT, message, strlen(message));
}

void onClose(WebSocket &ws, const eWebSocketCloseEvent code, const char *reason, uint16_t length) {
  SerialUSB.println("Disconnected");
  lcd.setCursor(0, 6);
  lcd.print("disconnected       ");
  ws_connected = false;

}

void onMessage(WebSocket &ws, const eWebSocketDataType dataType, const char *message, uint16_t length) {
  switch (dataType) {
    case TEXT:
      SerialUSB.print("Received: "); SerialUSB.println(message);
      lcd.setCursor(0, 6);
      lcd.print(message);
      lcd.print("                ");
      break;
    case BINARY:
      SerialUSB.println("Received binary data");
      break;
  }
}

void onError(const eWebSocketError code) {
  SerialUSB.print("Error: "); SerialUSB.println(code);
  lcd.setCursor(0, 6);
  lcd.print("error: ");
  lcd.print(code);
  lcd.print("            ");
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

void setup() {

  SerialUSB.begin(115200);
  SerialUSB.println("Starting sketch: WebSocket DEMO");

  Wire.begin();           // for MAC from RTC EEPROM

  pinMode(26, OUTPUT);
  digitalWrite(26, HIGH);  // LCD full backlight

  lcd.begin();
  lcd.setCursor(0, 0);
  lcd.print("WebSocket DEMO");

  SerialUSB.println("Get unique MAC from RTC EEPROM");
  readMACfromRTC();             // MAC stored in RTC EEPROM

  if (Ethernet.begin(mac) == 0) {
    SerialUSB.println("failed to start Ethernet with DCHP, stop here");
    while (true) ;
  }

  SerialUSB.print("Client IP: ");
  SerialUSB.println(Ethernet.localIP());

  lcd.setCursor(0, 2);
  lcd.print("IP: ");
  lcd.print(Ethernet.localIP());
  lcd.setCursor(0, 3);
  lcd.print("server:");
  lcd.setCursor(0, 4);
  lcd.print(server);
  lcd.print(":");
  lcd.print(port);

  SerialUSB.print("Trying to connect to server: ");
  SerialUSB.print(server);
  SerialUSB.print(":");
  SerialUSB.println(port);

  client.setOnOpenCallback(onOpen);
  client.setOnCloseCallback(onClose);
  client.setOnMessageCallback(onMessage);

  if (!client.open(server, port)) {
    SerialUSB.println("Connection failed, stop here");
    lcd.setCursor(0, 6);
    lcd.print("connection failed");
    while (true) ;
  }
  SerialUSB.println("Connected");
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

void loop() {
  client.listen();

  if (SerialUSB.available()) {
    char msg[1];
    msg[0] = SerialUSB.read();
    client.send(TEXT, msg, 1);
  }

  if (!digitalRead(24) && millis() - pressed_timestamp > pressed_interval) {
    char msg[] = "ENTER";
    client.send(TEXT, msg, strlen(msg));
    pressed_timestamp = millis();

    if (!ws_connected) {
      if (!client.open(server, port)) {
        SerialUSB.println("Connection failed, stop here");
        lcd.setCursor(0, 6);
        lcd.print("connection failed");
        while (true) ;
      }
    }
  }

  if (!digitalRead(23) && millis() - pressed_timestamp > pressed_interval) {
    char msg[] = "DOWN";
    client.send(TEXT, msg, strlen(msg));
    pressed_timestamp = millis();
  }

  if (!digitalRead(25) && millis() - pressed_timestamp > pressed_interval) {
    char msg[] = "UP";
    client.send(TEXT, msg, strlen(msg));
    pressed_timestamp = millis();
  }
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////
// the RTC has a MAC address stored in EEPROM - 8 bytes 0xf0 to 0xf7
void readMACfromRTC() {
  SerialUSB.println("READING MAC from RTC EEPROM");
  int mac_index = 0;
  for (int i = 0; i < 8; i++) {   // read 8 bytes of 64-bit MAC address, 3 bytes valid OUI, 5 bytes unique EI
    byte m = readByte(0x57, 0xf0 + i);
    SerialUSB.print(m, HEX);
    if (i < 7) SerialUSB.print(":");
    if (i != 3 && i != 4) {       // for 6-bytes MAC, skip first 2 bytes of EI
      mac[mac_index] = m;
      mac_index++;
    }
  }
  SerialUSB.println();
  SerialUSB.print("Extracted 6-byte MAC address: ");
  for (int u = 0; u < 6; u++) {
    SerialUSB.print(mac[u], HEX);
    if (u < 5) SerialUSB.print(":");
  }
  SerialUSB.println();
}

/////////////////////////////////////////////////////////////////////////
// the RTC has a MAC address stored in EEPROM
uint8_t readByte(uint8_t i2cAddr, uint8_t dataAddr) {
  Wire.beginTransmission(i2cAddr);
  Wire.write(dataAddr);
  Wire.endTransmission(false); // don't send stop
  Wire.requestFrom(i2cAddr, 1);
  return Wire.read();
}