primer commit

main
commit 7a12bcedaf

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++20",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}

@ -0,0 +1,155 @@
// En bmp180.c
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include "bmp180.h"
// --- FUNCIÓN AUXILIAR PARA LEER VLOQUES DE DATOS ---
int leer_bloque_i2c(int fd, uint8_t registro_inicial, uint8_t *buffer, int longitud) {
// Le decimos al sensor desde qué registro queremos empezar a leer
// (El sensor cuenta con un puntero autoincrementado que inicia del registro inicial enviando una rafaga)
if (write(fd, &registro_inicial, 1) != 1) {
perror("Fallo al escribir el registro inicial");
return -1; // Falló
}
// Leemos la cantidad de bytes que pedimos
if (read(fd, buffer, longitud) != longitud) {
perror("Fallo al leer los bytes del sensor");
return -1; // Falló
}
return 0; // Éxito
}
// --- MÉTODO DE INICIALIZACIÓN PARA RECONSTRUIR LOS DATOS DE CALIBRACIÓN ---
int bmp180_init(int fd, BMP180_CalibData *calib) {
uint8_t buffer[22]; // Necesitamos un arreglo de 22 espacios para guardar los 22 bytes
printf("Iniciando BMP180 y leyendo calibracion...\n");
// Leemos los 22 bytes empezando desde el registro 0xAA
if (leer_bloque_i2c(fd, 0xAA, buffer, 22) < 0) {
printf("Error fatal: No se pudo leer la EEPROM.\n");
return -1; // Falló
}
// Unimos los bytes.
// El sensor envía primero el byte "pesado" (MSB) y luego el "ligero" (LSB).
// Tomamos el MSB, lo desplazamos 8 espacios a la izquierda (<< 8) y le sumamos el LSB (|).
calib->ac1 = (buffer[0] << 8) | buffer[1];
calib->ac2 = (buffer[2] << 8) | buffer[3];
calib->ac3 = (buffer[4] << 8) | buffer[5];
// Estos tres deben ser tratados como sin signo (unsigned) según el datasheet
calib->ac4 = (buffer[6] << 8) | buffer[7];
calib->ac5 = (buffer[8] << 8) | buffer[9];
calib->ac6 = (buffer[10] << 8) | buffer[11];
calib->b1 = (buffer[12] << 8) | buffer[13];
calib->b2 = (buffer[14] << 8) | buffer[15];
calib->mb = (buffer[16] << 8) | buffer[17];
calib->mc = (buffer[18] << 8) | buffer[19];
calib->md = (buffer[20] << 8) | buffer[21];
// Verificamos que se realizo correctamente
// Si la comunicación falla o el cable está mal, a veces el I2C devuelve puros ceros (0)
// o puros unos (0xFFFF = -1 en short)
if (calib->ac1 == 0 || calib->ac1 == -1) {
printf("Advertencia: Los datos de calibración parecen inválidos.\n");
return -1; // Falló
}
printf("Calibracion exitosa. Valor AC1: %d\n", calib->ac1);
return 0; // Todo listo para medir
}
// --- MÉTODO PARA LEER LA TEMPERATURA CRUDA ---
long bmp180_read_ut(int fd) {
// 0xF4 es el registro de control, 0x2E es el comando de temperatura
uint8_t cmd[2] = {BMP180_REG_CTRL_MEAS, BMP180_CMD_TEMP};
// Escribimos el comando
write(fd, cmd, 2);
// Esperamos a que el hardware del sensor termine la conversión (4.5ms)
usleep(4500);
uint8_t buf[2];
// Leemos los 2 bytes resultantes en el registro 0xF6
leer_bloque_i2c(fd, BMP180_REG_ADC_MSB, buf, 2);
// Ensamblamos los 16 bits
return (buf[0] << 8) | buf[1];
}
// --- MÉTODO PARA LEER LA PRESIÓN CRUDA ---
long bmp180_read_up(int fd, int oss) {
// Calculamos el comando exacto basado en la resolución (oss)
uint8_t cmd_val = 0x34 + (oss << 6);
uint8_t cmd[2] = {BMP180_REG_CTRL_MEAS, cmd_val};
write(fd, cmd, 2);
// El tiempo de espera del hardware aumenta si pides más resolución
switch(oss) {
case 0: usleep(4500); break;
case 1: usleep(7500); break;
case 2: usleep(13500); break;
case 3: usleep(25500); break;
}
uint8_t buf[3];
// Leemos 3 bytes (MSB, LSB, XLSB) desde 0xF6
leer_bloque_i2c(fd, BMP180_REG_ADC_MSB, buf, 3);
// Desplazamiento a nivel de bits exigido por el fabricante
return ((long)buf[0] << 16 | (long)buf[1] << 8 | (long)buf[2]) >> (8 - oss);
}
// --- MÉTODO PARA MEDIR LA PRESIÓN CALIBRADA ---
int bmp180_get_pressure(int fd, BMP180_CalibData *calib, int oss, double *pressure) {
// Medimos la temperatura y presión crudas
long UT = bmp180_read_ut(fd);
long UP = bmp180_read_up(fd, oss);
// Declaramos las variables locales según la hoja de datos (página 15)
long X1, X2, X3, B3, B5, B6, p;
unsigned long B4, B7;
// --- Ecuaciones de Temperatura (Para obtener B5) ---
X1 = ((UT - calib->ac6) * calib->ac5) >> 15;
X2 = ((long)calib->mc << 11) / (X1 + calib->md); // Cast a long obligatorio en mc
B5 = X1 + X2;
// --- Ecuaciones de Presión ---
B6 = B5 - 4000;
X1 = (calib->b2 * (B6 * B6 >> 12)) >> 11;
X2 = (calib->ac2 * B6) >> 11;
X3 = X1 + X2;
B3 = ((((long)calib->ac1 * 4 + X3) << oss) + 2) >> 2;
X1 = (calib->ac3 * B6) >> 13;
X2 = (calib->b1 * ((B6 * B6) >> 12)) >> 16;
X3 = ((X1 + X2) + 2) >> 2;
B4 = (calib->ac4 * (unsigned long)(X3 + 32768)) >> 15;
B7 = ((unsigned long)UP - B3) * (50000 >> oss);
if (B7 < 0x80000000) {
p = (B7 * 2) / B4;
} else {
p = (B7 / B4) * 2;
}
X1 = (p >> 8) * (p >> 8);
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) >> 16;
p = p + ((X1 + X2 + 3791) >> 4);
// El cálculo arroja Pascales, dividimos entre 100 para Hectopascales (hPa)
*pressure = p / 100.0;
return 0; // Éxito
}

@ -0,0 +1,27 @@
#ifndef BMP180_H
#define BMP180_H
// --- DIRECCIÓN I2C ---
#define BMP180_I2C_ADDR 0x77
// --- REGISTROS DEL BMP180 ---
#define BMP180_REG_CTRL_MEAS 0xF4 // Registro de control para iniciar mediciones
#define BMP180_REG_ADC_MSB 0xF6 // Registro de salida de datos: Byte Más Significativo
#define BMP180_REG_ADC_LSB 0xF7 // Registro de salida de datos: Byte Menos Significativo
#define BMP180_REG_ADC_XLSB 0xF8 // Registro de salida de datos: Byte Extendido
// --- COMANDOS DE CONTROL (REGISTRO 0xF4) ---
#define BMP180_CMD_TEMP 0x2E // Orden de medir temperatura.
#define BMP180_CMD_PRES_OSS2 0xB4 // Presión: High Resolution (oss=2)
// Estructura para almacenar los datos de calibración de fábrica
typedef struct {
short ac1, ac2, ac3;
unsigned short ac4, ac5, ac6;
short b1, b2, mb, mc, md;
} BMP180_CalibData;
// --- DECLARACIÓN DE MÉTODOS PARA bmp180.c ---
int bmp180_init(int fd, BMP180_CalibData *calib); // Calibración
int bmp180_get_temperature(int fd, double *temperature); // Temperatura
int bmp180_get_pressure(int fd, BMP180_CalibData *calib, int oss, double *pressure); // Presión
#endif // BMP180_H

@ -0,0 +1,52 @@
#include <unistd.h> // Para enviar comandos y recibir datos desde dispositivos I2C
#include <sys/ioctl.h> // setting up and controlling the I2C device settings
#include <linux/i2c-dev.h> // Definiciones para el sistema de llamadas y estructuras especificaspara I2C
#include <i2c/smbus.h> // SMBus commands in a more standardized way for I2C
#include <stdio.h> // perror
#include "htu21d.h" // my own header file
// Reset function:
int reset(int fd)
{
if(0 > ioctl(fd, I2C_SLAVE, HTU21D_I2C_ADDR))
{
perror("Failed to open the bus");
return -1;
}
i2c_smbus_write_byte(fd, HTU21D_RESET);
return 0;
}
// Get temperature:
int getTemperature(int fd, double *temperature)
{
reset(fd);
char buf[3];
__s32 res = i2c_smbus_read_i2c_block_data(fd, HTU21D_TEMP,3,buf);
if(res<0)
{
perror("Failed to read from the device");
return -1;
}
*temperature = -46.85 + 175.72 * (buf[0]*256 + buf[1]) / 65536.0;
return 0;
}
// Get humidity:
int getHumidity(int fd, double *humidity)
{
reset(fd);
char buf[3];
__s32 res = i2c_smbus_read_i2c_block_data(fd, HTU21D_HUMID, 3, buf);
if(res<0)
{
perror("Failed to read from the device");
return -1;
}
*humidity = -6 + 125 * (buf[0]*256 + buf[1]) / 65536.0;
return 0;
}

@ -0,0 +1,15 @@
#ifndef HTU21D_H
#define HTU21D_H
// I2C Address
#define HTU21D_I2C_ADDR 0x40
// Commands
#define HTU21D_TEMP 0xE3
#define HTU21D_HUMID 0xE5
#define HTU21D_RESET 0xFE
// --- DECLARACIÓN DE MÉTODOS PARA htu21d.c ---
int getTemperature(int fd, double *temperature); // Temperatura
int getHumidity(int fd, double *humidity); // Humedad
#endif // HTU21D_H

@ -0,0 +1,188 @@
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <time.h> // Necesario para la fecha y hora
#include <sqlite3.h>
#include <string.h>
#include <sys/ioctl.h>
// Controladores
#include "bmp180.h"
#include "htu21d.h"
#include "sen0472_o3.h"
#include "sen0471_no2.h"
#include "sen0470_so2.h"
#include "sen0466_co.h"
// Definimos la ruta donde vivirá tu base de datos final
#define RUTA_BD "/var/www/html/AirQualityMicrostation/Database/microestacionAQ.db" // Ajusta esta ruta a tu carpeta real
void guardar_lote_en_bd(const char *ruta_lote) {
sqlite3 *bd;
char *mensaje_error = 0;
// 1. Abrimos o creamos la base de datos
if (sqlite3_open(RUTA_BD, &bd) != SQLITE_OK) {
printf("Error: No se pudo abrir la base de datos: %s\n", sqlite3_errmsg(bd));
return;
}
// 2. Creamos la tabla si no existe
const char *instruccion_crear =
"CREATE TABLE IF NOT EXISTS mediciones ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"fecha_hora TEXT, presion REAL, temperatura REAL, "
"humedad REAL, ozono REAL, no2 REAL, co INTEGER, so2 REAL, "
"ica INTEGER, banda_ica TEXT);";
if (sqlite3_exec(bd, instruccion_crear, 0, 0, &mensaje_error) != SQLITE_OK) {
printf("Error al crear tabla: %s\n", mensaje_error);
sqlite3_free(mensaje_error);
}
// 3. Abrimos el archivo temporal que contiene las lecturas de los últimos 5 minutos
FILE *archivo = fopen(ruta_lote, "r");
if (archivo == NULL) {
printf("Advertencia: No se encontró el archivo temporal para guardar.\n");
sqlite3_close(bd);
return;
}
// Arreglo temporal para guardar cada línea de texto que leamos
char linea[256];
// 4. Leemos el archivo línea por línea hasta llegar al final
while (fgets(linea, sizeof(linea), archivo)) {
char fecha_hora[64];
double presion, temperatura, humedad, ozono, no2, so2;
int co;
// 5. Extraemos los valores separados por comas
// El formato %[^,] significa "lee todo el texto hasta encontrar una coma"
if (sscanf(linea, "%[^,],%lf,%lf,%lf,%lf,%lf,%d,%lf",
fecha_hora, &presion, &temperatura, &humedad,
&ozono, &no2, &co, &so2) == 8) {
// 6. Armamos la instrucción de inserción con los valores exactos
char instruccion_insertar[512];
sprintf(instruccion_insertar,
"INSERT INTO mediciones (fecha_hora, presion, temperatura, humedad, ozono, no2, co, so2, ica, banda_ica) "
"VALUES ('%s', %.2f, %.2f, %.2f, %.2f, %.2f, %d, %.2f, NULL, NULL);",
fecha_hora, presion, temperatura, humedad, ozono, no2, co, so2);
// 7. Ejecutamos la inserción en la base de datos
if (sqlite3_exec(bd, instruccion_insertar, 0, 0, &mensaje_error) != SQLITE_OK) {
printf("Error al insertar fila: %s\n", mensaje_error);
sqlite3_free(mensaje_error);
}
}
}
// 8. Limpiamos y cerramos todo
fclose(archivo);
sqlite3_close(bd);
}
int main() {
// --- Apertura de los buses ---
// Bus principal: Clima (BMP180 y HTU21D)
int fd_principal = open("/dev/i2c-1", O_RDWR);
if (fd_principal < 0) {
printf("Error: No se pudo abrir el bus I2C principal.\n");
return 1;
}
// Bus secundario: Gases (Familia DFRobot)
int fd_secundario = open("/dev/i2c-3", O_RDWR);
if (fd_secundario < 0) {
printf("Error: No se pudo abrir el bus I2C secundario.\n");
return 1;
}
// --- Configuración inicial ---
printf("Iniciando microestación...\n");
// BMP180 en el bus principal (Conexión e inicialización)
ioctl(fd_principal, I2C_SLAVE, 0x77); // <- LÍNEA NUEVA
BMP180_CalibData calibracion_bmp;
bmp180_init(fd_principal, &calibracion_bmp);
// Sensores de gas en el bus secundario
changeQA_O3(fd_secundario);
changeQA_NO2(fd_secundario);
changeQA_CO(fd_secundario);
changeQA_SO2(fd_secundario);
double presion, temp_htu, humedad, ozono, dioxido_nitrogeno, dioxido_azufre;
int monoxido;
int contador_bd = 0;
// --- Ciclo continuo de monitoreo ---
while (1) {
// 1. Obtener la fecha y hora actual del sistema
time_t t = time(NULL);
struct tm tm = *localtime(&t);
char fecha_hora[64];
// Formato: Año-Mes-Día Hora:Minuto:Segundo
sprintf(fecha_hora, "%04d-%02d-%02d %02d:%02d:%02d",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
// 2. Extracción de datos separada por buses
// Bus Principal
ioctl(fd_principal, I2C_SLAVE, 0x77); // Recuperamos el control del bus para el bmp180
bmp180_get_pressure(fd_principal, &calibracion_bmp, 2, &presion);
getTemperature(fd_principal, &temp_htu);
getHumidity(fd_principal, &humedad);
// Bus Secundario
getO3(fd_secundario, &ozono);
getNO2(fd_secundario, &dioxido_nitrogeno);
getCO(fd_secundario, &monoxido);
getSO2(fd_secundario, &dioxido_azufre);
// 3. Escribir el estado actual (Sobrescribir archivo para el comando terminal)
FILE *archivo_actual = fopen("/dev/shm/aql_actual.txt", "w");
if (archivo_actual != NULL) {
fprintf(archivo_actual, "PRES=%.2f\n", presion);
fprintf(archivo_actual, "TEMP=%.2f\n", temp_htu);
fprintf(archivo_actual, "HUM=%.2f\n", humedad);
fprintf(archivo_actual, "O3=%.2f\n", ozono);
fprintf(archivo_actual, "NO2=%.2f\n", dioxido_nitrogeno);
fprintf(archivo_actual, "CO=%d\n", monoxido);
fprintf(archivo_actual, "SO2=%.2f\n", dioxido_azufre);
fprintf(archivo_actual, "FECHA=\"%s\"\n", fecha_hora);
fclose(archivo_actual);
}
// 4. Escribir el historial temporal (Agregar línea para la Base de Datos)
// Usamos "a" (append) para añadir sin borrar lo anterior
FILE *archivo_lote = fopen("/dev/shm/lote_bd.csv", "a");
if (archivo_lote != NULL) {
// Guardamos todo separado por comas para facilitar la lectura posterior
fprintf(archivo_lote, "%s,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%.2f\n",
fecha_hora, presion, temp_htu, humedad, ozono, dioxido_nitrogeno, monoxido, dioxido_azufre);
fclose(archivo_lote);
}
// 5. Lógica de almacenamiento masivo en la Base de Datos (Cada 5 minutos)
contador_bd++;
if (contador_bd >= 30) {
// Llamamos a la función pasándole la ruta del archivo temporal
guardar_lote_en_bd("/dev/shm/lote_bd.csv");
// Una vez guardado, borramos el archivo de lote para empezar de cero
FILE *limpiar_lote = fopen("/dev/shm/lote_bd.csv", "w");
if (limpiar_lote != NULL) fclose(limpiar_lote);
contador_bd = 0;
}
// Esperar 10 segundos
sleep(10);
}
close(fd_principal);
close(fd_secundario);
return 0;
}

@ -0,0 +1,92 @@
#include <unistd.h> // Para enviar comandos y recibir datos desde dispositivos I2C
#include <sys/ioctl.h> // setting up and controlling the I2C device settings
#include <linux/i2c-dev.h> // Definiciones para el sistema de llamadas y estructuras especificaspara I2C
#include <stdio.h> // perror
#include "sen0466_co.h" // Archivo header
int changeQA_CO(int fd) {
// El comando necesita 9 bytes
// 0xFF: Byte de inicio
// 0x01: Dirección
// 0x78: Comando para cambiar tipo
// 0x04: El tipo Q&A
// 0x00...: Rellenos
// 0x83: El checksum (suma de control)
unsigned char cmd[9] = {0xFF, 0x01, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x83};
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0466_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor");
return -1;
}
// Envia los 9 bytes
if (write(fd, cmd, 9) != 9) {
perror("Error al escribir el comando de cambio de modo");
return -1;
} /*else {
printf("Comando enviado exitosamente. Sensor en modo Q&A.\n");
}*/
return 0;
}
// --- MÉTODO PARA VERIFICAR EL CHECKSUM (Si el paquete recibido está libre de errores) ---
static int verificar_checksum(unsigned char *buf) {
unsigned char tempq = 0;
int i;
// Suma los bytes del 1 al 7
for (i = 1; i < 8; i++) {
tempq += buf[i];
}
// Calcula el complemento a 2 (negación + 1)
tempq = (~tempq) + 1;
// Compara el resultado con el octavo byte (el checksum recibido)
if (tempq == buf[8]) {
return 1; // El paquete es válido
}
return 0; // El paquete está corrompido
}
int getCO(int fd, int *co_valor){
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0466_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor de CO");
return -1;
}
// Enviar el comando de lectura para que el sensor prepare el dato y lo deje listo en el buffer de salida
unsigned char cmd = SEN0466_READ; // 0x86
if (write(fd, &cmd, 1) != 1) {
perror("Error al enviar comando de lectura");
return -1;
}
// Espera a que el sensor prepare el dato
usleep(100000); // 100ms
// Leer el bloque de datos y lo almacena en el arreglo buf
unsigned char buf[9];
int res = read(fd, buf, 9);
if (res < 0) {
perror("Error de lectura I2C");
return -1;
}
// Verifica el checksum
if (!verificar_checksum(buf)) {
printf("Error: Checksum inválido. Datos corruptos descartados.\n");
return -1;
}
// Convierte el valor a valor decimal
*co_valor = (buf[2] * 256) + buf[3];
return 0;
}

@ -0,0 +1,15 @@
#ifndef SEN0466_H
#define SEN0466_H
// --- DIRECCIÓN I2C ---
#define SEN0466_I2C_ADDR 0x76
// --- COMANDOS ---
#define SEN0466_CTYP 0x78
#define SEN0466_QA 0x04
#define SEN0466_READ 0x86
// --- DECLARACIÓN DE MÉTODOS PARA sen0466.c ---
int getCO(int fd, int *co_valor); // CO (Monoxido de Carbono)
int changeQA_CO(int fd); // Cambio a modo Q&A
#endif // SEN0466_H

@ -0,0 +1,92 @@
#include <unistd.h> // Para enviar comandos y recibir datos desde dispositivos I2C
#include <sys/ioctl.h> // setting up and controlling the I2C device settings
#include <linux/i2c-dev.h> // Definiciones para el sistema de llamadas y estructuras especificaspara I2C
#include <stdio.h> // perror
#include "sen0470_so2.h" // Archivo header
// --- MÉTODO PARA CAMBIAR A MODO Q&A AL SENSOR ---
int changeQA_SO2(int fd) {
// El comando necesita 9 bytes
// 0xFF: Byte de inicio
// 0x01: Dirección
// 0x78: Comando para cambiar tipo
// 0x04: El tipo Q&A
// 0x00...: Rellenos
// 0x83: El checksum (suma de control)
unsigned char cmd[9] = {0xFF, 0x01, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x83};
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0470_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor");
return -1;
}
// Envia los 9 bytes
if (write(fd, cmd, 9) != 9) {
perror("Error al escribir el comando de cambio de modo");
return -1; // Falló
}
return 0; // Éxito
}
// --- MÉTODO PARA VERIFICAR EL CHECKSUM (Si el paquete recibido está libre de errores) ---
static int verificar_checksum(unsigned char *buf) {
unsigned char tempq = 0;
int i;
// Suma los bytes del 1 al 7
for (i = 1; i < 8; i++) {
tempq += buf[i];
}
// Calcula el complemento a 2 (negación + 1)
tempq = (~tempq) + 1;
// Compara el resultado con el octavo byte (el checksum recibido)
if (tempq == buf[8]) {
return 1; // El paquete es válido
}
return 0; // El paquete está corrompido
}
// --- MÉTODO PARA OBTENER EL SO2 (Dioxido de Azufre) ---
double getSO2(int fd, double *so2_valor){
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0470_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor de SO2");
return -1;
}
// Enviar el comando de lectura para que el sensor prepare el dato y lo deje listo en el buffer de salida
unsigned char cmd = SEN0470_READ; // 0x86
if (write(fd, &cmd, 1) != 1) {
perror("Error al enviar comando de lectura");
return -1;
}
// Espera a que el sensor prepare el dato
usleep(100000); // 100ms
// Leer el bloque de datos y lo almacena en el arreglo buf
unsigned char buf[9];
int res = read(fd, buf, 9);
if (res < 0) {
perror("Error de lectura I2C");
return -1;
}
// Verifica el checksum
if (!verificar_checksum(buf)) {
printf("Error: Checksum inválido. Datos corruptos descartados.\n");
return -1;
}
// Convierte el valor a valor decimal
int valor_crudo = (buf[2] * 256) + buf[3];
*so2_valor = valor_crudo * 0.1;
return 0;
}

