Skip to content

Commit

Permalink
Add hotword support
Browse files Browse the repository at this point in the history
  • Loading branch information
titilambert committed Aug 31, 2016
1 parent a8213da commit 14a64bf
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ build/
dist
tuxeatpi/locale/messages.pot
*.mo
passive-listen.log
pocketsphinx-data/
4 changes: 2 additions & 2 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
ignore=tuxeatpi/locale

# Pickle collected data for later comparisons.
persistent=yes
Expand Down Expand Up @@ -198,7 +198,7 @@ ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus extisting member attributes cannot be deduced by static analysis
ignored-modules=
ignored-modules=tuxeatpi/experimental

# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
Expand Down
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ lang-gen:
cd tuxeatpi/locale/fr/LC_MESSAGES/ && msgfmt tuxeatpi.po -o tuxeatpi.mo
cd tuxeatpi/locale/en/LC_MESSAGES/ && msgfmt tuxeatpi.po -o tuxeatpi.mo

hotword-fr:
# Get acoustic model fr
mkdir -p pocketsphinx-data/fra-FRA/
ln -s fra-FRA pocketsphinx-data/fra-CAN
wget "https://sourceforge.net/projects/cmusphinx/files/Acoustic%20and%20Language%20Models/French/cmusphinx-fr-ptm-5.2.tar.gz/download" -O pocketsphinx-data/fra-FRA/cmusphinx-fr-ptm-5.2.tar.gz
cd pocketsphinx-data/fra-FRA/ && tar vxf cmusphinx-fr-ptm-5.2.tar.gz && mv cmusphinx-*-5.2 acoustic-model && rm -f cmusphinx-*-5.2.tar.gz
wget "https://sourceforge.net/projects/cmusphinx/files/Acoustic%20and%20Language%20Models/French/fr-small.lm.bin/download" -O pocketsphinx-data/fra-FRA/language-model.lm.bin
# Get dict
wget "https://sourceforge.net/projects/cmusphinx/files/Acoustic%20and%20Language%20Models/French/fr.dict/download" -O pocketsphinx-data/fra-FRA/pronounciation-dictionary.dict

hotword-en:
# Get acoustic model en
mkdir -p pocketsphinx-data
cp -r `python -c "import speech_recognition as sr;import os;print(os.path.dirname(os.path.abspath(sr.__file__)))"`/pocketsphinx-data/en-US pocketsphinx-data/eng-USA

hotword-clean:
rm -rf pocketsphinx-data

#######################################
### Test targets
#######################################
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ aiohttp==0.16.2
asyncio==3.4.3
pyaudio==0.2.8
git+https://github.com/NuanceDev/pyspeex.git@0.9.0#egg=pyspeex
SpeechRecognition==3.4.6
pocketsphinx==0.1.1
3 changes: 3 additions & 0 deletions tests/tux/conf/tux_tests_conf_1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ tux:
global:
gender: male
name: TuxDroid
hotword:
hotword: hello
model_dir: pocketsphinx-data
pins:
wings:
left_switch: 17
Expand Down
3 changes: 3 additions & 0 deletions tests/voice/conf/voice_tests_conf_1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ tux:
fake: true
log_level: DEBUG
data: {}
hotword:
hotword: hello
model_dir: pocketsphinx-data
global:
gender: male
name: TuxDroid
Expand Down
Empty file added tuxeatpi/hotword/__init__.py
Empty file.
82 changes: 82 additions & 0 deletions tuxeatpi/hotword/hotword.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Hotword component"""

import os
from multiprocessing import Process

import pyaudio
from pocketsphinx.pocketsphinx import Decoder


class HotWord(Process):
"""Define hotword component
For now hotword uses pocketsphinx with speech_recognition
"""
def __init__(self, settings, logger):
Process.__init__(self)
# Set logger
self.logger = logger.getChild("Hotword")
self.logger.debug("Initialization")
# Init private attributes
self._settings = settings
self._must_run = False
self._config = Decoder.default_config()
self.prepare_decoder()

def prepare_decoder(self):
"""Set decoder config"""
# prepare config
self._hotword = self._settings['hotword']['hotword']
acoustic_model = os.path.join(self._settings['hotword']['model_dir'],
self._settings['speech']['language'],
'acoustic-model',
)
language_model = os.path.join(self._settings['hotword']['model_dir'],
self._settings['speech']['language'],
'language-model.lm.bin',
)
pocket_dict = os.path.join(self._settings['hotword']['model_dir'],
self._settings['speech']['language'],
'pronounciation-dictionary.dict',
)
self._config.set_string('-logfn', "/dev/null")
self._config.set_string('-hmm', acoustic_model)
self._config.set_string('-lm', language_model)
self._config.set_string('-dict', pocket_dict)
self._decoder = Decoder(self._config)
self._decoder.set_keyphrase('wakeup', self._hotword)
self._decoder.set_search('wakeup')

def stop(self):
"""Stop process"""
self._must_run = False
self.terminate()

def run(self):
"""Text to speech"""
self._must_run = True
rerun = True
self.logger.debug("starting listening hotword %s", self._hotword)
while rerun:
rerun = False
self._paudio = pyaudio.PyAudio()
stream = self._paudio.open(format=pyaudio.paInt16, channels=1, rate=16000,
input=True, frames_per_buffer=1024)
stream.start_stream()
self._paudio.get_default_input_device_info()

self._decoder.start_utt()
while self._must_run:
buf = stream.read(1024)
self._decoder.process_raw(buf, False, False)
if self._decoder.hyp() and self._decoder.hyp().hypstr == self._hotword:
self.logger.debug("Hotword detected")
# TODO run nlu audio detection
rerun = True
break
self._decoder.end_utt()


class HotWordError(Exception):
"""Base class for voice exceptions"""
pass
2 changes: 1 addition & 1 deletion tuxeatpi/libs/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, config_file, logger):
def _check_conf(self):
"""Check settings validity"""
# Check main keys
for key in ['global', 'advanced', 'pins', 'speech']:
for key in ['global', 'advanced', 'pins', 'speech', 'hotword']:
if key not in self:
raise SettingsError("Missing {} key".format(key))
# Check sections
Expand Down
6 changes: 6 additions & 0 deletions tuxeatpi/tux.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from tuxeatpi.voice.voice import Voice
from tuxeatpi.actionner.actionner import Actionner
from tuxeatpi.nlu.nlu import NLU
from tuxeatpi.hotword.hotword import HotWord
from tuxeatpi.fake_components.wings import FakeWings
from tuxeatpi.libs.settings import Settings, SettingsError

Expand Down Expand Up @@ -54,6 +55,9 @@ def __init__(self, config_file):
self.event_queue,
self.logger)

# hotword
self.hotword = HotWord(self.settings, self.logger)
self.hotword.start()
# Init action
self.actionner = Actionner(self)
self.actionner.start()
Expand All @@ -80,6 +84,8 @@ def shutdown(self):
self.nlu.stop()
if hasattr(self, 'actionner'):
self.actionner.stop()
if hasattr(self, 'hotword'):
self.hotword.stop()
GPIO.cleanup()

def _birth(self):
Expand Down

0 comments on commit 14a64bf

Please sign in to comment.