├── hello.mp3 ├── requirements.txt ├── reset_alexa.sh ├── initd_alexa.sh ├── example_creds.py ├── .gitignore ├── LICENSE ├── setup.sh ├── auth_web.py ├── alexa_helper.py ├── main.py └── README.md /hello.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-raspberry-pi-guy/Artificial-Intelligence-Pi/HEAD/hello.mp3 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Wave>=0.0.2 2 | python-memcached>=1.50 3 | requests>=2.4.3 4 | wsgiref>=0.1.2 5 | CherryPy>=4.0.0 6 | 7 | -------------------------------------------------------------------------------- /reset_alexa.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Script to remove the credentials from this directory, therefore resetting Alexa 4 | # If you want to change the Amazon Alexa Voice Service device you are using, please re-run setup.sh (sudo ./setup.sh) and fill out the necessary NEW credentials, as if setting up from scratch 5 | # Created by Matthew Timmons-Brown, The Raspberry Pi Guy 6 | 7 | # To run this script: sudo sh reset_alexa.sh 8 | 9 | rm creds.py, creds.pyc 10 | -------------------------------------------------------------------------------- /initd_alexa.sh: -------------------------------------------------------------------------------- 1 | #You can use this script to start Alexa on boot 2 | 3 | #! /bin/bash 4 | 5 | exec > /var/log/alexa.log 2>&1 6 | case "$1" in 7 | 8 | start) 9 | echo "Starting Alexa..." 10 | python /root/AlexaPi/main.py & 11 | 12 | ;; 13 | 14 | stop) 15 | echo "Stopping Alexa.." 16 | pkill -SIGINT ^main.py$ 17 | ;; 18 | 19 | restart|force-reload) 20 | echo "Restarting Alexa.." 21 | $0 stop 22 | sleep 2 23 | $0 start 24 | echo "Restarted." 25 | ;; 26 | *) 27 | echo "Usage: $0 {start|stop|restart}" 28 | exit 1 29 | esac 30 | -------------------------------------------------------------------------------- /example_creds.py: -------------------------------------------------------------------------------- 1 | # This is just an EXAMPLE file. You do not need to look at this unless you are having difficulties 2 | # A creds.py file should be automatically created by setup.sh when run and the user fills in their details from Amazon 3 | # To remove the creds.py (and its compiled companion) currently in place, run reset_alexa.sh 4 | 5 | # RENAME THIS FILE TO creds.py before use! 6 | import os 7 | 8 | #Alexa Settings 9 | ProductID = "" 10 | Security_Profile_Description ="" 11 | Security_Profile_ID = "" 12 | Client_ID = "" 13 | Client_Secret = "" 14 | 15 | # Alexa Refresh Token 16 | refresh_token = '' 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sam Machin, Simon Beal and Matthew Timmons-Brown (The Raspberry Pi Guy) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | baselocation=$PWD 3 | apt-get update 4 | apt-get -y install libasound2-dev memcached python-pip mpg123 python-alsaaudio python-aubio 5 | pip install -r requirements.txt 6 | apt-get -y install python-dev python-pip gcc # Editions added by Raspberry Pi Guy to work with SenseHAT 7 | pip install evdev 8 | #cp initd_alexa.sh /etc/init.d/alexa #This part of the script did attempt to run Alexa on startup 9 | #cd /etc/rc5.d #Undesired for my tutorial - but if you want to run Alexa on boot then feel free to change! 10 | #ln -s ../init.d/alexa S99alexa 11 | #touch /var/log/alexa.log 12 | cd $baselocation 13 | echo "Enter your ProductID:" 14 | read productid 15 | echo ProductID = \"$productid\" >> creds.py 16 | 17 | echo "Enter your Security Profile Description:" 18 | read spd 19 | echo Security_Profile_Description = \"$spd\" >> creds.py 20 | 21 | echo "Enter your Security Profile ID:" 22 | read spid 23 | echo Security_Profile_ID = \"$spid\" >> creds.py 24 | 25 | echo "Enter your Security Client ID:" 26 | read cid 27 | echo Client_ID = \"$cid\" >> creds.py 28 | 29 | echo "Enter your Security Client Secret:" 30 | read secret 31 | echo Client_Secret = \"$secret\" >> creds.py 32 | 33 | ip=$(ifconfig eth0 | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1) 34 | echo "Open http://$ip:5000" 35 | python ./auth_web.py 36 | 37 | amixer cset numid=3 1 # This sets the audio to the 3.5mm audio jack, Alexa will talk to any 3.5mm speaker. If you want Alexa to talk over HDMI then change the number 1 to a 2 here. 38 | -------------------------------------------------------------------------------- /auth_web.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import cherrypy 4 | import os 5 | from cherrypy.process import servers 6 | import requests 7 | import json 8 | from creds import * 9 | import urllib 10 | 11 | class Start(object): 12 | def index(self): 13 | scope="alexa_all" 14 | sd = json.dumps({ 15 | "alexa:all": { 16 | "productID": ProductID, 17 | "productInstanceAttributes": { 18 | "deviceSerialNumber": "001" 19 | } 20 | } 21 | }) 22 | url = "https://www.amazon.com/ap/oa" 23 | callback = cherrypy.url() + "code" 24 | payload = {"client_id" : Client_ID, "scope" : "alexa:all", "scope_data" : sd, "response_type" : "code", "redirect_uri" : callback } 25 | req = requests.Request('GET', url, params=payload) 26 | p = req.prepare() 27 | raise cherrypy.HTTPRedirect(p.url) 28 | def code(self, var=None, **params): 29 | code = urllib.quote(cherrypy.request.params['code']) 30 | callback = cherrypy.url() 31 | payload = {"client_id" : Client_ID, "client_secret" : Client_Secret, "code" : code, "grant_type" : "authorization_code", "redirect_uri" : callback } 32 | url = "https://api.amazon.com/auth/o2/token" 33 | r = requests.post(url, data = payload) 34 | resp = r.json() 35 | line = refresh_token = '{}'.format(resp['refresh_token']) 36 | with open("creds.py", 'a') as f: 37 | f.write('refresh_token = "%s"' % line) 38 | return "Success!, refresh token has been added to your creds file, you may now reboot the Pi
{}".format(resp['refresh_token']) 39 | index.exposed = True 40 | code.exposed = True 41 | 42 | cherrypy.config.update({'server.socket_host': '0.0.0.0',}) 43 | cherrypy.config.update({'server.socket_port': int(os.environ.get('PORT', '5000')),}) 44 | cherrypy.quickstart(Start()) 45 | 46 | -------------------------------------------------------------------------------- /alexa_helper.py: -------------------------------------------------------------------------------- 1 | # Alexa Personal Assistant Companion Program for Raspberry Pi 2 | # Modified by Simon Beal and Matthew Timmons-Brown for "The Raspberry Pi Guy" YouTube channel 3 | # Built upon the work of Sam Machin, (c)2016 4 | # This is a library that includes all of the web functionality of the Alexa Amazon Echo personal assistant service 5 | # The code here was originally in main.py, but has been abstracted for ease of use (you should not need to change it) 6 | 7 | #! /usr/bin/env python 8 | 9 | import os 10 | import random 11 | import time 12 | import alsaaudio 13 | import wave 14 | import random 15 | from creds import * 16 | import requests 17 | import json 18 | import re 19 | from memcache import Client 20 | 21 | #Settings 22 | device = "plughw:1" # Name of your microphone/soundcard in "arecord -L" 23 | # Is your Amazon Echo clone not working? Perhaps the microphone is not connected properly or is not found at plughw:1 24 | # Check and then modify this variable. 25 | 26 | #Setup - details for Amazon server 27 | recorded = False 28 | servers = ["127.0.0.1:11211"] 29 | mc = Client(servers, debug=1) 30 | path = os.path.realpath(__file__).rstrip(os.path.basename(__file__)) 31 | 32 | 33 | # Check whether your Raspberry Pi is connected to the internet 34 | def internet_on(): 35 | print "Checking Internet Connection" 36 | try: 37 | r =requests.get('https://api.amazon.com/auth/o2/token') 38 | print "All systems GO" 39 | return True 40 | except: 41 | print "Connection Failed" 42 | return False 43 | 44 | # Sends access token to Amazon - value sent is unique to each device - we do not advise you to share it 45 | def gettoken(): 46 | token = mc.get("access_token") 47 | refresh = refresh_token 48 | if token: 49 | return token 50 | elif refresh: 51 | payload = {"client_id" : Client_ID, "client_secret" : Client_Secret, "refresh_token" : refresh, "grant_type" : "refresh_token", } 52 | url = "https://api.amazon.com/auth/o2/token" 53 | r = requests.post(url, data = payload) 54 | resp = json.loads(r.text) 55 | mc.set("access_token", resp['access_token'], 3570) 56 | return resp['access_token'] 57 | else: 58 | return False 59 | 60 | # Send the contents of "recording.wav" to Amazon's Alexa voice service 61 | def alexa(sense): 62 | url = 'https://access-alexa-na.amazon.com/v1/avs/speechrecognizer/recognize' 63 | headers = {'Authorization' : 'Bearer %s' % gettoken()} 64 | d = { 65 | "messageHeader": { 66 | "deviceContext": [ 67 | { 68 | "name": "playbackState", 69 | "namespace": "AudioPlayer", 70 | "payload": { 71 | "streamId": "", 72 | "offsetInMilliseconds": "0", 73 | "playerActivity": "IDLE" 74 | } 75 | } 76 | ] 77 | }, 78 | "messageBody": { 79 | "profile": "alexa-close-talk", 80 | "locale": "en-us", 81 | "format": "audio/S16; rate=16000; channels=1" 82 | } 83 | } 84 | with open(path+'recording.wav') as inf: 85 | files = [ 86 | ('file', ('request', json.dumps(d), 'application/json; charset=UTF-8')), 87 | ('file', ('audio', inf, 'audio/S16; rate=16000; channels=1')) 88 | ] 89 | r = requests.post(url, headers=headers, files=files) 90 | if r.status_code == 200: 91 | for v in r.headers['content-type'].split(";"): 92 | if re.match('.*boundary.*', v): 93 | boundary = v.split("=")[1] 94 | data = r.content.split(boundary) 95 | for d in data: 96 | if (len(d) >= 1024): 97 | audio = d.split('\r\n\r\n')[1].rstrip('--') 98 | with open(path+"response.mp3", 'wb') as f: 99 | f.write(audio) 100 | sense.show_letter("!") 101 | os.system('mpg123 -q {}response.mp3'.format(path, path)) # Writing response and playing response back to user 102 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # Alexa Personal Assitant for Raspberry Pi 2 | # Coded by Simon Beal and Matthew Timmons-Brown for "The Raspberry Pi Guy" YouTube channel 3 | # Built upon the work of Sam Machin, (c)2016 4 | # Feel free to look through the code, try to understand it & modify as you wish! 5 | # The installer MUST be run before this code. 6 | 7 | #!/usr/bin/python 8 | import sys 9 | import time 10 | from sense_hat import SenseHat 11 | import os 12 | import alsaaudio 13 | import wave 14 | import numpy 15 | import copy 16 | from evdev import InputDevice, list_devices, ecodes 17 | 18 | import alexa_helper # Import the web functions of Alexa, held in a separate program in this directory 19 | 20 | print "Welcome to Alexa. I will help you in anyway I can.\n Press Ctrl-C to quit" 21 | 22 | sense = SenseHat() # Initialise the SenseHAT 23 | sense.clear() # Blank the LED matrix 24 | 25 | # Search for the SenseHAT joystick 26 | found = False 27 | devices = [InputDevice(fn) for fn in list_devices()] 28 | for dev in devices: 29 | if dev.name == 'Raspberry Pi Sense HAT Joystick': 30 | found = True 31 | break 32 | 33 | # Exit if SenseHAT not found 34 | if not(found): 35 | print('Raspberry Pi Sense HAT Joystick not found. Aborting ...') 36 | sys.exit() 37 | 38 | # Initialise audio buffer 39 | audio = "" 40 | inp = None 41 | 42 | # We're British and we spell "colour" correctly :) Colour code for RAINBOWZ!! 43 | colours = [[255, 0, 0], [255, 0, 0], [255, 105, 0], [255, 223, 0], [170, 255, 0], [52, 255, 0], [0, 255, 66], [0, 255, 183]] 44 | 45 | # Loudness for highest bar of RGB display 46 | max_loud = 1024 47 | 48 | # Given a "loudness" of speech, convert into RGB LED bars and display - equaliser style 49 | def set_display(loudness): 50 | mini = [[0,0,0]]*8 51 | brightness = max(1, min(loudness, max_loud) / (max_loud/8)) 52 | mini[8-brightness:] = colours[8-brightness:] 53 | display = sum([[col]*8 for col in mini], []) 54 | sense.set_pixels(display) 55 | 56 | # When button is released, audio recording finishes and sent to Amazon's Alexa service 57 | def release_button(): 58 | global audio, inp 59 | sense.set_pixels([[0,0,0]]*64) 60 | w = wave.open(path+'recording.wav', 'w') # This and following lines saves voice to .wav file 61 | w.setnchannels(1) 62 | w.setsampwidth(2) 63 | w.setframerate(16000) 64 | w.writeframes(audio) 65 | w.close() 66 | sense.show_letter("?") # Convert to question mark on display 67 | alexa_helper.alexa(sense) # Call upon alexa_helper program (in this directory) 68 | sense.clear() # Clear display 69 | inp = None 70 | audio = "" 71 | 72 | # When button is pressed, start recording 73 | def press_button(): 74 | global audio, inp 75 | try: 76 | inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, alexa_helper.device) 77 | except alsaaudio.ALSAAudioError: 78 | print('Audio device not found - is your microphone connected? Please rerun program') 79 | sys.exit() 80 | inp.setchannels(1) 81 | inp.setrate(16000) 82 | inp.setformat(alsaaudio.PCM_FORMAT_S16_LE) 83 | inp.setperiodsize(1024) 84 | audio = "" 85 | l, data = inp.read() 86 | if l: 87 | audio += data 88 | 89 | # Whilst button is being pressed, continue recording and set "loudness" 90 | def continue_pressed(): 91 | global audio, inp 92 | l, data = inp.read() 93 | if l: 94 | audio += data 95 | a = numpy.fromstring(data, dtype='int16') # Converts audio data to a list of integers 96 | loudness = int(numpy.abs(a).mean()) # Loudness is mean of amplitude of sound wave - average "loudness" 97 | set_display(loudness) # Set the display to show this "loudness" 98 | 99 | # Event handler for button 100 | def handle_enter(pressed): 101 | handlers = [release_button, press_button, continue_pressed] # 0=released, 1=pressed, 2=held 102 | handlers[pressed]() 103 | 104 | # Continually loops for events, if event detected and is the middle joystick button, call upon event handler above 105 | def event_loop(): 106 | try: 107 | for event in dev.read_loop(): # for each event 108 | if event.type == ecodes.EV_KEY and event.code == ecodes.KEY_ENTER: # if event is a key and is the enter key (middle joystick) 109 | handle_enter(event.value) # handle event 110 | except KeyboardInterrupt: # If Ctrl+C pressed, pass back to main body - which then finishes and alerts the user the program has ended 111 | pass 112 | 113 | if __name__ == "__main__": # Run when program is called (won't run if you decide to import this program) 114 | while alexa_helper.internet_on() == False: 115 | print "." 116 | token = alexa_helper.gettoken() 117 | path = os.path.realpath(__file__).rstrip(os.path.basename(__file__)) 118 | os.system('mpg123 -q {}hello.mp3'.format(path, path)) # Say hello! 119 | event_loop() 120 | print "\nYou have exited Alexa. I hope that I was useful. To talk to me again just type: python main.py" 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Artifical Intelligence Pi 2 | ### Turn your Raspberry Pi into your own personal assistant using the Amazon Echo Alexa voice service! 3 | 4 | This repository accompanies my tutorial. This is a complete guide on setting up Alexa for your Raspberry Pi. I have adapated the code to use the fantastic Raspberry Pi SenseHAT for input and also RGB graphics! You can watch the full tutorial here: https://www.youtube.com/watch?v=tcI8ibjUOzg 5 | 6 | Here is an example of one of the things you can ask your new Raspberry Pi personal assistant: 7 | 8 |

