Sunday, January 24, 2021

Hockey Experience Project - Part 4

 If you landed on this page and haven't read the first few parts I would recommend at least starting on Part 2.  

This is a multi-part blog post on how to recreate a hockey experience at home.  The experience we are going for is to have a goal horn, light, our favorite teams song and of course food, drinks, and rally towels that one would expect when you go to a game, it'll just be cheaper :).

In the last part we put together our web application, have it running and sort of looking good, we can always improve upon it. Sadly where we left off was the web app just printing a message to the screen.


So now let's make it do something!

If you remember from our hardware list I'm using a bluetooth speaker that I had laying around the house that no one was using. Turns out that is a bit of a pain in the @$$ and I'll be looking to replace that with a wired speaker in the future, but if you got here and all you have is the bluetooth speaker let's get it working.

Pair Up and Make The Play

One of the main problems with a bluetooth device is auto pairing the device to our raspberry pi, and keeping that device powered on while it's not in use. I mean nothing says "whomp, whomp" more than seeing Aho score a goal you reach over tap the button on the web app, the light spins but no sound. 

The raspberry pi has a utility bluetoothctl that we can use from the command line to find our device and pair it. This should really be done once so that we can tell the raspberry pi to trust the device. 

From the terminal type in:
sudo bluetoothctl

Next, we want to turn on and set the default agent:
agent on
default-agent

Now put your device in pairing mode,  some devices you can just hold the power button till a blue light flashes or announces its in pairing mode but check your manual if your unsure.

Once the device is in pairing mode we go back to our terminal and scan for the device:
scan on

You should see a few devices pop-up that will look like the following:


When you see your device in the list copy the mac address, the 6 digits separated by colons next to the name, then we want to pir to that device by typing the following (use your device mac address).

pair FC:58:FA:B2:A5:85

You should see the device pair and hear an audible sound on your bluetooth speaker. Next we want to add the device to our trusted device list. Type in the following:

trust FC:58:FA:B2:A5:85

And finally to connect we type in:

connect FC:58:FA:B2:A5:85

When you are done just type in exit to get out of the bluetoothctl utility.

Now anytime we play a sound on our raspberry pi it should come through the bluetooth speaker.  But we still have an issue of auto-pairing the speaker so that we don't have to type all that in everytime, we want to just turn the speaker on and have sound.

We can auto connect with a helper script that will do this for us, then all we need to do is have our app call the script and ensure we are connected to our bluetooth device. So to our project let's add the a file called 'autopair' and add the following to the file:

#!/bin/bash
bluetoothctl << EOF
connect FC:58:FA:B2:A5:85
EOF

After we save the file we need to make it executable, go to the command line and type in:

sudo chmod +x autopair

Since the script is just a plain shell script we can run it separately from the command line to test it is working. So let's turn on the bluetooth device and run the script.

./autopair

You should hear the audible noise of the raspberry pi connecting and any sound the raspberry pi plays should now come through the bluetooth speaker.

Now that we have the audio connected, how do we play the mp3 file(s) of our horn and goal song? There are a few different options, I'll be using VLC Player as I'm a bit more familiar with it but we could have used the default OMXPlayer. Since the vlc player is not installed by default we will need to run apt-get in the following command to install the player.

sudo apt-get install vlc

VLC Player can be controlled from the command line with cvlc, our web app will call cvlc and pass the mp3 file to it to be played. while it is playing we want our goal light to be activated until the song stops. So let's create a new script called goal.py, this script will be responsible for playing the song and activating the light.

Let's create that goal.py file and add the following to our file:


import os, time
from gpiozero import LED
from signal import pause

led = LED(17)

def activate_experience():
  led.on()
  play_horn()
  led.off()

def play_horn():
  os.system('sudo ./autopair') # Ensure our speaker is still paired with our rpi
os.system('cvlc static/media/Carolina_Hurricanes.mp3 vlc://quit')

