Shiome Diaz
Published

Plataforma Colaborativa de Datos Ambientales con IoT y Machi

Sistema IoT que recopila datos ambientales con sensores físicos conectados a Arduino y ESP32. Visualiza información en tiempo real y predice

IntermediateWork in progress4 hours8
Plataforma Colaborativa de Datos Ambientales con IoT y Machi

Story

Read more

Code

App Web

JSX
import { useEffect, useState } from "react";
import mqtt from "mqtt";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";

// 🔹 Configuración de topics MQTT
const MQTT_BROKER = "wss://test.mosquitto.org:8081/mqtt";
const TOPICS = {
  temp: "esp32/dht22/temperatura",
  hum: "esp32/dht22/humedad",
  o2Raw: "esp32/o2/raw",
  o2Percent: "esp32/o2/percent",
  luxRaw: "esp32/temt6000/lux_raw",
  luxEst: "esp32/temt6000/lux_est",
  uvRaw: "esp32/uv/raw",
  uvPower: "esp32/uv/uWcm2",
  soundRaw: "esp32/sound/raw",
  soundDB: "esp32/sound/db_est",
  scdCo2: "esp32/scd41/co2",
  scdTemp: "esp32/scd41/temp",
  scdHum: "esp32/scd41/hum",
  co: "esp32/gas/co",
  no2: "esp32/gas/no2",
  etanol: "esp32/gas/etanol",
  nh3: "esp32/gas/nh3",
  ch4: "esp32/gas/ch4",
  bmpTemp: "esp32/bmp280/temp",
  bmpPresion: "esp32/bmp280/presion",
  ubicacion: "esp32/ubicacion",
  prediction: "esp32/model/prediction"
};

