squawk
This is an old revision of the document!
Table of Contents
Audio Alerts
Traditionally this was handled by doorpi. However, that pi has too much stuff hanging off it, and was very out of date. There was an issue of a loud pop occurring before any sound played, this was fixed in later firmware updates. Rob has now dedicated a pi (original B) for the purpose and documents it thus:
Usage
Sounds and TTS messages are triggered via the message bus.
Raspbian
Latest Raspbian minimal image from the Raspberry Pi website.
raspi-config
- Medium overclock
- Graphics mem reduced to 16M
- Audio output forced to 3.5mm
- Hostname set
RAM /tmp
sudo systemctl enable tmp.mount
Packages
sudo apt-get install mpg123 sudo apt-get install sox sudo apt-get install python-pip sudo pip install paho-mqtt
/etc/rc.local
These lines added before exit:
su -c "mpg123 /home/pi/sounds/indyboot.mp3" pi & su -c "/home/pi/respawn --syslog /home/pi/squawk.py" pi &
Scripts
Various scripts to make things happen.
/home/pi/squawk.py
#!/usr/bin/env python import paho.mqtt.client as mqtt import subprocess import os import logging import signal import time last_state = None max_playtime = 15 sounds_path = "/home/pi/sounds" # runs a command and terminates it after a specified timeout def call_with_timeout(command, timeout): logging.info('call_with_timeout(%r, %r)' % (command, timeout)) class TimeoutException(Exception): pass def alrm_handler(signum, frame): raise TimeoutException() try: old_handler = signal.signal(signal.SIGALRM, alrm_handler) signal.alarm(timeout) p = subprocess.Popen(command) retcode = p.wait() logging.info('call_with_timeout: command exited with code %s' % (retcode)) except TimeoutException: logging.info('call_with_timeout: command exceeded timeout, terminating...') p.terminate() retcode = p.wait() finally: signal.signal(signal.SIGALRM, old_handler) signal.alarm(0) return retcode # waffle waffle def speak(data, timeout=max_playtime): command = ['/home/pi/pico.sh', data] call_with_timeout(command, timeout=timeout) def getfiles(path, exts=[".mp3", ".wav"]): allfiles = [] for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: base, ext = os.path.splitext(filename) if ext in exts: #allfiles.append(os.path.relpath(os.path.join(dirpath, filename), path)) allfiles.append(os.path.join(dirpath, filename)) return allfiles # make some noise for the vengaboys def play(filename, timeout=max_playtime): allfiles = getfiles(sounds_path) filename = os.path.join(sounds_path, filename) if filename.endswith('/'): # pick a random file from a directory candidates = [] for f in allfiles: if f.startswith(filename): candidates.append(f) if len(candidates) == 0: raise Exception('No files matching %s' % (filename)) filename = random.choice(candidates) else: # single file requested if filename not in allfiles: raise Exception('File %s not found' % (filename)) base, ext = os.path.splitext(filename) if ext == '.mp3': command = ['mpg123', ' -q', filename] call_with_timeout(command, timeout=timeout) else: command = ['play', ' -q', filename] call_with_timeout(command, timeout=timeout) def on_connect(client, userdata, flags, rc): client.subscribe("sound/g1/play") client.subscribe("sound/g1/speak") def on_message(client, userdata, msg): # ignore retained (non-realtime) messages if msg.retain: return if msg.topic == 'sound/g1/play': play(msg.payload) if msg.topic == 'sound/g1/speak': play('dong.mp3') speak(msg.payload) m = mqtt.Client() m.on_connect = on_connect m.on_message = on_message m.connect("mqtt") m.loop_forever()
/home/pi/respawn
#!/usr/bin/env python # # Tim Hawes <me@timhawes.com> # April 2015 # import argparse import logging import logging.handlers import os import signal import subprocess import sys import time process = None hup_received = False term_received = False parser = argparse.ArgumentParser(description='Respawn an application.') parser.add_argument('--name', type=str, dest='name', action='store') parser.add_argument('--delay', type=int, dest='delay', action='store', default=1) parser.add_argument('--min-backoff', type=int, dest='min_backoff', action='store', default=1) parser.add_argument('--max-backoff', type=int, dest='max_backoff', action='store', default=60) parser.add_argument('--reset-backoff-after', type=int, dest='backoff_reset_after', action='store', default=30) parser.add_argument('--syslog', dest='syslog', action='store_true', default=False) parser.add_argument('--debug', dest='debug', action='store_true', default=False) parser.add_argument('command', nargs='*') args = parser.parse_args() def setup_logging(log_level=logging.INFO, syslog=True, stdout=False, ident=os.path.basename(sys.argv[0])): logger = logging.getLogger() logger.setLevel(log_level) if syslog: syslog_format_string = ident + "[%(process)d]: %(message)s" syslog_handler = logging.handlers.SysLogHandler(address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_USER) syslog_handler.log_format_string = "<%d>%s" syslog_handler.setFormatter(logging.Formatter(fmt=syslog_format_string)) syslog_handler.setLevel(log_level) logger.addHandler(syslog_handler) if stdout: stream_format_string = "%(asctime)s %(message)s" stream_handler = logging.StreamHandler(stream=sys.__stdout__) stream_handler.setFormatter(logging.Formatter(fmt=stream_format_string)) stream_handler.setLevel(log_level) logger.addHandler(stream_handler) def run(): global process start_time = time.time() process = subprocess.Popen(args.command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = process.stdout.readline() if line == '': break logging.info("< " + line.rstrip()) returncode = process.wait() runtime = time.time()-start_time process = None if returncode == 0: logging.info('exit=%d runtime=%.3f' % (returncode, runtime)) else: logging.warning('exit=%d runtime=%.3f' % (returncode, runtime)) return returncode, runtime def hup_handler(signum, frame): global process global hup_received if process is not None: logging.warning("received SIGHUP, sending SIGTERM to process") hup_received = True process.send_signal(signal.SIGTERM) else: logging.warning("received SIGHUP, but no process running") def term_handler(signum, frame): global process global term_received if process is not None: logging.warning("received SIGTERM, sending SIGTERM to process") term_received = True process.send_signal(signal.SIGTERM) else: logging.warning("received SIGTERM, but no process running") term_received = True signal.signal(signal.SIGHUP, hup_handler) signal.signal(signal.SIGTERM, term_handler) ident = os.path.basename(sys.argv[0]) if args.name is not None: ident = ident + "/" + args.name if args.debug: setup_logging(log_level=logging.DEBUG, stdout=True, syslog=False, ident=ident) else: setup_logging(log_level=logging.INFO, stdout=False, syslog=True, ident=ident) if args.delay > args.min_backoff: logging.debug('increasing min-backoff to match delay (%d)' % (args.delay)) args.min_backoff = args.delay if args.min_backoff > args.max_backoff: logging.debug('increasing max-backoff to match min-backoff (%d)' % (args.min_backoff)) args.max_backoff = args.min_backoff exit_requested = False backoff = args.min_backoff logging.info("command: %r" % (args.command)) while True: start_time = time.time() returncode, runtime = run() if term_received: logging.debug("exited after SIGTERM") break if hup_received: logging.debug("exited after SIGHUP, restarting immediately") hup_received = False continue if returncode == 0: if runtime > args.backoff_reset_after: backoff = args.min_backoff logging.debug('resetting backoff to %d' % (backoff)) else: logging.debug('delaying for %d after a successful run' % (args.delay)) time.sleep(args.delay) else: logging.info('backing-off for %d seconds' % (backoff)) time.sleep(backoff) backoff = min(backoff*2, args.max_backoff) logging.debug('next backoff will be %d seconds' % (backoff)) logging.info('exiting respawn')
/home/pi/pico.sh
#!/bin/bash pico2wave -l en-GB -w /tmp/pico.wav "$1" play -q /tmp/pico.wav rm -f /tmp/pico.wav
Audio files
These live in /home/pi/sounds and can be wav or mp3. SCP new ones into here.
pi@squawk:~ $ ls -l /home/pi/sounds/ total 504 -rw-r--r-- 1 pi pi 9249 Oct 4 22:32 alert12.mp3 -rw-r--r-- 1 pi pi 8594 Oct 4 22:32 bingo.mp3 -rw-r--r-- 1 pi pi 59350 Oct 4 22:32 canttouchthis.mp3 -rw-r--r-- 1 pi pi 11703 Oct 4 22:32 cheese.mp3 -rw-r--r-- 1 pi pi 71457 Oct 4 22:32 commandcodesverified_ep.mp3 -rw-r--r-- 1 pi pi 7713 Oct 4 22:32 computerbeep_4.mp3 -rw-r--r-- 1 pi pi 21759 Oct 5 02:24 dong.mp3 -rw-r--r-- 1 pi pi 118725 Oct 5 01:48 doorbell.mp3 -rw-r--r-- 1 pi pi 44329 Oct 5 02:07 indyboot.mp3 -rw-r--r-- 1 pi pi 90825 Oct 4 22:32 scatman.mp3 -rw-r--r-- 1 pi pi 5486 Oct 4 22:32 touchdown.mp3 -rw-r--r-- 1 pi pi 6414 Oct 4 22:32 uhoh.mp3 -rw-r--r-- 1 pi pi 27584 Oct 4 22:32 whistle.mp3
Pico TTS
mkdir ~/pico cd ~/pico wget http://incrediblepbx.com/picotts-raspi.tar.gz tar -zxf picotts-raspi.tar.gz sudo cp -R usr / cd /usr/src/pico_build sudo dpkg -i libttspico-data_1.0+git20110131-2_all.deb sudo dpkg -i libttspico0_1.0+git20110131-2_armhf.deb sudo dpkg -i libttspico-utils_1.0+git20110131-2_armhf.deb rm -rf ~/pico
Hardware
Pi lives above door. Connected to shit amp board. Amp board and speaker being replaced very soon - will document this then.
squawk.1475674324.txt.gz · Last modified: 2016-10-05 13:32 by tim