The software for the dawn clock is written in Python. And no, I’m not going to upload it to GitHub, or anywhere else for that matter. There are two reasons for this: first, it needs work still; and second, it would take too much explaining, not least because it uses loads of my hand-rolled libraries and classes for the Raspberry Pi.
So here I’m just going to discuss a few, pertinent details.
The ambient sound (bird song) and music are played using mpg123. I stuck with this solution, even though I would later use mplayer for other purposes, because it worked and I’d already written a class using it.
The class uses the following line to start playing an MP3 file:
self.subproc = subprocess.Popen([’/usr/bin/mpg123’, ’-q’, self.pathfile])
The string in self.pathfile includes the full path and filename of the audio file to be played. By assigning the object created with subprocess to a class variable, it’s possible to stop the audio playing with:
It’s also possible to test whether the song is still playing with the following function:
playing = False
if self.subproc.returncode == None:
# subprocess hasn’t terminated yet
playing = True
The volume of the output is set with amixer. From the MP3 class this is set with:
setAudio = subprocess.Popen([‘amixer’, ’-q’, ‘sset’, ‘PCM’, str(self.volume) + ’%’])
where self.volume is an object property – an integer between 0 and 100. The same method is used in the class I wrote to handle streaming radio. This class is always instantiated with the full URL for the stream and is played using the call:
self.subproc = subprocess.Popen([’/usr/bin/mplayer’, ’-nolirc’, ’-really-quiet’, self.url])
Like the MP3 class, playing can be stopped using the terminate() method.
Most of the settings for the dawn clock are configured via its web site. The settings are written to a simple config file which is accessed each time the Python script starts up. The web server has read/write access to this file and so I use simple HTML/PHP to manage the configuration. Here’s a screenshot from the dawn clock’s web server.
The Python temperature and pressure intervals refer to how often the readings are taken (once a minute for temps, every 10 mins for pressure, with the settings above) and how often they are logged to the MySQL database. All records in the database older than the data retention period are periodically deleted.
These records are accessed by the dawn clock’s web server to produce graphs on its home page.
The panel at the bottom of that screenshot is a PHP module written out by the Python script once an hour. You’ll see it includes details of the ‘alarm song’ that was played that morning. I often manage to sleep through this, but then – apparently inexplicably – have some music stuck in my head all day. This usually helps explain where that came from.
Times & Dates
The other thing worth mentioning is handling of times and dates. When I wrote the Arduino C code for the previous dawn clock, this caused me all kinds of headaches. Given that I was planning to implement a lot more functionality with the Mk.II clock, I quite expected the handling of times and dates to be the hardest part.
For example, there are many instances in the code where I need to know whether the current time is before or after a designated time (such as the alarm time). The thing is, the alarm time is the same every day, so any given time is always both before and after. Confused? Let’s say the alarm time is 7:15 and it’s now 11:30. The current time is after today’s alarm but before tomorrow’s. There are ways of getting around this, but it gets convoluted, and the example I gave was one of the simpler ones.
All these problems went away in an instant when I discovered Python’s datetime library. For example:
now = datetime.datetime.today()
That gives you an object with the current time and date. The latter is important.
Let’s set the alarm time to 7:15 today:
alarmTime = datetime.datetime(now.year, now.month, now.day, 7, 15, 0)
(This is a kind of paraphrase of how I do it in the code – simplified for clarity. For example, the hour and minute are pulled from the config file in the real code.)
That’s set the alarm time to 7:15 today, but what if, when this code is run, it’s already later than 7:15? That would mean the alarm had been set in the past. No problem:
if alarmTime < now:
# this is being set in the past
alarmTime = alarmTime + datetime.timedelta(days=1)
That’s now set the alarm to tomorrow. I could give plenty more examples, but the important thing is that the use of a datetime object, and the ability to modify it with methods like timedelta() makes working with dates and times astonishingly easy.
Finally, a note on the LCD panel.
The details are:
Line 1: Current time and date.
- Current temperature. If the temp is higher or lower than the previous reading, this is followed by an up or down arrow.
- Lowest/Highest temps in past 24 hours.
- Current barometric pressure. The arrow preceding the number shows that the pressure has fallen since the previous reading. The bar after the reading shows that this is considered ‘low’ pressure. Normal and high pressures are shown by ascending bars.
- Current settings (in %) for the volume settings for music and ambient sounds (this is temporary: I’ll probably replace this with something more useful).
- PIR setting. The appearance of ‘pir’ means that PIR mode has been selected. In this case, at any time after the alarm lights have gone off but before a cut-off time (set to 22:30 in the settings screenshot earlier), if the ambient light level drops below a certain point and the PIR sensor detects movement, the LEDs are switched on. The asterisk in this image shows that motion has been detected.
- Alarm time set.
Line 4: The last line is reserved for messages. In this case, it’s showing a ‘wake-up’ message, set in the configuration screen shown earlier. When the alarm time rolls around, this line shows the name of the song being played.
That’s it for now. My next post will sum things up…
[UPDATE 21/01/2012]: I changed my mind about using mpg123 for playing MP3s. The reason has to do with how I stop playing them. In the classes I created to play MP3s and radio streams, the stop() method used the Python subprocess.terminate() method to stop playing. However, this had the side-effect of leaving zombie processes unless I made sure to then delete the objects from the main program. That was difficult in some cicumstances and a bit clunky. I know that zombie processes consume almost nothing in the way of resources, but it’s inelegant.
It then occurred to me that mplayer allows you to send it keyboard commands. Actually, the commands are simply provided via STDIN, so that means they can be sent programmatically.
So, in the base class that is used by both the MP3 and RadioStream classes, the audio is started with:
self.subproc = subprocess.Popen([’/usr/bin/mplayer’, ’-msglevel’, ‘all=-1’, ’-really-quiet’, self.source], stdin=subprocess.PIPE)
(where self.source has previously been defined as either a full path and filename for an MP3 file or a URL for a radio stream).
And stopping the audio from playing uses:
This sends the single character ‘q’ to the STDIN of the mplayer subprocess. This causes mplayer to stop playing and exit. The b in front defines it as a byte array – a string won’t work. So now the mplayer process exits cleanly with no zombie processes left behind.