Networking and Communications

This week I was prompted to establish communication between one of my boards and another machine using either WIFI of Bluetooth.

As Kris found out, the most beginner-friendly yet allowing further development way would be to use the Seed Xiao ESP32C3 development board equipped with WIFI and Bluetooth capabilities and operate it through my laptop's Terminal using Fablab's WIFI network and WIFI REST API.This method I will be able to use later for interface design for my project.

According to ChatGPT: The WiFi_REST_Lib is a library for the Arduino platform that helps you create a web service that can be accessed over Wi-Fi using HTTP requests. It allows you to create endpoints that can be used to retrieve, create, update, or delete resources on the server. To use it, you need to include the library in your Arduino project, connect to a Wi-Fi network, and define the endpoints for your web service. You can define an endpoint by specifying the HTTP method (such as GET, POST, PUT, or DELETE) and the URL path. Then, you can write the code to handle the request and send a response back to the client. Finally, you start the server and handle incoming client requests. The WiFi_REST_Lib makes it easy to create a simple web service without needing to worry about the low-level details of handling HTTP requests and responses.

Seed Xiao ESP32C3 and Arduino software

Configuring the Arduino software to work with the board takes additional steps

  1. Adding ESP32 board package to Arduino IDE
    In the File > Preferences, "Additional Boards Manager URLs" field paste the url below: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
    Solvespace UI

  2. Installing the esp32 library. In the Tools > Board > Boards Manager..., type "esp32" in the search box, select the latest version of esp32, and install it.
    Solvespace UI

WiFi_REST_Lib

As Kris found out, there is a library available that makes use of the REST API with Seed Xiao ESP32C3 boards easier - WiFi_REST_Lib.

It requires three additional libraries to be installed via the Arduino software Library Manager

  1. AsyncTCP
  2. ESPAsyncTCP
  3. ESPAsyncWebSrv

Solvespace UI

ESPAsyncWebSrv library bug fix

The library contains a bug that may be fixed the following way:
In the ESPAsyncWebSrv folder created after the library installation, there should be AsyncWebSocket.cpp file.
In the line 832 the text return IPAddress(0U); should be changed to return IPAddress(static_cast(0U)); . The change shoudl be saved in the file.

Blink example breakdown

Whole blink example code

 #include 
#include 
#include 
#include 

// Some variables we will need along the way
const char* ssid     = "Fablab";
const char* password = "Fabricationlab1"; 
const char* PARAM_MESSAGE = "message"; 
int webServerPort = 80;
int led = D10;

// Setting up our webserver
AsyncWebServer server(webServerPort);

// This function will be called when human will try to access undefined endpoint
void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void setup() {
  pinMode(led, OUTPUT);

  Serial.begin(115200);
  delay(10);

  // We start by connecting to a WiFi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");

  // We want to know the IP address so we can send commands from our computer to the device
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Greet human when it tries to access the root / endpoint.
  // This is a good place to send some documentation about other calls available if you wish.
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", "Hello, world");
  });

  // Usage IP_ADDRESS/led?state=1 where /led is our endpoint and ?state=on is a variable definition.
  // You can also chain variables like this: /led?sate=off&color=blue
  server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){
    bool state; // LED state 
    if (request->hasParam("state")) {
      // The incoming params are Strings
      String param = request->getParam("state")->value();
      // .. so we have to interpret or cast them
      state = ((param == "on") ? HIGH : LOW); // Look up Terary Operator (e.g. https://www.programiz.com/cpp-programming/ternary-operator)
    } else {
      state = LOW;
    }

    // Send back message to human
    String response = "Turning LED ";
    response += state ? "on" : "off"; 
    request->send(200, "text/plain", response);

    // Operate LED
    digitalWrite(led, state);
  });

  // If human tries endpoint no exist, exec this function
  server.onNotFound(notFound);

  Serial.print("Starting web server on port ");
  Serial.println(webServerPort);
  server.begin();
} 