function App() {
  const [data, setData] = useState({
    temp: "---",
    hum: "---",
    o2Raw: "---",
    o2Percent: "---",
    luxRaw: "---",
    luxEst: "---",
    uvRaw: "---",
    uvPower: "---",
    soundRaw: "---",
    soundDB: "---",
    scdCo2: "---",
    scdTemp: "---",
    scdHum: "---",
    co: "---",
    no2: "---",
    etanol: "---",
    nh3: "---",
    ch4: "---",
    bmpTemp: "---",
    bmpPresion: "---",
    ubicacion: null,
    prediction: "---"
  });

  useEffect(() => {
    const client = mqtt.connect(MQTT_BROKER);

    client.on("connect", () => {
      Object.values(TOPICS).forEach((topic) => client.subscribe(topic));
    });

    client.on("message", (topic, message) => {
      const value = message.toString();
      setData((prev) => {
        const updated = { ...prev };
        for (const key in TOPICS) {
          if (TOPICS[key] === topic) {
            if (key === "ubicacion") {
              try {
                const [lat, lon] = value.split(",").map(parseFloat);
                updated[key] = { lat, lon };
              } catch (err) {
                console.error("❌ Error procesando ubicación:", value);
              }
            } else {
              updated[key] = value;
            }
          }
        }
        return updated;
      });
    });

    client.on("error", (err) => console.error("MQTT Error:", err));
    return () => client.end();
  }, []);

  const SensorCard = ({ title, value, subtitle, color = "#333" }) => (
    <div style={{
      background: "#f8f8f8",
      padding: "1rem",
      borderRadius: "10px",
      boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
      minWidth: "220px",
      flex: "1 1 250px",
      margin: "10px"
    }}>
      <h3 style={{ margin: 0, color: "#1e1e1e" }}>{title}</h3>
      <p style={{ fontSize: "24px", color, margin: "10px 0" }}><b>{value}</b></p>
      {subtitle && <p style={{ fontSize: "14px", color: "#666" }}>{subtitle}</p>}
    </div>
  );

  return (
    <div style={{ fontFamily: "Arial", padding: "20px", maxWidth: "1200px", margin: "auto", backgroundColor: "#1e1e1e", color: "#eee" }}>
      <h1 style={{ textAlign: "center" }}>🌡️ Monitor IoT - Sensores Ambientales</h1>
      <p style={{ textAlign: "center" }}>📡 Datos en tiempo real desde MQTT</p>

      {data.ubicacion && (
        <div style={{ height: "400px", margin: "20px 0", borderRadius: "10px", overflow: "hidden" }}>
          <MapContainer center={[data.ubicacion.lat, data.ubicacion.lon]} zoom={13} style={{ height: "100%", width: "100%" }}>
            <TileLayer
              attribution='&copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
              url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            />
            <Marker position={[data.ubicacion.lat, data.ubicacion.lon]}>
              <Popup>📍 Ubicación estimada de la ESP32</Popup>
            </Marker>
          </MapContainer>
        </div>
      )}

      <h2>🌐 Sensores Locales (ESP32)</h2>
      <div style={{ display: "flex", flexWrap: "wrap", justifyContent: "center" }}>
        <SensorCard title="🌡️ Temperatura (DHT22)" value={`${data.temp} °C`} subtitle="Sensor DHT22" ></SensorCard>
        <SensorCard title="💧 Humedad (DHT22)" value={`${data.hum} %`} subtitle="Sensor DHT22" ></SensorCard>
        <SensorCard title="🧪 O₂ crudo (ADC)" value={data.o2Raw} subtitle="Sensor Grove O₂" ></SensorCard>
        <SensorCard title="🔵 Oxígeno estimado" value={`${data.o2Percent} %`} color="#27ae60" subtitle="Sensor Grove O₂ (0–25%)" ></SensorCard>
        <SensorCard title="🔆 Luz cruda (ADC)" value={data.luxRaw} subtitle="Sensor TEMT6000" ></SensorCard>
        <SensorCard title="🌞 Lux estimado" value={`${data.luxEst} lux`} color="#2980b9" subtitle="Sensor TEMT6000" ></SensorCard>
        <SensorCard title="🌤️ UV crudo (ADC)" value={data.uvRaw} subtitle="Sensor UV Waveshare" ></SensorCard>
        <SensorCard title="☀️ UV estimado" value={`${data.uvPower} µW/cm²`} color="#8e44ad" subtitle="Waveshare UV (200–370 nm)" ></SensorCard>
        <SensorCard title="🎤 Sonido crudo (ADC)" value={data.soundRaw} subtitle="Sensor KY-037" ></SensorCard>
        <SensorCard title="🔊 Nivel de sonido" value={`${data.soundDB} dB`} color="#2c3e50" subtitle="Sensor KY-037 (no calibrado)" ></SensorCard>
        <SensorCard title="🧠 Predicción del modelo" value={data.prediction} color="#d35400" subtitle="Modelo de clasificación en ESP32" ></SensorCard>
      </div>

      <h2>🔗 Sensores Externos (Arduino)</h2>
      <div style={{ display: "flex", flexWrap: "wrap", justifyContent: "center" }}>
        <SensorCard title="🌿 CO₂ (SCD41)" value={`${data.scdCo2} ppm`} color="#16a085" subtitle="Sensor SCD41" ></SensorCard>
        <SensorCard title="🌡️ Temp SCD41" value={`${data.scdTemp} °C`} subtitle="Sensor SCD41" ></SensorCard>
        <SensorCard title="💧 Hum SCD41" value={`${data.scdHum} %`} subtitle="Sensor SCD41" ></SensorCard>
        <SensorCard title="🧪 CO" value={`${data.co} ppm`} subtitle="Multichannel Gas Sensor" ></SensorCard>
        <SensorCard title="🧪 NO₂" value={`${data.no2} ppm`} subtitle="Multichannel Gas Sensor" ></SensorCard>
        <SensorCard title="🧪 Etanol" value={`${data.etanol} ppm`} subtitle="Multichannel Gas Sensor" ></SensorCard>
        <SensorCard title="🧪 NH₃" value={`${data.nh3} ppm`} subtitle="Multichannel Gas Sensor" ></SensorCard>
        <SensorCard title="🧪 CH₄" value={`${data.ch4} ppm`} subtitle="Multichannel Gas Sensor" ></SensorCard>
        <SensorCard title="🌡️ Temp BMP280" value={`${data.bmpTemp} °C`} subtitle="Sensor BMP280" ></SensorCard>
        <SensorCard title="🔵 Presión BMP280" value={`${data.bmpPresion} hPa`} subtitle="Sensor BMP280" ></SensorCard>
      </div>
    </div>
  );
}

