The Pick Alarm encourages practicing while putting a stop to lost picks!

 

Step 1:

“Time to Practice” — The user is prompted to remove the pick and practice.

Step 2:

“Hit the Shed!” — While the user is practicing, a timer counts down one hour.

Step 3:

“Time’s Up!” — After an hour, a buzzer sounds until the pick is put back in its slot.

Step 4:

“Youre done, For today…” — The buzzer will stop once the pick is placed. The following day, the user will be prompted to practice again.

The red light is an indicator that the pick is NOT in its slot.

The green light is an indicator that the pick IS in its slot.

Side view

Process:

A major alteration that I made during my project was implementing the “practice once daily” feature. Originally, after a practice session there was another prompt to practice again. However, I wanted to make it so that the user could only practice once a day. In order to do this, I implemented a DS 3231 clock and majorly altered my finite state machine.

Original FSM

Final FSM

 

 

 

 

 

I would have liked to make my project more compact by using an Arduino nano and soldering my project to a proto board. However, there were no more proto boards or nanos left when I wanted to do this. As a result, I had to create a housing that was much bulkier than my original sketch.

Original sketch

Prototype in progress

 

 

 

 

 

 

Reflection:

Overall, I am proud of my project. First and foremost,  I am really impressed with how simple and effective implementing an FSM was. I  had never tried creating an FSM on a personal project before, but this turned out to be a perfect opportunity. However, my final product did fail to meet some of my expectations and my classmates had comments that reflected these shortcomings. For instance, one student said, “it would be obviously ideal if the ‘guts’ fit inside the box to make it more mobile,” and another said, “the execution needed a bit more time.” These are both valid and I agree with the former whole-heartedly. Although there are times when visibility of wires can be aesthetically pleasing, I don’t think my project benefits from it. In general, the phase I struggled most with was creating a housing. I would like to solder all of the circuitry to a proto board and use an Arduino nano to make the whole project more reliable and compact. Then, I would like to house the project in a clean lazercut box. Another comment that peaked my interest was, “it would be fun if the box pestered you to start practicing.” Many people wondered whether this was a feature and I honestly never thought to add it. In the future, I definitely would like to work it in, especially since so many people assumed it would be included. All in all, I had generally positive feedback saying that my project was a creative solution. It is encouraging to see and I would like to make a new version with all the features I have previously discussed. At the very least, my project will be useful for me. 

 

Code:

/*
 * The Pick Alarm
 * Created by Connor Maggio
 * 
 * The code below operates the Arduino that is the main funcitonality 
 * of the alarm. It uses a switch statement to change between states of 
 * an FSM based on IR LED values. It outputs to an LCD I2C screen, 
 * a green LED, a red LED, and a Piezo buzzer depending on the state. 
 * The LCD screen displays a timer that runs via the millis() function.
 * 
 * Pin mapping:
 * IR receiver --> A1
 * IR transmitter --> D3
 * Green LED --> D7
 * Red LED --> D8
 * Buzzer --> D10
 */

#include <DS3231.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C screen(0x27, 16, 2);
DS3231  rtc(SDA, SCL);

const int IR_RX = A1;
const int IR_TX = 3;
const int LEDG = 7;
const int LEDR = 8;
const int PIEZO = 10;

const int THRESHOLD = 12;
unsigned long start;
unsigned long timeElapsed;
unsigned long startTimer;
unsigned long threeSecStart;
unsigned long threeSecTimer;
unsigned long allSeconds;
int minutes;
int seconds;

String dayPracticed;

bool pickIn = false;
bool pickSet = false;
bool timerDone = false;
bool threeSecDone = false;
bool newDay = false;
bool alarmOn = false;

const int INIT = 0;
const int SETUP = 1;
const int MODE_IDLE = 2;
const int MODE_RUNNING = 3;
const int MODE_DONE = 4;
const int MODE_RINGING = 5;
const int MODE_TMRW = 6;

int currentMode = INIT;

