Skip to main content
Image
Webscreen camera monitor

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 mounting

Target 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 50

When 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 both

Monitor 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.

Image
Crowd Supply Logo

 

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!