After finishing my first entry for this competition where I almost missed the deadline.. I realised I had another whole month as the deadline was pushed back. So what better way to continue my learning than to build something for the other category using the Alexa Voice Service!
Trying to wrack my brain for ideas.. I was inspired by the header image and decided to build my own time machine!
OK so I'm not Doc and I didn't require any flux capacitors, but a soldering iron and 7 panel displays were involved. I decided to make an Alexa enabled alarm clock!
ConstructionAs usual I had my Raspberry Pi 3 with WiFi goodness, I added a display using an Adafruit 1.2" 7 panel (banana for scale).
I followed the instructions provided by adafruit which required a bit of soldering:
and a little bit of wiring per adafruit's instructions:
"D" - I2C Data Pin (SDA)
"C" - I2C Clock Pin (SCL)
"+" - 5v. (Will not run on 3.3v!)
"-" - GND
"IO" - I2C bus voltage.
We're in business! wow this thing is BRIGHT! I wonder if I can change that..
As the Alexa sample app we're going to use doesn't allow voice activation, I'm going to hardwire a switch. I have a simple push button purchased from here that I've wired up to a GPIO pin and a 3v pin which will be used to activate Alexa (more on that later).
First up, my warning to you is that I'm not a great programmer, so go easy on my terrible code ;-)
I grabbed the Adafruit library here on github and got to work making a clock. It's fairly simple, use python to get the time, format it and write them to the display all while blinking the colon. I had to add some try blocks because I occasionally get write errors to the display, but they never affected the overall functionality of the clock.
def clock():
display.clear()
colon = True
# Loop for 10 seconds
for x in range(0,10):
display.set_colon(colon)
t = float(strftime("%I.%M"))
dt = datetime.datetime.now()
# Put the time on the display
display.print_float(t)
try:
# Enable left colon to flag it's afternoon
if dt.hour > 11:
display.set_left_colon(True)
# Change brightness according to time of day
if dt.hour > 20:
display.set_brightness(4)
elif dt.hour > 8:
display.set_brightness(8)
# Write the display
display.write_display()
except:
print "Write error"
# Turn the colon off if it's on, and vice versa
colon = not colon
time.sleep(1)
Our result:
Woohoo! it looks good. What else can I get this to do? hmm.. lets try the temperature! I found an awesome API at openweathermap.org where I can get some current weather data. Sign up for an API key, find my local weather ID and map the digits against the predefined hex values for the numbers and letters.
# Pre-defined digit display values
DIGIT_VALUES = {
' ': 0x00,
'-': 0x40,
'0': 0x3F,
'1': 0x06,
'2': 0x5B,
'3': 0x4F,
'4': 0x66,
'5': 0x6D,
'6': 0x7D,
'7': 0x07,
'8': 0x7F,
'9': 0x6F,
}
def weather():
display.clear()
loop = True
try:
# Get our weather data from the API
response = urllib2.urlopen('http://api.openweathermap.org/data/2.5/weather?q=Wollongong&appid=<api key>&units=metric')
data = json.load(response)
t = str(data['main']['temp'])
# Format each digit according to the digit map
for x in range (0, 5):
display.set_digit_raw(0, DIGIT_VALUES.get(str(t[0]).upper(), 0x00))
display.set_digit_raw(1, DIGIT_VALUES.get(str(t[1]).upper(), 0x00))
# 'Degrees' symbol
display.set_digit_raw(2, 0x63)
# 'C' symbol
display.set_digit_raw(3, 0x39)
try:
display.write_display()
except:
print "Write error"
time.sleep(1)
except:
print "URL error"
Lets try it:
Now I have my weather and time, simply cycle between the two:
while True:
clock()
weather()
..and I'm happy with my display! I run this script
AlexaThe only thing missing from this Alexa alarm clock is.. Alexa! I began by using the Amazon Alexa AVS Raspberry Pi Project. The tutorial provided there is excellent so I won't go into how I installed it as anyone is free to follow those instructions. I did however make a few modifications that I will outline.
Firstly I had to get the push button I mentioned earlier working. Full credit to Eddie Hartman who figured out how to add this to the Amazon project. He adds some simple gpio code which calls the actionButton.doclick(), and it works perfectly:
GpioUtil.enableNonPrivilegedAccess();
final GpioController gpio = GpioFactory.getInstance();
final GpioPinDigitalInput myButton = gpio.provisionDigitalInputPin(RaspiPin.GPIO_02, PinPullResistance.PULL_DOWN);
myButton.addListener(new GpioPinListenerDigital() {
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event){
actionButton.doClick();
}
});
Be sure to check out the link to his project above if you want to implement the push button switch. It's pretty straight forward.
Next, I needed Alexa loading on boot, as I can't really VNC into the clock every time I plug it in. I wrote a script (which also loads my clock on boot):
alexa.sh:
#!/bin/bash
export LD_LIBRARY_PATH=/usr/lib/vlc
export VLC_PLUGIN_PATH=/usr/lib/vlc/plugins
export M2_HOME=/opt/apache-maven-3.3.9
export PATH=$PATH:/$M2_HOME/bin
export DISPLAY=:1
cd /home/pi/nicksclock/ && python clock.py &
cd /home/pi/Desktop/alexa-avs-raspberry-pi-master/samples/javaclient && /opt/apache-maven-3.3.9/bin/mvn exec:exec
and we add some lines to /etc/rc.local:
screen -dmS "nicksclock"
screen -S "nicksclock" -p 0 -X stuff "sudo -u pi /home/pi/nicksclock/alexa.sh$printf \\r"
This runs everything in a screen session as root, which is great for debugging. You can login, sudo su and screen -dr to find out what's going on. I plan to move this into a systemd file soon to clean up the process to solve an issue which is....:
Authenticating your Alexa AVS Raspberry Pi device. This was a huge issue I took a long time to overcome because the way the project is configured by default:
- You load the companion app (which takes care of authenticating with the Alexa Voice Service)
- You load the java sample application
- You are presented with a URL you must visit from the browser on the raspberry pi to authenticate your device
- You can now use the Alexa Voice Service
This is a huge obstacle because as I said earlier, I can't VNC into my raspberry pi every time I reboot the device. I went in search of help and the amazon staff members on the alexa forums are outstanding. My thread is available here and the answer was to run your javaclient in companionApp mode instead of companionService mode, and use an Android or iPhone app to authenticate your application (once). Once you get this done you never have to authenticate your application again if you reboot. The full instructions on doing this is available here.
My only comment is that the documentation says "The Android app should be running on the Android emulator on the same computer where you started the server and client." which is NOT true, I completed this just fine on my own Android phone (after spending hours trying to get an android emulator working on a Raspberry Pi):
My final issue regarding the Alexa Voice Service app was that even though I was starting it on boot as described above, it wouldn't always get a proper token to authenticate against the AVS. I found in the console that I was continually hitting this error:
14:57:38.637 [Timer-0] ERROR com.amazon.alexa.avs.auth.companionapp.CompanionAppAuthManager - There was a problem connecting to the LWA service. Trying again in 2 seconds
14:57:38.652 [Timer-0] ERROR com.amazon.alexa.avs.auth.companionapp.CompanionAppAuthManager - There was a problem connecting to the LWA service. Trying again in 2 seconds
14:57:38.667 [Timer-0] ERROR com.amazon.alexa.avs.auth.companionapp.CompanionAppAuthManager - There was a problem connecting to the LWA service. Trying again in 2 seconds
It took some research and a post to the Alexa forums again to realise that I was probably starting the application before my networking was available on the Raspberry Pi (another thing fixed by moving the startup to systemd.. I promise I'll do that soon!).
The issue I discovered was actually an issue with the amazon provided code, as you can see on the timestamps above, every request is miliseconds apart. There's a variable called TOKEN_REFRESH_RETRY_INTERVAL_IN_S in CompanionAppAuthManager.java which should be pausing between each request.. but it wasn't happening. Even when setting my TOKEN_REFRESH_RETRY_COUNT to 1000 it still wasn't able to connect to the service. I went digging and digging and finally found the culprit:
Thread.sleep(TOKEN_REFRESH_RETRY_INTERVAL_IN_S);
should actually be
Thread.sleep(TOKEN_REFRESH_RETRY_INTERVAL_IN_S * 1000);
! I fixed this in my own code, and it worked beautifully. I have not had an issue connecting to the LWA service since then.
So what does any good developer do? Fix the code and submit a Pull Request of course! My PR is here on github and hopefully they'll merge it into their code soon.
So that's all for the Alexa Voice Service on the Raspberry Pi.. now what we need is a...
Supporting Alexa SkillNo device is complete without its own Alexa Skill, so I went to work on finding something I could add which worked well with the Alarm Clock. The majority of information in regards to setting up an Alexa Skill is available in my first entry.
I ended up with "Nick's Clock" (no bonus points for figuring out why I called it that).. which greets you in the morning, gives you a weather update, gives you a news update and turns on your kettle, ready for the day!
Here's how it's done:
var IFTTTkey = "<key>";
# Trigger the IFTTT request to turn the kettle on
request('https://maker.ifttt.com/trigger/WeMo/with/key/' + IFTTTkey, function (error, response, body) { })
# Weather API request
request({
url: "http://api.openweathermap.org/data/2.5/weather?id=2171507&appid=5b0bf755ef773f2a284183cfee4aa540&units=metric",
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
# Store weather values
location = body.name;
temp = body.main.temp;
description = body.weather[0].description;
# News API request
request({
url: "https://newsapi.org/v1/articles?source=time&sortBy=top&apiKey=7516817b3d0d4d02ac778918fbd317d4",
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
# Store news headline
news = body.articles[0].title;
# Generate Alexa speech response and callback
speechOutput = "Good morning! The weather today in " + location + " is " + temp + " degrees with a " + description + ". The top news headline is " + news + ". I have turned on your kettle, have a great day!";
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
});
}
});
This skill borrows from this hackster project which explains very well how to get the WeMo working via a lambda call. Be sure to watch my video to see it in action!
Making it PrettyTime to tidy up this bad boy! I create some amazing instructions of how I wanted my case to look:
...and go to modelling. This should do!
Time to print
When the print was done I installed the panel and the button.
And we have the final result. Looks great if I say so myself!
And now to power it on to get that awesome product shot:
I'm very happy with the final result!
ChallengesI've mentioned the majority of the issues I came up already, but I'd like to mention two issues I spent a lot of time battling with that I wasn't able to win. Both were related to the same thing: Audio.
I built a speaker with a mini arduino amplifier and plugged it into the Pi.
The issue is that it would constantly emit a buzzing/feedback noise. I tried a plethora of solutions I found on the internet and none of them worked. My speaker plugged into an iPod worked fine, and a different large Sony external speaker (that I ended up using for the project) worked fine as well, so I was really unable to isolate the problem. If anyone out there has any clue, please comment! My final solution is going to be to test this device tomorrow but I'm not sure how it will go.
My other audio issue was my idea of using bluetooth audio instead of a wired speaker, to eliminate having to plug anything in to the Raspberry Pi. This then came with a whole swag of its own issues, making the built-in bluetooth of the Raspberry Pi 3 work, and then making it work on boot. I did manage to get it working, the commands for anyone wondering (again, in /etc/rc.local):
screen -dmS "audio"
screen -S "audio" -p 0 -X stuff "sleep 30; echo \"connect 8C:DE:52:0C:B5:EC \nquit\" | bluetoothctl$printf \\r"
and the result of this? well.. java applications seem to require completely different code to output to a bluetooth audio device.. so I just didn't bother. It wasn't worth my time when a simple Sony speaker worked so well via the 3.5mm port to the line in port on the speaker.
Alexa Skill CertificationAfter the troubles with my previous skill certification in my first entry I did not think this skill would be approved at all so I didn't submit it. It's a little too customised in the fact that it's accessing my local weather and turning on my own kettle. I don't want Alexa users around the world turning on my kettle! ;-)
You can view my other published skill here.
ConclusionThis was a fun, stressful and some times downright aggravating project, but I learnt a LOT about electronics, the Alexa Voice Service and improved my NodeJS skills when writing the Lambda part of the Alexa Skill. And learning is the point of these projects, so I would say it was a huge success!
I had a great time, I love my new alarm clock and it has earned a spot on my work desk (yay for working from home!)
Cheers,
Nick
Comments