SaveDose: pastillero automático

Canal de Youtube

https://www.youtube.com/channel/UC-_vfpkI3VTB_ZssYJn1GsQ

Descripción del proyecto

El proyecto consiste en un pastillero automático inteligente que permite la planificación y distribución de pastillas de acuerdo con los horarios programados por el usuario. El dispositivo tiene 7 temporizadores configurables que permiten al usuario programar hasta 7 momentos distintos para la dispensación de pastillas. Se comunica con el usuario a través de una aplicación móvil por medio de Bluetooth (la cual se puede descargar mediante el siguiente enlace ), brindando una experiencia interactiva para gestionar y administrar la toma de medicamentos.

Características del sistema

  • Temporizadores Configurables: El pastillero tiene 7 temporizadores independientes que el usuario puede ajustar según sus necesidades.
  • Interacción Bluetooth: Se conecta a la aplicación móvil a través de Bluetooth para gestionar el pastillero, programar los temporizadores y gestionar el código de seguridad.
  • Código de Seguridad: Al iniciar la aplicación, el usuario debe ingresar un código para poder acceder a la dispensación de pastillas.
  • Pantalla LCD: La pantalla LCD muestra la hora en tiempo real y las alertas de los temporizadores.
  • Alarma de Recordatorio: Cuando un temporizador se activa, una alarma suena para avisar al usuario de que es momento de recoger la pastilla.
  • Monitoreo de Estado mediante semáforo LED: un semáforo muestra el estado del pastillero basándose en la cantidad restante en el pastillero (verde en caso de estar completamente lleno o casi completo, amarillo en caso de que tenga la mitad o menos dosis, rojo en caso de que haga falta rellenarlo).
  • Vencimiento de Tiempo: Si el usuario no recoge la pastilla en el tiempo determinado (15 segundos), el sistema indica que el tiempo ha expirado y no dispensará la pastilla.

Implementación y desarollo

Componentes Hardware

ComponenteFunción
Elegoo Mega 2560 R3Microcontrolador y placa de conexiones. Sustituye a la placa de Arduino dada en clase
Módulo Bluetooth HC-05Para la comunicación inalámbrica con la app.
Pantalla LCD1602Muestra información sobre los temporizadores y el estado del sistema.
Módulo RTC DS3231 Para obtener la hora exacta.
Motor Paso a PasoPara el dispensado automático de pastillas. Gira para hacer que se mueva la pieza que empuja las pastillas por la rampa.
SemáforoPara indicar el estado del pastillero (verde : lleno o semilleno. Amarillo: quedan menos de la mitad de las dosis programadas. Rojo: hay que rellenar el pastillero)
LEDPara indicar de manera visual que hay que recoger la dosis
ZumbadorFunciona como alarma que avisa de que hay que recoger la dosis
BotónPara que el usuario indique que ya se ha rellenado el pastillero

Código

#include <Wire.h>
#include <RTClib.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>
#include <Stepper.h>

RTC_DS3231 rtc;

class Fecha {
  int anio;
  int mes;
  int dia;
  int hora;
  int minuto;
  int segundo;
  int diasPorMes[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

public:
  Fecha() : anio(0), mes(0), dia(0), hora(0), minuto(0), segundo(0) {}
  Fecha(int a, int m, int d, int h, int min, int seg) {
    anio = a;
    mes = m;
    dia = d;
    hora = h;
    minuto = min;
    segundo = seg;
  }

  int getAnio() { return anio; }
  int getMes() { return mes; }
  int getDia() { return dia; }
  int getHora() { return hora; }
  int getMinuto() { return minuto; }
  int getSegundo() { return segundo; }

  void setAnio(int a) { anio = a; }
  void setMes(int m) { mes = m; }
  void setDia(int d) { dia = d; }
  void setHora(int h) { hora = h; }
  void setMinuto(int min) { minuto = min; }
  void setSegundo(int seg) { segundo = seg; }

  bool isLaterThan(DateTime f) {
    if (anio != f.year()) return anio > f.year();
    if (mes != f.month()) return mes > f.month();
    if (dia != f.day()) return dia > f.day();
    if (hora != f.hour()) return hora > f.hour();
    if (minuto != f.minute()) return minuto > f.minute();
    return segundo > f.second();
  }

  bool isValid() {
    if (mes < 1 || mes > 12) return false;
    if (dia < 1 || dia > diasPorMes[mes - 1]) return false;
    if (hora < 0 || hora > 23) return false;
    if (minuto < 0 || minuto > 59) return false;
    if (segundo < 0 || segundo > 59) return false;
    return true;
  }

  void assign(Fecha f) {
    anio = f.getAnio();
    mes = f.getMes();
    dia = f.getDia();
    hora = f.getHora();
    minuto = f.getMinuto();
    segundo = f.getSegundo();
  }
};

#define bt Serial1
//SoftwareSerial bt(18, 19); // RX, TX

// Configuración de la pantalla LCD
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

// Pines para semáforo, LED, zumbador, botón y motor paso a paso
int semRojo = 22;
int semAmarillo = 24;
int semVerde = 26;
int ledTomar = 32;
int zumbador = 28;
int botonRellenar = 30;

const int stepsPerRevolution = 290;  // Para el motor 28BYJ-48
Stepper myStepper(stepsPerRevolution, 7, 8, 9, 10);

// Variables de control
int pastillasRestantes = 0;  // Inicia vacío
String codigoUsuario = "";
Fecha temporizadores[7];     // Los 7 temporizadores se setearán con 'cambiarTemporizador'
int temporizadorActual = 0;  // Índice del temporizador actual
// La fecha actual se obtendrá del RTC (aunque tenemos una fecha inicial de respaldo)
Fecha fechaActual(2025, 4, 17, 19, 30, 0);

// flag para evitar múltiples dispensaciones para el mismo temporizador
bool pastillaDispensada = false;

//
// FUNCIONES
//

void mostrarConScrollLCD(String mensaje, int tiempoScroll = 300) {
  int largoMensaje = mensaje.length();
  for (int i = 0; i <= largoMensaje - 16; i++) {
    lcd.clear();  // Limpiar pantalla para mostrar nuevo fragmento
    lcd.setCursor(0, 0);
    lcd.print(mensaje.substring(i, i + 16));  // Mostrar solo los primeros 16 caracteres
    delay(tiempoScroll);  // Pausar entre desplazamientos
  }
}


// Función que muestra el semáforo según el stock de pastillas.
// Si no hay pastillas, muestra "No hay pastillas / Pulsa el botón" y llama a esperarRelleno()
void controlSemaforo() {
  if (pastillasRestantes == 0) { // Pastillero vacío: se debe rellenar
    digitalWrite(semRojo, HIGH);
    digitalWrite(semAmarillo, LOW);
    digitalWrite(semVerde, LOW);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("No hay pastillas");
    lcd.setCursor(0, 1);
    lcd.print("Pulsa el boton:");
    esperarRelleno();
  }
  else if (pastillasRestantes > 0 && pastillasRestantes <= 4) {
    digitalWrite(semRojo, LOW);
    digitalWrite(semAmarillo, HIGH);
    digitalWrite(semVerde, LOW);
  }
  else {
    digitalWrite(semRojo, LOW);
    digitalWrite(semAmarillo, LOW);
    digitalWrite(semVerde, HIGH);
  }
}



// Función que espera a que el usuario rellene el pastillero,
// mostrando mensajes en Bluetooth y en la pantalla.
void esperarRelleno() {
  bt.println("Debes rellenar las pastillas. Una vez lo hayas hecho, dale al botón");
  bool rellenado = false;
  while (!rellenado) {
    if (digitalRead(botonRellenar) == HIGH) {
      pastillasRestantes = 7; // Se considera que se han rellenado todas
      rellenado = true;
      lcd.clear();
    }
  }
    bt.println("Pastillero relleno");

}

// Función para cambiar el código de acceso, similar al código original.
void cambiarCodigo(bool mostrarMensaje) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Cambio codigo:");
  bool recibidoValido = false;
   if (mostrarMensaje) {
    bt.println("Introduce un codigo para recoger tus pastillas:");
  }
  delay(1000);
  while (!recibidoValido) {
    if (bt.available()) {
      String codigo = bt.readStringUntil('\n');
      codigo.trim();
      if (codigo.length() == 0) {
        Serial.println("Falla: el codigo debe tener al menos un caracter");
      } else {
        codigoUsuario = codigo;
        lcd.setCursor(0, 1);
        lcd.print(codigoUsuario);
        Serial.println("Nuevo codigo: " + codigoUsuario);
        bt.println("Nuevo codigo: " + codigoUsuario + " . Operacion realizada con exito");
        recibidoValido = true;
        lcd.clear();
      }
    }
  }
}