export default App;

Esp32

Arduino
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

// 🔹 Pines
#define DHTPIN 4
#define DHTTYPE DHT22
#define O2_SENSOR_PIN 34
#define TEMT6000_PIN 35
#define UV_PIN 36
#define SOUND_SENSOR_PIN 32

// 🔹 WiFi
const char* ssid = "JUAN CAMILO";
const char* password = "1085339978";

// 🔹 MQTT
const char* mqtt_server = "test.mosquitto.org";
const int mqtt_port = 1883;

DHT dht(DHTPIN, DHTTYPE);
WiFiClient espClient;
PubSubClient client(espClient);
HardwareSerial SerialNode(2);  // UART2 para Arduino

bool ubicacionObtenida = false;
String ubicacionStr = ""; // Se guarda una sola vez

// 🔹 Lectura promediada para sonido
int readAverageSound(int pin, int muestras = 10) {
  long suma = 0;
  for (int i = 0; i < muestras; i++) {
    suma += analogRead(pin);
    delay(5);
  }
  return suma / muestras;
}

void setup() {
  Serial.begin(115200); // 🖥️ Monitor serial ESP32
  SerialNode.begin(9600, SERIAL_8N1, 16, 17); // 🧷 UART desde Arduino (RX=16)

  dht.begin();
  pinMode(O2_SENSOR_PIN, INPUT);
  pinMode(TEMT6000_PIN, INPUT);
  pinMode(UV_PIN, INPUT);
  pinMode(SOUND_SENSOR_PIN, INPUT);

  Serial.print("📶 Conectando a WiFi...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("\n✅ WiFi conectado con IP: " + WiFi.localIP().toString());

  client.setServer(mqtt_server, mqtt_port);
  reconnect();
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("🔄 Conectando a MQTT...");
    if (client.connect("ESP32_MainBoard")) {
      Serial.println("✅ Conectado a MQTT!");
    } else {
      Serial.print("❌ Error MQTT: ");
      Serial.println(client.state());
      delay(3000);
    }
  }
}