void setup() {
  Serial.begin(9600);
  pinMode(IR_RX, INPUT);
  pinMode(IR_TX, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(PIEZO, OUTPUT);

  // Initialize the rtc object
  rtc.begin();
  rtc.setDOW(TUESDAY);     // Set Day-of-Week to SUNDAY
  rtc.setTime(23, 59, 00);     // Set the time to 12:00:00 (24hr format)
  rtc.setDate(3, 5, 2019);   // Set the date to January 1st, 2014

  screen.init();
  screen.backlight();
  screen.home();
  start = millis();
  delay(500);
}



void loop() {
  int IR_received;
  analogWrite(IR_TX, 1023);
  IR_received = analogRead(IR_RX);
//  Serial.println(IR_received);
  Serial.print(rtc.getDOWStr());
  Serial.println(rtc.getTimeStr());
  
  /*
   * LED Management
   */
   if (pickSet == true) {
    digitalWrite(LEDG, HIGH);
    digitalWrite(LEDR, LOW);
  } else {
    digitalWrite(LEDR, HIGH);
    digitalWrite(LEDG, LOW);
  }

  /*
   * Timer Management
   */
  int secsRemaining = allSeconds%3600;
  int runMinutes = secsRemaining/60;
  int runSeconds = secsRemaining%60;

  int downMinutes = 59 - runMinutes;
  int downSeconds = 59 - runSeconds;

  if (runSeconds >= 30)
  {
    timerDone = true;
  } else {
    timerDone = false;
  }

  /*
   * Day Management
   */
   if (dayPracticed.equals(rtc.getDOWStr(FORMAT_SHORT)) == false)
   {
     newDay = true;
   } else {
     newDay = false;
   }

  /*
   * Pick Setup
   */
   pickIn = IR_received < THRESHOLD;
   //Serial.println(pickIn);
   if (pickIn == true)
   {
    pickSet = true;
   } else {
    pickSet = false;
   }

  /*
   * Pick Detection
   */
   pickIn = IR_received < THRESHOLD;
//   Serial.println(pickIn);

  /*
   * Alarm Management
   */
   if (alarmOn == true)
   {
     tone(PIEZO, 440, 250);
     delay(1000);
   } else { //alarm is off
     noTone(PIEZO);
   }

   
  /*
   * Mode Management
   */
  switch(currentMode)
  {
    case INIT:
      if (pickSet == false)
      {
        currentMode = SETUP;
      }
      break;
    case SETUP:
      if (pickSet == true)
      {
        screen.clear();
        currentMode = MODE_IDLE;
      }
      break;
    case MODE_IDLE:
      if (pickIn == false)
      {
        screen.clear();
        startTimer = millis();
        currentMode = MODE_RUNNING;
      }
      break;
    case MODE_RUNNING:
      allSeconds = (millis() - startTimer)/1000;
      if (pickIn == true)
        {
          screen.clear();
          threeSecStart = millis();
          currentMode = MODE_DONE;
        }
      if (timerDone == true)
      {
        allSeconds = 0;
        screen.clear();
        currentMode = MODE_RINGING;
      }
      break;
    case MODE_DONE:
      threeSecTimer = (millis() - threeSecStart)/1000;
      if (pickIn == false)
        {
          screen.clear();
          threeSecStart = 0;
          threeSecTimer = 0;
          currentMode = MODE_RUNNING;
        }
      if (threeSecTimer > 3)
        {
          screen.clear();
          allSeconds = 0;
          threeSecStart = 0;
          threeSecTimer = 0;
          dayPracticed = rtc.getDOWStr(FORMAT_SHORT);
          currentMode = MODE_TMRW;
        }
      break;
    case MODE_RINGING:
      alarmOn = true;
      if (pickIn == true)
      {
        alarmOn = false;
        screen.clear();
        dayPracticed = rtc.getDOWStr(FORMAT_SHORT);
        currentMode = MODE_TMRW;
      }
      break;
    case MODE_TMRW:
      if (newDay == true)
        {
          if (pickIn == true)
          {
            screen.clear();
            currentMode = MODE_IDLE;
          } else { //pickIn == false
            screen.clear();
            currentMode = SETUP;
          }
        }
      break;
  }

  /*
   * LCD Management
   */
  char buf[9];
  sprintf(buf, "%02d:%02d", downMinutes, downSeconds);

  char def[9];
  sprintf(def, "%02d:%02d", 60, 0);
  
  switch(currentMode)
  {
    case INIT:
      break;
    case SETUP:
      screen.home();
      screen.print("Insert your Pick");
      screen.setCursor(0,1);
      screen.print("to get started!");
      break;
    case MODE_IDLE:
      screen.home();
      screen.print("Time to Practice");
      screen.setCursor(lcdCenter(5),1);
      screen.print(def);
      break;
    case MODE_RUNNING:
      screen.setCursor(lcdCenter(13),0);
      screen.print("Hit the Shed!");
      screen.setCursor(lcdCenter(5),1);
      screen.print(buf);
      break;
    case MODE_DONE:
      screen.home();
      screen.print("Are you done?");
      screen.setCursor(lcdCenter(14),1);
      screen.print("Leave the Pick");
      break;
    case MODE_RINGING:
      screen.setCursor(lcdCenter(10),0);
      screen.print("Time's Up!");
      screen.setCursor(lcdCenter(5),1);
      screen.print("00:00");
      break;
    case MODE_TMRW:
      screen.home();
      screen.print("You're done!");
      screen.setCursor(lcdCenter(12),1);
      screen.print("For today...");
      break;
  }
}

int lcdCenter(int stringSize) {
  int buf = stringSize%2;
  return 8 - (stringSize/2 + buf);
}