This alarm clock won’t stop beeping until you unscramble a word.
Response to in-class crit
- “Maybe could laser cut something for a more permanent solution.”
- “Very annoying and effective alarm that will probably wake you up better since you need to actually think for this puzzle which uses brain power.”
- “It might be too hard when you wake up. I think you’ll end up unplugging it rather than solving the puzzle.”
As anticipated, a lot of the responses were that the enclosure was unrefined and a lasercut box would be more polished. One thing I didn’t consider was how to prevent methods of cheating, such as unplugging the Arduino or destroying the speaker. A sturdy box that’s screwed shut would be a good solution. You could still change the battery when needed, but it would take you several minutes to unscrew and open the box. People did like the concept though and said that the alarm and puzzle itself appeared very functional.
Self critique
I’m happy with how the project came out functionally. The menus and the puzzle are user-friendly and easy to interact with. My main goal was to have a hard puzzle that would be truly challenging and force me to have to think, which I satisfied. I wish I budgeted more time toward making a better/smaller/sturdier enclosure.
What I learned
Getting several components to interact took probably 3 times as long as I expected. I knew that all the components I was using had free libaries that I could use, but actually digging through the documentation and testing the behavior to get what I wanted was not always straightforward. There was lots of troubleshooting for each component, and some places where I had to check multiple places to see where my issue was. I got hung up for a while because one of my wires came out of the Arduino and my LCD display spontaneously stopped working.
Next steps
The project is functionally all there, but as I said before, I’d build a new enclosure for it that could only be taken apart with a screwdriver and lots of patience.
#include <Encoder.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <Keypad.h> #include "DS3231.h" const int NUM_WORDS = 32; String words[] = { "change", "again", "animal", "begin", "began", "black", "machine", "field", "final", "ocean", "behind", "decide", "common", "check", "among", "dance", "engine", "million", "middle", "child", "climb", "office", "clean", "blood", "decimal", "imagine", "chief", "clock", "block", "chance", "claim", "chick" }; const byte ROWS = 4; const byte COLS = 4; char keys[ROWS][COLS] = { {'a','b','c','d'}, {'e','f','g','h'}, {'i','j','k','l'}, {'m','n','o','p'} }; //byte rowPins[ROWS] = {5, 4, 3, 2}; //connect to the row pinouts of the keypad //byte colPins[COLS] = {12, 8, 7, 6}; //connect to the column pinouts of the keypad byte rowPins[ROWS] = {2, 3, 4, 5}; byte colPins[COLS] = {6, 7, 8, 12}; Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display RTClib RTC; Encoder knob(11, 9); String currentWord, currentScramble; String enteredSoFar; String shuffle(String s) { String result = String(s); for (int i = s.length() - 1; i > 0; i--) { int j = random(0, i + 1); char temp = result[i]; result.setCharAt(i, result[j]); result.setCharAt(j, temp); } Serial.print("Original string: "); Serial.println(s); Serial.print("Shuffled string: "); Serial.println(result); return result; } void resetWord() { currentWord = String(words[random(0, NUM_WORDS - 1)]); currentScramble = shuffle(currentWord); enteredSoFar = String(""); } void setup() { lcd.init(); lcd.backlight(); Serial.begin(9600); randomSeed(analogRead(0)); pinMode(13, INPUT); resetWord(); } enum State { REGULAR_CLOCK, SET_ALARM_H, SET_ALARM_M, CANCEL_ALARM, ALARM_WAITING, ALARM_RINGING, CONGRATS }; State currentState = REGULAR_CLOCK; int alarmH = 0; int alarmM = 0; long lastUpdateTime = 0; const int REFRESH_INTERVAL = 500; long setAlarmTime = 0; const int CANCEL_INTERVAL = 5000; bool oldButtonState = true; bool newButtonState; bool cleanButtonPress() { newButtonState = digitalRead(13); bool returnVal = !newButtonState && oldButtonState; oldButtonState = newButtonState; return returnVal; } void loop() { switch(currentState) { case REGULAR_CLOCK: { DateTime now = RTC.now(); if (millis() - lastUpdateTime > REFRESH_INTERVAL) { lcd.clear(); lcd.print(now.hour(), DEC); lcd.print(":"); byte m = now.minute(); lcd.print(m < 10 ? "0" : ""); lcd.print(m, DEC); lastUpdateTime = millis(); } if (cleanButtonPress()) { currentState = SET_ALARM_H; } } break; case SET_ALARM_H: alarmH = (knob.read() / 4) % 24; while(alarmH < 0) alarmH += 24; if (millis() - lastUpdateTime > 100) { lcd.clear(); lcd.print("Hour:"); lcd.print(alarmH); lcd.print(":"); lcd.print(alarmM < 10 ? "0" : ""); lcd.print(alarmM); lastUpdateTime = millis(); } if (cleanButtonPress()) { currentState = SET_ALARM_M; } break; case SET_ALARM_M: alarmM = (knob.read() / 4) % 60; while(alarmM < 0) alarmM += 60; if (millis() - lastUpdateTime > 100) { lcd.clear(); lcd.print("Minute:"); lcd.print(alarmH); lcd.print(":"); lcd.print(alarmM < 10 ? "0" : ""); lcd.print(alarmM); lastUpdateTime = millis(); } if (cleanButtonPress()) { currentState = CANCEL_ALARM; setAlarmTime = millis(); } break; case CANCEL_ALARM: if (millis() - lastUpdateTime > REFRESH_INTERVAL) { lcd.clear(); lcd.print("Set:"); lcd.print(alarmH); lcd.print(":"); lcd.print(alarmM < 10 ? "0" : ""); lcd.print(alarmM); lcd.setCursor(0, 1); lcd.print("Cancel? ("); lcd.print(((CANCEL_INTERVAL - (millis() - setAlarmTime)) / 1000) + 1); lcd.print(")"); lastUpdateTime = millis(); } if (cleanButtonPress()) { currentState = REGULAR_CLOCK; } if (millis() - setAlarmTime >= CANCEL_INTERVAL) { currentState = ALARM_WAITING; } break; case ALARM_WAITING: { DateTime now = RTC.now(); if (millis() - lastUpdateTime > REFRESH_INTERVAL) { lcd.clear(); lcd.print(now.hour(), DEC); lcd.print(":"); byte m = now.minute(); lcd.print(m < 10 ? "0" : ""); lcd.print(m, DEC); lastUpdateTime = millis(); lcd.setCursor(0, 1); lcd.print("Alarm at "); lcd.print(alarmH); lcd.print(":"); lcd.print(alarmM < 10 ? "0" : ""); lcd.print(alarmM); lastUpdateTime = millis(); } if (now.hour() == alarmH && now.minute() == alarmM) { currentState = ALARM_RINGING; } } break; case ALARM_RINGING: { if (millis() % 1000 < 500) tone(10, 600); else noTone(10); int i = (knob.read() / 4) % currentScramble.length(); while (i < 0) i += currentScramble.length(); if (millis() - lastUpdateTime > REFRESH_INTERVAL) { lcd.clear(); lcd.print(String(i + 1) + ": " + currentScramble[i] + " " + enteredSoFar); lastUpdateTime = millis(); } char key = keypad.getKey(); if (key) { enteredSoFar.concat(key); if (enteredSoFar != currentWord.substring(0, enteredSoFar.length())) { lcd.clear(); lcd.print("WROOOOOOONG"); enteredSoFar = String(""); delay(700); } else if (enteredSoFar == currentWord) { resetWord(); currentState = CONGRATS; } } } break; case CONGRATS: lcd.clear(); lcd.print("Hooray!"); lcd.setCursor(0,1); lcd.print("You're awake"); delay(2000); currentState = REGULAR_CLOCK; break; } }
Comments are closed.