├── 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 |
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 | --------------------------------------------------------------------------------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