Alarm Clock With Sound and Vibration

by alexbergsland in Circuits > Raspberry Pi

269 Views, 0 Favorites, 0 Comments

Alarm Clock With Sound and Vibration

IMG_20230920_204253.jpg
IMG_20230920_204309.jpg

I've made an alarm clock from things I had lying around, most of it anyway.

When we had kids I got an alarm clock with a vibrating puck to put under the mattress, that way I would not wake up the whole family when going to work early. That alarm clock had the same problem as any other alarm clock, you need to set it every day before you want to wake up.

I later replaced that with a Garmin Forerunner, it had recurring alarms, so I could set it to vibrate on my wrist every work day.

Recently I have been wanting to get a wrist watch with automatic winding, but first I needed to solve the waking up. So I built an alarm clock.

Features:

  • Recurring alarm every Monday to Friday at 05:40 (by cron job)
  • If there is an entry in my alarm clock calendar (Google Calendar) it counts as a day off and the alarm won't go off
  • If the calendar entry is a time, like 8:35, the alarm will go off on that time instead
  • A dot on the display will show if the alarm is due to set the next day, or today if it is before waking time
  • Ability to choose either sound (mp3) or vibration
  • Alarm stops after 10 seconds (can be changed in code) if no button is pressed
  • Set sound level
  • Set display brightness
  • Set vibration power

As always you start simple and add features as you go, it did not have the ability to adjust the force of the vibration at first and it felt like the whole bed was shaking. I do wake up very easily so that was definitely overkill, I ordered a motor speed controller and now it vibrates just so I wake up but the woman next to me does not.

This project uses three python scripts.

alarm.py - Takes care of the recurring alarm, set by a cron job

clock.py - Shows time, handles changing of volume and brightness, handles other alarms than the above

get_days_off_from_Google.py - Does what it says, runs as a cron job every hour


This project does not include setting up a Raspberry Pi or installing necessary files for the display, you will need to look in the documentation for your display for that. You will also need vlc and any other import the scripts call for.

Supplies

You can do this in any way you want but these are the things I used

If you want you can just connect everything in the box but I used connectors that I mounted on the box.

The puck had a 3.5mm plug so I got a jack for that. The power source was for guitar pedals so it had a normal barrel plug and I had jacks for that.

I just drilled a hole for the Raspberry plug.

Connect the Things

alarm clock.png
IMG_20230915_171108.jpg
IMG_20230924_120443.jpg

Connect all the things, I made a drawing of how I have it connected but it will probably not be exactly the same with your things.

The Four Letter Phat I use connects through I2C which only uses the four top most pins on the left rail on my drawing of the Raspberry Pi. The display you use might work differently.

I used two angeled headers soldered together to get the display stand on its own, I then soldered some wires to the header. You can also use gpio cabels or ribbon cable.

Python

Some comments in here will be in Swedish, deal with it ;)

Some variables are kept in files for persistence. I have made checks for the files but maybe not all. If you get an error running the scripts check for file errors.