@ -0,0 +1,15 @@
#ifndef SEN0470_H
#define SEN0470_H
// --- DIRECCIÓN I2C ---
#define SEN0470_I2C_ADDR 0x77
// --- COMANDOS ---
#define SEN0470_CTYP 0x78
#define SEN0470_QA 0x04
#define SEN0470_READ 0x86
// --- DECLARACIÓN DE MÉTODOS PARA sen0472.c ---
double getSO2(int fd, double *so2_valor); // SO2 (Dióxido de azufre)
int changeQA_SO2(int fd); // Cambio a modo Q&A
#endif // SEN0472_H

@ -0,0 +1,92 @@
#include <unistd.h> // Para enviar comandos y recibir datos desde dispositivos I2C
#include <sys/ioctl.h> // setting up and controlling the I2C device settings
#include <linux/i2c-dev.h> // Definiciones para el sistema de llamadas y estructuras especificaspara I2C
#include <stdio.h> // perror
#include "sen0471_no2.h" // Archivo header
// --- MÉTODO PARA CAMBIAR A MODO Q&A AL SENSOR ---
int changeQA_NO2(int fd) {
// El comando necesita 9 bytes
// 0xFF: Byte de inicio
// 0x01: Dirección
// 0x78: Comando para cambiar tipo
// 0x04: El tipo Q&A
// 0x00...: Rellenos
// 0x83: El checksum (suma de control)
unsigned char cmd[9] = {0xFF, 0x01, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x83};
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0471_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor");
return -1;
}
// Envia los 9 bytes
if (write(fd, cmd, 9) != 9) {
perror("Error al escribir el comando de cambio de modo");
return -1; // Falló
}
return 0; // Éxito
}
// --- MÉTODO PARA VERIFICAR EL CHECKSUM (Si el paquete recibido está libre de errores) ---
static int verificar_checksum(unsigned char *buf) {
unsigned char tempq = 0;
int i;
// Suma los bytes del 1 al 7
for (i = 1; i < 8; i++) {
tempq += buf[i];
}
// Calcula el complemento a 2 (negación + 1)
tempq = (~tempq) + 1;
// Compara el resultado con el octavo byte (el checksum recibido)
if (tempq == buf[8]) {
return 1; // El paquete es válido
}
return 0; // El paquete está corrompido
}
// --- MÉTODO PARA OBTENER EL O3 (OZONO) ---
double getNO2(int fd, double *no2_valor){
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0471_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor de NO2");
return -1;
}
// Enviar el comando de lectura para que el sensor prepare el dato y lo deje listo en el buffer de salida
unsigned char cmd = SEN0471_READ; // 0x86
if (write(fd, &cmd, 1) != 1) {
perror("Error al enviar comando de lectura");
return -1;
}
// Espera a que el sensor prepare el dato
usleep(100000); // 100ms
// Leer el bloque de datos y lo almacena en el arreglo buf
unsigned char buf[9];
int res = read(fd, buf, 9);
if (res < 0) {
perror("Error de lectura I2C");
return -1;
}
// Verifica el checksum
if (!verificar_checksum(buf)) {
printf("Error: Checksum inválido. Datos corruptos descartados.\n");
return -1;
}
// Convierte el valor a valor decimal
int valor_crudo = (buf[2] * 256) + buf[3];
*no2_valor = valor_crudo * 0.1;
return 0;
}

@ -0,0 +1,15 @@
#ifndef SEN0471_H
#define SEN0471_H
// --- DIRECCIÓN I2C ---
#define SEN0471_I2C_ADDR 0x75
// --- COMANDOS ---
#define SEN0471_CTYP 0x78
#define SEN0471_QA 0x04
#define SEN0471_READ 0x86
// --- DECLARACIÓN DE MÉTODOS PARA sen0472.c ---
double getNO2(int fd, double *no2_valor); // NO2 (Ozono)
int changeQA_NO2(int fd); // Cambio a modo Q&A
#endif // SEN0471_H

@ -0,0 +1,92 @@
#include <unistd.h> // Para enviar comandos y recibir datos desde dispositivos I2C
#include <sys/ioctl.h> // setting up and controlling the I2C device settings
#include <linux/i2c-dev.h> // Definiciones para el sistema de llamadas y estructuras especificaspara I2C
#include <stdio.h> // perror
#include "sen0472_o3.h" // Archivo header
// --- MÉTODO PARA CAMBIAR A MODO Q&A AL SENSOR ---
int changeQA_O3(int fd) {
// El comando necesita 9 bytes
// 0xFF: Byte de inicio
// 0x01: Dirección
// 0x78: Comando para cambiar tipo
// 0x04: El tipo Q&A
// 0x00...: Rellenos
// 0x83: El checksum (suma de control)
unsigned char cmd[9] = {0xFF, 0x01, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x83};
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0472_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor");
return -1;
}
// Envia los 9 bytes
if (write(fd, cmd, 9) != 9) {
perror("Error al escribir el comando de cambio de modo");
return -1; // Falló
}
return 0; // Éxito
}
// --- MÉTODO PARA VERIFICAR EL CHECKSUM (Si el paquete recibido está libre de errores) ---
static int verificar_checksum(unsigned char *buf) {
unsigned char tempq = 0;
int i;
// Suma los bytes del 1 al 7
for (i = 1; i < 8; i++) {
tempq += buf[i];
}
// Calcula el complemento a 2 (negación + 1)
tempq = (~tempq) + 1;
// Compara el resultado con el octavo byte (el checksum recibido)
if (tempq == buf[8]) {
return 1; // El paquete es válido
}
return 0; // El paquete está corrompido
}
// --- MÉTODO PARA OBTENER EL O3 (OZONO) ---
double getO3(int fd, double *o3_valor){
// Conecta el driver al sensor en el bus fd a traves de su dirección y estableciendolo como dispositivo esclavo
if (ioctl(fd, I2C_SLAVE, SEN0472_I2C_ADDR) < 0) {
perror("No se pudo conectar al sensor de O3");
return -1;
}
// Enviar el comando de lectura para que el sensor prepare el dato y lo deje listo en el buffer de salida
unsigned char cmd = SEN0472_READ; // 0x86
if (write(fd, &cmd, 1) != 1) {
perror("Error al enviar comando de lectura");
return -1;
}
// Espera a que el sensor prepare el dato
usleep(100000); // 100ms
// Leer el bloque de datos y lo almacena en el arreglo buf
unsigned char buf[9];
int res = read(fd, buf, 9);
if (res < 0) {
perror("Error de lectura I2C");
return -1;
}
// Verifica el checksum
if (!verificar_checksum(buf)) {
printf("Error: Checksum inválido. Datos corruptos descartados.\n");
return -1;
}
// Convierte el valor a valor decimal
int valor_crudo = (buf[2] * 256) + buf[3];
*o3_valor = valor_crudo * 0.1;
return 0;
}

@ -0,0 +1,15 @@
#ifndef SEN0472_H
#define SEN0472_H
// --- DIRECCIÓN I2C ---
#define SEN0472_I2C_ADDR 0x74
// --- COMANDOS ---
#define SEN0472_CTYP 0x78
#define SEN0472_QA 0x04
#define SEN0472_READ 0x86
// --- DECLARACIÓN DE MÉTODOS PARA sen0472.c ---
double getO3(int fd, double *o3_valor); // O3 (Ozono)
int changeQA_O3(int fd); // Cambio a modo Q&A
#endif // SEN0472_H

@ -0,0 +1,10 @@
Component Description,QTY,Ref Des,Mfg P/N #,Manufacturer,Distributor P/N #
"Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206","2","C1,C2","GRM31C5C2A104JA01L","muRata(村田)",""
"Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD","1","D1","PMEG3020CEP,115","Nexperia(安世)",""
"Standoff","4","H1,H2,H3,H4","M2106-2545-AL","RAF Electronic Hardware",""
"Connector","1","J1","CON-SOCJ-2155","Gravitech",""
"Connector","4","J2,J3,J4,J5","TSW-104-07-T-S","",""
"Resistor, 10kΩ","4","R1,R2,R3,R4","RC1206FR-0710KL","",""
"Connector","1","RPi1","SC0195","",""
"Sensor","1","U1","HTU21D","Measurement Specialties",""
"Sensor","1","U2","BMP180","Bosch Sensortec",""
1 Component Description QTY Ref Des Mfg P/N # Manufacturer Distributor P/N #
2 Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206 2 C1,C2 GRM31C5C2A104JA01L muRata(村田)
3 Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD 1 D1 PMEG3020CEP,115 Nexperia(安世)
4 Standoff 4 H1,H2,H3,H4 M2106-2545-AL RAF Electronic Hardware
5 Connector 1 J1 CON-SOCJ-2155 Gravitech
6 Connector 4 J2,J3,J4,J5 TSW-104-07-T-S
7 Resistor, 10kΩ 4 R1,R2,R3,R4 RC1206FR-0710KL
8 Connector 1 RPi1 SC0195
9 Sensor 1 U1 HTU21D Measurement Specialties
10 Sensor 1 U2 BMP180 Bosch Sensortec

@ -0,0 +1,10 @@
Item #,Ref Des,Qty,Manufacturer,Mfg Part #,Description / Value,Package,Type,Your Instructions / Notes
"1","C1,C2","2","muRata(村田)","GRM31C5C2A104JA01L","Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206","C1206","",""
"2","D1","1","Nexperia(安世)","PMEG3020CEP,115","Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD","SOD-128_L3.7-W2.5-LS4.7-RD","",""
"3","H1,H2,H3,H4","4","RAF Electronic Hardware","M2106-2545-AL","Standoff","","",""
"4","J1","1","Gravitech","CON-SOCJ-2155","Connector","","",""
"5","J2,J3,J4,J5","4","","TSW-104-07-T-S","Connector","","",""
"6","R1,R2,R3,R4","4","","RC1206FR-0710KL","Resistor, 10kΩ","","",""
"7","RPi1","1","","SC0195","Connector","","",""
"8","U1","1","Measurement Specialties","HTU21D","Sensor","","",""
"9","U2","1","Bosch Sensortec","BMP180","Sensor","","",""
1 Item # Ref Des Qty Manufacturer Mfg Part # Description / Value Package Type Your Instructions / Notes
2 1 C1,C2 2 muRata(村田) GRM31C5C2A104JA01L Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206 C1206
3 2 D1 1 Nexperia(安世) PMEG3020CEP,115 Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD SOD-128_L3.7-W2.5-LS4.7-RD
4 3 H1,H2,H3,H4 4 RAF Electronic Hardware M2106-2545-AL Standoff
5 4 J1 1 Gravitech CON-SOCJ-2155 Connector
6 5 J2,J3,J4,J5 4 TSW-104-07-T-S Connector
7 6 R1,R2,R3,R4 4 RC1206FR-0710KL Resistor, 10kΩ
8 7 RPi1 1 SC0195 Connector
9 8 U1 1 Measurement Specialties HTU21D Sensor
10 9 U2 1 Bosch Sensortec BMP180 Sensor

@ -0,0 +1,25 @@
*Reference,*Manufacturer Part number,*QTY,*Original,Description,Purchase Link
"C1, C2","GRM31C5C2A104JA01L","2","","100V 100nF C0G ±5% 1206 Multilayer Ceramic Capacitors Multilayer Ceramic Capacitors MLCC - SMD/SMT
GRM31C5C2A104JA01L
C1206
LCSC Part Number: C405303
JLCPCB Part Class: Extended Part
Manufactured by muRata(村田) ",""
"D1","PMEG3020CEP,115","1","","Schottky Barrier Diodes (SBD)
PMEG3020CEP,115
SOD-128_L3.7-W2.5-LS4.7-RD
LCSC Part Number: C552871
JLCPCB Part Class: Extended Part
Manufactured by Nexperia(安世) ",""
"H1, H2, H3, H4","M2106-2545-AL","4","","Hex Standoff Threaded M2.5x0.45 Aluminum 0.433"" (11.00mm)
#Standoff ",""
"J1","CON-SOCJ-2155","1","","DC Power Connectors Power Jack/Connector 2.1mm x 5.5mm #CommonPartsLibrary #",""
"J2, J3, J4, J5","TSW-104-07-T-S","4","","Through Hole straight pin header, 01x04, 2.54mm pitch, single row #pinheader #tht",""
"R1, R2, R3, R4","RC1206FR-0710KL","4","","Chip Resistor 1206 (3216 Metric) Template #noprop",""
"RPi1","SC0195","1","","Raspberry Pi 2, 3, 4, 5 or 400 Model B+ connector with RPi board outline and mounting holes. good for Raspberry Pi Shield projects. Insulation Height
0.335"" (8.51mm) Compatible part number: PPPC202LFBN-RC #Raspberry_Pi #Shield #RPi #template #part",""
"U1","HTU21D","1","","Board Mount Humidity Sensors I.C 21DF RH/T DIGITAL MODULE #commonpartslibrary
#integratedcircuit
#humiditysensor
#digitalmodule",""
"U2","BMP180","1","","Pressure Sensor 4.35PSI ~ 15.95PSI (30kPa ~ 110kPa) Absolute 16 ~ 19 b 7-VLGA #CommonPartsLibrary #Sensor #Transducer",""
1 *Reference *Manufacturer Part number *QTY *Original Description Purchase Link
2 C1, C2 GRM31C5C2A104JA01L 2 100V 100nF C0G ±5% 1206 Multilayer Ceramic Capacitors Multilayer Ceramic Capacitors MLCC - SMD/SMT GRM31C5C2A104JA01L C1206 LCSC Part Number: C405303 JLCPCB Part Class: Extended Part Manufactured by muRata(村田)
3 D1 PMEG3020CEP,115 1 Schottky Barrier Diodes (SBD) PMEG3020CEP,115 SOD-128_L3.7-W2.5-LS4.7-RD LCSC Part Number: C552871 JLCPCB Part Class: Extended Part Manufactured by Nexperia(安世)
4 H1, H2, H3, H4 M2106-2545-AL 4 Hex Standoff Threaded M2.5x0.45 Aluminum 0.433" (11.00mm) #Standoff
5 J1 CON-SOCJ-2155 1 DC Power Connectors Power Jack/Connector 2.1mm x 5.5mm #CommonPartsLibrary #
6 J2, J3, J4, J5 TSW-104-07-T-S 4 Through Hole straight pin header, 01x04, 2.54mm pitch, single row #pinheader #tht
7 R1, R2, R3, R4 RC1206FR-0710KL 4 Chip Resistor 1206 (3216 Metric) Template #noprop
8 RPi1 SC0195 1 Raspberry Pi 2, 3, 4, 5 or 400 Model B+ connector with RPi board outline and mounting holes. good for Raspberry Pi Shield projects. Insulation Height 0.335" (8.51mm) Compatible part number: PPPC202LFBN-RC #Raspberry_Pi #Shield #RPi #template #part
9 U1 HTU21D 1 Board Mount Humidity Sensors I.C 21DF RH/T DIGITAL MODULE #commonpartslibrary #integratedcircuit #humiditysensor #digitalmodule
10 U2 BMP180 1 Pressure Sensor 4.35PSI ~ 15.95PSI (30kPa ~ 110kPa) Absolute 16 ~ 19 b 7-VLGA #CommonPartsLibrary #Sensor #Transducer

@ -0,0 +1,10 @@
Reference designators,Quantity,MPN,Manufacturer,Part description,Value,SPN,Supplier,Package
"C1,C2","2","GRM31C5C2A104JA01L","muRata(村田)","Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206","100nF","","","C1206"
"D1","1","PMEG3020CEP,115","Nexperia(安世)","Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD","","","","SOD-128_L3.7-W2.5-LS4.7-RD"
"H1,H2,H3,H4","4","M2106-2545-AL","RAF Electronic Hardware","Standoff","","","",""
"J1","1","CON-SOCJ-2155","Gravitech","Connector","","","",""
"J2,J3,J4,J5","4","TSW-104-07-T-S","","Connector","","","",""
"R1,R2,R3,R4","4","RC1206FR-0710KL","","Resistor, 10kΩ","10kΩ","","",""
"RPi1","1","SC0195","","Connector","","","",""
"U1","1","HTU21D","Measurement Specialties","Sensor","","","",""
"U2","1","BMP180","Bosch Sensortec","Sensor","","","",""
1 Reference designators Quantity MPN Manufacturer Part description Value SPN Supplier Package
2 C1,C2 2 GRM31C5C2A104JA01L muRata(村田) Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206 100nF C1206
3 D1 1 PMEG3020CEP,115 Nexperia(安世) Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD SOD-128_L3.7-W2.5-LS4.7-RD
4 H1,H2,H3,H4 4 M2106-2545-AL RAF Electronic Hardware Standoff
5 J1 1 CON-SOCJ-2155 Gravitech Connector
6 J2,J3,J4,J5 4 TSW-104-07-T-S Connector
7 R1,R2,R3,R4 4 RC1206FR-0710KL Resistor, 10kΩ 10kΩ
8 RPi1 1 SC0195 Connector
9 U1 1 HTU21D Measurement Specialties Sensor
10 U2 1 BMP180 Bosch Sensortec Sensor

