Networking and Communication
ESP32 Networked Camera Node
A wireless camera node that automatically captures and saves images to a computer every 24 hours at random times. The ESP32 sleeps between captures to stay cool and save power.
Hardware
- ESP32S3 with Sense camera module
- OV5640 camera lens
- Micro SD card slot (included on Sense module)
Setup Notes
- Attach the antenna! The ESP32 needs it for WiFi
- Disable VPN on your computer during setup (blocks local network communication)
Software Architecture
Two-part system:
- ESP32 (Client) - Captures images and sends them over WiFi
- Python Server (Computer) - Receives and saves images
Part 1: Computer Setup (Python Server)
Install Python & Flask
Mac/Linux:
# Create project folder
cd Desktop
mkdir esp32_server
cd esp32_server
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install Flask
pip install flask
Create the Server
Create a file called image_server.py:
from flask import Flask, request
import os
from datetime import datetime
app = Flask(__name__)
SAVE_FOLDER = "esp32_images"
if not os.path.exists(SAVE_FOLDER):
os.makedirs(SAVE_FOLDER)
@app.route('/upload', methods=['POST'])
def upload_image():
try:
filename = request.headers.get('X-Filename')
if not filename:
filename = f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
filepath = os.path.join(SAVE_FOLDER, filename)
with open(filepath, 'wb') as f:
f.write(request.data)
print(f"Image saved: {filename} ({len(request.data)} bytes)")
return "Image received successfully", 200
except Exception as e:
print(f"Error: {e}")
return "Error saving image", 500
if __name__ == '__main__':
print("ESP32 Image Server")
print(f"Saving to: {os.path.abspath(SAVE_FOLDER)}")
app.run(host='0.0.0.0', port=8080, debug=False)
Run the Server
# Normal mode (terminal must stay open)
python3 image_server.py
# Background mode (can close terminal)
nohup python3 image_server.py > server.log 2>&1 &
Note your computer's IP address from the server output (e.g., 192.168.1.100). You'll need this for the ESP32 code!
Part 2: ESP32 Code
Arduino Libraries Needed
- WiFi.h
- WebServer.h
- HTTPClient.h
- esp_camera.h
- esp_sleep.h
Complete Code
Make sure to update:
- WiFi credentials (ssid and password)
- Server URL with your computer's IP address
#include <WiFi.h>
#include <HTTPClient.h>
#include "esp_camera.h"
#include "esp_sleep.h"
#include <time.h>
// WiFi credentials
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
// Server URL - UPDATE WITH YOUR COMPUTER'S IP
const char* serverUrl = "http://192.168.1.100:8080/upload";
// XIAO ESP32S3 Sense camera pins
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 10
#define SIOD_GPIO_NUM 40
#define SIOC_GPIO_NUM 39
#define Y9_GPIO_NUM 48
#define Y8_GPIO_NUM 11
#define Y7_GPIO_NUM 12
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 16
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 17
#define Y2_GPIO_NUM 15
#define VSYNC_GPIO_NUM 38
#define HREF_GPIO_NUM 47
#define PCLK_GPIO_NUM 13
// Sleep settings (hours)
#define MIN_SLEEP_HOURS 20
#define MAX_SLEEP_HOURS 24
#define uS_TO_S_FACTOR 1000000ULL
bool cameraInitialized = false;
void initCamera() {
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
cameraInitialized = (err == ESP_OK);
if (cameraInitialized) {
Serial.println("Camera initialized!");
} else {
Serial.println("Camera init failed");
}
}
bool sendImageToComputer(camera_fb_t * fb) {
HTTPClient http;
http.begin(serverUrl);
http.addHeader("Content-Type", "image/jpeg");
time_t now = time(nullptr);
struct tm* timeinfo = localtime(&now);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", timeinfo);
http.addHeader("X-Filename", String(timestamp) + ".jpg");
int httpResponseCode = http.POST(fb->buf, fb->len);
http.end();
return (httpResponseCode > 0);
}
void captureAndSendImage() {
// Take throwaway photo (camera sensor warm-up)
camera_fb_t * fb_warmup = esp_camera_fb_get();
if (fb_warmup) {
esp_camera_fb_return(fb_warmup);
}
delay(1000);
// Take real photo
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Capture failed");
return;
}
Serial.print("Photo captured: ");
Serial.print(fb->len);
Serial.println(" bytes");
bool success = sendImageToComputer(fb);
esp_camera_fb_return(fb);
Serial.println(success ? "✓ Saved to computer" : "✗ Failed to save");
}
void setup() {
Serial.begin(115200);
Serial.println("\n=== ESP32 Camera Node ===");
// Connect WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected!");
// Sync time
configTime(0, 0, "pool.ntp.org");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
now = time(nullptr);
}
randomSeed(now);
// Initialize camera
initCamera();
delay(2000);
if (cameraInitialized) {
captureAndSendImage();
}
// Sleep until next capture (random 20-24 hours)
float randomHours = MIN_SLEEP_HOURS + (random(0, 1000) / 1000.0) * (MAX_SLEEP_HOURS - MIN_SLEEP_HOURS);
uint64_t sleepSeconds = (uint64_t)(randomHours * 3600);
Serial.print("Sleeping for ");
Serial.print(randomHours);
Serial.println(" hours");
esp_sleep_enable_timer_wakeup(sleepSeconds * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {
// Never runs - device sleeps and resets
}
Common Issues & Solutions
| Problem |
Solution |
| ESP32 won't connect to WiFi |
Check antenna is attached, verify WiFi credentials |
| Computer can't be reached |
Disable VPN, use correct IP address |
| Port 5000 already in use |
Use port 8080 instead (AirPlay uses 5000 on Mac) |
| Pink/purple images |
Camera needs warm-up photo (code includes this fix) |
| ESP32 gets hot |
Use deep sleep mode (included in final code) |
| pip: command not found |
Use pip3 or python3 -m pip instead |