ESP32 BME280 Grafana Tutorial: Monitor Temperature & Humidity

The ESP32 is a powerful microcontroller with built-in Wi-Fi, making it an excellent choice for IoT projects. In this guide, we will walk you through how to collect temperature data from a BME280 sensor, send it to the cloud using Telemetry Harbor, and visualize it using a Grafana dashboard.

ESP32 BME280 Grafana Tutorial: Monitor Temperature & Humidity

Imagine having real-time insights into temperature changes, accessible from anywhere in the world. With an ESP32 and a BME280 sensor, you can build a smart, cloud-connected monitoring system that logs environmental data and visualizes it dynamically.

In this project, you'll configure an ESP32 to read temperature data from a BME280 sensor and transmit it to Telemetry Harbor, a powerful IoT data platform. From there, the data is stored, processed, and displayed on a Grafana dashboard, giving you a live view of temperature trends. Whether you're tracking climate conditions, monitoring sensitive equipment, or just experimenting with IoT, this setup provides a scalable foundation for automation, alerts, and advanced analytics.

By the end of this guide, you'll have a fully functional system that not only logs temperature readings but also lays the groundwork for expanding your IoT network with more sensors, automated triggers, and cloud-based intelligence. Let's dive in!

Hardware Requirements

Software Requirements

  • Arduino IDE (1.8.13 or newer) or PlatformIO
  • Required libraries: 
    • Adafruit BME280 Library 
    • Adafruit Unified Sensor 
    • WiFi Library (built into ESP32 core) 
    • HTTPClient Library (built into ESP32 core)
  • Telemetry Harbor account (free tier available)

Step 1: Setting Up Your Development Environment

Installing ESP32 Board Support

  1. Open Arduino IDE
  2. Go to File > Preferences
  3. Add the following URL to the "Additional Boards Manager URLs" https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  4. Go to Tools > Board > Boards Manager
  5. Search for "ESP32" and install the ESP32 by Espressif Systems

Installing Required Libraries

  1. Go to Sketch > Include Library > Manage Libraries
  2. Search for and install:
    • Adafruit BME280 Library
    • Adafruit Unified Sensor

Step 2: Wiring the BME280 Sensor to ESP32

The BME280 uses the I2C communication protocol, which requires just four connections:

BME280 PinESP32 PinFunction
VCC3.3VPower
GNDGNDGround
SDAGPIO 21Data
SCLGPIO 22Clock

Step 3: Setting Up Telemetry Harbor

Telemetry Harbor provides a straightforward platform for collecting, storing, and visualizing IoT sensor data. Follow these steps to set up your account:

  1. Create an Account:
  2. Create a New Harbor:
    • From your dashboard, click Create New Harbor
    • Name it something descriptive like "ESP32_Environmental_Monitor"
    • Select the harbor type as General and Specification Free
  3. Generate an API Key:
    • Navigate to "View Details" for your created harbor
    • Click "View API Key"
    • Copy and save your API Key securely - you'll need it for your ESP32 code
    • Note your Harbor ID from the API Endpoint

Step 4: Uploading and Testing the Code

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h> // For NTP time

// WiFi credentials
const char* ssid = "WIFI_SSID";
const char* password = "WIFI_PASSWORD";

// Telemetry Harbor API info
const String apiUrl = "https://telemetryharbor.com/api/v1/ingest/ingest/Harbor_ID";
const String apiKey = "API_KEY";
const String shipId = "Living Room";

// BME280 setup
Adafruit_BME280 bme; // I2C
#define SEALEVELPRESSURE_HPA (1013.25)

// LED setup
const int ledPin = 2; // Use built-in LED on most ESP32 boards (usually GPIO 2)

// NTP setup
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0; // Adjust this for your timezone offset
const int daylightOffset_sec = 0;

// Time management
unsigned long previousMillis = 0;
const unsigned long intervalMillis = 60000; // 1-second interval

// Function to flash the LED
void flashLED(int times, int delayMs) {
  for (int i = 0; i < times; i++) {
    digitalWrite(ledPin, HIGH);
    delay(delayMs);
    digitalWrite(ledPin, LOW);
    delay(delayMs);
  }
}

// Function to connect to Wi-Fi
void connectWiFi() {
  Serial.println("Connecting to WiFi...");
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting...");
  }
  Serial.println("Connected to WiFi!");
  flashLED(3, 200); // Flash LED 3 times quickly
}

// Function to initialize time using NTP
void initTime() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from NTP server.");
    while (1); // Stay here if time sync fails
  }
  Serial.println("Time synchronized successfully.");
}

void setup() {
  Serial.begin(115200);

  pinMode(ledPin, OUTPUT); // Set LED pin as output

  // Initialize BME280 sensor
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  // Connect to Wi-Fi and sync time
  connectWiFi();
  initTime();
}