The files are:

  • calendarfile_tomorrow (containing Working or DayOff)
  • calendarfile_today (containing Working or DayOff)
  • vibration_or_audio (containing vibration or audio)
  • volume (10 - 100
  • brightness (1-15, might have to use another scale on your display)
  • runonce (contains done or is empty)


And then there are the tree scripts.

alarm.py


#!/usr/bin/env python3


from gpiozero import Button
from gpiozero import OutputDevice
import time
import re
import vlc


btn = Button(17)
alarm = OutputDevice(27)


max_time = 10 # seconds


with open('/home/pi/volume', 'r') as file:
thevolume = int(file.read().rstrip())


player = vlc.MediaPlayer("/home/pi/alarm.mp3")
player.audio_set_volume(thevolume)
print(str(thevolume))


boolStopAlarm = False


with open('/home/pi/calendarfile_today', 'r') as file:
calendarfile = file.read().rstrip()
print(calendarfile)


start_time = time.time() # Register what time the alarm starts


def vibration_or_audio():
with open('/home/pi/vibration_or_audio', 'r') as file:
thefile = file.read().rstrip()
return thefile


def stopAlarm():
print('Button was pressed')
boolStopAlarm == True
if (vibration_or_audio() == "vibration"):
alarm.off()
else:
player.stop()


def playAudio():
player.play()


isitatime = re.search(r"^\d+:\d+$", calendarfile) # Check if it is a time like 05:38
if isitatime is not None:
print("Summary is a time: " + isitatime.string)
# Check whether to use vibration or audio
if (vibration_or_audio() == "vibration"):
alarm.on()
else:
playAudio()
else:
if (calendarfile == 'Working'):
print('Alarm!')
# Check whether to use vibration or audio
if (vibration_or_audio() == "vibration"):
alarm.on()
else:
playAudio()
else:
print('Inget alarm')


# Runs while alarm is on OR until max_time is reached
while (not boolStopAlarm) and ((time.time() - start_time) < max_time):
#print('Alarm!')
btn.when_pressed = stopAlarm
time.sleep(0.1)



clock.py

#!/usr/bin/env python3


import time
import fourletterphat # as flp
import datetime as dt
from gpiozero import Button
import os.path
import re
import subprocess
import vlc


btnright = Button(5, hold_time=3, bounce_time = 0.1)
btnleft = Button(6, hold_time=3, bounce_time = 0.1)

if (os.path.exists('/home/pi/volume')):
with open('/home/pi/volume', 'r') as file:
thevolume = int(file.read().rstrip())
else:
with open('/home/pi/volume', 'w') as file:
file.write("100")


if (os.path.exists('/home/pi/brightness')):
with open('/home/pi/brightness', 'r') as file:
brightnessfile = int(file.read().rstrip())
else:
brightnessfile = 1
with open('/home/pi/brightness', 'w') as file:
file.write(brightnessfile)


def isNowInTimePeriod(startTime, endTime, nowTime):
if startTime < endTime:
return nowTime >= startTime and nowTime <= endTime
else:
#Over midnight:
return nowTime >= startTime or nowTime <= endTime


def btnRight():
#print('Button Right was pressed')
global brightnessfile
with open('/home/pi/brightness', 'r') as file:
brightnessfile = int(file.read().rstrip())
brightnessfile = brightnessfile + 1
if (brightnessfile > 15):
brightnessfile = 0
with open('/home/pi/brightness', 'w') as file:
file.write(str(brightnessfile))
print('Brightness is now: ' + str(brightnessfile))
fourletterphat.clear()
fourletterphat.set_brightness(brightnessfile)
fourletterphat.print_number_str(brightnessfile)
fourletterphat.show()


def btnLeft():
print('Button Left was pressed')
media_player = vlc.MediaPlayer()
media = vlc.Media("/home/pi/alarm.mp3")
media_player.set_media(media)
with open('/home/pi/volume', 'r') as file:
thevolume = int(file.read().rstrip())
if thevolume < 100:
thevolume += 10
with open('/home/pi/volume', 'w') as file:
file.write(str(thevolume))
elif thevolume == 100:
thevolume = 10
with open('/home/pi/volume', 'w') as file:
file.write(str(thevolume))
fourletterphat.clear()
fourletterphat.print_number_str(thevolume)
fourletterphat.show()
# thevolume är redan int men konstigt nog funkar set_volume bara med int(thevolume)
media_player.audio_set_volume(int(thevolume))
media_player.play()
time.sleep(2)
media_player.stop()


def check_calendar():
calendarfile = "Nothing"


with open('/home/pi/calendarfile_today', 'r') as file:
calendarfile = file.read().rstrip()


isitatime = re.search(r"^\d+:\d+$", calendarfile) # Check if summary is a time like 05:38
if isitatime is not None:
print("Summary is a time: " + isitatime.string)
calendarfile = isitatime.string

else:
#Kväll, kolla om det är nåt imorgon
if (isNowInTimePeriod(dt.time(5,41), dt.time(00,00), dt.datetime.now().time())):
with open('/home/pi/calendarfile_tomorrow', 'r') as file:
calendarfile = file.read().rstrip()


#Morgon, kolla om det är nåt idag
elif (isNowInTimePeriod(dt.time(0,1), dt.time(5,40), dt.datetime.now().time())):
calendarfile = calendarfile


else:
print("inget")


return calendarfile


while True:
fourletterphat.clear()
str_time = time.strftime("%H%M")
fourletterphat.print_number_str(str_time)
fourletterphat.set_brightness(brightnessfile)
#fourletterphat.set_flipped()


# Show dot if the alarm is set
dtnow = dt.datetime.now()
what_to_do = check_calendar()
try:
time.strptime(what_to_do, '%H:%M')
except:
# Innehållet i calendarfile_today är ingen tid
###print('False')
if (dt.datetime.now().isoweekday() < 5):
#print('Vardag - mån-tors')
#print(check_calendar())
if (what_to_do == 'Working'):
fourletterphat.set_decimal(1, 1)
#print('visa prick')
else:
fourletterphat.set_decimal(1, 0)
#print('göm prick')
elif (dt.datetime.now().isoweekday() == 5):
#print('Vardag - fredag')
#Evening, Day Off tomorrow
if (isNowInTimePeriod(dt.time(5,41), dt.time(00,00), dt.datetime.now().time())):
fourletterphat.set_decimal(1, 0)
#print("")
#Morning, Working
elif (isNowInTimePeriod(dt.time(0,1), dt.time(5,40), dt.datetime.now().time())):
fourletterphat.set_decimal(1, 1)
#print("")
else:
print("inget")


elif (dt.datetime.now().isoweekday() == 6):
#print('Vardag - lördag')
fourletterphat.set_decimal(1, 0)
#print('göm prick')
elif (dt.datetime.now().isoweekday() == 7):
#print('Vardag - söndag')
#Evening, Working tomorrow
if (isNowInTimePeriod(dt.time(5,41), dt.time(00,00), dt.datetime.now().time())):
fourletterphat.set_decimal(1, 1)
#print("")
#Morning, Day Off
elif (isNowInTimePeriod(dt.time(0,1), dt.time(5,40), dt.datetime.now().time())):
fourletterphat.set_decimal(1, 0)
#print("")
else:
print("inget")
else:
print('Inget')


else:
# Innehållet i calendarfile_today är en tid
###print('True')
# Kolla om tiden redan varit
settime = dt.datetime.strptime(what_to_do, '%H:%M').time() # tiden från Google Calendar
settime = settime.replace(second=0, microsecond=0) # Gör om till fast klockslag


timenow = dt.datetime.now().time() # Just nu
timenow = timenow.replace(second=0, microsecond=0) # Gör om till fast klockslag


if settime > timenow:
print('Tiden har inte varit än: ' + str(settime) + ' > ' + str(timenow))
fourletterphat.set_decimal(3, 1) # Visa en prick på position 3
with open('/home/pi/runonce', 'r') as file:
runonce = file.read().rstrip()
if runonce != "":
with open('/home/pi/runonce', 'w') as file:
file.write('') # Rensa filen
elif settime == timenow:
print('Alarm!')
with open('/home/pi/runonce', 'r') as file:
runonce = file.read().rstrip()
if runonce == "":
subprocess.run(['python', 'alarm.py']) # Kör alarm.py i en annan tråd
with open('/home/pi/runonce', 'w') as file:
file.write('done') # Skriv till filen så att alarmen bara körs en gång
else:
print('Tiden har redan varit: ' + str(settime) + ' < ' + str(timenow))

btnright.when_pressed = btnRight
btnleft.when_pressed = btnLeft
fourletterphat.show()
time.sleep(1) # Vila en sekund



get_days_off_from_Google.py


You will need to get the ID of the calendar you want to use, I made a new one, the ID will look something like somethinglong@group.calendar.google.com paste that into mycalendar = 'your-calendar-id'.

#!/usr/bin/env python3


#import datetime
from datetime import datetime, timezone, timedelta
import os.path


from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
import re


# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']

def main():
"""Shows basic usage of the Google Calendar API.
Prints the start and name of the next 10 events on the user's calendar.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES,
redirect_uri='urn:ietf:wg:oauth:2.0:oob')


auth_uri, _ = flow.authorization_url()
print(f'Please visit {auth_uri} on your local computer')


# The user will get an authorization code. This code is used to get the
# access token.
code = input('Enter the authorization code: ')
flow.fetch_token(code=code)


creds = flow.credentials
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())


try:
service = build('calendar', 'v3', credentials=creds)


# Call the Calendar API
today = datetime.today();
starttoday = datetime(today.year, today.month, today.day, 00, 00).astimezone().isoformat()
tomorrow = today + timedelta(days=1)
starttomorrow = datetime(tomorrow.year, tomorrow.month, tomorrow.day, 00, 00).astimezone().isoformat()
dayaftertomorrow = today + timedelta(days=2)
startdayaftertomorrow = datetime(dayaftertomorrow.year, dayaftertomorrow.month, dayaftertomorrow.day, 00, 00).astimezone().isoformat()


mycalendar = 'your-calendar-id'
events_result_today = service.events().list(calendarId=mycalendar, timeMin=starttoday, timeMax=starttomorrow, singleEvents=True, orderBy='startTime').execute()
events_result_tomorrow = service.events().list(calendarId=mycalendar, timeMin=starttomorrow, timeMax=startdayaftertomorrow, singleEvents=True, orderBy='startTime').execute()
events_today = events_result_today.get('items', [])
events_tomorrow = events_result_tomorrow.get('items', [])


if not events_today:
print('No events today.')
f = open("/home/pi/calendarfile_today", "w")
f.write("Working")
f.close


else:


# Prints the start and name of the next 10 events


print('Getting todays events')
for event in events_today:
eventdate = event['start'].get('dateTime', event['start'].get('date'))
#print (start)
print(eventdate, event['summary'])
isitatime = re.search(r"^\d+:\d+$", event['summary']) # Check if summary is a time like 05:38


if isitatime is not None:
print("Summary is a time: " + isitatime.string)
summary = isitatime.string
else:
print("Summary is not a time")
summary = "DayOff"
with open('/home/pi/calendarfile_today', 'w') as file:
file.write(summary)


if not events_tomorrow:
print('No events tomorrow.')
f = open("/home/pi/calendarfile_tomorrow", "w")
f.write("Working")
f.close
else:
print('Getting tomorrows events')
for event in events_tomorrow:
eventdate = event['start'].get('dateTime', event['start'].get('date'))


print(eventdate, event['summary'])
f = open("/home/pi/calendarfile_tomorrow", "w")
f.write("DayOff")
f.close

except HttpError as error:
print('An error occurred: %s' % error)

if __name__ == '__main__':
main()




crontab -e

# Start clock at boot
@reboot sleep 60 && /usr/bin/python /home/pi/clock.py

# At minute one every hour
1 * * * * /usr/bin/python /home/pi/get_days_off_from_Google.py

# At 05:40 on every day-of-week from Monday through Friday.
40 5 * * 1-5 /usr/bin/python /home/pi/alarm.py