Just spent a lot of time hacking my @Raspberry_Pi and SenseHAT to turn it into my own personal assistant! @HAL9000_ pic.twitter.com/2jO4PEqW8Y

— The Raspberry Pi Guy (@RaspberryPiGuy1) May 30, 2016
9 | 10 | 11 | ##Information 12 | You will need: 13 | * A Raspberry Pi connected to the internet (my tutorial uses the Raspberry Pi 3, but it should work with every model compatible with the Raspberry Pi SenseHAT - B+,A+,Pi 2,Pi 3,Pi Zero) 14 | * An SD Card with a fresh install of Raspbian (tested with 2016-05 Raspbian) 15 | * An External Speaker with 3.5mm Jack (like this one: https://www.amazon.co.uk/XMI-Generation-Capsule-Compatible-Smartphones/dp/B001UEBN42/ref=sr_1_3?ie=UTF8&qid=1464643924&sr=8-3&keywords=mini+speaker) 16 | * A USB Microphone (ideally make sure it is a plug n play one! I used this inexpensive one in the video: https://www.amazon.co.uk/Tonor-Professional-Condenser-Microphone-Computer/dp/B01142EPO4/ref=sr_1_3?ie=UTF8&qid=1464644011&sr=8-3&keywords=usb+microphone) 17 | * A Raspberry Pi Official SenseHAT add-on (https://thepihut.com/products/raspberry-pi-sense-hat-astro-pi) 18 | 19 | ### Installation 20 | 21 | For all instructions and processes, see my tutorial. Included below are the commands needed to type in to the console: 22 | 23 | Display your Raspberry Pi's IP address: ifconfig 24 | 25 | Download the code: git clone https://github.com/the-raspberry-pi-guy/Artificial-Intelligence-Pi 26 | 27 | Change into the new directory: cd Artificial-Intelligence-Pi 28 | 29 | Run the setup script: sudo ./setup.sh 30 | 31 | Run Alexa: python main.py 32 | 33 | ### FAQ & Troubleshooting 34 | 35 | This section will be added to as people report their troubles and ask questions. 36 | 37 | - *'x,y,z' didn't work and an error was generated! What do I do?* 38 | 39 | 9/10 times this kind of error is down to accidental mistakes. As I try to stress in the video, check and check again that the commands and information you enter in the process are correct. If you come across a nasty error, just try again - it normally fixes things. 40 | 41 | - *How do I make Alexa's sound output work over HDMI?* 42 | 43 | My tutorial illustrates Alexa talking over the Pi's 3.5mm audio output to a small speaker. Whilst this is most likely what you will use, some people may want Alexa to use the HDMI audio on the Pi. This is very easy to change. Simply use the command: sudo raspi-config and then adjust the audio output device. More information is available on the Raspberry Pi Foundation's official website: https://www.raspberrypi.org/documentation/configuration/audio-config.md 44 | 45 | - *Can Alexa speak 'x' language?* 46 | 47 | To the best of my knowledge, Alexa is only available in English (US) at the moment. Amazon has over 1000 employees working on Alexa however - so this may change in the future. 48 | 49 | - *Can this be used to control 'x' on a Raspberry Pi?* 50 | 51 | As of current, Alexa cannot be customised to control things directly on your Raspberry Pi, such as turn GPIO pins on and off. The Alexa Voice Service does all of its processing in the cloud - none of it happens locally on the Pi. If you would like to learn a little bit more about what AVS can do, read this article: http://fieldguide.gizmodo.com/everything-you-can-say-to-your-amazon-echo-1762166130 52 | 53 | - *Do I have to use a SenseHAT to use Alexa?* 54 | 55 | The answer to this question is no, you do not need a SenseHAT to use Alexa. You do however need a SenseHAT to follow my tutorial. This is because I have edited the program to work with SenseHAT. I did this because, in my opinion, the SenseHAT is a fantastic add-on that makes the task of activating Alexa much more simple. If you take a look at the GitHub repo that I forked from, the code there triggers Alexa by using a button attached to the GPIO pins. As per Amazon's T&Cs you can NOT activate the service using your voice. 56 | 57 | - *Can I use earphones/headphones instead of a speaker?* 58 | 59 | Yes. Any 3.5mm audio device should work and that includes headphones/earphones. 60 | 61 | - *I have a different microphone to the one that you used in the tutorial, how can I ensure that it works?* 62 | 63 | There is no easy answer to this question - you will just have to try it! As I said in the tutorial, plug 'n' play microphones are ideal for this as you don't have to fiddle around with drivers. 64 | 65 | - *Alexa thinks that I live in Seattle! How do I change that?* 66 | 67 | Amazon Echo is an American product and consequently Alexa will most likely think you are in Seattle. Whilst I have not tried it, I believe there is a companion app that allows you to change the location of your device. This may not be available in every country however. Alternatively, end your commands with your location. For example: "What is the weather like in Cambridge, UK?" 68 | 69 | ### Thank You! 70 | 71 | The work in this repository is based off of the work of Novaspirit and Sammachin. I would also like to thank my good friend Simon Beal (muddyfish) for his help in getting this up and going! 72 | 73 | Thanks for watching, 74 | 75 | Matthew Timmons-Brown 76 | 77 | The Raspberry Pi Guy 78 | 79 | www.youtube.com/theraspberrypiguy 80 | 81 | www.theraspberrypiguy.com 82 | 83 | @RaspberryPiGuy1 84 | --------------------------------------------------------------------------------