void loop() {
  // Check Wi-Fi connection
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi(); // Reconnect if disconnected
  }

  // Measure elapsed time to push data every second
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= intervalMillis) {
    previousMillis = currentMillis;


    const float temperatureOffset = 0; // Adjust temperature by -3°C
    const float humidityOffset = 0;    // Adjust humidity by +7.2%
    // Read temperature and humidity
    float temperature = bme.readTemperature() + temperatureOffset;
    float humidity = bme.readHumidity() + humidityOffset;

    // Get current time in ISO8601 format
    time_t now = time(NULL);
    struct tm timeinfo;
    gmtime_r(&now, &timeinfo); // Convert to UTC time structure

    char timeBuffer[30];
    strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%dT%H:%M:%SZ", &timeinfo); // Format time

    // Create JSON payload for temperature
    String tempJsonPayload = "{";
    tempJsonPayload += "\"time\": \"" + String(timeBuffer) + "\",";
    tempJsonPayload += "\"ship_id\": \"" + shipId + "\",";
    tempJsonPayload += "\"cargo_id\": \"Temperature\",";
    tempJsonPayload += "\"value\": " + String(temperature, 2);
    tempJsonPayload += "}";

    // Create JSON payload for humidity
    String humidityJsonPayload = "{";
    humidityJsonPayload += "\"time\": \"" + String(timeBuffer) + "\",";
    humidityJsonPayload += "\"ship_id\": \"" + shipId + "\",";
    humidityJsonPayload += "\"cargo_id\": \"Humidity\",";
    humidityJsonPayload += "\"value\": " + String(humidity, 2);
    humidityJsonPayload += "}";

    // Send temperature data to API
    HTTPClient http;
    http.begin(apiUrl);
    http.addHeader("X-API-Key", apiKey);
    http.addHeader("Content-Type", "application/json");

    int tempResponseCode = http.POST(tempJsonPayload);
    if (tempResponseCode > 0) {
      Serial.println("Temperature data sent successfully! Response:");
      Serial.println(http.getString());
      flashLED(1, 300); // Flash LED for successful API request
    } else {
      Serial.print("Error sending temperature data. HTTP Response code: ");
      Serial.println(tempResponseCode);
    }
    http.end();

    // Send humidity data to API
    http.begin(apiUrl); // Reinitialize HTTPClient for humidity request
    http.addHeader("X-API-Key", apiKey);
    http.addHeader("Content-Type", "application/json");

    int humidityResponseCode = http.POST(humidityJsonPayload);
    if (humidityResponseCode > 0) {
      Serial.println("Humidity data sent successfully! Response:");
      Serial.println(http.getString());
      flashLED(1, 300); // Flash LED for successful API request
    } else {
      Serial.print("Error sending humidity data. HTTP Response code: ");
      Serial.println(humidityResponseCode);
    }
    http.end();
  }
}
  1. Select the Correct Board:
    • Go to Tools > Board and select your ESP32 board model
    • Select the correct COM port under Tools > Port
  2. Configure the Code:
    • Replace the WiFi SSID and password with your network credentials
    • Update the Telemetry Harbor API URL and API key with your account details
    • Adjust the sensor reading interval if needed (default is 60 seconds)
  3. Upload the Code:
    • Click the Upload button (→) in Arduino IDE
    • Wait for the compilation and upload to complete
  4. Verify Operation:
    • Open the Serial Monitor (Tools > Serial Monitor) set to 115200 baud
    • Check for successful WiFi connection and sensor initialization
    • Confirm data is being sent to Telemetry Harbor

Step 5: Visualizing Data with Telemetry Harbor

Once your ESP32 begins transmitting data, you can create powerful visualizations in Telemetry Harbor through its integrated Grafana dashboards:

  1. Access Grafana Credentials:
    • Go back to the Harbor Details page
    • Copy the Grafana Password shown on this page
    • Click on the Grafana Endpoint link provided
  2. Login to Grafana:
    • Use your Grafana Username (this will be the same as your Telemetry Harbor email)
    • Enter the Grafana Password you copied earlier
  3. Navigate to Dashboards:
    • In the left sidebar, click on Dashboards
    • Select the Comprehensive Telemetry Dashboard (this is the demo dashboard provided by Telemetry Harbor)
  4. Configure Your Dashboard View:
    • Choose your data source (which will be your harbor)
    • Use the filters to view data based on ship_id and cargo_id
    • Select appropriate time ranges to view your sensor history

Conclusion

Your ESP32 is now up and running with the BME280 sensor, streaming live temperature data to Telemetry Harbor. With this setup, you can keep an eye on environmental changes in real time. Want to take it further? Try adding more sensors, setting up alerts for temperature spikes, or even building a full monitoring system. The possibilities are endless.