Data Collection with an IoT Device
Design a sketch that simulates a real-world natural system,
drawing inspiration from physics, biology, or other natural sciences.
Rainforest Data Visualization
This time, I am completing the assignment while back in Vietnam, between January and March—a period when humidity levels are high due to frequent rain. That's why I want to create an IoT device that collects data on room temperature and humidity. I plan to use this data to visualize the rainforest environment.
Connecting the DHT11 Sensor to ESP32
This project involves integrating a DHT11 temperature and humidity sensor with an ESP32 microcontroller to collect humidity and temperature data.
Humidity Graph
Temp Graph
#include "config.h"
#include <WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include "DHT.h"

// Pin & Sensor Configuration
#define DHTPIN 15
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// Wi-Fi & MQTT Setup
WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
Adafruit_MQTT_Publish tempFeed(&mqtt, AIO_USERNAME "/feeds/temperature");
Adafruit_MQTT_Publish humidFeed(&mqtt, AIO_USERNAME "/feeds/humidity");

void setup() {
  Serial.begin(115200);
  WiFi.begin(WLAN_SSID, WLAN_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("\nConnected to WiFi");
  dht.begin();
}

void loop() {
  // Ensure MQTT connection
  if (!mqtt.connected()) {
    Serial.println("Connecting to MQTT...");
    while (mqtt.connect() != 0) {
      Serial.println("Retrying MQTT...");
      delay(5000);
    }
    Serial.println("MQTT Connected.");
  }

  // Read DHT11 sensor
  float temp = dht.readTemperature();
  float humid = dht.readHumidity();
  if (isnan(temp) || isnan(humid)) {
    Serial.println("Failed to read DHT sensor!");
    return;
  }

  // Publish sensor data
  Serial.printf("Temp: %.2f°C | Humidity: %.2f%%\n", temp, humid);
  tempFeed.publish(temp);
  humidFeed.publish(humid);

  delay(10000); // Wait 10 seconds before next reading
}
How the enviroment work
The rain conditions are determined by temperature and humidity. When the temperature rises above 24°C and humidity stays below 60%, there is no rain. However, if the temperature drops below 20°C and humidity exceeds 60%, rain begins to fall.
Rain occurs when the temperature decreases and humidity increases.
Rain will not occurs when the temperature increased and humidity decresed.
let baseTempFeedURL = "https://io.adafruit.com/api/v2/thanht0m/feeds/temperature-feed"
;
let baseHumidityFeedURL = "https://io.adafruit.com/api/v2/thanht0m/feeds/humidity-feed"
;

let terrain = [];
let rainDrops = [];
let waterLevel = 0;
let terrainWidth = 800;
let terrainHeight = 600;
let noiseScale = 0.02;

let temperatureData = []; // Array to store temperature data
let humidityData = []; // Array to store humidity data
let currentIndex = 0; // Index to track the current data point

function setup() {
  createCanvas(terrainWidth, terrainHeight);
  generateTerrain();

  // Fetch historical data for the specified date range
  fetchHistoricalData("2025-02-12T11:11:00Z");
}

function draw() {
  // Draw sky based on temperature
  drawSky();

  // Draw terrain
  drawTerrain();

  // Simulate rain based on humidity
  if (humidityData.length > 0 && humidityData[currentIndex].value >= 60) {
    simulateRain();
  }

  // Draw river
  drawRiver();

  // Update water level
  updateWaterLevel();

  // Draw vegetation
  drawVegetation();

  // Display current data
  displayCurrentData();
}

function generateTerrain() {
  for (let x = 0; x < terrainWidth; x++) {
    let noiseVal = noise(x * noiseScale);
    let y = map(noiseVal, 0, 1, terrainHeight / 2, terrainHeight);
    terrain[x] = y;
  }
}

function drawSky() {
  if (temperatureData.length > 0) {
    let temperature = temperatureData[currentIndex].value;
    if (temperature > 20) {
      // Sunny sky
      background(135, 206, 235); // Light blue
    } else {
      // Cloudy sky
      background(169, 169, 169); // Gray
    }
  }
}

function drawTerrain() {
  fill(34, 139, 34); // Green for terrain
  noStroke();
  beginShape();
  vertex(0, terrainHeight);
  for (let x = 0; x < terrainWidth; x++) {
    vertex(x, terrain[x]);
  }
  vertex(terrainWidth, terrainHeight);
  endShape(CLOSE);
}

function simulateRain() {
  // Add new raindrops
  if (frameCount % 5 === 0) {
    rainDrops.push(new RainDrop(random(width), 0));
  }

  // Update and display raindrops
  for (let i = rainDrops.length - 1; i >= 0; i--) {
    rainDrops[i].fall();
    rainDrops[i].display();

    // Remove raindrops that hit the ground
    if (rainDrops[i].y > terrain[rainDrops[i].x]) {
      rainDrops.splice(i, 1);
      waterLevel += 0.01; // Increase water level
    }
  }
}

function drawRiver() {
  fill(0, 119, 190); // Blue for river
  noStroke();
  rect(0, terrainHeight - waterLevel, width, waterLevel);
}

function updateWaterLevel() {
  // Gradually decrease water level over time
  waterLevel = max(0, waterLevel - 0.005);
}

function drawVegetation() {
  fill(0, 100, 0); // Dark green for vegetation
  noStroke();
  for (let x = 0; x < width; x += 20) {
    let y = terrain[x];
    let treeHeight = random(10, 30);
    rect(x - 2, y - treeHeight, 4, treeHeight);
  }
}

function displayCurrentData() {
  if (temperatureData.length > 0 && humidityData.length > 0) {
    let currentTemp = temperatureData[currentIndex].value;
    let currentHumidity = humidityData[currentIndex].value;
    let currentTime = new Date(temperatureData[currentIndex].created_at).toLocaleString();

    fill(255);
    textSize(16);
    textAlign(LEFT, TOP);
    text(`Date: ${currentTime}`, 10, 10);
    text(`Temperature: ${currentTemp}°C`, 10, 30);
    text(`Humidity: ${currentHumidity}%`, 10, 50);
  }
}

class RainDrop {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.speed = random(2, 5);
  }

  fall() {
    this.y += this.speed;
  }

  display() {
    fill(0, 0, 255); // Blue for raindrops
    noStroke();
    ellipse(this.x, this.y, 2, 5);
  }
}

async function fetchHistoricalData(startTime, endTime) {
  try {
    // Fetch temperature data
    let tempResponse = await fetch(
      `${baseTempFeedURL}/data?start_time=${startTime}&end_time=${endTime}`,
      { headers: { "X-AIO-Key": aioKey } }
    );
    if (!tempResponse.ok) throw new Error(`HTTP error! status: ${tempResponse.status}`);
    temperatureData = await tempResponse.json();

    // Fetch humidity data
    let humidityResponse = await fetch(
      `${baseHumidityFeedURL}/data?start_time=${startTime}&end_time=${endTime}`,
      { headers: { "X-AIO-Key": aioKey } }
    );
    if (!humidityResponse.ok) throw new Error(`HTTP error! status: ${humidityResponse.status}`);
    humidityData = await humidityResponse.json();

    console.log("Temperature Data:", temperatureData);
    console.log("Humidity Data:", humidityData);

    // Start cycling through the data
    setInterval(updateDataIndex, 1000); // Update data every second
  } catch (error) {
    console.error("Error fetching historical data:", error);
  }
}

function updateDataIndex() {
  if (currentIndex < temperatureData.length - 1 && currentIndex < humidityData.length - 1) {
    currentIndex++;
  } else {
    currentIndex = 0; // Loop back to the start
  }
}