Dynamic WiFi Image Gallery run on Waveshare ESP32-S3-Touch-AMOLED-2.16
In my previous post, I demonstrated how to
download and display JPG images via WiFi on the Waveshare
ESP32-C5-WIFI6-KIT-N16R8 with an 240x320 ST7789 SPI TFT LCD using hardcoded
filenames. This new implementation is much more flexible: filenames are now stored in
a jpg_list.txt file on the server, allowing the ESP32 to download the images
dynamically. I have also successfully tested this setup on the
Waveshare ESP32-S3-Touch-AMOLED-2.1 with its 480x480 AMOLED (CO5300
driver).
How it Works
The program operates in three distinct phases:
Exercise Code
S3_CO5300_wifi_JPEGDEC_list.ino
/*
Exercise run on Waveshare ESP32-S3-Touch-AMOLED-2.16
- Connect to WiFi Access Point,
- download jpg_list.txt
- download multiple jpgs accordingly in a loop,
- decode jpg using JPEGDEC,
- and display on 480*480 AMOLED with CO5300 driver using Arduino_GFX_Library.
https://coxxect.blogspot.com/2026/06/dynamic-wifi-image-gallery-run-on.html
Remark about "USB CDC On Boot" option for Serial.print()
For Waveshare ESP32-S3-Touch-AMOLED-2.16 with a single USB port only,
select USB CDC On Boot: "Enabled".
Otherwise, you cannot see the output by Serial.print().
Image server side:
> python -m http.server
Acknowledgment:
Special thanks to Gemini (Google AI) for the technical guidance.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <JPEGDEC.h>
#include <Arduino_GFX_Library.h>
// ==========================================
// 1. Configuration
// ==========================================
const char* ssid = "ssid";
const char* password = "password";
IPAddress server_ip;
// --- Dynamic Loop Variables ---
String imageList[64]; // Supports up to 64 images
int currentImageIndex = 0;
int totalImages = 0;
// ==========================================
// 2. Display Setup (Your specific config)
// ==========================================
#define LCD_SDIO0 4
#define LCD_SDIO1 5
#define LCD_SDIO2 6
#define LCD_SDIO3 7
#define LCD_SCLK 38
#define LCD_RESET 2
#define LCD_CS 12
#define LCD_WIDTH 480
#define LCD_HEIGHT 480
Arduino_DataBus *bus = new Arduino_ESP32QSPI(
LCD_CS /* CS */, LCD_SCLK /* SCK */, LCD_SDIO0 /* SDIO0 */, LCD_SDIO1 /* SDIO1 */,
LCD_SDIO2 /* SDIO2 */, LCD_SDIO3 /* SDIO3 */);
Arduino_CO5300 *gfx = new Arduino_CO5300(
bus, LCD_RESET /* RST */, 0 /* rotation */, LCD_WIDTH /* width */, LCD_HEIGHT /* height */, 0, 0, 0, 0);
// ==========================================
// 3. JPEG Decoder Setup
// ==========================================
JPEGDEC jpeg;
// This callback function is fired by JPEGDEC every time it decodes a block of the image.
// It hands the pixel data directly to your Arduino_GFX instance.
int JPEGDraw(JPEGDRAW *pDraw) {
// draw16bitRGBBitmap handles the standard Little-Endian output of JPEGDEC
gfx->draw16bitRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight);
return 1; // Return 1 to continue decoding
}
// ==========================================
// 4. Main Program
// ==========================================
void setup() {
delay(2000);
Serial.begin(115200);
delay(1000);
Serial.println("\n\n~ Start ~");
// Init Display
gfx->begin();
gfx->fillScreen(RGB565_BLACK);
gfx->drawRect(0, 0, gfx->width()-1, gfx->height()-1, RGB565_WHITE);
gfx->setCursor(10, 10);
gfx->setTextSize(3 /* x scale */, 3 /* y scale */, 3 /* pixel_margin */);
gfx->println("ESP32-C5-WIFI6-KIT-N16R8");
gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 2 /* pixel_margin */);
delay(500); // 0.5 seconds
gfx->println();
gfx->println("WiFi ---");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi Connected!");
gfx->println("WiFi Connected!");
uint8_t connectedChannel = WiFi.channel();
Serial.printf("Channel: %d\n", connectedChannel);
gfx->printf("Channel: %d\n", connectedChannel);
if (connectedChannel >= 1 && connectedChannel <= 14) {
Serial.println("Band: 2.4 GHz");
gfx->println("Band: 2.4 GHz");
} else if (connectedChannel > 14) {
Serial.println("Band: 5 GHz");
gfx->println("Band: 5 GHz");
} else {
Serial.println("Band: Unknown");
gfx->println("Band: Unknown");
}
Serial.printf("Signal Strength (RSSI): %d dBm\n", WiFi.RSSI());
gfx->printf("Signal Strength (RSSI): %d dBm\n", WiFi.RSSI());
// Save the gateway IP so loop() can use it
server_ip = WiFi.gatewayIP();
// Download the list of files before starting the loop
fetchImageList();
Serial.println("- Setup Done, entering Loop -");
delay(1000);
}
void loop() {
if (totalImages == 0) {
Serial.println("No images to display. Checking again in 10s...");
delay(10000);
return;
}
// 1. Get the filename from our dynamic list
String filename = imageList[currentImageIndex];
String urlString = "http://" + server_ip.toString() + ":8000/" + filename;
Serial.printf("\n--- Displaying [%d/%d]: %s ---\n",
currentImageIndex + 1, totalImages, filename.c_str());
// 2. Fetch and draw
fetchAndDisplayImage(urlString.c_str());
// 3. Move to next image
currentImageIndex++;
if (currentImageIndex >= totalImages) {
currentImageIndex = 0; // Loop back
}
delay(3000);
}
// ==========================================
// 5. Download and Decode Logic
// ==========================================
void fetchAndDisplayImage(const char* url) {
HTTPClient http;
Serial.println("Downloading image...");
Serial.println(url);
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
int len = http.getSize();
Serial.printf("Image size: %d bytes\n", len);
uint8_t *imgBuffer = (uint8_t *)malloc(len);
if (imgBuffer) {
WiFiClient *stream = http.getStreamPtr();
size_t bytesRead = 0;
while (http.connected() && (len > 0 || len == -1)) {
size_t size = stream->available();
if (size) {
int c = stream->readBytes(&imgBuffer[bytesRead], size);
bytesRead += c;
if (len > 0) len -= c;
}
delay(1);
}
Serial.println("Download complete. Decoding...");
if (jpeg.openRAM(imgBuffer, bytesRead, JPEGDraw)) {
jpeg.decode(0, 0, 0);
jpeg.close();
Serial.println("Image displayed successfully.");
} else {
Serial.println("Failed to open JPEG. Ensure it is a standard Baseline JPEG (not Progressive).");
}
free(imgBuffer);
} else {
Serial.println("Error: Not enough heap memory to hold the image.");
}
} else {
Serial.printf("HTTP GET failed. Error code: %d\n", httpCode);
}
http.end();
}
// ==========================================
// Download and Parse jpg_list.txt
// ==========================================
void fetchImageList() {
HTTPClient http;
String listUrl = "http://" + server_ip.toString() + ":8000/jpg_list.txt";
Serial.println("Downloading image list...");
http.begin(listUrl);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
int startIndex = 0;
int endIndex = payload.indexOf('\n');
while (endIndex != -1 && totalImages < 64) {
String line = payload.substring(startIndex, endIndex);
line.trim(); // Remove \r or spaces
if (line.length() > 0) {
imageList[totalImages] = line;
Serial.printf("Found file: %s\n", imageList[totalImages].c_str());
totalImages++;
}
startIndex = endIndex + 1;
endIndex = payload.indexOf('\n', startIndex);
}
// Handle the last line if it doesn't end with a newline
if (startIndex < payload.length() && totalImages < 64) {
String lastLine = payload.substring(startIndex);
Serial.printf("%i : ", startIndex);
Serial.println(lastLine);
lastLine.trim();
if (lastLine.length() > 0) {
imageList[totalImages] = lastLine;
totalImages++;
}
}
Serial.printf("Total images found: %d\n", totalImages);
} else {
Serial.printf("Failed to get list. Error: %d\n", httpCode);
}
http.end();
}
Steps to Test:
Test images:
All images were generated png with Gemini, then resized and converted to a
suitable 480*480 JPG format using FFmpeg.
Create a bat file convert_jpg.bat in the folder that the contains png images.
Re-sized jpg will be save in jpg_new sub-folder, named
<original_name>_new.jpg.
convert_jpg.bat
@echo off
setlocal enabledelayedexpansion
:: 1. Create the output directory if it doesn't exist
if not exist "jpg_new" (
mkdir "jpg_new"
)
:: 2. Loop through all .jpg files
for %%f in (*.png) do (
echo Processing: "%%f"
ffmpeg -y -i "%%f" -vf "scale=480:480:force_original_aspect_ratio=decrease" -pix_fmt yuv420p -q:v 7 -map_metadata -1 "jpg_new\%%~nf_new.jpg"
)
echo.
echo Conversion completed! Please check the "jpg_new" folder.
pause
Generate jpg_list.txt.
switch to the jpg_new sub-folder, run the DOS command:
dir *.jpg /b /on > jpg_list.txt
WiFi and server setup
Enable Windows 11 Mobile Hotspot, make sure it work in 2.4 GHz band. ESP32-S3 support 2.4 GHz only band.
Run Python-based HTTP server:
python -m http.server
Comments
Post a Comment