@ -0,0 +1,10 @@
Designator,Quantity,Element UIDs,Part UID,Specification,Part Type,Domain,Compliance,JLCPCB Part Class,Operating Voltage,Operating Temperature,Reliability Target (MTBF),Role,Role Details,Software,Connector Type,Connector Gender,Connector Pitch,Connector Positions,Connector Rows,Power Requirements,Connectivity,Human Interface,Manufacturing Quantity Target,Manufacturer Part Number,LCSC Part Number,Digi-Key Part Number,Mouser Part Number,Arrow Part Number,Verical Part Number,TME Part Number,HQonline Part Number,Allow Substitutes,Manufacturer Name,Price Change Threshold,Lead Time Change Threshold,Pricing Notification Frequency,Preferred Distributors,Datasheet URL,Machine Datasheet URL,Implementation Details,License,Package or Case Code,Pin Type,Voltage,Voltage Rating,Forward Voltage,Reverse Voltage,Max Reverse Voltage,Diode Type,Mount,Integrated Circuit Type,Breakdown Voltage,Threshold Voltage,Max Output Voltage,Min Output Voltage,Initial Voltage on Reset,Resistance,Resistor Type,Gate Resistance,On Resistance,Off Resistance,Tolerance,Capacitance,Capacitor Type,Power,Power Rating,Current,Max Current,Desired Temperature Rise,Current Rating,Leakage Current,Saturation Current,Initial Current on Reset,Trigger Current,Holding Current,Frequency,Logic Function,Inductance,Inductor Shielding,Inductor Type,Beta,Drain to Source Voltage,Continuous Drain Current,Transistor Type,Gain,Coupling Coefficient,Ratio,Reference Design URL,Price,Purchase Url,Product Info Url,Product Image Url,Substitute Manufacturer Part Number,KiCAD Library Reference,Exclude from PCB,Controlled Impedance,Controlled Impedance Tolerance,PN Skew Max,Pair to Pair Skew Max,Pin Delay,Controlled Impedance Pair,Bus Group,Pair Role,Pin Role,Bus Type,Symbol Style,Symbol Size,Net Type,Designator Prefix,Exclude from BOM
"C1,C2","2","24b6ddf3-0bca-4cad-aec4-00f7d928af61,3c30401b-4abb-4407-9583-a0d1ea577630","b635a371-dabd-48ec-a934-df10294c05e9","Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206","Multilayer Ceramic Capacitors MLCC - SMD/SMT","","","Extended Part","","","","","","","","","","","","","","","","GRM31C5C2A104JA01L","C405303","","","","","","","","muRata(村田)","","","","","","","","","C1206","","","100V","","","","","","","","","","","","","","","","","±5%","100nF","Multilayer Ceramic Capacitors MLCC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","C",""
"D1","1","391671a1-b24a-4d48-b9b1-8af6dd246342","edb8b34e-b9d4-42cd-9bbe-d2d5da299641","Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD","Schottky Barrier Diodes (SBD)","","","Extended Part","","","","","","","","","","","","","","","","PMEG3020CEP,115","C552871","","","","","","","","Nexperia(安世)","","","","","","","","","SOD-128_L3.7-W2.5-LS4.7-RD","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","D",""
"H1,H2,H3,H4","4","73ccedf8-d5ad-4a2f-98bb-b12c18b11033,7c9a51e0-294f-4392-a806-49bf562a6ecc,9c3a431c-7fd7-4e55-a32a-f6850df2c0ad,c84e7944-bff4-42e1-bee9-891a8cd1da61","351f9178-ebc0-404e-8acd-92168fc157cb","Standoff","Standoff","","","","","","","","","","","","","","","","","","","M2106-2545-AL","","","","","","","","","RAF Electronic Hardware","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","H","false"
"J1","1","793c0223-5b5c-4933-88b3-c7a5436d6631","2db66d7a-704e-46a2-afa0-8ddb30e1105e","Connector","Connector","","","","","","","","","","","","","","","","","","","CON-SOCJ-2155","","","","","","","","","Gravitech","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","J",""
"J2,J3,J4,J5","4","0405d75e-2977-4ad7-b204-fc1c30d42c08,27e0c528-1c8b-4165-9e4d-4b40f63dd90d,4e8edf92-ff52-408e-a1f2-fa4cb9d00e52,8a6c2842-8062-433c-a659-3c6653045d14","36fff81d-1413-b341-746d-86810eb6d064","Connector","Connector","","","","","","","","","","","","","","","","","","","TSW-104-07-T-S","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","J",""
"R1,R2,R3,R4","4","2181dddd-de7f-4487-8405-dc510340c330,583d211a-8265-4019-893f-9947306470f7,c89d5920-01c8-48e5-9d3f-a780fde3002b,d2663bc3-aef1-4cf9-9f4a-a8549d47c35f","2c457990-6429-56ee-7c80-8897f711cd80","Resistor, 10kΩ","Resistor","","","","","","","","","","","","","","","","","","","RC1206FR-0710KL","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","10kΩ","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","R",""
"RPi1","1","bb0d762a-7bd1-424d-9e51-faa6f68f4c80","99d76b2a-8a6f-42ab-82f1-746911ec4c04","Connector","Connector","","","","","","","","","","","","","","","","","","","SC0195","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","parametric-v1","","","RPi",""
"U1","1","981973d6-5f20-484d-b677-8a2f1c2083c8","abad12c7-f0a1-4902-a244-b747ab9387aa","Sensor","Sensor","","","","","","","","","","","","","","","","","","","HTU21D","","","","","","","","","Measurement Specialties","","","","","","","","https://creativecommons.org/licenses/by/4.0/","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","U",""
"U2","1","bb5f0d01-3b05-4e41-86c8-155dbecbff2a","1c3a3d45-055a-4237-a2f0-5f58039abf20","Sensor","Sensor","","","","","","","","","","","","","","","","","","","BMP180","","","","","","","","","Bosch Sensortec","","","","","","","","https://creativecommons.org/licenses/by/4.0/","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","U",""
1 Designator Quantity Element UIDs Part UID Specification Part Type Domain Compliance JLCPCB Part Class Operating Voltage Operating Temperature Reliability Target (MTBF) Role Role Details Software Connector Type Connector Gender Connector Pitch Connector Positions Connector Rows Power Requirements Connectivity Human Interface Manufacturing Quantity Target Manufacturer Part Number LCSC Part Number Digi-Key Part Number Mouser Part Number Arrow Part Number Verical Part Number TME Part Number HQonline Part Number Allow Substitutes Manufacturer Name Price Change Threshold Lead Time Change Threshold Pricing Notification Frequency Preferred Distributors Datasheet URL Machine Datasheet URL Implementation Details License Package or Case Code Pin Type Voltage Voltage Rating Forward Voltage Reverse Voltage Max Reverse Voltage Diode Type Mount Integrated Circuit Type Breakdown Voltage Threshold Voltage Max Output Voltage Min Output Voltage Initial Voltage on Reset Resistance Resistor Type Gate Resistance On Resistance Off Resistance Tolerance Capacitance Capacitor Type Power Power Rating Current Max Current Desired Temperature Rise Current Rating Leakage Current Saturation Current Initial Current on Reset Trigger Current Holding Current Frequency Logic Function Inductance Inductor Shielding Inductor Type Beta Drain to Source Voltage Continuous Drain Current Transistor Type Gain Coupling Coefficient Ratio Reference Design URL Price Purchase Url Product Info Url Product Image Url Substitute Manufacturer Part Number KiCAD Library Reference Exclude from PCB Controlled Impedance Controlled Impedance Tolerance PN Skew Max Pair to Pair Skew Max Pin Delay Controlled Impedance Pair Bus Group Pair Role Pin Role Bus Type Symbol Style Symbol Size Net Type Designator Prefix Exclude from BOM
2 C1,C2 2 24b6ddf3-0bca-4cad-aec4-00f7d928af61,3c30401b-4abb-4407-9583-a0d1ea577630 b635a371-dabd-48ec-a934-df10294c05e9 Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206 Multilayer Ceramic Capacitors MLCC - SMD/SMT Extended Part GRM31C5C2A104JA01L C405303 muRata(村田) C1206 100V ±5% 100nF Multilayer Ceramic Capacitors MLCC C
3 D1 1 391671a1-b24a-4d48-b9b1-8af6dd246342 edb8b34e-b9d4-42cd-9bbe-d2d5da299641 Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD Schottky Barrier Diodes (SBD) Extended Part PMEG3020CEP,115 C552871 Nexperia(安世) SOD-128_L3.7-W2.5-LS4.7-RD D
4 H1,H2,H3,H4 4 73ccedf8-d5ad-4a2f-98bb-b12c18b11033,7c9a51e0-294f-4392-a806-49bf562a6ecc,9c3a431c-7fd7-4e55-a32a-f6850df2c0ad,c84e7944-bff4-42e1-bee9-891a8cd1da61 351f9178-ebc0-404e-8acd-92168fc157cb Standoff Standoff M2106-2545-AL RAF Electronic Hardware H false
5 J1 1 793c0223-5b5c-4933-88b3-c7a5436d6631 2db66d7a-704e-46a2-afa0-8ddb30e1105e Connector Connector CON-SOCJ-2155 Gravitech J
6 J2,J3,J4,J5 4 0405d75e-2977-4ad7-b204-fc1c30d42c08,27e0c528-1c8b-4165-9e4d-4b40f63dd90d,4e8edf92-ff52-408e-a1f2-fa4cb9d00e52,8a6c2842-8062-433c-a659-3c6653045d14 36fff81d-1413-b341-746d-86810eb6d064 Connector Connector TSW-104-07-T-S J
7 R1,R2,R3,R4 4 2181dddd-de7f-4487-8405-dc510340c330,583d211a-8265-4019-893f-9947306470f7,c89d5920-01c8-48e5-9d3f-a780fde3002b,d2663bc3-aef1-4cf9-9f4a-a8549d47c35f 2c457990-6429-56ee-7c80-8897f711cd80 Resistor, 10kΩ Resistor RC1206FR-0710KL 10kΩ R
8 RPi1 1 bb0d762a-7bd1-424d-9e51-faa6f68f4c80 99d76b2a-8a6f-42ab-82f1-746911ec4c04 Connector Connector SC0195 parametric-v1 RPi
9 U1 1 981973d6-5f20-484d-b677-8a2f1c2083c8 abad12c7-f0a1-4902-a244-b747ab9387aa Sensor Sensor HTU21D Measurement Specialties https://creativecommons.org/licenses/by/4.0/ U
10 U2 1 bb5f0d01-3b05-4e41-86c8-155dbecbff2a 1c3a3d45-055a-4237-a2f0-5f58039abf20 Sensor Sensor BMP180 Bosch Sensortec https://creativecommons.org/licenses/by/4.0/ U

@ -0,0 +1,10 @@
Comment,Designator,Footprint,JLCPCB Part #,LCSC Part #
"GRM31C5C2A104JA01L","C1, C2","C1206","","C405303"
"PMEG3020CEP,115","D1","SOD-128_L3.7-W2.5-LS4.7-RD","","C552871"
"M2106-2545-AL","H1, H2, H3, H4","","",""
"CON-SOCJ-2155","J1","","",""
"TSW-104-07-T-S","J2, J3, J4, J5","","",""
"RC1206FR-0710KL","R1, R2, R3, R4","","",""
"SC0195","RPi1","","",""
"HTU21D","U1","","",""
"BMP180","U2","","",""
1 Comment Designator Footprint JLCPCB Part # LCSC Part #
2 GRM31C5C2A104JA01L C1, C2 C1206 C405303
3 PMEG3020CEP,115 D1 SOD-128_L3.7-W2.5-LS4.7-RD C552871
4 M2106-2545-AL H1, H2, H3, H4
5 CON-SOCJ-2155 J1
6 TSW-104-07-T-S J2, J3, J4, J5
7 RC1206FR-0710KL R1, R2, R3, R4
8 SC0195 RPi1
9 HTU21D U1
10 BMP180 U2

@ -0,0 +1,10 @@
Item #,Designator,Qty,Manufacturer,Mfg Part #,Description / Value,Package/Footprint,Type,Your Instructions / Notes
"1","C1,C2","2","muRata(村田)","GRM31C5C2A104JA01L","Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206","C1206","",""
"2","D1","1","Nexperia(安世)","PMEG3020CEP,115","Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD","SOD-128_L3.7-W2.5-LS4.7-RD","",""
"3","H1,H2,H3,H4","4","RAF Electronic Hardware","M2106-2545-AL","Standoff","","",""
"4","J1","1","Gravitech","CON-SOCJ-2155","Connector","","",""
"5","J2,J3,J4,J5","4","","TSW-104-07-T-S","Connector","","",""
"6","R1,R2,R3,R4","4","","RC1206FR-0710KL","Resistor, 10kΩ","","",""
"7","RPi1","1","","SC0195","Connector","","",""
"8","U1","1","Measurement Specialties","HTU21D","Sensor","","",""
"9","U2","1","Bosch Sensortec","BMP180","Sensor","","",""
1 Item # Designator Qty Manufacturer Mfg Part # Description / Value Package/Footprint Type Your Instructions / Notes
2 1 C1,C2 2 muRata(村田) GRM31C5C2A104JA01L Multilayer Ceramic Capacitors MLCC - SMD/SMT, 100nF, C1206 C1206
3 2 D1 1 Nexperia(安世) PMEG3020CEP,115 Schottky Barrier Diodes (SBD), SOD-128_L3.7-W2.5-LS4.7-RD SOD-128_L3.7-W2.5-LS4.7-RD
4 3 H1,H2,H3,H4 4 RAF Electronic Hardware M2106-2545-AL Standoff
5 4 J1 1 Gravitech CON-SOCJ-2155 Connector
6 5 J2,J3,J4,J5 4 TSW-104-07-T-S Connector
7 6 R1,R2,R3,R4 4 RC1206FR-0710KL Resistor, 10kΩ
8 7 RPi1 1 SC0195 Connector
9 8 U1 1 Measurement Specialties HTU21D Sensor
10 9 U2 1 Bosch Sensortec BMP180 Sensor

@ -0,0 +1,10 @@
Designator,Manufacturer Part Number or Seeed SKU,QTY,Link
"C1,C2","GRM31C5C2A104JA01L","2",""
"D1","PMEG3020CEP,115","1",""
"H1,H2,H3,H4","M2106-2545-AL","4",""
"J1","CON-SOCJ-2155","1",""
"J2,J3,J4,J5","TSW-104-07-T-S","4",""
"R1,R2,R3,R4","RC1206FR-0710KL","4",""
"RPi1","SC0195","1",""
"U1","HTU21D","1",""
"U2","BMP180","1",""
1 Designator Manufacturer Part Number or Seeed SKU QTY Link
2 C1,C2 GRM31C5C2A104JA01L 2
3 D1 PMEG3020CEP,115 1
4 H1,H2,H3,H4 M2106-2545-AL 4
5 J1 CON-SOCJ-2155 1
6 J2,J3,J4,J5 TSW-104-07-T-S 4
7 R1,R2,R3,R4 RC1206FR-0710KL 4
8 RPi1 SC0195 1
9 U1 HTU21D 1
10 U2 BMP180 1

@ -0,0 +1,20 @@
Designator,Mid X,Mid Y,Layer,Rotation,Package,Value
"C1","-14.1412mm","15.7713mm","Top",180,"C1206","100nF"
"C2","-24.3485mm","12.4544mm","Top",0,"C1206","100nF"
"D1","23.0604mm","-10.6429mm","Top",90,"SOD-128_L3.7-W2.5-LS4.7-RD",""
"H1","-28.8784mm","-24.6985mm","Bottom",0,"",""
"H2","29.1216mm","24.3015mm","Bottom",0,"",""
"H3","29.1216mm","-24.6985mm","Bottom",0,"",""
"H4","-28.8784mm","24.3015mm","Bottom",0,"",""
"J1","18.9638mm","-17.0821mm","Top",270,"",""
"J2","15.3438mm","-7.4333mm","Top",90,"",""
"J3","15.3438mm","-2.2688mm","Top",90,"",""
"J4","15.3438mm","2.2811mm","Top",90,"",""
"J5","15.3438mm","7.0363mm","Top",90,"",""
"R1","-24.3337mm","19.0737mm","Top",0,"","10kΩ"
"R2","-21.4087mm","15.7713mm","Top",180,"","10kΩ"
"R3","8.6087mm","16.5934mm","Top",180,"","10kΩ"
"R4","10.0712mm","12.7015mm","Top",180,"","10kΩ"
"RPi1","0.1216mm","0.4365mm","Bottom",0,"",""
"U1","-14.9562mm","11.9299mm","Top",180,"",""
"U2","-25.7962mm","8.4863mm","Top",270,"",""
1 Designator Mid X Mid Y Layer Rotation Package Value
2 C1 -14.1412mm 15.7713mm Top 180 C1206 100nF
3 C2 -24.3485mm 12.4544mm Top 0 C1206 100nF
4 D1 23.0604mm -10.6429mm Top 90 SOD-128_L3.7-W2.5-LS4.7-RD
5 H1 -28.8784mm -24.6985mm Bottom 0
6 H2 29.1216mm 24.3015mm Bottom 0
7 H3 29.1216mm -24.6985mm Bottom 0
8 H4 -28.8784mm 24.3015mm Bottom 0
9 J1 18.9638mm -17.0821mm Top 270
10 J2 15.3438mm -7.4333mm Top 90
11 J3 15.3438mm -2.2688mm Top 90
12 J4 15.3438mm 2.2811mm Top 90
13 J5 15.3438mm 7.0363mm Top 90
14 R1 -24.3337mm 19.0737mm Top 0 10kΩ
15 R2 -21.4087mm 15.7713mm Top 180 10kΩ
16 R3 8.6087mm 16.5934mm Top 180 10kΩ
17 R4 10.0712mm 12.7015mm Top 180 10kΩ
18 RPi1 0.1216mm 0.4365mm Bottom 0
19 U1 -14.9562mm 11.9299mm Top 180
20 U2 -25.7962mm 8.4863mm Top 270

@ -0,0 +1,14 @@
Project Name: RPi4-Air-Quality
Project Version: #5f84d571
Project Url: https://www.flux.ai/gmarx/rpi4-air-quality-b7dcy~qe
Project Description:
Template for Raspberry Pi 4 Shield. Include an official pinout so you will always know Raspberry Pi names, the alternative roles of pins, which one is SDA, or SCL, etc. On PCB you can find the 3D model of the Raspberry Pi itself along with the board outline on the silkscreen.
#RaspberryPi #Raspberry #Pi #RPi #Shield #template #project #project-template #raspberry
Project Properties:
License:
https://creativecommons.org/licenses/by/4.0/

@ -0,0 +1,340 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:17+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Copper,L4,Bot*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:17*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%TA.AperFunction,ComponentPad*%
%ADD10C,0.600000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD11C,1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD12R,1.700000X1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD13C,2.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD14O,2.250000X4.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD15O,5.000000X2.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD16O,4.500000X2.250000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD17C,1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD18R,1.524000X1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD19C,6.200000*%
%TD*%
%TA.AperFunction,Conductor*%
%ADD20C,1.000000*%
%TD*%
%TA.AperFunction,Conductor*%
%ADD21C,0.600000*%
%TD*%
G04 APERTURE END LIST*
D10*
%TO.P,,c3241ddd052a4cd29f34c51896eee025throughHole00*%
%TO.N,Net 3*%
X64456000Y-50146175D03*
%TO.P,,3aa9771e4e924536a5d3eb6e4053496dthroughHole00*%
%TO.N,Net 4*%
X52617600Y-32372375D03*
%TO.P,,714774cca06442519315d8ec73f7f803throughHole00*%
X54912200Y-43972075D03*
%TO.P,,f6df285557064514b98b1b6b7aa4936fthroughHole00*%
X54912200Y-34666975D03*
%TO.P,,35150496d72b4d88996f5c23762e5bf2throughHole00*%
%TO.N,Net 8*%
X63894500Y-54546175D03*
%TO.P,,aeea024bc7bf4ebcb47f536edcaab0fethroughHole00*%
%TO.N,Net 5*%
X56339400Y-31710575D03*
%TO.P,,2a33a8bd8bc64a68a844a2a93dba4e08throughHole00*%
%TO.N,GND*%
X59992200Y-61785375D03*
%TO.P,,35871135b5cb4ecf9a59823d976e4a7dthroughHole00*%
X61888700Y-31150575D03*
%TD*%
D11*
%TO.P,J3,Pin_4*%
%TO.N,Net 3*%
X62532286Y-43972389D03*
%TO.P,J3,Pin_3*%
%TO.N,GND*%
X59992286Y-43972408D03*
%TO.P,J3,Pin_2*%
%TO.N,Net 5*%
X57452286Y-43972426D03*
D12*
%TO.P,J3,Pin_1*%
%TO.N,Net 4*%
X54912286Y-43972445D03*
%TD*%
D11*
%TO.P,J4,Pin_4*%
%TO.N,Net 3*%
X62532286Y-39422489D03*
%TO.P,J4,Pin_3*%
%TO.N,GND*%
X59992286Y-39422508D03*
%TO.P,J4,Pin_2*%
%TO.N,Net 5*%
X57452286Y-39422526D03*
D12*
%TO.P,J4,Pin_1*%
%TO.N,Net 4*%
X54912286Y-39422545D03*
%TD*%
D11*
%TO.P,J5,Pin_4*%
%TO.N,Net 3*%
X62532286Y-34667289D03*
%TO.P,J5,Pin_3*%
%TO.N,GND*%
X59992286Y-34667308D03*
%TO.P,J5,Pin_2*%
%TO.N,Net 5*%
X57452286Y-34667326D03*
D12*
%TO.P,J5,Pin_1*%
%TO.N,Net 4*%
X54912286Y-34667345D03*
%TD*%
D13*
%TO.P,H4,P1*%
%TO.N,N/C*%
X14500000Y-17401775D03*
%TD*%
D14*
%TO.P,J1,~*%
%TO.N,GND*%
X64692206Y-58785365D03*
D15*
%TO.N,Net 8*%
X59992200Y-55785375D03*
D16*
%TO.N,GND*%
X59992212Y-61785375D03*
%TD*%
D13*
%TO.P,H3,P1*%
%TO.N,N/C*%
X72500000Y-66401775D03*
%TD*%
D11*
%TO.P,J2,Pin_4*%
%TO.N,Net 3*%
X62532286Y-49136889D03*
%TO.P,J2,Pin_3*%
%TO.N,GND*%
X59992286Y-49136908D03*
%TO.P,J2,Pin_2*%
%TO.N,Net 5*%
X57452286Y-49136926D03*
D12*
%TO.P,J2,Pin_1*%
%TO.N,Net 4*%
X54912286Y-49136945D03*
%TD*%
D13*
%TO.P,H1,P1*%
%TO.N,N/C*%
X14500000Y-66401775D03*
%TD*%
D17*
%TO.P,RPi1,3V3*%
%TO.N,Net 6*%
X39690000Y-18671775D03*
%TO.P,RPi1,MOSI0/GPIO10*%
%TO.N,N/C*%
X42230000Y-18671775D03*
%TO.P,RPi1,GPIO20/MOSI1*%
X65090000Y-16131775D03*
%TO.P,RPi1,GND*%
X60010000Y-16131775D03*
%TO.P,RPi1,PWM1/GPIO13*%
X60010000Y-18671775D03*
%TO.P,RPi1,SCL/GPIO3*%
%TO.N,Net 2*%
X24450000Y-18671775D03*
%TO.P,RPi1,GPIO16*%
%TO.N,N/C*%
X62550000Y-16131775D03*
%TO.P,RPi1,GPIO25*%
X44770000Y-16131775D03*
%TO.P,RPi1,GPIO18/PWM0*%
X32070000Y-16131775D03*
%TO.P,RPi1,5V*%
X21910000Y-16131775D03*
%TO.P,RPi1,GPIO26*%
X65090000Y-18671775D03*
%TO.P,RPi1,GND*%
%TO.N,GND*%
X24450000Y-16131775D03*
%TO.P,RPi1,SCLK0/GPIO11*%
%TO.N,N/C*%
X47310000Y-18671775D03*
%TO.P,RPi1,ID_SC/GPIO1*%
X52390000Y-16131775D03*
%TO.P,RPi1,GND*%
X29530000Y-18671775D03*
%TO.P,RPi1,GPIO24*%
X39690000Y-16131775D03*
%TO.P,RPi1,GND*%
%TO.N,GND*%
X54930000Y-16131775D03*
X49850000Y-18671775D03*
%TO.P,RPi1,GPIO19/MISO1*%
%TO.N,N/C*%
X62550000Y-18671775D03*
%TO.P,RPi1,MISO0/GPIO9*%
X44770000Y-18671775D03*
%TO.P,RPi1,GPIO22*%
X37150000Y-18671775D03*
%TO.P,RPi1,GND*%
X34610000Y-16131775D03*
%TO.P,RPi1,GPIO21/SCLK1*%
X67630000Y-16131775D03*
%TO.P,RPi1,GPIO17*%
X32070000Y-18671775D03*
%TO.P,RPi1,GPIO15/RXD*%
X29530000Y-16131775D03*
%TO.P,RPi1,GPIO27*%
X34610000Y-18671775D03*
%TO.P,RPi1,GND*%
X42230000Y-16131775D03*
%TO.P,RPi1,GCLK1/GPIO5*%
%TO.N,Net 5*%
X54930000Y-18671775D03*
%TO.P,RPi1,GPIO14/TXD*%
%TO.N,N/C*%
X26990000Y-16131775D03*
%TO.P,RPi1,ID_SD/GPIO0*%
X52390000Y-18671775D03*
%TO.P,RPi1,GPIO23*%
X37150000Y-16131775D03*
%TO.P,RPi1,GCLK2/GPIO6*%
X57470000Y-18671775D03*
%TO.P,RPi1,~CE0~/GPIO8*%
X47310000Y-16131775D03*
%TO.P,RPi1,5V*%
X19370000Y-16131775D03*
%TO.P,RPi1,GND*%
X67630000Y-18671775D03*
%TO.P,RPi1,SDA/GPIO2*%
%TO.N,Net 7*%
X21910000Y-18671775D03*
D18*
%TO.P,RPi1,3V3*%
%TO.N,Net 1*%
X19370000Y-18671775D03*
D17*
%TO.P,RPi1,~CE1~/GPIO7*%
%TO.N,N/C*%
X49850000Y-16131775D03*
%TO.P,RPi1,GCLK0/GPIO4*%
%TO.N,Net 4*%
X26990000Y-18671775D03*
%TO.P,RPi1,PWM0/GPIO12*%
%TO.N,N/C*%
X57470000Y-16131775D03*
D19*
%TO.P,RPi1,S1*%
X14500000Y-17401775D03*
%TO.P,RPi1,S2*%
X72500000Y-17401775D03*
%TO.P,RPi1,S3*%
X72500000Y-66401775D03*
%TO.P,RPi1,S4*%
X14500000Y-66401775D03*
%TD*%
D13*
%TO.P,H2,P1*%
%TO.N,N/C*%
X72500000Y-17401775D03*
%TD*%
D20*
%TO.N,Net 3*%
X63541900Y-50146175D02*
X62532300Y-49136575D01*
X62532200Y-34666975D02*
X62532200Y-49136575D01*
X64456000Y-50146175D02*
X63541800Y-50146175D01*
D21*
%TO.N,Net 4*%
X52617600Y-32372375D02*
X54912200Y-34666975D01*
D20*
X54912200Y-34666975D02*
X54912200Y-49136575D01*
%TO.N,Net 8*%
X63894500Y-54546175D02*
X63894500Y-54546175D01*
X59992200Y-55785375D02*
X62655200Y-55785375D01*
X62655300Y-55785375D02*
X63894500Y-54546175D01*
D21*
%TO.N,Net 5*%
X56339400Y-31710575D02*
X56790400Y-31710575D01*
X57452200Y-32372375D02*
X57452200Y-34666975D01*
X56790400Y-31710575D02*
X57452200Y-32372375D01*
D20*
X57452200Y-34666975D02*
X57452200Y-49136575D01*
%TO.N,GND*%
X59992200Y-61436575D02*
X54341000Y-55785375D01*
X59302800Y-50823575D02*
X54341000Y-55785375D01*
X62655200Y-61785375D02*
X59992200Y-61785375D01*
X59992200Y-50823575D02*
X59992200Y-50823575D01*
X59992200Y-61785375D02*
X59992200Y-61436575D01*
X59992200Y-49136575D02*
X59992200Y-49136575D01*
D21*
X61888700Y-31150575D02*
X61888700Y-32770575D01*
D20*
X64692200Y-58785375D02*
X64692200Y-59748375D01*
X59992200Y-50823575D02*
X59302800Y-50823575D01*
X59992200Y-34666975D02*
X59992200Y-49136575D01*
D21*
X61888700Y-32770575D02*
X59992300Y-34666975D01*
D20*
X54341000Y-55785375D02*
X54341000Y-55785375D01*
X64692200Y-59748375D02*
X62655200Y-61785375D01*
X59992200Y-50823575D02*
X59992200Y-49136575D01*
%TD*%
M02*