In the above code you can see we assign the GPIO pin 17 to the LED object. While our rotating light is technically not an led the LED object is a basic switch with on/off functionality. While the object does contain other functionality we will be using it as a simple switch.

The pin number will depend on which pin you have wired your raspberry pi to the controller. In this example I'm using 17.  If you are unsure of which pin is which your raspberry pi should have come with a handy GPIO charts or you can find your model and the pins here.

While it may be a bit hard to tell from the picture we attach a ground wire to a ground pin and the positive to pin GPIO 17 on the raspberry pi, then connect the positive/negative sides to our controller. If you connect to a different pin then just adjust the pin number used for the LED object in the code.


On thing, well actually a couple of things I really like about this controller is it looks more finished than having a breadboard and a bunch of wires everywhere and it has an "always on" plug so we can actually plug our raspberry pi's power cable into it and keep everything a little more self-contained. It would have been better if the raspberry pi's plug didn't cover up our "normally on" plug but for this project we are only plugging on thing in to be controlled so it'll work for now.



In the activate_experience method, we turn the light on, call the play_horn method and then turn the light off. 

In the play_horn method, we call our autopair script to ensure our bluetooth device is connected to the raspberry pi then play the mp3 that contains the goal horn and song. If you are playing the horn then song as separate mp3s just add an additional line(s) to play your files. I like the idea of modifying both into a single file as you can cross-fade and make the experience more seamless.

The last step in this series is to call our goal.py code from the web app. To do this we want to open the app.py file, add an import to the top of the file so that we can call the script.

import goal

then in the same file find the line that matches:

print("Play Horn!")

and replace it with 

goal.activate_experience()

Now our completed code should look like the following:

from flask import Flask, render_template, request
import goal

app = Flask(__name__)

templateData = {
    'title': "Carolina Hurricanes"
}

@app.route('/', methods=['GET','POST'])

def index():
  if request.method == 'POST':  goal.activate_experience()
    return render_template("index.html", **templateData)

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')


That's it! The only thing left to do is place the tablet in an open area so anyone can activate the experience. For the rotating bean, we tried several places but found it works best behind the t.v. , it keeps it out of the way and doesn't accidentally blind your family.

To complete our experience we had a good friend of ours use their cricut to make some rally towels.


Finally, get some Storm Brew, fire up the smoker for some pulled pork bbq nachos and other arena snacks and immerse yourself in the game.

And the next time Svech scores that Lacrosse goal....



Friday, January 22, 2021

Hockey Experience Project - Part 3

 If you just happened to land here on this page by accident or without reading part 1 or 2, this multi-part blog is how to bring home an NHL experience to your living room, complete with goal light, horn, song, etc.  I would recommend going back and reading part 1 and 2 to get fully up-to-speed with where we are and why we chose to do things in this manner.


Carolina GOOOAAALLL!

So hopefully somewhere between part 2 and today you did a little research and obtained the mp3s for the goal horn, song, and maybe a few logos. I won't be uploading or sharing my files due to possible copyright issues, I don't own the rights to those so I'll only be using them for my personal at home enjoyment.


I did find a pretty cool hockey rink image that is freely available that would be perfect to use as the main background for our webapp. 


We will save this to our project as hockey_rink.jpg in the static/media folder along with any other media files for goal horns etc.  Since I want to ensure the goal song is played after the goal horn, I combined the two together into a single mp3, that way there are less media files to worry about and I'm guaranteed the effects I want. Obviously, I could have kept them separate and just queued them one after another, but I like the simple approach better.

Our project structure should now look like this.




Time to get on that ice and make something happen!

So at this point we have a functioning webapp, although it is kinda lame and boring at this point and we have our media files. So let's add our background image and some functionality to the page.

If you were wondering why we created an empty libs and templates folder, well now we shall add some files to them. The libs folder will hold any css (Cascading Style Sheets) and js (JavaScript) we write. This will make maintaining the webpage much easier and also it is best practice to not just throw everything in one file.

