Building a Camera System with WebScren. XIAO ESP32S3 and ESP-NOW
We set out to stream video from a XIAO ESP32S3 Sense (OV2640 camera) to a WebScreen without using a router. We implemented a direct peer-to-peer link with ESP-NOW.
Why ESP-NOW?
ESP-NOW is Espressif’s connectionless peer-to-peer protocol: devices exchange vendor-specific 802.11 action frames directly, so no access point or association is required. It’s lightweight and low-latency, which fits a camera-to-display pipeline. Typical latency is around 100–200 ms at QVGA/20 FPS in our setup.
Payload limits matter: many Arduino stacks cap user payloads at ~250 bytes (ESP-NOW v1 behavior). ESP-NOW v2 increases the maximum user payload up to 1470 bytes on supported stacks. Range is highly environment-dependent—line-of-sight distances are commonly tens to a few hundred meters; with ESP-NOW Long Range (LR) mode and favorable conditions, distances can approach ~1 km at reduced throughput.
The catch: a 320x240 RGB565 frame is 153,600 bytes. With 250-byte payloads, that’s 615 chunks per frame (153,600/250, rounded up). With larger v2 payloads, the chunk count drops significantly (~105 chunks at 1470 bytes).
Hardware Setup
Camera (Sender):
■ Seeed Studio XIAO ESP32S3 Sense
■ OV2640 camera sensor
■ 320x240 resolution
Display (Receiver):
■ WebScreen
■ LVGL for graphics
The Implementation
Chunking Frames
Each frame is split into fixed-size chunks with minimal metadata for reassembly:
typedef struct {
uint32_t frame_id;
uint16_t chunk_index;
uint16_t total_chunks;
uint16_t chunk_size;
uint8_t data[250]; // sized for stacks capped at ~250 B payloads
} espnow_camera_chunk_t;The receiver assembles chunks back into complete frames using double buffering to avoid tearing while the previous frame is on screen.
Camera Configuration
OV2640 sensor tuned for balanced image quality and stable exposure:
s->set_brightness(s, 1); // slight boost
s->set_contrast(s, 1);
s->set_saturation(s, 1);
s->set_sharpness(s, 1);
s->set_vflip(s, 1); // match physical mountingTarget frame rate is 20 FPS. At short range and clean spectrum, this provides smooth motion with minimal packet loss.
The WiFi Channel Problem
A common pitfall is channel mismatch. ESP-NOW peers must operate on the same 2.4 GHz channel; otherwise frames won’t be received even though the sender reports successful transmissions at the MAC layer.
Ensure Wi-Fi is started in station mode, set the channel, then initialize ESP-NOW and add peers:
WiFi.mode(WIFI_STA); WiFi.setChannel(1); // init ESP-NOW after setting the channel
After aligning channels, the link stabilizes immediately.
Display Layout
Split screen design:
■ Left panel (200px): Dark gray info panel with statistics
■ Right side (320px): Camera feed
Info panel shows:
■ FPS counter
■ Total frames received
■ Packet loss percentage
■ System uptime
■ Streaming quality (GOOD/FAIR/SLOW)
■ Motion detection status
Adding Motion Detection
Lightweight motion detection runs per frame:
Downsample to 32x32 grayscale
Compare with previous frame
Count changed pixels
Trigger if threshold exceeded
The detector uses <2 KB RAM and adds ~2–5 ms of processing per frame.
// Motion detection configuration
#define MOTION_SAMPLE_SIZE 32
#define MOTION_THRESHOLD 20
#define MOTION_MIN_CHANGES 50When motion is detected:
■ Red indicator lights up
■ Counter increments
■ Event logged to serial
Sensitivity is adjustable from 1–10 (default 5).
Performance Numbers
Frame Rate: 18–20 FPS
Resolution: 320x240 RGB565
Latency: ~100–200 ms
Memory: 153 KB per frame buffer (2 buffers for double buffering)
Motion Detection Overhead: <5 ms per frame
Serial output during operation:
ESP-NOW Camera: Initialized successfully
- Frame size: 320x240
- Buffer size: 153600 bytes
- WiFi Channel: 1
Camera Display: Initialized successfully
- Canvas size: 320x240
- Screen size: 536x240
- Info panel: 200x240
- Motion detection: enabled
Motion Detection: MOTION STARTED (changed: 127, level: 45%)Code Structure
Project is modular:
Camera Sender (camera_sender/camera_sender.ino):
■ Camera initialization and configuration
■ Frame capture (RGB565 format)
■ Frame chunking
■ ESP-NOW transmission
■ Statistics tracking
WebScreen Receiver (webscreen/webscreen.ino):
■ ESP-NOW frame reception
■ Frame assembly with double buffering
■ LVGL rendering
■ Motion detection integration
■ Real-time statistics display
Motion Detection (webscreen/motion_detect.cpp):
■ Frame downsampling
■ Pixel difference comparison
■ Motion threshold detection
■ Statistics tracking
Getting It Running
Flash WebScreen receiver firmware
Open serial monitor at 115200 baud
Note the MAC address from output
Update camera_sender.ino line 40 with that MAC address (peer)
Initialize Wi-Fi in STA mode and set the same channel on both devices
Initialize ESP-NOW, add peers, and then start streaming
Flash camera sender firmware and power both
The MAC address line looks like:
====================================
WEBSCREEN MAC ADDRESS: 34:B4:72:5C:8A:10
====================================
Copy this MAC to camera_sender.ino line 40!Optimizations Made
Increased FPS: 10 → 20 FPS for smoother video
Faster Updates: Reduced loop delay from 10 ms to 5 ms
Stats Refresh: Updates every 200 ms instead of 500 ms
Sensor Tuning: Balanced brightness/contrast/saturation/sharpness
Send Pacing: Aligned send cadence with callbacks to reduce queue contention under load
Known Limitations
■ Packet loss can increase in crowded 2.4 GHz environments; choose a clean channel and reduce FPS if needed
■ 320x240 resolution (OV2640 constraint in this configuration)
■ No retransmission of missed chunks in this demo
■ Security: unicast peers can be encrypted (PMK/LMK); broadcast/multicast traffic is not encrypted
■ Range depends on environment and antennas; LR mode trades throughput for distance
Potential Improvements
JPEG Compression: Send compressed JPEG instead of raw RGB565
Selective Retransmission: Request missing chunks by index
Face Detection: Using SenseCraft AI
Motion Recording: Auto-save frames to SD when motion detected
Multiple Cameras: Support receiving from multiple senders
Use Cases
Works well for:
■ Baby monitor (short range)
■ Security camera (single room)
■ RC car FPV system
■ Wildlife camera with motion trigger
■ Workshop/3D Printer monitoring
Not suitable for:
■ Long-range surveillance requiring high throughput
■ High-resolution video
■ Critical security applications
Technical Notes
Double Buffering: Two frame buffers prevent tearing during assembly
Grayscale Conversion: Motion detection converts RGB565 to grayscale using:
Y = 0.299R + 0.587G + 0.114B
Downsampling: Bilinear interpolation for smooth resize from 320x240 to 32x32
Channel Selection: Use the same channel (1–14) on both devices; set channel before initializing ESP-NOW
Power Consumption
The camera node draws approximately 150–200 mA during streaming. With a 1000 mAh battery, expect ~5–6 hours of continuous operation. Duty-cycling or sleeping between motion events can substantially extend runtime.
Debugging Tips
Enable Serial Debug:
Serial.printf("Frames sent: %lu\n", frames_sent);
Serial.printf("Send errors: %lu\n", send_errors);Check WiFi Channel:
Serial.printf("WiFi Channel: %d\n", WiFi.channel()); // must match on bothMonitor Packet Loss: Watch the "Loss: X%" on the display. Above ~5% indicates issues; try a cleaner channel, shorter range, fewer chunks (if using v2 payloads), or lower FPS.
Test at Close Range: Start with devices ~1 m apart to rule out RF issues, then increase distance.
What's Next
Currently exploring:
■ H.264 (or JPEG) on the sender to reduce packet count
■ SenseCraft AI integration for object detection
■ Multi-camera support with channel hopping
■ Web interface for configuration
Code Availability
The complete project is available on GitHub.
Resources
■ [Espressif Official Documents] ESPRESSIF ESP-IDF ESP-NOW
■ Start running with ESP-NOW protocol on XIAO Series
Order your WebScreen now
WebScreen is available for pre-order on Crowd Supply, and we need your help to bring WebScreen to more people. Your backing makes it possible to keep prices low and quality high, so teams everywhere can use this open-source gadget.
Check out the campaign and get involved: WebScreen on Crowd Supply
We’ll continue expanding the marketplace with more apps and features. We welcome feedback—join our Discord community or email [email protected].
Thanks for your continued support and for helping us build WebScreen together!