# Imports:
# import network
# import socket
from time import sleep, time
import picozero
#from picozero import pico_temp_sensor, pico_led, Button, InputDevice, OutputDevice, PWMOutputDevice
from picozero import pico_temp_sensor, pico_led, Button, PWMOutputDevice, Speaker, RGBLED
import machine
import utime
from machine import I2C, Pin, Timer
from I2C_LCD import I2CLcd
import math
import hashable_timer
from hashable_timer import HashableTimer
import random
# Variables:
upButton = Button(2)
downButton = Button(3)
setButton = Button(4)
#hapticFeedback = PWMOutputDevice(15)
speaker = Speaker(6)
rgb = RGBLED(red = 28, green = 27, blue = 26)
i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=400000)
devices = i2c.scan()
if devices != []:
lcd = I2CLcd(i2c, devices[0], 2, 16)
else:
print("No address found")
#Relevant to numerous areas:
setModes = ["clock", "timer", "reminder"] #["clock", "timer", "reminder"] #["sleep", "default", "timer", "reminder", "calendarEvent", "study", "personalGoal"]
setModeFunction = {
"sleep" : lambda: sleepMode(),
"clock" : lambda: clockMode(),
"timer" : lambda: timerMode(),
"reminder" : lambda: reminderMode(),
#"calendarEvent" : lambda: calendarMode(),
#"study" : lambda: studyMode(),
#"personalGoal" : lambda: personalGoalMode()
}
selectFunction = {
"timer" : lambda: selectTimer(),
"reminder" : lambda: selectReminder(),
"clock" : lambda: startClock(),
"settings" : lambda: changeSettings()
}
modeIndex = 0
currMode = setModes[modeIndex]
selecting = False
#Relevant to specific categories:
#clock variables:
clockSecondsElapsed = 0
startTime = 0
#timer variables:
activeTimers = []
#timerIDs = {}
#Stored in seconds
timerLengths = {}
timersElapsed = {}
currTimeIndex = 0
displayingTimer = False
#reminder variables:
activeReminders = []
reminderOptions = ["Do Laundry", "Take medicine", "Feed cat", "Call parents", "Do homework", "Pick up sister", "Get groceries"]
#reminderOptions = ["Revisit question", "Revisit section", "Answer question", "Check question"]
currRemindIndex = 0
#user preference stuff:
timeInterval = 2.0
#start the time interval reminder once the user chooses the length
alarmType = "rgb" #"speaker" #other option is "rgb"
alarmSpecification = (1, 0, 0) #"d3" #alternately, a color in (r, g, b) format (values of 1 or 0)
# Functions:
def changeMode(value):
global currMode, modeIndex, setModes, lcd
lcd.display_on()
modeIndex = setModes.index(currMode)
modeIndex += value
modeIndex = modeIndex % (len(setModes))
currMode = setModes[modeIndex]
#note: sleep mode does not work this way. it is not in the list setModes
#the following code produces a vibration to signal which setting they're selecting
"""for i in range(modeIndex):
hapticFeedback.value = 0.5
sleep(0.5)
hapticFeedback.value = 0
sleep(0.5)"""
#instead just display which mode is being selected
print(currMode)
setModeFunction[currMode]()
def modeButtons():
global currMode
while (True):
if upButton.is_pressed:
changeMode(1)
elif downButton.is_pressed:
changeMode(-1)
elif setButton.is_pressed:
selectFunction[currMode]()
#mode functions:
def sleepMode():
global selecting, lcd
selecting = False
#screen off, minimize power usage
lcd.display_off()
while (not selecting):
if (upButton.is_pressed or downButton.is_pressed or setButton.is_pressed):
#when they press a button, turn display on and enter clock mode
"""lcd.display_on()
currMode = "clock"
setModeFunction(currMode)"""
#might change it to be that they enter last used mode
changeMode(0)
selecting = True
def clockMode():
global clockSecondsElapsed
#display clock w/ current time
if clockSecondsElapsed == 0:
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("Begin Timing?")
else:
clockIncrementTimer = HashableTimer(mode=Timer.PERIODIC, period = 1000, callback = displayClock)
def timerMode():
global currTimeIndex, displayingTimer, selecting
selecting = False
#display current timer (soonest ending)
currTimeIndex = 0
if len(activeTimers) >= 1:
currentTimer = activeTimers[currTimeIndex]
displayingTimer = True
displayTimer(currentTimer)
else:
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("No active timers :(")
#select button lets you rotate through if multiple timers are set
#last option on the list of timers is to set new time
def reminderMode():
global currRemindIndex, activeReminders
#display either the most urgent reminder or the one pinned by the user (consider using queues). Alternatively, display the reminders randomly
#like timers, select button lets you rotate through the various reminders (THESE WILL BE SORTED)
#last option sets new reminder
if len(activeReminders) >= 1:
currentReminder = activeReminders[currRemindIndex]
currRemindIndex = random.randint(0, len(activeReminders)-1)
currentReminder = activeReminders[currRemindIndex]
displayReminder(currentReminder)
else:
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("No active reminders :(")
#REMINDER: MAKE FUNCTIONS FOR THE OTHER MODES
# Clock Functions:
def startClock():
global clockSecondsElapsed, startTime, timeInterval
startTime = utime.time()
clockSecondsElapsed = 1
#clock = Timer(mode=Timer.PERIODIC, period = 1000, callback = incrementClock)
if (not (timeInterval == 0)):
timeIntervalReminder = Timer(mode=Timer.PERIODIC, period = (int)(timeInterval * 60000), callback = indicateTimeInterval)
#use a function that wraps around and returns the endTimer function
clockMode()
def incrementClock(t):
global clockSecondsElapsed
clockSecondsElapsed += 1
def indicateTimeInterval(t):
global alarmType, alarmSpecification
if alarmType == "speaker":
speaker.play("alarmSpecification", 0.35)
elif alarmType == "rgb":
rgb.pulse(n = 1)
def displayClock(t):
global currMode, clockSecondsElapsed, startTime
if currMode == "clock":
clockSecondsElapsed = utime.time() - startTime
hoursElapsed = math.floor(clockSecondsElapsed / 3600)
minutesElapsed = math.floor((clockSecondsElapsed % 3600) / 60)
secondsElapsed = math.floor(clockSecondsElapsed % 60)
clockTimeString = "{:02}:{:02}:{:02}".format(hoursElapsed, minutesElapsed, secondsElapsed)
lcd.clear()
lcd.move_to(0,0)
lcd.putstr(clockTimeString)
else:
return
# Timer Functions:
#in the future, allow users to customize the intervals they want for setting timers
def setTimer():
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
selecting = True
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Setting Timer:")
lcd.move_to(0, 1)
timerLength = 0.5 #CHANGE THIS BACK TO 0.5
lcd.putstr(str(timerLength) + " Minutes")
while selecting:
#if (buttonUp.is_pressed and buttonDown.is_pressed):
#do whatever happens then
if (upButton.is_pressed): #elif (upButton.is_pressed):
exitSelection.deinit()
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
if timerLength <= 5:
timerLength+=0.5
elif timerLength <= 60:
timerLength += 5
else:
timerLength += 15
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Setting Timer...")
lcd.move_to(0, 1)
lcd.putstr(str(timerLength) + " Minutes")
#change this for decreasing
elif (downButton.is_pressed):
exitSelection.deinit()
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
if timerLength > 0.5:
if timerLength <= 5:
timerLength-=0.5
elif timerLength <= 60:
timerLength -= 5
else:
timerLength -= 15
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Setting Timer...")
lcd.move_to(0, 1)
lcd.putstr(str(timerLength) + " Minutes")
#timer class: https://docs.micropython.org/en/latest/library/machine.Timer.html
if (setButton.is_pressed):
exitSelection.deinit()
sortTimer(HashableTimer(mode=Timer.ONE_SHOT, period = int((timerLength * 60000)), callback = lambda t: endTimer(t, timerLength*60)), timerLength)
selecting = False
timerMode()
def sortTimer(newTimer, timerLength):
global timerLengths, timersElapsed
timerLengths.update({newTimer:(timerLength * 60)})
timersElapsed.update({newTimer:0})
#This runs a function every second that increases the variable tracking seconds elapsed
Timer(mode=Timer.PERIODIC, period = 1000, callback = lambda t: updateTimerTimeElapsed(newTimer))
if len(activeTimers) >= 1:
foundSpot = False
lowBound = 0
highBound = len(activeTimers)
while not foundSpot:
currIndex = int((highBound-lowBound)/2) + lowBound
if timersElapsed[activeTimers[currIndex]] >= timersElapsed[newTimer]:
activeTimers.insert(currIndex, newTimer)
newTimer.secondsElapsed = 0
foundSpot = True
elif timersElapsed[thisTimer] < activeTimers[currIndex]:
highBound = currIndex - 1
elif timersElapsed[thisTimer] > activeTimers[currIndex]:
lowBound = currIndex + 1
else:
activeTimers.append(newTimer)
def updateTimerTimeElapsed(thisTimer):
global timersElapsed
timersElapsed[thisTimer] += 1
def endTimer(thisTimer, timerLength):
global timerLengths, displayingTimer
displayingTimer = False
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(str((timerLength / 60.0)) + " Minute Timer Has Ended")
#CODE VIBRATION MOTOR ALARM
runningAlarm = True
"""for i in range (5): # ultimately make this a while loop that ends when the user turns off the timer. while(runningAlarm)
for i in range(5):
hapticFeedback.value = 0.5
sleep(0.25)
hapticFeedback.value = 0
sleep(0.25)
sleep(0.5)"""
#find a way to check for this simultaneous with the alarm buzzing. Look into threading.
selecting = True
while (selecting):
if (upButton.is_pressed):
#snooze timer
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("Snoozing Timer")
sortTimer(HashableTimer(mode=Timer.ONE_SHOT, period = 300000, callback = lambda t: endTimer(t, 300)), 300)
elif (downButton.is_pressed):
#repeat timer
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("Repeating Timer")
sortTimer(HashableTimer(mode=Timer.ONE_SHOT, period = timerLength * 1000, callback = lambda t: endTimer(t, timerLength)), timerLength / 60.0)
elif (setButton.is_pressed):
#stop/turn off timer
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("Stopping Timer")
selecting = False
runningAlarm = False
timerMode()
def displayTimer(thisTimer):
global timerLengths, displayingTimer
#add a way to pause the timer while it is displayed
#length in minutes
timerLength = timerLengths[thisTimer]
lcd.clear()
timeLeftString = str(timerLength / 60.0) + " minute timer: "
lcd.putstr(timeLeftString)
#consider finding a way to format this to be 00:00
displayingTimer = True
updateTimerDisplay(thisTimer, HashableTimer(mode=Timer.ONE_SHOT, period = 1000, callback = lambda t: print()))
#could try using a periodic timer to continuously update the screen
HashableTimer(mode=Timer.PERIODIC, period = 1000, callback = lambda t: updateTimerDisplay(thisTimer, t))
#note: this will update it every second, but the seconds will start when the function does so it may not align with the actual time
def updateTimerDisplay(thisTimer, t):
global timerLengths, currTimeIndex, displayingTimer, currMode
if (not (activeTimers[currTimeIndex] == thisTimer)) or (not displayingTimer) or (not (currMode == "timer")):
t.deinit()
return
#change what is displayed to be equal to the timer's current time elapsed
#total time remaining in seconds
timerLength = timerLengths[thisTimer]
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(str(timerLength / 60.0) + " minute timer: ")
lcd.move_to(0,1)
timerRemainingSeconds = (timerLength) - (timersElapsed[thisTimer])
#hours remaining
hoursRemaining = math.floor(timerRemainingSeconds / 3600)
#minutes remaining after the hours
minutesRemaining = math.floor((timerRemainingSeconds % 3600) / 60)
#seconds remaining after the minutes
secondsRemaining = timerRemainingSeconds % 60
#consider finding a way to format this to be 00:00
if hoursRemaining > 0:
#timeLeftString = hoursRemaining + ":" + minutesRemaining + ":" + secondsRemaining
timeLeftString = "{:02}:{:02}:{:02} remaining".format(hoursRemaining, minutesRemaining, secondsRemaining)
else:
timeLeftString = "{:02}:{:02} remaining".format(minutesRemaining, secondsRemaining)
lcd.move_to(0, 1)
lcd.putstr(timeLeftString)
def selectTimer():
global currTimeIndex, displayingTimer
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
while (True):
if len(activeTimers) >= 1:
if upButton.is_pressed:
exitSelection.deinit()
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
displayingTimer = True
currTimeIndex += 1
currTimeIndex = currTimeIndex % len(activeTimers)
currentTimer = activeTimers[currTimeIndex]
displayTimer(currentTimer)
elif downButton.is_pressed:
exitSelection.deinit()
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
displayingTimer = True
currTimeIndex -= 1
currTimeIndex = currTimeIndex % len(activeTimers)
currentTimer = activeTimers[currTimeIndex]
displayTimer(currentTimer)
elif setButton.is_pressed:
exitSelection.deinit()
displayingTimer = False
setTimer()
else:
selecting = True
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Set new timer?")
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: timerMode())
while (selecting):
if setButton.is_pressed:
exitSelection.deinit()
setTimer()
#Reminder Functions:
def setReminder():
global activeReminders, reminderOptions
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Setting Reminder:")
lcd.move_to(0, 1)
currentIndex = 0
currReminderOption = reminderOptions[currentIndex]
lcd.putstr(currReminderOption)
selecting = True
while selecting:
if upButton.is_pressed:
currentIndex += 1
currReminderOption = reminderOptions[currentIndex]
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Setting Reminder:")
lcd.move_to(0, 1)
lcd.putstr(currReminderOption)
elif downButton.is_pressed:
currentIndex -= 1
currReminderOption = reminderOptions[currentIndex]
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Setting Reminder:")
lcd.move_to(0, 1)
lcd.putstr(currReminderOption)
elif setButton.is_pressed:
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("Reminder set!")
lcd.move_to(0,1)
lcd.putstr(currReminderOption)
activeReminders.append(currReminderOption)
selecting = False
reminderMode()
def displayReminder(thisReminder):
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Remember to " + thisReminder)
def selectReminder():
global currRemindIndex, activeReminders
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: reminderMode())
while (True):
if len(activeReminders) >= 1:
if upButton.is_pressed:
exitSelection.deinit()
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: reminderMode())
currRemindIndex += 1
currRemindIndex = currRemindIndex % len(activeReminders)
currentReminder = activeReminders[currRemindIndex]
displayReminder(currentReminder)
elif downButton.is_pressed:
exitSelection.deinit()
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: reminderMode())
currRemindIndex -= 1
currRemindIndex = currRemindIndex % len(activeReminders)
currentReminder = activeReminders[currRemindIndex]
displayReminder(currentReminder)
elif setButton.is_pressed:
exitSelection.deinit()
setReminder()
else:
selecting = True
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Set new Reminder?")
exitSelection = Timer(mode=Timer.ONE_SHOT, period = 5000, callback = lambda t: reminderMode())
while (selecting):
if setButton.is_pressed:
exitSelection.deinit()
setReminder()
# Main Code:
"""setModeFunction = {
"sleep" : lambda: sleepMode(),
#"clock" : lambda: clockMode(),
"timer" : lambda: timerMode(),
#"reminder" : lambda: reminderMode(),
#"calendarEvent" : lambda: calendarMode(),
#"study" : lambda: studyMode(),
#"personalGoal" : lambda: personalGoalMode()
}"""
sleepMode()
while (True):
modeButtons()
print("back")
# TO DO:
# Add new Timer
# Add new Reminder
# Add new Calendar Event
# Set Study Mode
# Update Personal Goals