Build a Personalized Smart Alarm Clock with Python
Tags: python, programming, machinelearning
Published: 2021-01-03 07:18:26

Hello readers, you often would have seen or build alarm clocks using Python language which would help you wake up or remind you of an important meeting.

Almost all of those are simple with no intelligence in them and all they do is play an alarm tune set up by you or choose a random YouTube video or song to play.

So, let’s go a level up and build something smart, something more personalized which understands you and helps you wake up in a better and faster way.

Our alarm, which we will be building in this article, will learn from past events and understand them for better performance in each next alarm. It gets better each time it is used. It keeps a track of time taken by the user to put the alarm off (the time user took to wake up) and recommends the alarm tunes which help you wake faster.

So, without further ado, let’s begin building our alarm. We will build it step by step below.

###Importing the Required Packages

The first step would be to import the required packages into our Python code to use them in building the alarm.

If they are not installed, you need to first install these using pip install method. After you are done with the installation step, proceed with importing them into the code.
```py
import datetime
import os
import time
import random
import csv
from pygame import mixer
import pandas as pd
import numpy as np
```
###Setting up the Tunes Folder
Next step would be to set up an Alarm tunes folder where the user would be storing his preferred alarm tunes.

You can choose any path for the alarm tunes, I preferred to create the folder in the same folder as the Python script. We need to create the folder only once, so we need to check whether the folder exists or not. If the folder does not exist, we will create one.
```py
# Getting the current path of the script
path = os.getcwd()
# Setting up the alarm path
alarm_path = path + '\Alarm_Tunes'
# If no directory present, create one.
if not os.path.isdir(alarm_path):
    os.makedirs(alarm_path)
```
Now, after our folder is created, we will ask users to add some alarm tunes to the folder, if and only if the folder is currently empty.
```py
# Ask user to add some alarm tunes to the folder.
while len(os.listdir(alarm_path))==0:
    print("No Alarm Tunes Present. Please add some tunes to the folder before proceeding.")
    confirm = input("Have you added songs? Press Y or N:\t")
    if(confirm=="Y"):
        print("Good! Let's continue!")
        continue
    else:
        continue
```
So, as seen above, we ask the user to add at least a single alarm tune. If there is no alarm tune, raise a warning and ask the user again.

###Creating a CSV file and defining a helper function
Now, let’s define a helper function before jumping into CSV file creation part.

This helper function helps us to calculate the difference between two Python lists. This will be used later in our program.
```py
def List_diff(list1, list2): 
    if len(list1)>=len(list2):
        return (list(set(list1) - set(list2)))
    else:
        return (list(set(list2) - set(list1)))
```
Now, since we have now written our helper function to calculate the difference between two lists, let’s move forward to create a CSV file if it is not already present.
```py
# If no csv file, create the lists with parameters as zero
if not os.path.isfile("tune_parameters.csv"):
    tune_list = os.listdir(alarm_path)
    tune_time = [60]*len(tune_list)
    tune_counter = [1]*len(tune_list)
    tune_avg = [60]*len(tune_list)
    tune_prob_rev = [1/len(tune_list)]*len(tune_list)
    tune_prob = [1/len(tune_list)]*len(tune_list)
```
So, the above code checks whether we have a CSV file present or not, if not we will create the lists as you can see above. We will save these in CSV file at end of the program.

Now, let’s explain the significance of each list present in the code. Let’s see them one by one.

**tune_list**: It stores the name of the alarm tunes as is evident from the code as it stores the list of files present in the alarm_path.

**tune_time**: It stores the sum of the amount of time a user takes to close that particular alarm i.e. the time taken by the user to wake up.

**tune_counter**: It keeps track of the number of times each alarm tune is played till now.

**tune_avg**: It finds out the average time taken by the user to wake up and close the alarm for every alarm tune.

**tune_prob_rev**: It calculates a kind of reverse probability based on the average time a user requires for each alarm tune.

**tune_prob**: It is the probability of an alarm tune to be played each time. It keeps updating itself based on previous results and is calculated using the tune_rev_prob.

One thing to note here is I have set some default values to all these lists instead of providing zero since it will affect the model negatively as the ones never played would never get a chance due to zero probability.

So, I preferred to assume that each of these has been played once and the average time is 60 seconds. Thus it makes our work easier.
Now, if the CSV file is already present, we need to load the data from the CSV file.

Also, we need to take care if there had been any changes to the Alarm tunes folder. The user might have added new tunes or removed some present tunes. So, we need to update the either add new tunes to our list or remove the ones which have been removed from the folder.

So, we use the helper function defined before to find out any differences between the list obtained by the folder and list obtained from the CSV file. And, thus we can perform the required things on our code and update the tune_prob_rev and tune_prob respectively using their respective formulas.
```py
# If csv file is present, read from csv file
else:
    tune_df = pd.read_csv("tune_parameters.csv")
    tune_list_os = os.listdir(alarm_path)
    tune_list = list(tune_df['Tunes'])
    tune_diff = List_diff(tune_list_os, tune_list)
    tune_time = list(tune_df['Delay Times'])
    tune_counter = list(tune_df['Count'])
    tune_avg = list(tune_df['Average'])
    tune_prob_rev = list(tune_df['Reverse Probability'])
    tune_prob = list(tune_df['Probability'])
    
    if len(tune_list_os)>=len(tune_list):
        for i in range(0,len(tune_diff)):
            tune_list.append(tune_diff[i])
            tune_time.append(60)
            tune_counter.append(1)
            tune_avg.append(60)
            tune_prob_rev.append(0.1)
            tune_prob.append(0.1)
            
    else:
        for i in range(0,len(tune_diff)):
            tune_diff_index = tune_list.index(tune_diff[i])
            tune_list.pop(tune_diff_index)
            tune_time.pop(tune_diff_index)
            tune_counter.pop(tune_diff_index)
            tune_avg.pop(tune_diff_index)
            tune_prob_rev.pop(tune_diff_index)
            tune_prob.pop(tune_diff_index)
    
    avg_sum = sum(tune_avg)
    
    for i in range(0,len(tune_prob_rev)):
        tune_prob_rev[i] = 1 - tune_avg[i]/avg_sum
    
    avg_prob = sum(tune_prob_rev)
    
    for i in range(0,len(tune_prob)):
        tune_prob[i] = tune_prob_rev[i]/avg_prob
```

