Network
Objective
For this week I focus on connecting the Xiao ESP32-S3 to WiFi and feeding the image from the camera to a local server, that I can later deploy as the backend for my webapp. I started by making a local Flask python app that will capture the POST request from the Xiao and will report it to the frontend.
I had to use "MIT" wifi with a static password from a the PSK request on MIT's IS&T website. Also, both the Xiao and the python app accepting it's report (so - my computer) had to be connected to the same network for it to work locally.
The camera output went very bad, so I decided to start more simple with an open/close status report from the Xiao to the web app, based on the position of the Servo.
After many tries it worked but not very consistently, so I added a mechanism for retrying/timeout and made sure it reconnects to the internet within thereporting function in cases where the internet became unstable by then.
The next stage was to try making it work with a few controllers in parallel. I used the ESP32C3 I had from previous weeks to try it out.
However, I ran into the next problem I'll have to solve soon which is every time I connect to the MIT network I get a new IP address and have to reload the Xiao with the new address which is not a sustainable solution. But ok, this will be fixed when I'll deploy to an actual website.
I was very releaved to see it's working and updating connected to a regular power outlet.
This is the modified code for the C3, a little more basic (without the indicator leds and the camera):
#include
#include
#include
#include
// WiFi credentials
#include "wifi_codes.h"
// Define locker ID
const int locker_id = 2; // This is locker 2
// Server URLs
const char* statusServerURL = "http://10.29.226.47:4000/update_locker"; // For locker status updates
// Define the pin connected to the servo's PWM wire
int servoPin = 20;
int ledPin = 8;
Servo myServo;
// Setup keypad pins and layout
const byte ROWS = 4; // 4 rows
const byte COLS = 3; // 3 columns
char keys[ROWS][COLS] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {2, 3, 4, 5};
byte colPins[COLS] = {6, 7, 21};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Define access codes
String open_code = "1234";
String inputCode = "";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected.");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Servo initialization
myServo.attach(servoPin);
myServo.write(90); // Locked position
// LED initialization
digitalWrite(ledPin, LOW);
}
void loop() {
char key = keypad.getKey(); // Get keypad input
if (key) {
if (key == '#') { // '#' triggers code submission
if (inputCode == open_code) {
openLocker();
} else {
Serial.println("Invalid code.");
}
inputCode = ""; // Reset input after submission
} else if (key == '*') { // '*' closes locker
closeLocker();
} else {
inputCode += key; // Append key to input
Serial.println("Entered: " + inputCode);
}
}
}
void openLocker() {
Serial.println("Opening locker...");
myServo.write(0); // Open servo
sendLockerStatus(1); // Send status: open
delay(2000);
}
void closeLocker() {
Serial.println("Closing locker...");
// digitalWrite(ledPin, HIGH); // Turn on locker light
myServo.write(90); // Lock servo
sendLockerStatus(0); // Send status: closed
// captureAndSendImage(); // Capture and send an image
delay(2000); // Keep light on for 2 seconds
// digitalWrite(ledPin, LOW); // Turn off locker light
}
void sendLockerStatus(int status) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
const int maxRetries = 5; // Maximum number of retry attempts
const int retryDelay = 2000; // Delay between retries in milliseconds
int attempt = 0;
bool success = false;
String payload = "{\"locker_id\": " + String(locker_id) + ", \"status\": " + String(status) + "}";
while (attempt < maxRetries && !success) {
Serial.printf("Attempt %d to send locker status...\n", attempt + 1);
// Debug: Signal strength and free memory
Serial.printf("WiFi Signal Strength (RSSI): %d dBm\n", WiFi.RSSI());
Serial.printf("Free heap memory: %d bytes\n", ESP.getFreeHeap());
http.begin(statusServerURL); // Initialize the connection
http.addHeader("Content-Type", "application/json");
// Debug: Print payload
Serial.println("Sending payload:");
Serial.println(payload);
int httpResponseCode = http.POST(payload); // Send the HTTP POST request
Serial.print("HTTP Response Code: ");
Serial.println(httpResponseCode);
if (httpResponseCode > 0) {
// Success: Print the server's response
String response = http.getString();
Serial.println("Response:");
Serial.println(response);
success = true; // Mark success
} else {
// Failed: Print error and retry
Serial.printf("HTTP POST failed, code: %d. Retrying...\n", httpResponseCode);
delay(retryDelay); // Wait before retrying
attempt++;
}
http.end(); // Close the connection after each attempt
}
if (!success) {
// After maximum retries
Serial.println("Failed to send locker status after maximum retries.");
}
} else {
// WiFi is not connected
Serial.println("WiFi not connected, attempting reconnection...");
WiFi.begin(ssid, password); // Try reconnecting to WiFi
delay(1000); // Allow time for reconnection
}
}
YAY 1: Local Host
A long chat with ChatGPT led me to try deploying the site to Render, the main reasons being:
- It is realtively cheap
- It can support both backend (web service) and frontend (static site)
- It has good connectivity to GitHub, which I'm familiar with and wanted to use as my online repo
I started by making a SvelteKit Frontend basic website, showing rectangles for the different lockers and marking them green/red if they're open/close. I cloned both frontend and backend files to my GitHub repo which I called BuyBye. Then, I deployed a webservice and a frontend site on Render that will connect to each of the frontend/backend root directories from that repo:
Over the next stages I had a lot of debugging to do, finally realizing that I was running the code from the GitHub root directory on the web server, instead of out of the backend directory. I tested with a curl command from VS directly to the server and finally got a good log on the web server, showing the manual POST changed the state of the lockers both on the backend JSON file and on the frontend.
Here is how it looks from the Xiao ESP32-S3 POST through the server, to the frontend client:
ESP32-S3 Camera Feed
For the next step I tried to get the output from the Xiao ESP32-S3 Sense camera but went out of time. The plan was to get the camera feed on the website, and then integrate it with the keypad input. I ran into a lot of difficulty connecting to the camera at all from my home wifi in the dorms or on my cell's hotspot. It worked eventually when I tried connecting through the EECS's lab designated wi-fi for IoT, but I need a more robust solution that will allow connecting to the cameras when I place the final cabinet in the housing's loby.
This dissapointing square is where I got this far with getting the image from the camera: