Stream Images from ESP32-CAM to MQTTfy Dashboard via MQTT
November 21, 2025
The ESP32-CAM is a powerful, low-cost microcontroller with an integrated camera, making it a perfect choice for a wide range of IoT projects, from home security to remote monitoring. When combined with the MQTT protocol and a real-time dashboard like MQTTfy, you can create a surprisingly capable live image streaming system.
This comprehensive guide will walk you through the entire process: setting up your Arduino IDE, programming the ESP32-CAM to capture images, encoding them, and publishing them over MQTT. We'll then show you how to configure an Image Widget in MQTTfy to display your live feed.
Core Concepts: How It Works
Before diving into the code, let's understand the data flow. The browser-based MQTTfy dashboard cannot directly receive a raw image file. Instead, we must encode the image into a text format that can be sent within a standard MQTT message. The best format for this is a Base64 Data URI.
The process is as follows:
- The ESP32-CAM captures an image from its camera sensor.
- The firmware encodes the raw image data (JPEG) into a Base64 string.
- This string is prepended with
data:image/jpeg;base64,to form a complete Data URI. - The entire Data URI string is published as the payload of an MQTT message to a specific topic.
- The Image Widget in MQTTfy, subscribed to that topic, receives the Data URI and natively renders the image in your browser.
Prerequisites
- Hardware: An ESP32-CAM board and an FTDI programmer to upload code.
- Software: Arduino IDE with the ESP32 board manager installed.
- Libraries:
PubSubClient: For MQTT communication.Arduino_JSON: For handling JSON data (optional but good practice).base64.h: For encoding the image.
- MQTT Broker: A publicly accessible MQTT broker that supports WebSockets (WSS). For testing, you can use
broker.hivemq.comon port8884. - MQTTfy Dashboard: An active MQTTfy dashboard to display the images.
Step 1: Setting Up the Arduino IDE
If you haven't already, make sure your Arduino IDE is set up for ESP32 development.
- In Arduino IDE, go to
File > Preferences. - Add the following URL to "Additional Boards Manager URLs":
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Go to
Tools > Board > Boards Manager..., search for "esp32", and install the package by Espressif Systems. - Install the
PubSubClientlibrary fromTools > Manage Libraries....
For Base64 encoding, we will use a simple and effective header file. Create a new tab in your Arduino sketch named base64.h and paste the following code into it:
// In a new tab named "base64.h"
#ifndef BASE64_H
#define BASE64_H
const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
int base64_encode(char *output, const char *input, int inputLen) {
int i = 0, j = 0;
int encLen = 0;
unsigned char a3[3];
unsigned char a4[4];
while(inputLen--) {
a3[i++] = *(input++);
if(i == 3) {
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = a3[2] & 0x3f;
for(i = 0; i < 4; i++) {
output[encLen++] = b64_alphabet[a4[i]];
}
i = 0;
}
}
if(i) {
for(j = i; j < 3; j++) {
a3[j] = '\0';
}
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = a3[2] & 0x3f;
for(j = 0; j < i + 1; j++) {
output[encLen++] = b64_alphabet[a4[j]];
}
while((i++ < 3)) {
output[encLen++] = '=';
}
}
output[encLen] = '\0';
return encLen;
}
#endif // BASE64_H
Step 2: The Full ESP32-CAM Arduino Code
Now, in your main sketch file, paste the following code. Remember to update the WiFi and MQTT configuration variables with your own credentials.
#include "WiFi.h"
#include "esp_camera.h"
#include "PubSubClient.h"
#include "base64.h"
// --- WiFi & MQTT Configuration ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
const char* mqtt_image_topic = "esp32/cam/image";
// --- Camera Pin Definitions (AI-Thinker Model) ---
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
WiFiClient wifiClient;
PubSubClient client(wifiClient);
unsigned long lastCaptureTime = 0;
const long captureInterval = 5000; // Capture image every 5 seconds
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
// Initialize Camera
initCamera();
// Connect to Wi-Fi
connectWifi();
// Configure MQTT
client.setServer(mqtt_server, mqtt_port);
}
void loop() {
if (!client.connected()) {
reconnectMQTT();
}
client.loop();
unsigned long currentMillis = millis();
if (currentMillis - lastCaptureTime >= captureInterval) {
lastCaptureTime = currentMillis;
captureAndPublishImage();
}
}
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;
// For better quality, use UXGA. For faster streaming, use smaller resolutions.
config.frame_size = FRAMESIZE_VGA; // 640x480
config.jpeg_quality = 12; // 0-63, lower number means higher quality
config.fb_count = 1;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
}
void connectWifi() {
Serial.print("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
}
void reconnectMQTT() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32-CAM-Client-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void captureAndPublishImage() {
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
Serial.printf("Picture file name: /image.jpg, size: %zu bytes\n", fb->len);
// Allocate memory for the Base64-encoded image
// Base64 output is approx. 4/3 the size of the input
char *base64_data = (char*) malloc((fb->len * 4 / 3) + 4);
if (!base64_data) {
Serial.println("Malloc for base64 data failed");
esp_camera_fb_return(fb);
return;
}
// Encode the image
int encoded_len = base64_encode(base64_data, (const char*)fb->buf, fb->len);
// The full data URI prefix
const char* data_uri_prefix = "data:image/jpeg;base64,";
// We need to publish in chunks due to MQTT packet size limits (default 128 bytes in PubSubClient)
client.beginPublish(mqtt_image_topic, strlen(data_uri_prefix) + encoded_len, false);
client.print(data_uri_prefix);
// Send the Base64 data in chunks
int chunkSize = 256;
for (int i = 0; i < encoded_len; i += chunkSize) {
client.write((uint8_t*)(base64_data + i), min(chunkSize, encoded_len - i));
}
client.endPublish();
free(base64_data);
esp_camera_fb_return(fb);
Serial.println("Image published to MQTT");
}
Step 3: Configure MQTTfy Dashboard
The final step is to set up a widget in MQTTfy to display the image stream.
- Add an Image Widget: In your MQTTfy dashboard, click to add a new widget and select the Image Display widget.
- Configure the Data Source:
- Set the Data Source Type to
MQTT. - Enter the details for your MQTT broker (e.g.,
broker.hivemq.com, port8884, protocolwss). - In the MQTT Topic field, enter the exact topic you defined in the Arduino code:
esp32/cam/image.
- Set the Data Source Type to
- Configure Image Settings:
- Ensure the Image Source Type is set to
MQTT (Base64 Data URI). This is crucial for the widget to correctly interpret the incoming message.
- Ensure the Image Source Type is set to
- Save the Widget: Save the configuration, and the widget will appear on your dashboard.
Within a few seconds of your ESP32-CAM connecting and publishing its first image, you should see the live feed appear directly in your MQTTfy dashboard, updating automatically at the interval you set.
This setup provides a powerful and flexible foundation. You can now expand on this by adding buttons in your dashboard to trigger a photo on demand, integrate multiple cameras on different topics, or even pass the image data to an AI widget for analysis.