// Función que dispensa una pastilla (tras validar el código) y muestra el mensaje "Recoja su pastilla".
void dispensarPastillas() {
  if (pastillasRestantes > 0) {  // Solo procede si hay pastillas
    lcd.clear();
    lcd.setCursor(0, 0);
    mostrarConScrollLCD("Introduce tu codigo personal:");
    bt.println("Introduce el codigo personal para recoger tu pastilla:");
    unsigned long startTime = millis();
    bool recibidoValido = false;
    
    // Espera hasta 10 segundos para recibir el código
    while (!recibidoValido && millis() - startTime < 20000) {
      if (bt.available()) {
        String codigo = bt.readStringUntil('\n');
        codigo.trim();
        if (codigo.equals(codigoUsuario)) {
          lcd.clear();
          lcd.setCursor(0, 0);
          mostrarConScrollLCD("Recoja su pastilla");
          delay(1500); // Se muestra el mensaje para que el usuario lo vea
          myStepper.step(stepsPerRevolution);  // Dispensa la pastilla
          pastillasRestantes--;
          recibidoValido = true;
        } else {
          bt.println("Codigo incorrecto. Intenta de nuevo:");
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Codigo incorrecto");
        }
      } else {
        digitalWrite(ledTomar, HIGH);
        digitalWrite(zumbador, HIGH);
        delay(500);
        digitalWrite(zumbador, LOW);
        digitalWrite(ledTomar, LOW);
        delay(500);
      }
    }
    
    if (!recibidoValido) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Tiempo agotado");
      bt.println("Se ha agotado el tiempo para dispensar la pastilla");
      delay(2000);
    }
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("No hay pastillas");
    controlSemaforo();
    delay(2000);
  }
  // Avanza al siguiente temporizador y marca que se dispensó esta pastilla.
  temporizadorActual = (temporizadorActual + 1) % 7;
  pastillaDispensada = true;
}

// Función para cambiar el temporizador (fecha/hora) según comando recibido por Bluetooth.
void cambiarTemporizador() {
  int numero = 0;
  bool recibidoValido = false;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Cambio fecha ");
  bt.println("Introduce el temporizador (1-7) a cambiar:");
  while (!recibidoValido) {
    if (bt.available()) {
      String input = bt.readStringUntil('\n');
      numero = input.toInt();
      if (numero >= 1 && numero <= 7) {
        bt.println("Temporizador " + String(numero) + " seleccionado");
        recibidoValido = true;
        lcd.print(numero);
      } else {
        bt.println("Numero no valido, intenta de nuevo.");
      }
    }
  }
  recibidoValido = false;
  bt.println("Introduce una fecha en formato dd/mm/aaaa hh:mm:");
  while (!recibidoValido) {
    if (bt.available()) {
      String mensaje = bt.readStringUntil('\n');
      mensaje.trim();
      bt.print("Recibido: [");
      bt.print(mensaje);
      bt.println("]");
      Fecha nuevaFecha;
      nuevaFecha.setDia(mensaje.substring(0, 2).toInt());
      nuevaFecha.setMes(mensaje.substring(3, 5).toInt());
      nuevaFecha.setAnio(mensaje.substring(6, 10).toInt());
      nuevaFecha.setHora(mensaje.substring(11, 13).toInt());
      nuevaFecha.setMinuto(mensaje.substring(14, 16).toInt());
      if (nuevaFecha.isValid()) {
        temporizadores[numero - 1].assign(nuevaFecha);
        bt.println("Operacion correcta.");
        recibidoValido = true;
        lcd.clear();
      } else {
        bt.println("Fecha no valida, intenta de nuevo.");
      }
    }
  }
}

// Actualiza la pantalla LCD con la fecha y hora actual obtenida del RTC.
void actualizarFechaEnPantalla() {
  lcd.clear();
  DateTime now = rtc.now();
  lcd.setCursor(0, 0);
  if (now.day() < 10) lcd.print("0");
  lcd.print(now.day());
  lcd.print("/");
  if (now.month() < 10) lcd.print("0");
  lcd.print(now.month());
  lcd.print("/");
  lcd.print(now.year());

  lcd.print(" Sig:" + String(temporizadorActual + 1));
  
  lcd.setCursor(0, 1);
  if (now.hour() < 10) lcd.print("0");
  lcd.print(now.hour());
  lcd.print(":");
  if (now.minute() < 10) lcd.print("0");
  lcd.print(now.minute());
  lcd.print(":");
  if (now.second() < 10) lcd.print("0");
  lcd.print(now.second());
}

//
// CONFIGURACIÓN
//
void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  lcd.begin(16, 2);
  pinMode(semRojo, OUTPUT);
  pinMode(semAmarillo, OUTPUT);
  pinMode(semVerde, OUTPUT);
  pinMode(ledTomar, OUTPUT);
  pinMode(zumbador, OUTPUT);
  pinMode(botonRellenar, INPUT);
  myStepper.setSpeed(60); // revoluciones por minuto
  
  if (!rtc.begin()) {
    Serial.println("No se pudo encontrar el RTC");
    while (1);
  }
  if (rtc.lostPower()) {
    Serial.println("RTC perdió energía, configurando hora...");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  
  digitalWrite(zumbador, LOW);
  delay(1000); 
  bt.println("Bienvenido. Por favor, introduce un codigo para recoger tus pastillas.");

  // Configuraciones iniciales: código, temporizador y pastillero vacío.
  actualizarFechaEnPantalla();
  cambiarCodigo(false);
  cambiarTemporizador();
  pastillasRestantes = 0;  // Inicia vacío
  
  // Mensaje inicial enviado por Bluetooth para que se ingrese el código personal
  bt.println("Puedes cambiar 'codigo' o 'tiempo' escribiendo ese comando.");
}

