Networking & Comm – ESP32 Sensor Web Server
Table of contents
Group Assignment for Output Devices
send a message between two projects.
shown in Week 10 of EECS Group Assignments.
ESP32-CAM
As mentioned in my ongoing final project WingCam, I would like to use a light-weighted camera as the image and video capturing module on the airplane. Anthony recommended me with ESP32-CAM. And I found it convinient to prepare it for my final project.
ESP32-CAM basic layout and GPIO pins.
I focused on two tasks related to ESP32-CAM that will be used for my final project.
WiFi Scan with ESP32-CAM
Firstly, I try to program ESP32-CAM with Arduino and start with a basic WiFi Scanner as listed in the Arduino ESP32 example.
Since this ESP32-CAM does not come with a serial port such as USB Micro / Mini connector, it requires an addition programmer to write code on it using Arduino, as shown below.
ESP32-CAM programming tools and instructions from here.
Here, I was using the FTDI programmer (bottom one) and followed the connection shown below. Here is an interesting combination of 3.3V and 5V power on both ESP32-CAM and FTDI programmer because both can support 3.3/5V power as input or output while program uploading (And the jumper shown in green between GND
and IO0
to turn off CSI_MCLK
and write code into the board). However, while functioning, the board requires 5V/2A as input, otherwise it will raise brownout
issue, as shown below.
1
Brownout detector was triggered
Eventually, the minimal solution is to use 5V power on both ESP32-CAM and FTDI programmer while program uploading and functioning. The jumper between GND
and IO0
is used to switch between these two modes, where program uploading requires the jumper is connected.
After figuring out the power and jumper issue, I managed to get the WiFi Scan work, as shown below. The source code is from the Arduino ESP32 example.
ESP32-CAM basic WiFi scanning.
As shown in the photo, I used AI Thinker ESP32-CAM
as the board and pre-installed Arduino ESP32 Libarary by adding https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
to the additional board manager
in Preferences
.
WiFi WebCam with ESP32-CAM
Then I experimented with the WiFi WebCam using ESP32-CAM, which is essentially why I want to use this module. Everything comes exactly the same as the WiFi Scan part, I only changed to the CameraWebServer example and set the corresponding WiFi access point that ESP32-CAM can directly connect to without further certification. So I used the SSID StataCenter
(instead of CSAILPrivate
requiring CSAIL kerberos certification) in Stata Building and MIT
(instead of MIT (Secure)
requiring MIT kerberos certification) and set password
to empty string.
And it worked pretty well so long as the client opening the URL in the web browser is in the same WLAN or LAN network as the ESP32-CAM.
ESP32-CAM as a web camera.
ToF and Hall Sensor Web Server using Server-Sent Events (SSE)
As the assignment indicates, the intuitive idea to make a cool networking and communication demo is to connect the ESP32-CAM with a sensor and have its value broadcasted on a webpage. So, I decided to show two values, one it the ToF sensor (VL53L0X), which I designed and used in Week 8 as a 2D localizer, another is the Hall magnetic sensor embedded with ESP32.
One challenge is to configure the board with I²C for the ToF sensor, because the default I²C channel is already occupied by the OV2640 camera module and there are no open connectors to that.
I followed this neat example and set IO14
and IO15
as I2C_SDA
and I2C_SCL
, respectively. One mistake I was making and took me almost one hour to figure it out is I put TwoWire I2CWire = TwoWire(0);
in setup()
instead of as a global variable and Arduino cannot correctly use I2CWire
in the sensor reading stage.
Another challenge is to use asynchronous TCP for Server-Sent Events (SSE), which is crucial because the basic web server example only uses synchronous TCP and failed to update the sensor value in real-time. Following this nice example of asynchronous TCP for asynchronous web server, I managed to make the Sensor Web Server work using server-sent events (after manually installing AsyncTCP and ESPAsyncWebServer libraries by going Sketch
-> Include Library
-> Add .zip Library
). A bonus of this example is that it offers a great template for displaying the sensor layout.
Results
Code Snippets
esp32_sensor_async_webserver.ino
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
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Wire.h>
#include <VL53L0X.h>
#define I2C_SDA 14
#define I2C_SCL 15
// const char* ssid = "StataCenter";
const char* ssid = "MIT";
const char* password = "";
AsyncWebServer server(80);
AsyncEventSource events("/events"); // Create an Event Source on /events
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 100; // update interval [ms]
const int led = 33; // built-in LED1
VL53L0X ToFSensor;
const int tof_shutdown = 13; // Pin of ToF sensor SHUTDOWN
TwoWire I2CWire = TwoWire(0);
float range; // ToF range in cm [from mm]
int hall; // Hall magnetic field detector
void initToFSensor() {
// setup I2C ToF Sensor
I2CWire.begin(I2C_SDA, I2C_SCL, 100000);
ToFSensor.setBus(&I2CWire);
I2CWire.begin();
pinMode(tof_shutdown, OUTPUT);
digitalWrite(tof_shutdown, HIGH);
ToFSensor.setTimeout(500);
if (!ToFSensor.init())
{
Serial.println("Failed to detect and initialize ToFSensor!");
while (1) {}
}
}
void getSensorReadings() {
// [1] read hall sensor
hall = hallRead();
// [2] read ToF sensor
int rangemm = ToFSensor.readRangeSingleMillimeters();
range = float(rangemm) / 10.0F;
if (ToFSensor.timeoutOccurred()) { Serial.print(" ToFSensor TIMEOUT"); }
}
void initWiFi() {
pinMode(led, OUTPUT);
digitalWrite(led, 0);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
String processor(const String& var){
getSensorReadings();
//Serial.println(var);
if(var == "RANGE"){
return String(range);
}
else if(var == "HALL"){
return String(hall);
}
return String();
}
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP32-CAM Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #50B8B4; color: white; font-size: 1rem; }
.content { padding: 20px; }
.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
.cards { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.reading { font-size: 1.4rem; }
</style>
</head>
<body>
<div class="topnav">
<h1>ToF and Hall Sensor Web Server (ESP32-CAM)</h1>
</div>
<div class="content">
<div class="cards">
<div class="card">
<p><i class="fas fa-ruler-horizontal" style="color:#059e8a;"></i> RANGE</p><p><span class="reading"><span id="temp">%RANGE%</span> cm</span></p>
</div>
<div class="card">
<p><i class="fas fa-magnet" style="color:#00add6;"></i> HALL</p><p><span class="reading"><span id="hum">%HALL%</span> </span></p>
</div>
</div>
</div>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('range', function(e) {
console.log("range", e.data);
document.getElementById("temp").innerHTML = e.data;
}, false);
source.addEventListener('hall', function(e) {
console.log("hall", e.data);
document.getElementById("hum").innerHTML = e.data;
}, false);
}
</script>
</body>
</html>)rawliteral";
void setup(void) {
Serial.begin(115200);
initWiFi();
initToFSensor();
// Handle Web Server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// Handle Web Server Events
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
if ((millis() - lastTime) > timerDelay) {
getSensorReadings();
Serial.printf("ToF Range = %.2f cm \n", range);
Serial.printf("Hall = %d \n", hall);
Serial.println();
// Send Events to the Web Server with the Sensor Readings
events.send("ping",NULL,millis());
events.send(String(range).c_str(),"range",millis());
events.send(String(hall).c_str(),"hall",millis());
lastTime = millis();
}
}