Monday, February 13, 2017

has a/is a/multiple inheritance/interface classes

I want the main class for my app to be basically one big event handler ie
class MonkeyMouse
  def onMouseUp(self)
    ...
  def onWheel(self, direction)
    ...
  def onCalendarUpdate(self)
    ...
  def onAlarm(self)
    ...
Right now I am doing that by doing multiple inheritance (using the "is a" solution)
class MonkeyMouse(timerAlarm.TimerAlarm, mouseEvent.MouseEvent, google_cal.GoogleCal, pianobar.PianoBar)
  def onMouseUp(self)
    ...
  def onWheel(self, direction)
    ...
  def onCalendarUpdate(self)
    ...
  def onAlarm(self)
    ...
The problem comes up when I create a method in 2 classes with the same name ie both the mouseevent and the timerevent classes both spawn threads calling a method that I initially called "worker". Obviously the name conflict caused issues.  Since it is me writing all the code I can obviously get around this issue, but also since it is me I have a high likelihood of causing conflicts for methods that do the same thing.

Compounding the issue is the desire to not spend huge amounts of time creating the perfect solution for a fun little app...

Another way to get around this would be to have the mouseevent class take a mouseevent handler class and have my main app inherit from each handler class, this would mean that the onXXX methods are the only place that I need to avoid conflict. This is kind of a pain in that I need to put try: / except around every call to make sure the method is defined.

The third way (using a "has a" solution) involves registering a callback method for each item that we are interested in - this still involves putting try/except around calls like self.mouseUpCallback() - however if we want to be lazy (cough) we can set all of our callbacks to be to null functions so then we just have to register for the callbacks that we care about and we can assume that the calls will work ie
class MouseEvent
   def __init__(self):
      super(MouseEvent, self).__init__()
      self.mouseUpCallback = self.nullFunc 
  def worker(self):
      ...
      self.mouseUpCallback()
      ....
  def nullFunc(self)
    pass 
Then all we need to do is write bind methods - I like this as it is the most elegant.

The only issue then is that we are not on a single thread that handles events "onMouseUp" could be called concurrently with "onAlarm" meaning they could have resource contention so we would need to put thread protection into our code. If we want to mitigate this (and avoid it in the onXxx code) we could instead create a event handler class and have the main thread run it in a loop. The other threads (mouse, calendar, alarm etc) would register their event and then signal the handler thread to wake it up using a condition. This would mean that the event handler class would be the only place that had to have mutex/thread awareness and the onXxx would be run synchronously avoiding many asynchronous headaches. I may have talked myself into doing it that way even though it is just me writing this app as it will let the app be more reusable...

Friday, February 10, 2017

Notes on pi alarm clock

I have been busy at work and have not done much with the project the past week or 2.

So far I have just created 3 classes to make the proof of concept code into reusable modules.
mouseevent.py, pianobar.py, google_cal.py

The mouseevent class starts a thread inside it and thus creates an event loop to trigger methods like onMouseUp when you inherit from mouseevent.

The pianobar class takes the work from http://www.instructables.com/id/Pandoras-Box-An-Internet-Radio-player-made-with and turns it into a class with methods like start, volUp, volDown, pause, etc

The google_cal.py allows you to override a method called handleEvent(calname, event) which is passed each event as the weeks calendar is iterated over from google. This class may turn into a data structure that calles an onChange method.

The code is being hosted in a private repository on github right now, I suppose I could make it public if there is ever any interest.

Some random notes on installing what I am calling monkey clock (not sure about my apparent fascination with monkeys, but...)
From a fresh Raspbian system these are the commands to get the system setup to play audio and read mouse events.

First we will do the typical refresh of install.
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
Now lets install the packages that I seem to auto install (I probably dont need these but...)
  • vim for editing files in a civilized manner
  • build-essential g++
  • autoconf - autoconf for configure files

sudo apt-get install vim build-essential autoconf
These packages are needed for various things for an alarm clock
  • ntpdate to keep the date and time correct
  • pianobar to play pandora
  • python-dev you will need to install evdev
  • mplayer to play mp3 files.
sudo apt-get install python-dev ntpdate pianobar mplayer
Install google api and evdev (for mouse events)
sudo pip install --upgrade google-api-python-client
sudo pip install evdev
If you are plugged in to an hdmi port and want to hear output from speakers you will need to tell it to send the output that way
#if you have your hdmi setup as the output for audio set it to the headphone jack
amixer cset numid=3 1
You are going to want to have the device be headless so lets enable ssh so that we can log in remotely.
https://www.raspberrypi.org/documentation/remote-access/ssh/
Note that we are going to want to disable password login in /etc/ssh/sshd_config and create keys to login https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys--2

In the stock /etc/ssh/sshd_config you want to set the following line to turn off ssh passwords "PasswordAuthentication no"

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.