├── intent ├── package.json ├── intents │ ├── Default Welcome Intent.json │ ├── Default Fallback Intent.json │ ├── Default Welcome Intent_usersays_es.json │ ├── RSVPHack.json │ └── RSVPHack_usersays_es.json └── agent.json ├── Helloworld ├── package.json ├── intents │ ├── Default Fallback Intent.json │ ├── Default Welcome Intent.json │ └── Default Welcome Intent_usersays_es.json └── agent.json ├── customentity ├── package.json ├── entities │ ├── firts-time.json │ ├── type-of-hacker.json │ ├── firts-time_entries_es.json │ └── type-of-hacker_entries_es.json ├── intents │ ├── Default Welcome Intent.json │ ├── Default Fallback Intent.json │ ├── Default Welcome Intent_usersays_es.json │ ├── RSVPHack.json │ └── RSVPHack_usersays_es.json └── agent.json ├── rpi-assistant-demo ├── googlesamples │ ├── assistant │ │ ├── library │ │ │ ├── __init__.py │ │ │ ├── requirements.txt │ │ │ ├── README.rst │ │ │ └── hotword.py │ │ ├── grpc │ │ │ ├── __pycache__ │ │ │ │ ├── audio_helpers.cpython-37.pyc │ │ │ │ ├── device_helpers.cpython-37.pyc │ │ │ │ ├── assistant_helpers.cpython-37.pyc │ │ │ │ └── browser_helpers.cpython-37.pyc │ │ │ ├── requirements.txt │ │ │ ├── __init__.py │ │ │ ├── browser_helpers.py │ │ │ ├── assistant_helpers.py │ │ │ ├── device_helpers.py │ │ │ ├── README.rst │ │ │ ├── audiofileinput.py │ │ │ ├── textinput.py │ │ │ ├── audio_helpers.py │ │ │ ├── devicetool.py │ │ │ └── pushtotalk.py │ │ └── __init__.py │ └── __init__.py ├── setup.cfg ├── MANIFEST.in ├── tests │ ├── data │ │ ├── turnon.riff │ │ ├── grapefruit.riff │ │ └── whattimeisit.riff │ ├── test_devicetool.py │ ├── test_device_helpers.py │ ├── test_audio_helpers.py │ └── test_endtoend.py ├── MAINTAINER.md ├── nox.py ├── CHANGELOG.rst ├── actions.json ├── setup.py ├── README.rst └── LICENSE └── responsivebasiccard ├── package.json ├── entities ├── firts-time.json ├── type-of-hacker.json ├── firts-time_entries_es.json └── type-of-hacker_entries_es.json ├── intents ├── Default Fallback Intent.json ├── Default Welcome Intent.json ├── Default Welcome Intent_usersays_es.json ├── RSVPHack_usersays_es.json └── RSVPHack.json └── agent.json /intent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0" 3 | } -------------------------------------------------------------------------------- /Helloworld/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0" 3 | } -------------------------------------------------------------------------------- /customentity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0" 3 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/library/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /responsivebasiccard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0" 3 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [flake8] 5 | show-source = 1 6 | -------------------------------------------------------------------------------- /rpi-assistant-demo/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst CHANGELOG.rst LICENSE 2 | recursive-include tests * 3 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/library/requirements.txt: -------------------------------------------------------------------------------- 1 | google-assistant-library==1.0.1 2 | argparse>=1.4.0,<2 3 | google-auth>=1.0.1,<2 4 | -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/data/turnon.riff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/tests/data/turnon.riff -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/data/grapefruit.riff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/tests/data/grapefruit.riff -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/data/whattimeisit.riff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/tests/data/whattimeisit.riff -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/audio_helpers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/audio_helpers.cpython-37.pyc -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/device_helpers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/device_helpers.cpython-37.pyc -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/assistant_helpers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/assistant_helpers.cpython-37.pyc -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/browser_helpers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurelabmx/localhostgoogleassistant/master/rpi-assistant-demo/googlesamples/assistant/grpc/__pycache__/browser_helpers.cpython-37.pyc -------------------------------------------------------------------------------- /customentity/entities/firts-time.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0f9a5196-26f4-4d34-b918-293c45f9e1a9", 3 | "name": "firts-time", 4 | "isOverridable": true, 5 | "isEnum": false, 6 | "isRegexp": false, 7 | "automatedExpansion": false, 8 | "allowFuzzyExtraction": false 9 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/requirements.txt: -------------------------------------------------------------------------------- 1 | google-assistant-grpc==0.2.1 2 | google-auth-oauthlib>=0.1.0,<0.3 3 | urllib3[secure]>=1.21,<2 4 | sounddevice>=0.3.7,<0.4 5 | click>=6.7,<7 6 | tenacity>=4.1.0,<5 7 | futures>=3.1.1,<4 8 | pathlib2>=2.3.0,<3 9 | -------------------------------------------------------------------------------- /customentity/entities/type-of-hacker.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "a731ae1f-7bf6-4310-b19c-62094c8c0e90", 3 | "name": "type-of-hacker", 4 | "isOverridable": true, 5 | "isEnum": false, 6 | "isRegexp": false, 7 | "automatedExpansion": false, 8 | "allowFuzzyExtraction": false 9 | } -------------------------------------------------------------------------------- /responsivebasiccard/entities/firts-time.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0f9a5196-26f4-4d34-b918-293c45f9e1a9", 3 | "name": "firts-time", 4 | "isOverridable": true, 5 | "isEnum": false, 6 | "isRegexp": false, 7 | "automatedExpansion": false, 8 | "allowFuzzyExtraction": false 9 | } -------------------------------------------------------------------------------- /responsivebasiccard/entities/type-of-hacker.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "a731ae1f-7bf6-4310-b19c-62094c8c0e90", 3 | "name": "type-of-hacker", 4 | "isOverridable": true, 5 | "isEnum": false, 6 | "isRegexp": false, 7 | "automatedExpansion": false, 8 | "allowFuzzyExtraction": false 9 | } -------------------------------------------------------------------------------- /customentity/entities/firts-time_entries_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": "No", 4 | "synonyms": [ 5 | "No", 6 | "na", 7 | "nah", 8 | "nel", 9 | "nop" 10 | ] 11 | }, 12 | { 13 | "value": "Sí", 14 | "synonyms": [ 15 | "Claro", 16 | "Sí", 17 | "simon", 18 | "sip", 19 | "yes" 20 | ] 21 | } 22 | ] -------------------------------------------------------------------------------- /responsivebasiccard/entities/firts-time_entries_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": "No", 4 | "synonyms": [ 5 | "No", 6 | "na", 7 | "nah", 8 | "nel", 9 | "nop" 10 | ] 11 | }, 12 | { 13 | "value": "Sí", 14 | "synonyms": [ 15 | "Claro", 16 | "Sí", 17 | "simon", 18 | "sip", 19 | "yes" 20 | ] 21 | } 22 | ] -------------------------------------------------------------------------------- /rpi-assistant-demo/MAINTAINER.md: -------------------------------------------------------------------------------- 1 | Maintainer guide 2 | ================ 3 | 4 | ## Prerequisites 5 | 6 | - Install automation tools 7 | 8 | pip install --upgrade nox-automation 9 | 10 | ## Tasks 11 | 12 | - Install package with local modifications 13 | 14 | pip install -e .[samples] 15 | 16 | - Run lint tool 17 | 18 | nox -s lint 19 | 20 | - Run unit tests 21 | 22 | nox -s unittest 23 | 24 | - Create a new `google_assistant_sdk` release in `dist` 25 | 26 | nox -s release 27 | -------------------------------------------------------------------------------- /customentity/entities/type-of-hacker_entries_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": "Devs", 4 | "synonyms": [ 5 | "Codigo", 6 | "Desarrollo", 7 | "Development", 8 | "Devs", 9 | "Programacion", 10 | "Software" 11 | ] 12 | }, 13 | { 14 | "value": "Maker", 15 | "synonyms": [ 16 | "Arduino", 17 | "Circuitos", 18 | "Electromecanica", 19 | "Electronica", 20 | "Maker", 21 | "Mecatronica" 22 | ] 23 | }, 24 | { 25 | "value": "Negocios", 26 | "synonyms": [ 27 | "Canvas", 28 | "Emprendimiento", 29 | "Ideas", 30 | "Modelo de negocios", 31 | "Negocios" 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /responsivebasiccard/entities/type-of-hacker_entries_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": "Devs", 4 | "synonyms": [ 5 | "Codigo", 6 | "Desarrollo", 7 | "Development", 8 | "Devs", 9 | "Programacion", 10 | "Software" 11 | ] 12 | }, 13 | { 14 | "value": "Maker", 15 | "synonyms": [ 16 | "Arduino", 17 | "Circuitos", 18 | "Electromecanica", 19 | "Electronica", 20 | "Maker", 21 | "Mecatronica" 22 | ] 23 | }, 24 | { 25 | "value": "Negocios", 26 | "synonyms": [ 27 | "Canvas", 28 | "Emprendimiento", 29 | "Ideas", 30 | "Modelo de negocios", 31 | "Negocios" 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Samples for Google Assistant gRPC API.""" 16 | -------------------------------------------------------------------------------- /intent/intents/Default Welcome Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f1c122c4-b199-4d1c-a4e0-f5685f13146c", 3 | "name": "Default Welcome Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.welcome", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "¡Hola!", 18 | "¡Hey!", 19 | "¡Buenos días!" 20 | ] 21 | } 22 | ], 23 | "defaultResponsePlatforms": {}, 24 | "speech": [] 25 | } 26 | ], 27 | "priority": 500000, 28 | "webhookUsed": false, 29 | "webhookForSlotFilling": false, 30 | "fallbackIntent": false, 31 | "events": [ 32 | { 33 | "name": "WELCOME" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /customentity/intents/Default Welcome Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f1c122c4-b199-4d1c-a4e0-f5685f13146c", 3 | "name": "Default Welcome Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.welcome", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "¡Hola!", 18 | "¡Hey!", 19 | "¡Buenos días!" 20 | ] 21 | } 22 | ], 23 | "defaultResponsePlatforms": {}, 24 | "speech": [] 25 | } 26 | ], 27 | "priority": 500000, 28 | "webhookUsed": false, 29 | "webhookForSlotFilling": false, 30 | "fallbackIntent": false, 31 | "events": [ 32 | { 33 | "name": "WELCOME" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """googlesamples namespace package.""" 16 | 17 | try: 18 | import pkg_resources 19 | pkg_resources.declare_namespace(__name__) 20 | except ImportError: 21 | import pkgutil 22 | __path__ = pkgutil.extend_path(__path__, __name__) 23 | -------------------------------------------------------------------------------- /intent/intents/Default Fallback Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "9990a444-6328-4ba8-9c29-9f687781db42", 3 | "name": "Default Fallback Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.unknown", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "Ups, no he entendido a que te refieres.", 18 | "¿Podrías repetirlo, por favor?", 19 | "¿Disculpa?", 20 | "¿Decías?", 21 | "¿Cómo?" 22 | ] 23 | } 24 | ], 25 | "defaultResponsePlatforms": {}, 26 | "speech": [] 27 | } 28 | ], 29 | "priority": 500000, 30 | "webhookUsed": false, 31 | "webhookForSlotFilling": false, 32 | "fallbackIntent": true, 33 | "events": [] 34 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """googlesamples.assistant namespace package.""" 16 | 17 | try: 18 | import pkg_resources 19 | pkg_resources.declare_namespace(__name__) 20 | except ImportError: 21 | import pkgutil 22 | __path__ = pkgutil.extend_path(__path__, __name__) 23 | -------------------------------------------------------------------------------- /Helloworld/intents/Default Fallback Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "49b5c106-844b-4a89-a25b-59c51b91e809", 3 | "name": "Default Fallback Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.unknown", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "Ups, no he entendido a que te refieres.", 18 | "¿Podrías repetirlo, por favor?", 19 | "¿Disculpa?", 20 | "¿Decías?", 21 | "¿Cómo?" 22 | ] 23 | } 24 | ], 25 | "defaultResponsePlatforms": {}, 26 | "speech": [] 27 | } 28 | ], 29 | "priority": 500000, 30 | "webhookUsed": false, 31 | "webhookForSlotFilling": false, 32 | "fallbackIntent": true, 33 | "events": [] 34 | } -------------------------------------------------------------------------------- /customentity/intents/Default Fallback Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "9990a444-6328-4ba8-9c29-9f687781db42", 3 | "name": "Default Fallback Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.unknown", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "Ups, no he entendido a que te refieres.", 18 | "¿Podrías repetirlo, por favor?", 19 | "¿Disculpa?", 20 | "¿Decías?", 21 | "¿Cómo?" 22 | ] 23 | } 24 | ], 25 | "defaultResponsePlatforms": {}, 26 | "speech": [] 27 | } 28 | ], 29 | "priority": 500000, 30 | "webhookUsed": false, 31 | "webhookForSlotFilling": false, 32 | "fallbackIntent": true, 33 | "events": [] 34 | } -------------------------------------------------------------------------------- /responsivebasiccard/intents/Default Fallback Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "9990a444-6328-4ba8-9c29-9f687781db42", 3 | "name": "Default Fallback Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.unknown", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "Ups, no he entendido a que te refieres.", 18 | "¿Podrías repetirlo, por favor?", 19 | "¿Disculpa?", 20 | "¿Decías?", 21 | "¿Cómo?" 22 | ] 23 | } 24 | ], 25 | "defaultResponsePlatforms": {}, 26 | "speech": [] 27 | } 28 | ], 29 | "priority": 500000, 30 | "webhookUsed": false, 31 | "webhookForSlotFilling": false, 32 | "fallbackIntent": true, 33 | "events": [] 34 | } -------------------------------------------------------------------------------- /responsivebasiccard/intents/Default Welcome Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f1c122c4-b199-4d1c-a4e0-f5685f13146c", 3 | "name": "Default Welcome Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.welcome", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "¡Hola, soy Jarvis y te ayudare con el registro!", 18 | "¡Hey me llamo Jarvis y te voy a ayudar con tu registro!" 19 | ] 20 | } 21 | ], 22 | "defaultResponsePlatforms": {}, 23 | "speech": [] 24 | } 25 | ], 26 | "priority": 500000, 27 | "webhookUsed": false, 28 | "webhookForSlotFilling": false, 29 | "fallbackIntent": false, 30 | "events": [ 31 | { 32 | "name": "WELCOME" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /Helloworld/intents/Default Welcome Intent.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "aa03068e-d39c-44db-9eae-e19a4ccf47a7", 3 | "name": "Default Welcome Intent", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "action": "input.welcome", 10 | "affectedContexts": [], 11 | "parameters": [], 12 | "messages": [ 13 | { 14 | "type": 0, 15 | "lang": "es", 16 | "speech": [ 17 | "¡Hola mundo soy tu primer chatbot!", 18 | "¡Hey soy el primero!", 19 | "¡Buenos días se siente bien ser tu primer agente!" 20 | ] 21 | } 22 | ], 23 | "defaultResponsePlatforms": {}, 24 | "speech": [] 25 | } 26 | ], 27 | "priority": 500000, 28 | "webhookUsed": false, 29 | "webhookForSlotFilling": false, 30 | "fallbackIntent": false, 31 | "events": [ 32 | { 33 | "name": "WELCOME" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/browser_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os.path 16 | import tempfile 17 | import webbrowser 18 | 19 | ASSISTANT_HTML_FILE = 'google-assistant-sdk-screen-out.html' 20 | 21 | 22 | class SystemBrowser(object): 23 | def __init__(self): 24 | self.tempdir = tempfile.mkdtemp() 25 | self.filename = os.path.join(self.tempdir, ASSISTANT_HTML_FILE) 26 | 27 | def display(self, html): 28 | with open(self.filename, 'wb') as f: 29 | f.write(html) 30 | webbrowser.open(self.filename, new=0) 31 | 32 | 33 | system_browser = SystemBrowser() 34 | -------------------------------------------------------------------------------- /intent/intents/Default Welcome Intent_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "3572b644-9bae-4b23-afbf-f1f51f660440", 4 | "data": [ 5 | { 6 | "text": "hola", 7 | "userDefined": false 8 | } 9 | ], 10 | "isTemplate": false, 11 | "count": 0, 12 | "updated": 0 13 | }, 14 | { 15 | "id": "bc8e3699-4c15-437a-a4bf-1742cfe637e3", 16 | "data": [ 17 | { 18 | "text": "hey", 19 | "userDefined": false 20 | } 21 | ], 22 | "isTemplate": false, 23 | "count": 0, 24 | "updated": 0 25 | }, 26 | { 27 | "id": "6710f42c-ef85-41eb-8c2a-a06650b2bb5b", 28 | "data": [ 29 | { 30 | "text": "saludos", 31 | "userDefined": false 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "941f7097-0930-49d0-913e-c14c52564d74", 40 | "data": [ 41 | { 42 | "text": "hey ho", 43 | "userDefined": false 44 | } 45 | ], 46 | "isTemplate": false, 47 | "count": 0, 48 | "updated": 0 49 | }, 50 | { 51 | "id": "1f539538-a8b9-454b-8e40-bcbc537ad3c7", 52 | "data": [ 53 | { 54 | "text": "chao", 55 | "userDefined": false 56 | } 57 | ], 58 | "isTemplate": false, 59 | "count": 0, 60 | "updated": 0 61 | } 62 | ] -------------------------------------------------------------------------------- /customentity/intents/Default Welcome Intent_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "3572b644-9bae-4b23-afbf-f1f51f660440", 4 | "data": [ 5 | { 6 | "text": "hola", 7 | "userDefined": false 8 | } 9 | ], 10 | "isTemplate": false, 11 | "count": 0, 12 | "updated": 0 13 | }, 14 | { 15 | "id": "bc8e3699-4c15-437a-a4bf-1742cfe637e3", 16 | "data": [ 17 | { 18 | "text": "hey", 19 | "userDefined": false 20 | } 21 | ], 22 | "isTemplate": false, 23 | "count": 0, 24 | "updated": 0 25 | }, 26 | { 27 | "id": "6710f42c-ef85-41eb-8c2a-a06650b2bb5b", 28 | "data": [ 29 | { 30 | "text": "saludos", 31 | "userDefined": false 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "941f7097-0930-49d0-913e-c14c52564d74", 40 | "data": [ 41 | { 42 | "text": "hey ho", 43 | "userDefined": false 44 | } 45 | ], 46 | "isTemplate": false, 47 | "count": 0, 48 | "updated": 0 49 | }, 50 | { 51 | "id": "1f539538-a8b9-454b-8e40-bcbc537ad3c7", 52 | "data": [ 53 | { 54 | "text": "chao", 55 | "userDefined": false 56 | } 57 | ], 58 | "isTemplate": false, 59 | "count": 0, 60 | "updated": 0 61 | } 62 | ] -------------------------------------------------------------------------------- /intent/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "", 3 | "language": "es", 4 | "shortDescription": "", 5 | "examples": "", 6 | "linkToDocs": "", 7 | "disableInteractionLogs": false, 8 | "disableStackdriverLogs": true, 9 | "googleAssistant": { 10 | "googleAssistantCompatible": false, 11 | "project": "rsvpcf-aaamhk", 12 | "welcomeIntentSignInRequired": false, 13 | "startIntents": [], 14 | "systemIntents": [], 15 | "endIntentIds": [], 16 | "oAuthLinking": { 17 | "required": false, 18 | "providerId": "", 19 | "authorizationUrl": "", 20 | "tokenUrl": "", 21 | "scopes": "", 22 | "privacyPolicyUrl": "", 23 | "grantType": "AUTH_CODE_GRANT" 24 | }, 25 | "voiceType": "MALE_1", 26 | "capabilities": [], 27 | "env": "", 28 | "protocolVersion": "V2", 29 | "autoPreviewEnabled": false, 30 | "isDeviceAgent": false 31 | }, 32 | "defaultTimezone": "America/Chicago", 33 | "webhook": { 34 | "url": "", 35 | "username": "", 36 | "headers": {}, 37 | "available": false, 38 | "useForDomains": false, 39 | "cloudFunctionsEnabled": false, 40 | "cloudFunctionsInitialized": false 41 | }, 42 | "isPrivate": true, 43 | "customClassifierMode": "use.after", 44 | "mlMinConfidence": 0.3, 45 | "supportedLanguages": [], 46 | "onePlatformApiVersion": "v2", 47 | "analyzeQueryTextSentiment": false, 48 | "enabledKnowledgeBaseNames": [], 49 | "knowledgeServiceConfidenceAdjustment": -0.4, 50 | "dialogBuilderMode": false, 51 | "baseActionPackagesUrl": "" 52 | } -------------------------------------------------------------------------------- /customentity/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "", 3 | "language": "es", 4 | "shortDescription": "", 5 | "examples": "", 6 | "linkToDocs": "", 7 | "disableInteractionLogs": false, 8 | "disableStackdriverLogs": true, 9 | "googleAssistant": { 10 | "googleAssistantCompatible": false, 11 | "project": "rsvpcf-aaamhk", 12 | "welcomeIntentSignInRequired": false, 13 | "startIntents": [], 14 | "systemIntents": [], 15 | "endIntentIds": [], 16 | "oAuthLinking": { 17 | "required": false, 18 | "providerId": "", 19 | "authorizationUrl": "", 20 | "tokenUrl": "", 21 | "scopes": "", 22 | "privacyPolicyUrl": "", 23 | "grantType": "AUTH_CODE_GRANT" 24 | }, 25 | "voiceType": "MALE_1", 26 | "capabilities": [], 27 | "env": "", 28 | "protocolVersion": "V2", 29 | "autoPreviewEnabled": false, 30 | "isDeviceAgent": false 31 | }, 32 | "defaultTimezone": "America/Chicago", 33 | "webhook": { 34 | "url": "", 35 | "username": "", 36 | "headers": {}, 37 | "available": false, 38 | "useForDomains": false, 39 | "cloudFunctionsEnabled": false, 40 | "cloudFunctionsInitialized": false 41 | }, 42 | "isPrivate": true, 43 | "customClassifierMode": "use.after", 44 | "mlMinConfidence": 0.3, 45 | "supportedLanguages": [], 46 | "onePlatformApiVersion": "v2", 47 | "analyzeQueryTextSentiment": false, 48 | "enabledKnowledgeBaseNames": [], 49 | "knowledgeServiceConfidenceAdjustment": -0.4, 50 | "dialogBuilderMode": false, 51 | "baseActionPackagesUrl": "" 52 | } -------------------------------------------------------------------------------- /responsivebasiccard/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "", 3 | "language": "es", 4 | "shortDescription": "", 5 | "examples": "", 6 | "linkToDocs": "", 7 | "disableInteractionLogs": false, 8 | "disableStackdriverLogs": true, 9 | "googleAssistant": { 10 | "googleAssistantCompatible": true, 11 | "project": "rsvpcf-aaamhk", 12 | "welcomeIntentSignInRequired": false, 13 | "startIntents": [], 14 | "systemIntents": [], 15 | "endIntentIds": [], 16 | "oAuthLinking": { 17 | "required": false, 18 | "providerId": "", 19 | "authorizationUrl": "", 20 | "tokenUrl": "", 21 | "scopes": "", 22 | "privacyPolicyUrl": "", 23 | "grantType": "AUTH_CODE_GRANT" 24 | }, 25 | "voiceType": "MALE_1", 26 | "capabilities": [], 27 | "env": "", 28 | "protocolVersion": "V2", 29 | "autoPreviewEnabled": false, 30 | "isDeviceAgent": false 31 | }, 32 | "defaultTimezone": "America/Chicago", 33 | "webhook": { 34 | "url": "", 35 | "username": "", 36 | "headers": {}, 37 | "available": false, 38 | "useForDomains": false, 39 | "cloudFunctionsEnabled": false, 40 | "cloudFunctionsInitialized": false 41 | }, 42 | "isPrivate": true, 43 | "customClassifierMode": "use.after", 44 | "mlMinConfidence": 0.3, 45 | "supportedLanguages": [], 46 | "onePlatformApiVersion": "v2", 47 | "analyzeQueryTextSentiment": false, 48 | "enabledKnowledgeBaseNames": [], 49 | "knowledgeServiceConfidenceAdjustment": -0.4, 50 | "dialogBuilderMode": false, 51 | "baseActionPackagesUrl": "" 52 | } -------------------------------------------------------------------------------- /Helloworld/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "", 3 | "language": "es", 4 | "shortDescription": "", 5 | "examples": "", 6 | "linkToDocs": "", 7 | "disableInteractionLogs": false, 8 | "disableStackdriverLogs": true, 9 | "googleAssistant": { 10 | "googleAssistantCompatible": false, 11 | "project": "holamundoagente-mmqjyr", 12 | "welcomeIntentSignInRequired": false, 13 | "startIntents": [], 14 | "systemIntents": [], 15 | "endIntentIds": [], 16 | "oAuthLinking": { 17 | "required": false, 18 | "providerId": "", 19 | "authorizationUrl": "", 20 | "tokenUrl": "", 21 | "scopes": "", 22 | "privacyPolicyUrl": "", 23 | "grantType": "AUTH_CODE_GRANT" 24 | }, 25 | "voiceType": "MALE_1", 26 | "capabilities": [], 27 | "env": "", 28 | "protocolVersion": "V2", 29 | "autoPreviewEnabled": false, 30 | "isDeviceAgent": false 31 | }, 32 | "defaultTimezone": "America/Chicago", 33 | "webhook": { 34 | "url": "", 35 | "username": "", 36 | "headers": {}, 37 | "available": false, 38 | "useForDomains": false, 39 | "cloudFunctionsEnabled": false, 40 | "cloudFunctionsInitialized": false 41 | }, 42 | "isPrivate": true, 43 | "customClassifierMode": "use.after", 44 | "mlMinConfidence": 0.3, 45 | "supportedLanguages": [], 46 | "onePlatformApiVersion": "v2", 47 | "analyzeQueryTextSentiment": false, 48 | "enabledKnowledgeBaseNames": [], 49 | "knowledgeServiceConfidenceAdjustment": -0.4, 50 | "dialogBuilderMode": false, 51 | "baseActionPackagesUrl": "" 52 | } -------------------------------------------------------------------------------- /Helloworld/intents/Default Welcome Intent_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "6371395c-b063-4174-9028-816c7588808d", 4 | "data": [ 5 | { 6 | "text": "que onda", 7 | "userDefined": false 8 | } 9 | ], 10 | "isTemplate": false, 11 | "count": 0, 12 | "updated": 0 13 | }, 14 | { 15 | "id": "da973be0-c18f-48f2-90ad-f32ac068acd2", 16 | "data": [ 17 | { 18 | "text": "holi", 19 | "userDefined": false 20 | } 21 | ], 22 | "isTemplate": false, 23 | "count": 0, 24 | "updated": 0 25 | }, 26 | { 27 | "id": "1fb1d844-3c70-42bd-86f7-4af0d04ea200", 28 | "data": [ 29 | { 30 | "text": "hola", 31 | "userDefined": false 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "5c92e8e0-4874-444b-8193-290841d12a97", 40 | "data": [ 41 | { 42 | "text": "hey", 43 | "userDefined": false 44 | } 45 | ], 46 | "isTemplate": false, 47 | "count": 0, 48 | "updated": 0 49 | }, 50 | { 51 | "id": "97cc1534-3ec2-4194-8f87-18ed60b423fe", 52 | "data": [ 53 | { 54 | "text": "saludos", 55 | "userDefined": false 56 | } 57 | ], 58 | "isTemplate": false, 59 | "count": 0, 60 | "updated": 0 61 | }, 62 | { 63 | "id": "f7596825-9974-4321-9db9-1af86491cf08", 64 | "data": [ 65 | { 66 | "text": "hey ho", 67 | "userDefined": false 68 | } 69 | ], 70 | "isTemplate": false, 71 | "count": 0, 72 | "updated": 0 73 | }, 74 | { 75 | "id": "18ec253c-c818-4073-9adc-e8cf34946791", 76 | "data": [ 77 | { 78 | "text": "chao", 79 | "userDefined": false 80 | } 81 | ], 82 | "isTemplate": false, 83 | "count": 0, 84 | "updated": 0 85 | } 86 | ] -------------------------------------------------------------------------------- /responsivebasiccard/intents/Default Welcome Intent_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "77b365e4-df1c-4136-91bb-2fdf994d5c4b", 4 | "data": [ 5 | { 6 | "text": "que onda", 7 | "userDefined": false 8 | } 9 | ], 10 | "isTemplate": false, 11 | "count": 0, 12 | "updated": 0 13 | }, 14 | { 15 | "id": "917e51de-e2ae-47c7-9e6a-1bdaf0283150", 16 | "data": [ 17 | { 18 | "text": "holi", 19 | "userDefined": false 20 | } 21 | ], 22 | "isTemplate": false, 23 | "count": 0, 24 | "updated": 0 25 | }, 26 | { 27 | "id": "3572b644-9bae-4b23-afbf-f1f51f660440", 28 | "data": [ 29 | { 30 | "text": "hola", 31 | "userDefined": false 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "bc8e3699-4c15-437a-a4bf-1742cfe637e3", 40 | "data": [ 41 | { 42 | "text": "hey", 43 | "userDefined": false 44 | } 45 | ], 46 | "isTemplate": false, 47 | "count": 0, 48 | "updated": 0 49 | }, 50 | { 51 | "id": "6710f42c-ef85-41eb-8c2a-a06650b2bb5b", 52 | "data": [ 53 | { 54 | "text": "saludos", 55 | "userDefined": false 56 | } 57 | ], 58 | "isTemplate": false, 59 | "count": 0, 60 | "updated": 0 61 | }, 62 | { 63 | "id": "941f7097-0930-49d0-913e-c14c52564d74", 64 | "data": [ 65 | { 66 | "text": "hey ho", 67 | "userDefined": false 68 | } 69 | ], 70 | "isTemplate": false, 71 | "count": 0, 72 | "updated": 0 73 | }, 74 | { 75 | "id": "1f539538-a8b9-454b-8e40-bcbc537ad3c7", 76 | "data": [ 77 | { 78 | "text": "chao", 79 | "userDefined": false 80 | } 81 | ], 82 | "isTemplate": false, 83 | "count": 0, 84 | "updated": 0 85 | } 86 | ] -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/test_devicetool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from googlesamples.assistant.grpc import devicetool 17 | 18 | 19 | def test_print_model_with_no_trait(caplog): 20 | devicetool.pretty_print_model({ 21 | 'deviceModelId': 'model-id', 22 | 'projectId': 'project-id', 23 | 'deviceType': 'device-type' 24 | }) 25 | assert 'model-id' in caplog.text 26 | assert 'project-id' in caplog.text 27 | assert 'device-type' in caplog.text 28 | 29 | 30 | def test_build_api_url(): 31 | assert ('https://myhostname/myversion/projects/myproject' == 32 | devicetool.build_api_url('myhostname', 'myversion', 'myproject')) 33 | 34 | 35 | class Context(object): 36 | pass 37 | 38 | 39 | class Session(object): 40 | pass 41 | 42 | 43 | def test_build_client(): 44 | my_session = Session() 45 | my_context = Context() 46 | my_context.obj = { 47 | 'PROJECT_ID': 'myproject', 48 | 'API_ENDPOINT': 'myhostname', 49 | 'API_VERSION': 'myversion', 50 | 'SESSION': my_session 51 | } 52 | session, api_url, project = devicetool.build_client_from_context( 53 | my_context 54 | ) 55 | assert session is my_session 56 | assert project == 'myproject' 57 | assert 'myhostname' in api_url 58 | assert 'myversion' in api_url 59 | assert 'myproject' in api_url 60 | -------------------------------------------------------------------------------- /rpi-assistant-demo/nox.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import nox 17 | 18 | 19 | @nox.session 20 | def lint(session): 21 | session.interpreter = 'python3' 22 | session.install('pip', 'setuptools') 23 | session.install('docutils', 'flake8', 'readme_renderer') 24 | session.run('flake8', 25 | 'googlesamples', 'tests', 26 | 'nox.py', 'setup.py') 27 | session.run('python', 'setup.py', 'check', 28 | '--restructuredtext', '--strict') 29 | session.run('python', '-m', 'json.tool', 'actions.json') 30 | 31 | 32 | @nox.session 33 | @nox.parametrize('python_version', ['2.7', '3']) 34 | def unittest(session, python_version): 35 | session.interpreter = 'python' + python_version 36 | session.install('pip', 'setuptools') 37 | session.install('pytest', 'future') 38 | session.install('../google-assistant-grpc/') 39 | session.install('-e', '.[samples]') 40 | session.run('py.test', '-k', 'not test_endtoend', 'tests') 41 | 42 | 43 | @nox.session 44 | @nox.parametrize('python_version', ['2.7', '3']) 45 | def endtoend_test(session, python_version): 46 | session.interpreter = 'python' + python_version 47 | session.install('pip', 'setuptools') 48 | session.install('pytest', 'future') 49 | session.install('../google-assistant-grpc/') 50 | session.install('-e', '.[samples]') 51 | session.run('py.test', '-k', 'test_endtoend', 'tests') 52 | 53 | 54 | @nox.session 55 | def release(session): 56 | session.install('pip', 'setuptools', 'wheel') 57 | session.run('python', 'setup.py', 'sdist', 'bdist_wheel') 58 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/assistant_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Helper functions for the Google Assistant API.""" 16 | 17 | import logging 18 | 19 | from google.assistant.embedded.v1alpha2 import embedded_assistant_pb2 20 | 21 | 22 | def log_assist_request_without_audio(assist_request): 23 | """Log AssistRequest fields without audio data.""" 24 | if logging.getLogger().isEnabledFor(logging.DEBUG): 25 | resp_copy = embedded_assistant_pb2.AssistRequest() 26 | resp_copy.CopyFrom(assist_request) 27 | if len(resp_copy.audio_in) > 0: 28 | size = len(resp_copy.audio_in) 29 | resp_copy.ClearField('audio_in') 30 | logging.debug('AssistRequest: audio_in (%d bytes)', 31 | size) 32 | return 33 | logging.debug('AssistRequest: %s', resp_copy) 34 | 35 | 36 | def log_assist_response_without_audio(assist_response): 37 | """Log AssistResponse fields without audio data.""" 38 | if logging.getLogger().isEnabledFor(logging.DEBUG): 39 | resp_copy = embedded_assistant_pb2.AssistResponse() 40 | resp_copy.CopyFrom(assist_response) 41 | has_audio_data = (resp_copy.HasField('audio_out') and 42 | len(resp_copy.audio_out.audio_data) > 0) 43 | if has_audio_data: 44 | size = len(resp_copy.audio_out.audio_data) 45 | resp_copy.audio_out.ClearField('audio_data') 46 | if resp_copy.audio_out.ListFields(): 47 | logging.debug('AssistResponse: %s audio_data (%d bytes)', 48 | resp_copy, 49 | size) 50 | else: 51 | logging.debug('AssistResponse: audio_data (%d bytes)', 52 | size) 53 | return 54 | logging.debug('AssistResponse: %s', resp_copy) 55 | -------------------------------------------------------------------------------- /rpi-assistant-demo/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.5.1 5 | ----- 6 | - Fix Python generator termination. 7 | - Lint/Fix README.rst. 8 | - Make device action keys uppercase. 9 | - Add `query` and `nickname` flag to library sample. 10 | 11 | 12 | 0.5.0 13 | ----- 14 | - Add HTML output. 15 | - Add simpler file i/o sample. 16 | - Fix crash with empty audio query. 17 | 18 | 19 | 0.4.4 20 | ----- 21 | - Fix DeviceHandler initialization issue. 22 | - Add example action packages for custom device actions. 23 | - Better feedback on device registration. 24 | 25 | 26 | 0.4.3 27 | ----- 28 | - Fix Python 2.7/3.6 compatibility for ``pushtotalk`` and ``hotword`` sample. 29 | 30 | 31 | 0.4.2 32 | ----- 33 | - Add client type for ``pushtotalk`` registration. 34 | 35 | 36 | 0.4.1 37 | ----- 38 | - Update outdated ``hotword`` sample. 39 | 40 | 41 | 0.4.0 42 | ----- 43 | - Add Device actions handling to samples. 44 | - Update ``pushtotalk`` sample to ``v1alpha2`` of Google Assistant Service. 45 | - Add language selection to ``pushtotalk`` sample. 46 | - New ``textinput`` sample for the Google Assistant Service. 47 | - New ``devicetool`` tool for device registration. 48 | 49 | 50 | 0.3.3 51 | ----- 52 | - Update Google Assistant Library from 0.0.2 to 0.0.3 53 | 54 | 55 | 0.3.2 56 | ----- 57 | - Bump urllib3 dependency. 58 | 59 | 60 | 0.3.1 61 | ----- 62 | - Bump dependencies to use new ``google-assistant-grpc`` package (faster install). 63 | 64 | 65 | 0.3.0 66 | ----- 67 | - Move grpc bindings to the `google-assistant-grpc `_ package. 68 | - Moved reference grpc sample to ``googlesamples.assistant.grpc.pushtotalk`` with `updated instructions `_. 69 | - Replaced ``auth_helpers`` with ``google-oauthlibtool``: 70 | 71 | - Follow the `updated instructions `_ to generate and use new credentials. 72 | 73 | - Add ``--once`` flag to pushtotalk grpc sample (@r-clancy). 74 | - Fix typo in IFTTT handling in pushtotalk grpc sample (@kadeve). 75 | - Add ``google-assistant-library`` package `installation instructions `_ and `sample `_. 76 | 77 | 78 | 0.2.1 79 | ----- 80 | - Fix audio helpers. 81 | 82 | 83 | 0.2.0 84 | ----- 85 | - Add basic travis config. 86 | - Add retry logic. 87 | - Implement volume control. 88 | 89 | 90 | 0.1.0 91 | ----- 92 | - Initial release. 93 | -------------------------------------------------------------------------------- /intent/intents/RSVPHack.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "79c3ed2e-82e7-43fd-9ca7-1e49b394d48c", 3 | "name": "RSVPHack", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "affectedContexts": [], 10 | "parameters": [ 11 | { 12 | "id": "7e42b522-2ea6-415e-a65c-75126c1b0e5b", 13 | "required": true, 14 | "dataType": "@sys.given-name", 15 | "name": "given-name", 16 | "value": "$given-name", 17 | "prompts": [ 18 | { 19 | "lang": "es", 20 | "value": "Claro, para comenzar tu registro ¿Podrías proporcionarme tu nombre, por favor?" 21 | }, 22 | { 23 | "lang": "es", 24 | "value": "Muy bien, ¿Cuál es tú nombre?" 25 | } 26 | ], 27 | "promptMessages": [], 28 | "noMatchPromptMessages": [], 29 | "noInputPromptMessages": [], 30 | "outputDialogContexts": [], 31 | "isList": false 32 | }, 33 | { 34 | "id": "c0b64d32-5738-4c63-a4fc-5bf18275fcf9", 35 | "required": true, 36 | "dataType": "@sys.number", 37 | "name": "number", 38 | "value": "$number", 39 | "prompts": [ 40 | { 41 | "lang": "es", 42 | "value": "Muy bien entonces, ¿Cuántas personas hay en el equipo?" 43 | }, 44 | { 45 | "lang": "es", 46 | "value": "Ok, ¿ Cuántos integrantes tiene tu equipo?" 47 | } 48 | ], 49 | "promptMessages": [], 50 | "noMatchPromptMessages": [], 51 | "noInputPromptMessages": [], 52 | "outputDialogContexts": [], 53 | "isList": false 54 | }, 55 | { 56 | "id": "210b8055-b3fc-45b5-bda5-36550141b8c6", 57 | "required": true, 58 | "dataType": "@sys.email", 59 | "name": "email", 60 | "value": "$email", 61 | "prompts": [ 62 | { 63 | "lang": "es", 64 | "value": "Excelente, ¿ A que dirección de correo electrónico debería enviar tus boletos?" 65 | }, 66 | { 67 | "lang": "es", 68 | "value": "Gracias, ¿ Cuál es la dirección de correo a la que enviaré los boletos?" 69 | } 70 | ], 71 | "promptMessages": [], 72 | "noMatchPromptMessages": [], 73 | "noInputPromptMessages": [], 74 | "outputDialogContexts": [], 75 | "isList": false 76 | } 77 | ], 78 | "messages": [ 79 | { 80 | "type": 0, 81 | "lang": "es", 82 | "speech": "Gracias, tu registro se ha realizado con éxito. Nos vemos en el Hack" 83 | } 84 | ], 85 | "defaultResponsePlatforms": {}, 86 | "speech": [] 87 | } 88 | ], 89 | "priority": 500000, 90 | "webhookUsed": false, 91 | "webhookForSlotFilling": false, 92 | "fallbackIntent": false, 93 | "events": [] 94 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/actions.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": { 3 | "displayName": "Blinky light", 4 | "invocationName": "Blinky light", 5 | "category": "PRODUCTIVITY" 6 | }, 7 | "actions": [ 8 | { 9 | "name": "com.example.actions.BlinkLight", 10 | "availability": { 11 | "deviceClasses": [ 12 | { 13 | "assistantSdkDevice": {} 14 | } 15 | ] 16 | }, 17 | "intent": { 18 | "name": "com.example.intents.BlinkLight", 19 | "parameters": [ 20 | { 21 | "name": "number", 22 | "type": "SchemaOrg_Number" 23 | }, 24 | { 25 | "name": "speed", 26 | "type": "Speed" 27 | } 28 | ], 29 | "trigger": { 30 | "queryPatterns": [ 31 | "blink ($Speed:speed)? $SchemaOrg_Number:number times", 32 | "blink $SchemaOrg_Number:number times ($Speed:speed)?" 33 | ] 34 | } 35 | }, 36 | "fulfillment": { 37 | "staticFulfillment": { 38 | "templatedResponse": { 39 | "items": [ 40 | { 41 | "simpleResponse": { 42 | "textToSpeech": "Blinking $number times" 43 | } 44 | }, 45 | { 46 | "deviceExecution": { 47 | "command": "com.example.commands.BlinkLight", 48 | "params": { 49 | "speed": "$speed", 50 | "number": "$number" 51 | } 52 | } 53 | } 54 | ] 55 | } 56 | } 57 | } 58 | } 59 | ], 60 | "types": [ 61 | { 62 | "name": "$Speed", 63 | "entities": [ 64 | { 65 | "key": "SLOWLY", 66 | "synonyms": [ 67 | "slowly", 68 | "slow" 69 | ] 70 | }, 71 | { 72 | "key": "NORMALLY", 73 | "synonyms": [ 74 | "normally", 75 | "regular" 76 | ] 77 | }, 78 | { 79 | "key": "QUICKLY", 80 | "synonyms": [ 81 | "quickly", 82 | "fast", 83 | "quick" 84 | ] 85 | } 86 | ] 87 | } 88 | ] 89 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from setuptools import setup, find_packages 16 | 17 | import io 18 | import os.path 19 | 20 | install_requires = [ 21 | 'google-auth-oauthlib[tool]>=0.1.0' 22 | ] 23 | 24 | samples_packages = [ 25 | 'grpc' 26 | ] 27 | 28 | 29 | def samples_requirements(): 30 | for p in samples_packages: 31 | with io.open(os.path.join('googlesamples', 'assistant', p, 32 | 'requirements.txt')) as f: 33 | for p in f: 34 | yield p.strip() 35 | 36 | 37 | with io.open('README.rst', 'r') as fh: 38 | long_description = fh.read() 39 | 40 | setup( 41 | name='google-assistant-sdk', 42 | version='0.5.1', 43 | author='Google Assistant SDK team', 44 | author_email='proppy+assistant-sdk@google.com', 45 | description='Samples and Tools the Google Assistant SDK', 46 | long_description=long_description, 47 | url='https://github.com/googlesamples/assistant-sdk-python', 48 | packages=find_packages(exclude=['tests*']), 49 | namespace_packages=[ 50 | 'googlesamples', 51 | 'googlesamples.assistant', 52 | ], 53 | install_requires=install_requires, 54 | extras_require={ 55 | 'samples': list(samples_requirements()), 56 | }, 57 | entry_points={ 58 | 'console_scripts': [ 59 | 'googlesamples-assistant-audiotest' 60 | '=googlesamples.assistant.grpc.audio_helpers:main', 61 | 'googlesamples-assistant-devicetool' 62 | '=googlesamples.assistant.grpc.devicetool:main', 63 | 'googlesamples-assistant-pushtotalk' 64 | '=googlesamples.assistant.grpc.pushtotalk:main [samples]', 65 | 'googlesamples-assistant-hotword' 66 | '=googlesamples.assistant.library.hotword:main [samples]', 67 | ], 68 | }, 69 | license='Apache 2.0', 70 | keywords='google assistant api sdk sample', 71 | classifiers=( 72 | 'Programming Language :: Python :: 2', 73 | 'Programming Language :: Python :: 2.7', 74 | 'Programming Language :: Python :: 3', 75 | 'Programming Language :: Python :: 3.4', 76 | 'Programming Language :: Python :: 3.5', 77 | 'Development Status :: 4 - Beta', 78 | 'Intended Audience :: Developers', 79 | 'License :: OSI Approved :: Apache Software License', 80 | 'Operating System :: POSIX', 81 | 'Operating System :: Microsoft :: Windows', 82 | 'Operating System :: MacOS :: MacOS X', 83 | 'Operating System :: OS Independent', 84 | 'Topic :: Internet :: WWW/HTTP', 85 | ), 86 | ) 87 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/library/README.rst: -------------------------------------------------------------------------------- 1 | Python samples for the Google Assistant library 2 | =============================================== 3 | 4 | This repository contains a reference sample for the ``google-assistant-library`` Python package_. 5 | 6 | It demonstrates: 7 | - Initialization of the Assistant 8 | - Basic event handling including hotword detection. 9 | 10 | .. _package: https://github.com/googlesamples/assistant-sdk-python/tree/master/google-assistant-library 11 | 12 | Prerequisites 13 | ------------- 14 | 15 | - `Python `_ >= 2.7 16 | - SBC with ``linux-arm7l`` (eg: Rasbperry Pi 3) or ``linux-x86-64`` architecture. 17 | - An `Actions Console Project `_ 18 | - A `Google account `_ 19 | 20 | Setup 21 | ----- 22 | 23 | - Install Python 3 24 | 25 | - Ubuntu/Debian GNU/Linux:: 26 | 27 | sudo apt-get update 28 | sudo apt-get install python3 python3-venv 29 | 30 | - Create a new virtual environment (recommended):: 31 | 32 | python3 -m venv env 33 | env/bin/python -m pip install --upgrade pip setuptools wheel 34 | source env/bin/activate 35 | 36 | Authorization 37 | ------------- 38 | 39 | - Follow the steps to `configure the Actions Console project and the Google account `_. 40 | - Follow the steps to `register a new device model and download the client secrets file `_. 41 | - Generate device credentials using ``google-oauthlib-tool``: 42 | 43 | pip install --upgrade google-auth-oauthlib[tool] 44 | google-oauthlib-tool --client-secrets path/to/client_secret_.json --scope https://www.googleapis.com/auth/assistant-sdk-prototype --save --headless 45 | 46 | Run the sample 47 | -------------- 48 | 49 | - Install the sample dependencies using pip_:: 50 | 51 | pip install --upgrade -r requirements.txt 52 | 53 | .. _pip: https://pip.pypa.io/ 54 | .. _GitHub releases page: https://github.com/googlesamples/assistant-sdk-python/releases 55 | 56 | - Run the hotword sample. The sample waits for the "Ok Google" hotword, then records a voice query and plays back the Google Assistant's answer:: 57 | 58 | python -m hotword --device_model_id 'my-model-identifier' 59 | 60 | Troubleshooting 61 | --------------- 62 | 63 | - If audio is not working, verify the ALSA setup:: 64 | 65 | # Play a test sound 66 | speaker-test -t wav 67 | 68 | # Record and play back some audio using ALSA command-line tools 69 | arecord --format=S16_LE --duration=5 --rate=16000 --file-type=raw out.raw 70 | aplay --format=S16_LE --rate=16000 --file-type=raw out.raw 71 | 72 | See also the `troubleshooting section `_ of the official documentation. 73 | 74 | License 75 | ------- 76 | 77 | Copyright (C) 2017 Google Inc. 78 | 79 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 80 | license agreements. See the NOTICE file distributed with this work for 81 | additional information regarding copyright ownership. The ASF licenses this 82 | file to you under the Apache License, Version 2.0 (the "License"); you may not 83 | use this file except in compliance with the License. You may obtain a copy of 84 | the License at 85 | 86 | http://www.apache.org/licenses/LICENSE-2.0 87 | 88 | Unless required by applicable law or agreed to in writing, software 89 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 90 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 91 | License for the specific language governing permissions and limitations under 92 | the License. 93 | 94 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/device_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Helper functions for the Device Actions.""" 16 | 17 | import concurrent.futures 18 | import logging 19 | import sys 20 | 21 | 22 | key_inputs_ = 'inputs' 23 | key_intent_ = 'intent' 24 | key_payload_ = 'payload' 25 | key_commands_ = 'commands' 26 | key_id_ = 'id' 27 | 28 | 29 | class DeviceRequestHandler(object): 30 | """Asynchronous dispatcher for Device actions commands. 31 | 32 | Dispatch commands to the given device handlers. 33 | 34 | Args: 35 | device_id: device id to match command against 36 | 37 | Example: 38 | # Use as as decorator to register handler. 39 | device_handler = DeviceRequestHandler('my-device') 40 | @device_handler.command('INTENT_NAME') 41 | def handler(param): 42 | pass 43 | """ 44 | 45 | def __init__(self, device_id): 46 | self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) 47 | self.device_id = device_id 48 | self.handlers = {} 49 | 50 | def __call__(self, device_request): 51 | """Handle incoming device request. 52 | 53 | Returns: List of concurrent.futures for each command execution. 54 | """ 55 | fs = [] 56 | if key_inputs_ in device_request: 57 | for input in device_request[key_inputs_]: 58 | if input[key_intent_] == 'action.devices.EXECUTE': 59 | for command in input[key_payload_][key_commands_]: 60 | fs.extend(self.submit_commands(**command)) 61 | return fs 62 | 63 | def command(self, intent): 64 | """Register a device action handlers.""" 65 | def decorator(fn): 66 | self.handlers[intent] = fn 67 | return decorator 68 | 69 | def submit_commands(self, devices, execution): 70 | """Submit device command executions. 71 | 72 | Returns: a list of concurrent.futures for scheduled executions. 73 | """ 74 | fs = [] 75 | for device in devices: 76 | if device[key_id_] != self.device_id: 77 | logging.warning('Ignoring command for unknown device: %s' 78 | % device[key_id_]) 79 | continue 80 | if not execution: 81 | logging.warning('Ignoring noop execution') 82 | continue 83 | for command in execution: 84 | f = self.executor.submit( 85 | self.dispatch_command, **command 86 | ) 87 | fs.append(f) 88 | return fs 89 | 90 | def dispatch_command(self, command, params=None): 91 | """Dispatch device commands to the appropriate handler.""" 92 | try: 93 | if command in self.handlers: 94 | self.handlers[command](**params) 95 | else: 96 | logging.warning('Unsupported command: %s: %s', 97 | command, params) 98 | except Exception as e: 99 | logging.warning('Error during command execution', 100 | exc_info=sys.exc_info()) 101 | raise e 102 | -------------------------------------------------------------------------------- /intent/intents/RSVPHack_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ac0127cb-5899-4637-8d35-529b670c2554", 4 | "data": [ 5 | { 6 | "text": "a mi correo ", 7 | "userDefined": false 8 | }, 9 | { 10 | "text": "flab@flab.mx", 11 | "alias": "email", 12 | "meta": "@sys.email", 13 | "userDefined": true 14 | } 15 | ], 16 | "isTemplate": false, 17 | "count": 0, 18 | "updated": 0 19 | }, 20 | { 21 | "id": "3c8ba730-c6cb-451f-9cac-277a295e6cea", 22 | "data": [ 23 | { 24 | "text": "al correo ", 25 | "userDefined": false 26 | }, 27 | { 28 | "text": "flab@f.mx", 29 | "alias": "email", 30 | "meta": "@sys.email", 31 | "userDefined": true 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "6de872ad-53e8-4cb7-b224-1010863b03c7", 40 | "data": [ 41 | { 42 | "text": "Mi equipo tiene ", 43 | "userDefined": false 44 | }, 45 | { 46 | "text": "4", 47 | "alias": "number", 48 | "meta": "@sys.number", 49 | "userDefined": false 50 | }, 51 | { 52 | "text": " integrantes", 53 | "userDefined": false 54 | } 55 | ], 56 | "isTemplate": false, 57 | "count": 0, 58 | "updated": 0 59 | }, 60 | { 61 | "id": "dff1c0f8-8b14-4ff6-b845-70086f8a5e60", 62 | "data": [ 63 | { 64 | "text": "Somos ", 65 | "userDefined": false 66 | }, 67 | { 68 | "text": "5", 69 | "alias": "number", 70 | "meta": "@sys.number", 71 | "userDefined": false 72 | }, 73 | { 74 | "text": " personas", 75 | "userDefined": false 76 | } 77 | ], 78 | "isTemplate": false, 79 | "count": 0, 80 | "updated": 0 81 | }, 82 | { 83 | "id": "0fe986ca-efb3-4b59-bb97-6ad3f57ccf3b", 84 | "data": [ 85 | { 86 | "text": "Me llamo ", 87 | "userDefined": false 88 | }, 89 | { 90 | "text": "Francisco", 91 | "alias": "given-name", 92 | "meta": "@sys.given-name", 93 | "userDefined": true 94 | } 95 | ], 96 | "isTemplate": false, 97 | "count": 0, 98 | "updated": 0 99 | }, 100 | { 101 | "id": "2fd95cec-2c5e-47ce-aa9f-f04265e4d7d8", 102 | "data": [ 103 | { 104 | "text": "Mi nombre es ", 105 | "userDefined": false 106 | }, 107 | { 108 | "text": "Eduardo", 109 | "alias": "given-name", 110 | "meta": "@sys.given-name", 111 | "userDefined": true 112 | } 113 | ], 114 | "isTemplate": false, 115 | "count": 0, 116 | "updated": 0 117 | }, 118 | { 119 | "id": "0f23b971-19f2-4011-88ac-6f2454c20052", 120 | "data": [ 121 | { 122 | "text": "Quiero registrarme al evento", 123 | "userDefined": false 124 | } 125 | ], 126 | "isTemplate": false, 127 | "count": 0, 128 | "updated": 0 129 | }, 130 | { 131 | "id": "1c94cac7-20a3-4482-bccb-51f6fe8b026c", 132 | "data": [ 133 | { 134 | "text": "Quiero inscribirme al hack", 135 | "userDefined": false 136 | } 137 | ], 138 | "isTemplate": false, 139 | "count": 0, 140 | "updated": 0 141 | }, 142 | { 143 | "id": "00e25791-9731-4739-bb92-2ac6d0b23189", 144 | "data": [ 145 | { 146 | "text": "Hola, quiero ", 147 | "userDefined": false 148 | }, 149 | { 150 | "text": "un", 151 | "meta": "@sys.ignore", 152 | "userDefined": false 153 | }, 154 | { 155 | "text": " boleto para BeeHack", 156 | "userDefined": false 157 | } 158 | ], 159 | "isTemplate": false, 160 | "count": 0, 161 | "updated": 0 162 | } 163 | ] -------------------------------------------------------------------------------- /customentity/intents/RSVPHack.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "79c3ed2e-82e7-43fd-9ca7-1e49b394d48c", 3 | "name": "RSVPHack", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "affectedContexts": [], 10 | "parameters": [ 11 | { 12 | "id": "7e42b522-2ea6-415e-a65c-75126c1b0e5b", 13 | "required": true, 14 | "dataType": "@sys.given-name", 15 | "name": "given-name", 16 | "value": "$given-name", 17 | "prompts": [ 18 | { 19 | "lang": "es", 20 | "value": "Claro, para comenzar tu registro ¿Podrías proporcionarme tu nombre, por favor?" 21 | }, 22 | { 23 | "lang": "es", 24 | "value": "Muy bien, ¿Cuál es tú nombre?" 25 | } 26 | ], 27 | "promptMessages": [], 28 | "noMatchPromptMessages": [], 29 | "noInputPromptMessages": [], 30 | "outputDialogContexts": [], 31 | "isList": false 32 | }, 33 | { 34 | "id": "c0b64d32-5738-4c63-a4fc-5bf18275fcf9", 35 | "required": true, 36 | "dataType": "@sys.number", 37 | "name": "number", 38 | "value": "$number", 39 | "prompts": [ 40 | { 41 | "lang": "es", 42 | "value": "Muy bien entonces, ¿Cuántas personas hay en el equipo?" 43 | }, 44 | { 45 | "lang": "es", 46 | "value": "Ok, ¿ Cuántos integrantes tiene tu equipo?" 47 | } 48 | ], 49 | "promptMessages": [], 50 | "noMatchPromptMessages": [], 51 | "noInputPromptMessages": [], 52 | "outputDialogContexts": [], 53 | "isList": false 54 | }, 55 | { 56 | "id": "210b8055-b3fc-45b5-bda5-36550141b8c6", 57 | "required": true, 58 | "dataType": "@sys.email", 59 | "name": "email", 60 | "value": "$email", 61 | "prompts": [ 62 | { 63 | "lang": "es", 64 | "value": "Excelente, ¿ A que dirección de correo electrónico debería enviar tus boletos?" 65 | }, 66 | { 67 | "lang": "es", 68 | "value": "Gracias, ¿ Cuál es la dirección de correo a la que enviaré los boletos?" 69 | } 70 | ], 71 | "promptMessages": [], 72 | "noMatchPromptMessages": [], 73 | "noInputPromptMessages": [], 74 | "outputDialogContexts": [], 75 | "isList": false 76 | }, 77 | { 78 | "id": "1cb48e82-8904-4c5a-88b3-91abe304a816", 79 | "required": true, 80 | "dataType": "@type-of-hacker", 81 | "name": "type-of-hacker", 82 | "value": "$type-of-hacker", 83 | "prompts": [ 84 | { 85 | "lang": "es", 86 | "value": "¿Comó describirias a los integrantes de tu equipo?(Makers, Devs, Negocios)." 87 | } 88 | ], 89 | "promptMessages": [], 90 | "noMatchPromptMessages": [], 91 | "noInputPromptMessages": [], 92 | "outputDialogContexts": [], 93 | "isList": false 94 | }, 95 | { 96 | "id": "1b3d3659-3f29-4405-b782-dfac2b88f488", 97 | "required": true, 98 | "dataType": "@firts-time", 99 | "name": "firts-time", 100 | "value": "$firts-time", 101 | "prompts": [ 102 | { 103 | "lang": "es", 104 | "value": "Genial, ¿Es tu primer Hackaton?" 105 | }, 106 | { 107 | "lang": "es", 108 | "value": "Por último, ¿Has ido a algún Hackaton antes?" 109 | } 110 | ], 111 | "promptMessages": [], 112 | "noMatchPromptMessages": [], 113 | "noInputPromptMessages": [], 114 | "outputDialogContexts": [], 115 | "isList": false 116 | } 117 | ], 118 | "messages": [ 119 | { 120 | "type": 0, 121 | "lang": "es", 122 | "speech": "Gracias, tu registro se ha realizado con éxito. Nos vemos en el Hack" 123 | } 124 | ], 125 | "defaultResponsePlatforms": {}, 126 | "speech": [] 127 | } 128 | ], 129 | "priority": 500000, 130 | "webhookUsed": false, 131 | "webhookForSlotFilling": false, 132 | "fallbackIntent": false, 133 | "events": [] 134 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/test_device_helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import unittest 17 | import concurrent.futures 18 | 19 | from googlesamples.assistant.grpc import device_helpers 20 | 21 | 22 | def build_device_request(device_id, command, arg): 23 | return { 24 | 'inputs': [{ 25 | 'intent': 'action.devices.EXECUTE', 26 | 'payload': { 27 | 'commands': [{ 28 | 'devices': [ 29 | {'id': device_id} 30 | ], 31 | 'execution': [{ 32 | 'command': command, 33 | 'params': {'arg': arg} 34 | }] 35 | }] 36 | } 37 | }], 38 | 'requestId': '42' 39 | } 40 | 41 | 42 | def build_noop_device_request(device_id): 43 | return { 44 | 'inputs': [{ 45 | 'intent': 'action.devices.EXECUTE', 46 | 'payload': { 47 | 'commands': [{ 48 | 'devices': [ 49 | {'id': device_id} 50 | ], 51 | 'execution': None, 52 | }] 53 | } 54 | }], 55 | 'requestId': '42' 56 | } 57 | 58 | 59 | class DeviceRequestHandlerTest(unittest.TestCase): 60 | def setUp(self): 61 | self.handler_called = False 62 | 63 | def handler(self, arg): 64 | self.handler_called = arg 65 | 66 | def test_success(self): 67 | device_handler = device_helpers.DeviceRequestHandler( 68 | 'some-device', 69 | ) 70 | device_handler.command('SOME_COMMAND')(self.handler) 71 | device_request = build_device_request('some-device', 72 | 'SOME_COMMAND', 73 | 'some-arg') 74 | fs = device_handler(device_request) 75 | self.assertEqual(len(fs), 1) 76 | concurrent.futures.wait(fs) 77 | self.assertEqual(self.handler_called, 'some-arg') 78 | 79 | def test_different_device(self): 80 | device_handler = device_helpers.DeviceRequestHandler( 81 | 'some-device', 82 | ) 83 | device_handler.command('SOME_COMMAND')(self.handler) 84 | device_request = build_device_request('other-device', 85 | 'SOME_COMMAND', 86 | 'some-arg') 87 | fs = device_handler(device_request) 88 | self.assertEqual(len(fs), 0) 89 | self.assertFalse(self.handler_called) 90 | 91 | def test_unknown_command(self): 92 | device_handler = device_helpers.DeviceRequestHandler( 93 | 'some-device', 94 | ) 95 | device_handler.command('SOME_COMMAND')(self.handler) 96 | device_request = build_device_request('some-device', 97 | 'OTHER_COMMAND', 98 | 'some-arg') 99 | fs = device_handler(device_request) 100 | self.assertEqual(len(fs), 1) 101 | self.assertFalse(self.handler_called) 102 | 103 | def test_noop_execution(self): 104 | device_handler = device_helpers.DeviceRequestHandler( 105 | 'some-device', 106 | ) 107 | device_handler.command('SOME_COMMAND')(self.handler) 108 | device_request = build_noop_device_request('some-device') 109 | fs = device_handler(device_request) 110 | self.assertEqual(len(fs), 0) 111 | self.assertFalse(self.handler_called) 112 | 113 | def test_exception(self): 114 | err = Exception('some error') 115 | 116 | def failing_command(arg): 117 | raise err 118 | device_handler = device_helpers.DeviceRequestHandler( 119 | 'some-device', 120 | ) 121 | device_handler.command('FAILING_COMMAND')(failing_command) 122 | device_request = build_device_request('some-device', 123 | 'FAILING_COMMAND', 124 | 'some-arg') 125 | fs = device_handler(device_request) 126 | self.assertEqual(len(fs), 1) 127 | concurrent.futures.wait(fs) 128 | self.assertEqual(fs[0].exception(), err) 129 | -------------------------------------------------------------------------------- /customentity/intents/RSVPHack_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "fb6201aa-d8e9-48fe-8e17-d8f3638a9061", 4 | "data": [ 5 | { 6 | "text": "Somos ", 7 | "userDefined": false 8 | }, 9 | { 10 | "text": "desarrolladores", 11 | "alias": "type-of-hacker", 12 | "meta": "@type-of-hacker", 13 | "userDefined": false 14 | } 15 | ], 16 | "isTemplate": false, 17 | "count": 0, 18 | "updated": 0 19 | }, 20 | { 21 | "id": "5f650581-12e5-45c4-8073-1084cfa8b636", 22 | "data": [ 23 | { 24 | "text": "Sí", 25 | "alias": "firts-time", 26 | "meta": "@firts-time", 27 | "userDefined": false 28 | }, 29 | { 30 | "text": ", estamos nerviosos", 31 | "userDefined": false 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "d1b6d74b-2239-4e57-b1ec-149e2d8a41dc", 40 | "data": [ 41 | { 42 | "text": "No", 43 | "alias": "firts-time", 44 | "meta": "@firts-time", 45 | "userDefined": false 46 | }, 47 | { 48 | "text": ", he ido a varios", 49 | "userDefined": false 50 | } 51 | ], 52 | "isTemplate": false, 53 | "count": 0, 54 | "updated": 0 55 | }, 56 | { 57 | "id": "ac0127cb-5899-4637-8d35-529b670c2554", 58 | "data": [ 59 | { 60 | "text": "a mi correo ", 61 | "userDefined": false 62 | }, 63 | { 64 | "text": "flab@flab.mx", 65 | "alias": "email", 66 | "meta": "@sys.email", 67 | "userDefined": true 68 | } 69 | ], 70 | "isTemplate": false, 71 | "count": 0, 72 | "updated": 0 73 | }, 74 | { 75 | "id": "3c8ba730-c6cb-451f-9cac-277a295e6cea", 76 | "data": [ 77 | { 78 | "text": "al correo ", 79 | "userDefined": false 80 | }, 81 | { 82 | "text": "flab@f.mx", 83 | "alias": "email", 84 | "meta": "@sys.email", 85 | "userDefined": true 86 | } 87 | ], 88 | "isTemplate": false, 89 | "count": 0, 90 | "updated": 0 91 | }, 92 | { 93 | "id": "6de872ad-53e8-4cb7-b224-1010863b03c7", 94 | "data": [ 95 | { 96 | "text": "Mi equipo tiene ", 97 | "userDefined": false 98 | }, 99 | { 100 | "text": "4", 101 | "alias": "number", 102 | "meta": "@sys.number", 103 | "userDefined": false 104 | }, 105 | { 106 | "text": " integrantes", 107 | "userDefined": false 108 | } 109 | ], 110 | "isTemplate": false, 111 | "count": 0, 112 | "updated": 0 113 | }, 114 | { 115 | "id": "dff1c0f8-8b14-4ff6-b845-70086f8a5e60", 116 | "data": [ 117 | { 118 | "text": "Somos ", 119 | "userDefined": false 120 | }, 121 | { 122 | "text": "5", 123 | "alias": "number", 124 | "meta": "@sys.number", 125 | "userDefined": false 126 | }, 127 | { 128 | "text": " personas", 129 | "userDefined": false 130 | } 131 | ], 132 | "isTemplate": false, 133 | "count": 0, 134 | "updated": 0 135 | }, 136 | { 137 | "id": "0fe986ca-efb3-4b59-bb97-6ad3f57ccf3b", 138 | "data": [ 139 | { 140 | "text": "Me llamo ", 141 | "userDefined": false 142 | }, 143 | { 144 | "text": "Francisco", 145 | "alias": "given-name", 146 | "meta": "@sys.given-name", 147 | "userDefined": true 148 | } 149 | ], 150 | "isTemplate": false, 151 | "count": 0, 152 | "updated": 0 153 | }, 154 | { 155 | "id": "2fd95cec-2c5e-47ce-aa9f-f04265e4d7d8", 156 | "data": [ 157 | { 158 | "text": "Mi nombre es ", 159 | "userDefined": false 160 | }, 161 | { 162 | "text": "Eduardo", 163 | "alias": "given-name", 164 | "meta": "@sys.given-name", 165 | "userDefined": true 166 | } 167 | ], 168 | "isTemplate": false, 169 | "count": 0, 170 | "updated": 0 171 | }, 172 | { 173 | "id": "0f23b971-19f2-4011-88ac-6f2454c20052", 174 | "data": [ 175 | { 176 | "text": "Quiero registrarme al evento", 177 | "userDefined": false 178 | } 179 | ], 180 | "isTemplate": false, 181 | "count": 0, 182 | "updated": 0 183 | }, 184 | { 185 | "id": "1c94cac7-20a3-4482-bccb-51f6fe8b026c", 186 | "data": [ 187 | { 188 | "text": "Quiero inscribirme al hack", 189 | "userDefined": false 190 | } 191 | ], 192 | "isTemplate": false, 193 | "count": 0, 194 | "updated": 0 195 | }, 196 | { 197 | "id": "00e25791-9731-4739-bb92-2ac6d0b23189", 198 | "data": [ 199 | { 200 | "text": "Hola, quiero ", 201 | "userDefined": false 202 | }, 203 | { 204 | "text": "un", 205 | "meta": "@sys.ignore", 206 | "userDefined": false 207 | }, 208 | { 209 | "text": " boleto para BeeHack", 210 | "userDefined": false 211 | } 212 | ], 213 | "isTemplate": false, 214 | "count": 0, 215 | "updated": 0 216 | } 217 | ] -------------------------------------------------------------------------------- /responsivebasiccard/intents/RSVPHack_usersays_es.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "fb6201aa-d8e9-48fe-8e17-d8f3638a9061", 4 | "data": [ 5 | { 6 | "text": "Somos ", 7 | "userDefined": false 8 | }, 9 | { 10 | "text": "desarrolladores", 11 | "alias": "type-of-hacker", 12 | "meta": "@type-of-hacker", 13 | "userDefined": false 14 | } 15 | ], 16 | "isTemplate": false, 17 | "count": 0, 18 | "updated": 0 19 | }, 20 | { 21 | "id": "5f650581-12e5-45c4-8073-1084cfa8b636", 22 | "data": [ 23 | { 24 | "text": "Sí", 25 | "alias": "firts-time", 26 | "meta": "@firts-time", 27 | "userDefined": false 28 | }, 29 | { 30 | "text": ", estamos nerviosos", 31 | "userDefined": false 32 | } 33 | ], 34 | "isTemplate": false, 35 | "count": 0, 36 | "updated": 0 37 | }, 38 | { 39 | "id": "d1b6d74b-2239-4e57-b1ec-149e2d8a41dc", 40 | "data": [ 41 | { 42 | "text": "No", 43 | "alias": "firts-time", 44 | "meta": "@firts-time", 45 | "userDefined": false 46 | }, 47 | { 48 | "text": ", he ido a varios", 49 | "userDefined": false 50 | } 51 | ], 52 | "isTemplate": false, 53 | "count": 0, 54 | "updated": 0 55 | }, 56 | { 57 | "id": "ac0127cb-5899-4637-8d35-529b670c2554", 58 | "data": [ 59 | { 60 | "text": "a mi correo ", 61 | "userDefined": false 62 | }, 63 | { 64 | "text": "flab@flab.mx", 65 | "alias": "email", 66 | "meta": "@sys.email", 67 | "userDefined": true 68 | } 69 | ], 70 | "isTemplate": false, 71 | "count": 0, 72 | "updated": 0 73 | }, 74 | { 75 | "id": "3c8ba730-c6cb-451f-9cac-277a295e6cea", 76 | "data": [ 77 | { 78 | "text": "al correo ", 79 | "userDefined": false 80 | }, 81 | { 82 | "text": "flab@f.mx", 83 | "alias": "email", 84 | "meta": "@sys.email", 85 | "userDefined": true 86 | } 87 | ], 88 | "isTemplate": false, 89 | "count": 0, 90 | "updated": 0 91 | }, 92 | { 93 | "id": "6de872ad-53e8-4cb7-b224-1010863b03c7", 94 | "data": [ 95 | { 96 | "text": "Mi equipo tiene ", 97 | "userDefined": false 98 | }, 99 | { 100 | "text": "4", 101 | "alias": "number", 102 | "meta": "@sys.number", 103 | "userDefined": false 104 | }, 105 | { 106 | "text": " integrantes", 107 | "userDefined": false 108 | } 109 | ], 110 | "isTemplate": false, 111 | "count": 0, 112 | "updated": 0 113 | }, 114 | { 115 | "id": "dff1c0f8-8b14-4ff6-b845-70086f8a5e60", 116 | "data": [ 117 | { 118 | "text": "Somos ", 119 | "userDefined": false 120 | }, 121 | { 122 | "text": "5", 123 | "alias": "number", 124 | "meta": "@sys.number", 125 | "userDefined": false 126 | }, 127 | { 128 | "text": " personas", 129 | "userDefined": false 130 | } 131 | ], 132 | "isTemplate": false, 133 | "count": 0, 134 | "updated": 0 135 | }, 136 | { 137 | "id": "0fe986ca-efb3-4b59-bb97-6ad3f57ccf3b", 138 | "data": [ 139 | { 140 | "text": "Me llamo ", 141 | "userDefined": false 142 | }, 143 | { 144 | "text": "Francisco", 145 | "alias": "given-name", 146 | "meta": "@sys.given-name", 147 | "userDefined": true 148 | } 149 | ], 150 | "isTemplate": false, 151 | "count": 0, 152 | "updated": 0 153 | }, 154 | { 155 | "id": "2fd95cec-2c5e-47ce-aa9f-f04265e4d7d8", 156 | "data": [ 157 | { 158 | "text": "Mi nombre es ", 159 | "userDefined": false 160 | }, 161 | { 162 | "text": "Eduardo", 163 | "alias": "given-name", 164 | "meta": "@sys.given-name", 165 | "userDefined": true 166 | } 167 | ], 168 | "isTemplate": false, 169 | "count": 0, 170 | "updated": 0 171 | }, 172 | { 173 | "id": "0f23b971-19f2-4011-88ac-6f2454c20052", 174 | "data": [ 175 | { 176 | "text": "Quiero registrarme al evento", 177 | "userDefined": false 178 | } 179 | ], 180 | "isTemplate": false, 181 | "count": 0, 182 | "updated": 0 183 | }, 184 | { 185 | "id": "1c94cac7-20a3-4482-bccb-51f6fe8b026c", 186 | "data": [ 187 | { 188 | "text": "Quiero inscribirme al hack", 189 | "userDefined": false 190 | } 191 | ], 192 | "isTemplate": false, 193 | "count": 0, 194 | "updated": 0 195 | }, 196 | { 197 | "id": "00e25791-9731-4739-bb92-2ac6d0b23189", 198 | "data": [ 199 | { 200 | "text": "Hola, quiero ", 201 | "userDefined": false 202 | }, 203 | { 204 | "text": "un", 205 | "meta": "@sys.ignore", 206 | "userDefined": false 207 | }, 208 | { 209 | "text": " boleto para BeeHack", 210 | "userDefined": false 211 | } 212 | ], 213 | "isTemplate": false, 214 | "count": 0, 215 | "updated": 0 216 | } 217 | ] -------------------------------------------------------------------------------- /responsivebasiccard/intents/RSVPHack.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "79c3ed2e-82e7-43fd-9ca7-1e49b394d48c", 3 | "name": "RSVPHack", 4 | "auto": true, 5 | "contexts": [], 6 | "responses": [ 7 | { 8 | "resetContexts": false, 9 | "affectedContexts": [], 10 | "parameters": [ 11 | { 12 | "id": "7e42b522-2ea6-415e-a65c-75126c1b0e5b", 13 | "required": true, 14 | "dataType": "@sys.given-name", 15 | "name": "given-name", 16 | "value": "$given-name", 17 | "prompts": [ 18 | { 19 | "lang": "es", 20 | "value": "Claro, para comenzar tu registro ¿Podrías proporcionarme tu nombre, por favor?" 21 | }, 22 | { 23 | "lang": "es", 24 | "value": "Muy bien, ¿Cuál es tú nombre?" 25 | } 26 | ], 27 | "promptMessages": [], 28 | "noMatchPromptMessages": [], 29 | "noInputPromptMessages": [], 30 | "outputDialogContexts": [], 31 | "isList": false 32 | }, 33 | { 34 | "id": "c0b64d32-5738-4c63-a4fc-5bf18275fcf9", 35 | "required": true, 36 | "dataType": "@sys.number", 37 | "name": "number", 38 | "value": "$number", 39 | "prompts": [ 40 | { 41 | "lang": "es", 42 | "value": "Muy bien entonces, ¿Cuántas personas hay en el equipo?" 43 | }, 44 | { 45 | "lang": "es", 46 | "value": "Ok, ¿ Cuántos integrantes tiene tu equipo?" 47 | } 48 | ], 49 | "promptMessages": [], 50 | "noMatchPromptMessages": [], 51 | "noInputPromptMessages": [], 52 | "outputDialogContexts": [], 53 | "isList": false 54 | }, 55 | { 56 | "id": "210b8055-b3fc-45b5-bda5-36550141b8c6", 57 | "required": true, 58 | "dataType": "@sys.email", 59 | "name": "email", 60 | "value": "$email", 61 | "prompts": [ 62 | { 63 | "lang": "es", 64 | "value": "Excelente, ¿ A que dirección de correo electrónico debería enviar tus boletos?" 65 | }, 66 | { 67 | "lang": "es", 68 | "value": "Gracias, ¿ Cuál es la dirección de correo a la que enviaré los boletos?" 69 | } 70 | ], 71 | "promptMessages": [], 72 | "noMatchPromptMessages": [], 73 | "noInputPromptMessages": [], 74 | "outputDialogContexts": [], 75 | "isList": false 76 | }, 77 | { 78 | "id": "1cb48e82-8904-4c5a-88b3-91abe304a816", 79 | "required": true, 80 | "dataType": "@type-of-hacker", 81 | "name": "type-of-hacker", 82 | "value": "$type-of-hacker", 83 | "prompts": [ 84 | { 85 | "lang": "es", 86 | "value": "¿Comó describirias a los integrantes de tu equipo?(Makers, Devs, Negocios)." 87 | } 88 | ], 89 | "promptMessages": [], 90 | "noMatchPromptMessages": [], 91 | "noInputPromptMessages": [], 92 | "outputDialogContexts": [], 93 | "isList": false 94 | }, 95 | { 96 | "id": "1b3d3659-3f29-4405-b782-dfac2b88f488", 97 | "required": true, 98 | "dataType": "@firts-time", 99 | "name": "firts-time", 100 | "value": "$firts-time", 101 | "prompts": [ 102 | { 103 | "lang": "es", 104 | "value": "Genial, ¿Es tu primer Hackaton?" 105 | }, 106 | { 107 | "lang": "es", 108 | "value": "Por último, ¿Has ido a algún Hackaton antes?" 109 | } 110 | ], 111 | "promptMessages": [], 112 | "noMatchPromptMessages": [], 113 | "noInputPromptMessages": [], 114 | "outputDialogContexts": [], 115 | "isList": false 116 | } 117 | ], 118 | "messages": [ 119 | { 120 | "type": "basic_card", 121 | "platform": "google", 122 | "lang": "es", 123 | "title": "Future Lab", 124 | "formattedText": "Gracias, tu registro se ha realizado con éxito. Nos vemos en el Hack", 125 | "image": { 126 | "url": "https://futurelab.mx/", 127 | "accessibilityText": "Pagina oficial de Future Lab" 128 | }, 129 | "buttons": [ 130 | { 131 | "title": "Facebook Future Lab", 132 | "openUrlAction": { 133 | "url": "https://www.facebook.com/f.lab.mx", 134 | "urlTypeHint": "URL_TYPE_HINT_UNSPECIFIED" 135 | } 136 | } 137 | ] 138 | }, 139 | { 140 | "type": 0, 141 | "lang": "es", 142 | "speech": "Gracias, tu registro se ha realizado con éxito. Nos vemos en el Hack" 143 | } 144 | ], 145 | "defaultResponsePlatforms": { 146 | "google": true 147 | }, 148 | "speech": [] 149 | } 150 | ], 151 | "priority": 500000, 152 | "webhookUsed": false, 153 | "webhookForSlotFilling": false, 154 | "fallbackIntent": false, 155 | "events": [] 156 | } -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/README.rst: -------------------------------------------------------------------------------- 1 | # Initial Note: 2 | This repository contains a modified file specifically made for the demo at *MLH localhosts*. Such file's name is `pushtotalk.py` 3 | 4 | ================================================ 5 | 6 | Python Samples for the Google Assistant gRPC API 7 | ================================================ 8 | 9 | This repository contains a reference sample for the ``google-assistant-grpc`` Python package_. 10 | 11 | It implements the following features: 12 | 13 | - Triggering a conversation using a key press 14 | - Audio recording of user queries (single or multiple consecutive queries) 15 | - Playback of the Assistant response 16 | - Conversation state management 17 | - Volume control 18 | 19 | .. _package: https://pypi.python.org/pypi/google-assistant-grpc 20 | 21 | Prerequisites 22 | ------------- 23 | 24 | - `Python `_ (>= 3.4 recommended) 25 | - An `Actions Console Project `_ 26 | - A `Google account `_ 27 | 28 | Setup 29 | ----- 30 | 31 | - Install Python 3 32 | 33 | - Ubuntu/Debian GNU/Linux:: 34 | 35 | sudo apt-get update 36 | sudo apt-get install python3 python3-venv 37 | 38 | - `MacOSX, Windows, Other `_ 39 | 40 | - Create a new virtual environment (recommended):: 41 | 42 | python3 -m venv env 43 | env/bin/python -m pip install --upgrade pip setuptools wheel 44 | source env/bin/activate 45 | 46 | Authorization 47 | ------------- 48 | 49 | - Follow the steps to `configure the Actions Console project and the Google account `_. 50 | - Follow the steps to `register a new device model and download the client secrets file `_. 51 | - Generate device credentials using ``google-oauthlib-tool``: 52 | 53 | pip install --upgrade google-auth-oauthlib[tool] 54 | google-oauthlib-tool --client-secrets path/to/client_secret_.json --scope https://www.googleapis.com/auth/assistant-sdk-prototype --save --headless 55 | 56 | Run the samples 57 | --------------- 58 | 59 | - Install the sample dependencies:: 60 | 61 | sudo apt-get install portaudio19-dev libffi-dev libssl-dev 62 | pip install --upgrade -r requirements.txt 63 | 64 | - Verify audio setup:: 65 | 66 | # Record a 5 sec sample and play it back 67 | python -m audio_helpers 68 | 69 | - Run the push to talk sample. The sample records a voice query after a key press and plays back the Google Assistant's answer:: 70 | 71 | python -m pushtotalk --device-id 'my-device-identifier' --device-model-id 'my-model-identifier' 72 | 73 | - Try some Google Assistant voice query like "What time is it?" or "Who am I?". 74 | 75 | - Try a device action query like "Turn on". 76 | 77 | - Run in verbose mode to see the gRPC communication with the Google Assistant API:: 78 | 79 | python -m pushtotalk --device-id 'my-device-identifier' --device-model-id 'my-model-identifier' -v 80 | 81 | - Send a pre-recorded request to the Assistant:: 82 | 83 | python -m pushtotalk --device-id 'my-device-identifier' --device-model-id 'my-model-identifier' -i in.wav 84 | 85 | - Save the Assistant response to a file:: 86 | 87 | python -m pushtotalk --device-id 'my-device-identifier' --device-model-id 'my-model-identifier' -o out.wav 88 | 89 | - Send text requests to the Assistant:: 90 | 91 | python -m textinput --device-id 'my-device-identifier' --device-model-id 'my-model-identifier' 92 | 93 | - Send a request to the Assistant from a local audio file and write the Assistant audio response to another file:: 94 | 95 | python -m audiofileinput --device-id 'my-device-identifier' --device-model-id 'my-model-identifier' -i in.wav -o out.wav 96 | 97 | Troubleshooting 98 | --------------- 99 | 100 | - Verify ALSA setup:: 101 | 102 | # Play a test sound 103 | speaker-test -t wav 104 | 105 | # Record and play back some audio using ALSA command-line tools 106 | arecord --format=S16_LE --duration=5 --rate=16000 --file-type=raw out.raw 107 | aplay --format=S16_LE --rate=16000 --file-type=raw out.raw 108 | 109 | - If Assistant audio is choppy, try adjusting the sound device's block size:: 110 | 111 | # If using a USB speaker or dedicated soundcard, set block size to "0" 112 | # to automatically adjust the buffer size 113 | python -m audio_helpers --audio-block-size=0 114 | 115 | # If using the line-out 3.5mm audio jack on the device, set block size 116 | # to a value larger than the `ConverseResponse` audio payload size 117 | python -m audio_helpers --audio-block-size=3200 118 | 119 | # Run the Assistant sample using the best block size value found above 120 | python -m pushtotalk --audio-block-size=value 121 | 122 | - If Assistant audio is truncated, try adjusting the sound device's flush size:: 123 | 124 | # Set flush size to a value larger than the audio block size. You can 125 | # run the sample using the --audio-flush-size flag as well. 126 | python -m audio_helpers --audio-block-size=3200 --audio-flush-size=6400 127 | 128 | See also the `troubleshooting section `_ of the official documentation. 129 | 130 | License 131 | ------- 132 | 133 | Copyright (C) 2017 Google Inc. 134 | 135 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 136 | license agreements. See the NOTICE file distributed with this work for 137 | additional information regarding copyright ownership. The ASF licenses this 138 | file to you under the Apache License, Version 2.0 (the "License"); you may not 139 | use this file except in compliance with the License. You may obtain a copy of 140 | the License at 141 | 142 | http://www.apache.org/licenses/LICENSE-2.0 143 | 144 | Unless required by applicable law or agreed to in writing, software 145 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 146 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 147 | License for the specific language governing permissions and limitations under 148 | the License. 149 | -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/test_audio_helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import unittest 17 | 18 | import time 19 | import wave 20 | 21 | from googlesamples.assistant.grpc import audio_helpers 22 | from six import BytesIO 23 | 24 | 25 | class WaveSourceTest(unittest.TestCase): 26 | def setUp(self): 27 | stream = BytesIO() 28 | w = wave.open(stream, 'wb') 29 | sample_rate = 16000 30 | bytes_per_sample = 2 31 | w.setframerate(sample_rate) 32 | w.setsampwidth(bytes_per_sample) 33 | w.setnchannels(1) 34 | w.writeframes(b'audiodata') 35 | self.stream = BytesIO(stream.getvalue()) 36 | self.source = audio_helpers.WaveSource( 37 | self.stream, sample_rate, bytes_per_sample) 38 | self.sleep_time_1024 = self.source._sleep_time(1024) 39 | self.sleep_time_512 = self.source._sleep_time(512) 40 | 41 | def test_sleep_time(self): 42 | self.assertEqual(self.sleep_time_512, self.sleep_time_1024 / 2) 43 | 44 | def test_no_sleep_on_first_read(self): 45 | previous_time = time.time() 46 | self.source.read(1024) 47 | # check sleep was not called 48 | self.assertLess(time.time(), previous_time + self.sleep_time_1024) 49 | 50 | def test_first_sleep(self): 51 | self.source.read(1024) 52 | previous_time = time.time() 53 | self.source.read(512) 54 | # sleep was called with sleep_time_1024 55 | self.assertGreater(time.time(), previous_time + self.sleep_time_1024) 56 | 57 | def test_next_sleep(self): 58 | self.source.read(1024) 59 | self.source.read(512) 60 | previous_time = time.time() 61 | self.source.read(0) 62 | # sleep was called with sleep_time_512 63 | self.assertGreater(time.time(), previous_time + self.sleep_time_512) 64 | self.assertLess(time.time(), previous_time + self.sleep_time_1024) 65 | 66 | def test_read_header(self): 67 | self.assertEqual(b'audiodata', self.source.read(9)) 68 | 69 | def test_raw(self): 70 | self.stream = BytesIO(b'audiodata') 71 | self.source = audio_helpers.WaveSource(self.stream, 16000, 2) 72 | self.assertEqual(b'audiodata', self.source.read(9)) 73 | 74 | def test_silence(self): 75 | self.assertEqual(b'audiodata', self.source.read(9)) 76 | self.assertEqual(b'\x00'*9, self.source.read(9)) 77 | 78 | 79 | class WaveSinkTest(unittest.TestCase): 80 | def setUp(self): 81 | self.stream = BytesIO() 82 | self.sink = audio_helpers.WaveSink(self.stream, 16000, 2) 83 | 84 | def test_write_header(self): 85 | self.sink.write(b'abcd') 86 | self.assertEqual(b'RIFF', self.stream.getvalue()[:4]) 87 | 88 | 89 | class DummyStream(BytesIO, object): 90 | started = False 91 | stopped = False 92 | flushed = False 93 | 94 | def start(self): 95 | self.started = True 96 | 97 | def stop(self): 98 | self.stopped = True 99 | 100 | def read(self, *args): 101 | if self.stopped: 102 | return b'' 103 | return super(DummyStream, self).read(*args) 104 | 105 | def write(self, *args): 106 | if not self.started: 107 | return 108 | return super(DummyStream, self).write(*args) 109 | 110 | def flush(self): 111 | self.flushed = True 112 | 113 | 114 | class ConversationStreamTest(unittest.TestCase): 115 | def setUp(self): 116 | self.source = DummyStream(b'audio data') 117 | self.sink = DummyStream() 118 | self.stream = audio_helpers.ConversationStream( 119 | source=self.source, 120 | sink=self.sink, 121 | iter_size=1024, 122 | sample_width=2) 123 | self.stream.volume_percentage = 100 124 | 125 | def test_stop_recording(self): 126 | self.stream.start_recording() 127 | self.assertEqual(b'audio', self.stream.read(5)) 128 | self.stream.stop_recording() 129 | self.assertEqual(b'', self.stream.read(5)) 130 | 131 | def test_start_playback(self): 132 | self.playback_started = False 133 | self.stream.write(b'foo') 134 | self.assertEqual(b'', self.sink.getvalue()) 135 | self.stream.start_playback() 136 | self.stream.write(b'foo') 137 | self.assertEqual(b'foo\0', self.sink.getvalue()) 138 | 139 | def test_sink_source_state(self): 140 | self.assertEquals(False, self.source.started) 141 | self.stream.start_recording() 142 | self.assertEquals(True, self.source.started) 143 | self.stream.stop_recording() 144 | self.assertEquals(True, self.source.stopped) 145 | 146 | self.assertEquals(False, self.sink.started) 147 | self.stream.start_playback() 148 | self.assertEquals(True, self.sink.started) 149 | self.stream.stop_playback() 150 | self.assertEquals(True, self.sink.stopped) 151 | 152 | def test_oneshot_conversation(self): 153 | self.assertEqual(b'audio', self.stream.read(5)) 154 | self.stream.stop_recording() 155 | self.stream.start_playback() 156 | self.stream.write(b'foo') 157 | self.stream.stop_playback() 158 | 159 | def test_normalize_audio_buffer(self): 160 | self.assertEqual(b'', 161 | audio_helpers.normalize_audio_buffer(b'', 100)) 162 | self.assertEqual(b'foobar', 163 | audio_helpers.normalize_audio_buffer(b'foobar', 100)) 164 | self.assertEqual(b'\xd4\x00\xa9\x01', 165 | audio_helpers.normalize_audio_buffer( 166 | b'\x01\x02\x03\x04', 50)) 167 | 168 | def test_align_buf(self): 169 | self.assertEqual(b'foo\0', audio_helpers.align_buf(b'foo', 2)) 170 | self.assertEqual(b'foobar', audio_helpers.align_buf(b'foobar', 2)) 171 | self.assertEqual(b'foo\0\0\0', audio_helpers.align_buf(b'foo', 6)) 172 | 173 | 174 | if __name__ == '__main__': 175 | unittest.main() 176 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/library/hotword.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2017 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | from __future__ import print_function 19 | import argparse 20 | import json 21 | import os.path 22 | import pathlib2 as pathlib 23 | 24 | import google.oauth2.credentials 25 | 26 | from google.assistant.library import Assistant 27 | from google.assistant.library.event import EventType 28 | from google.assistant.library.file_helpers import existing_file 29 | from google.assistant.library.device_helpers import register_device 30 | 31 | import faulthandler 32 | faulthandler.enable() 33 | 34 | try: 35 | FileNotFoundError 36 | except NameError: 37 | FileNotFoundError = IOError 38 | 39 | 40 | WARNING_NOT_REGISTERED = """ 41 | This device is not registered. This means you will not be able to use 42 | Device Actions or see your device in Assistant Settings. In order to 43 | register this device follow instructions at: 44 | 45 | https://developers.google.com/assistant/sdk/guides/library/python/embed/register-device 46 | """ 47 | 48 | 49 | def process_event(event): 50 | """Pretty prints events. 51 | 52 | Prints all events that occur with two spaces between each new 53 | conversation and a single space between turns of a conversation. 54 | 55 | Args: 56 | event(event.Event): The current event to process. 57 | """ 58 | if event.type == EventType.ON_CONVERSATION_TURN_STARTED: 59 | print() 60 | 61 | print(event) 62 | 63 | if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED and 64 | event.args and not event.args['with_follow_on_turn']): 65 | print() 66 | if event.type == EventType.ON_DEVICE_ACTION: 67 | for command, params in event.actions: 68 | print('Do command', command, 'with params', str(params)) 69 | 70 | 71 | def main(): 72 | parser = argparse.ArgumentParser( 73 | formatter_class=argparse.RawTextHelpFormatter) 74 | parser.add_argument('--device-model-id', '--device_model_id', type=str, 75 | metavar='DEVICE_MODEL_ID', required=False, 76 | help='the device model ID registered with Google') 77 | parser.add_argument('--project-id', '--project_id', type=str, 78 | metavar='PROJECT_ID', required=False, 79 | help='the project ID used to register this device') 80 | parser.add_argument('--nickname', type=str, 81 | metavar='NICKNAME', required=False, 82 | help='the nickname used to register this device') 83 | parser.add_argument('--device-config', type=str, 84 | metavar='DEVICE_CONFIG_FILE', 85 | default=os.path.join( 86 | os.path.expanduser('~/.config'), 87 | 'googlesamples-assistant', 88 | 'device_config_library.json' 89 | ), 90 | help='path to store and read device configuration') 91 | parser.add_argument('--credentials', type=existing_file, 92 | metavar='OAUTH2_CREDENTIALS_FILE', 93 | default=os.path.join( 94 | os.path.expanduser('~/.config'), 95 | 'google-oauthlib-tool', 96 | 'credentials.json' 97 | ), 98 | help='path to store and read OAuth2 credentials') 99 | parser.add_argument('--query', type=str, 100 | metavar='QUERY', 101 | help='query to send as soon as the Assistant starts') 102 | parser.add_argument('-v', '--version', action='version', 103 | version='%(prog)s ' + Assistant.__version_str__()) 104 | 105 | args = parser.parse_args() 106 | with open(args.credentials, 'r') as f: 107 | credentials = google.oauth2.credentials.Credentials(token=None, 108 | **json.load(f)) 109 | 110 | device_model_id = None 111 | last_device_id = None 112 | try: 113 | with open(args.device_config) as f: 114 | device_config = json.load(f) 115 | device_model_id = device_config['model_id'] 116 | last_device_id = device_config.get('last_device_id', None) 117 | except FileNotFoundError: 118 | pass 119 | 120 | if not args.device_model_id and not device_model_id: 121 | raise Exception('Missing --device-model-id option') 122 | 123 | # Re-register if "device_model_id" is given by the user and it differs 124 | # from what we previously registered with. 125 | should_register = ( 126 | args.device_model_id and args.device_model_id != device_model_id) 127 | 128 | device_model_id = args.device_model_id or device_model_id 129 | 130 | with Assistant(credentials, device_model_id) as assistant: 131 | events = assistant.start() 132 | 133 | device_id = assistant.device_id 134 | print('device_model_id:', device_model_id) 135 | print('device_id:', device_id + '\n') 136 | 137 | # Re-register if "device_id" is different from the last "device_id": 138 | if should_register or (device_id != last_device_id): 139 | if args.project_id: 140 | register_device(args.project_id, credentials, 141 | device_model_id, device_id, args.nickname) 142 | pathlib.Path(os.path.dirname(args.device_config)).mkdir( 143 | exist_ok=True) 144 | with open(args.device_config, 'w') as f: 145 | json.dump({ 146 | 'last_device_id': device_id, 147 | 'model_id': device_model_id, 148 | }, f) 149 | else: 150 | print(WARNING_NOT_REGISTERED) 151 | 152 | for event in events: 153 | if event.type == EventType.ON_START_FINISHED and args.query: 154 | assistant.send_text_query(args.query) 155 | 156 | process_event(event) 157 | 158 | 159 | if __name__ == '__main__': 160 | main() 161 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/audiofileinput.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Simple file-based sample for the Google Assistant Service.""" 16 | 17 | import json 18 | import logging 19 | import os 20 | import os.path 21 | import sys 22 | 23 | import click 24 | import google.auth.transport.grpc 25 | import google.auth.transport.requests 26 | import google.oauth2.credentials 27 | 28 | from google.assistant.embedded.v1alpha2 import ( 29 | embedded_assistant_pb2, 30 | embedded_assistant_pb2_grpc 31 | ) 32 | 33 | 34 | END_OF_UTTERANCE = embedded_assistant_pb2.AssistResponse.END_OF_UTTERANCE 35 | 36 | 37 | @click.command() 38 | @click.option('--api-endpoint', default='embeddedassistant.googleapis.com', 39 | metavar='', show_default=True, 40 | help='Address of Google Assistant API service.') 41 | @click.option('--credentials', 42 | metavar='', show_default=True, 43 | default=os.path.join(click.get_app_dir('google-oauthlib-tool'), 44 | 'credentials.json'), 45 | help='Path to read OAuth2 credentials.') 46 | @click.option('--device-model-id', required=True, 47 | metavar='', 48 | help='Unique device model identifier.') 49 | @click.option('--device-id', required=True, 50 | metavar='', 51 | help='Unique registered device instance identifier.') 52 | @click.option('--lang', show_default=True, 53 | metavar='', 54 | default='en-US', 55 | help='Language code of the Assistant.') 56 | @click.option('--verbose', '-v', is_flag=True, default=False, 57 | help='Enable verbose logging.') 58 | @click.option('--input-audio-file', '-i', required=True, 59 | metavar='', type=click.File('rb'), 60 | help='Path to input audio file (format: LINEAR16 16000 Hz).') 61 | @click.option('--output-audio-file', '-o', required=True, 62 | metavar='', type=click.File('wb'), 63 | help='Path to output audio file (format: LINEAR16 16000 Hz).') 64 | @click.option('--block-size', default=1024, 65 | metavar='', show_default=True, 66 | help='Size of each input stream read in bytes.') 67 | @click.option('--grpc-deadline', default=300, 68 | metavar='', show_default=True, 69 | help='gRPC deadline in seconds') 70 | def main(api_endpoint, credentials, 71 | device_model_id, device_id, lang, verbose, 72 | input_audio_file, output_audio_file, 73 | block_size, grpc_deadline, *args, **kwargs): 74 | """File based sample for the Google Assistant API. 75 | 76 | Examples: 77 | $ python -m audiofileinput -i -o 78 | """ 79 | # Setup logging. 80 | logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO) 81 | 82 | # Load OAuth 2.0 credentials. 83 | try: 84 | with open(credentials, 'r') as f: 85 | credentials = google.oauth2.credentials.Credentials(token=None, 86 | **json.load(f)) 87 | http_request = google.auth.transport.requests.Request() 88 | credentials.refresh(http_request) 89 | except Exception as e: 90 | logging.error('Error loading credentials: %s', e) 91 | logging.error('Run google-oauthlib-tool to initialize ' 92 | 'new OAuth 2.0 credentials.') 93 | sys.exit(-1) 94 | 95 | # Create an authorized gRPC channel. 96 | grpc_channel = google.auth.transport.grpc.secure_authorized_channel( 97 | credentials, http_request, api_endpoint) 98 | logging.info('Connecting to %s', api_endpoint) 99 | 100 | # Create gRPC stubs 101 | assistant = embedded_assistant_pb2_grpc.EmbeddedAssistantStub(grpc_channel) 102 | 103 | # Generate gRPC requests. 104 | def gen_assist_requests(input_stream): 105 | dialog_state_in = embedded_assistant_pb2.DialogStateIn( 106 | language_code=lang, 107 | conversation_state=b'' 108 | ) 109 | config = embedded_assistant_pb2.AssistConfig( 110 | audio_in_config=embedded_assistant_pb2.AudioInConfig( 111 | encoding='LINEAR16', 112 | sample_rate_hertz=16000, 113 | ), 114 | audio_out_config=embedded_assistant_pb2.AudioOutConfig( 115 | encoding='LINEAR16', 116 | sample_rate_hertz=16000, 117 | volume_percentage=100, 118 | ), 119 | dialog_state_in=dialog_state_in, 120 | device_config=embedded_assistant_pb2.DeviceConfig( 121 | device_id=device_id, 122 | device_model_id=device_model_id, 123 | ) 124 | ) 125 | # Send first AssistRequest message with configuration. 126 | yield embedded_assistant_pb2.AssistRequest(config=config) 127 | while True: 128 | # Read user request from file. 129 | data = input_stream.read(block_size) 130 | if not data: 131 | break 132 | # Send following AssitRequest message with audio chunks. 133 | yield embedded_assistant_pb2.AssistRequest(audio_in=data) 134 | 135 | for resp in assistant.Assist(gen_assist_requests(input_audio_file), 136 | grpc_deadline): 137 | # Iterate on AssistResponse messages. 138 | if resp.event_type == END_OF_UTTERANCE: 139 | logging.info('End of audio request detected') 140 | if resp.speech_results: 141 | logging.info('Transcript of user request: "%s".', 142 | ' '.join(r.transcript 143 | for r in resp.speech_results)) 144 | if len(resp.audio_out.audio_data) > 0: 145 | # Write assistant response to supplied file. 146 | output_audio_file.write(resp.audio_out.audio_data) 147 | if resp.dialog_state_out.supplemental_display_text: 148 | logging.info('Assistant display text: "%s"', 149 | resp.dialog_state_out.supplemental_display_text) 150 | if resp.device_action.device_request_json: 151 | device_request = json.loads(resp.device_action.device_request_json) 152 | logging.info('Device request: %s', device_request) 153 | 154 | 155 | if __name__ == '__main__': 156 | main() 157 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/textinput.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Sample that implements a text client for the Google Assistant Service.""" 16 | 17 | import os 18 | import logging 19 | import json 20 | 21 | import click 22 | import google.auth.transport.grpc 23 | import google.auth.transport.requests 24 | import google.oauth2.credentials 25 | 26 | from google.assistant.embedded.v1alpha2 import ( 27 | embedded_assistant_pb2, 28 | embedded_assistant_pb2_grpc 29 | ) 30 | 31 | try: 32 | from . import ( 33 | assistant_helpers, 34 | browser_helpers, 35 | ) 36 | except (SystemError, ImportError): 37 | import assistant_helpers 38 | import browser_helpers 39 | 40 | 41 | ASSISTANT_API_ENDPOINT = 'embeddedassistant.googleapis.com' 42 | DEFAULT_GRPC_DEADLINE = 60 * 3 + 5 43 | PLAYING = embedded_assistant_pb2.ScreenOutConfig.PLAYING 44 | 45 | 46 | class SampleTextAssistant(object): 47 | """Sample Assistant that supports text based conversations. 48 | 49 | Args: 50 | language_code: language for the conversation. 51 | device_model_id: identifier of the device model. 52 | device_id: identifier of the registered device instance. 53 | display: enable visual display of assistant response. 54 | channel: authorized gRPC channel for connection to the 55 | Google Assistant API. 56 | deadline_sec: gRPC deadline in seconds for Google Assistant API call. 57 | """ 58 | 59 | def __init__(self, language_code, device_model_id, device_id, 60 | display, channel, deadline_sec): 61 | self.language_code = language_code 62 | self.device_model_id = device_model_id 63 | self.device_id = device_id 64 | self.conversation_state = None 65 | # Force reset of first conversation. 66 | self.is_new_conversation = True 67 | self.display = display 68 | self.assistant = embedded_assistant_pb2_grpc.EmbeddedAssistantStub( 69 | channel 70 | ) 71 | self.deadline = deadline_sec 72 | 73 | def __enter__(self): 74 | return self 75 | 76 | def __exit__(self, etype, e, traceback): 77 | if e: 78 | return False 79 | 80 | def assist(self, text_query): 81 | """Send a text request to the Assistant and playback the response. 82 | """ 83 | def iter_assist_requests(): 84 | config = embedded_assistant_pb2.AssistConfig( 85 | audio_out_config=embedded_assistant_pb2.AudioOutConfig( 86 | encoding='LINEAR16', 87 | sample_rate_hertz=16000, 88 | volume_percentage=0, 89 | ), 90 | dialog_state_in=embedded_assistant_pb2.DialogStateIn( 91 | language_code=self.language_code, 92 | conversation_state=self.conversation_state, 93 | is_new_conversation=self.is_new_conversation, 94 | ), 95 | device_config=embedded_assistant_pb2.DeviceConfig( 96 | device_id=self.device_id, 97 | device_model_id=self.device_model_id, 98 | ), 99 | text_query=text_query, 100 | ) 101 | # Continue current conversation with later requests. 102 | self.is_new_conversation = False 103 | if self.display: 104 | config.screen_out_config.screen_mode = PLAYING 105 | req = embedded_assistant_pb2.AssistRequest(config=config) 106 | assistant_helpers.log_assist_request_without_audio(req) 107 | yield req 108 | 109 | text_response = None 110 | html_response = None 111 | for resp in self.assistant.Assist(iter_assist_requests(), 112 | self.deadline): 113 | assistant_helpers.log_assist_response_without_audio(resp) 114 | if resp.screen_out.data: 115 | html_response = resp.screen_out.data 116 | if resp.dialog_state_out.conversation_state: 117 | conversation_state = resp.dialog_state_out.conversation_state 118 | self.conversation_state = conversation_state 119 | if resp.dialog_state_out.supplemental_display_text: 120 | text_response = resp.dialog_state_out.supplemental_display_text 121 | return text_response, html_response 122 | 123 | 124 | @click.command() 125 | @click.option('--api-endpoint', default=ASSISTANT_API_ENDPOINT, 126 | metavar='', show_default=True, 127 | help='Address of Google Assistant API service.') 128 | @click.option('--credentials', 129 | metavar='', show_default=True, 130 | default=os.path.join(click.get_app_dir('google-oauthlib-tool'), 131 | 'credentials.json'), 132 | help='Path to read OAuth2 credentials.') 133 | @click.option('--device-model-id', 134 | metavar='', 135 | required=True, 136 | help=(('Unique device model identifier, ' 137 | 'if not specifed, it is read from --device-config'))) 138 | @click.option('--device-id', 139 | metavar='', 140 | required=True, 141 | help=(('Unique registered device instance identifier, ' 142 | 'if not specified, it is read from --device-config, ' 143 | 'if no device_config found: a new device is registered ' 144 | 'using a unique id and a new device config is saved'))) 145 | @click.option('--lang', show_default=True, 146 | metavar='', 147 | default='en-US', 148 | help='Language code of the Assistant') 149 | @click.option('--display', is_flag=True, default=False, 150 | help='Enable visual display of Assistant responses in HTML.') 151 | @click.option('--verbose', '-v', is_flag=True, default=False, 152 | help='Verbose logging.') 153 | @click.option('--grpc-deadline', default=DEFAULT_GRPC_DEADLINE, 154 | metavar='', show_default=True, 155 | help='gRPC deadline in seconds') 156 | def main(api_endpoint, credentials, 157 | device_model_id, device_id, lang, display, verbose, 158 | grpc_deadline, *args, **kwargs): 159 | # Setup logging. 160 | logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO) 161 | 162 | # Load OAuth 2.0 credentials. 163 | try: 164 | with open(credentials, 'r') as f: 165 | credentials = google.oauth2.credentials.Credentials(token=None, 166 | **json.load(f)) 167 | http_request = google.auth.transport.requests.Request() 168 | credentials.refresh(http_request) 169 | except Exception as e: 170 | logging.error('Error loading credentials: %s', e) 171 | logging.error('Run google-oauthlib-tool to initialize ' 172 | 'new OAuth 2.0 credentials.') 173 | return 174 | 175 | # Create an authorized gRPC channel. 176 | grpc_channel = google.auth.transport.grpc.secure_authorized_channel( 177 | credentials, http_request, api_endpoint) 178 | logging.info('Connecting to %s', api_endpoint) 179 | 180 | with SampleTextAssistant(lang, device_model_id, device_id, display, 181 | grpc_channel, grpc_deadline) as assistant: 182 | while True: 183 | query = click.prompt('') 184 | click.echo(' %s' % query) 185 | response_text, response_html = assistant.assist(text_query=query) 186 | if display and response_html: 187 | system_browser = browser_helpers.system_browser 188 | system_browser.display(response_html) 189 | if response_text: 190 | click.echo('<@assistant> %s' % response_text) 191 | 192 | 193 | if __name__ == '__main__': 194 | main() 195 | -------------------------------------------------------------------------------- /rpi-assistant-demo/README.rst: -------------------------------------------------------------------------------- 1 | Google Assistant SDK for devices - Python 2 | ========================================= 3 | 4 | This package contains a collection of samples and tools to help you 5 | get started with the `Google Assistant SDK`_ using `Python`_. 6 | 7 | Installing 8 | ---------- 9 | 10 | - You can install using `pip`_:: 11 | 12 | pip install --upgrade google-assistant-sdk[samples] 13 | 14 | Usage 15 | ----- 16 | 17 | google-oauthlib-tool 18 | ~~~~~~~~~~~~~~~~~~~~ 19 | 20 | This tool creates test credentials to authorize devices to call the 21 | Google Assistant API when prototyping. 22 | 23 | - Follow the steps to `configure the Actions Console project and the Google account `_. 24 | - Follow the steps to `register a new device model and download the client secrets file `_. 25 | - Generate device credentials using ``google-oauthlib-tool``: 26 | 27 | pip install --upgrade google-auth-oauthlib[tool] 28 | google-oauthlib-tool --client-secrets path/to/client_secret_.json --scope https://www.googleapis.com/auth/assistant-sdk-prototype --save --headless 29 | 30 | googlesamples-assistant-audiotest 31 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 | 33 | This tool verifies device setup for audio recording and playback. 34 | 35 | - Install the sample's dependencies:: 36 | 37 | sudo apt-get install portaudio19-dev libffi-dev libssl-dev 38 | pip install --upgrade google-assistant-sdk[samples] 39 | 40 | - Record 10 seconds of audio samples and play them back:: 41 | 42 | googlesamples-assistant-audiotest --record-time 10 43 | 44 | - Adjust the sound device block size and flush size for a soundcard with limited throughput:: 45 | 46 | googlesamples-assistant-audiotest --record-time 10 --audio-block-size=3200 --audio-flush-size=6400 47 | 48 | The same ``--audio-block-size`` and ``--audio-flush-size`` options can 49 | be used on the ``gRPC`` samples included in the SDK. 50 | 51 | googlesamples-assistant-devicetool 52 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 53 | 54 | This tool allows you to register Google Assistant device models and 55 | instances and associate them with Device Actions traits. 56 | 57 | - Install the sample's dependencies:: 58 | 59 | sudo apt-get install portaudio19-dev libffi-dev libssl-dev 60 | pip install --upgrade google-assistant-sdk[samples] 61 | 62 | - Show the CLI tool usage:: 63 | 64 | googlesamples-assistant-devicetool --help 65 | 66 | - Register a new device model and new device instance (after replacing the 'placeholder values' between quotes):: 67 | 68 | googlesamples-assistant-devicetool --project-id PROJECT_ID register --model 'my-model-identifier' \ 69 | --type LIGHT --trait action.devices.traits.OnOff \ 70 | --manufacturer 'Assistant SDK developer' \ 71 | --product-name 'Assistant SDK light' \ 72 | --description 'Assistant SDK light device' \ 73 | --device 'my-device-identifier' \ 74 | --nickname 'My Assistant Light' 75 | 76 | - Register or overwrite the device model with the supported traits (after replacing the 'placeholder values' between quotes):: 77 | 78 | googlesamples-assistant-devicetool --project-id PROJECT_ID register-model --model 'my-model-identifier' \ 79 | --type LIGHT --trait action.devices.traits.OnOff \ 80 | --manufacturer 'Assistant SDK developer' \ 81 | --product-name 'Assistant SDK light' \ 82 | --description 'Assistant SDK light device' 83 | 84 | *Note: The model identifier must be globally unique.* 85 | 86 | - Register or overwrite the device instance using the device model (after replacing the 'placeholder values' between quotes):: 87 | 88 | googlesamples-assistant-devicetool --project-id PROJECT_ID register-device --device 'my-device-identifier' \ 89 | --model 'my-model-identifier' \ 90 | --nickname 'My Assistant Light' 91 | 92 | *Note: The device instance identifier should be unique within the Google Developer Project associated with the device.* 93 | 94 | - Verify that the device model and instance have been registered correctly:: 95 | 96 | googlesamples-assistant-devicetool --project-id PROJECT_ID get --model 'my-model-identifier' 97 | googlesamples-assistant-devicetool --project-id PROJECT_ID get --device 'my-device-identifier' 98 | 99 | - List all device models and instances:: 100 | 101 | googlesamples-assistant-devicetool --project-id PROJECT_ID list --model 102 | googlesamples-assistant-devicetool --project-id PROJECT_ID list --device 103 | 104 | googlesamples-assistant-pushtotalk 105 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 106 | 107 | This reference sample implements a simple but functional client for the `Google Assistant Service`_. 108 | 109 | - Install the sample's dependencies:: 110 | 111 | sudo apt-get install portaudio19-dev libffi-dev libssl-dev 112 | pip install --upgrade google-assistant-sdk[samples] 113 | 114 | - Run the push to talk sample. The sample records a voice query after a key press and plays back the Google Assistant's answer:: 115 | 116 | googlesamples-assistant-pushtotalk --device-model-id 'my-device-model' --device-id 'my-device-identifier' 117 | 118 | - Try some Google Assistant voice query like "What time is it?" or "Who am I?". 119 | 120 | - Try a device action query like "Turn on". 121 | 122 | - Run in verbose mode to see the gRPC communication with the Google Assistant API:: 123 | 124 | googlesamples-assistant-pushtotalk --device-model-id 'my-device-model' --device-id 'my-device-identifier' -v 125 | 126 | Also see the `grpc sample README `_. 127 | 128 | googlesamples-assistant-hotword 129 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 130 | 131 | This reference sample implements a simple but functional client for the `Google Assistant Library`_ (``linux_arm7l`` and ``linux_x86_64``). 132 | 133 | - Install the ``google-assistant-library`` package:: 134 | 135 | pip install --upgrade google-assistant-library 136 | pip install --upgrade google-assistant-sdk[samples] 137 | 138 | - Try the hotword sample:: 139 | 140 | googlesamples-assistant-hotword --device_model_id 'my-model-identifier' 141 | 142 | Also see the sample `library sample README `_. 143 | 144 | For Maintainers 145 | --------------- 146 | 147 | See `MAINTAINER.md `_ for more documentation on the 148 | development, maintainance and release of the Python package itself. 149 | 150 | Contributing 151 | ------------ 152 | 153 | Contributions to this repository are always welcome and highly encouraged. 154 | 155 | See `CONTRIBUTING.md `_ for more information on how to get started. 156 | 157 | License 158 | ------- 159 | 160 | Copyright (C) 2017 Google Inc. 161 | 162 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 163 | license agreements. See the NOTICE file distributed with this work for 164 | additional information regarding copyright ownership. The ASF licenses this 165 | file to you under the Apache License, Version 2.0 (the "License"); you may not 166 | use this file except in compliance with the License. You may obtain a copy of 167 | the License at 168 | 169 | http://www.apache.org/licenses/LICENSE-2.0 170 | 171 | Unless required by applicable law or agreed to in writing, software 172 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 173 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 174 | License for the specific language governing permissions and limitations under 175 | the License. 176 | 177 | .. _Python: https://python.org/ 178 | .. _pip: https://pip.pypa.io/ 179 | .. _Google Assistant SDK: https://developers.google.com/assistant/sdk 180 | .. _Google Assistant Service: https://developers.google.com/assistant/sdk/reference/rpc 181 | .. _Google Assistant Library: https://developers.google.com/assistant/sdk/reference/library/python 182 | -------------------------------------------------------------------------------- /rpi-assistant-demo/tests/test_endtoend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import builtins 17 | import json 18 | import tempfile 19 | import time 20 | import os 21 | import os.path 22 | import pytest 23 | import subprocess 24 | import uuid 25 | 26 | 27 | # Propagation delays for Device Registration API resource creation. 28 | DEVICE_MODEL_PROPAGATION_DELAY_S = 30 29 | DEVICE_INSTANCE_PROPAGATION_DELAY_S = 60 30 | 31 | PROJECT_ID = os.environ.get('PROJECT_ID', 'dummy-project-id') 32 | 33 | 34 | @pytest.fixture(scope='session') 35 | def device_model(): 36 | device_model_id = 'assistant-sdk-test-model-%s' % str(uuid.uuid1()) 37 | subprocess.check_call(['python', '-m', 38 | 'googlesamples.assistant.grpc.devicetool', 39 | '--project-id', PROJECT_ID, 40 | 'register-model', '--model', device_model_id, 41 | '--type', 'LIGHT', 42 | '--trait', 'action.devices.traits.OnOff', 43 | '--manufacturer', 'assistant-sdk-test', 44 | '--product-name', 'assistant-sdk-test']) 45 | # Wait for model registration to be consistent 46 | # on the Device Registration API. 47 | time.sleep(DEVICE_MODEL_PROPAGATION_DELAY_S) 48 | yield device_model_id 49 | subprocess.check_call(['python', '-m', 50 | 'googlesamples.assistant.grpc.devicetool', 51 | '--project-id', PROJECT_ID, 52 | 'delete', '--model', device_model_id]) 53 | 54 | 55 | @pytest.fixture(scope='session') 56 | def device_instance(device_model): 57 | device_instance_id = 'assistant-sdk-test-device-%s' % str(uuid.uuid1()) 58 | subprocess.check_call(['python', '-m', 59 | 'googlesamples.assistant.grpc.devicetool', 60 | '--project-id', PROJECT_ID, 61 | 'register-device', '--model', device_model, 62 | '--client-type', 'SERVICE', 63 | '--device', device_instance_id]) 64 | # Wait for device registration to be consistent 65 | # on the Device Registration API. 66 | time.sleep(DEVICE_INSTANCE_PROPAGATION_DELAY_S) 67 | yield device_instance_id 68 | subprocess.check_call(['python', '-m', 69 | 'googlesamples.assistant.grpc.devicetool', 70 | '--project-id', PROJECT_ID, 71 | 'delete', '--device', device_instance_id]) 72 | 73 | 74 | def test_endtoend_pushtotalk(): 75 | temp_dir = tempfile.mkdtemp() 76 | audio_out_file = os.path.join(temp_dir, 'out.raw') 77 | out = subprocess.check_output(['python', '-m', 78 | 'googlesamples.assistant.grpc.pushtotalk', 79 | '--verbose', 80 | '--device-model-id', 'test-device-model', 81 | '--device-id', 'test-device', 82 | '-i', 'tests/data/whattimeisit.riff', 83 | '-o', audio_out_file], 84 | stderr=subprocess.STDOUT) 85 | print(out) 86 | assert 'what time is it' in builtins.str(out).lower() 87 | assert os.path.getsize(audio_out_file) > 0 88 | 89 | 90 | def test_endtoend_pushtotalk_htmloutput(device_model, device_instance): 91 | temp_dir = tempfile.mkdtemp() 92 | audio_out_file = os.path.join(temp_dir, 'out.raw') 93 | env = os.environ.copy() 94 | env['TMPDIR'] = temp_dir 95 | out = subprocess.check_output(['python', '-m', 96 | 'googlesamples.assistant.grpc.pushtotalk', 97 | '--verbose', 98 | '--device-model-id', device_model, 99 | '--device-id', device_instance, 100 | '-i', 'tests/data/grapefruit.riff', 101 | '--display', 102 | '-o', audio_out_file], 103 | stderr=subprocess.STDOUT, env=env) 104 | print(out) 105 | assert 'grapefruit' in builtins.str(out).lower() 106 | assert os.path.getsize(audio_out_file) > 0 107 | files = [os.path.join(path, f) 108 | for path, _, fs in os.walk(temp_dir) for f in fs] 109 | assert len(files) > 0 110 | screen_out = None 111 | for f in files: 112 | if os.path.basename(f) == 'google-assistant-sdk-screen-out.html': 113 | screen_out = f 114 | assert screen_out is not None 115 | with open(screen_out, 'r') as f: 116 | assert 'pamplemousse' in f.read() 117 | 118 | 119 | def test_registration_pushtotalk(device_model): 120 | temp_dir = tempfile.mkdtemp() 121 | audio_out_file = os.path.join(temp_dir, 'out.raw') 122 | # Use an non-existing device config file intentionally 123 | # to force device registration. 124 | device_config = os.path.join(temp_dir, 'device_config.json') 125 | out = subprocess.check_output(['python', '-m', 126 | 'googlesamples.assistant.grpc.pushtotalk', 127 | '--verbose', 128 | '--project-id', PROJECT_ID, 129 | '--device-model-id', device_model, 130 | '--device-config', device_config, 131 | '-i', 'tests/data/whattimeisit.riff', 132 | '-o', audio_out_file], 133 | stderr=subprocess.STDOUT) 134 | assert 'what time is it' in builtins.str(out).lower() 135 | assert os.path.getsize(audio_out_file) > 0 136 | with open(device_config) as f: 137 | config = json.load(f) 138 | assert ('device registered: %s' % config['id'] 139 | in builtins.str(out).lower()) 140 | out = subprocess.check_output( 141 | ['python', '-m', 142 | 'googlesamples.assistant.grpc.devicetool', 143 | '--project-id', PROJECT_ID, 144 | 'get', '--device', config['id']], 145 | stderr=subprocess.STDOUT 146 | ) 147 | print(out) 148 | assert ('device instance id: %s' % config['id'] 149 | in builtins.str(out).lower()) 150 | subprocess.check_call(['python', '-m', 151 | 'googlesamples.assistant.grpc.devicetool', 152 | '--project-id', PROJECT_ID, 153 | 'delete', '--device', config['id']]) 154 | 155 | 156 | def test_endtoend_textinput(device_model, device_instance): 157 | p = subprocess.Popen(['python', '-m', 158 | 'googlesamples.assistant.grpc.textinput', 159 | '--verbose', 160 | '--device-model-id', device_model, 161 | '--device-id', device_instance], 162 | stdin=subprocess.PIPE, 163 | stdout=subprocess.PIPE) 164 | out, err = p.communicate(b'how do you say grapefruit in French?') 165 | print(out) 166 | out = builtins.str(out).lower() 167 | assert err is None 168 | assert 'grapefruit' in out 169 | assert 'pamplemousse' in out 170 | 171 | 172 | def test_endtoend_textinput_htmloutput(device_model, device_instance): 173 | temp_dir = tempfile.mkdtemp() 174 | env = os.environ.copy() 175 | env['TMPDIR'] = temp_dir 176 | p = subprocess.Popen(['python', '-m', 177 | 'googlesamples.assistant.grpc.textinput', 178 | '--verbose', 179 | '--device-model-id', device_model, 180 | '--device-id', device_instance, 181 | '--display'], 182 | stdin=subprocess.PIPE, 183 | stdout=subprocess.PIPE, 184 | env=env) 185 | out, err = p.communicate(b'how do you say grapefruit in French?') 186 | print(out) 187 | out = builtins.str(out).lower() 188 | assert err is None 189 | assert 'grapefruit' in out 190 | files = [os.path.join(path, f) 191 | for path, _, fs in os.walk(temp_dir) for f in fs] 192 | assert len(files) == 1 193 | assert os.path.basename(files[0]) == 'google-assistant-sdk-screen-out.html' 194 | with open(files[0], 'r') as f: 195 | assert 'pamplemousse' in f.read() 196 | 197 | 198 | def test_endtoend_audiofileinput(device_model, device_instance): 199 | temp_dir = tempfile.mkdtemp() 200 | audio_out_file = os.path.join(temp_dir, 'out.raw') 201 | out = subprocess.check_output( 202 | ['python', '-m', 203 | 'googlesamples.assistant.grpc.audiofileinput', 204 | '--verbose', 205 | '--device-model-id', device_model, 206 | '--device-id', device_instance, 207 | '-i', 'tests/data/whattimeisit.riff', 208 | '-o', audio_out_file], 209 | stderr=subprocess.STDOUT) 210 | print(out) 211 | assert 'what time is it' in builtins.str(out).lower() 212 | assert os.path.getsize(audio_out_file) > 0 213 | 214 | 215 | def test_onoff_device_action(device_model, device_instance): 216 | temp_dir = tempfile.mkdtemp() 217 | audio_out_file = os.path.join(temp_dir, 'out.raw') 218 | out = subprocess.check_output(['python', '-m', 219 | 'googlesamples.assistant.grpc.pushtotalk', 220 | '--verbose', 221 | '--project-id', PROJECT_ID, 222 | '--device-model-id', device_model, 223 | '--device-id', device_instance, 224 | '-i', 'tests/data/turnon.riff', 225 | '-o', audio_out_file], 226 | stderr=subprocess.STDOUT) 227 | print(out) 228 | assert 'turning device on' in builtins.str(out).lower() 229 | assert os.path.getsize(audio_out_file) > 0 230 | -------------------------------------------------------------------------------- /rpi-assistant-demo/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 The Android Open Source Project 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /rpi-assistant-demo/googlesamples/assistant/grpc/audio_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Helper functions for audio streams.""" 16 | 17 | import array 18 | import logging 19 | import math 20 | import time 21 | import threading 22 | import wave 23 | 24 | import click 25 | import sounddevice as sd 26 | 27 | 28 | DEFAULT_AUDIO_SAMPLE_RATE = 16000 29 | DEFAULT_AUDIO_SAMPLE_WIDTH = 2 30 | DEFAULT_AUDIO_ITER_SIZE = 3200 31 | DEFAULT_AUDIO_DEVICE_BLOCK_SIZE = 6400 32 | DEFAULT_AUDIO_DEVICE_FLUSH_SIZE = 25600 33 | 34 | 35 | def normalize_audio_buffer(buf, volume_percentage, sample_width=2): 36 | """Adjusts the loudness of the audio data in the given buffer. 37 | 38 | Volume normalization is done by scaling the amplitude of the audio 39 | in the buffer by a scale factor of 2^(volume_percentage/100)-1. 40 | For example, 50% volume scales the amplitude by a factor of 0.414, 41 | and 75% volume scales the amplitude by a factor of 0.681. 42 | For now we only sample_width 2. 43 | 44 | Args: 45 | buf: byte string containing audio data to normalize. 46 | volume_percentage: volume setting as an integer percentage (1-100). 47 | sample_width: size of a single sample in bytes. 48 | """ 49 | if sample_width != 2: 50 | raise Exception('unsupported sample width:', sample_width) 51 | scale = math.pow(2, 1.0*volume_percentage/100)-1 52 | # Construct array from bytes based on sample_width, multiply by scale 53 | # and convert it back to bytes 54 | arr = array.array('h', buf) 55 | for idx in range(0, len(arr)): 56 | arr[idx] = int(arr[idx]*scale) 57 | buf = arr.tostring() 58 | return buf 59 | 60 | 61 | def align_buf(buf, sample_width): 62 | """In case of buffer size not aligned to sample_width pad it with 0s""" 63 | remainder = len(buf) % sample_width 64 | if remainder != 0: 65 | buf += b'\0' * (sample_width - remainder) 66 | return buf 67 | 68 | 69 | class WaveSource(object): 70 | """Audio source that reads audio data from a WAV file. 71 | 72 | Reads are throttled to emulate the given sample rate and silence 73 | is returned when the end of the file is reached. 74 | 75 | Args: 76 | fp: file-like stream object to read from. 77 | sample_rate: sample rate in hertz. 78 | sample_width: size of a single sample in bytes. 79 | """ 80 | def __init__(self, fp, sample_rate, sample_width): 81 | self._fp = fp 82 | try: 83 | self._wavep = wave.open(self._fp, 'r') 84 | except wave.Error as e: 85 | logging.warning('error opening WAV file: %s, ' 86 | 'falling back to RAW format', e) 87 | self._fp.seek(0) 88 | self._wavep = None 89 | self._sample_rate = sample_rate 90 | self._sample_width = sample_width 91 | self._sleep_until = 0 92 | 93 | def read(self, size): 94 | """Read bytes from the stream and block until sample rate is achieved. 95 | 96 | Args: 97 | size: number of bytes to read from the stream. 98 | """ 99 | now = time.time() 100 | missing_dt = self._sleep_until - now 101 | if missing_dt > 0: 102 | time.sleep(missing_dt) 103 | self._sleep_until = time.time() + self._sleep_time(size) 104 | data = (self._wavep.readframes(size) 105 | if self._wavep 106 | else self._fp.read(size)) 107 | # When reach end of audio stream, pad remainder with silence (zeros). 108 | if not data: 109 | return b'\x00' * size 110 | return data 111 | 112 | def close(self): 113 | """Close the underlying stream.""" 114 | if self._wavep: 115 | self._wavep.close() 116 | self._fp.close() 117 | 118 | def _sleep_time(self, size): 119 | sample_count = size / float(self._sample_width) 120 | sample_rate_dt = sample_count / float(self._sample_rate) 121 | return sample_rate_dt 122 | 123 | def start(self): 124 | pass 125 | 126 | def stop(self): 127 | pass 128 | 129 | @property 130 | def sample_rate(self): 131 | return self._sample_rate 132 | 133 | 134 | class WaveSink(object): 135 | """Audio sink that writes audio data to a WAV file. 136 | 137 | Args: 138 | fp: file-like stream object to write data to. 139 | sample_rate: sample rate in hertz. 140 | sample_width: size of a single sample in bytes. 141 | """ 142 | def __init__(self, fp, sample_rate, sample_width): 143 | self._fp = fp 144 | self._wavep = wave.open(self._fp, 'wb') 145 | self._wavep.setsampwidth(sample_width) 146 | self._wavep.setnchannels(1) 147 | self._wavep.setframerate(sample_rate) 148 | 149 | def write(self, data): 150 | """Write bytes to the stream. 151 | 152 | Args: 153 | data: frame data to write. 154 | """ 155 | self._wavep.writeframes(data) 156 | 157 | def close(self): 158 | """Close the underlying stream.""" 159 | self._wavep.close() 160 | self._fp.close() 161 | 162 | def start(self): 163 | pass 164 | 165 | def stop(self): 166 | pass 167 | 168 | def flush(self): 169 | pass 170 | 171 | 172 | class SoundDeviceStream(object): 173 | """Audio stream based on an underlying sound device. 174 | 175 | It can be used as an audio source (read) and a audio sink (write). 176 | 177 | Args: 178 | sample_rate: sample rate in hertz. 179 | sample_width: size of a single sample in bytes. 180 | block_size: size in bytes of each read and write operation. 181 | flush_size: size in bytes of silence data written during flush operation. 182 | """ 183 | def __init__(self, sample_rate, sample_width, block_size, flush_size): 184 | if sample_width == 2: 185 | audio_format = 'int16' 186 | else: 187 | raise Exception('unsupported sample width:', sample_width) 188 | self._audio_stream = sd.RawStream( 189 | samplerate=sample_rate, dtype=audio_format, channels=1, 190 | blocksize=int(block_size/2), # blocksize is in number of frames. 191 | ) 192 | self._block_size = block_size 193 | self._flush_size = flush_size 194 | self._sample_rate = sample_rate 195 | 196 | def read(self, size): 197 | """Read bytes from the stream.""" 198 | buf, overflow = self._audio_stream.read(size) 199 | if overflow: 200 | logging.warning('SoundDeviceStream read overflow (%d, %d)', 201 | size, len(buf)) 202 | return bytes(buf) 203 | 204 | def write(self, buf): 205 | """Write bytes to the stream.""" 206 | underflow = self._audio_stream.write(buf) 207 | if underflow: 208 | logging.warning('SoundDeviceStream write underflow (size: %d)', 209 | len(buf)) 210 | return len(buf) 211 | 212 | def flush(self): 213 | if self._audio_stream.active and self._flush_size > 0: 214 | self._audio_stream.write(b'\x00' * self._flush_size) 215 | 216 | def start(self): 217 | """Start the underlying stream.""" 218 | if not self._audio_stream.active: 219 | self._audio_stream.start() 220 | 221 | def stop(self): 222 | """Stop the underlying stream.""" 223 | if self._audio_stream.active: 224 | self._audio_stream.stop() 225 | 226 | def close(self): 227 | """Close the underlying stream and audio interface.""" 228 | if self._audio_stream: 229 | self.stop() 230 | self._audio_stream.close() 231 | self._audio_stream = None 232 | 233 | @property 234 | def sample_rate(self): 235 | return self._sample_rate 236 | 237 | 238 | class ConversationStream(object): 239 | """Audio stream that supports half-duplex conversation. 240 | 241 | A conversation is the alternance of: 242 | - a recording operation 243 | - a playback operation 244 | 245 | Excepted usage: 246 | 247 | For each conversation: 248 | - start_recording() 249 | - read() or iter() 250 | - stop_recording() 251 | - start_playback() 252 | - write() 253 | - stop_playback() 254 | 255 | When conversations are finished: 256 | - close() 257 | 258 | Args: 259 | source: file-like stream object to read input audio bytes from. 260 | sink: file-like stream object to write output audio bytes to. 261 | iter_size: read size in bytes for each iteration. 262 | sample_width: size of a single sample in bytes. 263 | """ 264 | def __init__(self, source, sink, iter_size, sample_width): 265 | self._source = source 266 | self._sink = sink 267 | self._iter_size = iter_size 268 | self._sample_width = sample_width 269 | self._volume_percentage = 50 270 | self._stop_recording = threading.Event() 271 | self._source_lock = threading.RLock() 272 | self._recording = False 273 | self._playing = False 274 | 275 | def start_recording(self): 276 | """Start recording from the audio source.""" 277 | self._recording = True 278 | self._stop_recording.clear() 279 | self._source.start() 280 | 281 | def stop_recording(self): 282 | """Stop recording from the audio source.""" 283 | self._stop_recording.set() 284 | with self._source_lock: 285 | self._source.stop() 286 | self._recording = False 287 | 288 | def start_playback(self): 289 | """Start playback to the audio sink.""" 290 | self._playing = True 291 | self._sink.start() 292 | 293 | def stop_playback(self): 294 | """Stop playback from the audio sink.""" 295 | self._sink.flush() 296 | self._sink.stop() 297 | self._playing = False 298 | 299 | @property 300 | def recording(self): 301 | return self._recording 302 | 303 | @property 304 | def playing(self): 305 | return self._playing 306 | 307 | @property 308 | def volume_percentage(self): 309 | """The current volume setting as an integer percentage (1-100).""" 310 | return self._volume_percentage 311 | 312 | @volume_percentage.setter 313 | def volume_percentage(self, new_volume_percentage): 314 | self._volume_percentage = new_volume_percentage 315 | 316 | def read(self, size): 317 | """Read bytes from the source (if currently recording). 318 | """ 319 | with self._source_lock: 320 | return self._source.read(size) 321 | 322 | def write(self, buf): 323 | """Write bytes to the sink (if currently playing). 324 | """ 325 | buf = align_buf(buf, self._sample_width) 326 | buf = normalize_audio_buffer(buf, self.volume_percentage) 327 | return self._sink.write(buf) 328 | 329 | def close(self): 330 | """Close source and sink.""" 331 | self._source.close() 332 | self._sink.close() 333 | 334 | def __iter__(self): 335 | """Returns a generator reading data from the stream.""" 336 | while True: 337 | if self._stop_recording.is_set(): 338 | return 339 | yield self.read(self._iter_size) 340 | 341 | @property 342 | def sample_rate(self): 343 | return self._source._sample_rate 344 | 345 | 346 | @click.command() 347 | @click.option('--record-time', default=5, 348 | metavar='', show_default=True, 349 | help='Record time in secs') 350 | @click.option('--audio-sample-rate', 351 | default=DEFAULT_AUDIO_SAMPLE_RATE, 352 | metavar='