@ -0,0 +1,131 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:17+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Soldermask,Bot*%
%TF.FilePolarity,Negative*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:17*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10C,1.700000*%
%ADD11R,1.700000X1.700000*%
%ADD12C,2.700000*%
%ADD13O,2.250000X4.500000*%
%ADD14O,5.000000X2.500000*%
%ADD15O,4.500000X2.250000*%
%ADD16C,1.524000*%
%ADD17R,1.524000X1.524000*%
%ADD18C,6.200000*%
G04 APERTURE END LIST*
D10*
%TO.C,J3*%
X62532286Y-43972389D03*
X59992286Y-43972408D03*
X57452286Y-43972426D03*
D11*
X54912286Y-43972445D03*
%TD*%
D10*
%TO.C,J4*%
X62532286Y-39422489D03*
X59992286Y-39422508D03*
X57452286Y-39422526D03*
D11*
X54912286Y-39422545D03*
%TD*%
D10*
%TO.C,J5*%
X62532286Y-34667289D03*
X59992286Y-34667308D03*
X57452286Y-34667326D03*
D11*
X54912286Y-34667345D03*
%TD*%
D12*
%TO.C,H4*%
X14500000Y-17401775D03*
%TD*%
D13*
%TO.C,J1*%
X64692206Y-58785365D03*
D14*
X59992200Y-55785375D03*
D15*
X59992212Y-61785375D03*
%TD*%
D12*
%TO.C,H3*%
X72500000Y-66401775D03*
%TD*%
D10*
%TO.C,J2*%
X62532286Y-49136889D03*
X59992286Y-49136908D03*
X57452286Y-49136926D03*
D11*
X54912286Y-49136945D03*
%TD*%
D12*
%TO.C,H1*%
X14500000Y-66401775D03*
%TD*%
D16*
%TO.C,RPi1*%
X39690000Y-18671775D03*
X42230000Y-18671775D03*
X65090000Y-16131775D03*
X60010000Y-16131775D03*
X60010000Y-18671775D03*
X24450000Y-18671775D03*
X62550000Y-16131775D03*
X44770000Y-16131775D03*
X32070000Y-16131775D03*
X21910000Y-16131775D03*
X65090000Y-18671775D03*
X24450000Y-16131775D03*
X47310000Y-18671775D03*
X52390000Y-16131775D03*
X29530000Y-18671775D03*
X39690000Y-16131775D03*
X54930000Y-16131775D03*
X49850000Y-18671775D03*
X62550000Y-18671775D03*
X44770000Y-18671775D03*
X37150000Y-18671775D03*
X34610000Y-16131775D03*
X67630000Y-16131775D03*
X32070000Y-18671775D03*
X29530000Y-16131775D03*
X34610000Y-18671775D03*
X42230000Y-16131775D03*
X54930000Y-18671775D03*
X26990000Y-16131775D03*
X52390000Y-18671775D03*
X37150000Y-16131775D03*
X57470000Y-18671775D03*
X47310000Y-16131775D03*
X19370000Y-16131775D03*
X67630000Y-18671775D03*
X21910000Y-18671775D03*
D17*
X19370000Y-18671775D03*
D16*
X49850000Y-16131775D03*
X26990000Y-18671775D03*
X57470000Y-16131775D03*
D18*
X14500000Y-17401775D03*
X72500000Y-17401775D03*
X72500000Y-66401775D03*
X14500000Y-66401775D03*
%TD*%
D12*
%TO.C,H2*%
X72500000Y-17401775D03*
%TD*%
M02*

@ -0,0 +1,131 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:18+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Paste,Bot*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:18*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10C,1.700000*%
%ADD11R,1.700000X1.700000*%
%ADD12C,2.700000*%
%ADD13O,2.250000X4.500000*%
%ADD14O,5.000000X2.500000*%
%ADD15O,4.500000X2.250000*%
%ADD16C,1.524000*%
%ADD17R,1.524000X1.524000*%
%ADD18C,6.200000*%
G04 APERTURE END LIST*
D10*
%TO.C,J3*%
X62532286Y-43972389D03*
X59992286Y-43972408D03*
X57452286Y-43972426D03*
D11*
X54912286Y-43972445D03*
%TD*%
D10*
%TO.C,J4*%
X62532286Y-39422489D03*
X59992286Y-39422508D03*
X57452286Y-39422526D03*
D11*
X54912286Y-39422545D03*
%TD*%
D10*
%TO.C,J5*%
X62532286Y-34667289D03*
X59992286Y-34667308D03*
X57452286Y-34667326D03*
D11*
X54912286Y-34667345D03*
%TD*%
D12*
%TO.C,H4*%
X14500000Y-17401775D03*
%TD*%
D13*
%TO.C,J1*%
X64692206Y-58785365D03*
D14*
X59992200Y-55785375D03*
D15*
X59992212Y-61785375D03*
%TD*%
D12*
%TO.C,H3*%
X72500000Y-66401775D03*
%TD*%
D10*
%TO.C,J2*%
X62532286Y-49136889D03*
X59992286Y-49136908D03*
X57452286Y-49136926D03*
D11*
X54912286Y-49136945D03*
%TD*%
D12*
%TO.C,H1*%
X14500000Y-66401775D03*
%TD*%
D16*
%TO.C,RPi1*%
X39690000Y-18671775D03*
X42230000Y-18671775D03*
X65090000Y-16131775D03*
X60010000Y-16131775D03*
X60010000Y-18671775D03*
X24450000Y-18671775D03*
X62550000Y-16131775D03*
X44770000Y-16131775D03*
X32070000Y-16131775D03*
X21910000Y-16131775D03*
X65090000Y-18671775D03*
X24450000Y-16131775D03*
X47310000Y-18671775D03*
X52390000Y-16131775D03*
X29530000Y-18671775D03*
X39690000Y-16131775D03*
X54930000Y-16131775D03*
X49850000Y-18671775D03*
X62550000Y-18671775D03*
X44770000Y-18671775D03*
X37150000Y-18671775D03*
X34610000Y-16131775D03*
X67630000Y-16131775D03*
X32070000Y-18671775D03*
X29530000Y-16131775D03*
X34610000Y-18671775D03*
X42230000Y-16131775D03*
X54930000Y-18671775D03*
X26990000Y-16131775D03*
X52390000Y-18671775D03*
X37150000Y-16131775D03*
X57470000Y-18671775D03*
X47310000Y-16131775D03*
X19370000Y-16131775D03*
X67630000Y-18671775D03*
X21910000Y-18671775D03*
D17*
X19370000Y-18671775D03*
D16*
X49850000Y-16131775D03*
X26990000Y-18671775D03*
X57470000Y-16131775D03*
D18*
X14500000Y-17401775D03*
X72500000Y-17401775D03*
X72500000Y-66401775D03*
X14500000Y-66401775D03*
%TD*%
D12*
%TO.C,H2*%
X72500000Y-17401775D03*
%TD*%
M02*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,713 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:19+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Copper,L1,Top*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:19*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
G04 Aperture macros list end*
%TA.AperFunction,ComponentPad*%
%ADD10C,0.600000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD11C,1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD12R,1.700000X1.700000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD13RoundRect,0.131300X0.431200X0.743700X-0.431200X0.743700X-0.431200X-0.743700X0.431200X-0.743700X0*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD14R,1.490000X1.730000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD15R,1.700000X1.250000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD16C,2.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD17O,2.250000X4.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD18O,5.000000X2.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD19O,4.500000X2.250000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD20R,0.800000X0.450000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD21R,1.600000X2.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD22C,1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD23R,1.524000X1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD24C,6.200000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD25R,0.700000X0.600000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD26R,0.500000X0.600000*%
%TD*%
%TA.AperFunction,SMDPad,CuDef*%
%ADD27RoundRect,0.131300X-0.431200X-0.743700X0.431200X-0.743700X0.431200X0.743700X-0.431200X0.743700X0*%
%TD*%
%TA.AperFunction,Conductor*%
%ADD28C,1.000000*%
%TD*%
%TA.AperFunction,Conductor*%
%ADD29C,0.600000*%
%TD*%
%TA.AperFunction,Conductor*%
%ADD30C,0.400000*%
%TD*%
G04 APERTURE END LIST*
D10*
%TO.P,,c3241ddd052a4cd29f34c51896eee025throughHole00*%
%TO.N,Net 3*%
X64456000Y-50146175D03*
%TO.P,,3aa9771e4e924536a5d3eb6e4053496dthroughHole00*%
%TO.N,Net 4*%
X52617600Y-32372375D03*
%TO.P,,714774cca06442519315d8ec73f7f803throughHole00*%
X54912200Y-43972075D03*
%TO.P,,f6df285557064514b98b1b6b7aa4936fthroughHole00*%
X54912200Y-34666975D03*
%TO.P,,35150496d72b4d88996f5c23762e5bf2throughHole00*%
%TO.N,Net 8*%
X63894500Y-54546175D03*
%TO.P,,aeea024bc7bf4ebcb47f536edcaab0fethroughHole00*%
%TO.N,Net 5*%
X56339400Y-31710575D03*
%TO.P,,2a33a8bd8bc64a68a844a2a93dba4e08throughHole00*%
%TO.N,GND*%
X59992200Y-61785375D03*
%TO.P,,35871135b5cb4ecf9a59823d976e4a7dthroughHole00*%
X61888700Y-31150575D03*
%TD*%
D11*
%TO.P,J3,Pin_4*%
%TO.N,Net 3*%
X62532286Y-43972389D03*
%TO.P,J3,Pin_3*%
%TO.N,GND*%
X59992286Y-43972408D03*
%TO.P,J3,Pin_2*%
%TO.N,Net 5*%
X57452286Y-43972426D03*
D12*
%TO.P,J3,Pin_1*%
%TO.N,Net 4*%
X54912286Y-43972445D03*
%TD*%
D13*
%TO.P,R4,P1*%
%TO.N,Net 5*%
X54912100Y-29001783D03*
%TO.P,R4,P2*%
%TO.N,Net 6*%
X51987100Y-29001767D03*
%TD*%
D14*
%TO.P,C2,1*%
%TO.N,Net 1*%
X17439900Y-29248875D03*
%TO.P,C2,2*%
%TO.N,GND*%
X20619900Y-29248875D03*
%TD*%
D11*
%TO.P,J4,Pin_4*%
%TO.N,Net 3*%
X62532286Y-39422489D03*
%TO.P,J4,Pin_3*%
%TO.N,GND*%
X59992286Y-39422508D03*
%TO.P,J4,Pin_2*%
%TO.N,Net 5*%
X57452286Y-39422526D03*
D12*
%TO.P,J4,Pin_1*%
%TO.N,Net 4*%
X54912286Y-39422545D03*
%TD*%
D15*
%TO.P,D1,1*%
%TO.N,Net 8*%
X66438816Y-54546175D03*
%TO.P,D1,2*%
%TO.N,Net 3*%
X66438784Y-50146175D03*
%TD*%
D14*
%TO.P,C1,1*%
%TO.N,GND*%
X30827200Y-25931983D03*
%TO.P,C1,2*%
%TO.N,Net 6*%
X27647200Y-25931967D03*
%TD*%
D11*
%TO.P,J5,Pin_4*%
%TO.N,Net 3*%
X62532286Y-34667289D03*
%TO.P,J5,Pin_3*%
%TO.N,GND*%
X59992286Y-34667308D03*
%TO.P,J5,Pin_2*%
%TO.N,Net 5*%
X57452286Y-34667326D03*
D12*
%TO.P,J5,Pin_1*%
%TO.N,Net 4*%
X54912286Y-34667345D03*
%TD*%
D13*
%TO.P,R3,P1*%
%TO.N,Net 4*%
X53449600Y-25109883D03*
%TO.P,R3,P2*%
%TO.N,Net 6*%
X50524600Y-25109867D03*
%TD*%
D16*
%TO.P,H4,P1*%
%TO.N,N/C*%
X14500000Y-17401775D03*
%TD*%
D17*
%TO.P,J1,~*%
%TO.N,GND*%
X64692206Y-58785365D03*
D18*
%TO.N,Net 8*%
X59992200Y-55785375D03*
D19*
%TO.N,GND*%
X59992212Y-61785375D03*
%TD*%
D16*
%TO.P,H3,P1*%
%TO.N,N/C*%
X72500000Y-66401775D03*
%TD*%
D11*
%TO.P,J2,Pin_4*%
%TO.N,Net 3*%
X62532286Y-49136889D03*
%TO.P,J2,Pin_3*%
%TO.N,GND*%
X59992286Y-49136908D03*
%TO.P,J2,Pin_2*%
%TO.N,Net 5*%
X57452286Y-49136926D03*
D12*
%TO.P,J2,Pin_1*%
%TO.N,Net 4*%
X54912286Y-49136945D03*
%TD*%
D20*
%TO.P,U1,NC_1*%
%TO.N,N/C*%
X29872205Y-28773383D03*
%TO.P,U1,VDD*%
%TO.N,Net 6*%
X26972200Y-29773367D03*
%TO.P,U1,SCK*%
%TO.N,Net 2*%
X26972195Y-30773367D03*
%TO.P,U1,DATA*%
%TO.N,Net 7*%
X29872195Y-30773383D03*
%TO.P,U1,GND*%
%TO.N,GND*%
X29872200Y-29773383D03*
D21*
%TO.P,U1,EP*%
%TO.N,N/C*%
X28422200Y-29773375D03*
D20*
%TO.P,U1,NC_2*%
X26972205Y-28773367D03*
%TD*%
D16*
%TO.P,H1,P1*%
%TO.N,N/C*%
X14500000Y-66401775D03*
%TD*%
D22*
%TO.P,RPi1,3V3*%
%TO.N,Net 6*%
X39690000Y-18671775D03*
%TO.P,RPi1,MOSI0/GPIO10*%
%TO.N,N/C*%
X42230000Y-18671775D03*
%TO.P,RPi1,GPIO20/MOSI1*%
X65090000Y-16131775D03*
%TO.P,RPi1,GND*%
X60010000Y-16131775D03*
%TO.P,RPi1,PWM1/GPIO13*%
X60010000Y-18671775D03*
%TO.P,RPi1,SCL/GPIO3*%
%TO.N,Net 2*%
X24450000Y-18671775D03*
%TO.P,RPi1,GPIO16*%
%TO.N,N/C*%
X62550000Y-16131775D03*
%TO.P,RPi1,GPIO25*%
X44770000Y-16131775D03*
%TO.P,RPi1,GPIO18/PWM0*%
X32070000Y-16131775D03*
%TO.P,RPi1,5V*%
X21910000Y-16131775D03*
%TO.P,RPi1,GPIO26*%
X65090000Y-18671775D03*
%TO.P,RPi1,GND*%
%TO.N,GND*%
X24450000Y-16131775D03*
%TO.P,RPi1,SCLK0/GPIO11*%
%TO.N,N/C*%
X47310000Y-18671775D03*
%TO.P,RPi1,ID_SC/GPIO1*%
X52390000Y-16131775D03*
%TO.P,RPi1,GND*%
X29530000Y-18671775D03*
%TO.P,RPi1,GPIO24*%
X39690000Y-16131775D03*
%TO.P,RPi1,GND*%
%TO.N,GND*%
X54930000Y-16131775D03*
X49850000Y-18671775D03*
%TO.P,RPi1,GPIO19/MISO1*%
%TO.N,N/C*%
X62550000Y-18671775D03*
%TO.P,RPi1,MISO0/GPIO9*%
X44770000Y-18671775D03*
%TO.P,RPi1,GPIO22*%
X37150000Y-18671775D03*
%TO.P,RPi1,GND*%
X34610000Y-16131775D03*
%TO.P,RPi1,GPIO21/SCLK1*%
X67630000Y-16131775D03*
%TO.P,RPi1,GPIO17*%
X32070000Y-18671775D03*
%TO.P,RPi1,GPIO15/RXD*%
X29530000Y-16131775D03*
%TO.P,RPi1,GPIO27*%
X34610000Y-18671775D03*
%TO.P,RPi1,GND*%
X42230000Y-16131775D03*
%TO.P,RPi1,GCLK1/GPIO5*%
%TO.N,Net 5*%
X54930000Y-18671775D03*
%TO.P,RPi1,GPIO14/TXD*%
%TO.N,N/C*%
X26990000Y-16131775D03*
%TO.P,RPi1,ID_SD/GPIO0*%
X52390000Y-18671775D03*
%TO.P,RPi1,GPIO23*%
X37150000Y-16131775D03*
%TO.P,RPi1,GCLK2/GPIO6*%
X57470000Y-18671775D03*
%TO.P,RPi1,~CE0~/GPIO8*%
X47310000Y-16131775D03*
%TO.P,RPi1,5V*%
X19370000Y-16131775D03*
%TO.P,RPi1,GND*%
X67630000Y-18671775D03*
%TO.P,RPi1,SDA/GPIO2*%
%TO.N,Net 7*%
X21910000Y-18671775D03*
D23*
%TO.P,RPi1,3V3*%
%TO.N,Net 1*%
X19370000Y-18671775D03*
D22*
%TO.P,RPi1,~CE1~/GPIO7*%
%TO.N,N/C*%
X49850000Y-16131775D03*
%TO.P,RPi1,GCLK0/GPIO4*%
%TO.N,Net 4*%
X26990000Y-18671775D03*
%TO.P,RPi1,PWM0/GPIO12*%
%TO.N,N/C*%
X57470000Y-16131775D03*
D24*
%TO.P,RPi1,S1*%
X14500000Y-17401775D03*
%TO.P,RPi1,S2*%
X72500000Y-17401775D03*
%TO.P,RPi1,S3*%
X72500000Y-66401775D03*
%TO.P,RPi1,S4*%
X14500000Y-66401775D03*
%TD*%
D25*
%TO.P,U2,SDA*%
%TO.N,Net 7*%
X19082203Y-34666972D03*
%TO.P,U2,VDD*%
%TO.N,Net 1*%
X17582197Y-31766975D03*
%TO.P,U2,SCL*%
%TO.N,Net 2*%
X17582203Y-34666975D03*
%TO.P,U2,SDO*%
%TO.N,Net 1*%
X16082203Y-34666978D03*
%TO.P,U2,VDDIO*%
X16082197Y-31766978D03*
%TO.P,U2,CSB*%
X19082197Y-31766972D03*
D26*
%TO.P,U2,GND*%
%TO.N,GND*%
X19082200Y-33216972D03*
%TD*%
D16*
%TO.P,H2,P1*%
%TO.N,N/C*%
X72500000Y-17401775D03*
%TD*%
D27*
%TO.P,R1,P1*%
%TO.N,Net 7*%
X17582200Y-22629575D03*
%TO.P,R1,P2*%
%TO.N,Net 1*%
X20507200Y-22629575D03*
%TD*%
D13*
%TO.P,R2,P1*%
%TO.N,Net 2*%
X23432200Y-25931983D03*
%TO.P,R2,P2*%
%TO.N,Net 1*%
X20507200Y-25931967D03*
%TD*%
D28*
%TO.N,Net 3*%
X64456000Y-50146175D02*
X64456000Y-50146175D01*
X66438800Y-50146175D02*
X64456000Y-50146175D01*
D29*
%TO.N,Net 1*%
X17582200Y-31766975D02*
X19082200Y-31766975D01*
X15459000Y-31766975D02*
X14009000Y-33216975D01*
X17582200Y-31766975D02*
X16082200Y-31766975D01*
X14009000Y-33216975D02*
X14009000Y-33283975D01*
X20507200Y-22629575D02*
X19352200Y-21474575D01*
X20507200Y-25931975D02*
X20507200Y-26181575D01*
X15392000Y-34666975D02*
X16082200Y-34666975D01*
X17439900Y-29248675D02*
X17439900Y-30124675D01*
D28*
X19352200Y-18671775D02*
X19352200Y-21474575D01*
D29*
X20507200Y-22629575D02*
X20507200Y-25931975D01*
X14009100Y-33283975D02*
X15392100Y-34666975D01*
X16082200Y-31766975D02*
X15459000Y-31766975D01*
X20507300Y-26181575D02*
X17439900Y-29248775D01*
X17439900Y-30124775D02*
X19082100Y-31766975D01*
%TO.N,Net 4*%
X52617500Y-31057875D02*
X52617500Y-32372275D01*
X28260000Y-17401775D02*
X28260000Y-15758175D01*
X52617700Y-14835775D02*
X52617700Y-14835775D01*
X26972200Y-18671775D02*
X26990000Y-18671775D01*
X52617700Y-14835775D02*
X53129700Y-14835775D01*
X29182400Y-14835775D02*
X52617600Y-14835775D01*
X52617500Y-32372275D02*
X52617700Y-32372275D01*
X53793200Y-19519675D02*
X53793200Y-18069075D01*
X53449600Y-30225775D02*
X52617600Y-31057775D01*
X53795200Y-16444475D02*
X53795200Y-16739875D01*
X53129800Y-14835775D02*
X53795200Y-15501175D01*
X53449600Y-25109875D02*
X53449600Y-30225675D01*
X53129600Y-17405475D02*
X53795200Y-16739875D01*
X53129600Y-17405475D02*
X53793200Y-18069075D01*
X29182400Y-14835775D02*
X28260000Y-15758175D01*
X53793200Y-19519675D02*
X51987200Y-21325675D01*
X54912200Y-43972075D02*
X54912200Y-43972075D01*
X51987200Y-23647475D02*
X53449600Y-25109875D01*
X51987100Y-21325675D02*
X51987100Y-23647475D01*
D28*
X26990000Y-18671775D02*
X28260000Y-17401775D01*
D29*
X29197200Y-14835775D02*
X29182400Y-14835775D01*
X54912200Y-34666975D02*
X54912200Y-34666975D01*
X53795200Y-16444475D02*
X53795200Y-15501175D01*
%TO.N,Net 7*%
X23432200Y-33216975D02*
X28260000Y-33216975D01*
X16807200Y-22629575D02*
X16593600Y-22629575D01*
X12289600Y-34120275D02*
X14725400Y-36556075D01*
X16593600Y-22629575D02*
X16082200Y-23140975D01*
X17193000Y-36556175D02*
X19082200Y-34666975D01*
X12289700Y-28773375D02*
X12289700Y-34120375D01*
X29872200Y-31604775D02*
X29872200Y-30773375D01*
X17582200Y-20841775D02*
X17582200Y-22629575D01*
X17582200Y-22629575D02*
X16807200Y-22629575D01*
X21982200Y-34666975D02*
X23432200Y-33216975D01*
X18097500Y-17401775D02*
X18097500Y-20326375D01*
X18097400Y-20326475D02*
X17582200Y-20841675D01*
X28260000Y-33216975D02*
X29872200Y-31604775D01*
X14725300Y-36556075D02*
X17193100Y-36556075D01*
X16082200Y-23140875D02*
X16082200Y-24980875D01*
X16082200Y-24980775D02*
X12289600Y-28773375D01*
X16807200Y-22629575D02*
X16807200Y-22629575D01*
D28*
X21892200Y-18671775D02*
X21777200Y-18671775D01*
D29*
X19082200Y-34666975D02*
X21982200Y-34666975D01*
X20507200Y-17401775D02*
X18097400Y-17401775D01*
D28*
X21777200Y-18671775D02*
X20507200Y-17401775D01*
D29*
%TO.N,Net 6*%
X50524600Y-20545375D02*
X50524600Y-21692875D01*
X50524600Y-25109875D02*
X50395500Y-24980775D01*
X49587800Y-24173075D02*
X50524600Y-25109875D01*
X39672200Y-20823175D02*
X38457600Y-22037775D01*
X50395500Y-17429175D02*
X50395500Y-17429175D01*
X50524600Y-20545375D02*
X51147500Y-19922475D01*
X51164000Y-18197675D02*
X50395500Y-17429175D01*
X49587900Y-22629575D02*
X49587900Y-24173175D01*
X51164000Y-18197675D02*
X51164000Y-19905975D01*
X50524600Y-27539175D02*
X51987200Y-29001775D01*
X26106600Y-29773375D02*
X26972200Y-29773375D01*
X41237800Y-17429175D02*
X50395400Y-17429175D01*
X26485400Y-25931975D02*
X27647200Y-25931975D01*
X50524600Y-25109875D02*
X50524600Y-27539275D01*
X25351200Y-27066175D02*
X25351200Y-29017975D01*
X31541500Y-22037675D02*
X27647300Y-25931875D01*
X39995200Y-18671775D02*
X41237800Y-17429175D01*
X51164100Y-19905875D02*
X51147500Y-19922475D01*
X38457700Y-22037675D02*
X31541500Y-22037675D01*
X39672200Y-18671775D02*
X39672200Y-20823175D01*
X39672200Y-18671775D02*
X39995200Y-18671775D01*
X25351200Y-29017975D02*
X26106600Y-29773375D01*
X26485400Y-25931975D02*
X25351200Y-27066175D01*
X50524600Y-21692775D02*
X49587800Y-22629575D01*
D28*
%TO.N,Net 8*%
X63894500Y-54546175D02*
X66438900Y-54546175D01*
D29*
%TO.N,Net 5*%
X54912200Y-20463175D02*
X57078600Y-22629575D01*
X54912100Y-31150575D02*
X55472100Y-31710575D01*
X57078700Y-26835175D02*
X54912100Y-29001775D01*
X57078700Y-22629575D02*
X57078700Y-26835175D01*
X55472200Y-31710575D02*
X56339400Y-31710575D01*
X54912200Y-18671775D02*
X54912200Y-20463175D01*
X54912100Y-29001775D02*
X54912100Y-31150575D01*
X56339400Y-31710575D02*
X56339400Y-31710575D01*
%TO.N,GND*%
X20507200Y-29361675D02*
X20620000Y-29248875D01*
X61888700Y-25033075D02*
X56311300Y-19455675D01*
X31541400Y-29773375D02*
X35082700Y-29773375D01*
X61888700Y-25176375D02*
X61888700Y-25033175D01*
X61888700Y-25554375D02*
X61888700Y-25176375D01*
X37465600Y-29773375D02*
X35082700Y-29773375D01*
X46693600Y-20545375D02*
X47958600Y-20545375D01*
X61888800Y-31150575D02*
X61888800Y-31150575D01*
X24432200Y-16131775D02*
X24425000Y-16131775D01*
X21892200Y-21474575D02*
X21892200Y-27863975D01*
X30827200Y-25931975D02*
X33624200Y-25931975D01*
X61888700Y-31150575D02*
X61888700Y-31150575D01*
X55002200Y-16968275D02*
X56311200Y-18277275D01*
X37465600Y-29773375D02*
X46693600Y-20545375D01*
X33624200Y-25931975D02*
X37465600Y-29773375D01*
X21892200Y-27863975D02*
X21892200Y-27976575D01*
X23155000Y-20211775D02*
X21892200Y-21474575D01*
X54912200Y-16131775D02*
X55069400Y-16131775D01*
X55002200Y-16131775D02*
X55002200Y-16968375D01*
X56311200Y-18277275D02*
X56311200Y-19455675D01*
D28*
X24425000Y-16131775D02*
X23155000Y-17401775D01*
D29*
X20507200Y-31791975D02*
X19082200Y-33216975D01*
X23155000Y-17401775D02*
X23155000Y-20211775D01*
X29872200Y-29773375D02*
X31541400Y-29773375D01*
X21892200Y-27976475D02*
X20620000Y-29248875D01*
X59992200Y-61785375D02*
X59992200Y-61785375D01*
X47958600Y-20545375D02*
X49832200Y-18671775D01*
X61888700Y-25554375D02*
X61888700Y-31150575D01*
X20507200Y-31791875D02*
X20507200Y-29361675D01*
%TO.N,Net 2*%
X23432200Y-28413575D02*
X25792000Y-30773375D01*
X23432200Y-28413575D02*
X23432200Y-30181775D01*
X25792000Y-30773375D02*
X26972200Y-30773375D01*
X23432200Y-30181875D02*
X19632800Y-33981275D01*
X25792000Y-21325775D02*
X25792000Y-23572175D01*
X23432200Y-25931975D02*
X23432200Y-28413575D01*
X18267900Y-33981275D02*
X17582200Y-34666975D01*
D28*
X24432200Y-18671775D02*
X24432200Y-19965975D01*
D29*
X25792000Y-23572175D02*
X23432200Y-25931975D01*
X24432200Y-19965875D02*
X25792000Y-21325675D01*
D30*
X18267900Y-33981275D02*
X19632800Y-33981275D01*
%TD*%
M02*