void obtenerUbicacionPorIP() {
  if (WiFi.status() == WL_CONNECTED && !ubicacionObtenida) {
    HTTPClient http;
    http.begin("http://ip-api.com/json");
    int httpCode = http.GET();

    if (httpCode == 200) {
      String payload = http.getString();
      DynamicJsonDocument doc(1024);
      deserializeJson(doc, payload);

      float lat = doc["lat"];
      float lon = doc["lon"];
      ubicacionStr = String(lat, 6) + "," + String(lon, 6);
      Serial.println("📍 Ubicación guardada: " + ubicacionStr);
      ubicacionObtenida = true;
    } else {
      Serial.println("⚠️ Error al obtener ubicación IP");
    }
    http.end();
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  if (!ubicacionObtenida) {
    obtenerUbicacionPorIP();
  }

  if (ubicacionObtenida) {
    client.publish("esp32/ubicacion", ubicacionStr.c_str());
    Serial.println("📤 MQTT → esp32/ubicacion: " + ubicacionStr);
  }

  // 🟢 DHT22
  float humedad = dht.readHumidity();
  float temperatura = dht.readTemperature();
  if (!isnan(humedad) && !isnan(temperatura)) {
    client.publish("esp32/dht22/temperatura", String(temperatura).c_str());
    client.publish("esp32/dht22/humedad", String(humedad).c_str());
    Serial.println("🌡️ Temp: " + String(temperatura) + " °C");
    Serial.println("💧 Hum:  " + String(humedad) + " %");
  } else {
    Serial.println("⚠️ Error al leer DHT22");
  }

  // 🧪 Sensor de Oxígeno Grove
  int o2Raw = analogRead(O2_SENSOR_PIN);
  float o2Voltage = (o2Raw / 4095.0) * 3.3;
  float o2Percent = (o2Voltage / 2.0) * 25.0;
  client.publish("esp32/o2/raw", String(o2Raw).c_str());
  client.publish("esp32/o2/percent", String(o2Percent, 2).c_str());
  Serial.println("🧪 O₂ crudo: " + String(o2Raw));
  Serial.println("🔵 O₂ estimado: " + String(o2Percent, 2) + " %");

  // 🔆 TEMT6000
  int luzCruda = analogRead(TEMT6000_PIN);
  float voltajeLuz = (luzCruda / 4095.0) * 3.3;
  float lux = voltajeLuz * 2000;
  client.publish("esp32/temt6000/lux_raw", String(luzCruda).c_str());
  client.publish("esp32/temt6000/lux_est", String((int)lux).c_str());
  Serial.println("🔆 Luz cruda: " + String(luzCruda));
  Serial.println("🌞 Lux estimado: " + String((int)lux));

  // ☀️ UV Sensor
  int uvRaw = analogRead(UV_PIN);
  float voltajeUV = (uvRaw / 4095.0) * 3.3;
  float uvPower = voltajeUV * 1000;
  client.publish("esp32/uv/raw", String(uvRaw).c_str());
  client.publish("esp32/uv/uWcm2", String((int)uvPower).c_str());
  Serial.println("🌤️ UV crudo: " + String(uvRaw));
  Serial.println("☀️ UV potencia: " + String((int)uvPower) + " µW/cm²");

  // 🔊 Sonido
  int sonidoRaw = readAverageSound(SOUND_SENSOR_PIN);
  float sonidoVolt = (sonidoRaw / 4095.0) * 3.3;
  float sonidoDB = sonidoVolt * 100;
  client.publish("esp32/sound/raw", String(sonidoRaw).c_str());
  client.publish("esp32/sound/db_est", String((int)sonidoDB).c_str());
  Serial.println("🎤 Sonido crudo: " + String(sonidoRaw));
  Serial.println("🔊 Estimación dB: " + String((int)sonidoDB));

  // 🔄 Lectura UART desde Arduino
  while (SerialNode.available()) {
    String dataLine = SerialNode.readStringUntil('\n');
    dataLine.trim();

    if (dataLine.startsWith("DATA")) {
      Serial.println("📩 Recibido de Arduino: " + dataLine);

      dataLine.remove(0, 5);  // eliminar "DATA,"
      int i = 0;
      const char* topics[] = {
        "esp32/scd41/co2", "esp32/scd41/temp", "esp32/scd41/hum",
        "esp32/gas/co", "esp32/gas/no2", "esp32/gas/etanol",
        "esp32/gas/nh3", "esp32/gas/ch4",
        "esp32/bmp280/temp", "esp32/bmp280/presion"
      };

      while (dataLine.length() > 0 && i < 10) {
        int sep = dataLine.indexOf(',');
        String value = (sep == -1) ? dataLine : dataLine.substring(0, sep);

        client.publish(topics[i], value.c_str());
        Serial.println("📤 MQTT → " + String(topics[i]) + ": " + value);

        if (sep == -1) break;
        dataLine.remove(0, sep + 1);
        i++;
      }
      Serial.println("✅ Datos Arduino publicados\n");
    }
  }

  delay(2500); // Espera entre lecturas
}

Arduino Uno

Arduino
#include <Wire.h>
#include "SensirionI2cScd4x.h"
#include "MutichannelGasSensor.h"
#include <Adafruit_BMP280.h>
#include <Adafruit_Sensor.h>

// Objetos de sensores
SensirionI2cScd4x scd4x;
Adafruit_BMP280 bmp; // Usa I2C por defecto

void setup() {
  Serial.begin(9600);
  Wire.begin();

  // 🟢 Inicializar SCD41 (CO2, temp, humedad)
  scd4x.begin(Wire, 0x62);
  scd4x.stopPeriodicMeasurement();
  scd4x.startPeriodicMeasurement();

  // 🟢 Inicializar sensor de gases multicanal
  gas.begin(0x04);  // Dirección I2C por defecto

  // 🟢 Inicializar BMP280
  if (!bmp.begin(0x77)) { // o 0x77 si tu módulo usa esa dirección
    Serial.println("❌ No se detecta el BMP280");
    while (1);
  }

  Serial.println("✅ Todos los sensores inicializados.");
}

void loop() {
  Serial.println("📡 Lectura de sensores:");

  // 🔹 Leer SCD41
  uint16_t co2;
  float temp, hum;
  if (!scd4x.readMeasurement(co2, temp, hum)) {
    Serial.print("🌿 CO₂: "); Serial.print(co2); Serial.println(" ppm");
    Serial.print("🌡️ Temp SCD41: "); Serial.print(temp); Serial.println(" °C");
    Serial.print("💧 Humedad: "); Serial.print(hum); Serial.println(" %");
  } else {
    Serial.println("⚠️ Error al leer SCD41");
  }

  // 🔹 Leer gases
  Serial.print("🧪 CO: "); Serial.print(gas.measure_CO()); Serial.println(" ppm");
  Serial.print("🧪 NO2: "); Serial.print(gas.measure_NO2()); Serial.println(" ppm");
  Serial.print("🧪 Etanol: "); Serial.print(gas.measure_C2H5OH()); Serial.println(" ppm");
  Serial.print("🧪 NH3: "); Serial.print(gas.measure_NH3()); Serial.println(" ppm");
  Serial.print("🧪 CH4: "); Serial.print(gas.measure_CH4()); Serial.println(" ppm");

  // 🔹 Leer BMP280
  float bmpTemp = bmp.readTemperature();         // °C
  float pressure = bmp.readPressure() / 100.0F;  // hPa

  Serial.print("🌡️ Temp BMP280: "); Serial.print(bmpTemp); Serial.println(" °C");
  Serial.print("🔵 Presión: "); Serial.print(pressure); Serial.println(" hPa");

  Serial.println("------------------------------");
  delay(5000); // cada 5 segundos
}

Backend

Python
import threading
import math
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
import paho.mqtt.client as mqtt
import firebase_admin
from firebase_admin import credentials, firestore
import pickle

# ---------------------------
# Cargar el modelo de predicción
# ---------------------------
try:
    with open("environment_quality_model.pkl", "rb") as f:
        clf = pickle.load(f)
except Exception as e:
    raise RuntimeError("No se pudo cargar el modelo: " + str(e))

# ---------------------------
# Inicialización de FastAPI y configuración CORS
# ---------------------------
app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # En producción, restringir a los orígenes necesarios, ej.: ["http://localhost:8001"]
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ---------------------------
# Inicialización de Firebase Admin
# ---------------------------
try:
    firebase_admin.get_app()
except ValueError:
    cred = credentials.Certificate("firebase_credentials.json")
    firebase_admin.initialize_app(cred)

db = firestore.client()

# ---------------------------
# Configuración de MQTT
# ---------------------------
MQTT_BROKER = "test.mosquitto.org"
MQTT_PORT = 1883
MQTT_TOPICS = {
    "temp": "esp32/dht22/temperatura",
    "hum": "esp32/dht22/humedad",
    "gas": "esp32/mq5/gas",
    "luxRaw": "esp32/temt6000/lux_raw",
    "luxEst": "esp32/temt6000/lux_est",
    "uvRaw": "esp32/uv/raw",
    "uvPower": "esp32/uv/uWcm2",
    "soundRaw": "esp32/sound/raw",
    "soundDB": "esp32/sound/db_est"
}

# Diccionario global para almacenar los datos de los sensores
sensor_data = {
    "temp": "---",
    "hum": "---",
    "gas": "---",
    "gasPPM": "---",
    "luxRaw": "---",
    "luxEst": "---",
    "uvRaw": "---",
    "uvPower": "---",
    "soundRaw": "---",
    "soundDB": "---"
}

# ---------------------------
# Función para actualizar Firebase (crea un nuevo documento con timestamp)
# ---------------------------
def update_firebase(data: dict):
    data_with_timestamp = data.copy()
    data_with_timestamp["timestamp"] = firestore.SERVER_TIMESTAMP
    db.collection("sensors").add(data_with_timestamp)

# ---------------------------
# Callbacks de MQTT
# ---------------------------
def on_connect(client, userdata, flags, rc):
    print("Conectado a MQTT con código de resultado:", rc)
    for topic in MQTT_TOPICS.values():
        client.subscribe(topic)
        print("Suscrito al topic:", topic)

def on_message(client, userdata, msg):
    topic = msg.topic
    value = msg.payload.decode()
    print(f"Mensaje recibido en {topic}: {value}")

    global sensor_data
    if topic == MQTT_TOPICS["temp"]:
        sensor_data["temp"] = value
    elif topic == MQTT_TOPICS["hum"]:
        sensor_data["hum"] = value
    elif topic == MQTT_TOPICS["gas"]:
        sensor_data["gas"] = value
        try:
            raw = int(value)
            if raw > 0 and raw < 4095:
                ratio = (4095 - raw) / raw
                gas_ppm = round(116250 * math.pow(ratio, 2.65))
                sensor_data["gasPPM"] = gas_ppm
            else:
                sensor_data["gasPPM"] = "---"
        except Exception as e:
            sensor_data["gasPPM"] = "---"
    elif topic == MQTT_TOPICS["luxRaw"]:
        sensor_data["luxRaw"] = value
    elif topic == MQTT_TOPICS["luxEst"]:
        sensor_data["luxEst"] = value
    elif topic == MQTT_TOPICS["uvRaw"]:
        sensor_data["uvRaw"] = value
    elif topic == MQTT_TOPICS["uvPower"]:
        sensor_data["uvPower"] = value
    elif topic == MQTT_TOPICS["soundRaw"]:
        sensor_data["soundRaw"] = value
    elif topic == MQTT_TOPICS["soundDB"]:
        sensor_data["soundDB"] = value

    update_firebase(sensor_data)

# Configuración del cliente MQTT
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message

def mqtt_loop():
    mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
    mqtt_client.loop_forever()

# ---------------------------
# Definición de modelos y endpoints de FastAPI
# ---------------------------
# Modelo para datos de sensores (para el endpoint /sensors)
class SensorData(BaseModel):
    temp: str
    hum: str
    gas: str
    gasPPM: str
    luxRaw: str
    luxEst: str
    uvRaw: str
    uvPower: str
    soundRaw: str
    soundDB: str

@app.get("/sensors", response_model=SensorData)
def get_sensor_data():
    """
    Devuelve los datos más recientes de los sensores.
    Se intenta recuperar el documento 'latest' de Firestore;
    si no existe, se retorna el diccionario en memoria.
    """
    doc_ref = db.collection("sensors").document("latest")
    doc = doc_ref.get()
    if doc.exists:
        return doc.to_dict()
    return sensor_data

@app.get("/sensors/history")
def get_sensor_history(limit: int = Query(50, gt=0)):
    """
    Devuelve los registros históricos de la colección "sensors".
    Puedes limitar la cantidad de documentos devueltos con el parámetro 'limit'.
    """
    try:
        docs = db.collection("sensors") \
                 .order_by("timestamp", direction=firestore.Query.DESCENDING) \
                 .limit(limit).stream()
        history = []
        for doc in docs:
            data = doc.to_dict()
            data["id"] = doc.id
            history.append(data)
        return history
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error al obtener la historia: {e}")

@app.get("/sensors/{doc_id}")
def get_sensor_by_id(doc_id: str):
    """
    Devuelve los datos de un registro específico identificado por su ID.
    """
    doc_ref = db.collection("sensors").document(doc_id)
    doc = doc_ref.get()
    if doc.exists:
        return doc.to_dict()
    else:
        raise HTTPException(status_code=404, detail="Documento no encontrado")

# Modelo de entrada para el endpoint de predicción (/predict)
class SensorInput(BaseModel):
    temp: float
    hum: float
    gasPPM: float
    luxEst: float
    uvPower: float
    soundDB: float

@app.post("/predict")
def predict_quality(input_data: SensorInput):
    """
    Realiza la predicción de calidad ambiental utilizando el modelo entrenado y
    publica el resultado en MQTT (topic: "esp32/model/prediction").
    """
    X_input = [[
        input_data.temp,
        input_data.hum,
        input_data.gasPPM,
        input_data.luxEst,
        input_data.uvPower,
        input_data.soundDB
    ]]
    try:
        prediction = clf.predict(X_input)[0]
        # Publicar el resultado de la predicción en MQTT:
        mqtt_client.publish("esp32/model/prediction", str(prediction))
    except Exception as e:
        raise HTTPException(status_code=500, detail="Error al predecir: " + str(e))
    # Retornamos un mensaje indicando que la predicción se publicó vía MQTT
    return {"detail": "Prediction published via MQTT"}

# ---------------------------
# Iniciar el loop de MQTT al arrancar la aplicación
# ---------------------------
@app.on_event("startup")
def startup_event():
    thread = threading.Thread(target=mqtt_loop)
    thread.daemon = True
    thread.start()

# ---------------------------
# Ejecutar la aplicación con uvicorn
# ---------------------------
if __name__ == "__main__":
    uvicorn.run("plataformaDatosAmbientales:app", host="0.0.0.0", port=8000)

model.py

Python
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
import pickle

# 1. Cargar el CSV
df = pd.read_csv("firestore_data.csv")
print(df.head())
print("Número de registros:", len(df))

# 2. Convertir columnas a numérico y reemplazar NaN por 0
numeric_cols = [
    "temp", "hum", "gas", "gasPPM", 
    "luxRaw", "luxEst", "uvRaw", "uvPower", 
    "soundRaw", "soundDB"
]
for col in numeric_cols:
    df[col] = pd.to_numeric(df[col], errors='coerce')
df[numeric_cols] = df[numeric_cols].fillna(0)

# 3. Definir las variables que usaremos en la métrica multivariable:
# La fórmula es:
# Calidad_ambiental = w1·Temp + w2·Humedad + w3·GasPPM +
#                      w4·LuxEst + w5·UV_power + w6·SoundDB
features = ["temp", "hum", "gasPPM", "luxEst", "uvPower", "soundDB"]

# 4. Calcular la métrica de calidad ambiental (quality_score)
# Simulamos unos pesos determinados históricamente (estos valores son un ejemplo)
weights = [0.2, 0.15, 0.3, 0.1, 0.15, 0.1]  # w1, w2, ..., w6
df['quality_score'] = (weights[0] * df['temp'] +
                       weights[1] * df['hum'] +
                       weights[2] * df['gasPPM'] +
                       weights[3] * df['luxEst'] +
                       weights[4] * df['uvPower'] +
                       weights[5] * df['soundDB'])

# 5. Definir umbrales de clasificación basados en cuantiles del quality_score
threshold1 = df['quality_score'].quantile(0.33)
threshold2 = df['quality_score'].quantile(0.66)

def classify_quality(score):
    if score < threshold1:
        return "Buena"
    elif score < threshold2:
        return "Regular"
    else:
        return "Mala"

df['quality_cat'] = df['quality_score'].apply(classify_quality)

print("Distribución de calidad ambiental:")
print(df['quality_cat'].value_counts())

# 6. Seleccionar características y variable objetivo para el modelo
X = df[features]
y = df["quality_cat"]

# 7. Dividir el dataset en entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 8. Entrenar un modelo de clasificación (RandomForestClassifier)
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)

