#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <AccelStepper.h>
#include <HX711.h>
// --- Configuración WiFi ---
// !!! REEMPLAZA CON TUS DATOS !!!
const char* ssid = "red Anto_2.4GHz";
const char* password = "Tabeni2928";
// --- Configuración NTP (Hora de Internet) ---
WiFiUDP ntpUDP;
// GMT-3 (Argentina). Ajusta el offset en segundos (3600 * -3)
NTPClient timeClient(ntpUDP, "pool.ntp.org", -10800, 60000);
// --- Pines de Componentes ---
// Pantalla OLED I2C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Motor Stepper (A4988)
// CORREGIDO: para coincidir con tu diagrama (4 y 5)
#define STEP_PIN 4
#define DIR_PIN 5
// (Modo DRIVER: 1 pulso = 1 paso)
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
// Celda de Carga (HX711)
#define LOADCELL_DOUT_PIN 17
#define LOADCELL_SCK_PIN 16
HX711 scale;
float calibration_factor = -7050; // Ajusta este valor
// Componentes simples
// CORREGIDO: para coincidir con tu diagrama (32 y 2)
#define LED_PIN 32
#define BUZZER_PIN 2
// --- NUEVOS BOTONES ---
#define UP_PIN 13
#define DOWN_PIN 12
#define SELECT_PIN 14
// --- Variables Globales ---
int alarmHour = 8; // Hora de alarma por defecto
int alarmMinute = 0; // Minuto de alarma por defecto
bool alarmTriggeredToday = false; // Para que suene solo una vez
// Estados de la UI para configurar la alarma
enum UIState { IDLE, SETTING_HOUR, SETTING_MINUTE };
UIState currentState = IDLE;
// Variables para debounce de botones
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 250; // 250ms
// --- Prototipos de Funciones ---
void connectToWiFi();
void setupPins();
void updateDisplay();
void handleButtons();
void dispenseMedication();
void setup() {
Serial.begin(115200);
Wire.begin(); // Iniciar I2C (para OLED)
setupPins();
// Iniciar OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("Fallo al iniciar SSD1306"));
for(;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("Dispensador Meds");
display.println("Conectando WiFi...");
display.display();
connectToWiFi(); // Conectar a WiFi
timeClient.begin(); // Iniciar cliente NTP
// Iniciar Stepper
stepper.setMaxSpeed(500); // Pasos por segundo
stepper.setAcceleration(200);
// Iniciar Celda de Carga
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
scale.set_scale(calibration_factor);
scale.tare(); // Poner en cero la balanza
display.println("Conectado!");
display.println("Listo.");
display.display();
delay(1000);
}
void loop() {
// Actualizar la hora de internet
timeClient.update();
// Manejar los botones (solo si no se está dispensando)
handleButtons();
// Actualizar la pantalla
updateDisplay();
// Lógica principal de la alarma (solo si no estamos configurando)
if (currentState == IDLE) {
int currentHour = timeClient.getHours();
int currentMinute = timeClient.getMinutes();
// Comprobar si es la hora de la alarma
if (currentHour == alarmHour && currentMinute == alarmMinute) {
if (!alarmTriggeredToday) {
dispenseMedication();
alarmTriggeredToday = true; // Marcar como disparada
}
} else {
// Resetear el trigger al pasar el minuto de la alarma
alarmTriggeredToday = false;
}
}
// El motor stepper necesita ser "corrido" constantemente
stepper.run();
}
void setupPins() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
// Configurar botones con PULLUP interno
// (Detectan LOW cuando se presionan)
pinMode(UP_PIN, INPUT_PULLUP);
pinMode(DOWN_PIN, INPUT_PULLUP);
pinMode(SELECT_PIN, INPUT_PULLUP);
}
void connectToWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi conectado!");
}
/**
* @brief Actualiza la pantalla OLED según el estado de la UI
*/
void updateDisplay() {
display.clearDisplay();
display.setTextSize(2); // Tamaño grande
display.setCursor(0, 0);
// Formatear hora actual (HH:MM)
String currentTime = timeClient.getFormattedTime().substring(0, 5);
display.print("Hora: ");
display.println(currentTime);
display.setTextSize(1);
display.print("Alarma: ");
// Formatear hora de alarma (HH:MM)
char alarmTime[6];
sprintf(alarmTime, "%02d:%02d", alarmHour, alarmMinute);
display.println(alarmTime);
display.println("-----------------");
// Lógica de la UI de configuración
switch (currentState) {
case IDLE:
display.println("Pres. SELECT conf.");
break;
case SETTING_HOUR:
display.println("CONFIGURANDO HORA");
display.drawRect(36, 16, 26, 10, WHITE); // Resaltar hora
break;
case SETTING_MINUTE:
display.println("CONFIG. MINUTO");
display.drawRect(65, 16, 26, 10, WHITE); // Resaltar minuto
break;
}
display.display();
}
/**
* @brief Maneja la lógica de los botones con debounce
*/
void handleButtons() {
// Simple debounce: solo reacciona si ha pasado un tiempo
if (millis() - lastDebounceTime < debounceDelay) {
return;
}
// Botón SELECT
if (digitalRead(SELECT_PIN) == LOW) {
lastDebounceTime = millis();
currentState = (UIState)((currentState + 1) % 3); // Cambia entre IDLE, SET_H, SET_M
if (currentState == IDLE) {
Serial.println("Alarma guardada!");
// Aquí podrías guardar en EEPROM si quisieras
}
}
// Botón UP (Subir)
else if (digitalRead(UP_PIN) == LOW) {
lastDebounceTime = millis();
if (currentState == SETTING_HOUR) {
alarmHour = (alarmHour + 1) % 24; // 0-23
} else if (currentState == SETTING_MINUTE) {
alarmMinute = (alarmMinute + 1) % 60; // 0-59
}
}
// Botón DOWN (Bajar)
else if (digitalRead(DOWN_PIN) == LOW) {
lastDebounceTime = millis();
if (currentState == SETTING_HOUR) {
alarmHour = (alarmHour - 1 + 24) % 24; // 0-23 (con vuelta)
} else if (currentState == SETTING_MINUTE) {
alarmMinute = (alarmMinute - 1 + 60) % 60; // 0-59 (con vuelta)
}
}
}
/**
* @brief Activa el motor, LED y buzzer para dispensar
*/
void dispenseMedication() {
Serial.println("DISPENSANDO MEDICACION!");
// Encender LED y Buzzer
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH); // O usa tone() para un sonido
// Mover el stepper (ej. media vuelta = 100 pasos en 1/2 step)
// Ajusta '200' a los pasos que necesites
stepper.move(200);
stepper.runToPosition(); // Bloqueante: espera a que termine
// (Aquí podrías agregar lógica con la celda de carga)
// ej. Esperar a que el peso baje
delay(1000); // Espera 1 segundo
// Apagar
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
Serial.println("Dispensado completado.");
}