Progressive Pipeline API
Header: main/cam_pjpeg.h
A FreeRTOS task that captures RGB565 frames, encodes progressive JPEG via esp_pjpeg, fragments into MTU-sized binary packets via pjpeg_lora, and transmits each packet with lora_send_binary(). This is a parallel alternative to cam_lora.h — selected at compile time with USE_PROGRESSIVE_JPEG.
Progressive structure means the receiver sees a recognizable image after just the DC scans (~3 KB), with detail added by later scans.
Configuration
Section titled “Configuration”cam_pjpeg_config_t
Section titled “cam_pjpeg_config_t”| Field | Type | Default | Description |
|---|---|---|---|
lora_dest | uint16_t | 0 | Base station LoRa address |
periodic_ms | uint32_t | 0 | Periodic capture interval in ms (0 = disabled) |
motion_enabled | bool | false | Reserved for future use |
jpeg_quality | uint8_t | 75 | JPEG quality 1–100 (pjpeg encoder scale) |
scan_preset | pjpeg_scan_preset_t | PJPEG_SCAN_LORA | Scan organization (LORA preset optimized for early visual payoff) |
mtu | uint16_t | 0 | Packet MTU (0 = LORA_MAX_PAYLOAD 240, use 184 for MeshCore) |
use_ack | bool | false | Enable windowed ACK/retransmit via pjpeg_lora_ack |
use_crc8 | bool | true | Append CRC8 integrity byte to each data packet |
Functions
Section titled “Functions”cam_pjpeg_start
Section titled “cam_pjpeg_start”esp_err_t cam_pjpeg_start(const cam_pjpeg_config_t *config);Initialize and start the progressive pipeline. Creates a FreeRTOS task (cam_pjpeg, 12KB stack, priority 5) that:
- Initializes the camera (fake or real) in RGB565 mode
- Polls for LoRa commands every 100ms
- Checks periodic and on-demand triggers
- Captures, encodes, and transmits progressive JPEG images
Returns: ESP_OK on success, ESP_ERR_NO_MEM if task creation fails, ESP_ERR_INVALID_STATE if already running.
cam_pjpeg_stop
Section titled “cam_pjpeg_stop”void cam_pjpeg_stop(void);Signal the pipeline task to stop. Deinitializes the ACK sender if it was active. The task self-deletes on its next iteration.
cam_pjpeg_trigger_capture
Section titled “cam_pjpeg_trigger_capture”void cam_pjpeg_trigger_capture(void);Request an immediate capture from outside the pipeline task. Sets an internal flag that the task checks on its next poll cycle.
cam_pjpeg_get_capture_count
Section titled “cam_pjpeg_get_capture_count”uint32_t cam_pjpeg_get_capture_count(void);Returns the total number of successful captures since pipeline start.
cam_pjpeg_is_running
Section titled “cam_pjpeg_is_running”bool cam_pjpeg_is_running(void);Check if the pipeline task is currently running.
ACK mode
Section titled “ACK mode”When use_ack is true, the pipeline initializes a pjpeg_lora_ack_sender_t that wraps the packetizer with windowed acknowledgement:
- All packets for a segment (JPEG headers or a single scan) are captured into a PSRAM buffer
- Packets are transmitted in windows of 8
- After each window,
cam_pjpeg_rx_callbackcallslora_receive_binary()to wait for a 4-byte ACK bitmap - Missing packets are retransmitted (up to 3 retries per window)
- On NACK or max retries, the segment is marked failed and the next segment begins
When use_ack is false (default), packets are sent fire-and-forget. CRC8 is still appended (controlled by use_crc8) so the receiver can detect corruption even without ACK.
Trigger modes
Section titled “Trigger modes”The task supports two capture triggers, evaluated in priority order:
1. On-demand (highest priority)
Section titled “1. On-demand (highest priority)”A remote C:CAPTURE command or call to cam_pjpeg_trigger_capture() sets the capture_pending flag. The task processes this immediately on its next poll cycle.
2. Periodic
Section titled “2. Periodic”When periodic_ms > 0, the task captures at fixed intervals. An on-demand capture resets the periodic timer.
Remote commands
Section titled “Remote commands”The task listens for LoRa packets and handles these commands:
| Command | Action |
|---|---|
C:CAPTURE | Set capture pending flag |
C:STATUS | Reply with S:HEAP=N:UP=N:RSSI=N:CAP=N:VER=X:MODE=PJPEG |
C:PING | Reply with S:PONG |
C:CONFIG:period=N | Set periodic interval (ms) |
C:CONFIG:quality=N | Set JPEG quality (1–100) |
Capture flow
Section titled “Capture flow”flowchart TD
A["cam_pjpeg_capture_and_send()"] --> B["fake_camera_fb_get()<br/>Get RGB565 framebuffer"]
B --> C["Copy pixels to PSRAM<br/>Free camera buffer early"]
C --> D["fake_camera_fb_return()"]
D --> E["pjpeg_encoder_create()"]
E --> F["pjpeg_encode_coefficients()<br/>DCT + quantize → PSRAM"]
F --> G["free(pixels)"]
G --> H["pjpeg_lora_ack_send_image()<br/>Packetize + ACK + lora_send_binary()"]
H --> I["pjpeg_encoder_destroy()"]
The camera framebuffer is released before encoding begins. The encoder stores DCT coefficients in PSRAM, then the packetizer reads them scan-by-scan — this keeps peak memory usage lower than holding both pixels and JPEG data simultaneously.
Pipeline comparison
Section titled “Pipeline comparison”| Feature | cam_lora.h (legacy) | cam_pjpeg.h (progressive) |
|---|---|---|
| Input format | JPEG (from OV2640) | RGB565 (software encode) |
| Encoding | Hardware JPEG | Progressive JPEG via esp_pjpeg |
| Wire format | ASCII Base64 + CRC16 | Binary packets + CRC8 |
| Fragmentation | lora_transfer.h | pjpeg_lora.h + pjpeg_lora_ack.h |
| Send function | lora_send() | lora_send_binary() |
| ACK protocol | Windowed (8-frag bitmap) | Windowed (8-frag bitmap) |
| Progressive display | No | Yes (DC scans first) |
| Motion detection | Yes | Reserved (not yet wired) |
| Compile flag | Default | USE_PROGRESSIVE_JPEG |