├── .gitignore ├── 1sec.mp3 ├── Circuit diagram.fzz ├── Circuit diagram_bb.png ├── LICENSE ├── README.md ├── auth_web.py ├── beep.wav ├── example_creds.py ├── hello.mp3 ├── initd_alexa.sh ├── main.py ├── requirements.txt └── setup.sh /.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 | -------------------------------------------------------------------------------- /1sec.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-pi/AlexaPiDEPRECATED/41c9cb5712ada427f385a6ea084e88678f2a820e/1sec.mp3 -------------------------------------------------------------------------------- /Circuit diagram.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-pi/AlexaPiDEPRECATED/41c9cb5712ada427f385a6ea084e88678f2a820e/Circuit diagram.fzz -------------------------------------------------------------------------------- /Circuit diagram_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-pi/AlexaPiDEPRECATED/41c9cb5712ada427f385a6ea084e88678f2a820e/Circuit diagram_bb.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sam Machin 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlexaPi 2 | 3 | ## Use the new AlexaPi https://github.com/alexa-pi/AlexaPi instead. 4 | 5 | This project is now deprecated in favor of the new AlexaPi (https://github.com/alexa-pi/AlexaPi) which has all the features of this project and much more, such as: 6 | - support for a wide range of devices and platforms (Raspberry Pi, CHIP, Orange Pi, desktops, Magic Mirror and others) 7 | - voice activation (great PocketSphinx support and snowboy support soon to be released) 8 | - much more robust installation and configuration 9 | - better security 10 | - Arch Linux support 11 | - awesome documentation 12 | - and much, much more! You get it - it's just awesome. 13 | 14 | Please switch over to the new project completely. Thank you. 15 | 16 | 17 | --- 18 | --- 19 | --- 20 | --- 21 | --- 22 | --- 23 | --- 24 | --- 25 | 26 | ## Original README: 27 | 28 | ## Contributors 29 | - Sam Machin 30 | 31 | --- 32 | This is the code needed to Turn a Raspberry Pi into a client for Amazon's Alexa service. Feedback welcome. 33 | --- 34 | 35 | ### Requirements 36 | 37 | You will need: 38 | * A Raspberry Pi 39 | * An SD Card with a fresh install of Raspbian (tested against build 2015-11-21 Jessie) 40 | * An External Speaker with 3.5mm Jack 41 | * A USB Sound Dongle and Microphone 42 | * A push to make button connected between GPIO 18 and GND 43 | * (Optionally) A Dual colour LED (or 2 single LEDs) Connected to GPIO 24 & 25 44 | 45 | 46 | Next you need to obtain a set of credentials from Amazon to use the Alexa Voice service, login at http://developer.amazon.com and Goto Alexa then Alexa Voice Service 47 | You need to create a new product type as a Device, for the ID use something like AlexaPi, create a new security profile and under the web settings allowed origins put http://localhost:5000 and as a return URL put http://localhost:5000/code you can also create URLs replacing localhost with the IP of your Pi eg http://192.168.1.123:5000 48 | Make a note of these credentials you will be asked for them during the install process 49 | 50 | ### Installation 51 | 52 | Boot your fresh Pi and login to a command prompt as root. 53 | 54 | Make sure you are in /root 55 | 56 | Clone this repo to the Pi 57 | `git clone https://github.com/sammachin/AlexaPi.git` 58 | Run the setup script 59 | `./setup.sh` 60 | 61 | Follow instructions.... 62 | 63 | Enjoy :) 64 | 65 | ### Issues/Bugs etc. 66 | 67 | If your alexa isn't running on startup you can check /var/log/alexa.log for errors. 68 | 69 | If the error is complaining about alsaaudio you may need to check the name of your sound card input device, use 70 | `arecord -L` 71 | The device name can be set in the settings at the top of main.py 72 | 73 | You may need to adjust the volume and/or input gain for the microphone, you can do this with 74 | `alsamixer` 75 | 76 | ### Advanced Install 77 | 78 | For those of you that prefer to install the code manually or tweak things here's a few pointers... 79 | 80 | The Amazon AVS credentials are stored in a file called creds.py which is used by auth_web.py and main.py, there is an example with blank values. 81 | 82 | The auth_web.py is a simple web server to generate the refresh token via oAuth to the amazon users account, it then appends this to creds.py and displays it on the browser. 83 | 84 | main.py is the 'main' alexa client it simply runs on a while True loop waiting for the button to be pressed, it then records audio and when the button is released it posts this to the AVS service using the requests library, When the response comes back it is played back using mpg123 via an os system call, The 1sec.mp3 file is a 1second silent MP3) I found that my soundcard/pi was clipping the beginning of audio files and i was missing the first bit of the response so this is there to pad the audio. 85 | 86 | The LED's are a visual indicator of status, I used a duel Red/Green LED but you could also use separate LEDS, Red is connected to GPIO 24 and green to GPIO 25, When recording the RED LED will be lit when the file is being posted and waiting for the response both LED's are lit (or in the case of a dual R?G LED it goes Yellow) and when the response is played only the Green LED is lit. If The client gets an error back from AVS then the Red LED will flash 3 times. 87 | 88 | The internet_on() routine is testing the connection to the Amazon auth server as I found that running the script on boot it was failing due to the network not being fully established so this will keep it retrying until it can make contact before getting the auth token. 89 | 90 | The auth token is generated from the request_token the auth_token is then stored in a local memcache with and expiry of just under an hour to align with the validity at Amazon, if the function fails to get an access_token from memcache it will then request a new one from Amazon using the refresh token. 91 | -------------------------------------------------------------------------------- /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(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 | -------------------------------------------------------------------------------- /beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-pi/AlexaPiDEPRECATED/41c9cb5712ada427f385a6ea084e88678f2a820e/beep.wav -------------------------------------------------------------------------------- /example_creds.py: -------------------------------------------------------------------------------- 1 | # RENAME THIS FILE TO creds.py before use! 2 | import os 3 | 4 | #Alexa Settings 5 | ProductID = "" 6 | Security_Profile_Description ="" 7 | Security_Profile_ID = "" 8 | Client_ID = "" 9 | Client_Secret = "" 10 | 11 | # Alexa Refresh Token 12 | refresh_token = '' -------------------------------------------------------------------------------- /hello.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-pi/AlexaPiDEPRECATED/41c9cb5712ada427f385a6ea084e88678f2a820e/hello.mp3 -------------------------------------------------------------------------------- /initd_alexa.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: AlexaPi 5 | # Required-Start: $all 6 | # Required-Stop: $all 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: AlexaPi Service 10 | # Description: Start / Stop AlexaPi Service 11 | ### END INIT INFO 12 | 13 | exec > /var/log/alexa.log 2>&1 14 | case "$1" in 15 | 16 | start) 17 | echo "Starting Alexa..." 18 | python /root/AlexaPi/main.py & 19 | 20 | ;; 21 | 22 | stop) 23 | echo "Stopping Alexa.." 24 | pkill -f AlexaPi\/main\.py 25 | ;; 26 | 27 | restart|force-reload) 28 | echo "Restarting Alexa.." 29 | $0 stop 30 | sleep 2 31 | $0 start 32 | echo "Restarted." 33 | ;; 34 | *) 35 | echo "Usage: $0 {start|stop|restart}" 36 | exit 1 37 | esac 38 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os 4 | import random 5 | import time 6 | import RPi.GPIO as GPIO 7 | import alsaaudio 8 | import wave 9 | import random 10 | from creds import * 11 | import requests 12 | import json 13 | import re 14 | from memcache import Client 15 | 16 | #Settings 17 | button = 18 #GPIO Pin with button connected 18 | lights = [24, 25] # GPIO Pins with LED's conneted 19 | device = "plughw:1" # Name of your microphone/soundcard in arecord -L 20 | 21 | #Setup 22 | recorded = False 23 | servers = ["127.0.0.1:11211"] 24 | mc = Client(servers, debug=1) 25 | path = os.path.realpath(__file__).rstrip(os.path.basename(__file__)) 26 | 27 | 28 | 29 | def internet_on(): 30 | print "Checking Internet Connection" 31 | try: 32 | r =requests.get('https://api.amazon.com/auth/o2/token') 33 | print "Connection OK" 34 | return True 35 | except: 36 | print "Connection Failed" 37 | return False 38 | 39 | 40 | def gettoken(): 41 | token = mc.get("access_token") 42 | refresh = refresh_token 43 | if token: 44 | return token 45 | elif refresh: 46 | payload = {"client_id" : Client_ID, "client_secret" : Client_Secret, "refresh_token" : refresh, "grant_type" : "refresh_token", } 47 | url = "https://api.amazon.com/auth/o2/token" 48 | r = requests.post(url, data = payload) 49 | resp = json.loads(r.text) 50 | mc.set("access_token", resp['access_token'], 3570) 51 | return resp['access_token'] 52 | else: 53 | return False 54 | 55 | 56 | def alexa(): 57 | GPIO.output(lights[0], GPIO.HIGH) 58 | url = 'https://access-alexa-na.amazon.com/v1/avs/speechrecognizer/recognize' 59 | headers = {'Authorization' : 'Bearer %s' % gettoken()} 60 | d = { 61 | "messageHeader": { 62 | "deviceContext": [ 63 | { 64 | "name": "playbackState", 65 | "namespace": "AudioPlayer", 66 | "payload": { 67 | "streamId": "", 68 | "offsetInMilliseconds": "0", 69 | "playerActivity": "IDLE" 70 | } 71 | } 72 | ] 73 | }, 74 | "messageBody": { 75 | "profile": "alexa-close-talk", 76 | "locale": "en-us", 77 | "format": "audio/L16; rate=16000; channels=1" 78 | } 79 | } 80 | with open(path+'recording.wav') as inf: 81 | files = [ 82 | ('file', ('request', json.dumps(d), 'application/json; charset=UTF-8')), 83 | ('file', ('audio', inf, 'audio/L16; rate=16000; channels=1')) 84 | ] 85 | r = requests.post(url, headers=headers, files=files) 86 | if r.status_code == 200: 87 | for v in r.headers['content-type'].split(";"): 88 | if re.match('.*boundary.*', v): 89 | boundary = v.split("=")[1] 90 | data = r.content.split(boundary) 91 | for d in data: 92 | if (len(d) >= 1024): 93 | audio = d.split('\r\n\r\n')[1].rstrip('--') 94 | with open(path+"response.mp3", 'wb') as f: 95 | f.write(audio) 96 | GPIO.output(lights[1], GPIO.LOW) 97 | 98 | os.system('mpg123 -q {}1sec.mp3 {}response.mp3 {}1sec.mp3'.format(path, path, path)) 99 | GPIO.output(lights[0], GPIO.LOW) 100 | else: 101 | GPIO.output(lights[1], GPIO.LOW) 102 | for x in range(0, 3): 103 | time.sleep(.2) 104 | GPIO.output(lights[1], GPIO.HIGH) 105 | time.sleep(.2) 106 | GPIO.output(lights[1], GPIO.LOW) 107 | 108 | 109 | 110 | 111 | def start(): 112 | last = GPIO.input(button) 113 | while True: 114 | val = GPIO.input(button) 115 | GPIO.wait_for_edge(button, GPIO.FALLING) # we wait for the button to be pressed 116 | GPIO.output(lights[1], GPIO.HIGH) 117 | inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, device) 118 | inp.setchannels(1) 119 | inp.setrate(16000) 120 | inp.setformat(alsaaudio.PCM_FORMAT_S16_LE) 121 | inp.setperiodsize(500) 122 | audio = "" 123 | while(GPIO.input(button)==0): # we keep recording while the button is pressed 124 | l, data = inp.read() 125 | if l: 126 | audio += data 127 | rf = open(path+'recording.wav', 'w') 128 | rf.write(audio) 129 | rf.close() 130 | inp = None 131 | alexa() 132 | 133 | 134 | 135 | if __name__ == "__main__": 136 | GPIO.setwarnings(False) 137 | GPIO.cleanup() 138 | GPIO.setmode(GPIO.BCM) 139 | GPIO.setup(button, GPIO.IN, pull_up_down=GPIO.PUD_UP) 140 | GPIO.setup(lights, GPIO.OUT) 141 | GPIO.output(lights, GPIO.LOW) 142 | while internet_on() == False: 143 | print "." 144 | token = gettoken() 145 | os.system('mpg123 -q {}1sec.mp3 {}hello.mp3'.format(path, path)) 146 | for x in range(0, 3): 147 | time.sleep(.1) 148 | GPIO.output(lights[0], GPIO.HIGH) 149 | time.sleep(.1) 150 | GPIO.output(lights[0], GPIO.LOW) 151 | start() 152 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | apt-get update 4 | apt-get install libasound2-dev memcached mpg123 python-alsaaudio -y 5 | easy_install pip 6 | pip install -r requirements.txt 7 | cp initd_alexa.sh /etc/init.d/AlexaPi 8 | update-rc.d AlexaPi defaults 9 | touch /var/log/alexa.log 10 | 11 | echo "Enter your Device Type ID :" 12 | read productid 13 | echo ProductID = \"$productid\" >> creds.py 14 | 15 | echo "Enter your Security Profile Description:" 16 | read spd 17 | echo Security_Profile_Description = \"$spd\" >> creds.py 18 | 19 | echo "Enter your Security Profile ID:" 20 | read spid 21 | echo Security_Profile_ID = \"$spid\" >> creds.py 22 | 23 | echo "Enter your Security Client ID:" 24 | read cid 25 | echo Client_ID = \"$cid\" >> creds.py 26 | 27 | echo "Enter your Security Client Secret:" 28 | read secret 29 | echo Client_Secret = \"$secret\" >> creds.py 30 | 31 | ip=`hostname -I` 32 | python ./auth_web.py 33 | echo "Open http://$ip:5000" 34 | 35 | echo "You can now reboot" 36 | 37 | --------------------------------------------------------------------------------