@ -0,0 +1,215 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:19+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Soldermask,Top*%
%TF.FilePolarity,Negative*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:19*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
G04 Aperture macros list end*
%ADD10C,1.700000*%
%ADD11R,1.700000X1.700000*%
%ADD12RoundRect,0.131300X0.431200X0.743700X-0.431200X0.743700X-0.431200X-0.743700X0.431200X-0.743700X0*%
%ADD13R,1.490000X1.730000*%
%ADD14R,1.700000X1.250000*%
%ADD15C,2.700000*%
%ADD16O,2.250000X4.500000*%
%ADD17O,5.000000X2.500000*%
%ADD18O,4.500000X2.250000*%
%ADD19R,0.800000X0.450000*%
%ADD20R,1.600000X2.500000*%
%ADD21C,1.524000*%
%ADD22R,1.524000X1.524000*%
%ADD23C,6.200000*%
%ADD24R,0.700000X0.600000*%
%ADD25R,0.500000X0.600000*%
%ADD26RoundRect,0.131300X-0.431200X-0.743700X0.431200X-0.743700X0.431200X0.743700X-0.431200X0.743700X0*%
G04 APERTURE END LIST*
D10*
%TO.C,J3*%
X62532286Y-43972389D03*
X59992286Y-43972408D03*
X57452286Y-43972426D03*
D11*
X54912286Y-43972445D03*
%TD*%
D12*
%TO.C,R4*%
X54912100Y-29001783D03*
X51987100Y-29001767D03*
%TD*%
D13*
%TO.C,C2*%
X17439900Y-29248875D03*
X20619900Y-29248875D03*
%TD*%
D10*
%TO.C,J4*%
X62532286Y-39422489D03*
X59992286Y-39422508D03*
X57452286Y-39422526D03*
D11*
X54912286Y-39422545D03*
%TD*%
D14*
%TO.C,D1*%
X66438816Y-54546175D03*
X66438784Y-50146175D03*
%TD*%
D13*
%TO.C,C1*%
X30827200Y-25931983D03*
X27647200Y-25931967D03*
%TD*%
D10*
%TO.C,J5*%
X62532286Y-34667289D03*
X59992286Y-34667308D03*
X57452286Y-34667326D03*
D11*
X54912286Y-34667345D03*
%TD*%
D12*
%TO.C,R3*%
X53449600Y-25109883D03*
X50524600Y-25109867D03*
%TD*%
D15*
%TO.C,H4*%
X14500000Y-17401775D03*
%TD*%
D16*
%TO.C,J1*%
X64692206Y-58785365D03*
D17*
X59992200Y-55785375D03*
D18*
X59992212Y-61785375D03*
%TD*%
D15*
%TO.C,H3*%
X72500000Y-66401775D03*
%TD*%
D10*
%TO.C,J2*%
X62532286Y-49136889D03*
X59992286Y-49136908D03*
X57452286Y-49136926D03*
D11*
X54912286Y-49136945D03*
%TD*%
D19*
%TO.C,U1*%
X29872205Y-28773383D03*
X26972200Y-29773367D03*
X26972195Y-30773367D03*
X29872195Y-30773383D03*
X29872200Y-29773383D03*
D20*
X28422200Y-29773375D03*
D19*
X26972205Y-28773367D03*
%TD*%
D15*
%TO.C,H1*%
X14500000Y-66401775D03*
%TD*%
D21*
%TO.C,RPi1*%
X39690000Y-18671775D03*
X42230000Y-18671775D03*
X65090000Y-16131775D03*
X60010000Y-16131775D03*
X60010000Y-18671775D03*
X24450000Y-18671775D03*
X62550000Y-16131775D03*
X44770000Y-16131775D03*
X32070000Y-16131775D03*
X21910000Y-16131775D03*
X65090000Y-18671775D03*
X24450000Y-16131775D03*
X47310000Y-18671775D03*
X52390000Y-16131775D03*
X29530000Y-18671775D03*
X39690000Y-16131775D03*
X54930000Y-16131775D03*
X49850000Y-18671775D03*
X62550000Y-18671775D03*
X44770000Y-18671775D03*
X37150000Y-18671775D03*
X34610000Y-16131775D03*
X67630000Y-16131775D03*
X32070000Y-18671775D03*
X29530000Y-16131775D03*
X34610000Y-18671775D03*
X42230000Y-16131775D03*
X54930000Y-18671775D03*
X26990000Y-16131775D03*
X52390000Y-18671775D03*
X37150000Y-16131775D03*
X57470000Y-18671775D03*
X47310000Y-16131775D03*
X19370000Y-16131775D03*
X67630000Y-18671775D03*
X21910000Y-18671775D03*
D22*
X19370000Y-18671775D03*
D21*
X49850000Y-16131775D03*
X26990000Y-18671775D03*
X57470000Y-16131775D03*
D23*
X14500000Y-17401775D03*
X72500000Y-17401775D03*
X72500000Y-66401775D03*
X14500000Y-66401775D03*
%TD*%
D24*
%TO.C,U2*%
X19082203Y-34666972D03*
X17582197Y-31766975D03*
X17582203Y-34666975D03*
X16082203Y-34666978D03*
X16082197Y-31766978D03*
X19082197Y-31766972D03*
D25*
X19082200Y-33216972D03*
%TD*%
D15*
%TO.C,H2*%
X72500000Y-17401775D03*
%TD*%
D26*
%TO.C,R1*%
X17582200Y-22629575D03*
X20507200Y-22629575D03*
%TD*%
D12*
%TO.C,R2*%
X23432200Y-25931983D03*
X20507200Y-25931967D03*
%TD*%
M02*

@ -0,0 +1,215 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:19+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Paste,Top*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:19*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
G04 Aperture macros list end*
%ADD10C,1.700000*%
%ADD11R,1.700000X1.700000*%
%ADD12RoundRect,0.131300X0.431200X0.743700X-0.431200X0.743700X-0.431200X-0.743700X0.431200X-0.743700X0*%
%ADD13R,1.490000X1.730000*%
%ADD14R,1.700000X1.250000*%
%ADD15C,2.700000*%
%ADD16O,2.250000X4.500000*%
%ADD17O,5.000000X2.500000*%
%ADD18O,4.500000X2.250000*%
%ADD19R,0.800000X0.450000*%
%ADD20R,1.600000X2.500000*%
%ADD21C,1.524000*%
%ADD22R,1.524000X1.524000*%
%ADD23C,6.200000*%
%ADD24R,0.700000X0.600000*%
%ADD25R,0.500000X0.600000*%
%ADD26RoundRect,0.131300X-0.431200X-0.743700X0.431200X-0.743700X0.431200X0.743700X-0.431200X0.743700X0*%
G04 APERTURE END LIST*
D10*
%TO.C,J3*%
X62532286Y-43972389D03*
X59992286Y-43972408D03*
X57452286Y-43972426D03*
D11*
X54912286Y-43972445D03*
%TD*%
D12*
%TO.C,R4*%
X54912100Y-29001783D03*
X51987100Y-29001767D03*
%TD*%
D13*
%TO.C,C2*%
X17439900Y-29248875D03*
X20619900Y-29248875D03*
%TD*%
D10*
%TO.C,J4*%
X62532286Y-39422489D03*
X59992286Y-39422508D03*
X57452286Y-39422526D03*
D11*
X54912286Y-39422545D03*
%TD*%
D14*
%TO.C,D1*%
X66438816Y-54546175D03*
X66438784Y-50146175D03*
%TD*%
D13*
%TO.C,C1*%
X30827200Y-25931983D03*
X27647200Y-25931967D03*
%TD*%
D10*
%TO.C,J5*%
X62532286Y-34667289D03*
X59992286Y-34667308D03*
X57452286Y-34667326D03*
D11*
X54912286Y-34667345D03*
%TD*%
D12*
%TO.C,R3*%
X53449600Y-25109883D03*
X50524600Y-25109867D03*
%TD*%
D15*
%TO.C,H4*%
X14500000Y-17401775D03*
%TD*%
D16*
%TO.C,J1*%
X64692206Y-58785365D03*
D17*
X59992200Y-55785375D03*
D18*
X59992212Y-61785375D03*
%TD*%
D15*
%TO.C,H3*%
X72500000Y-66401775D03*
%TD*%
D10*
%TO.C,J2*%
X62532286Y-49136889D03*
X59992286Y-49136908D03*
X57452286Y-49136926D03*
D11*
X54912286Y-49136945D03*
%TD*%
D19*
%TO.C,U1*%
X29872205Y-28773383D03*
X26972200Y-29773367D03*
X26972195Y-30773367D03*
X29872195Y-30773383D03*
X29872200Y-29773383D03*
D20*
X28422200Y-29773375D03*
D19*
X26972205Y-28773367D03*
%TD*%
D15*
%TO.C,H1*%
X14500000Y-66401775D03*
%TD*%
D21*
%TO.C,RPi1*%
X39690000Y-18671775D03*
X42230000Y-18671775D03*
X65090000Y-16131775D03*
X60010000Y-16131775D03*
X60010000Y-18671775D03*
X24450000Y-18671775D03*
X62550000Y-16131775D03*
X44770000Y-16131775D03*
X32070000Y-16131775D03*
X21910000Y-16131775D03*
X65090000Y-18671775D03*
X24450000Y-16131775D03*
X47310000Y-18671775D03*
X52390000Y-16131775D03*
X29530000Y-18671775D03*
X39690000Y-16131775D03*
X54930000Y-16131775D03*
X49850000Y-18671775D03*
X62550000Y-18671775D03*
X44770000Y-18671775D03*
X37150000Y-18671775D03*
X34610000Y-16131775D03*
X67630000Y-16131775D03*
X32070000Y-18671775D03*
X29530000Y-16131775D03*
X34610000Y-18671775D03*
X42230000Y-16131775D03*
X54930000Y-18671775D03*
X26990000Y-16131775D03*
X52390000Y-18671775D03*
X37150000Y-16131775D03*
X57470000Y-18671775D03*
X47310000Y-16131775D03*
X19370000Y-16131775D03*
X67630000Y-18671775D03*
X21910000Y-18671775D03*
D22*
X19370000Y-18671775D03*
D21*
X49850000Y-16131775D03*
X26990000Y-18671775D03*
X57470000Y-16131775D03*
D23*
X14500000Y-17401775D03*
X72500000Y-17401775D03*
X72500000Y-66401775D03*
X14500000Y-66401775D03*
%TD*%
D24*
%TO.C,U2*%
X19082203Y-34666972D03*
X17582197Y-31766975D03*
X17582203Y-34666975D03*
X16082203Y-34666978D03*
X16082197Y-31766978D03*
X19082197Y-31766972D03*
D25*
X19082200Y-33216972D03*
%TD*%
D15*
%TO.C,H2*%
X72500000Y-17401775D03*
%TD*%
D26*
%TO.C,R1*%
X17582200Y-22629575D03*
X20507200Y-22629575D03*
%TD*%
D12*
%TO.C,R2*%
X23432200Y-25931983D03*
X20507200Y-25931967D03*
%TD*%
M02*

File diff suppressed because it is too large Load Diff

@ -0,0 +1,206 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:20+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Copper,L2,Inr*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:20*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%TA.AperFunction,ComponentPad*%
%ADD10C,0.600000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD11C,1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD12R,1.700000X1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD13C,2.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD14O,2.250000X4.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD15O,5.000000X2.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD16O,4.500000X2.250000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD17C,1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD18R,1.524000X1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD19C,6.200000*%
%TD*%
G04 APERTURE END LIST*
D10*
%TO.N,Net 3*%
%TO.C,*%
X64456000Y-50146175D03*
%TO.N,Net 4*%
X52617600Y-32372375D03*
X54912200Y-43972075D03*
X54912200Y-34666975D03*
%TO.N,Net 8*%
X63894500Y-54546175D03*
%TO.N,Net 5*%
X56339400Y-31710575D03*
%TO.N,GND*%
X59992200Y-61785375D03*
X61888700Y-31150575D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J3*%
X62532286Y-43972389D03*
%TO.N,GND*%
X59992286Y-43972408D03*
%TO.N,Net 5*%
X57452286Y-43972426D03*
D12*
%TO.N,Net 4*%
X54912286Y-43972445D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J4*%
X62532286Y-39422489D03*
%TO.N,GND*%
X59992286Y-39422508D03*
%TO.N,Net 5*%
X57452286Y-39422526D03*
D12*
%TO.N,Net 4*%
X54912286Y-39422545D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J5*%
X62532286Y-34667289D03*
%TO.N,GND*%
X59992286Y-34667308D03*
%TO.N,Net 5*%
X57452286Y-34667326D03*
D12*
%TO.N,Net 4*%
X54912286Y-34667345D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H4*%
X14500000Y-17401775D03*
%TD*%
D14*
%TO.N,GND*%
%TO.C,J1*%
X64692206Y-58785365D03*
D15*
%TO.N,Net 8*%
X59992200Y-55785375D03*
D16*
%TO.N,GND*%
X59992212Y-61785375D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H3*%
X72500000Y-66401775D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J2*%
X62532286Y-49136889D03*
%TO.N,GND*%
X59992286Y-49136908D03*
%TO.N,Net 5*%
X57452286Y-49136926D03*
D12*
%TO.N,Net 4*%
X54912286Y-49136945D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H1*%
X14500000Y-66401775D03*
%TD*%
D17*
%TO.N,Net 6*%
%TO.C,RPi1*%
X39690000Y-18671775D03*
%TO.N,N/C*%
X42230000Y-18671775D03*
X65090000Y-16131775D03*
X60010000Y-16131775D03*
X60010000Y-18671775D03*
%TO.N,Net 2*%
X24450000Y-18671775D03*
%TO.N,N/C*%
X62550000Y-16131775D03*
X44770000Y-16131775D03*
X32070000Y-16131775D03*
X21910000Y-16131775D03*
X65090000Y-18671775D03*
%TO.N,GND*%
X24450000Y-16131775D03*
%TO.N,N/C*%
X47310000Y-18671775D03*
X52390000Y-16131775D03*
X29530000Y-18671775D03*
X39690000Y-16131775D03*
%TO.N,GND*%
X54930000Y-16131775D03*
X49850000Y-18671775D03*
%TO.N,N/C*%
X62550000Y-18671775D03*
X44770000Y-18671775D03*
X37150000Y-18671775D03*
X34610000Y-16131775D03*
X67630000Y-16131775D03*
X32070000Y-18671775D03*
X29530000Y-16131775D03*
X34610000Y-18671775D03*
X42230000Y-16131775D03*
%TO.N,Net 5*%
X54930000Y-18671775D03*
%TO.N,N/C*%
X26990000Y-16131775D03*
X52390000Y-18671775D03*
X37150000Y-16131775D03*
X57470000Y-18671775D03*
X47310000Y-16131775D03*
X19370000Y-16131775D03*
X67630000Y-18671775D03*
%TO.N,Net 7*%
X21910000Y-18671775D03*
D18*
%TO.N,Net 1*%
X19370000Y-18671775D03*
D17*
%TO.N,N/C*%
X49850000Y-16131775D03*
%TO.N,Net 4*%
X26990000Y-18671775D03*
%TO.N,N/C*%
X57470000Y-16131775D03*
D19*
X14500000Y-17401775D03*
X72500000Y-17401775D03*
X72500000Y-66401775D03*
X14500000Y-66401775D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H2*%
X72500000Y-17401775D03*
%TD*%
M02*