If you are not familiar, Flask uses the Jinja templating language which will allow us to create html pages with certain data as variables then when we render the page we can pass the values of those variables. Technically Jinja will provide so much more to us but since this is a simple webapp for our own personal use and won't be exposed to the Internet there isn't really a need to cover it all in this blog.

Why is this useful to us?

Let's say we want to add a banner at the top of the page that should display the teams playing, such as: "Hurricanes vs Red Wings".  We could add this directly to the page in html like such:

<h1>Hurricanes vs Red Wings</h1>

The problem with this approach is we will have to manually update the html everytime there is a new game and that can be a pain and if you are like me you will eventually forget to do it before game time, you'll be rushed to change it and probably make a mistake and miss the first goal of the game.

With jinja we can simply add the team names as variables from the APIs we explored in part 1. If you skipped part 1 this may be a good time to go review.  So now our html with jinja would look like:

<h1>{{ away_team }} at {{ home_team }}</h1>

We could do this with any bit of data we want to change dynamically on the page, team logos, scores, power play time, empty goal status, etc.  We could even use jinja to control the design of our page by using jinja in our css class names for the html, tags this would allow us to give the page a 'theme' for the home team or just our favorite team(s). If you follow more than one team this may be the approach you want to go with. I'll be using this approach in my examples so when I want to watch Marc-Andre Fleury play I can give the page a Vegas Golden Knights theme.


Every good website always has a default 'index.html' page that is usually used for the homepage. For our use that will pretty much be the only page we need. We can always add more later if we have a need to. So let's add a new page called index.html under templates and add the following markup to the file.

<!DOCTYPE html>
  <head>
    <title>{{ title }}</title>
    <link rel="icon" href={{ url_for('static', 
          filename='media/Carolina_Hurricanes.svg') }}>
    <link rel="stylesheet" href={{ url_for('static', 
          filename='libs/css/site.css') }}>
  </head>
  <body>
    <div class="header"></div>
    <div class="main__content">
      <form action="/" method="POST">
        <button type="submit" value="" class="btn__goal">
          <img src={{ url_for('static', 
  		filename='media/Carolina_Hurricanes.svg') }}>
        </button>
      </form>
     </div>
  </body>

Our html is pretty basic as you can see from above.  We will add more to it later to give it a better team theme.

The main important part in our markup above is the form section.

    <form action="/" method="POST">
       <button type="submit" value="" class="btn__goal">
          <img src={{ url_for('static', 
  		filename='media/Carolina_Hurricanes.svg') }}>
       </button>
    </form>
Our form action "/" tells our application to go to the root of the website, which is the index page. So essentially it will just refresh itself when the form is sent to the backend server. The HTTP method we will use is a POST method, this is what the web app will look for to determine if it should kick off our 'experience'.

You'll notice I've used an image for my button, in this case I'm using a team logo, so instead of  a cheezy "Click me" button we just touch the team logo to make magic happen.

Now that we have our index.html template completed, for now, we will want to add some design to it.  In the head section you'll notice I've already linked our main css (Cascading Style Sheets) file so lets go ahead and create that file and add some styles.

Under the directory 'libs' we will create a new subdirectory called "css", this css folder is where we will put all our styles for our web app.  Go ahead and create our main css file called 'site.css' and add the following to that file.


body, html {
height: 100%;
   margin: 0;
}
.main__content {
    background-color: white;
    background-image: url('../../media/hockey_rink.png');
    background-repeat: no-repeat;
    background-size: cover; height: 100%;
}
.header {
    height:10%; margin: 0;
    background-color: rgba(204, 0, 0);
}
form {
    position: fixed;
    top: 60%; left: 50%;
    transform: translate(-50%, -50%);
}
.btn__goal {
   background-color:rgba(255, 255, 255, 0.1); border: none;
}
In the above css we set the background color of the header section to one of the team's color of red, position the form that contains our button to be in the center of the screen, and add our hockey rink image as the background to the page. So when it is all put together what does the page look like?

 

Originally I thought about using a puck as the button icon, thought it would be more fitting but the logo is a nice size for someone to lean over and tap and still looks pretty good in my opinion.

