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.
#include"WiFi.h"voidsetup(){Serial.begin(115200);// Set WiFi to station mode and disconnect from an AP if it was previously connectedWiFi.mode(WIFI_STA);WiFi.disconnect();delay(100);Serial.println("Setup done");}voidloop(){Serial.println("scan start");// WiFi.scanNetworks will return the number of networks foundintn=WiFi.scanNetworks();Serial.println("scan done");if(n==0){Serial.println("no networks found");}else{Serial.print(n);Serial.println(" networks found");for(inti=0;i<n;++i){// Print SSID and RSSI for each network foundSerial.print(i+1);Serial.print(": ");Serial.print(WiFi.SSID(i));Serial.print(" (");Serial.print(WiFi.RSSI(i));Serial.print(")");Serial.println((WiFi.encryptionType(i)==WIFI_AUTH_OPEN)?" ":"*");delay(10);}}Serial.println("");// Wait a bit before scanning againdelay(5000);}
The scanning code worked well, and the serial monitor showed that the XIAO ESP32C3 was able to identify nearby WiFi networks.
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.
#include<WiFi.h>constchar*ssid="your-ssid";constchar*password="your-password";voidsetup(){Serial.begin(115200);delay(10);// We start by connecting to a WiFi networkSerial.println();Serial.println();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");Serial.println("IP address: ");Serial.println(WiFi.localIP());}voidloop(){}
The WiFi connecting code worked well, and the serial monitor showed that the XIAO ESP32C3 was able to connect with our local WiFi network.
Connecting to lakehouse89
...
WiFi connected
IP address:
192.168.1.121
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.
/********* Rui Santos Complete project details at http://randomnerdtutorials.com *********/// Load Wi-Fi library#include<WiFi.h>// Replace with your network credentialsconstchar*ssid="REPLACE_WITH_YOUR_SSID";constchar*password="REPLACE_WITH_YOUR_PASSWORD";// Set web server port number to 80WiFiServerserver(80);// Variable to store the HTTP requestStringheader;// Auxiliar variables to store the current output stateStringoutput26State="off";// Assign output variables to GPIO pinsconstintoutput26=D6;// Current timeunsignedlongcurrentTime=millis();// Previous timeunsignedlongpreviousTime=0;// Define timeout time in milliseconds (example: 2000ms = 2s)constlongtimeoutTime=2000;voidsetup(){Serial.begin(115200);// Initialize the output variables as outputspinMode(output26,OUTPUT);// Set outputs to LOWdigitalWrite(output26,LOW);// Connect to Wi-Fi network with SSID and passwordSerial.print("Connecting to ");Serial.println(ssid);WiFi.begin(ssid,password);while(WiFi.status()!=WL_CONNECTED){delay(500);Serial.print(".");}// Print local IP address and start web serverSerial.println("");Serial.println("WiFi connected.");Serial.println("IP address: ");Serial.println(WiFi.localIP());server.begin();}voidloop(){WiFiClientclient=server.available();// Listen for incoming clientsif(client){// If a new client connects,currentTime=millis();previousTime=currentTime;Serial.println("New Client.");// print a message out in the serial portStringcurrentLine="";// make a String to hold incoming data from the clientwhile(client.connected()&¤tTime-previousTime<=timeoutTime){// loop while the client's connectedcurrentTime=millis();if(client.available()){// if there's bytes to read from the client,charc=client.read();// read a byte, thenSerial.write(c);// print it out the serial monitorheader+=c;if(c=='\n'){// if the byte is a newline character// if the current line is blank, you got two newline characters in a row.// that's the end of the client HTTP request, so send a response:if(currentLine.length()==0){// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)// and a content-type so the client knows what's coming, then a blank line:client.println("HTTP/1.1 200 OK");client.println("Content-type:text/html");client.println("Connection: close");client.println();// turns the GPIOs on and offif(header.indexOf("GET /26/on")>=0){Serial.println("GPIO 26 on");output26State="on";digitalWrite(output26,HIGH);}elseif(header.indexOf("GET /26/off")>=0){Serial.println("GPIO 26 off");output26State="off";digitalWrite(output26,LOW);}// Display the HTML web pageclient.println("<!DOCTYPE html><html>");client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");client.println("<link rel=\"icon\" href=\"data:,\">");// CSS to style the on/off buttons // Feel free to change the background-color and font-size attributes to fit your preferencesclient.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");client.println(".button2 {background-color: #555555;}</style></head>");// Web Page Headingclient.println("<body><h1>ESP32 Web Server</h1>");// Display current state, and ON/OFF buttons for GPIO 26 client.println("<p>GPIO 26 - State "+output26State+"</p>");// If the output26State is off, it displays the ON button if(output26State=="off"){client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");}else{client.println("<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>");}client.println("</body></html>");// The HTTP response ends with another blank lineclient.println();// Break out of the while loopbreak;}else{// if you got a newline, then clear currentLinecurrentLine="";}}elseif(c!='\r'){// if you got anything else but a carriage return character,currentLine+=c;// add it to the end of the currentLine}}}// Clear the header variableheader="";// Close the connectionclient.stop();Serial.println("Client disconnected.");Serial.println("");}}
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.
Connecting to lakehouse89
....
WiFi connected.
IP address:
192.168.1.121
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.
/********* Rui Santos & Sara Santos - Random Nerd Tutorials Complete project details at https://RandomNerdTutorials.com/esp32-websocket-server-arduino/ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.*********/// Import required libraries#include<WiFi.h>#include<AsyncTCP.h>#include<ESPAsyncWebServer.h>// Replace with your network credentialsconstchar*ssid="REPLACE_WITH_YOUR_SSID";constchar*password="REPLACE_WITH_YOUR_PASSWORD";constuint8_tDEBOUNCE_DELAY=5;// in millisecondsboolledState=0;constintledPin=D6;structButton{// state variablesuint8_tpin;boollevelForPressed;boollastReading;uint32_tlastDebounceTime;uint16_tstate;// methods determining the logical state of the buttonboolpressed(){returnstate==1;}boolreleased(){returnstate==0xffff;}boolheld(uint16_tcount=0){returnstate>1+count&&state<0xffff;}// method for reading the physical state of the buttonvoidread(){// reads the voltage on the pin connected to the buttonboolreading=digitalRead(pin);// if the logic level has changed since the last reading,// we reset the timer which counts down the necessary time// beyond which we can consider that the bouncing effect// has passed.if(reading!=lastReading){lastDebounceTime=millis();}// from the moment we're out of the bouncing phase// the actual status of the button can be determinedif(millis()-lastDebounceTime>DEBOUNCE_DELAY){// don't forget that the read pin is pulled-upboolpressed=reading==levelForPressed;if(pressed){if(state<0xfffe)state++;elseif(state==0xfffe)state=2;}elseif(state){state=state==0xffff?0:0xffff;}}// finally, each new reading is savedlastReading=reading;}};// Create AsyncWebServer object on port 80AsyncWebServerserver(80);AsyncWebSocketws("/ws");constcharindex_html[]PROGMEM=R"rawliteral(<!DOCTYPEHTML><html><head><title>ESPWebServer</title><metaname="viewport"content="width=device-width, initial-scale=1"><linkrel="icon"href="data:,"><style>html{font-family:Arial,Helvetica,sans-serif;text-align:center;}h1{font-size:1.8rem;color:white;}h2{font-size:1.5rem;font-weight:bold;color:#143642;}.topnav{overflow:hidden;background-color:#143642;}body{margin:0;}.content{padding:30px;max-width:600px;margin:0auto;}.card{background-color:#F8F7F9;;box-shadow:2px2px12px1pxrgba(140,140,140,.5);padding-top:10px;padding-bottom:20px;}.button{padding:15px50px;font-size:24px;text-align:center;outline:none;color:#fff;background-color:#0f8b8d;border:none;border-radius:5px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);}/*.button:hover {background-color: #0f8b8d}*/.button:active{background-color:#0f8b8d;box-shadow:22px#CDCDCD;transform:translateY(2px);}.state{font-size:1.5rem;color:#8c8c8c;font-weight:bold;}</style><title>ESPWebServer</title><metaname="viewport"content="width=device-width, initial-scale=1"><linkrel="icon"href="data:,"></head><body><divclass="topnav"><h1>XIAOESP32C3WebSocketServer</h1></div><divclass="content"><divclass="card"><h2>OnboardLED</h2><pclass="state">state:<spanid="state">%STATE%</span></p><p><buttonid="button"class="button">Toggle</button></p></div></div><script>vargateway=`ws://${window.location.hostname}/ws`;varwebsocket;window.addEventListener('load',onLoad);functioninitWebSocket(){console.log('TryingtoopenaWebSocketconnection...');websocket=newWebSocket(gateway);websocket.onopen=onOpen;websocket.onclose=onClose;websocket.onmessage=onMessage;// <-- add this line}functiononOpen(event){console.log('Connectionopened');}functiononClose(event){console.log('Connectionclosed');setTimeout(initWebSocket,2000);}functiononMessage(event){varstate;if(event.data=="1"){state="ON";}else{state="OFF";}document.getElementById('state').innerHTML=event.data;}functiononLoad(event){initWebSocket();initButton();}functioninitButton(){document.getElementById('button').addEventListener('click',toggle);}functiontoggle(){websocket.send('toggle');}</script></body></html>)rawliteral";voidnotifyClients(){ws.textAll(String(ledState));}voidhandleWebSocketMessage(void*arg,uint8_t*data,size_tlen){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();}}}voidonEvent(AsyncWebSocket*server,AsyncWebSocketClient*client,AwsEventTypetype,void*arg,uint8_t*data,size_tlen){switch(type){caseWS_EVT_CONNECT:Serial.printf("WebSocket client #%u connected from %s\n",client->id(),client->remoteIP().toString().c_str());break;caseWS_EVT_DISCONNECT:Serial.printf("WebSocket client #%u disconnected\n",client->id());break;caseWS_EVT_DATA:handleWebSocketMessage(arg,data,len);break;caseWS_EVT_PONG:caseWS_EVT_ERROR:break;}}voidinitWebSocket(){ws.onEvent(onEvent);server.addHandler(&ws);}Stringprocessor(constString&var){Serial.println(var);if(var=="STATE"){if(ledState){return"ON";}else{return"OFF";}}returnString();}Buttonbutton={D7,LOW,LOW,0,0};voidsetup(){// Serial port for debugging purposesSerial.begin(115200);pinMode(button.pin,INPUT);pinMode(ledPin,OUTPUT);digitalWrite(ledPin,LOW);// Connect to Wi-FiWiFi.begin(ssid,password);while(WiFi.status()!=WL_CONNECTED){delay(1000);Serial.println("Connecting to WiFi..");}// Print ESP Local IP AddressSerial.println(WiFi.localIP());initWebSocket();// Route for root / web pageserver.on("/",HTTP_GET,[](AsyncWebServerRequest*request){request->send(200,"text/html",index_html,processor);});// Start serverserver.begin();}voidloop(){ws.cleanupClients();digitalWrite(ledPin,ledState);button.read();if(button.pressed()){ledState=!ledState;notifyClients();}}
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.
Two-Way Browser / Board Control of XIAO Onboard LED Using Web Socket Connection