void loop() {
  // Nothing needed here at the moment
}

The ternary operator

It is a shorthand way of writing an if-else statement in a single line of code.

The syntax for the ternary operator is as follows:
condition ? value_if_true : value_if_false;

Breakdown

Including all the necessary libraries that I have installed in the previous steps

#include 
#include 
#include 
#include  
Basic user-defined setup, defining the variables

// Some variables we will need along the way
const char* ssid     = "Fablab";   // wifi network name
const char* password = "PASSWORD GOES HERE";   //wifi network password
const char* PARAM_MESSAGE = "message"; 
int webServerPort = 80;
int led = D10;   // defining the LED pin

// Setting up our webserver
AsyncWebServer server(webServerPort);  
User-friendly debugging feature

// This function will be called when human will try to access undefined endpoint
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");  //show 404 error
}
Setup void
           
 void setup() {
  pinMode(led, OUTPUT); // setting up the led pin as an output pin

  Serial.begin(115200);  // starting the Serial communication
  delay(10);   // adding delay to be able to read what Serial shows
          
Serial communication for the user and connecting to WiFi
  
// We start by connecting to a WiFi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
While statement, while there is Wifi connection, print "WiFi connected"

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");

  // We want to know the IP address so we can send commands from our computer to the device
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
Sending out the first message via the server
   
 // Greet human when it tries to access the root / endpoint.
  // This is a good place to send some documentation about other calls available if you wish.
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", "Hello, world");  // display "Hello, world" message as plain text
  });

Adding the LED endpoint
  
  server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){  //creating the endpoint with name "led" available at   curl 193.167.5.143/led 
    bool state; // LED state  // defining the state variable of type boolean
    if (request->hasParam("state")) {  //defining the "state" variable as an endpoint parameter
      // The incoming params are Strings
      String param = request->getParam("state")->value();  // creating the String variable "param", assigning it what is stored in the "state" variable
      // .. so we have to interpret or cast them
      state = ((param == "on") ? HIGH : LOW); // Look up Terary Operator (e.g. https://www.programiz.com/cpp-programming/ternary-operator)
    } else {
      state = LOW;
    }
   
// Send back message to human
String response = "Turning LED ";
response += state ? "on" : "off"; 
request->send(200, "text/plain", response);

// Operate LED
digitalWrite(led, state);
});
 
// If human tries endpoint no exist, exec this function
server.onNotFound(notFound);
 
Serial.print("Starting web server on port ");
Serial.println(webServerPort);
server.begin();
} 

void loop() {
// Nothing needed here at the moment
}

Connecting to and operating a custom board

The idea was to connect to my previously made pcb, which included the Seed Xiao RP2040 development board, a tactile switch, potentiometer and an RGB LED. For the sake of the exercise, I aimed to have the tactile switch turn on the LED (and turn it off when pressed again) and have the user define the hue of the LED by giving a number.

The Seed Xiao RP2040 development board does not have networking features, so I swapped it with the Seed Xiao ESP32C3 board that had Bluetooth and Wifi enabled. The pinout of the board is exactly the same and the board was soldered on pins and headers, otherwise it wouldn't be possible.

I first declared the necessary pins and variables
 
int led = D10;

int LED_R_PIN = D0;
int LED_G_PIN = D1;
int LED_B_PIN = D2;
int POT_PIN = D3;
int BUTTON_PIN = D9;

int potValue = 0;
bool ledOn = false;   // the initial state of the led was to be off

int red_value = 0;
int green_value = 0;
int blue_value = 0;
int hue = 0;
Setting up the pins to be wither input or output pins
         
pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_G_PIN, OUTPUT);
pinMode(LED_B_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
pinMode(led, OUTPUT);
 
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Welcome to Saskias ESP32C3!");   // my very own welcome message to be send as a response to the initial request
});
                     
Setting up the "button" endpoint that display the current status of the button (pressed or not pressed)
 
  server.on("/button", HTTP_GET, [](AsyncWebServerRequest *request){  //setting up the button endpoint (available at IP address/button)
    // is the button pressed or not?
    String response = "Currently the button is ";  //respond with the "Currently the button is " string as a reponse to the button request
    response += !digitalRead(BUTTON_PIN) ? "not pressed" : "pressed";   
/*
digitalRead(BUTTON_PIN) reads the state of a button connected to a pin specified by BUTTON_PIN.    request->send(200, "text/plain", response);
!digitalRead(BUTTON_PIN) evaluates to true if the button is not pressed (i.e., the pin reads LOW), and false if the button is pressed (i.e., the pin reads HIGH).
The ternary operator ? : is used to choose between two possible values based on the result of the previous expression. If the button is not pressed, the value "not pressed" is selected, otherwise the value "pressed" is selected.
The resulting value is appended (added in the end) to the String variable "response" using the += operator.
*/
  });
                     
Solvespace UI

Setting up the "led" endpoint
 
 server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){  //add the "led" endppoint (available at IP address/led)
    if (request->hasParam("state")) {  //if the led enpoint has a parameter named "state"...
      String stateString = request->getParam("state")->value();  //extract the value stored in it and store it and store as stateString string
      ledOn = (stateString == "on" ? true : false);  // condition ? value_if_true : value_if_false; 
    }
                     
If the "state" parameter of the "led" enpoint is set to "on", execute the setColor function and assign it the (hue) value. Note, the LED pins have to be set to analog mode to be able to adjust the brightness and thus color of the RGB LED. Also here - 0 means full brightness and 255 turning it entirely off.

    if (ledOn) {
      setColor(hue);
    } else { 
      analogWrite(LED_R_PIN, 255);  // assign the RED_R_PIN the value of 255 
      analogWrite(LED_G_PIN, 255);
      analogWrite(LED_B_PIN, 255);
    }
 
                     
Send out the response with the current status of the LED
 
    String response = "Currently the LED is ";
    response += (ledOn ? "on" : "off");   //condition ? value_if_true : value_if_false; 
    request->send(200, "text/plain", response);
  });
                     
Solvespace UI

Setting up the "hue" endpoint
 
    server.on("/hue", HTTP_GET, [](AsyncWebServerRequest *request){ // add the "hue" endpoint (available at IP address/hue)
    if (request->hasParam("value")) {  // check if the endpoint has a parameter "value"...
      String valueString = request->getParam("value")->value();   // if yes, create a string of it
      int value = valueString.toInt();  // declare an int "value", convert the valueString to an integer and assign it to the int "value"
      int hue = map(value, 0, 1023, 0, 255);   //map the value int from 0-1023 to 0-255 to use as the hue value
      setColor(hue);  //execute the  setColor function with value (hue)
    }
                     
Define the response to the "hue" endpoint
 
    String response = "Currently the RGB value is ";
    response +=  String(red_value) + ", ";   // append the "Currently the RGB value is " with the red, green and blue values stored as strings and separated with comas (", ")
    response +=  String(green_value) + ", ";
    response +=  String(blue_value);
    request->send(200, "text/plain", response);
  });
                     
Setting up the setColor function. _hue is a global variable, while hue is a local one!
 
                                            void setColor(int _hue) {
  hue = _hue;  //assign the local "hue" variable the value of the global "hue" variable (_hue)
  // Calculate the RGB values based on the hue
  red_value = cos((hue + 120) * 0.01745) * 127 + 128;
  green_value = cos(hue * 0.01745) * 127 + 128;
  blue_value = cos((hue - 120) * 0.01745) * 127 + 128;
 
Solvespace UI

Solvespace UI

Writing the calculated red green and blue values to the LED pins (0 - maximum brightness, 255 - off)
 
  // Set the LED colors
  analogWrite(LED_R_PIN, red_value);
  analogWrite(LED_G_PIN, green_value);
  analogWrite(LED_B_PIN, blue_value);
}
 

Whole code with hue, led and button endpoints

#include 
#include 
#include 
#include 

// Some variables we will need along the way
const char* ssid     = "Fablab";
const char* password = "Fabricationlab1"; 
const char* PARAM_MESSAGE = "message"; 
int webServerPort = 80;
int led = D10;

int LED_R_PIN = D0;
int LED_G_PIN = D1;
int LED_B_PIN = D2;
int POT_PIN = D3;
int BUTTON_PIN = D9;

int potValue = 0;
bool ledOn = false;

int red_value = 0;
int green_value = 0;
int blue_value = 0;
int hue = 0;

// Setting up our webserver
AsyncWebServer server(webServerPort);

// This function will be called when human will try to access undefined endpoint
void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void setup() {
  //pin setup
  pinMode(LED_R_PIN, OUTPUT);
  pinMode(LED_G_PIN, OUTPUT);
  pinMode(LED_B_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT);
  pinMode(led, OUTPUT);

  Serial.begin(115200);
  delay(10);

  // We start by connecting to a WiFi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");

  // We want to know the IP address so we can send commands from our computer to the device
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Greet human when it tries to access the root / endpoint.
  // This is a good place to send some documentation about other calls available if you wish.
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", "Welcome to Saskias ESP32C3!");
  });

  server.on("/button", HTTP_GET, [](AsyncWebServerRequest *request){
    // is the button pressed or not?
    String response = "Currently the button is ";
    response += !digitalRead(BUTTON_PIN) ? "not pressed" : "pressed";
    request->send(200, "text/plain", response);
  });

  server.on("/potentiometer", HTTP_GET, [](AsyncWebServerRequest *request){
    int potValue; // potentiometer value 
    if (request->hasParam("potValue")) {
      // The incoming params are Strings
      String param = request->getParam("potValue")->value();
      // .. so we have to interpret or cast them
      potValue = param.toInt(); // Look up Terary Operator (e.g. https://www.programiz.com/cpp-programming/ternary-operator)
      hue = map(potValue, 0, 1023, 0, 255);
      setColor(hue);
    }
  
    // Send back message to human
    String response = "Potentiometer value is  ";
    response += String(potValue); 
    request->send(200, "text/plain", response);
  });

  server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){
    if (request->hasParam("state")) {
      String stateString = request->getParam("state")->value();
      ledOn = (stateString == "on" ? true : false);
    }

    if (ledOn) {
      setColor(hue);
    } else {
      analogWrite(LED_R_PIN, 255);
      analogWrite(LED_G_PIN, 255);
      analogWrite(LED_B_PIN, 255);
    }

    String response = "Currently the LED is ";
    response += (ledOn ? "on" : "off");
    request->send(200, "text/plain", response);
  });

  server.on("/hue", HTTP_GET, [](AsyncWebServerRequest *request){
    if (request->hasParam("value")) {
      String valueString = request->getParam("value")->value();
      int value = valueString.toInt();
      int hue = map(value, 0, 1023, 0, 255);
      setColor(hue);
    }

    String response = "Currently the RGB value is ";
    response +=  String(red_value) + ", ";
    response +=  String(green_value) + ", ";
    response +=  String(blue_value);
    request->send(200, "text/plain", response);
  });

  server.onNotFound(notFound);

  Serial.print("Starting web server on port ");
  Serial.println(webServerPort);
  server.begin();
} 

void loop(){
  //
}

void setColor(int _hue) {
  hue = _hue;
  // Calculate the RGB values based on the hue
  red_value = cos((hue + 120) * 0.01745) * 127 + 128;
  green_value = cos(hue * 0.01745) * 127 + 128;
  blue_value = cos((hue - 120) * 0.01745) * 127 + 128;

  // Set the LED colors
  analogWrite(LED_R_PIN, red_value);
  analogWrite(LED_G_PIN, green_value);
  analogWrite(LED_B_PIN, blue_value);
}

Group Assignment page