Ok, so we have a basic page layout and some styling so that it is a little less boring, now we just need to add a bit more code to our main application file so that when the button is pushed and the post request is made we can tell the server to do something.

For this we will add the following code in our app.py file.

    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    templateData = {
       'title': "Carolina Hurricanes"
    }
    
    @app.route('/', methods=['GET','POST'])
    def index():
        if request.method == 'POST':
           print("Play Horn!")    
        return render_template("index.html", **templateData)
    
    if __name__ == '__main__':
        app.run(debug=True, host='0.0.0.0')
    

Most of this should look familiar, we added the template data which is a json object to store the key: value pairs our template will render. Right now we are only setting the title of the page.
templateData = {
    'title': "Carolina Hurricanes"
}

We also modified the route to allow both GET and POST requests. 

To prevent the experience from kicking off everytime the page is loaded we added an if statement to check the request object's method, if the method was post then we know someone touched the logo aka button on the main page. To test this we will just print out "Play Horn!" anytime the button is pushed.

if request.method == 'POST':
   print("Play Horn!")

Once we save everything and run our app, if you forgot how from the command line or our terminal type in the following command:

python3 app.py
Now we should see our server come up, go to the server's ip and port 5000 in your browser and you should now see the page that looks similar to the one above, clicking the logo should also print the "Play Horn!" message to our console.

If your having issues or want to double-check your code you can go to my repo and pull or review the code for this section from my github repo.

In the next section Part 4 of our hockey experience will probably be the last section of this series where we put the rest of the solution together.

Friday, January 8, 2021

Hockey Experience Project - Part 2

 Putting it all together

This is the second part of a multi-part blog post on recreating an arena hockey experience at home. If you got here without reading Part I you can find it here

Part I TL;DR

In part 1 we covered the NHL APIs, which ones to use, how to find games, etc. We also realized that an automated approach may not work due to TV delays and such, so we came up with something a bit more manual. I may go back and update my web app to include automated score tracking later. I mean how cool would that be to display the current score on my web app.


Equipment Used

UPDATE: Turns out the bluetooth speaker is a bigger hassle than it is worth. The steps in this series continues to show how to work with it but I'll be trading mine out for a wired speaker.

Since our goal light plugs in to the wall, we could either splice the wires and wire up a breadboard to a relay or use the control relay I linked above that does this for us and we can just plug in multiple devices. I like this approach as it is cleaner, supports multiple devices, and I don't have to worry about electrocuting myself which is always a plus.

For the Android Tablet, this can be any tablet or device you can get a web browser on. I like the idea of the tablet as it makes it real easy to setup and anyone in the room could use it. If you didn't read part 1, I chose the Altec Lansing speaker merely based on the fact I already had one sitting around that wasn't being used. You could use any compatible speaker it doesn't have to be a bluetooth one. But I do like the idea that the speaker can be setup anywhere in the room.

Programs! Get Your Programs Here!

Our raspberry Pi can support a number of different programming languages, Java, Python, Node.js, Go,  Bash, etc, but before we begin writing code we need to pick one.  Most of my professional career as a Developer was spent writing Java code for both standalone and web applications. When I started doing more DevOps and systems related work I started using more shell scripting and Python. For this project I want something robust, fun, and easy to get started with.

I feel like this is a Pokemon moment, "I choose you Python" or more specifically Flask.

Why Python/Flask?
Somewhere a long time ago I read some Python documentation that suggested when you comment your code it should be done in the style of Monty Python, the comedy group. While by today's standards that is probably a bad idea, I've always enjoyed watching Monty Python and loved the idea of this so I use Python whenever I can.

Ok so here is a more valid list of reasons:
  • Python is easy to learn
  • Most *nix systems, including our raspberry pi already has Python installed
  • Python is an interpreted language so we don't have to compile it
  • Python is a mature language and we can easily find support on forums, etc
  • Freely available libraries, including the GPIO Zero library we will use to activate our goal light.
  • Flask is a web application framework that can be easily customized to our needs.
  • Flask has a built-in webserver we can use for development.
  • Our application is static, meaning the content won't really change, Flask is perfect for small static apps.
