Even though time was tight (it always is) and I already had plenty to build for my Halloween party, I decided to take a chance and build an interactive prop for this year’s Monopoly game theme. My idea was a simple, arcade-like game with a single button. When you pressed the button it would play either a video clip of something cute and fluffy (like puppies playing or pandas rolling down hills) or a scary clip of a monster attacking or a killer stalking. That’s it. Sounds simple, right? Actually – it is!
Demo of the Chance Game in Operation
Despite it being such a simple concept, it still needed to be created and built. It would have been possible to do this with a standard computer or a cheap Android tablet, but the form factor wasn’t what I saw in my head – I wanted it to look similar to something you would see in an arcade. So, I decided to turn to a Raspberry Pi 3. I had one laying around, ignored and unloved, and I thought it would be a great opportunity to learn a bit about it and put it through its paces, even though this should not be taxing the hardware at all. As a bonus, it allowed me to use a larger monitor and add more fun buttons in the end.
For those not familiar with Raspberry Pi (RPi), suffice to say that it is an amazing piece of hardware for DIY! Devised, designed, and delivered by a charity organization, the 3rd generation Pi has a quad-core 1.2GHz ARM Cortex-A53 processor surrounded by the standard complement of SoC peripherals. In non-geek speak, it’s the guts of a high-end tablet, less the display, on a tiny board that’s intentionally made hacker-friendly. For $30. You can probably find cheap throw-away tablets for not that much more, but you trade easy access to pins and ports for a tiny, cruddy screen and lose the invaluable ecosystem and community that surrounds RPi.
Image from Amazon.com
Sorry, I guess I geeked out there a bit about the hardware, but you have to allow me this indulgence – when I got my EE degree something like this wasn’t even Sci-Fi, it had never been dreamt of. To bring a RPi into a lab when I was in college would have been like Ash demonstrating his boomstick before Lord Arthur – tantamount to magic.
The RPi was perfect for this project. It has plenty of hardware acceleration for playing the videos quickly and smoothly, it’s low power, it has an HDMI port, and it has General Purpose Input/Output (GPIO) pins that you can easily get to and use to attach a peripheral like my button.
I knew the RPi could easily handle the video playback task, but I had never touched one or tried to create a video playback app on embedded hardware before. A quick Google search practically handed me the answer on a silver platter. ThothLoki at Hackster had a write up of exactly what I needed to do! Score!!! See, this is why we wanted the community that surrounds the RPi!
His method goes through unboxing the RPi, flashing the microSD card to install NOOBS (which installs Raspbian) and creating a simple Python script to read a couple of buttons and then start a video player when you press the play button. Note that I did come across a few typos and minor mistakes in his write up, but the concept is dead-on and working through the issues never took more than a few quick Google searches. I will walk through my code step by step, but I do recommend his write-up too.
I want to state now that this was my first attempt ever at Python. I am a hardware guy and so my forays into software have usually been short, painful, and often unsuccessful. But, this project was a breeze, largely due to having most of the work handed to me by ThothLoki. I know that this program could be considered crude, hack-and-slash scripting, but it works, it’s fairly readable and given that a hardware guy only spent an hour or so on it, I am putting it in the “win” category.
Image from Python.org
My project was slightly different than the example in that I wanted to be playing a screensaver video by default and then interrupt it and play a randomly chosen video clip when the button was pressed. Once the clip ended, I restarted the screensaver. I had looked into using the screensaver function of Raspbian, but decided it was much simpler to just play a video I cryptically named “Screensaver.mp4”. The other major difference is that I wanted to play a random file, whereas ThothLoki knew exactly which video he wanted to show. I also only needed one button.
The entire script is posted on Github, but I think it’s best to go through it in bite sized chunks. However, before I get into the gory details, here’s a thumbnail of what the code does:
Configure everything Start the screensaver Jump into an infinite loop that: Reads the pins Picks a random number If GPIO17 is pressed, plays the video clip associated with the random number Waits around for the video to finish playing Restarts the screensaver Jump back to the top of the infinite loop
Now to delve into each part in painful detail!
import RPi.GPIO as GPIO import sys import os import random import time from subprocess import Popen GPIO.setmode(GPIO.BCM) GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_UP)
The “import” statements provide access to all the peripheral files and functions I’ll need, such as the ability to read GPIO pins. I also needed the “random” and “time” libraries, which were not required for ThothLoki’s code.
I set up two buttons, GPIO17 as the button to play a video, and GPIO24 as the escape button, which I never actually implemented, but it did no harm. I should say though that I left GPIO24 floating, which is generally bad practice, but since the GPIO.setup call initiated the pull up, it is OK this time. The single button that I used is connected between GPIO17 and ground (GND).
As an extra precaution, I added a small (1Kohm) resistor between the switch and ground. The internal pull-up makes this optional, but in case I somehow messed up the software configuration of the internal pull-up on GPIO17, this small resistor will limit the current drawn from the GPIO and save my RPi from a sad and unnecessary death.
OK, now we have everything set up and configured. That was ridiculously easy!
scarymovie1 = ("/home/pi/Videos/Scary1.mp4") scarymovie2 = ("/home/pi/Videos/Scary2.mp4") scarymovie3 = ("/home/pi/Videos/Scary3.mp4") … scarymovie20 = ("/home/pi/Videos/Scary20.mp4") happymovie1 = ("/home/pi/Videos/Happy1.mp4") happymovie2 = ("/home/pi/Videos/Happy2.mp4") … happymovie14 = ("/home/pi/Videos/Happy14.mp4") screensaver = ("/home/pi/Videos/Screensaver.mp4")
This part is just giving a friendlier name to the individual clips that I will play. For simplicity I called all the happy/fluffy clips “HappyX.mp4” (where X is the clip number) and all the scary clips “ScaryX.mp4” and the screensaver, “Screensaver.mp4”. I could have actually hard coded the filename right into the video calls, but this made it a bit cleaner for me.
One big improvement could be to skip all of this and modify the video player call to just catenate the clip number (X) to the file name in the video player call. However, lack of time and software ability made this an upgrade that was defeatured.
Now, all the video clips can be called by the nice, short names. One thing to note is that Raspbian is case sensitive. “Screensaver.mp4” is NOT the same as “screensaver.mp4” – you need to be case correct for everything.
Adding video clips:
I also want to show just how easy it is to add a video clip. I had cobbled together four or five pretty basic clips, but Robyt graciously volunteered to rip the rest from YouTube videos collect a bunch more for me from totally legal sources. In order to add them, all I need to do is:
- Copy them to the correct directory and rename them to the proper format (Happyx.mp4 or Scaryx.mp4)
- Change the following 5 lines of code:
scarymovie20 = ("/home/pi/Videos/Scary20.mp4") maxnumber = 34 if movienum == 2: os.system('killall omxplayer.bin') #This is unaltered omxc = Popen(['omxplayer', '-b', happymovie1]) time.sleep(10)
Instead of explaining what I am doing in each one, I just highlighted the portions to be changed in BOLD. I am sure that what needs to happen is obvious enough to not need explanation.
A little more set up:
input_state1 = True quit_video = True player = False maxnumber = 34 random.seed() omxc = Popen(['omxplayer', '-b', screensaver])
Going through these lines individually:
- input_state1 = True – This variable is used as the last state the button (GPIO17) was in. “True” means that the GPIO is reading as a 1, so the button is NOT pressed. When the button is pressed, the GPIO will go low (0) and thus Input_state1 would be “False”.
- quit_video = True – Similar to input_state1, this is the state of GPIO24 – which is the quit button. It needs to be set to “True” to avoid immediately stopping the script.
- player = False – I actually never use this, it’s a holdover from ThothLoki’s code and can be removed.
- maxnumber = 34 – this is the total number of video clips I have, excluding the screensaver.
- random.seed() – this is a call for the seed for the random number generator.
Creating TRULY random numbers in computers is actually pretty tough. A seed is used to initialize the random number generator (RNG) – different seeds will give a different set of “random” numbers. While each call for a new number will look pretty random, every time you started the script you would get the same sequence of numbers unless you changed the seed. Since I don’t really care much about randomness I can get away without providing a proper seed value (mine is blank) because I will turn on the game once and it will stay on for its entire life (one party). But, if you really care about randomness, you will need to generate seeds using a dynamic parameter, such as the noise on a GPIO pin or the current UNIX timestamp.
- omxc = Popen([‘omxplayer’, ‘-b’, screensaver]) – This is the call to start the video player.
I will explain this more later, but for now, take on faith that this starts the screensaver playing. Without this, you would see the Raspbian desktop screen and no one would know what this is or what to do. As in “Why is there a computer with a single button set up on this stand at a party?” Note that this is outside the main loop, so it only gets called once.
A short segue about the screensaver video. Its purpose has been explained (to hide the desktop) but I also used it to explain what the game was and how to play it. I wanted it to entice people to try the game. You can make this be whatever you want of course. I made a few simple, scrolling text segments in a video editor and then kept copying and pasting them one after another in the project. In the end, my screensaver was almost 2 hours long and really only was 4 video sequences mixed up and repeated in different orders. I made it so long because once it was done playing, the screen would be blank until the button was pressed. There are ways to avoid this, but it would have taken more time to code; this was simpler.
while True: #Read states of inputs input_state1 = GPIO.input(17) quit_video = GPIO.input(24) movienum = random.randint(1,maxnumber)
Finally! We are into the main loop. The “while” loop is what is executed endlessly.
First, let’s read the state of GPIO17 (the button) – it will be low (False) only when it’s being pressed. That now becomes the value of the variable input_state1. We also read the quit/stop button (GPIO24) in case it’s been pressed and we need to stop the game.
Last, we get a random number and assign it to the variable movienum. This is an integer between 1 and the total number of clips we have (maxnumber) – which we set up at the top. So, each time we come through this loop we will pick a different movie to play, regardless of whether either button is pressed.
#If GPIO(17) is shorted to ground, play scary movie if input_state1 == False: if movienum == 1: os.system('killall omxplayer.bin') omxc = Popen(['omxplayer', '-b', scarymovie1]) time.sleep(6) if movienum == 2: os.system('killall omxplayer.bin') omxc = Popen(['omxplayer', '-b', happymovie1]) time.sleep(10)
Next, we actually use the state of the button and act if it IS pressed. When it’s pressed input_state1 will be False and we will enter the loop. If it’s not pressed, it will simply jump around all of this and fall to the bottom (WAY down there!) to the elif (equivalent to “else if”) and check to see if GPIO24 (the stop/quit button) is pressed. If it’s pressed, it will stop the screensaver playing. This lets you get back to the desktop to do whatever you need. Note that while this appears to end the game, you are STILL in the “while” loop, so pressing the button (GPIO17) will start a new video playing, and the game will have restarted. Since this is just a means to interrupt the script without having to pull the power on the RPi, it is fine for my means.
Looking a bit more at the repeated loops, this is where the work really is done.
if movienum == 2: os.system('killall omxplayer.bin') omxc = Popen(['omxplayer', '-b', happymovie1]) time.sleep(10)
Since input_state1 is False (GPIO17 is pressed), we need to play a movie. We have already chosen a random number and assigned it to movienum. For this example, let’s say movienum is 2, but all the calls do the same thing, just for a different videoclip.
- os.system(‘killall omxplayer.bin’) – This stops the video that is being played, which is the screensaver. If you exclude this line, you will actually play the clips on top of each other.
- omxc = Popen([‘omxplayer’, ‘-b’, happymovie1]) – This is the call to play the clip. It plays the video in full screen. Having the friendly names here just makes it more readable, but you could use the video clip’s path if you prefer. Note that the Popen command starts the video clip playing and immediately returns (it doesn’t wait for the video to finish playing).
- time.sleep(10) – The reason for this is probably not obvious. As I mentioned, Popen returns as soon as the video has STARTED playing. In order to avoid playing multiple movies on top of each other, I actually need Popen to NOT return until the video is done. So, this is simply a delay until the video is done. In this example “10” is the number of seconds to wait. This is the length of the particular video clip, plus 1 second. I added the extra second since time.sleep only works in increments of 1 second and none of my clips ended exactly at the end of some number of seconds. This needs to be edited manually for each clip.
Image from Pinterest
The only thing left if this little bit of code hiding right before the elif:
if True: os.system('killall omxplayer.bin') omxc = Popen(['omxplayer', '-b', screensaver])
This looks pretty similar to video clip call code, except it starts strangely. “If True” is meaningless, it will ALWAYS happen and I could have got rid of that one line, but for readability I used it so the other two lines stand out from the previous lines. This is just to restart the screensaver once the video clip has played to completion. It looks much like the code above, except I do NOT add the time.sleep function since I want to immediately jump back to the top of the “while” loop and start looking for another button press. I also could have removed the middle killall command, but I put it in, just in case…
I have already explained the last couple of lines. So that means…
Yep, that’s it. To me, it’s still amazing that about an hour of work for someone who had never used a RPi or scripted with Python could get this little game working. The open source community who made this possible is just unbelievable! There is no way I could have done this without all the incredible work they did – and never asked a penny for.
While this game is very simple and unlikely to be directly used by anyone else, it is so simple to take these concepts and with very minor tweaks to the logic make a $30 video player for whatever function you need. Oh, and a button, you do need a button too. Or maybe you need five buttons… You’ll have to come back next week to find out why might want five buttons.