@ -0,0 +1,206 @@
%TF.GenerationSoftware,Flux,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1*%
%TF.CreationDate,2026-08-02T17:48:21+00:00*%
%TF.ProjectId,input,696e7075-742e-46b6-9963-61645f706362,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Copper,L3,Inr*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Filename: rpi4-air-quality-b7dcy*
G04 Build it with Flux! Visit our site at: https://www.flux.ai (PCBNEW 9.0.9-9.0.9~ubuntu22.04.1) date 2026-08-02 17:48:21*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%TA.AperFunction,ComponentPad*%
%ADD10C,0.600000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD11C,1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD12R,1.700000X1.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD13C,2.700000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD14O,2.250000X4.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD15O,5.000000X2.500000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD16O,4.500000X2.250000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD17C,1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD18R,1.524000X1.524000*%
%TD*%
%TA.AperFunction,ComponentPad*%
%ADD19C,6.200000*%
%TD*%
G04 APERTURE END LIST*
D10*
%TO.N,Net 3*%
%TO.C,*%
X64456000Y-50146175D03*
%TO.N,Net 4*%
X52617600Y-32372375D03*
X54912200Y-43972075D03*
X54912200Y-34666975D03*
%TO.N,Net 8*%
X63894500Y-54546175D03*
%TO.N,Net 5*%
X56339400Y-31710575D03*
%TO.N,GND*%
X59992200Y-61785375D03*
X61888700Y-31150575D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J3*%
X62532286Y-43972389D03*
%TO.N,GND*%
X59992286Y-43972408D03*
%TO.N,Net 5*%
X57452286Y-43972426D03*
D12*
%TO.N,Net 4*%
X54912286Y-43972445D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J4*%
X62532286Y-39422489D03*
%TO.N,GND*%
X59992286Y-39422508D03*
%TO.N,Net 5*%
X57452286Y-39422526D03*
D12*
%TO.N,Net 4*%
X54912286Y-39422545D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J5*%
X62532286Y-34667289D03*
%TO.N,GND*%
X59992286Y-34667308D03*
%TO.N,Net 5*%
X57452286Y-34667326D03*
D12*
%TO.N,Net 4*%
X54912286Y-34667345D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H4*%
X14500000Y-17401775D03*
%TD*%
D14*
%TO.N,GND*%
%TO.C,J1*%
X64692206Y-58785365D03*
D15*
%TO.N,Net 8*%
X59992200Y-55785375D03*
D16*
%TO.N,GND*%
X59992212Y-61785375D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H3*%
X72500000Y-66401775D03*
%TD*%
D11*
%TO.N,Net 3*%
%TO.C,J2*%
X62532286Y-49136889D03*
%TO.N,GND*%
X59992286Y-49136908D03*
%TO.N,Net 5*%
X57452286Y-49136926D03*
D12*
%TO.N,Net 4*%
X54912286Y-49136945D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H1*%
X14500000Y-66401775D03*
%TD*%
D17*
%TO.N,Net 6*%
%TO.C,RPi1*%
X39690000Y-18671775D03*
%TO.N,N/C*%
X42230000Y-18671775D03*
X65090000Y-16131775D03*
X60010000Y-16131775D03*
X60010000Y-18671775D03*
%TO.N,Net 2*%
X24450000Y-18671775D03*
%TO.N,N/C*%
X62550000Y-16131775D03*
X44770000Y-16131775D03*
X32070000Y-16131775D03*
X21910000Y-16131775D03*
X65090000Y-18671775D03*
%TO.N,GND*%
X24450000Y-16131775D03*
%TO.N,N/C*%
X47310000Y-18671775D03*
X52390000Y-16131775D03*
X29530000Y-18671775D03*
X39690000Y-16131775D03*
%TO.N,GND*%
X54930000Y-16131775D03*
X49850000Y-18671775D03*
%TO.N,N/C*%
X62550000Y-18671775D03*
X44770000Y-18671775D03*
X37150000Y-18671775D03*
X34610000Y-16131775D03*
X67630000Y-16131775D03*
X32070000Y-18671775D03*
X29530000Y-16131775D03*
X34610000Y-18671775D03*
X42230000Y-16131775D03*
%TO.N,Net 5*%
X54930000Y-18671775D03*
%TO.N,N/C*%
X26990000Y-16131775D03*
X52390000Y-18671775D03*
X37150000Y-16131775D03*
X57470000Y-18671775D03*
X47310000Y-16131775D03*
X19370000Y-16131775D03*
X67630000Y-18671775D03*
%TO.N,Net 7*%
X21910000Y-18671775D03*
D18*
%TO.N,Net 1*%
X19370000Y-18671775D03*
D17*
%TO.N,N/C*%
X49850000Y-16131775D03*
%TO.N,Net 4*%
X26990000Y-18671775D03*
%TO.N,N/C*%
X57470000Y-16131775D03*
D19*
X14500000Y-17401775D03*
X72500000Y-17401775D03*
X72500000Y-66401775D03*
X14500000Y-66401775D03*
%TD*%
D13*
%TO.N,N/C*%
%TO.C,H2*%
X72500000Y-17401775D03*
%TD*%
M02*

@ -0,0 +1,107 @@
P CODE 00
P UNITS CUST 0
P arrayDim N
317NET?3 -c324 D0118PA00X-028954Y-003798X0236Y0000R000S3
317NET?4 -3aa9 D0118PA00X-033615Y+003200X0236Y0000R000S3
317NET?4 -7147 D0118PA00X-032712Y-001367X0236Y0000R000S3
317NET?4 -f6df D0118PA00X-032712Y+002296X0236Y0000R000S3
317NET?8 -3515 D0118PA00X-029175Y-005530X0236Y0000R000S3
317NET?5 -aeea D0118PA00X-032150Y+003460X0236Y0000R000S3
317GND -2a33 D0118PA00X-030712Y-008380X0236Y0000R000S3
317GND -3587 D0118PA00X-029965Y+003681X0236Y0000R000S3
317NET?3 J3 -Pin_ D0394PA00X-029712Y-001367X0669Y0000R270S0
317GND J3 -Pin_ D0394PA00X-030712Y-001367X0669Y0000R270S0
317NET?5 J3 -Pin_ D0394PA00X-031712Y-001367X0669Y0000R270S0
317NET?4 J3 -Pin_ D0394PA00X-032712Y-001367X0669Y0669R270S0
327NET?5 R4 -P1 A01X-032712Y+004527X0443Y0689R180S2
327NET?6 R4 -P2 A01X-033863Y+004527X0443Y0689R180S2
327NET?1 C2 -1 A01X-047465Y+004430X0587Y0681R000S2
327GND C2 -2 A01X-046213Y+004430X0587Y0681R000S2
317NET?3 J4 -Pin_ D0394PA00X-029712Y+000424X0669Y0000R270S0
317GND J4 -Pin_ D0394PA00X-030712Y+000424X0669Y0000R270S0
317NET?5 J4 -Pin_ D0394PA00X-031712Y+000424X0669Y0000R270S0
317NET?4 J4 -Pin_ D0394PA00X-032712Y+000424X0669Y0669R270S0
327NET?8 D1 -1 A01X-028174Y-005530X0492Y0669R090S2
327NET?3 D1 -2 A01X-028174Y-003798X0492Y0669R090S2
327GND C1 -1 A01X-042194Y+005735X0587Y0681R180S2
327NET?6 C1 -2 A01X-043446Y+005735X0587Y0681R180S2
317NET?3 J5 -Pin_ D0394PA00X-029712Y+002296X0669Y0000R270S0
317GND J5 -Pin_ D0394PA00X-030712Y+002296X0669Y0000R270S0
317NET?5 J5 -Pin_ D0394PA00X-031712Y+002296X0669Y0000R270S0
317NET?4 J5 -Pin_ D0394PA00X-032712Y+002296X0669Y0669R270S0
327NET?4 R3 -P1 A01X-033288Y+006059X0443Y0689R180S2
327NET?6 R3 -P2 A01X-034439Y+006059X0443Y0689R180S2
317N/C H4 -P1 D1063PA00X-048622Y+009094X1063Y0000R000S0
317GND J1 -~ D0394PA00X-028861Y-007199X1772Y0886R090S0
317NET?8 J1 -~ D0315PA00X-030712Y-006018X0984Y1969R090S0
317GND J1 -~ D0394PA00X-030712Y-008380X0886Y1772R090S0
317N/C H3 -P1 D1063PA00X-025787Y-010198X1063Y0000R000S0
317NET?3 J2 -Pin_ D0394PA00X-029712Y-003400X0669Y0000R270S0
317GND J2 -Pin_ D0394PA00X-030712Y-003400X0669Y0000R270S0
317NET?5 J2 -Pin_ D0394PA00X-031712Y-003400X0669Y0000R270S0
317NET?4 J2 -Pin_ D0394PA00X-032712Y-003400X0669Y0669R270S0
327N/C U1 -NC_1 A01X-042570Y+004617X0177Y0315R090S2
327NET?6 U1 -VDD A01X-043712Y+004223X0177Y0315R090S2
327NET?2 U1 -SCK A01X-043712Y+003829X0177Y0315R090S2
327NET?7 U1 -DATA A01X-042570Y+003829X0177Y0315R090S2
327GND U1 -GND A01X-042570Y+004223X0177Y0315R090S2
327N/C U1 -EP A01X-043141Y+004223X0630Y0984R180S2
327N/C U1 -NC_2 A01X-043712Y+004617X0177Y0315R090S2
317N/C H1 -P1 D1063PA00X-048622Y-010198X1063Y0000R000S0
317NET?6 RPi1 -3V3 D0400PA00X-038705Y+008594X0600Y0000R000S0
317N/C RPi1 -MOSI D0400PA00X-037705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-028705Y+009594X0600Y0000R000S0
317N/C RPi1 -GND D0400PA00X-030705Y+009594X0600Y0000R000S0
317N/C RPi1 -PWM1 D0400PA00X-030705Y+008594X0600Y0000R000S0
317NET?2 RPi1 -SCL/ D0400PA00X-044705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-029705Y+009594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-036705Y+009594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-041705Y+009594X0600Y0000R000S0
317N/C RPi1 -5V D0400PA00X-045705Y+009594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-028705Y+008594X0600Y0000R000S0
317GND RPi1 -GND D0400PA00X-044705Y+009594X0600Y0000R000S0
317N/C RPi1 -SCLK D0400PA00X-035705Y+008594X0600Y0000R000S0
317N/C RPi1 -ID_S D0400PA00X-033705Y+009594X0600Y0000R000S0
317N/C RPi1 -GND D0400PA00X-042705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-038705Y+009594X0600Y0000R000S0
317GND RPi1 -GND D0400PA00X-032705Y+009594X0600Y0000R000S0
317GND RPi1 -GND D0400PA00X-034705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-029705Y+008594X0600Y0000R000S0
317N/C RPi1 -MISO D0400PA00X-036705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-039705Y+008594X0600Y0000R000S0
317N/C RPi1 -GND D0400PA00X-040705Y+009594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-027705Y+009594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-041705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-042705Y+009594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-040705Y+008594X0600Y0000R000S0
317N/C RPi1 -GND D0400PA00X-037705Y+009594X0600Y0000R000S0
317NET?5 RPi1 -GCLK D0400PA00X-032705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-043705Y+009594X0600Y0000R000S0
317N/C RPi1 -ID_S D0400PA00X-033705Y+008594X0600Y0000R000S0
317N/C RPi1 -GPIO D0400PA00X-039705Y+009594X0600Y0000R000S0
317N/C RPi1 -GCLK D0400PA00X-031705Y+008594X0600Y0000R000S0
317N/C RPi1 -~CE0 D0400PA00X-035705Y+009594X0600Y0000R000S0
317N/C RPi1 -5V D0400PA00X-046705Y+009594X0600Y0000R000S0
317N/C RPi1 -GND D0400PA00X-027705Y+008594X0600Y0000R000S0
317NET?7 RPi1 -SDA/ D0400PA00X-045705Y+008594X0600Y0000R000S0
317NET?1 RPi1 -3V3 D0400PA00X-046705Y+008594X0600Y0600R000S0
317N/C RPi1 -~CE1 D0400PA00X-034705Y+009594X0600Y0000R000S0
317NET?4 RPi1 -GCLK D0400PA00X-043705Y+008594X0600Y0000R000S0
317N/C RPi1 -PWM0 D0400PA00X-031705Y+009594X0600Y0000R000S0
317N/C RPi1 -S1 D1083PA00X-048622Y+009094X2441Y0000R000S0
317N/C RPi1 -S2 D1083PA00X-025787Y+009094X2441Y0000R000S0
317N/C RPi1 -S3 D1083PA00X-025787Y-010198X2441Y0000R000S0
317N/C RPi1 -S4 D1083PA00X-048622Y-010198X2441Y0000R000S0
327NET?7 U2 -SDA A01X-046818Y+002296X0236Y0276R090S2
327NET?1 U2 -VDD A01X-047409Y+003438X0236Y0276R090S2
327NET?2 U2 -SCL A01X-047409Y+002296X0236Y0276R090S2
327NET?1 U2 -SDO A01X-047999Y+002296X0236Y0276R090S2
327NET?1 U2 -VDDI A01X-047999Y+003438X0236Y0276R090S2
327NET?1 U2 -CSB A01X-046818Y+003438X0236Y0276R090S2
327GND U2 -GND A01X-046818Y+002867X0197Y0236R000S2
317N/C H2 -P1 D1063PA00X-025787Y+009094X1063Y0000R000S0
327NET?7 R1 -P1 A01X-047409Y+007036X0443Y0689R000S2
327NET?1 R1 -P2 A01X-046257Y+007036X0443Y0689R000S2
327NET?2 R2 -P1 A01X-045105Y+005735X0443Y0689R180S2
327NET?1 R2 -P2 A01X-046257Y+005735X0443Y0689R180S2
999

@ -0,0 +1,109 @@
M48
; DRILL file {Flux 9.0.9-9.0.9~ubuntu22.04.1} date 2026-08-02T17:48:21+0000
; FORMAT={-:-/ absolute / metric / decimal}
; #@! TF.CreationDate,2026-08-02T17:48:21+00:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,9.0.9-9.0.9~ubuntu22.04.1
; #@! TF.FileFunction,MixedPlating,1,4
FMAT,2
METRIC
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T1C0.300
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T2C0.800
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T3C1.000
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T4C1.016
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T5C2.700
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T6C2.750
%
G90
G05
T1
X52.618Y-32.372
X54.912Y-34.667
X54.912Y-43.972
X56.339Y-31.711
X59.992Y-61.785
X61.889Y-31.151
X63.894Y-54.546
X64.456Y-50.146
T3
X54.912Y-34.667
X54.912Y-39.423
X54.912Y-43.972
X54.912Y-49.137
X57.452Y-34.667
X57.452Y-39.423
X57.452Y-43.972
X57.452Y-49.137
X59.992Y-34.667
X59.992Y-39.423
X59.992Y-43.972
X59.992Y-49.137
X62.532Y-34.667
X62.532Y-39.422
X62.532Y-43.972
X62.532Y-49.137
T4
X19.37Y-16.132
X19.37Y-18.672
X21.91Y-16.132
X21.91Y-18.672
X24.45Y-16.132
X24.45Y-18.672
X26.99Y-16.132
X26.99Y-18.672
X29.53Y-16.132
X29.53Y-18.672
X32.07Y-16.132
X32.07Y-18.672
X34.61Y-16.132
X34.61Y-18.672
X37.15Y-16.132
X37.15Y-18.672
X39.69Y-16.132
X39.69Y-18.672
X42.23Y-16.132
X42.23Y-18.672
X44.77Y-16.132
X44.77Y-18.672
X47.31Y-16.132
X47.31Y-18.672
X49.85Y-16.132
X49.85Y-18.672
X52.39Y-16.132
X52.39Y-18.672
X54.93Y-16.132
X54.93Y-18.672
X57.47Y-16.132
X57.47Y-18.672
X60.01Y-16.132
X60.01Y-18.672
X62.55Y-16.132
X62.55Y-18.672
X65.09Y-16.132
X65.09Y-18.672
X67.63Y-16.132
X67.63Y-18.672
T5
X14.5Y-17.402
X14.5Y-66.402
X72.5Y-17.402
X72.5Y-66.402
T6
X14.5Y-17.402
X14.5Y-66.402
X72.5Y-17.402
X72.5Y-66.402
T2
X61.092Y-55.785G85X58.892Y-55.785
G05
T3
X60.992Y-61.785G85X58.992Y-61.785
G05
X64.692Y-57.785G85X64.692Y-59.785
G05
M30

Binary file not shown.

