Monday, January 30, 2017

Weekend Project - Raspberry Pi alarm clock

Background:

I have a cool app on my phone called Alarm Calendar Plus that I use as my alarm clock as well as a reminder for the kids to head out to the bus.
It has a few issues - no regular expression matching and no way to do boolean expressions ((A and not B) or C).  Right now I have three rules to wake me at 7 if any of my kids have school that day, one of those rules wins every morning and the others are reported as "missed" and I have to dismiss them manually. I also have 3 rules relating to going out to the bus 2 of them are duplicates. I had an alarm for "art" that was picking up "party" as a match. When I am on travel I have to manually disable all 6 of the rules so that I dont have my alarm going off at odd times.

We have a no devices in the bedroom rule for my kids right now. They also have been known to forget to turn alarms off on weekends. I wanted them to be able to listen to music and also have alarms only on days that they needed alarms on.

I decided that I could solve many of these requirements with a raspberry pi hooked to speakers.

Requirements:

  • It needs to use google calendar to decide if it should set the alarm or not - the entire family is set up using individual calendars that are all shared with each other.
  • It needs to be easy to deal with.
  • It would be cool if it would play music from online streaming. Most of the family has a Pandora account but it would be nifty to also do google play/amazon streaming/etc.

Gather the functionality:

Scripting language:

I decided to use python because it has become popular and I normally use perl or javascript so forcing myself to use a new scripting language to implement something I wanted is really the only way to learn a new language.

BTW I dont like indent based languages - my brain has been trained for curly braces....

Pandora:

First lets get pandora working:
I found this link that looked promising:
http://www.instructables.com/id/Pandoras-Box-An-Internet-Radio-player-made-with

This ends up using pianobar for the Pandora interface.
https://6xq.net/pianobar/

The use of a fifo to have an external program send commands is the part that I will pull from the instructable.

To run pianobar in the background do this:
$ nohup pianobar < /dev/null &

Interface:

The instructable had buttons that use the pi's gpio - I was looking for big buttons that could be used to turn an alarm on and off and pricing them out when I realized that I could just attach a mouse to the thing and use the buttons on the mouse (I know that is not as cool as hardwired buttons but it meant that I could quickly get something working).

Basic mouse:
https://smile.amazon.com/gp/product/B005EJH6RW/ref=oh_aui_detailpage_o02_s00?ie=UTF8&psc=1

More buttons:
https://smile.amazon.com/gp/product/B01BC4TXXC/ref=crt_ewc_title_dp_1?ie=UTF8&psc=1

While I was looking at those I also saw this numeric keypad which would give more options (perhaps to many?)
https://smile.amazon.com/gp/product/B01E8TTWZ2/ref=oh_aui_detailpage_o02_s00?ie=UTF8&psc=1

Reading USB events:

After some googling I found evdev to read the events from the mouse:
https://python-evdev.readthedocs.io/en/latest/
and some sample code here:
https://www.raspberrypi.org/forums/viewtopic.php?p=339731

BTW: To make it read from all the input devices so that I did not have to figure out which one was the mouse I modified the example code to create an array of all the /dev/input/event[X] devices.

I changed:
dev = InputDevice('/dev/input/event0')

while True:
  r,w,x = select([dev], [], [])
  for event in dev.read():
To this:
files = []
i=0
while os.path.exists('/dev/input/event' + str(i)):
  files.append(InputDevice('/dev/input/event' + str(i)))
  i += 1

while True:
  r,w,x = select(files, [], [])
  for dev in r:
    for event in dev.read():

I then did a quick test writing commands to a fifo that was being read by pianobar (text based Pandora app)
I need to think on the most intuitive interface - right now I have right mouse pause/play and scroll wheel volume.

Reading google calendar:

Google makes it easy to get a hello world type app up:
https://developers.google.com/google-apps/calendar/quickstart/python

After a successful test I modified the code to only show the next week and to show the data from all of the calendars that I had.

    now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
    next = datetime.datetime.utcnow() + datetime.timedelta(weeks=1)
    nextstr = next.isoformat() + 'Z'

    page_token = None
    while True:
      calendar_list = service.calendarList().list(pageToken=page_token).execute()
      for calendar_list_entry in calendar_list['items']:
        eventsResult = service.events().list(
             calendarId=calendar_list_entry['id'], timeMin=now, maxResults=10, singleEvents=True,
             timeMax=nextstr, orderBy='startTime').execute()
        events = eventsResult.get('items', [])

        if not events:
            print('No upcoming events found for ' + calendar_list_entry['summary'])
        for event in events:
            start = event['start'].get('dateTime', event['start'].get('date'))
            end   = event['end'].get('dateTime', event['end'].get('date'))
            print(calendar_list_entry['summary'], start, end, event['summary'])

      page_token = calendar_list.get('nextPageToken')
      if not page_token:
        break

Storing user preferences and data:

Feeling confident after reading data from the calendar I decided that user prefs and data could be stored on google as well.
https://developers.google.com/sheets/api/quickstart/python

It turns out that Google Docs is not as mature as Google Sheets for api's so I will have to figure out a schema that uses a spreadsheet (I was going to just store a JSON string in a doc).

I modified their quickstart after getting it to work to show each row as a comma delimited string

    rangeName = 'Sheet1!A1:E'
    result = service.spreadsheets().values().get(
        spreadsheetId=spreadsheetId, range=rangeName).execute()
    values = result.get('values', [])

    if not values:
        print('No data found.')
    else:
        for row in values:
             print(', '.join(row))

Text to speech:

I found this site and used the google method to implement a quick text to speech interface:
http://elinux.org/RPi_Text_to_Speech_(Speech_Synthesis)

Alarm sounds:

Grabbing the mplayer command from the google text to speech I was able to get the command to play an alarm as an mp3
/usr/bin/mplayer --really-quiet -ao alsa -noconsolecontrols alarm.mp3

Find your favorite mp3 to use as an alarm. I found some cool ones here

Conclusion:

OK, I did not finish the implementation, but I think I have all the unknowns figured out.
If/when I have it all working I can post the full implementation.