###Setting the Alarm and Verifying the Time
Now, we need to define another helper function to check whether the time entered by the user is correct or not. So, we have defined the function verify_alarm to do so.
```py
# Verify whether time entered is correct or not.
def verify_alarm(hour,minute,seconds):
    if((hour>=0 and hour<=23) and (minute>=0 and minute<=59) and (seconds>=0 and seconds<=59)):
        return True
    else:
        return False
```
Now, we have the helper function ready. So, we need to ask the user for the alarm time. We will use a loop to ask for the alarm and we will break out once we verify the time is valid. If invalid, we will ask the user again until he enters a valid time.
```py
# Asking user to set alarm time and verifying whether true or not.
while(True):
    hour = int(input("Enter the hour in 24 Hour Format (0-23):\t"))
    minute = int(input("Enter the minutes (0-59):\t"))
    seconds = int(input("Enter the seconds (0-59):\t"))
    if verify_alarm(hour,minute,seconds):
        break
    else:
        print("Error: Wrong Time Entered! Please enter again!")
```
Now, after taking input from the user, we will find out the current time and will convert both of these times to seconds and will find out the difference between the times. If the difference is negative, it will mean the alarm is for the next day.

We will then make the python code sleep for that amount of seconds so that alarm rings at the required time only.
```py
# Converting the alarm time to seconds
alarm_sec = hour*3600 + minute*60 + seconds
# Getting current time and converting it to seconds
curr_time = datetime.datetime.now()
curr_sec = curr_time.hour*3600 + curr_time.minute*60 + curr_time.second
# Calculating the number of seconds left for alarm
time_diff = alarm_sec - curr_sec
#If time difference is negative, it means the alarm is for next day.
if time_diff < 0:
    time_diff += 86400
# Displaying the time left for alarm
print("Time left for alarm is %s" % datetime.timedelta(seconds=time_diff))
# Sleep until the time at which alarm rings
time.sleep(time_diff)
```

###Ringing the Alarm
Now, we will ring our alarm and we will need to randomly choose the alarm tune based on the probability list. To play the alarm tune, we will be using the pygame. mixer.music library. We will loop the alarm tune infinitely until the user stops it.
```py
print("Alarm time! Wake up! Wake up!")
# Choose a tune based on probability
tune_choice_np = np.random.choice(tune_list, 1, tune_prob)
tune_choice = tune_choice_np[0]
# Getting the index of chosen tune in list
tune_index = tune_list.index(tune_choice)
# Play the alarm tune
mixer.init()
mixer.music.load(alarm_path+"/"+tune_choice)
# Setting loops=-1 to ensure that alarm only stops when user stops it!
mixer.music.play(loops=-1)
# Asking user to stop the alarm
input("Press ENTER to stop alarm")
mixer.music.stop()
```

###Calculations and Updatations of Lists
Now, we will update the values of the lists based on the time needed by the user to stop the alarm.

We will find the time difference between the alarm and the current time of stopping the alarm. We will convert it to seconds and then update accordingly.
```py
# Finding the time of stopping the alarm
time_stop = datetime.datetime.now()
stop_sec = time_stop.hour*3600 + time_stop.minute*60 + time_stop.second
# Calculating the time delay
time_delay = stop_sec - alarm_sec
# Updating the values
tune_time[tune_index] += time_delay
tune_counter[tune_index] += 1
tune_avg[tune_index] = tune_time[tune_index] / tune_counter[tune_index]
new_avg_sum = sum(tune_avg)
for i in range(0,len(tune_list)):
    tune_prob_rev[i] = 1 - tune_avg[i] / new_avg_sum
    
new_avg_prob = sum(tune_prob_rev)
    
for i in range(0,len(tune_list)):
    tune_prob[i] = tune_prob_rev[i] / new_avg_prob
```

###Merging the Lists and saving as CSV file
Now, we will merge all the lists in a single multi-dimensional list and then we will convert it to a pandas data frame and then we will save it as a CSV file.

```py
#Create the merged list of all six quantities
tune_rec = [[[[[[]]]]]]
for i in range (0,len(tune_list)):
    temp=[]
    temp.append(tune_list[i])
    temp.append(tune_time[i])
    temp.append(tune_counter[i])
    temp.append(tune_avg[i])
    temp.append(tune_prob_rev[i])
    temp.append(tune_prob[i])
    tune_rec.append(temp)
tune_rec.pop(0)
#Convert merged list to a pandas dataframe
df = pd.DataFrame(tune_rec, columns=['Tunes','Delay Times','Count','Average','Reverse Probability','Probability'],dtype=float)
#Save the dataframe as a csv (if already present, will overwrite the previous one)
df.to_csv('tune_parameters.csv',index=False)
```

We have finally finished building our smart alarm. For complete code, visit my [Github Repository](https://github.com/shubham1710/Personalised-Alarm-using-Python) and do contribute to the repository if you have some enhancements or new ideas.

Hope you find this article insightful. Try building your version and do share your thoughts in the comments. Thanks for reading!

*Originally published at [Towards Data Science](https://towardsdatascience.com/build-a-personalized-smart-alarm-with-python-ceae3682a761)*