Skip to content

Commit

Permalink
Successful local tests after MQTT switch
Browse files Browse the repository at this point in the history
  • Loading branch information
synesthesiam committed Jan 13, 2019
1 parent 5f2d196 commit 01a5933
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 148 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__pycache__/
__pycache__/
.venv/
20 changes: 9 additions & 11 deletions mycroft-precise/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
FROM python:3.6
FROM resin/rpi-raspbian:stretch
LABEL maintainer="Michael Hansen <[email protected]>"

# Install system packages
RUN apt-get update && apt-get install -y python3-dev build-essential \
RUN apt-get update && apt-get install -y python3 \
python3-dev build-essential \
python3-setuptools \
python3-scipy python3-numpy \
libhdf5-dev \
portaudio19-dev alsa-utils \
jq libnanomsg-dev
portaudio19-dev \
alsa-utils \
jq

RUN pip3 install --no-cache-dir wheel
RUN pip3 install --no-cache-dir flask
COPY etc/nanomsg-python-master.zip /

# Install nanomsg
RUN pip3 install --no-cache-dir /nanomsg-python-master.zip
RUN pip3 install --no-cache-dir paho-mqtt

# Install tensorflow
RUN pip3 install --no-cache-dir tensorflow==1.8.0
Expand All @@ -21,9 +22,6 @@ RUN pip3 install --no-cache-dir tensorflow==1.8.0
RUN pip3 install --no-cache-dir pkgconfig cython
RUN pip3 install --no-cache-dir mycroft-precise==0.2.0

# Add random sounds for training
#COPY etc/pbsounds /pbsounds

# Patch precise runner to use acrecord instead of PyAudio
COPY src/precise_runner/runner.py /usr/lib/python3.6/site-packages/precise_runner/
COPY src/precise/scripts/listen.py /usr/lib/python3.6/site-packages/precise/scripts/
Expand Down
22 changes: 16 additions & 6 deletions mycroft-precise/config.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
{
"name": "Mycroft Precise Wake System for Rhasspy",
"slug": "precise-rhasspy",
"version": "0.2.0-1",
"version": "0.2.0-2",
"description": "Mycroft Precise wake word detection (Mycroft.ai) for Rhasspy voice assistant",
"startup": "application",
"boot": "auto",
"options": {
"pub_address": "tcp://75f2ff60-rhasspy:5000",
"pull_address": "tcp://75f2ff60-rhasspy:5001",
"host": "localhost",
"port": 1883,
"username": "",
"password": "",
"reconnect": 5,
"site_id": "default",
"wakeword_id": "default",
"model": "/models/okay-rhasspy.pb",
"sensitivity": 0.5,
"trigger_level": 3
"trigger_level": 3,
},
"ports": {
"12103/tcp": 12103
Expand All @@ -19,8 +24,13 @@
"model": "str",
"sensitivity": "float",
"trigger_level": "int",
"pub_address": "str",
"pull_address": "str"
"host": "str",
"port": "int",
"username": "str",
"password": "str",
"reconnect": "float",
"site_id": "str",
"wakeword_id": "str"
},
"webui": "http://[HOST]:[PORT:12103]/"
}
Binary file removed mycroft-precise/etc/nanomsg-python-master.zip
Binary file not shown.
200 changes: 138 additions & 62 deletions mycroft-precise/main.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
#!/usr/bin/env python3
import os
import io
import json
import wave
import argparse
import subprocess
import threading
import logging
logging.basicConfig(level=logging.DEBUG)

from precise_runner import PreciseEngine, PreciseRunner
from nanomsg import Socket, SUB, SUB_SUBSCRIBE, PUSH
import paho.mqtt.client as mqtt

def main():
# Parse arguments
parser = argparse.ArgumentParser(description='mycroft-precise')
parser.add_argument('--pub-address',
help='nanomsg address of PUB socket (default=tcp://127.0.0.1:5000)',
type=str, default='tcp://127.0.0.1:5000')
parser.add_argument('--host',
help='MQTT host (default=localhost)',
type=str, default='localhost')

parser.add_argument('--pull-address',
help='nanomsg address of PULL socket (default=tcp://127.0.0.1:5001)',
type=str, default='tcp://127.0.0.1:5001')
parser.add_argument('--port',
help='MQTT port (default=1883)',
type=int, default=1883)