What about Django you say. We could very well have used Django for this, it is a very mature web framework with a lot of support, but my focus right now is on time. We need to get something going quickly, and I know with a few lines of code I can get a static Flask app up and going pretty quick.

 Puck Drop

If you haven't done so already, you will need to setup your raspberry Pi. I'm not going to go over how to put your raspberry pi together and do the initial setup, but if you need them you can find them here. Go ahead and get the Raspbian OS installed, WiFi setup, etc. You can plug a monitor, keyboard, mouse, etc in and work directly on the raspberryPi if you wish or following these instructions you can setup a Remote Desktop (RDP) session, which I will be using in my examples.

I prefer the RDP method as it allows me to use my MacBook to connect remotely to the pi where my development environment and IDE will be installed at. If you are going to hookup a mouse and keyboard directly to the raspberry pi, just make sure you use USB devices and not bluetooth ones since our speaker will be paired via bluetooth.

If you don't have a favorite IDE to use on the raspberry pi I would recommend installing VSCode as your editor it has a lot of great extensions to make your life so much easier and it is faster than Eclipse or IntelliJ and IMHO it is way better than the Python editors Thonny or IDLE. 

VSCode isn't in the package repository but you can use the web browser on your raspberry pi to download it, just be sure to grab the 32-bit ARM .deb package unless you know for sure you have a 64-bit OS on your raspberry pi.

Ok, let's get started!

Lets create a new directory for our application, we will call it 'nhl_webapp'. VSCode allows us to open a terminal directly in the IDE which can be faster to create the base files/folders on the command line.

mkdir -p nhl_webapp nhl_webapp/templates nhl_webapp/static nhl_webapp/static/media nhl_webapp/libs
cd nhl_webapp
Initialize our project as a git project
git init

There are a few files we will need to get started. We can use the 'touch' command to create the empty files in one entry.

touch app.py .gitignore README.md
  • app.py  - will hold our main web application code
  • .gitignore - Since I'll be using git to manage my code this allows me to omit certain files/folders from being checked in.
  • README.md - Markdown file that will describe our project and tell others how to use it.
Next we will create our virtual environment and activate it. The virtual environment will be used to install our dependencies to and run our application.

python3 -m venv env
source env/bin/activate

Great, now lets install Flask, GPIOZero and their dependencies to our virtual environment.

python -m pip install Flask gpiozero

We don't need to check in our entire virtual environment to git but we do want to ensure we capture the requirements for others, so lets add the `env/*` folder to our .gitignore and capture our requirements in a separate file.

python -m pip freeze > requirements.txt

cat env/* > .gitignore

So now our file structure should look like the screenshot below. 


Let's add some code and test that everything is working.

In VSCode open the app.py file and add the following code and save the file. If you don't want to type it out you can find the source on my github repo.


Now to test this basic application. 

In the terminal window type the following to start the application. Because Flask comes with a built-in dev web server, starting the app will also launch the web server on its default port of 5000.

python3 app.py

If everything worked we should see the server startup.


Now let's see what the web app looks like in the web browser. Open Chromium or whatever browser you have on the raspberry pi and go to the address of http://localhost:5000 If all went well we should see the page with "Our NHL WebApp" on it.


So now we have a functioning Flask web app, although at this point it is very basic and doesn't do anything other than print our text. 

If you are using git and have a remote repository, now may be a good time to commit those changes.  If you are unfamiliar with git there are a lot of free resources on the web to learn git. Git is a great way to keep track of all your changes in a code repository so if you ever accidentally delete a file or make a change that breaks something it is easy to go back to a previous version of your code. It also allows you to see what you changed and when.

In our next steps we will add some colors, graphics, and a button to activate the goal horn and goal light to tie it all together. So this is a good time to search the web for some free hockey graphics, team logos, and an mp4 of the goal horn.