Primer Commit Repositorio

main
commit fb6c1efba4

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,3 @@
{
"ppm": 400
}

@ -0,0 +1,117 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
// --- 1. FUNCIÓN DE INICIALIZACIÓN ---
// Devuelve el descriptor de archivo (fd) o -1 si falla.
int init_uart(const char *puerto, speed_t baudrate) {
// Abrir el puerto: Lectura/Escritura, no ser la terminal controladora, no bloquear.
int fd = open(puerto, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("Error al abrir el puerto UART");
return -1;
}
struct termios tty;
if (tcgetattr(fd, &tty) != 0) {
perror("Error al obtener los atributos del puerto");
close(fd);
return -1;
}
// Configurar Baudios
cfsetispeed(&tty, baudrate);
cfsetospeed(&tty, baudrate);
// MODO CRUDO: 8N1 (8 bits, Sin paridad, 1 bit de parada)
tty.c_cflag &= ~PARENB; // Sin paridad
tty.c_cflag &= ~CSTOPB; // 1 bit de parada
tty.c_cflag &= ~CSIZE; // Limpiar máscara de tamaño
tty.c_cflag |= CS8; // 8 bits de datos
// Desactivar control de flujo por hardware y señales de módem
tty.c_cflag &= ~CRTSCTS;
tty.c_cflag |= CREAD | CLOCAL;
// Desactivar procesamiento de software (Modo crudo absoluto)
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tty.c_oflag &= ~OPOST;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
// Configuración de tiempos de espera (Timeout)
tty.c_cc[VMIN] = 0; // No exigir un mínimo de bytes
tty.c_cc[VTIME] = 20; // Esperar máximo 2 segundos (20 decimas)
// Limpiar basura del búfer y aplicar la configuración
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
perror("Error al aplicar la configuración UART");
close(fd);
return -1;
}
// Quitar el modo "No Bloqueante" para que VTIME y VMIN funcionen correctamente
fcntl(fd, F_SETFL, 0);
return fd;
}
// --- 2. FUNCIÓN PARA ENVIAR ---
void send_uart(int fd, unsigned char *trama, int longitud) {
tcflush(fd, TCIOFLUSH); // Limpiar tubería antes de hablar
int escritos = write(fd, trama, longitud);
if (escritos < 0) {
perror("Error al escribir en el puerto");
}
}
// --- 3. FUNCIÓN PARA RECIBIR ---
int receive_uart(int fd, unsigned char *buffer, int longitud_esperada) {
int bytes_leidos = read(fd, buffer, longitud_esperada);
return bytes_leidos;
}
// --- EJEMPLO DE USO ---
int main() {
int uart_fd = init_uart("/dev/serial0", B9600);
if (uart_fd == -1) return 1;
unsigned char comando[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
unsigned char respuesta[9];
while(1) {
send_uart(uart_fd, comando, sizeof(comando));
usleep(100000); // 100ms para que el sensor responda
int bytes = receive_uart(uart_fd, respuesta, 9);
if (bytes == 9) {
// 1. Calcular el valor real
int co2_ppm = (respuesta[2] * 256) + respuesta[3];
// 2. Imprimir en consola (para tu propio monitoreo)
printf("Respuesta OK: CO2 = %d ppm\n", co2_ppm);
// 3. Escribir el valor en el archivo JSON para la interfaz web
FILE *archivo_json = fopen("Data/MHZ19B.json", "w"); // "w" sobreescribe el archivo cada vez
if (archivo_json != NULL) {
// Formatear estrictamente como JSON
fprintf(archivo_json, "{\n \"ppm\": %d\n}\n", co2_ppm);
fclose(archivo_json); // Liberar el archivo para que JS pueda leerlo
} else {
perror("Error: No se pudo crear/abrir el archivo JSON");
}
} else {
printf("Error: Bytes recibidos = %d\n", bytes);
}
sleep(10);
}
close(uart_fd);
return 0;
}

Binary file not shown.

@ -0,0 +1,245 @@
# Proyecto de Investigación: Integración del Sensor NDIR MH-Z19B vía UART
Este repositorio documenta la integración, adquisición y visualización de datos del sensor de dióxido de carbono (CO₂) **MH-Z19B**.
El propósito principal de este proyecto es estudiar y comprender la arquitectura del protocolo de comunicación asíncrona **UART** a bajo nivel en sistemas embebidos basados en Linux utilizando una **Raspberry Pi 4**. Esta implementación integra una prueba experimental para la integración de sensores que formarán parte del desarrollo de microestaciones destinadas al monitoreo de la calidad del aire, desarrolladas durante el **Verano de la Investigación Científica y Tecnológica del Programa Delfín 2026**.
---
# Objetivo del Proyecto
Implementar un controlador de bajo nivel para el sensor **MH-Z19B** utilizando comunicación **UART**, integrándolo con una interfaz web capaz de visualizar las mediciones de CO₂ en tiempo real. Esta implementación constituye la base para el desarrollo de futuras microestaciones inteligentes destinadas al monitoreo ambiental y la calidad del aire.
---
# Especificaciones Técnicas del Sensor (MH-Z19B)
El **MH-Z19B** es un sensor compacto que utiliza la tecnología **NDIR (Non-Dispersive Infrared)** para medir la concentración de dióxido de carbono (CO₂) en el aire. Esta tecnología se basa en que las moléculas de gas absorben luz infrarroja a longitudes de onda muy específicas por lo que proporciona alta selectividad, buena estabilidad y no depende de la concentración de oxígeno para realizar las mediciones.
Las principales características del sensor son:
| Característica | Valor |
| :--- | :--- |
| Tecnología | NDIR (Non-Dispersive Infrared) |
| Gas medido | CO₂ |
| Rango de medición | 02000 ppm o 05000 ppm (configurable) |
| Voltaje de alimentación | 3.65.5 V DC |
| Tiempo de precalentamiento | 3 minutos |
| Comunicación | UART TTL |
| Velocidad de transmisión | 9600 baudios |
| Configuración UART | 8 bits de datos, sin paridad, 1 bit de parada (8N1) |
| Vida útil estimada | Mayor a 5 años |
---
# Material
## Hardware
- Raspberry Pi 4
- Sensor MH-Z19B
- Cables Jumper
- MicroSD card with Raspberry Pi OS
## Software
- UART (TTL)
- Lenguaje C
- HTML5
- CSS3
- JavaScript
- JSON
- Nginx
- Linux
- Git
---
# Conexiones de Hardware (Capa Física)
Para establecer correctamente la comunicación UART entre el sensor y la Raspberry Pi 4 es necesario cruzar las líneas de transmisión y recepción.
| Pin MH-Z19B | Función | Raspberry Pi 4 | Descripción |
| :--- | :--- | :--- | :--- |
| Vin (Pin 6) | Alimentación | Pin físico **2** o **4** (5 V) | Alimenta el sensor. |
| GND (Pin 7) | Tierra | Pin físico **6** (GND) | Referencia eléctrica común. |
| TXD (Pin 3) | Transmisión | Pin físico **10** (GPIO15 / RXD) | Envía los datos del sensor hacia la Raspberry Pi. |
| RXD (Pin 2) | Recepción | Pin físico **8** (GPIO14 / TXD) | Recibe los comandos enviados por la Raspberry Pi. |
![Esquema de Conexiones](EsquemaPines.png)
---
# Guía de Uso
## 1. Actualizar el Sistema
```bash
sudo apt update && sudo apt upgrade -y
```
---
## 2. Instalar Dependencias
```bash
sudo apt install git gcc nginx -y
```
---
## 3. Habilitar UART
Ejecutar:
```bash
sudo raspi-config
```
Ir a:
```text
Interface Options
└── Serial Port
```
Configurar:
```text
Would you like a login shell to be accessible over serial?
No
Would you like the serial port hardware to be enabled?
Yes
```
Reiniciar el sistema:
```bash
sudo reboot
```
---
## 4. Clonar el Repositorio
```bash
git clone https://gitea.itmorelia.com/Verano-Delfin-2026/CO2-Sensor.git
cd CO2-Sensor
```
---
## 5. Configurar Nginx
Copiar los archivos de la interfaz web al servidor:
```bash
sudo cp index.html /var/www/html/
sudo cp -r MHZ19B /var/www/html/
```
Reiniciar Nginx:
```bash
sudo systemctl restart nginx
```
---
# Compilación del Controlador en C
Ubicarse en el directorio del proyecto:
```bash
cd /var/www/html/
```
Compilar el controlador:
```bash
gcc mhz19b_driver.c -o driver_co2
```
Asignar permisos:
```bash
sudo chmod +x driver_co2
```
Ejecutar el programa:
```bash
sudo ./driver_co2
```
El controlador se comunicará con el sensor mediante el dispositivo:
```text
/dev/serial0
```
Las mediciones serán almacenadas periódicamente en el archivo:
```text
MHZ19B.json
```
Este archivo será utilizado posteriormente por la interfaz web.
---
# Visualización del Dashboard
Obtener la dirección IP de la Raspberry Pi:
```bash
hostname -I
```
Abrir un navegador web desde cualquier dispositivo conectado a la misma red local e ingresar:
```text
http://<IP_DE_LA_RASPBERRY_PI>
```
![Pagina resultante con las mediciones](InterfazWeb.png)
La interfaz web consultará periódicamente el archivo **MHZ19B.json** mediante JavaScript utilizando peticiones asíncronas (`fetch()`), mostrando en tiempo real la concentración de CO₂ y el estado de la calidad del aire.
---
# Arquitectura General del Sistema
```mermaid
graph TD
%% Sensor físico
A[ Sensor MH-Z19B] -->|UART 9600 baudios| B
%% Placa/Hardware
B{{ Raspberry Pi 4}} --> C
%% Proceso de Software
C( Driver en C) -->|Escribe datos| D
%% Base de datos / Archivo
D[( MHZ19B.json)] -.->|Lectura local| E
%% Servidor Web
E[[ Servidor Nginx]] -->|HTTP / Fetch API| F
%% Nube / Web Dashboard
F(( Dashboard Web / Cliente))
```
---
# Conclusión
El uso del protocolo UART implica un gran desafío debido a su necesidad de programación a bajo nivel en C. Durante la fase de depuración se logró identificar que dejar que el sistema operativo controle los tiempos lo hace muy propenso a fallas de sincronización. Esto se evidenció en la recepción de "basura" hexadecimal, la captura de tramas incompletas de apenas 3 bytes, el congelamiento del sistema por saturación de peticiones y un retraso de 6 segundos entre la consola y la interfaz gráfica.
A pesar de esto, UART sigue siendo uno de los pilares dentro de los sistemas embebidos por ser una herramienta directa, robusta y útil. No solo permite una transmisión de datos confiable en tiempo real, sino que otorga el control absoluto sobre el hardware.
# Limitaciones
Aunque este mini proyecto cumple su objetivo de leer el CO₂ y probar la comunicación UART, tiene las siguientes limitaciones prácticas que debemos tomar en cuenta:
- **Tiempos no exactos:** La Raspberry Pi funciona como una computadora normal y no está diseñada para controlar el tiempo con una precisión matemática perfecta. Esto hace que las pausas para comunicarse con el sensor tengan ligeras variaciones.
- **El programa se "pausa" a esperar:** Actualmente, el código en C se detiene por completo hasta que el sensor responde y se guarda el archivo. Esto funciona bien para un solo sensor, pero si en el futuro queremos conectar varios a la vez, el sistema se volvería lento porque tendría que esperar a uno por uno en lugar de atenderlos al mismo tiempo.
- **Consumo innecesario en la página web:** El panel web descarga el archivo completo cada dos segundos, haya o no haya datos nuevos. Para la versión final de la estación, lo ideal sería usar un método más inteligente donde la página se actualice *solo* cuando el nivel de CO₂ cambie, para no desperdiciar recursos ni hacer trabajar a la placa de más.

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Monitor de Calidad del Aire</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
background-color: #f4f4f9;
padding-top: 50px;
}
.panel {
background-color: white;
border: 2px solid #ccc;
padding: 40px;
display: inline-block;
border-radius: 15px;
box-shadow: 0px 4px 8px rgba(0,0,0,0.1);
}
.valor {
font-size: 5em;
font-weight: bold;
color: #2c3e50;
margin: 20px 0;
}
.etiqueta {
font-size: 1.2em;
color: #7f8c8d;
}
/* Clases para cambiar el color según el nivel de CO2 */
.excelente { color: #27ae60; } /* Verde */
.regular { color: #f39c12; } /* Naranja */
.peligro { color: #e74c3c; } /* Rojo */
</style>
</head>
<body>
<div class="panel">
<h2>Monitoreo de CO₂ (MH-Z19B)</h2>
<div class="etiqueta">Concentración actual en el ambiente:</div>
<div class="valor" id="display_co2">-- ppm</div>
<div class="etiqueta" id="estado_aire">Calculando estado...</div>
</div>
<script>
async function actualizarPantalla() {
try {
// 1. Ir a buscar el archivo JSON generado por el código en C
const respuesta = await fetch('MHZ19B/Data/MHZ19B.json?nocache=' + Date.now());
const datos = await respuesta.json();
// 2. Extraer el valor de ppm
const co2 = datos.ppm;
// 3. Obtener los elementos HTML
const display = document.getElementById('display_co2');
const estado = document.getElementById('estado_aire');
// 4. Actualizar el texto en pantalla
display.innerText = co2 + " ppm";
// 5. Lógica visual: Cambiar colores según la concentración (Estándares típicos)
if (co2 < 800) {
display.className = "valor excelente";
estado.innerText = "Estado: Excelente (Aire Fresco)";
} else if (co2 >= 800 && co2 <= 1500) {
display.className = "valor regular";
estado.innerText = "Estado: Regular (Ventilación recomendada)";
} else {
display.className = "valor peligro";
estado.innerText = "Estado: Peligro (Ventilar inmediatamente)";
}
} catch (error) {
console.error("Error de conexión:", error);
document.getElementById('estado_aire').innerText = "Error: Sensor desconectado";
}
}
// Ejecutar inmediatamente al abrir la página
actualizarPantalla();
// Repetir automáticamente cada 2 segundos
setInterval(actualizarPantalla, 2000);
</script>
</body>
</html>
Loading…
Cancel
Save