@ -0,0 +1,158 @@
# Diseño de una Arquitectura de Sistemas Embebidos Basada en Drivers para la Adquisición, Transformación y Análisis de Datos de Micro Estaciones de Calidad del Aire
## Descripción del Proyecto
Este proyecto fue desarrollado en el marco de la **Estancia en el Verano de la Investigación Científica y Tecnológica del Programa Delfín 2026** en el Instituto Tecnológico de Morelia, bajo la asesoría del Dr. Gerardo Marx Chávez Campos. Desarrollado por Fernando Martínez Valenzuela.
El proyecto consiste en la evaluación experimental del comportamiento de sensores de bajo costo para la creación de redes de monitoreo de calidad del aire en contextos urbanos. Su enfoque principal radica en el diseño de una plataforma de hardware embebido altamente eficiente que gestione la recolección, el procesamiento, el almacenamiento y la visualización de los datos ambientales en tiempo real, operando bajo un entorno de Linux.
## Objetivo
Analizar el comportamiento de sensores de bajo costo mediante el diseño y desarrollo de una plataforma de hardware embebido basada en Raspberry Pi 4, con el fin de cuantificar la deriva instrumental y el error sistemático en la adquisición de datos de calidad del aire bajo condiciones controladas.
## Arquitectura del Sistema
El sistema está construido sobre una arquitectura integral operando en **Raspbian OS Lite**, estructurada en las siguientes capas (basado en el flujo de datos del proyecto):
1. **Adquisición de Hardware:** Lectura de sensores ambientales a través del protocolo **I2C**, utilizando buses separados para evitar latencia y conflictos de dirección.
2. **Procesamiento Embebido (C):**
- Un programa principal (orquestador) actúa como driver en espacio de usuario (*User space*).
- Realiza validación de *checksum* sobre las lecturas de los sensores.
- **Gestión a corto plazo:** Escribe los datos crudos en un disco RAM (`/dev/shm/aql_actual.txt`) cada **10 segundos** para proteger la vida útil de la memoria SD frente al desgaste por ciclos continuos de escritura.
- **Gestión a largo plazo:** Acumula la información por lotes (`lote_bd.csv`) y la guarda en la memoria SD/SQLite cada **5 minutos**.
3. **Procesamiento de Python y Datos:** Un motor desarrollado en Python que lee los datos crudos desde la RAM, calcula el Índice de Calidad del Aire (ICA) basándose estrictamente en la normativa oficial **NOM-172-SEMARNAT-2019**, y genera archivos JSON.
4. **Capa de Despliegue (Front-end):** Utiliza **Nginx** como servidor web local. La interfaz de usuario (IU Estilo Apple) ofrece:
- Panel de control en tiempo real con detección asíncrona de errores de I2C.
- Semáforo ambiental visual para la interpretación del ICA.
- Visualización dinámica de tendencias históricas utilizando la librería *Chart.js*.
- Exportación estándar de datos completos en formato CSV.
## Materiales
- **Raspberry Pi 4B** (Unidad central de procesamiento).
- **Sensores de Gas Gravity DFRobot** para medir gases nocivos (CO, NO2, SO2, O3).
- **Sensores de Clima** (HTU21D y BMP180) para medir temperatura, humedad y presión atmosférica.
- Componentes para la Placa de Circuito Impreso (PCB) a medida diseñada en **Flux**.
## Conexiones de Hardware
El hardware cuenta con un diseño de **2 capas** implementando planos de Tierra (GND) y Alimentación (VCC) para mitigar la Interferencia Electromagnética (EMI). Es de importancia crítica la integración de resistencias Pull-Up (4.7kΩ a 10kΩ) en las líneas SDA y SCL para mantener el estado lógico ALTO y evitar pérdida de paquetes.
| Sensor / Módulo | Pin RPi 4B (Físico) | Pin RPi 4B (GPIO) | Función / Bus |
|-----------------|---------------------|-------------------|---------------|
| **VCC** (Todos) | Pin 2 o 4 | 5V | Alimentación |
| **GND** (Común) | Pin 6, 9, 14... | GND | Tierra común |
| **SDA Clima** | Pin 3 | GPIO 2 | I2C-1 (Datos) |
| **SCL Clima** | Pin 5 | GPIO 3 | I2C-1 (Reloj) |
| **SDA Gases** | Pin 7 | GPIO 4 | I2C-3 (Datos) |
| **SCL Gases** | Pin 29 | GPIO 5 | I2C-3 (Reloj) |
*(Nota: Asegúrate de habilitar el bus i2c-3 modificando el archivo `/boot/firmware/config.txt` si es necesario).*
## Guía de Implementación
Sigue estas instrucciones paso a paso para poner en marcha el sistema desde cualquier instalación limpia de Raspbian OS Lite.
### 1. Activar los buses I2C
El primer bus (`i2c-1`) se activa desde la herramienta de configuración de la Raspberry Pi:
```bash
sudo raspi-config
```
> Ve a **Interface Options** -> **I2C** -> Selecciona **Yes** -> Sal de la herramienta.
Para activar el segundo bus (`i2c-3`) que utilizarán los sensores de gases, debes editar el archivo de configuración de arranque:
```bash
sudo nano /boot/firmware/config.txt
```
> Agrega al final del archivo la siguiente línea:
> `dtoverlay=i2c3,pins_4_5`
>
> Guarda el archivo (`Ctrl+O`, `Enter`, `Ctrl+X`) y reinicia la Raspberry Pi ejecutando `sudo reboot`.
### 2. Instalar dependencias del sistema
Instala los compiladores, herramientas I2C y el servidor web:
```bash
sudo apt update
sudo apt install -y build-essential gcc i2c-tools nginx python3 python3-pip python3-flask sqlite3 libi2c-dev
```
### 3. Clonar el repositorio
```bash
git clone https://gitea.itmorelia.com/Verano-Delfin-2026/AirQualityMicrostation.git
cd AirQualityMicrostation
```
### 4. Verificar los sensores conectados
Verifica la comunicación en los buses separados:
```bash
i2cdetect -y 1
i2cdetect -y 3
```
### 5. Compilar y Ejecutar el Controlador Principal en C
El controlador principal (orquestador) se encuentra en el archivo `main.c`. Para compilarlo vinculando la librería I2C, ejecuta:
```bash
gcc src/main.c -o orquestador_main -li2c
```
Una vez compilado, debes ejecutarlo en segundo plano (agregando `&`) para que inicie la recolección de datos continua:
```bash
./orquestador_main &
```
### 6. Ejecutar el Motor de Python y Configurar Nginx
Los archivos de Python se encargan de calcular el ICA (NOM-172) y generar los JSON. Ejecuta el script principal de la API de la siguiente manera:
```bash
python3 api/app.py &
```
Finalmente, copia los archivos del front-end a la ruta pública de Nginx y reinicia el servicio web:
```bash
sudo cp -r web/* /var/www/html/
sudo systemctl restart nginx
```
*(Puedes acceder al panel interactivo ingresando la IP de la Raspberry Pi en tu navegador).*
### 7. Configuración y Uso de la Herramienta CLI
Para consultar los datos de los sensores directamente desde la terminal, necesitas agregar el script `comandos.bash` a la Raspberry Pi.
**Paso A: Agregar o crear el archivo**
Si el archivo no está incluido en el repositorio clonado, puedes crearlo directamente:
```bash
nano comandos.bash
```
*(Pega allí el código del script proporcionado, guarda con `Ctrl+O`, presiona `Enter`, y sal con `Ctrl+X`). Alternativamente, si tienes el archivo en tu PC, puedes transferirlo a la Raspberry Pi mediante SCP o un cliente SFTP como FileZilla.*
**Paso B: Instalar globalmente el comando**
Una vez que el archivo `comandos.bash` esté listo en tu Raspberry, instálalo en el sistema con permisos de ejecución:
```bash
sudo cp comandos.bash /usr/local/bin/aql
sudo chmod +x /usr/local/bin/aql
```
**Funcionamiento:**
El script es muy eficiente porque no consulta la base de datos, sino que lee la información en tiempo real directamente desde el disco RAM (`/dev/shm/aql_actual.txt`).
**Modo de Uso:**
- `aql clima` (Muestra Temperatura, Humedad y Presión).
- `aql values` (Muestra un resumen de todos los gases en ppm).
- `aql CO`, `aql O3`, `aql NO2`, `aql SO2` (Consultas individuales por gas).
**Nota de Solución de problemas:** Si recibes el error *"La microestación no está ejecutándose o aún no hay datos"*, significa que el archivo temporal en RAM no existe y debes verificar que el programa orquestador en C esté funcionando.
## Conclusiones
La microestación de calidad del aire demuestra la viabilidad técnica de implementar redes densas de monitoreo urbano mediante componentes de bajo costo. La arquitectura desarrollada prioriza la adaptabilidad y la alta disponibilidad. Al decidir integrar los controladores en el espacio de usuario (y no en el núcleo) en conjunto con un modelo híbrido de escritura de memoria (RAM Disk y SQLite), se ha logrado un sistema resistente frente al estrés informático continuo, que previene colapsos operativos del núcleo (Kernel Panic) y que disminuye drásticamente el desgaste del almacenamiento físico.
## Limitaciones
Para mantener el rigor académico, el alcance del estudio excluye los siguientes aspectos (limitaciones técnicas actuales de la plataforma):
1. **Calibración robusta y validación en campo:** No se realizan campañas de medición en exteriores a largo plazo, ni contrastación contra equipos meteorológicos oficiales para corregir deriva de calibración (*drift*) o sensibilidades climáticas.
2. **Diseño mecánico y aislamiento:** El proyecto no contempla el diseño o fabricación de una carcasa protectora con grado de protección IP contra la intemperie (lluvia/polvo).
3. **Análisis predictivo de datos:** No se implementan algoritmos de Machine Learning ni almacenamiento masivo en la nube para análisis a gran escala.
4. **Drivers en el Espacio de Usuario (User space):** Los drivers en C se ejecutan en el espacio de usuario en lugar del núcleo (Kernel space) para evitar bloqueos críticos (*Kernel Panic*) ante fallos de hardware intermitentes en los sensores.
---
*Documentación basada en la Metodología del Protocolo Oficial. Programa Delfín 2026.*

@ -0,0 +1,38 @@
#!/bin/bash
# Guardar en sudo nano /usr/local/bin/aql
# Verificamos si el archivo existe en la memoria
if [ ! -f "/dev/shm/aql_actual.txt" ]; then
echo "Error: La microestación no está ejecutándose o aún no hay datos."
exit 1
fi
# Cargamos las variables directamente desde el archivo
source /dev/shm/aql_actual.txt
case "$1" in
"CO")
echo "CO: $CO ppm (Actualizado: $FECHA)"
;;
"O3")
echo "O3: $O3 ppm (Actualizado: $FECHA)"
;;
"SO2")
echo "SO2: $SO2 ppm (Actualizado: $FECHA)"
;;
"NO2")
echo "NO2: $NO2 ppm (Actualizado: $FECHA)"
;;
"clima")
echo "Temperatura: $TEMP C -- Humedad: $HUM % -- Presión: $PRES hPa"
echo "Última lectura: $FECHA"
;;
"values")
echo "CO: $CO ppm -- SO2: $SO2 ppm -- O3: $O3 ppm -- NO2: $NO2 ppm"
echo "Última lectura: $FECHA"
;;
*)
echo "Comando no reconocido. Opciones válidas:"
echo " aql CO, aql O3, aql SO2, aql NO2, aql clima, aql values"
;;
esac

