#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
struct NodoMedicion {
int id;
int medicionesIteracion;
unsigned long ultimaMedicion;
NodoMedicion* siguiente;
};
class MedicionQueue {
public:
MedicionQueue() {
cabeza = nullptr;
cola = nullptr;
}
~MedicionQueue() {
NodoMedicion* actual = cabeza;
while (actual != nullptr) {
NodoMedicion* siguiente = actual->siguiente;
delete actual;
actual = siguiente;
}
}
void agregarOActualizar(int id) {
NodoMedicion* anterior = nullptr;
NodoMedicion* existente = buscarNodoPorID(id, &anterior);
if (existente) {
// Mantenemos la iteración y medición previa
moverNodoAlFinal(existente, anterior);
} else {
NodoMedicion* nuevo = new NodoMedicion{ id, 0, 0, nullptr };
if (!cabeza) {
cabeza = nuevo;
cola = nuevo;
} else {
cola->siguiente = nuevo;
cola = nuevo;
}
}
}
NodoMedicion* frente() {
return cabeza;
}
void moverFrenteAlFinal() {
if (!cabeza || cabeza == cola) return;
NodoMedicion* actual = cabeza;
cabeza = cabeza->siguiente;
actual->siguiente = nullptr;
cola->siguiente = actual;
cola = actual;
}
void imprimirCola() {
NodoMedicion* actual = cabeza;
int i = 0;
while (actual != nullptr) {
Serial.print("[" + String(i) + "] ID: ");
Serial.print(actual->id);
Serial.print(", medicionesIteracion: ");
Serial.print(actual->medicionesIteracion);
Serial.print(", ultimaMedicion: ");
Serial.println(actual->ultimaMedicion);
actual = actual->siguiente;
i++;
}
}
private:
NodoMedicion* cabeza;
NodoMedicion* cola;
NodoMedicion* buscarNodoPorID(int id, NodoMedicion** anterior = nullptr) {
NodoMedicion* actual = cabeza;
NodoMedicion* prev = nullptr;
while (actual != nullptr) {
if (actual->id == id) {
if (anterior) *anterior = prev;
return actual;
}
prev = actual;
actual = actual->siguiente;
}
return nullptr;
}
void moverNodoAlFinal(NodoMedicion* nodo, NodoMedicion* anterior) {
if (nodo == cola) return;
if (nodo == cabeza) {
cabeza = nodo->siguiente;
} else if (anterior) {
anterior->siguiente = nodo->siguiente;
}
nodo->siguiente = nullptr;
cola->siguiente = nodo;
cola = nodo;
}
};
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_B3 247
#define BUZZER_PIN 27
#define COLOR_OIL ILI9341_YELLOW
#define COLOR_BAR ILI9341_WHITE
#define COLOR_ERROR ILI9341_RED
#define COLOR_OK ILI9341_GREEN
#define COLOR_BG ILI9341_BLACK
#define COLOR_WARN ILI9341_ORANGE
MedicionQueue cola;
const int pinBoton = 12;
const unsigned long tiempoEspera = 30000;
unsigned long tiempoAnterior = 0;
const int umbralCPT = 28;
const int umbralCPT_medio = 20;
const int umbralTemp = 170;
bool enEspera = true;
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
void alertaLa440() {
tone(BUZZER_PIN, 440); // La 440 Hz continuo
}
void detenerAlerta() {
noTone(BUZZER_PIN);
}
void sonidoPositivo() {
tone(BUZZER_PIN, NOTE_E4, 150);
delay(150);
tone(BUZZER_PIN, NOTE_G4, 150);
delay(150);
tone(BUZZER_PIN, NOTE_C5, 200);
delay(200);
noTone(BUZZER_PIN);
}
void sonidoNegativo() {
tone(BUZZER_PIN, NOTE_C4, 300);
delay(300);
tone(BUZZER_PIN, NOTE_B3, 400); // intervalo incómodo (tritono o semitono descendente)
delay(400);
noTone(BUZZER_PIN);
}
void dibujarRelojArena(int x, int y) {
tft.fillScreen(ILI9341_BLACK); // Fondo
// Dimensiones base del reloj
int ancho = 30;
int alto = 50;
int centroX = x + ancho / 2;
// Estructura exterior
tft.drawLine(x, y, x + ancho, y, ILI9341_WHITE); // borde superior
tft.drawLine(x, y + alto, x + ancho, y + alto, ILI9341_WHITE); // borde inferior
tft.drawLine(x, y, centroX, y + alto / 2, ILI9341_WHITE); // diagonal izquierda arriba
tft.drawLine(x + ancho, y, centroX, y + alto / 2, ILI9341_WHITE); // diagonal derecha arriba
tft.drawLine(x, y + alto, centroX, y + alto / 2, ILI9341_WHITE); // diagonal izquierda abajo
tft.drawLine(x + ancho, y + alto, centroX, y + alto / 2, ILI9341_WHITE); // diagonal derecha abajo
// Arena superior (triángulo invertido)
tft.fillTriangle(x + 5, y + 5, x + ancho - 5, y + 5, centroX, y + alto / 2 - 4, ILI9341_YELLOW);
// Arena inferior (triángulo apuntando hacia arriba)
tft.fillTriangle(
centroX - 5, y + alto - 5, // izquierda base
centroX + 5, y + alto - 5, // derecha base
centroX, y + alto / 2 + 6, // vértice arriba
ILI9341_YELLOW
);
// Goteo en el centro
tft.drawPixel(centroX, y + alto / 2, ILI9341_YELLOW);
tft.drawPixel(centroX, y + alto / 2 + 2, ILI9341_YELLOW);
// Texto informativo
tft.setCursor(x - 5, y + alto + 10);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.println("Esperando la succion del aceite");
}
void dibujarRelojEspera(int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(0, 0);
// Esfera del reloj
tft.drawCircle(x + 20, y + 20, 18, ILI9341_WHITE); // Borde
tft.fillCircle(x + 20, y + 20, 2, ILI9341_WHITE); // Centro
// Manecilla horaria (ej: 10 en punto)
tft.drawLine(x + 20, y + 20, x + 12, y + 12, ILI9341_WHITE);
// Manecilla minutera (ej: 12 en punto)
tft.drawLine(x + 20, y + 20, x + 20, y + 5, ILI9341_WHITE);
// Texto debajo
tft.setCursor(x - 10, y + 45);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.println("Esperando la siguiente");
tft.println("medicion en la cola");
tft.println("Presione el boton para anadir una tina");
}
void dibujarErrorTina(int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
// Tina
tft.drawRect(x + 10, y + 10, 40, 25, COLOR_OIL);
tft.fillRect(x + 10, y + 25, 40, 10, COLOR_OIL);
// X roja encima
tft.drawLine(x + 10, y + 10, x + 50, y + 35, COLOR_ERROR);
tft.drawLine(x + 50, y + 10, x + 10, y + 35, COLOR_ERROR);
// Texto
tft.setCursor(x, y + 45);
tft.setTextColor(COLOR_ERROR);
tft.println("Error, saque inmediatamente el sensor");
tft.println("E intente despues");
tft.println("Presione el boton para detener la alerta");
}
void dibujarCodigoBarras(int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
for (int i = 0; i < 16; i++) {
int grosor = (i % 3 == 0) ? 3 : 1;
tft.fillRect(x + i * 4, y, grosor, 30, COLOR_BAR);
}
tft.setCursor(x, y + 35);
tft.setTextColor(COLOR_BAR);
tft.println("Escaneando un nuevo dispositivo");
tft.println("Introduzca el ID por monitor serial");
tft.println("Confirme con el boton");
}
void dibujarCheck(int x, int y) {
tft.fillScreen(COLOR_BG); // Fondo
// Dibujo del check más grueso con líneas paralelas
for (int offset = -1; offset <= 1; offset++) {
tft.drawLine(x + 5, y + 20 + offset, x + 20, y + 35 + offset, COLOR_OK);
tft.drawLine(x + 5 + offset, y + 20, x + 20 + offset, y + 35, COLOR_OK);
tft.drawLine(x + 20, y + 35 + offset, x + 45, y + 5 + offset, COLOR_OK);
tft.drawLine(x + 20 + offset, y + 35, x + 45 + offset, y + 5, COLOR_OK);
}
// Texto debajo
tft.setCursor(x - 10, y + 50);
tft.setTextColor(COLOR_OK);
tft.setTextSize(1);
tft.println("Medición realizada con éxito");
}
void dibujarCajaComunicando(int targetID, int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
// Caja
tft.fillRect(x, y + 10, 20, 20, ILI9341_CYAN);
// Señales como ondas
tft.drawCircle(x + 25, y + 20, 5, ILI9341_WHITE);
tft.drawCircle(x + 25, y + 20, 8, ILI9341_WHITE);
tft.drawCircle(x + 25, y + 20, 11, ILI9341_WHITE);
// Texto
tft.setCursor(x, y + 35);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.println("Escaneando la tina...");
while (Serial.available() > 0) Serial.read();
String entrada = "";
tft.println("Escriba el ID en el Serial.");
tft.println("Luego presione el boton para confirmar.");
// Esperar que el usuario escriba algo y luego presione botón
while (true) {
if (digitalRead(pinBoton) == HIGH) {
delay(300); // debounce
if (Serial.available()) {
entrada = Serial.readStringUntil('\n');
if (targetID != entrada.toInt()){
dibujarComunicacionFallida(20, 20);
delay(2000);
dibujarCajaComunicando(targetID, x, y);
}else{
dibujarComunicacionExitosa(20, 20);
delay(2000);
}
break;
} else {
tft.println("No hay datos en Serial. Intente de nuevo.");
}
}
}
}
void dibujarComunicacionFallida(int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
// Caja
tft.fillRect(x, y + 10, 20, 20, ILI9341_RED);
// Señal base
tft.drawCircle(x + 25, y + 20, 8, ILI9341_RED);
// X sobre señal
tft.drawLine(x + 18, y + 13, x + 32, y + 27, ILI9341_RED);
tft.drawLine(x + 32, y + 13, x + 18, y + 27, ILI9341_RED);
// Texto
tft.setCursor(x, y + 35);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.print("Tina incorrecta");
}
void dibujarComunicacionExitosa(int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
// Caja
tft.fillRect(x, y + 10, 20, 20, ILI9341_GREEN);
// Ondas
tft.drawCircle(x + 25, y + 20, 5, ILI9341_GREEN);
tft.drawCircle(x + 25, y + 20, 8, ILI9341_GREEN);
// Check
tft.drawLine(x + 32, y + 22, x + 36, y + 26, ILI9341_GREEN);
tft.drawLine(x + 36, y + 26, x + 42, y + 14, ILI9341_GREEN);
// Texto
tft.setCursor(x, y + 35);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(1);
tft.print("Tina verificada");
}
void dibujarBateria(int x, int y, int nivel) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
// Marco
tft.drawRect(x, y, 36, 20, ILI9341_WHITE);
tft.drawRect(x + 36, y + 5, 4, 10, ILI9341_WHITE); // terminal
// Colores según nivel
uint16_t colores[3] = {ILI9341_GREEN, ILI9341_ORANGE, ILI9341_RED};
for (int i = 0; i < 3; i++) {
int relleno = (nivel > i) ? 10 : 0;
tft.fillRect(x + 2 + i * 11, y + 2, relleno, 16, (nivel > i) ? colores[i] : ILI9341_DARKGREY);
}
tft.setCursor(x, y + 25);
if (nivel == 3){
tft.setTextColor(ILI9341_RED);
tft.println("El aceite acabo su vida util");
tft.println("Por favor, cambie el aceite");
}else if (nivel == 2){
tft.setTextColor(ILI9341_YELLOW);
tft.println("El aceite esta a mediados de su vida util");
tft.println("No requiere cambio");
}else{
tft.setTextColor(ILI9341_WHITE);
tft.println("El aceite esta empezando su vida util");
tft.println("No requiere cambio");
}
}
void dibujarTinaPregunta(NodoMedicion* actual, int x, int y) {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(0, 0);
if (!actual) return;
// Tina base (rectángulo con borde inferior curvo simulada)
tft.fillRect(x + 10, y + 10, 40, 20, ILI9341_YELLOW); // cuerpo
tft.drawRoundRect(x + 10, y + 10, 40, 20, 5, ILI9341_ORANGE); // borde
// "Curva" inferior (simulada con líneas diagonales)
tft.drawLine(x + 10, y + 30, x + 12, y + 32, ILI9341_ORANGE);
tft.drawLine(x + 50, y + 30, x + 48, y + 32, ILI9341_ORANGE);
// Signo de pregunta encima
tft.setTextColor(ILI9341_RED);
tft.setTextSize(2);
tft.setCursor(x + 22, y - 5);
tft.print("?");
// Texto informativo debajo
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(x, y + 40);
tft.print("ID: ");
tft.println(actual->id);
tft.setCursor(x, y + 50);
tft.print("Ultima: ");
tft.println(actual->ultimaMedicion);
tft.setCursor(x, y + 60);
tft.print("Iteracion: ");
tft.println(actual->medicionesIteracion);
}
int leerMedicionDesdeSerialConfirmadaConBoton() {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(20, 20);
// Limpiar buffer anterior
while (Serial.available() > 0) Serial.read();
String entrada = "";
tft.println("Escriba la medicion en el Serial.");
tft.println("Luego presione el boton para confirmar.");
// Esperar que el usuario escriba algo y luego presione botón
while (true) {
if (digitalRead(pinBoton) == HIGH) {
delay(300); // debounce
if (Serial.available()) {
entrada = Serial.readStringUntil('\n');
return entrada.toInt();
} else {
tft.println("No hay datos en Serial. Intente de nuevo.");
}
}
}
}
int leerTempDesdeSerialConfirmadaConBoton() {
tft.fillScreen(COLOR_BG); // O ILI9341_BLACK si usás esa
tft.setTextSize(1);
tft.setCursor(20, 20);
// Limpiar buffer anterior
while (Serial.available() > 0) Serial.read();
String entrada = "";
tft.println("Escriba la temperatura en el Serial.");
tft.println("Luego presione el boton para confirmar.");
// Esperar que el usuario escriba algo y luego presione botón
while (true) {
if (digitalRead(pinBoton) == HIGH) {
delay(300); // debounce
if (Serial.available()) {
entrada = Serial.readStringUntil('\n');
return entrada.toInt();
} else {
tft.println("No hay datos en Serial. Intente de nuevo.");
}
}
}
}
void setup() {
tft.begin();
tft.setRotation(0);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(0, 0);
tft.println("Modo espera, presione el boton si desea anadir una tina nueva");
//dibujarRelojEspera(20,20);
Serial.begin(11520);
pinMode(pinBoton, INPUT);
tiempoAnterior = millis();
}
void loop() {
if (digitalRead(pinBoton) == HIGH) {
delay(300); // debounce
dibujarCodigoBarras(20, 20);
// Limpiar entrada anterior
while (Serial.available() > 0) Serial.read();
String entradaID = "";
bool idRegistrado = false;
while (!idRegistrado) {
if (digitalRead(pinBoton) == HIGH) {
delay(300); // debounce
if (Serial.available()) {
entradaID = Serial.readStringUntil('\n');
int nuevoID = entradaID.toInt();
if (nuevoID > 0) {
cola.agregarOActualizar(nuevoID);
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.print("ID ");
tft.print(nuevoID);
tft.println(" registrado!");
idRegistrado = true;
} else {
tft.println("ID invalido. Intente de nuevo.");
}
delay(2000);
dibujarRelojEspera(20,20);
} else {
tft.println("No hay datos en Serial.");
}
}
}
delay(500);
tiempoAnterior = millis(); // Reiniciar temporizador de espera
}
if (millis() - tiempoAnterior >= tiempoEspera) {
alertaLa440();
NodoMedicion* actual = cola.frente();
if (actual) {
/*
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.print("Revisar aceite ID ");
tft.println(actual->id);
tft.print("Ultima: ");
tft.println(actual->ultimaMedicion);
tft.print("Iteraciones: ");
tft.println(actual->medicionesIteracion);
*/
dibujarTinaPregunta(actual, 20, 20);
// Espera confirmación con botón
tft.println("Presione el boton para comenzar.");
while (digitalRead(pinBoton) != HIGH); // Esperar botón presionado
detenerAlerta();
dibujarCajaComunicando(actual->id, 20, 20);
delay(300); // debounce
dibujarRelojArena(20, 20);
delay(3000);
int nuevaTemp = leerTempDesdeSerialConfirmadaConBoton();
if (nuevaTemp > umbralTemp) {
//tft.setTextColor(ILI9341_RED, ILI9341_BLACK);
//tft.println("Temperatura muy alta, saque el sensor");
dibujarErrorTina(20, 20);
while (digitalRead(pinBoton) != HIGH) {
sonidoNegativo();
delay(300);
}
} else {
dibujarCheck(20, 20);
delay(300); // debounce
int nuevaMedicion = leerMedicionDesdeSerialConfirmadaConBoton();
actual->ultimaMedicion = nuevaMedicion;
actual->medicionesIteracion++;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
if (nuevaMedicion > umbralCPT) {
/*
tft.setTextColor(ILI9341_RED, ILI9341_BLACK);
tft.print("Cambio requerido (");
tft.print(nuevaMedicion);
tft.println(")");
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.println("Modo espera, presione el boton si desea anadir una tina nueva");
*/
actual->medicionesIteracion = 0;
dibujarBateria(20, 20, 3);
sonidoNegativo();
} else if ((nuevaMedicion >= umbralCPT_medio) && (nuevaMedicion <= umbralCPT)){
dibujarBateria(20, 20, 2);
sonidoPositivo();
}
else {
/*
tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
tft.print("Aun en buen estado (");
tft.print(nuevaMedicion);
tft.println(")");
sonidoPositivo();
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.println("Modo espera, presione el boton si desea anadir una tina nueva");
*/
dibujarBateria(20, 20, 1);
sonidoPositivo();
}
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); // Restaurar color
cola.moverFrenteAlFinal();
}
delay(10000);
dibujarRelojEspera(20,20);
} else {
tft.println("No hay IDs registrados.");
delay(300); // debounce
detenerAlerta();
}
tiempoAnterior = millis(); // Reiniciar espera
}
}