parser.add_argument('--payload', help='Payload string to send when wake word is detected (default=OK)',
type=str, default='OK')
parser.add_argument('--username',
help='MQTT username (default=)',
type=str, default='')

parser.add_argument('--password',
help='MQTT password (default=)',
type=str, default='')

parser.add_argument('--reconnect',
help='Seconds before MQTT reconnect (default=5, disabled=0)',
type=float, default=5)

parser.add_argument('--site-id', help='Hermes siteId (default=default)',
type=str, default='default')

parser.add_argument('--wakeword-id', help='Hermes wakewordId (default=default)',
type=str, default='default')

parser.add_argument('--model',
type=str,
Expand All @@ -39,65 +57,121 @@ def main():
parser.add_argument('--feedback', help='Show printed feedback', action='store_true')
args = parser.parse_args()

topic_audio_frame = 'hermes/audioServer/%s/audioFrame' % args.site_id
topic_hotword_detected = 'hermes/hotword/%s/detected' % args.wakeword_id

# Create runner
engine = PreciseEngine('precise-engine', args.model)
engine = PreciseEngine('precise-engine', args.model, chunk_size=4096)
stream = ByteStream()

with Socket(PUSH) as push_socket:
# Response is sent via nanomsg
push_socket.connect(args.pull_address)
logging.info('Connected to PULL socket at %s' % args.pull_address)

first_frame = False

def on_activation():
nonlocal first_frame

# Hotword detected
if args.feedback:
print('!', end='', flush=True)

push_socket.send(payload) # response
logging.info('Wake word detected!')
first_frame = False

runner = PreciseRunner(engine, stream=stream,
sensitivity=args.sensitivity,
trigger_level=args.trigger_level,
on_activation=on_activation)

runner.start()

# Do detection
client = mqtt.Client()

# Login
if len(args.username) > 0:
logging.debug('Logging in as %s' % args.username)
client.username_pw_set(args.username, args.password)

first_frame = True

def on_activation():
nonlocal first_frame
if args.feedback:
print('!', end='', flush=True)

logging.debug('Hotword detected!')
payload = json.dumps({
'siteId': args.site_id,
'modelId': args.model,
'modelVersion': '',
'modelType': 'personal',
'currentSensitivity': args.sensitivity
}).encode()

client.publish(topic_hotword_detected, payload)
first_frame = True

runner = PreciseRunner(engine, stream=stream,
sensitivity=args.sensitivity,
trigger_level=args.trigger_level,
on_activation=on_activation)

# Set up MQTT
def on_connect(client, userdata, flags, rc):
client.subscribe(topic_audio_frame)
client.subscribe('hermes/hotword/toggleOn')
client.subscribe('hermes/hotword/toggleOff')
logging.debug('Connected to %s:%s' % (args.host, args.port))

listening = True
def on_message(client, userdata, message):
nonlocal first_frame, listening
try:
payload = args.payload.encode()

# Receive raw audio data via nanomsg
with Socket(SUB) as sub_socket:
sub_socket.connect(args.pub_address)
sub_socket.set_string_option(SUB, SUB_SUBSCRIBE, '')
logging.info('Connected to PUB socket at %s' % args.pub_address)

while True:
data = sub_socket.recv() # audio data
if args.feedback:
print('.', end='', flush=True)

if not first_frame:
logging.debug('Receiving audio data from Rhasspy')
first_frame = True
if message.topic == topic_audio_frame:
if not listening:
return

if first_frame:
logging.debug('Receiving audio data')
first_frame = False

if args.feedback:
print('.', end='', flush=True)

# Extract audio data
with io.BytesIO(message.payload) as wav_buffer:
with wave.open(wav_buffer, mode='rb') as wav_file:
audio_data = wav_file.readframes(wav_file.getnframes())
stream.write(audio_data)

elif message.topic == 'hermes/hotword/toggleOn':
listening = True
logging.debug('On')
elif message.topic == 'hermes/hotword/toggleOff':
listening = False
logging.debug('Off')
except Exception as e:
logging.exception('on_message')

client.on_connect = on_connect
client.on_message = on_message

def on_disconnect(client, userdata, rc):
logging.warn('Disconnected')

if args.reconnect > 0:
time.sleep(args.reconnect)
logging.debug('Reconnecting')
client.connect(args.host, args.port)

client.on_disconnect = on_disconnect

connected = False
while not connected:
try:
client.connect(args.host, args.port)
connected = True
except Exception as e:
logging.exception('connect')

if args.reconnect > 0:
time.sleep(args.reconnect)
logging.debug('Reconnecting')
else:
return

# Write to in-memory stream
stream.write(data)
runner.start()

except KeyboardInterrupt:
pass
try:
logging.info('Listening')
client.loop_forever()
except KeyboardInterrupt:
pass

try:
stream.close()
runner.stop()
except:
pass
try:
stream.close()
runner.stop()
except:
pass

# -----------------------------------------------------------------------------

Expand All @@ -113,6 +187,7 @@ def read(self, n=-1):
if not self.closed:
self.event.wait()
else:
# Pad with zeros
self.buffer += bytearray(n - len(self.buffer))

chunk = self.buffer[:n]
Expand All @@ -124,7 +199,8 @@ def write(self, data):
return

self.buffer += data
self.event.set()
if not self.event.is_set():
self.event.set()

def close(self):
self.closed = True
Expand Down
1 change: 1 addition & 0 deletions mycroft-precise/models/okay-rhasspy.pb.params
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"window_t": 0.1, "hop_t": 0.05, "buffer_t": 1.5, "sample_rate": 16000, "sample_depth": 2, "n_mfcc": 13, "n_filt": 20, "n_fft": 512}
18 changes: 14 additions & 4 deletions mycroft-precise/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@
DIR="$( cd "$( dirname "$0" )" && pwd )"
CONFIG_PATH="/data/options.json"