@ -0,0 +1,705 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Micro Air Quality Station</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
/* Estilo general con gradiente sutil de fondo */
body {
background: linear-gradient(135deg, #f5f5f7 0%, #e8e8ed 100%);
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: #1d1d1f;
margin: 0;
padding: 40px 20px;
min-height: 100vh;
}
h1 {
font-weight: 700;
letter-spacing: -1px;
font-size: 2.5rem;
margin-bottom: 5px;
background: linear-gradient(90deg, #1d1d1f, #434344);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
h2 {
font-weight: 600;
letter-spacing: -0.5px;
color: #1d1d1f;
}
.contenedor {
max-width: 1200px;
margin: 0 auto;
}
/* Franja de Color NOM-172 con efecto premium */
#franja-ica {
padding: 24px;
border-radius: 24px;
margin-bottom: 40px;
text-align: center;
color: white;
font-weight: 700;
font-size: 24px;
background: linear-gradient(135deg, #86868b, #6e6e73);
transition: background 0.5s ease, box-shadow 0.3s ease;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
letter-spacing: 0.5px;
}
/* Diseño de las Tarjetas (Cards) con profundidad multicapa */
.grid-tarjetas {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
margin-bottom: 50px;
}
.grid-graficas {
display: grid;
grid-template-columns: 1fr;
gap: 40px;
margin-bottom: 50px;
}
.tarjeta, .tarjeta-grafica {
background: linear-gradient(145deg, #ffffff, #fdfdfd);
border-radius: 24px;
padding: 28px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.02);
border: 1px solid rgba(255, 255, 255, 0.8);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
position: relative;
overflow: hidden;
}
.tarjeta:hover {
transform: translateY(-6px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08), 0 2px 5px rgba(0, 0, 0, 0.03);
}
/* Acento decorativo sutil en las tarjetas */
.tarjeta::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, transparent, rgba(0, 113, 227, 0.1), transparent);
opacity: 0;
transition: opacity 0.3s;
}
.tarjeta:hover::before { opacity: 1; }
.titulo-tarjeta {
font-size: 15px;
font-weight: 600;
color: #86868b;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.valor-tarjeta {
font-size: 46px;
font-weight: 700;
color: #1d1d1f;
display: flex;
align-items: baseline;
gap: 8px;
letter-spacing: -1.5px;
}
.unidad {
font-size: 18px;
font-weight: 600;
color: #a1a1a6;
letter-spacing: normal;
}
.error-desconectado {
font-size: 16px;
color: #ff3b30;
font-weight: 600;
background: rgba(255, 59, 48, 0.1);
padding: 8px 12px;
border-radius: 12px;
}
/* Elementos de formulario y botones modernizados */
.controles-superiores {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 15px;
background: rgba(255, 255, 255, 0.6);
padding: 15px 25px;
border-radius: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02);
}
select, input[type="date"] {
padding: 10px 16px;
border-radius: 12px;
border: 1px solid #d2d2d7;
background-color: #ffffff;
font-family: inherit;
font-size: 14px;
font-weight: 500;
outline: none;
cursor: pointer;
transition: all 0.2s;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.02);
}
select:focus, input[type="date"]:focus {
border-color: #0071e3;
box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.2);
}
button {
padding: 10px 20px;
border-radius: 20px;
border: none;
font-family: inherit;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 6px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
}
.btn-buscar { background: linear-gradient(135deg, #0071e3, #005bb5); color: white; font-weight: 600; }
.btn-csv { background: linear-gradient(135deg, #34c759, #28a745); color: white; font-weight: 600; }
.btn-excel { background: linear-gradient(135deg, #107c41, #0c5e31); color: white; font-weight: 600; }
/* Estilos de la Miniterminal estilo macOS */
.tarjeta-terminal {
background-color: #1e1e1e;
border-radius: 12px;
padding: 0;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 5px 15px rgba(0, 0, 0, 0.2);
margin-bottom: 50px;
font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', Courier, monospace;
overflow: hidden;
border: 1px solid #333;
}
.terminal-header {
background: linear-gradient(180deg, #3a3a3a 0%, #2a2a2a 100%);
padding: 10px 15px;
display: flex;
align-items: center;
border-bottom: 1px solid #111;
}
.terminal-dots {
display: flex;
gap: 8px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.dot.red { background-color: #ff5f56; box-shadow: inset 0 0 4px rgba(0,0,0,0.2); }
.dot.yellow { background-color: #ffbd2e; box-shadow: inset 0 0 4px rgba(0,0,0,0.2); }
.dot.green { background-color: #27c93f; box-shadow: inset 0 0 4px rgba(0,0,0,0.2); }
.terminal-title {
color: #999;
font-size: 13px;
margin: 0 auto;
font-weight: 500;
transform: translateX(-20px); /* Ajuste visual por los puntos */
}
.terminal-cuerpo { padding: 20px; }
.terminal-salida {
height: 250px;
overflow-y: auto;
color: #4af626;
margin-bottom: 15px;
white-space: pre-wrap;
font-size: 14px;
line-height: 1.5;
text-shadow: 0 0 2px rgba(74, 246, 38, 0.4);
}
.terminal-salida::-webkit-scrollbar { width: 8px; }
.terminal-salida::-webkit-scrollbar-thumb { background-color: #444; border-radius: 4px; }
.terminal-input-container {
display: flex;
gap: 10px;
align-items: center;
background: rgba(255, 255, 255, 0.05);
padding: 10px 15px;
border-radius: 8px;
border: 1px solid #333;
}
.terminal-input {
flex-grow: 1;
background-color: transparent;
color: #fff;
border: none;
font-family: inherit;
font-size: 14px;
outline: none;
}
.terminal-btn {
background: #333;
color: white;
padding: 8px 16px;
border-radius: 6px;
box-shadow: none;
}
.terminal-btn:hover { background: #555; transform: none; box-shadow: none; }
</style>
</head>
<body>
<div class="contenedor">
<h1>CENTRO DE MONITOREO DE CALIDAD DEL AIRE</h1>
<p style="color: #86868b; margin-bottom: 30px; font-size: 1.1rem; font-weight: 500;">Adquisición de Datos de Calidad del Aire en Tiempo Real</p>
<!-- FRANJA NOM-172 -->
<div id="franja-ica">Calculando Índice de Calidad del Aire (ICA)...</div>
<!-- TARJETAS DE LECTURA EN TIEMPO REAL -->
<div class="grid-tarjetas">
<div class="tarjeta">
<div class="titulo-tarjeta"><span>☁️</span> Monóxido de Carbono (CO)</div>
<div class="valor-tarjeta" id="valor-co">--</div>
</div>
<div class="tarjeta">
<div class="titulo-tarjeta"><span>🛡️</span> Ozono (O3)</div>
<div class="valor-tarjeta" id="valor-o3">--</div>
</div>
<div class="tarjeta">
<div class="titulo-tarjeta"><span>🚗</span> Dióxido de Nitrógeno (NO2)</div>
<div class="valor-tarjeta" id="valor-no2">--</div>
</div>
<div class="tarjeta">
<div class="titulo-tarjeta"><span>🏭</span> Dióxido de Azufre (SO2)</div>
<div class="valor-tarjeta" id="valor-so2">--</div>
</div>
<div class="tarjeta">
<div class="titulo-tarjeta"><span>🌡️</span> Temperatura</div>
<div class="valor-tarjeta" id="valor-temp">--</div>
</div>
<div class="tarjeta">
<div class="titulo-tarjeta"><span>💧</span> Humedad</div>
<div class="valor-tarjeta" id="valor-hum">--</div>
</div>
<div class="tarjeta">
<div class="titulo-tarjeta"><span>🧭</span> Presión Atmosférica</div>
<div class="valor-tarjeta" id="valor-pres">--</div>
</div>
</div>
<!-- GRÁFICA COMPARATIVA ICA -->
<div class="tarjeta-grafica" style="margin-bottom: 50px;">
<h2>Comparativa de Índices de Calidad (ICA)</h2>
<canvas id="graficaComparativaICA" height="80"></canvas>
</div>
<!-- SECCIÓN DE GRÁFICAS INDIVIDUALES -->
<div class="controles-superiores">
<h2 style="margin: 0;">Tendencias Históricas Individuales</h2>
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
<select id="selector-rango" onchange="gestionarSelector()">
<option value="1_mes">Último Mes</option>
<option value="2_meses">Últimos 2 Meses</option>
<option value="1_anio">Último Año</option>
<option value="personalizado">Personalizado...</option>
</select>
<div id="rango-personalizado" style="display: none; gap: 10px; align-items: center;">
<input type="date" id="fecha-inicio">
<span style="font-weight: 600; color: #86868b;">a</span>
<input type="date" id="fecha-fin">
<button class="btn-buscar" onclick="actualizarGrafica()">🔍 Buscar</button>
</div>
<button class="btn-csv" onclick="exportarCSV()">📄 Exportar CSV</button>
<button class="btn-excel" onclick="exportarExcel()">📊 Exportar Excel</button>
</div>
</div>
<div class="grid-graficas">
<div class="tarjeta-grafica">
<h2>Gráfica de Monóxido de Carbono (CO)</h2>
<canvas id="graficaCO"></canvas>
</div>
<div class="tarjeta-grafica">
<h2>Gráfica de Ozono (O3)</h2>
<canvas id="graficaO3"></canvas>
</div>
<div class="tarjeta-grafica">
<h2>Gráfica de Dióxido de Nitrógeno (NO2)</h2>
<canvas id="graficaNO2"></canvas>
</div>
<div class="tarjeta-grafica">
<h2>Gráfica de Dióxido de Azufre (SO2)</h2>
<canvas id="graficaSO2"></canvas>
</div>
<div class="tarjeta-grafica">
<h2>Gráfica de Temperatura</h2>
<canvas id="graficaTemp"></canvas>
</div>
<div class="tarjeta-grafica">
<h2>Gráfica de Humedad</h2>
<canvas id="graficaHum"></canvas>
</div>
<div class="tarjeta-grafica">
<h2>Gráfica de Presión Atmosférica</h2>
<canvas id="graficaPres"></canvas>
</div>
</div>
<!-- MINITERMINAL REMOTA ESTILO MACOS -->
<div class="tarjeta-terminal">
<div class="terminal-header">
<div class="terminal-dots">
<div class="dot red"></div>
<div class="dot yellow"></div>
<div class="dot green"></div>
</div>
<div class="terminal-title">pi@microestacion:~ (Bash)</div>
</div>
<div class="terminal-cuerpo">
<div class="terminal-salida" id="terminal-salida">Bienvenido a la Raspberry Pi. Sistema listo.
Para consultar parámetros manuales utilice los comandos locales (ej. aql clima, aql CO, aql values)...
</div>
<div class="terminal-input-container">
<span style="color: #4af626; font-weight: bold;">$</span>
<input type="text" id="terminal-input" class="terminal-input" placeholder="Escribe un comando..." onkeypress="manejarEnterTerminal(event)">
<button class="terminal-btn" onclick="ejecutarComando()">Enviar</button>
</div>
</div>
</div>
</div>
<script>
let datosGlobalesParaExportar = [];
// --- 1. LÓGICA NOM-172 ---
function calcularICA(cp, gas) {
const breakpoints = {
"O3": [[0.0, 0.051, 0, 50], [0.052, 0.070, 51, 100], [0.071, 0.092, 101, 150], [0.093, 0.114, 151, 200], [0.115, 10.0, 201, 500]],
"CO": [[0.0, 8.74, 0, 50], [8.75, 11.00, 51, 100], [11.01, 13.30, 101, 150], [13.31, 15.50, 151, 200], [15.51, 100.0, 201, 500]],
"SO2": [[0.0, 0.008, 0, 50], [0.009, 0.110, 51, 100], [0.111, 0.165, 101, 150], [0.166, 0.220, 151, 200], [0.221, 10.0, 201, 500]],
"NO2": [[0.0, 0.107, 0, 50], [0.108, 0.210, 51, 100], [0.211, 0.230, 101, 150], [0.231, 0.250, 151, 200], [0.251, 10.0, 201, 500]]
};
if (!breakpoints[gas]) return 0;
for (let bp of breakpoints[gas]) {
if (cp >= bp[0] && cp <= bp[1]) {
return Math.round(((bp[3] - bp[2]) / (bp[1] - bp[0])) * (cp - bp[0]) + bp[2]);
}
}
return 500;
}
// Modificado para usar gradientes modernos en la franja
function obtenerColorBanda(ica) {
if (ica <= 50) return { texto: "Buena", color: "linear-gradient(135deg, #34c759, #28a745)" };
if (ica <= 100) return { texto: "Aceptable", color: "linear-gradient(135deg, #ffcc00, #e5b700)" };
if (ica <= 150) return { texto: "Mala", color: "linear-gradient(135deg, #ff9500, #e58600)" };
if (ica <= 200) return { texto: "Muy Mala", color: "linear-gradient(135deg, #ff3b30, #d93128)" };
return { texto: "Extremadamente Mala", color: "linear-gradient(135deg, #af52de, #9948c2)" };
}
let graficaComparativa;
// --- 2. TIEMPO REAL ---
async function obtenerDatosActuales() {
try {
const respuesta = await fetch('/api/actual');
const datos = await respuesta.json();
if (datos.estado === "desconectado" || datos.estado === "error") {
const mensajeError = `<span class="error-desconectado">⚠️ Desconectado</span>`;
document.querySelectorAll('.valor-tarjeta').forEach(el => el.innerHTML = mensajeError);
document.getElementById('franja-ica').innerText = "Error: Sensores Desconectados";
document.getElementById('franja-ica').style.background = "linear-gradient(135deg, #86868b, #6e6e73)";
return;
}
document.getElementById('valor-co').innerHTML = `${datos.CO} <span class="unidad">ppm</span>`;
document.getElementById('valor-o3').innerHTML = `${datos.O3} <span class="unidad">ppm</span>`;
document.getElementById('valor-no2').innerHTML = `${datos.NO2} <span class="unidad">ppm</span>`;
document.getElementById('valor-so2').innerHTML = `${datos.SO2} <span class="unidad">ppm</span>`;
document.getElementById('valor-temp').innerHTML = `${datos.TEMP} <span class="unidad">°C</span>`;
document.getElementById('valor-hum').innerHTML = `${datos.HUM} <span class="unidad">%</span>`;
document.getElementById('valor-pres').innerHTML = `${datos.PRES} <span class="unidad">hPa</span>`;
let icaCO = calcularICA(parseFloat(datos.CO), "CO");
let icaO3 = calcularICA(parseFloat(datos.O3), "O3");
let icaNO2 = calcularICA(parseFloat(datos.NO2), "NO2");
let icaSO2 = calcularICA(parseFloat(datos.SO2), "SO2");
let icaMaximo = Math.max(icaCO, icaO3, icaNO2, icaSO2);
let banda = obtenerColorBanda(icaMaximo);
let franja = document.getElementById('franja-ica');
franja.style.background = banda.color;
franja.innerText = `Calidad del Aire: ${banda.texto} (ICA: ${icaMaximo})`;
if(graficaComparativa) graficaComparativa.destroy();
const ctxComp = document.getElementById('graficaComparativaICA').getContext('2d');
graficaComparativa = new Chart(ctxComp, {
type: 'bar',
data: {
labels: ['Monóxido de Carbono', 'Ozono', 'Dióxido de Nitrógeno', 'Dióxido de Azufre'],
datasets: [{
label: 'Índice ICA Individual',
data: [icaCO, icaO3, icaNO2, icaSO2],
backgroundColor: ['#0071e3', '#34c759', '#ff9500', '#ff3b30'],
borderRadius: 12, // Bordes más redondeados
borderSkipped: false
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, suggestedMax: 150, grid: { color: '#f0f0f5' } },
x: { grid: { display: false } }
}
}
});
} catch (error) { console.error("Error de conexión:", error); }
}
setInterval(obtenerDatosActuales, 10000);
obtenerDatosActuales();
// --- 3. GRÁFICAS HISTÓRICAS INDIVIDUALES ---
let historicas = {};
function renderizarGraficaIndividual(idCanvas, etiquetas, datos, label, colorHex) {
if (historicas[idCanvas]) { historicas[idCanvas].destroy(); }
const ctx = document.getElementById(idCanvas).getContext('2d');
// Crear gradiente para el relleno debajo de la línea
let gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, colorHex + '66'); // 40% opacidad
gradient.addColorStop(1, colorHex + '00'); // Transparente
historicas[idCanvas] = new Chart(ctx, {
type: 'line',
data: {
labels: etiquetas.length > 0 ? etiquetas : ['Sin datos'],
datasets: [{
label: label,
data: datos.length > 0 ? datos : [0],
borderColor: colorHex,
backgroundColor: gradient, // Relleno degradado moderno
borderWidth: 3, // Línea un poco más gruesa
tension: 0.4,
fill: true,
pointBackgroundColor: '#ffffff',
pointBorderColor: colorHex,
pointBorderWidth: 2
}]
},
options: {
responsive: true,
aspectRatio: 2.5,
plugins: {
legend: { display: false },
title: { display: false },
tooltip: {
backgroundColor: 'rgba(255, 255, 255, 0.9)',
titleColor: '#1d1d1f',
bodyColor: '#1d1d1f',
borderColor: 'rgba(0,0,0,0.1)',
borderWidth: 1,
padding: 12,
boxPadding: 6,
usePointStyle: true
}
},
scales: {
x: {
display: true,
title: { display: true, text: 'Fecha y Hora', color: '#86868b', font: { weight: '600', size: 13 } },
grid: { display: false }, // Limpieza visual en eje X
border: { display: true, color: '#e5e5ea', width: 2 },
ticks: { display: true, color: '#86868b', maxTicksLimit: 10, maxRotation: 45, minRotation: 45, font: {size: 11} }
},
y: {
display: true,
title: { display: true, text: label, color: '#86868b', font: { weight: '600', size: 13 } },
grid: { display: true, color: '#f0f0f5', drawOnChartArea: true, drawTicks: false },
border: { display: false }, // Ocultar borde Y para un look más abierto
ticks: { display: true, color: '#86868b', font: {size: 11}, padding: 10 },
beginAtZero: true
}
},
elements: { point: { radius: 0, hitRadius: 15, hoverRadius: 6 } }, // Puntos invisibles hasta hacer hover
interaction: { mode: 'index', intersect: false }
}
});
}
function gestionarSelector() {
const rango = document.getElementById('selector-rango').value;
const divPersonalizado = document.getElementById('rango-personalizado');
if (rango === 'personalizado') {
divPersonalizado.style.display = 'flex';
} else {
divPersonalizado.style.display = 'none';
actualizarGrafica();
}
}
async function actualizarGrafica() {
const rango = document.getElementById('selector-rango').value;
let urlAPI = `/api/historial?rango=${rango}`;
if (rango === 'personalizado') {
const inicio = document.getElementById('fecha-inicio').value;
const fin = document.getElementById('fecha-fin').value;
if (!inicio || !fin) {
alert("Por favor, selecciona una fecha de inicio y una de fin.");
return;
}
urlAPI += `&inicio=${inicio}&fin=${fin}`;
}
const respuesta = await fetch(urlAPI);
const datosHistorial = await respuesta.json();
datosGlobalesParaExportar = datosHistorial;
if (datosHistorial.length === 0) {
alert("La base de datos no tiene registros para este rango de fechas.");
}
const fechas = datosHistorial.map(f => f.fecha_hora);
renderizarGraficaIndividual('graficaCO', fechas, datosHistorial.map(f => f.co), 'Monóxido de Carbono (ppm)', '#0071e3');
renderizarGraficaIndividual('graficaO3', fechas, datosHistorial.map(f => f.ozono), 'Ozono (ppm)', '#34c759');
renderizarGraficaIndividual('graficaNO2', fechas, datosHistorial.map(f => f.no2), 'Dióxido de Nitrógeno (ppm)', '#ff9500');
renderizarGraficaIndividual('graficaSO2', fechas, datosHistorial.map(f => f.so2), 'Dióxido de Azufre (ppm)', '#ff3b30');
renderizarGraficaIndividual('graficaTemp', fechas, datosHistorial.map(f => f.temperatura), 'Temperatura (°C)', '#ffcc00');
renderizarGraficaIndividual('graficaHum', fechas, datosHistorial.map(f => f.humedad), 'Humedad (%)', '#5ac8fa');
renderizarGraficaIndividual('graficaPres', fechas, datosHistorial.map(f => f.presion), 'Presión Atmosférica (hPa)', '#5856d6');
}
// --- 4. FUNCIÓN PARA EXPORTAR A CSV (EXCEL) ---
function exportarCSV() {
if (datosGlobalesParaExportar.length === 0) {
alert("No hay datos disponibles para exportar en este rango.");
return;
}
let contenidoCSV = "Fecha y Hora,CO (ppm),O3 (ppm),NO2 (ppm),SO2 (ppm),Temperatura (C),Humedad (%),Presion Atmosferica (hPa)\n";
datosGlobalesParaExportar.forEach(fila => {
contenidoCSV += `${fila.fecha_hora},${fila.co},${fila.ozono},${fila.no2},${fila.so2},${fila.temperatura},${fila.humedad},${fila.presion}\n`;
});
const blob = new Blob([contenidoCSV], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const enlaceVirtual = document.createElement("a");
enlaceVirtual.setAttribute("href", url);
enlaceVirtual.setAttribute("download", "Historial_Calidad_Aire.csv");
document.body.appendChild(enlaceVirtual);
enlaceVirtual.click();
document.body.removeChild(enlaceVirtual);
}
// --- 5. FUNCIÓN PARA EXPORTAR A EXCEL (.XLSX) ---
function exportarExcel() {
if (datosGlobalesParaExportar.length === 0) {
alert("No hay datos disponibles para exportar en este rango.");
return;
}
const datosFormateados = datosGlobalesParaExportar.map(fila => ({
"Fecha y Hora": fila.fecha_hora,
"CO (ppm)": fila.co,
"Ozono - O3 (ppm)": fila.ozono,
"Dióxido de Nitrógeno - NO2 (ppm)": fila.no2,
"Dióxido de Azufre - SO2 (ppm)": fila.so2,
"Temperatura (°C)": fila.temperatura,
"Humedad (%)": fila.humedad,
"Presión Atmosférica (hPa)": fila.presion
}));
const hoja = XLSX.utils.json_to_sheet(datosFormateados);
const libro = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(libro, hoja, "Calidad del Aire");
XLSX.writeFile(libro, "Historial_Calidad_Aire.xlsx");
}
// --- 6. LÓGICA DE LA MINITERMINAL ---
function manejarEnterTerminal(event) {
if (event.key === "Enter") {
ejecutarComando();
}
}
async function ejecutarComando() {
const inputElement = document.getElementById('terminal-input');
const salidaElement = document.getElementById('terminal-salida');
const comando = inputElement.value.trim();
if (!comando) return;
salidaElement.innerHTML += `\n<span style="color: #ffffff;">$ ${comando}</span>\n`;
inputElement.value = "";
salidaElement.scrollTop = salidaElement.scrollHeight;
try {
const respuesta = await fetch('/api/ejecutar', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ comando: comando })
});
const datos = await respuesta.json();
if (datos.error && datos.error !== "") {
salidaElement.innerHTML += `<span style="color: #ff5f56;">${datos.error}</span>`;
} else if (datos.salida && datos.salida !== "") {
salidaElement.innerHTML += `${datos.salida}`;
}
} catch (error) {
salidaElement.innerHTML += `<span style="color: #ff5f56;">Error de comunicación con el servidor.</span>\n`;
}
salidaElement.scrollTop = salidaElement.scrollHeight;
}
actualizarGrafica();
</script>
</body>
</html>

@ -0,0 +1,152 @@
from flask import Flask, jsonify, request
from flask_cors import CORS
import sqlite3
import os
import time
import threading
app = Flask(__name__)
CORS(app) # Permite que la página web consulte este servidor
RUTA_ACTUAL = "/dev/shm/aql_actual.txt"
RUTA_BD = "/home/pi/microestacion.db"
# --- LÓGICA DE LA NOM-172-SEMARNAT-2019 ---
def calcular_ica_individual(cp, gas):
# Tablas de cortes (Breakpoints) según la NOM-172
# Estructura: [BP_low, BP_high, Index_low, Index_high]
breakpoints = {
"O3": [(0.0, 0.051, 0, 50), (0.052, 0.070, 51, 100), (0.071, 0.092, 101, 150), (0.093, 0.114, 151, 200), (0.115, 10.0, 201, 500)],
"CO": [(0.0, 8.74, 0, 50), (8.75, 11.00, 51, 100), (11.01, 13.30, 101, 150), (13.31, 15.50, 151, 200), (15.51, 100.0, 201, 500)],
"SO2": [(0.0, 0.008, 0, 50), (0.009, 0.110, 51, 100), (0.111, 0.165, 101, 150), (0.166, 0.220, 151, 200), (0.221, 10.0, 201, 500)],
"NO2": [(0.0, 0.107, 0, 50), (0.108, 0.210, 51, 100), (0.211, 0.230, 101, 150), (0.231, 0.250, 151, 200), (0.251, 10.0, 201, 500)]
}
if gas not in breakpoints:
return 0
for bp in breakpoints[gas]:
bp_lo, bp_hi, i_lo, i_hi = bp
if bp_lo <= cp <= bp_hi:
# Fórmula de interpolación lineal oficial
ica = ((i_hi - i_lo) / (bp_hi - bp_lo)) * (cp - bp_lo) + i_lo
return round(ica)
# Si sobrepasa el límite máximo establecido (Caso extremo)
return 500
def obtener_banda_ica(ica):
if ica <= 50: return "Buena"
elif ica <= 100: return "Aceptable"
elif ica <= 150: return "Mala"
elif ica <= 200: return "Muy Mala"
else: return "Extremadamente Mala"
def procesador_ica_background():
"""Hilo que corre en segundo plano calculando el ICA de los nuevos registros"""
while True:
try:
conexion = sqlite3.connect(RUTA_BD)
cursor = conexion.cursor()
# Buscamos todas las filas que el programa C acaba de insertar (ica es NULL)
cursor.execute("SELECT id, ozono, no2, co, so2 FROM mediciones WHERE ica IS NULL")
filas_pendientes = cursor.fetchall()
for fila in filas_pendientes:
id_fila = fila[0]
# Calculamos el ICA de cada contaminante por separado
ica_o3 = calcular_ica_individual(fila[1], "O3")
ica_no2 = calcular_ica_individual(fila[2], "NO2")
ica_co = calcular_ica_individual(fila[3], "CO")
ica_so2 = calcular_ica_individual(fila[4], "SO2")
# La norma dicta que el ICA global es el valor máximo encontrado
ica_global = max(ica_o3, ica_no2, ica_co, ica_so2)
banda = obtener_banda_ica(ica_global)
# Actualizamos la fila en la base de datos con el cálculo final
cursor.execute("UPDATE mediciones SET ica = ?, banda_ica = ? WHERE id = ?", (ica_global, banda, id_fila))
conexion.commit()
conexion.close()
except Exception as e:
print(f"Error procesando ICA: {e}")
# Esperamos 30 segundos antes de volver a revisar la base de datos
time.sleep(30)
# Función 1: Obtener datos en tiempo real
@app.route('/api/actual')
def obtener_actual():
datos = {"estado": "ok"}
# Validamos si el archivo existe
if not os.path.exists(RUTA_ACTUAL):
return jsonify({"estado": "error", "mensaje": "Archivo no encontrado"})
# Validamos si el programa en C se detuvo (Si el archivo tiene más de 20 segundos sin modificarse)
tiempo_modificacion = os.path.getmtime(RUTA_ACTUAL)
if (time.time() - tiempo_modificacion) > 20:
return jsonify({"estado": "desconectado"}) # Esto activará el mensaje de "Desconectado" en la web
# Leemos el archivo línea por línea
with open(RUTA_ACTUAL, 'r') as f:
for linea in f:
if '=' in linea:
clave, valor = linea.strip().split('=', 1)
datos[clave] = valor.replace('"', '') # Limpiamos comillas de la fecha
return jsonify(datos)
# Función 2: Obtener historial para las gráficas
@app.route('/api/historial')
def obtener_historial():
rango = request.args.get('rango', '1_mes') # Por defecto lee el último mes
conexion = sqlite3.connect(RUTA_BD)
conexion.row_factory = sqlite3.Row # Permite acceder a las columnas por nombre
cursor = conexion.cursor()
# Si el usuario eligió fechas personalizadas
if rango == 'personalizado':
inicio = request.args.get('inicio')
fin = request.args.get('fin')
# Usamos ? para inyectar las variables de forma segura
consulta = "SELECT fecha_hora, presion, temperatura, humedad, ozono, no2, co, so2 FROM mediciones WHERE fecha_hora BETWEEN ? AND ? ORDER BY fecha_hora ASC"
# Agregamos las horas al inicio y al fin para abarcar los días completos
cursor.execute(consulta, (f"{inicio} 00:00:00", f"{fin} 23:59:59"))
# Si eligió los rangos predefinidos
else:
filtro_fecha = ""
if rango == '1_mes':
filtro_fecha = "date('now', '-1 month')"
elif rango == '2_meses':
filtro_fecha = "date('now', '-2 months')"
elif rango == '1_anio':
filtro_fecha = "date('now', '-1 year')"
consulta = f"SELECT fecha_hora, presion, temperatura, humedad, ozono, no2, co, so2 FROM mediciones WHERE fecha_hora >= {filtro_fecha} ORDER BY fecha_hora ASC"
cursor.execute(consulta)
filas = cursor.fetchall()
conexion.close()
# Convertimos los resultados de SQLite a una lista de diccionarios (JSON)
resultado = [dict(fila) for fila in filas]
return jsonify(resultado)
# --- ARRANQUE DEL SERVIDOR Y EL HILO ---
if __name__ == '__main__':
# Iniciamos el procesador de ICA en un hilo paralelo para que no bloquee el servidor web
hilo_ica = threading.Thread(target=procesador_ica_background, daemon=True)
hilo_ica.start()
app.run(host='0.0.0.0', port=5000)

@ -0,0 +1,179 @@
from flask import Flask, jsonify, request
from flask_cors import CORS
import sqlite3
import os
import time
import threading
import subprocess # NUEVO: Librería para ejecutar comandos en la terminal
app = Flask(__name__)
CORS(app) # Permite que la página web consulte este servidor
RUTA_ACTUAL = "/dev/shm/aql_actual.txt"
RUTA_BD = "/home/pi/microestacion.db"
# --- LÓGICA DE LA NOM-172-SEMARNAT-2019 ---
def calcular_ica_individual(cp, gas):
# Tablas de cortes (Breakpoints) según la NOM-172
# Estructura: [BP_low, BP_high, Index_low, Index_high]
breakpoints = {
"O3": [(0.0, 0.051, 0, 50), (0.052, 0.070, 51, 100), (0.071, 0.092, 101, 150), (0.093, 0.114, 151, 200), (0.115, 10.0, 201, 500)],
"CO": [(0.0, 8.74, 0, 50), (8.75, 11.00, 51, 100), (11.01, 13.30, 101, 150), (13.31, 15.50, 151, 200), (15.51, 100.0, 201, 500)],
"SO2": [(0.0, 0.008, 0, 50), (0.009, 0.110, 51, 100), (0.111, 0.165, 101, 150), (0.166, 0.220, 151, 200), (0.221, 10.0, 201, 500)],
"NO2": [(0.0, 0.107, 0, 50), (0.108, 0.210, 51, 100), (0.211, 0.230, 101, 150), (0.231, 0.250, 151, 200), (0.251, 10.0, 201, 500)]
}
if gas not in breakpoints:
return 0
for bp in breakpoints[gas]:
bp_lo, bp_hi, i_lo, i_hi = bp
if bp_lo <= cp <= bp_hi:
# Fórmula de interpolación lineal oficial
ica = ((i_hi - i_lo) / (bp_hi - bp_lo)) * (cp - bp_lo) + i_lo
return round(ica)
# Si sobrepasa el límite máximo establecido (Caso extremo)
return 500
def obtener_banda_ica(ica):
if ica <= 50: return "Buena"
elif ica <= 100: return "Aceptable"
elif ica <= 150: return "Mala"
elif ica <= 200: return "Muy Mala"
else: return "Extremadamente Mala"
def procesador_ica_background():
"""Hilo que corre en segundo plano calculando el ICA de los nuevos registros"""
while True:
try:
conexion = sqlite3.connect(RUTA_BD)
cursor = conexion.cursor()
# Buscamos todas las filas que el programa C acaba de insertar (ica es NULL)
cursor.execute("SELECT id, ozono, no2, co, so2 FROM mediciones WHERE ica IS NULL")
filas_pendientes = cursor.fetchall()
for fila in filas_pendientes:
id_fila = fila[0]
# Calculamos el ICA de cada contaminante por separado
ica_o3 = calcular_ica_individual(fila[1], "O3")
ica_no2 = calcular_ica_individual(fila[2], "NO2")
ica_co = calcular_ica_individual(fila[3], "CO")
ica_so2 = calcular_ica_individual(fila[4], "SO2")
# La norma dicta que el ICA global es el valor máximo encontrado
ica_global = max(ica_o3, ica_no2, ica_co, ica_so2)
banda = obtener_banda_ica(ica_global)
# Actualizamos la fila en la base de datos con el cálculo final
cursor.execute("UPDATE mediciones SET ica = ?, banda_ica = ? WHERE id = ?", (ica_global, banda, id_fila))
conexion.commit()
conexion.close()
except Exception as e:
print(f"Error procesando ICA: {e}")
# Esperamos 30 segundos antes de volver a revisar la base de datos
time.sleep(30)
# Función 1: Obtener datos en tiempo real
@app.route('/api/actual')
def obtener_actual():
datos = {"estado": "ok"}
# Validamos si el archivo existe
if not os.path.exists(RUTA_ACTUAL):
return jsonify({"estado": "error", "mensaje": "Archivo no encontrado"})
# Validamos si el programa en C se detuvo (Si el archivo tiene más de 20 segundos sin modificarse)
tiempo_modificacion = os.path.getmtime(RUTA_ACTUAL)
if (time.time() - tiempo_modificacion) > 20:
return jsonify({"estado": "desconectado"}) # Esto activará el mensaje de "Desconectado" en la web
# Leemos el archivo línea por línea
with open(RUTA_ACTUAL, 'r') as f:
for linea in f:
if '=' in linea:
clave, valor = linea.strip().split('=', 1)
datos[clave] = valor.replace('"', '') # Limpiamos comillas de la fecha
return jsonify(datos)
# Función 2: Obtener historial para las gráficas
@app.route('/api/historial')
def obtener_historial():
rango = request.args.get('rango', '1_mes') # Por defecto lee el último mes
conexion = sqlite3.connect(RUTA_BD)
conexion.row_factory = sqlite3.Row # Permite acceder a las columnas por nombre
cursor = conexion.cursor()
# Si el usuario eligió fechas personalizadas
if rango == 'personalizado':
inicio = request.args.get('inicio')
fin = request.args.get('fin')
# Usamos ? para inyectar las variables de forma segura
consulta = "SELECT fecha_hora, presion, temperatura, humedad, ozono, no2, co, so2 FROM mediciones WHERE fecha_hora BETWEEN ? AND ? ORDER BY fecha_hora ASC"
# Agregamos las horas al inicio y al fin para abarcar los días completos
cursor.execute(consulta, (f"{inicio} 00:00:00", f"{fin} 23:59:59"))
# Si eligió los rangos predefinidos
else:
filtro_fecha = ""
if rango == '1_mes':
filtro_fecha = "date('now', '-1 month')"
elif rango == '2_meses':
filtro_fecha = "date('now', '-2 months')"
elif rango == '1_anio':
filtro_fecha = "date('now', '-1 year')"
consulta = f"SELECT fecha_hora, presion, temperatura, humedad, ozono, no2, co, so2 FROM mediciones WHERE fecha_hora >= {filtro_fecha} ORDER BY fecha_hora ASC"
cursor.execute(consulta)
filas = cursor.fetchall()
conexion.close()
# Convertimos los resultados de SQLite a una lista de diccionarios (JSON)
resultado = [dict(fila) for fila in filas]
return jsonify(resultado)
# Función 3: Terminal Remota (Ejecutar comandos)
@app.route('/api/ejecutar', methods=['POST'])
def ejecutar_comando():
datos = request.get_json()
comando = datos.get('comando', '')
if not comando:
return jsonify({'salida': '', 'error': 'No se proporcionó ningún comando.'})
try:
# Ejecutar el comando en el shell de la Raspberry Pi
# Se captura la salida normal (stdout) y los errores (stderr)
resultado = subprocess.run(comando, shell=True, capture_output=True, text=True, timeout=10)
salida = resultado.stdout
error = resultado.stderr
# Combinar salida y error (Priorizar mostrar el error si existe)
respuesta = error if error else salida
return jsonify({'salida': respuesta, 'error': ''})
except subprocess.TimeoutExpired:
return jsonify({'salida': '', 'error': 'El comando tardó demasiado en ejecutarse (Timeout).'})
except Exception as e:
return jsonify({'salida': '', 'error': str(e)})
# --- ARRANQUE DEL SERVIDOR Y EL HILO ---
if __name__ == '__main__':
# Iniciamos el procesador de ICA en un hilo paralelo para que no bloquee el servidor web
hilo_ica = threading.Thread(target=procesador_ica_background, daemon=True)
hilo_ica.start()
app.run(host='0.0.0.0', port=5000)
Loading…
Cancel
Save