# 9. Evaluar el modelo
y_pred = clf.predict(X_test)
print("Reporte de clasificación:")
print(classification_report(y_test, y_pred))

# 10. Guardar el modelo para uso futuro
with open("environment_quality_model.pkl", "wb") as f:
    pickle.dump(clf, f)

print("Modelo entrenado y guardado correctamente.")

consumeModel.py

Python
import pickle
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

# Cargar el modelo entrenado
try:
    with open("environment_quality_model.pkl", "rb") as f:
        clf = pickle.load(f)
except Exception as e:
    raise RuntimeError("No se pudo cargar el modelo: " + str(e))

# Definir el modelo de entrada con Pydantic
class SensorInput(BaseModel):
    temp: float
    hum: float
    gasPPM: float
    luxEst: float
    uvPower: float
    soundDB: float

app = FastAPI()

@app.post("/predict")
def predict_quality(input_data: SensorInput):
    # Orden de características según se entrenó el modelo
    X_input = [[
        input_data.temp,
        input_data.hum,
        input_data.gasPPM,
        input_data.luxEst,
        input_data.uvPower,
        input_data.soundDB
    ]]
    try:
        prediction = clf.predict(X_input)[0]
    except Exception as e:
        raise HTTPException(status_code=500, detail="Error al predecir: " + str(e))
    return {"prediction": prediction}

Credits

Shiome Diaz
1 project • 0 followers
Thanks to Juan Esteban Castaño.

Comments