pub_address="$(jq --raw-output '.pub_address' $CONFIG_PATH)"
pull_address="$(jq --raw-output '.pull_address' $CONFIG_PATH)"
host="$(jq --raw-output '.host' $CONFIG_PATH)"
port="$(jq --raw-output '.port' $CONFIG_PATH)"
username="$(jq --raw-output '.username' $CONFIG_PATH)"
password="$(jq --raw-output '.password' $CONFIG_PATH)"
reconnect="$(jq --raw-output '.reconnect' $CONFIG_PATH)"
site_id="$(jq --raw-output '.site_id' $CONFIG_PATH)"
wakeworkd_id="$(jq --raw-output '.wakeword_id' $CONFIG_PATH)"
model="$(jq --raw-output '.model' $CONFIG_PATH)"
sensitivity="$(jq --raw-output '.sensitivity' $CONFIG_PATH)"
trigger_level="$(jq --raw-output '.trigger_level' $CONFIG_PATH)"

cd "$DIR"
FLASK_APP=app.py flask run --host=0.0.0.0 --port=12103 &
python3 main.py \
--pub-address "$pub_address" \
--pull-address "$pull_address" \
--host "$host" \
--port "$port" \
--username "$username" \
--password "$password" \
--reconnect "$reconnect" \
--site-id "$site_id" \
--wakeword-id "$wakeword_id" \
--model "$model" \
--sensitivity "$sensitivity" \
--trigger-level "$trigger_level"
14 changes: 7 additions & 7 deletions snowboy/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
FROM python:3.6
LABEL maintainer="Michael Hansen <[email protected]>"

RUN apt-get update && apt-get install -y python3-pip python3-dev \
RUN apt-get update && apt-get install -y python3 \
python3-pip python3-dev \
python3-setuptools \
build-essential swig \
jq \
libasound2-dev swig \
libasound2-dev \
portaudio19-dev \
libatlas-base-dev \
libnanomsg-dev
libatlas-base-dev

COPY snowboy-1.3.0.tar.gz /
COPY nanomsg-python-master.zip /

RUN pip3 install --no-cache-dir wheel
RUN pip3 install --no-cache-dir flask
RUN pip3 install --no-cache-dir flask paho-mqtt
RUN pip3 install --no-cache-dir /snowboy-1.3.0.tar.gz
RUN pip3 install --no-cache-dir /nanomsg-python-master.zip

COPY models/ /models
COPY *.py /
Expand Down
Loading

0 comments on commit 01a5933

Please sign in to comment.