
ESP32 connected to multiple sensors with cloud dashboard
Introduction
The ESP32 has revolutionized IoT development with its built-in Wi-Fi and Bluetooth capabilities, dual-core processor, and extensive GPIO options. In this comprehensive tutorial, we'll build a complete IoT sensor monitoring system that sends data to the cloud and provides real-time visualization.
By the end of this project, you'll have a fully functional IoT system that can:
- Read data from multiple sensors (temperature, humidity, pressure)
- Connect to Wi-Fi networks automatically
- Send data to ThingSpeak and Firebase
- Display real-time data on web dashboards
- Handle network disconnections gracefully
What You'll Learn
HTTP requests, JSON handling, cloud APIs, sensor interfacing, WiFi management, and building responsive IoT systems with proper error handling.
Hardware Requirements
Setting Up the Hardware
The wiring is straightforward, but proper connections are crucial for reliable operation:
DHT22 Connections
// DHT22 Wiring
// VCC -> 3.3V
// GND -> GND
// DATA -> GPIO 4 (with 10kΩ pull-up resistor)
#include "DHT.h"
#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
BMP280 I2C Connections
// BMP280 I2C Wiring
// VCC -> 3.3V
// GND -> GND
// SDA -> GPIO 21
// SCL -> GPIO 22
#include
Adafruit_BMP280 bmp;
Setting Up Cloud Services
ThingSpeak Setup
ThingSpeak is perfect for IoT data logging and visualization. Here's how to set it up:
- Create a free account at ThingSpeak.com
- Create a new channel with fields: Temperature, Humidity, Pressure, Light
- Note down your Channel ID and Write API Key
Firebase Setup (Optional)
For more advanced features and real-time databases:
- Create a Firebase project
- Set up Realtime Database
- Configure authentication rules
- Get your database URL
Complete ESP32 Code
Here's the complete code with error handling and multiple sensor support:
#include
#include
#include
#include "DHT.h"
#include
// WiFi credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// ThingSpeak settings
const char* thingspeakServer = "api.thingspeak.com";
const char* writeAPIKey = "YOUR_WRITE_API_KEY";
// Sensor pins and objects
#define DHT_PIN 4
#define LDR_PIN A0
DHT dht(DHT_PIN, DHT22);
Adafruit_BMP280 bmp;
// Timing variables
unsigned long lastSensorRead = 0;
unsigned long lastCloudUpdate = 0;
const unsigned long sensorInterval = 2000; // Read sensors every 2 seconds
const unsigned long cloudInterval = 15000; // Update cloud every 15 seconds
// Sensor data structure
struct SensorData {
float temperature;
float humidity;
float pressure;
int lightLevel;
bool valid;
};
SensorData currentData;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("ESP32 IoT Sensor System Starting...");
// Initialize sensors
initializeSensors();
// Connect to WiFi
connectToWiFi();
Serial.println("System ready!");
}
void loop() {
// Check WiFi connection
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi disconnected. Reconnecting...");
connectToWiFi();
}
// Read sensors at regular intervals
if (millis() - lastSensorRead >= sensorInterval) {
readAllSensors();
lastSensorRead = millis();
}
// Update cloud at regular intervals
if (millis() - lastCloudUpdate >= cloudInterval) {
if (currentData.valid) {
sendToThingSpeak();
sendToFirebase(); // Optional
}
lastCloudUpdate = millis();
}
delay(100); // Small delay to prevent watchdog issues
}
void initializeSensors() {
Serial.println("Initializing sensors...");
// Initialize DHT22
dht.begin();
// Initialize BMP280
if (!bmp.begin(0x76)) { // Try default address first
if (!bmp.begin(0x77)) { // Try alternate address
Serial.println("Could not find BMP280 sensor!");
} else {
Serial.println("BMP280 initialized on address 0x77");
}
} else {
Serial.println("BMP280 initialized on address 0x76");
}
// Configure BMP280 settings
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
Serial.println("Sensors initialized!");
}
void connectToWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.println("WiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println();
Serial.println("Failed to connect to WiFi!");
}
}
void readAllSensors() {
currentData.valid = true;
// Read DHT22
currentData.temperature = dht.readTemperature();
currentData.humidity = dht.readHumidity();
// Read BMP280
currentData.pressure = bmp.readPressure() / 100.0F; // Convert to hPa
// Read LDR
int ldrRaw = analogRead(LDR_PIN);
currentData.lightLevel = map(ldrRaw, 0, 4095, 0, 100);
// Validate readings
if (isnan(currentData.temperature) || isnan(currentData.humidity)) {
Serial.println("Failed to read from DHT sensor!");
currentData.valid = false;
}
// Print readings
if (currentData.valid) {
Serial.println("--- Sensor Readings ---");
Serial.printf("Temperature: %.2f°C\n", currentData.temperature);
Serial.printf("Humidity: %.2f%%\n", currentData.humidity);
Serial.printf("Pressure: %.2f hPa\n", currentData.pressure);
Serial.printf("Light Level: %d%%\n", currentData.lightLevel);
Serial.println("----------------------");
}
}
void sendToThingSpeak() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected. Cannot send to ThingSpeak.");
return;
}
HTTPClient http;
String url = "http://api.thingspeak.com/update?api_key=";
url += writeAPIKey;
url += "&field1=" + String(currentData.temperature);
url += "&field2=" + String(currentData.humidity);
url += "&field3=" + String(currentData.pressure);
url += "&field4=" + String(currentData.lightLevel);
http.begin(url);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
String response = http.getString();
Serial.println("ThingSpeak Response: " + response);
} else {
Serial.println("Error sending to ThingSpeak: " + String(httpResponseCode));
}
http.end();
}
void sendToFirebase() {
// Optional: Implement Firebase integration
// This would involve JSON payload creation and HTTP POST
Serial.println("Firebase integration - implement if needed");
}
Building a Web Dashboard
Create a simple HTML dashboard to visualize your data:
<!DOCTYPE html>
<html>
<head>
<title>ESP32 IoT Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.sensor-card {
border: 1px solid #ddd;
padding: 20px;
margin: 10px;
border-radius: 8px;
display: inline-block;
width: 200px;
}
.sensor-value { font-size: 2em; color: #2196F3; }
</style>
</head>
<body>
<h1>ESP32 IoT Sensor Dashboard</h1>
<div class="sensor-card">
<h3>Temperature</h3>
<div class="sensor-value" id="temp">--</div>
<p>°C</p>
</div>
<div class="sensor-card">
<h3>Humidity</h3>
<div class="sensor-value" id="humidity">--</div>
<p>%</p>
</div>
<div class="sensor-card">
<h3>Pressure</h3>
<div class="sensor-value" id="pressure">--</div>
<p>hPa</p>
</div>
<script>
// Fetch data from ThingSpeak API
async function fetchSensorData() {
try {
const response = await fetch('https://api.thingspeak.com/channels/YOUR_CHANNEL_ID/feeds/last.json');
const data = await response.json();
document.getElementById('temp').textContent = parseFloat(data.field1).toFixed(1);
document.getElementById('humidity').textContent = parseFloat(data.field2).toFixed(1);
document.getElementById('pressure').textContent = parseFloat(data.field3).toFixed(1);
} catch (error) {
console.error('Error fetching data:', error);
}
}
// Update data every 30 seconds
setInterval(fetchSensorData, 30000);
fetchSensorData(); // Initial load
</script>
</body>
</html>
Important Notes
Always implement proper error handling, use secure connections (HTTPS) in production, and consider power management for battery-operated devices. Monitor your cloud service usage to avoid unexpected charges.
Troubleshooting Common Issues
Problem | Possible Cause | Solution |
---|---|---|
WiFi won't connect | Wrong credentials or weak signal | Double-check SSID/password, move closer to router |
Sensor readings are NaN | Loose connections or faulty sensor | Check wiring, add pull-up resistors |
ThingSpeak not updating | Wrong API key or rate limiting | Verify API key, respect update intervals |
ESP32 keeps resetting | Power issues or watchdog timer | Use proper power supply, add delays in loops |
Next Steps and Improvements
Once you have the basic system working, consider these enhancements:
- Mobile App: Build a React Native or Flutter app for mobile monitoring
- Alerts: Implement email/SMS notifications for threshold violations
- Data Analysis: Add machine learning for pattern recognition
- Multiple Devices: Scale to multiple ESP32 nodes
- Local Storage: Add SD card logging for offline capability
- OTA Updates: Enable over-the-air firmware updates
Conclusion
Building an ESP32 IoT sensor system with cloud integration opens up endless possibilities for monitoring and automation. The combination of powerful hardware, cloud connectivity, and real-time visualization makes it perfect for both learning and practical applications.
This project demonstrates core IoT concepts while providing a solid foundation for more complex systems. The modular code structure makes it easy to add new sensors or change cloud providers as needed.