ANGRY POMODORO
Proyecto realizado por:
- Sergio Mulas Azabal
- Carlos Mariano García Sanz
- David González Rueda
- Gonzalo Lobo González
INTRODUCCIÓN
Angry Pomodoro es un dispositivo multifunción que está orientado al uso del estudio como herramienta. Se basa en el sistema Pomodoro que fuerza al estudiante a mantener el teléfono móvil en desuso durante el tiempo de estudio. Además, cuenta con una función alternativa de lamparita con varios modos de iluminación y un pequeño detalle a modo de función de bienvenida.
MATERIALES Y CONEXIONES
Los materiales utilizados en la totalidad del proyecto son:
Las conexiones del Arduino:
Video explicación sobre el Hardware del Angry Pomodoro:
IMPLEMENTACIÓN
SISTEMA POMODORO
Pomodoro es una técnica de gestión de tiempo utilizada para cualquier tarea. Si bien el proyecto está pensado para estudiantes, se podría utilizar en otros ámbitos de trabajo. Habitual-mente, los periodos de tiempo de esta técnica son de 25 minutos.
Una vez accedido al modo ‘Pomodoro’ con el pulsador ‘P0’ y, a través del pulsador ‘P1’, haber escogido el número de rondas de estudio, se le exige al usuario dejar su teléfono en un soporte encima del dispositivo. Esto causa que se active un pulsador (por el peso) que dará comienzo a los 25 minutos.
Si por algún motivo el usuario tomase el teléfono del soporte antes de que el periodo finalice, se desactivaría el pulsador, haciendo que el ‘Angry Pomodoro’ pare el tiempo de estudio. Si se coloca de nuevo el teléfono, se reanudaría el tiempo de estudio.
Como el usuario puede escoger cuantos periodos quiere realizar, entre periodo y periodo se deja un descanso de 5 minutos, en los que el usuario podrá tomar el teléfono del soporte sin que ‘Angry Pomodoro’ realice ningún sonido.
Esto ha sido implementado en código de la siguiente forma:
CÓDIGO
Existen variables que serán comunes a todos los modos. Éstas son:
Int state: el funcionamiento general, al ser una máquina de estados, utilizará esta variable. Indica en el modo en el que se encuentra el dispositivo.
Int buttonMode: indica el puerto al que se conectará el pulsador P0. Se trata del puerto 3 ya que, junto al 2, puede gestionar interrupciones.
Bool buttonModeSignal: en este se almacena el valor que recoge Arduino del pulsador ‘P0’ cuando se eje-cuta la interrupción.
LiquidCrystal lcd: utilizado para la conexión del LCD al Arduino. Se utilizan los puertos que se definen en el constructor (7 al 12). Se hace uso de una librería externa para obtener una capa de abstracción mayor sobre la escritura en el LCD.
Int buttonAdd: hace referencia al puerto en el que se conectará el pulsador ‘P1’. Está conectado en el puerto 2 porque, como se ha explicado anteriormente, puede gestionar interrupciones.
Bool buttonAddSignal: en este se almacena el valor que recoge Arduino del pulsador ‘P1’ cuando se ejecuta la interrupción.
Las variables dedicadas al funcionamiento del Pomodoro incluyen:
- Variables dedicadas al tiempo: son aquellas que se utilizan para la gestión del tiempo de estudio de la técnica Pomodoro. Estas son:
- minsForPomodoro: minutos pertenecientes a el periodo de tiempo de estudio. Para comprobar que el código funciona correctamente, se inicializa a 1, aunque a nivel de ejecución real, ésta debería inicializarse a 25.
- studyTime: cuánto tiempo (en segundos) queda para terminar el periodo de es-tudio (aunque también sirve para definir cuánto tiempo queda para terminar el periodo de descanso).
- secCounter y studyMin: variables auxiliares utilizadas para la impresión en el LCD del tiempo restante (tanto en el periodo de estudio como en el de descanso).
- Variables dedicadas al control de los ciclos de repetición: son aquellas que permiten la gestión de el número de rondas de estudio que se realizarán. Estas son:
- numOfCycles: el número de ciclos que el usuario realizará de la técnica (que puede escoger en tiempo de ejecución y que nunca superará de 9 rondas).
- currentCycle: ciclo en el que se encuentra el Pomodoro en el un determinado momento de tiempo.
- Variables dedicadas al control de tiempo de ejecución: son variables que sirven para sincronizar de manera correcta cada cuánto se ejecuta el bucle de Pomodoro (se explicará posteriormente). Éstas son:
- msPerLoop: número de milisegundos entre ejecuciones del bucle. Como se quiere sincronizar a un segundo, se inicializa a 1000.
- currentTime: variable auxiliar para almacenar los milisegundos que han pasado desde el comienzo de la ejecución (con la función millis( )).
- Variables para el pulsador utilizado como sensor de presión: para evitar tener que comprar un sensor de presión del que no se disponía, se utilizará un pulsador. Las variables que lo gestionan son:
- buttonPressure: es decir, a que puerto se conectará el pulsador. En el caso del Pomodoro, será el puerto 13.
- Active: es decir, si está o no pulsado (para controlar que el móvil está ejerciendo presión sobre el botón durante el estudio).
- Aux y AuxInit: variables auxiliares que sirven para control de ejecución (y que algunas partes del código se ejecuten únicamente una vez).
El funcionamiento de la máquina de estados se basa en la implementación de interrupciones. En Arduino, una interrupción es definida de la siguiente forma.
La función attachInterrupt recibe tres parámetros: el botón que genera la interrupción, la función que se ejecutará y en qué caso se ejecutará (CHANGE es cuando pasa de HIGH a LOW o de LOW a HIGH).
De esta forma, cuando se presione el botón ‘P0’, se ejecutará una interrupción, llamando a la función standPhone(). Esta función es la siguiente:
Esta función se encarga de variar el modo, cambiando la variable state. El condicional if(buttonModeSignal == HIGH) existe para que sólo se ejecute la función cuando no se tenga el botón presionado y se presione. Si no estuviera, se llamaría a la función dos veces (porque la señal cambia de LOW a HIGH cuando se presiona y de HIGH a LOW cuando se suelta).
Además, en esta función se llama a resetPomodoro(), encargada de resetear el dispositivo para que entre en el nuevo modo correctamente.
De esta forma, el bucle principal (loop) se compondrá únicamente de un switch que llamará a las diferentes funciones que implementan los modos. En el caso de que el estado sea 1 (es decir, modo Pomodoro) se ejecuta el siguiente código en el bucle principal:
La función POMODORO() está fragmentada en diferentes partes:
- Sincronización del bucle de ejecución con segundos
Este fragmento de código implementa una sincronización a nivel de segundo del bucle de Pomodoro. Se quiere que el bucle ejecute una vez cada segundo. Si por ejemplo se ejecuta la primera iteración del bucle en 5 milisegundos, no se quiere que se avance en la ejecución si no han pasado al menos un segundo. No es un método de sincronización exacto, pero el error que se comete es lo suficientemente pequeño para ser aceptable (ya que el dispositivo no debe de cumplir requisitos de tiempo real). Además, se lee si está o no activo el pulsador de presión.
- Ejecución del propio Pomodoro
[1] El Pomodoro debe de estar ejecutándose siempre que el ciclo en el que se encuentra el dispositivo en ese momento sea igual o menor que los ciclos que ha decidido el usuario.
[2] Comprobación de que el se encuentre activo el pulsador que mide la presión (es decir, que el dispositivo se encuentre en modo de estudio y que el móvil esté apoyado) o que el dispositivo se encuentre en modo descanso.
[3] La variable aux marca si se ha pasado de parado a ejecutando para cambiar lo que muestra el LCD. Es decir, que se limpie ‘PARADO’ y se escriba ‘ESTÁS ESTUDIANDO’.
[4] TimerFunc es una función que actualizará la cantidad de tiempo que falta para que se acabe el periodo de tiempo que se está ejecutando en ese momento (tanto si se encuentra el usuario en periodo de estudio o de descanso).
[5] Condicional que solo se ejecuta para actualizar la funcionalidad del Pomodoro si se ha acabado algún periodo, tanto de estudio como de descanso.
[6] Si no se viene de un descanso (es decir que se estaba ejecutando un periodo de estudio) y el ciclo actual no es el final, se debe de comenzar un descanso. Por lo tanto, se limpia la pantalla del LCD, se actualiza el booleano de restTime a true (marcando que se está en un periodo de descanso) y marcando el número de minutos de estudio (en una ejecución real serían 5, pero para la realización de pruebas se ha definido un descanso de 1 minuto).
[7] Si se vuelve de un periodo de descanso, se imprime en el LCD que se está estudiando y da comienzo a un nuevo ciclo (sumando uno a la variable que marca la variable actual). Si se superasen los ciclos, no se entraría en el condicional explicado en [1].
[8] Si no se está en periodo de descanso y no se detecta presión en el botón (es decir, que el usuario ha levantado el móvil mientras que estaba en periodo de estudio) se para la ejecución, imprimiendo que se ha parado el tiempo de estudio del Pomodoro.
[9] LCDPrint es la función encargada de imprimir cuanto tiempo queda tanto del periodo de estudio como del periodo de descanso:
[10] Por último, cuando se terminan los ciclos que se han decidido, se realiza una operación con el número de ciclos para que no se vuelva a entrar en el Pomodoro. Por último, se imprime que se ha terminado.
Para implementar que el usuario pueda añadir ciclos de estudio, se debe de utilizar el pulsador ‘P1’. Por lo tanto, en setup() se debe de inicializar el pulsador como pin de entrada, además de definir la interrupción que debe de ejecutar.
Como la función que implementa la interrupción en el pulsador ‘P0’, se llamará a la función moreCycles cada vez que se detecte que se ha presionado el botón ‘P1’. Esta función es la siguiente:
Como se ha explicado anteriormente, P1 ejecutará una funcionalidad distinta dependiendo del estado. Para ello, se tiene el switch que varía según el estado. En el caso de que se encuentre en modo Pomodoro (es decir, state = 1), se añade un ciclo de estudio (hasta un máximo de 9):
SISTEMA LUCES
Este modo se basa en el encendido de varios LEDs. Dependiendo del número de veces que se presione el pulsador ‘P1’, se cambiará el modo en el que los LEDs se activen. Se podrán iluminar:
Habrá varios modos:
- Modo 0: apagadas ambas luces. Utilizado para la inicialización.
- Modo 1: ambas luces iluminadas al máximo.
- Modo 2: luz roja encendida al máximo.
- Modo 3: luz azul encendida al máximo.
- Modo 4: ambas luces parpadean alternándose.
- Modo 5: ambas luces encendidas a menor potencia.
- Modo 6: ambas luces varían su potencia de forma simultánea.
- Modo 7: ambas luces cambian su potencia, pero de forma alterna (cuando una brilla más, la otra lo hace menos).
- Modo 8: funcionalidad del modo 6, pero más rápido.
Ya que los puertos varían su potencia, deberán estar conectados a algunos de los puertos PWN.
Esto ha sido implementado en código:
SISTEMA SALUDO
El usuario, al presionar el botón ‘P1’, hará que el Angry Pomodoro le salude hablándole y moviendo su cola (a través de un servo). Se mueve de derecha a izquierda dos veces de forma rápida.
Esto ha sido implementado en código:
CÓDIGO
- Variables iniciales: las variables utilizadas para la gestión del saludo son:
- Servo servo: para manejar el servo, se utiliza la abstracción proporcionada por la cabecera “Servo.h”. Se instancia un objeto de tipo Servo que representa el servo conectado.
- Int pos: variable que indica el número de grados que se mueve el servo.
- Int hello, helloAux: variables utilizadas para evitar los solapamientos de una ejecución del saludo con la siguiente.
- Inicialización en setup(): la inicialización se realiza con el siguiente código. El servo será conectado al puerto del Arduino 4.
- Funcionamiento: durante el loop, si la variable hello es 0 (es decir, que no se está ejecutando en ese momento la función de saludo), se llama a la función cuicui(), que implementa el movimiento del servo.
Lo más importante a comentar en la función es el movimiento que realiza el servo. Se ha implementado mediante la escritura de dos estructuras iterativas for que escriben en el servo el ángulo de giro que debe tener en un momento de la ejecución determinada.
Como en los anteriores casos, solo se llamará a la función cuicui de nuevo si no se encuentra en ejecución en ese momento y se pulsa P1 (captura del código de moreCycles(), función llamada en la interrupción de P1 y explicada anteriormente).
Se invocará a la función de saludo en el bucle de ejecución principal de la siguiente forma:
CÓDIGO COMPLETO
int state; int buttonMode = 3; bool buttonModeSignal = LOW; //------------------------------------------------------------------------------------------------------------------------------ // VARIABLES RELATED WITH POMODORO //------------------------------------------------------------------------------------------------------------------------------ //Set up the LCD #include <LiquidCrystal.h> LiquidCrystal lcd(7, 8, 9, 10, 11, 12); //Variables for initial Pomodoro process int minsForPomodoro; int studyTime; int secCounter; int studyMin; //Variables for Cyclic Pomodoro unsigned int numOfCycles; unsigned int currentCycle; bool restTime; //Variables for time control unsigned long msPerLoop = 1000; unsigned long currentTime = 0; //Variable for buttonAdd int buttonAdd = 2; bool buttonAddSignal = LOW; long debounce; long interruptInit; //Variable for buttonPressure int buttonPressure = 13; bool active; int aux; int auxInit; int configPom; //------------------------------------------------------------------------------------------------------------------------------ // VARIABLES RELATED WITH HELLO! //------------------------------------------------------------------------------------------------------------------------------ #include<Servo.h> Servo servo; int pos; int hello, helloAux; //------------------------------------------------------------------------------------------------------------------------------ // VARIABLES RELATED WITH LIGHTS //------------------------------------------------------------------------------------------------------------------------------ int contador; const int Azul = 5; const int Rojo = 6; void setup(){ debounce = 150; interruptInit=0; restTime= false; state = 0; pinMode(buttonMode,INPUT); attachInterrupt(digitalPinToInterrupt(buttonMode), standPhone, CHANGE); //------------------------------------------------------------------------------------------------------------------------------ // SET UP FOR POMODORO //------------------------------------------------------------------------------------------------------------------------------ //Open serial monitor for debugging Serial.begin(9600); //Set up buttonAdd pinMode(buttonAdd, INPUT); attachInterrupt(digitalPinToInterrupt(buttonAdd), moreCycles, CHANGE); //Set up buttonAdd pinMode(buttonPressure, INPUT); resetPomodoro(); if(state == 1) initLCD(); //------------------------------------------------------------------------------------------------------------------------------ // SET UP FOR HELLO! //------------------------------------------------------------------------------------------------------------------------------ servo.attach(4); pos = 0; servo.write(pos); hello = 0; helloAux = 0; //------------------------------------------------------------------------------------------------------------------------------ // SET UP FOR LIGHTS //------------------------------------------------------------------------------------------------------------------------------ contador=0; pinMode(Rojo,OUTPUT); pinMode(Azul,OUTPUT); lights(); } void loop(){ switch (state) { case 0: if (hello == 0) { hello = 1; cuicui(); } break; case 1: if (auxInit == 0){ initLCD(); configPom = 0; auxInit = 1; } POMODORO(); //State 1 break; case 2: lights(); break; } } void lights() { Serial.println(contador); switch (contador){ case 0: digitalWrite(Azul,LOW); digitalWrite(Rojo,LOW); break; case 1: digitalWrite(Azul,HIGH); digitalWrite(Rojo,HIGH); break; case 2: digitalWrite(Azul,LOW); digitalWrite(Rojo,HIGH); break; case 3: digitalWrite(Azul,HIGH); digitalWrite(Rojo,LOW); break; case 4: digitalWrite(Azul,HIGH); digitalWrite(Rojo,LOW); delay(250); digitalWrite(Azul,LOW); digitalWrite(Rojo,HIGH); delay(250); break; case 5: analogWrite(Azul,100); analogWrite(Rojo,100); break; case 6: for(int i=0;i<255;i++){ analogWrite(Azul,i); analogWrite(Rojo,i); delay(10); } for(int i=0;i<255;i++){ analogWrite(Azul,255-i); analogWrite(Rojo,255-i); delay(10); } break; case 7: for(int i=0;i<255;i++){ analogWrite(Azul,i); analogWrite(Rojo,255-i); delay(10); } for(int i=0;i<255;i++){ analogWrite(Azul,255-i); analogWrite(Rojo,i); delay(10); } break; case 8: for(int i=0;i<255;i++){ analogWrite(Azul,i); analogWrite(Rojo,255-i); delay(2); } for(int i=0;i<255;i++){ analogWrite(Azul,255-i); analogWrite(Rojo,i); delay(2); } break; } } void timerFunc(int* studyTime, int* secCounter, int* studyMin) { //Update s to end *studyTime = *studyTime - 1; //Control secsPerMin if((*secCounter) == 00) { (*secCounter) = 59; (*studyMin)--; }else (*secCounter)--; } void moreCycles() { //Control cyclic component buttonAddSignal = digitalRead(buttonAdd); // CONTROL THE STATE if(buttonAddSignal == HIGH and millis()-interruptInit>debounce) { interruptInit=millis(); switch(state) { case 0: Serial.println("Llamo cuicui"); if(helloAux == 0) hello = 0; break; case 1: numOfCycles = ((numOfCycles)%9)+1; Serial.println(numOfCycles); break; case 2: contador= (contador +1)%9; lights(); break; } } } void standPhone() { //Control cyclic component buttonModeSignal = digitalRead(buttonMode); Serial.println("Entro en la interrupcion de cambio estado"); if(buttonModeSignal == HIGH) { Serial.println("Cambio estado"); state = (state+1)%3; Serial.println(state); resetPomodoro(); delay(500); Serial.println("Ya he cambiado todo"); } } void resetPomodoro() { aux = 0; auxInit = 0; active = LOW; //Reset ligths contador = 0; //Set up cyclic variable config numOfCycles = 1; currentCycle = 1; restTime = 0; digitalWrite(Rojo,LOW); digitalWrite(Azul,LOW); //Set up initial time variable config minsForPomodoro = 1; (minsForPomodoro < 1)? minsForPomodoro = 1: minsForPomodoro = minsForPomodoro; studyTime = minsForPomodoro * 60; secCounter = 00; studyMin = minsForPomodoro; lcd.clear(); if (state == 0) lcd.print("[MODO SALUDO]"); if (state == 2) lcd.print("[MODO LUCES]"); } void POMODORO() { //Updating current time currentTime = millis(); active = digitalRead(buttonPressure); if (active == HIGH and configPom ==0) { lcd.setCursor(0,0); lcd.print("ESTAS ESTUDIANDO"); configPom = 1; } while(millis() < currentTime+msPerLoop){ //Synchronizing the loop with secs } //Update the LCD if(currentCycle <= numOfCycles) { if (active || restTime == true){ if (aux == 1){ lcd.clear(); lcd.setCursor(0, 0); lcd.print("ESTAS ESTUDIANDO"); aux = 0; } //Update Timer timerFunc(&studyTime, &secCounter, &studyMin); if(studyTime == 0) { Serial.println("He terminado el tiempo"); if(restTime == 0 && currentCycle < numOfCycles) { Serial.println("CAMBIO A DESCANSO"); restTime = 1; lcd.clear(); lcd.setCursor(0,0); lcd.print("DESCANSO"); studyMin = 1; studyTime = studyMin * 60; } else{ lcd.clear(); lcd.setCursor(0,0); lcd.print("ESTAS ESTUDIANDO"); restTime = 0; currentCycle++; (currentCycle <= numOfCycles)? studyMin = minsForPomodoro : studyMin = 0; studyTime = studyMin * 60; } } } else{ if(aux == 0 && configPom == 1) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("ESTUDIA >:C"); aux = 1; } } if(state == 1) LCDPrint(); }else{ lcd.setCursor(0, 0); if (currentCycle <= 10) { currentCycle = 11; lcd.clear(); } lcd.setCursor(0,0); lcd.print(" FIN DE ESTUDIO"); } //Debugging values Serial.print("SecsFromThisMin: "); Serial.println(secCounter); Serial.print("Mins to End: "); Serial.println(studyMin); Serial.print("currentMillis: "); Serial.println(currentTime); if (state == 2){ lcd.clear(); lcd.print("[MODO LUCES]"); } } void LCDPrint() { //Set up LCD values lcd.setCursor(0, 1); //Print mins left if(studyMin < 10){ lcd.print(0); lcd.print(studyMin); }else lcd.print(studyMin); lcd.print(":"); //Print secs left if(secCounter < 10){ lcd.print(0); lcd.print(secCounter); }else lcd.print(secCounter); //Print rounds lcd.setCursor(11,1); lcd.print("R:"); (currentCycle > numOfCycles)? lcd.print(currentCycle-1) : lcd.print(currentCycle); lcd.print("/"); lcd.print(numOfCycles); } void initLCD() { //Set up LCD config lcd.begin(16, 2); lcd.print("PREPARA ESTUDIO:"); //Print mins left lcd.setCursor(0, 1); if(studyMin < 10){ lcd.print(0); lcd.print(studyMin); }else lcd.print(studyMin); lcd.print(":"); //Print secs left if(secCounter < 10){ lcd.print(0); lcd.print(secCounter); }else lcd.print(secCounter); //Print rounds lcd.setCursor(11,1); lcd.print("R:"); lcd.print(currentCycle); lcd.print("/"); lcd.print(numOfCycles); } void cuicui(){ if(state == 0) { lcd.setCursor(0,0); lcd.print("HOLA AMIGO! :D"); helloAux = 1; for(int i = 0; i< 2; i++){ for(pos = 0; pos<115; pos = pos +5){ if(state != 0) break; Serial.println("Moviendo un grado"); servo.write(pos); delay(5); } for(pos = pos; pos>80; pos = pos-5){ if(state != 0) break; Serial.println("Moviendo menos un grado"); servo.write(pos); delay(5); } } } servo.write(90); helloAux = 0; lcd.clear(); lcd.setCursor(0,0); lcd.print("[MODO SALUDO]"); }
COSTE DE MATERIALES
Los materiales que se han tenido que comprar para la implementación final del proyecto son:
Nombre del material | Coste (p/u) |
1x Pila | 4 € |
PROBLEMAS ENCONTRADOS Y SOLUCIONES
Inicialmente, el proyecto tenía planteados otras directrices y funciones que, por cuestiones de hardware en su mayoría, no han podido ser implementadas:
- Función de reproducción de sonido: se compraron lectores de microSD para reproducir sonidos pregrabados como música, alarmas (que saltarían si se levantaba el teléfono cuando se estaba estudiando en el Pomodoro) y saludos. Sin embargo, ya que gran parte de los pines fueron conectados al LCD y a los tres botones de interrupción, no se pudo implementar su uso de forma correcta. Esto implicó un coste económico y de tiempo, ya que se habían comprobado las especificaciones del componente y cómo se programaba de forma correcta y se había comprado previamente.
- Funciones respecto a las luces: se utilizaron transistores para intentar que, con una misma señal de un puerto digital, se encendieran simultáneamente varios LEDs. Al montar el circuito, hubo un problema con los transistores, que acabaron quemándose. Como la implementación física necesitaba esos transistores y no se pudieron comprar a tiempo, se cambió esta implementación para dar soporte únicamente a las dos luces que tiene actualmente el proyecto.
Una vez explicados estos problemas, se pueden explicar aquellos encontrados durante el desarrollo de la práctica actual:
- Tiempo de control de ejecución en segundos del Pomodoro: ya que había que sincronizar el temporizador del tiempo de estudio en segundos reales.
- Efecto rebote en los pulsadores: hubo que implementar variables que evitaran que se detectaran pulsaciones fantasma por el efecto rebote de los pulsadores.
- Dificultades manejando descanso y periodo de estudio en Pomodoro.
Problemas de localización: ya que se ha tenido que hacer trabajo concurrente y separado por la situación sanitaria, han existido algunos problemas de comunicación y coordinación de los integrantes en diferentes situaciones. Por ejemplo, tras la nevada de Filomena, no se pudo continuar con el desarrollo de la parte hardware ya que uno de los integrantes se había olvidado de los componentes en su segunda residencia y no podía ir a por ellos por la situación meteorológica.