Act I - The gift
Where I live, often boxes with 'Zu verschenken' (To give away) are placed on the street and at one point, my girlfriend found a typical wooden clock with LED's behind the front over there.
It was working, but she didn't want it in the end and I was interested to place it at my nightstand (so as not to need my phone to know the time at night, but also to distract myself in the morning before getting up).
When it would be running from batteries, the clock had a function to disable the display at night. You could wake it by tapping on the housing, which would trigger an internal piezo contact microphone. Sometimes this even worked.
The original power supply was also available, so I didn't want any batteries, unfortunately, the disable-at-night-function didn't work when powered by the supply. Taking it apart, rewiring the power supply from the supply input to the battery input on the PCB worked, but when trying it out at night, it would have reset itself in the morning and had to reconnect to a time server, so all in all: not great.
In the end, it ended in the parts bin.
Act II - The mornings
My girlfriend is not great at getting up in the morning. To put it mildly. She would therefore set up to 10 alarms, with a 5-10 minute interval, up to an hour before she really needed to get up. Furthermore, she has to get up a bit earlier than I do, but it would also wake me up, let me fall asleep again, wake me up again, and so on. I was not a fan.
Apart from that, she always wanted a radio alarm clock, with the one from Groundhog Day explicitly mentioned several times. But it should also play Deep Oldies, a radio station not playing any ads, but with her kind of music. 'Unfortunately', that meant standard FM alarm clocks were no option. Now, there also are internet radio alarm clocks these days, but I haven't found Deep Oldies in the usual radio stations yet or on for example Streamurl, so my hopes of one of those being able to play Deep Oldies were slim. (On the other hand, I didn't search that well, because I might've ended up finding something, which would mean this project was not necessary...)
Act III - The research
Now, I knew a flip type clock as in the movie was not really feasible at this point. I could have probably managed it, but not given my available time and the additional research/sourcing I had to do for that. But I did still have the 'wooden' clock. So I took that apart again (did I already mention that they spent a lot of glue to close it up?) and started measuring on the cable between the display PCB and the main PCB.
It featured a 13 pin, 2mm pitch band cable, nicely numbered on both side. As I hoped, it was just a 4 number, 7 seven segment display with a common anode (that's a common VCC, I always seem to forget the naming convention of anode/cathode), but with 2 dp connections as in the table below. See one of the images for the display when everything is turned on.
Pin | Connection | Original PCB connection |
---|---|---|
1 | CA 3 | Output (3V3) |
2 | segment c | 27Ω to sink (GND) |
3 | CA 2 | Output (3V3) |
4 | segment e | 27Ω to sink (GND) |
5 | CA1 | Output (3V3) |
6 | segment g | 27Ω to sink (GND) |
7 | segment f | 27Ω to sink (GND) |
8 | segment dp1 - CA1: alarm - CA2: point 2 - CA3: double point (bottom) - CA4: point 4 | 100Ω to sink (GND) |
9 | segment a | 27Ω to sink (GND) |
10 | segment b | 27Ω to sink (GND) |
11 | segment d | 27Ω to sink (GND) |
12 | CA 4 | Output (3V3) |
13 | segment dp2 - CA1: double point (top) - CA2: Wifi symbol - CA3: not connected - CA4: not connected | 200Ω to sink (GND) |
Internally, the LED's were powered from 3V3, with the resistors on the cathode (GND) side to limit the current. I have to say I didn't quite understand logic behind the resistor values. It seemed to be a kind of R=200 [Ω] / (amount of segments in parallel) or R=400 [Ω] / (amount of dp segments in parallel), which didn't make sense to me, since they wouldn't be powered all at the same time anyway.
In the end, I just ignored the resistor values on the original design and used higher values to make the display less bright.
Most importantly: this was easy to work with, no need to dive into communication protocols with which I am not that familiar, so off we go!
Act IV - The rest of the hardware
Good, the display was fixed, now for the rest of the hardware.
I needed some buttons, a speaker and amplifier, a controller and it should all be fitted into a housing in the end.
I had a Raspberry Pi 3 lying around, so that'd be the controller. It would also be overkill, but at least I knew how to work with it, without needing to learn how to work with for example an ESP32 or a Raspberry Pi Pico.
To test the display with the Raspberry, I got some resistors and other stuff at a local electronics store with an archaic site but good service: Segor. When I got those, I also found the typical, simple push buttons in several different colours and decided on using those.
As for sound, I found that stereo would be useless for an alarm clock. At best the speakers would be placed a couple of centimeters apart, so mono it was. As I didn't want to go soldering SMD components unless really necessary, I ended up with a MAX98357A breakout from Adafruit. The MAX98357A is a mono amp, which works over I2S.
At first, I paired the amp with a small piezo-speaker, but that sounded awful for music, especially in the higher frequencies, so those definitely needed to be replaced. On the other hand, it worked great when paired with the Pi over I2S, except for a popping noise when starting the output stream, but more on that later.
For the speaker, I ended up with a stereo speaker set, which I just cut up to connect 1 of the speaker to the amp. I wouldn't call the result High Fidelity, but certainly good enough for waking up with.
Act V - Software side of things
For software, it was necessary for my girlfriend to be able to control the clock herself. For another project, I at one point switch to Home Assistant (HA), mostly for the front end/GUI options. I am not good at designing pretty things and HA made it possible to design something that looked at least consistent without too much effort. And the RPi3 is powerful enough to run it, so for the front end I used HA.
The back end would be a C++ program, handling the button inputs, setting up the display and informing HA when an alarm should start.
The front and back end communicate over MQTT, using the Mosquitto broker, and use retained messages to save settings/alarms on power cycles.
I chose for MQTT as I figured that out for another project already. And it could possibly in the feature lead to a more expanded home automation, should my girlfriend want that.
Act VI - Schematics
The schematics are done in KiCad, a program I absolutely love. Pdf's and original files are available in the project.
It is a fairly easy schematic, mostly just hooking everything up together. The Pi has plenty of I/O, so I didn't use any shift register for the buttons or LED display.
The amplifier has a selectable output gain (3-15dB in 3dB increments), depending on if you connect the GAIN_SLOT input to VCC or GND (optionally through a resistor). I added both options for Justin and Kees. In the end, a 3dB gain was sufficient, although I wired it up to 6dB. The speaker couldn't handle that at full volume though.
The amplifier can also be shutdown remotely if you set it the SD_MODE pin to the correct voltage. I did wire that up through a couple of optional resistors, but didn't use that in the end.
Power is provided using 2 pole header connected to an USB-C input. I wanted a USB-C input (Raspberry Pi 3 uses a micro USB) and probably couldn't use the Raspberry's input directly anyway, since the connector would be in the wrong place. So I used the break-out to power it directly on the 5V pins on the GPIO header. In combination with the 15W RPi4 power supply, that worked flawlessly.
The buttons use an internal RPi pull-up resistor.
The LED's are powered directly from 3V3, not the GPIO outputs, with a BC559 (but any FET'll probably do) to switch them. I added 330Ω and 1kΩ resistors to limit the current through the LED's. That was sufficiently bright.
Lastly, the spare GPIO's are wired to a header, along with 3V3, 5V and GND.
I used through hole components, because I'm lazy and they are easy to solder by hand. And I had plenty of room anyway. For the BC559 FET's I did use a footprint with the pins very close to each other. That lead to some soldering short. Next time I will be even more lazy and put more space between the pins.
Act VII - C++ program
The following files are not included in this project, but you can get them externally:
- For MQTT communication, the Eclipse Paho MQTT C++ Library was used.
- For JSON parsing, Niels Lohmann's JSON library.
- And for GPIO manipulation, Mike McCauley's bcm2835 library.
Files can be found in this project.
For MQTT, I made my own class that kind of re-implements the Paho MQTT class. Mostly because I didn't know how work with that class yet. In hindsight, I don't think this implementation really makes sense. But hey, it works and when I came to that insight, I didn't want to change it anymore.
Also, don't mix up GPIO numbers and pin numbers with the bcm2835 class. It will cause your program to crash if you do it wrong. If you have been working on a large other chunk of the code when you also make a tiny change in the pin numbers, you might end up looking in the wrong place for a couple of hours first. That's why I included a manual GPIO checker. Probably it will be optimised out by your compiler when you don't mix up GPIO and pin numbering.
And lastly, nobody ever taught me proper programming and mark up rules, so I came up with my own standard 15-20 years ago. Since programming is not my job anyway, feel free to have an opinion on that and send it to /dev/null, but constructive criticism is allowed.
Act VIII - Housing
Fitting it all together was a challenge. Not really helped by FreeCAD. I have mixed feelings about that program. Professionally, I've used Solidworks and Siemens NX, which are great. And greatly expensive without a pirate hat. FreeCAD has some quirks and limitations (especially when you make changes higher up the tree, that most of the time ruins your model). On the other hand, that does force you to really think your model through beforehand, to save yourself the trouble of rebuilding it all the time. Which I still did obviously. Also because every change seemed to break the constraints I made in the full assembly.
I split the model in 3 parts, so as to have it printed without supports. A 3D printer I don't have, instead I had it printed at 3dk.berlin. They printed some other stuff for me before and their service and pricing is good. (That's not an ad, but an opinion. I had to pay for it myself.) Anyway, the prints came out very well and they also had some tips that I took along in the second revision.
The second revision was never printed though, but my screw clearance holes were pretty tight so I increased the size a bit and made the print less vulnerable.
Act IX - The last steps
Well, it all was done on time. Well on time actually, I had some days to spare. The only thing that was kind of difficult was getting it to work at her place (we don't live together) on her Wifi network. As the RPi didn't have a monitor attached (and I already wrapped it in gift paper, leaving only the USB-C plug available) I could only login to a terminal over SSH, but I did need Wifi for that.. It took me a while to figure out that editing wpa_supplicant files or through wpa_cli wouldn't work and I just needed to run the raspi-config again to set the correct network. After that it connected and all was good.
Act X - The reveal
So, it was a Friday morning, my girlfriend still needed to go to work, but of course I had already set an alarm on her present. Meaning: in the morning she did successfully wake up with Deep Oldies in her ears!
After unpacking and explaining a bit, she was very happy with it, although it doesn't look like Groundhog Day at all..
Epilogue
Some minor things I wanted to add/am proud off, mostly configuration wise.
The looks
It ended up way more industrial looking than I had planned. But to my defense and like I said earlier: I find functionality more important than looks. I had it printed in Light grey, but another colour might've been a better option.
Audio
The MAX98357A mono amp breakout has a quirk that introduces a popping sound when activated. Adafruit's guide mentions some tweaks, which didn't work for me somehow. Maybe because I didn't use the installation script or because I used mpd.
Anyway, what fixed it for me, was:
- Modifying asound.conf as in the guide under 'Add software volume control' to enable volume control from Home Assistant
- Commenting out the device entry in the mpd.conf file as shown below.
audio_output { type "alsa" name "MAX98657A" # device "hw:0,0" mixer_type "software" }
Home Assistant configuration
Home Assistant is great, but I didn't find a good way to work with variable length arrays yet. So the maximum number of alarms is now 5, because I didn't want to copy/paste for more alarms..
The user can select on which days an alarm should ring through 7 booleans. This is then saved in a single int and sent in a json to the backend. That does lead to some interesting templates in the Home Assistant's configuration.yaml to convert it back and forth. I don't know if it is the best way, but again: it works.
First one to make a number out of the 7 booleans:
template:
- sensor:
- name: alarm_days
state: >
{% set value = 0 %}
{%if states('input_boolean.alarm_day_mo')=='on'%}{% set value = value + 1 %}{%endif%}
{%if states('input_boolean.alarm_day_tu')=='on'%}{% set value = value + 2 %}{%endif%}
{%if states('input_boolean.alarm_day_we')=='on'%}{% set value = value + 4 %}{%endif%}
{%if states('input_boolean.alarm_day_th')=='on'%}{% set value = value + 8 %}{%endif%}
{%if states('input_boolean.alarm_day_fr')=='on'%}{% set value = value + 16 %}{%endif%}
{%if states('input_boolean.alarm_day_sa')=='on'%}{% set value = value + 32 %}{%endif%}
{%if states('input_boolean.alarm_day_su')=='on'%}{% set value = value + 64 %}{%endif%}
{{ value }}
Second one below to convert the number (that was received from MQTT in the alarm list) to a string
The (bitwise) number 0b0010101 would result in "Mo Mi Fr" (for the German Monntag, Mittwoch, Freitag). (Side note: When no days are selected, it would only ring once and not repeat on a next day.)
template:
- sensor:
- name: alarm1_days
unique_id: templ2404261132
state: >
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b0000001)) %}Mo {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b0000010)) %}Di {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b0000100)) %}Mi {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b0001000)) %}Do {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b0010000)) %}Fr {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b0100000)) %}Sa {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days | bitwise_and(0b1000000)) %}So {%endif%}
{% if(state_attr('sensor.alarm_list','alarms')[0].days == 0) %}Nur einmal{%endif%}
icon: mdi:calendar-range-outline
The configuration for Home Assistant (3 dashboards and 3 .yaml files) can also be found in the files.
MQTT communication
Shout out to MQTT explorer, which works great to see what is going on with your MQTT communication. (And clean it up after messing with it.)
Function creep
Light every hobby project, this one also had a problem with function creep. Although I kept it to the goals, there are also several configuration options available that might not be used, but which I would like for such an alarm:
- Configurable turn-display-off-at-night
- You can turn this function off and on
- And you can set the times at which it should switch
- The option to slowly raise the volume when an alarm starts, along with a maximum volume.
- The option to turn off the alarm after a certain amount of time (because I guessed she would forget turning it off at some point).
- Snooze time.
- And of course the radio station. 2 are now pre-programmed by adding a link in a text input in HA. In total 6 can be pre-programmed.
- And she didn't like the blinking of the double point every second, so that is now configurable too (and permanently set to: not blinking).
Note to self
Make more photo's during the development of a project..