Week 14 Assignments - Interface and Application Programming
Two-Way Browser / Board Control of XIAO Onboard LED Using Web Socket Connection
Group Assignment
The group assignment for this week was to:
- Compare as many tool options as possible.
- Document your work on the group work page and reflect on your individual page what you learned.
Outcomes
Link to Group Site
The group assignment page for this week is on the 2025 Charlotte Super Fab Lab group site for Week 14 - Interface and Application Programming.
What Was Learned
In the group assignment, we considered a variety of platforms and languages to support interface and application programming. This included languages such as HTML, JavaScript, and C, interfaces such as Web Sockets, and platforms such MIT App Inventor, Bluefruit Connect, Adafruit IO, Blynk, and Losant.
Discussion on the different options provided us a sense of the tradeoffs on capability and complexity in selecting tools to support interface and application programming.
Individual Assignment
The individual assignment for this week was to:
- Write an application for the embedded board that you made. that interfaces a user with an input and/or output device(s)
Outcomes
I tried a web services approach for basic interface and application development. The microcontroller board would act as a web server to provide web content / services connected to board functionality. The connection would be over WiFi and support web browser interaction.
WiFi Connectivity - Scanning
As a foundation for the setup, I tested the WiFi connectivity for the XIAO ESP32C3 microcontroller development board I created in Week 8.
The first step was to check basic WiFi capability for the XIAO ESP32C3 board by scanning for available WiFi networks. I used the Arduino IDE environment. The scanning setup followed the Seeed Studio WiFi example documentation on how to Scan WiFi networks.
The scanning code worked well, and the serial monitor showed that the XIAO ESP32C3 was able to identify nearby WiFi networks.
Setup done
scan start
scan done
5 networks found
1: lakehouse89 (-69)*
2: FiOS-MGSU8 (-86)*
3: mesh (-88)*
4: mesh (-90)*
5: wanda (-90)*
...
scan start
scan done
6 networks found
1: lakehouse89 (-69)*
2: FiOS-MGSU8 (-86)*
3: wanda (-87)*
4: mesh (-89)*
5: Thomas Ring (-89)*
6: Rutiger 2.4 (-90)*
WiFi Connectivity - Connecting
The second step was to check whether the XIAO ESP32C3 board could connect to our local WiFi network. The connection setup followed the Seeed Studio WiFi example documentation on how to Connect to a WiFi network.
The WiFi connecting code worked well, and the serial monitor showed that the XIAO ESP32C3 was able to connect with our local WiFi network.
Web Server
With the WiFi connection in place, I tried to create a web server that would run on the XIAO board and provide board-specific web services. This involved setting up a web server to run on the XIAO board.
I followed the tutorial on creating a ESP32 Web Server – Arduino IDE. The tutorial used a setup with 2 LEDs. I adjusted the steps of the tutorial for the single LED setup with my development board.
The web server application uses structured URLs as end points for LED on/off actions. When a request is made to a web server, the URL for that request has a number of parts. The first part is the protocol, typically http://
or https://
, which indicates the kind of message content expected. The second part is the domain address of the server (at a particular domain name / IP address), such as 192.168.1.121
. The third part describes the path to the resource that is being requested from the server, such as /26/on
. In the application below, a URL for turning on the LED is structured as:
http://192.168.1.121/26/on
And a URL for turning off the LED is structured as:
http://192.168.1.121/26/off
For hypertext transfer protocol requests (http or https), a method is also specified as part of the communication, such as GET
or POST
, that can be used to indicate different kinds of actions.
When the server receives the URL request, it checks what resource path is being requested. If it sees a request for /26/on
using the GET
method, it can direct to the code for turning the LED on. If it sees a request for /26/off
using the GET
method, it can direct to the part of the code for turning the LED off. In the complete application code (later), the part that does this is:
// turns the GPIOs on and off
if (header.indexOf("GET /26/on") >= 0) {
Serial.println("GPIO 26 on");
output26State = "on";
digitalWrite(output26, HIGH);
} else if (header.indexOf("GET /26/off") >= 0) {
Serial.println("GPIO 26 off");
output26State = "off";
digitalWrite(output26, LOW);
}
If the server is set up to respond to a URL request, then a web page can use standard HTML links as a way to communicate with the server - and to send commands. An HTML anchor link has 2 basic components:
- The text to be displayed in the web page
- The URL address of the destination - the page / content to go to when the link is activated / clicked
And HTML anchor link template is structured as
<a href=""> </a>
- The
<a> </a>
structure indicates that it is an HTML link href=""
is where the destination URL goes - between the quotes- The space between the start
<a href="">
and end</a>
is where the link text for display goes
- The
So, an HTML page can send a structured message to the server using the previously described URL in an HTML link, such as:
<a href="http://192.168.1.121/26/on">Turn LED On</a>
When clicked, the link will make a http://192.168.1.121/26/on
request to the server, which will trigger the LED on action in the code.
In the application below, the server provides an initial web page on first request. That web page contains an HTML link that can be used in this way to send commands to the server for LED actions. Part of the code that does this is:
client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");
There are extra control characters needed as part of the code, but this creates an HTML link as part of the web page:
<a href="/26/on"><button class="button">ON</button></a>
- this uses some shorthand - because the web page came from the same web server being used, it presumes the same server address, so does not include the
http://192.168.1.121
part - this uses a fancier link display - instead of just text, the space between the start
<a href="">
and end</a>
displays an HTML button
- this uses some shorthand - because the web page came from the same web server being used, it presumes the same server address, so does not include the
- When the displayed button is clicked, the link is activated, the control message is sent to the server, and the LED action is taken
The full code for the Web Server application follows.
ESP32 Web Server | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
|
When the code is uploaded and run, the microcontroller connects to WiFi and starts the web server. The IP address assigned to the development board is printed on the serial monitor. This is the URL address that can be used in a web browser on the same local network, in order to connect to the development board web services.
Entering the IP address into a web browser on the same local network results in the web page being loaded from the web server on the development board.
Web page served from XIAO
Button interaction on the web page makes a request to the server to turn the LED on / off and receives a corresponding web page update as a response.
Browser Control of XIAO Onboard LED Using Web Server
The web server shows details on the HTTP request using the serial monitor.
New Client.
GET /26/on HTTP/1.1
Host: 192.168.1.121
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Referer: http://192.168.1.121/26/off
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Accept-Encoding: gzip, deflate
Connection: keep-alive
GPIO 26 on
Client disconnected.
...
New Client.
GET /26/off HTTP/1.1
Host: 192.168.1.121
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Referer: http://192.168.1.121/26/on
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Accept-Encoding: gzip, deflate
Connection: keep-alive
GPIO 26 off
Client disconnected.
Web Socket
In order to enable two-way communication between a web interface and the board running a web server, web sockets can be used. When a client connects with the web server, a web socket connection is established using JavaScript and the web socket protocol. Event-based processing is used to trigger communication and response.
I followed a tutorial on ESP32 WebSocket Server: Control Outputs (Arduino IDE). I modified the code to include a debounced button control for the onboard development board button. The button acts as a physical control for the LED alongside the web service based control.
ESP32 WebSocket Server | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
|
The web socket setup enables two-way communication. So, the web page button can turn the LED on and off, with status update on the web page. But also, the physical button on the development board can control the LED and push the status update to the web page connected via the web socket.
The web socket application uses several core libraries for network connectivity and web services.
#include <WiFi.h>
- provides WiFi connectivity functions#include <AsyncTCP.h>
- enables multiple network connections needed for simultaneous web socket connections#include <ESPAsyncWebServer.h>
- provides core customizable WebSocket server
Several global parameters are defined for use in the application, including WiFi credentials, debouncing delay, LED status, and LED pin detail.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
const uint8_t DEBOUNCE_DELAY = 5; // in milliseconds
bool ledState = 0;
const int ledPin = D6;
The WebSocket library is used to create a web server (on port 80) and associated web socket service (on /ws
path), which can then be customized for the application. This defines the server
and ws
variables used in other places in the code.
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
The overall web socket application has several main parts. The application centers on knowing the state of the LED - whether it is on or off. This is handled by the boolean LED state variable ledState
, which is initialized to 0 (false) at the start.
The core of the application is the standard Arduino loop()
. Here, the loop is reasonably short and does the following:
ws.cleanupClients();
- library function to close stale web socket connectionsdigitalWrite(ledPin, ledState);
- standard Arduino pin function - set the LED pin on/off to correspond with the value of theledState
variable- the
ledState
variable may be changed either by the physical button or by web socket interaction - either way the LED will follow suit
- the
button.read();
- uses a helper function to read whether the physical button is pressedif (button.pressed())
- checks if the button was read as being pressed, if soledState = !ledState;
- toggle the LED state - LED will respond in the next loop iterationnotifyClients();
- sends a message to all connected web pages that a change has happened, so the web pages can update accordingly
The rest of the application is essentially setup and helper functions. The standard Arduino setup()
is used for the server application. The first part is typical Arduinio initialization, setting up the serial monitor, button pin as input, LED pin as output, and initializing the LED pin (LOW).
Serial.begin(115200);
pinMode(button.pin, INPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
The rest of the setup()
is for connecting the board to WiFi and starting the web socket server. For connecting to WiFi:
WiFi.begin(ssid, password);
- uses WiFi library to make a WiFi connection using defined credentials for network name and passwordwhile (WiFi.status() != WL_CONNECTED) {
- wait until a WiFi connection has been establisheddelay(1000);
- give some time before checking WiFi connection againSerial.println("Connecting to WiFi..");
- print WiFi status message on serial monitor
Serial.println(WiFi.localIP());
- once WiFi is connected, print IP address as status message on serial monitor
Once WiFi is connected, setup()
can initialize and start the web server and web socket service.
- initWebSocket();
- helper function to initialize the web socket service with customizations
- server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
- tells the server how to respond to an inital browser request for the home page
* request->send(200, "text/html", index_html, processor);
- provides the web page content
- server.begin();
- starts the web server
The web socket service is customized to provide straightforward logging data and to respond to a specific kind of LED control message. Customized responses are defined in the onEvent
function. There are 3 basic customizations:
- On Connect (WS_EVT_CONNECT)- when a web page connects to the web socket service, the connection is logged with the serial monitor
- On Disconnect (WS_EVT_DISCONNECT) - when a web page disconnects from the web socket service, the disconnection is logged with the serial monitor
- On Message (WS_EVT_DATA) - when a web socket message is sent from a connected web page, the service processes the message with the
handleWebSocketMessage
function
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
If the web socket service receives a message, it is handled with the handleWebSocketMessage
function. This checks the messaage and responds as follows:
- If the message is the string "toggle"
- toggle the value of the
ledState
variable - this will get picked up in the next iteration of theloop()
function and turn the LED on/off - notify all web paged connected to the web socket service with a message that there has been an update, so the pages can respond accordingly
- toggle the value of the
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
}
}
When an initial browser request is made to the web server, it provides the web page content for the application. The web page content is a large literal string representing an HTML document. It is defined in the application between the rawliteral
constructs.
const char index_html[] PROGMEM = R"rawliteral(
- ... long string of HTML web page content ...
)rawliteral";
The HTML page content includes CSS styling and site structure to show a basic web page with a panel for information about the onboard LED state. It also includes a button for toggling the LED state. The page design can be seen in the video. In terms of application functionality, the web page includes embedded JavaScript functions inside the <script> </script>
HTML element that are used for web socket communication between the web page and the web server / web socket service.
The initWebSocket()
function is used to establish a connection with the web server's web socket service and set up handler functions for different events.
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
}
The onOpen(event)
function listens for when a web socket connection to the service is successfully made and logs that to the developer console in the web browser.
The onClose(event)
function listens for when a web socket connection to the service is closed. It logs that to the to the developer console in the web browser and then tries to reopen a new connection.
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
The onMessage(event)
listens for when a message is received from the server application through the web socket. If the message content is a "1", it changes the web page content to show the LED state as ON. If the message content is anything else, it changes the web page content to show the LED state as OFF. This keeps the displayed content of the web page synchronized with the application server.
function onMessage(event) {
var state;
if (event.data == "1"){
state = "ON";
}
else{
state = "OFF";
}
document.getElementById('state').innerHTML = event.data;
}
Three functions ae used to set up all of the other functions once the web page loads. The onLoad(event)
function is called when the web page is done loading. It first initializes the web socket connection, as noted earlier. It then defnines a response for the button on the web page - when clicked call the toggle()
function. The toggle()
function uses the web socket to send a "toggle" message to the server through the web socket connection. The server will respond to the "toggle" message as noted earlier - changing the state of the LED and messaging all connected web pages about the update.
function onLoad(event) {
initWebSocket();
initButton();
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
The Web Socket Applicaton can be seen in action below.
Two-Way Browser / Board Control of XIAO Onboard LED Using Web Socket Connection