//
// CICLO PRINCIPAL
//
void loop() {
  DateTime now = rtc.now();
  actualizarFechaEnPantalla();
  controlSemaforo();
  
  // Si la hora actual es igual o supera el temporizador programado,
  // se procede a dispensar la pastilla (se valida con !isLaterThan).
  if (!temporizadores[temporizadorActual].isLaterThan(now)) {
    if (!pastillaDispensada) {
      dispensarPastillas();
    }
  } else {
    pastillaDispensada = false;
  }
  
  // Procesa comandos entrantes por Bluetooth para cambiar el código o el temporizador
  if (bt.available()) {
    String comando = bt.readStringUntil('\n');
    comando.trim();
    String codigo;
    if (comando.equals("codigo")) {
      bt.println("Cambio de codigo. Introduce tu codigo actual:");
      bool disponible = false;
      while (!disponible) {
        if (bt.available()) {
          codigo = bt.readStringUntil('\n');
          codigo.trim();
          disponible = true;
        }
      }
      if (codigo.equals(codigoUsuario)) {
        cambiarCodigo(true);
      } else {
        bt.println("Codigo incorrecto.");
      }
    }
    else if (comando.equals("tiempo")) {
      bt.println("Cambio de temporizador:");
      cambiarTemporizador();
    }
  }
  
  delay(100);
}

Planos del sistema

A continuación, se pueden ver los planos iniciales del sistema, los cuales se hicieron cuando se diseñó el pastillero.

Plano de la base del pastillero

Plano de la estructura que gira

Plano de la estructura que gira por abajo

plano de la caja que contiene el pastillero

plano lateral 1 de la caja

plano lateral 2 de la caja

Diseño del circuito en Fritzing

circuito en Fritzing

Casos de uso

ID-CUTítuloActorDescripción
CU-1Rellenar pastilleroUsuarioEl usuario presiona el botón de «rellenar» para indicar que ha colocado las pastillas en el pastillero. Una vez realizado el relleno, el sistema lo notifica y se restablece el número de dosis 7.
CU-2Cambiar el código de accesoUsuarioEl usuario puede cambiar el código de acceso para retirar las pastillas a través de la comunicación Bluetooth con la app móvil. Se solicita al usuario introducir el código actual y luego se le permite establecer uno nuevo.
CU-3Configurar temporizadoresUusarioA través de Bluetooth, el usuario puede configurar hasta 7 temporizadores (día, mes, hora, minuto) para programar el dispensado automático de pastillas. Cada temporizador puede configurarse de manera independiente.
CU-4Recoger pastillaUusarioCuando el temporizador alcanza la hora programada, el sistema emite una alarma (zumbador y luz LED) para notificar al usuario. El usuario debe ingresar su código personal a través de la app para recibir la pastilla.
CU-5Verificar estado del pastilleroUsuarioEl sistema indica el estado del pastillero mediante un semáforo de LED (Verde: Pastillero lleno o con suficientes pastillas.
Amarillo: Pastillero con menos de la mitad de las pastillas.
Rojo: Pastillero vacío y necesita ser rellenado.)
CU-6Alarma por tiempo agotadoUsuarioSi el usuario no ingresa el código de acceso para recoger la pastilla dentro de los 10 segundos, el sistema emite una advertencia por Bluetooth y en la pantalla LCD («Tiempo agotado»), y no se dispensa la pastilla.
CU-7Actualizar fecha y hora del sistemaSistemaEl sistema sincroniza la fecha y hora automáticamente usando el módulo RTC DS3231. La hora actual se muestra en la pantalla LCD para el usuario y se usa para gestionar los temporizadores.

Vídeo del sistema

Fotos del sistema

montaje completo

conexiones en la placa

posición del motor

Costes

PiezaUnidadesCoste (€)
ELEGOO MEGA Azul R3 Board ATmega 2560 Compatible Con Arduino125,99
AZDelivery HC-05 Bluetooth Wireless Module110,99
AZDelivery RTC DS323117,49
Semáforo LED38,49
Láminas de madera de balsa27,5
Lámina de madera de la caja110
Total
70,46

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *