User Tools

Site Tools


squawk

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
squawk [2016-10-09 22:28] timsquawk [2024-12-07 21:22] (current) aria
Line 1: Line 1:
-====== Audio Alerts ======+====== Squawk ======
  
-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:+Squawk is the Hacklab's sound effects, alerts (e.g. doorbell), and announcement system. 
 + 
 +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 purchased a pi (original B), amplifier and speakers for the purpose and documents them thus:
  
 ===== Usage ===== ===== Usage =====
Line 9: Line 11:
 Examples for testing might be something like: Examples for testing might be something like:
  
 +Play a sound file:
 +<code>
 +mosquitto_pub -h mqtt -t 'sound/g1/play' -m canttouchthis.mp3
 +</code>
 +Play a list of sound files with no gap:
 +<code>
 +mosquitto_pub -h mqtt -t 'sound/g1/playlist' -m "long_long_man/long.mp3,long_long_man/loooong.mp3,long_long_man/maaaan.mp3"
 +</code>
 +Text-to-speech message (with a subtle notification bong):
 <code> <code>
-mosquitto_pub -h mqtt -t 'sound/g1/play' -m dong.mp3 
 mosquitto_pub -h mqtt -t 'sound/g1/speak' -m "All your base are belong to us" mosquitto_pub -h mqtt -t 'sound/g1/speak' -m "All your base are belong to us"
 </code> </code>
 +Text-to-speech message (with a clear airport/transit chiming sound):
 +<code>
 +mosquitto_pub -h mqtt -t 'sound/g1/announce' -m "Good evening, my sensors indicate that the laser cutter may be on fire."
 +</code>
 +
 +===== Adding new sounds =====
 +
 +<del>Use the web file browser at http://squawk.hacklab:8080/.</del>
 +
 +For now, use scp to squawk.hacklab.
  
-===== Raspbian =====+===== Raspberry Pi OS =====
  
-Latest Raspbian minimal image from the Raspberry Pi website.+Latest Raspberry Pi OS, installed using Raspberry Pi Imager.
  
 ==== raspi-config ==== ==== raspi-config ====
Line 22: Line 42:
   * Expand filesystem   * Expand filesystem
   * Medium overclock   * Medium overclock
-  * Graphics mem reduced to 16M 
   * Audio output forced to 3.5mm   * Audio output forced to 3.5mm
   * Hostname set   * Hostname set
  
-==== RAM /tmp ====+==== RAM /tmp /var/log ==== 
 +Appended to /etc/fstab:
  
 <code> <code>
-sudo systemctl enable tmp.mount+tmpfs /var/log tmpfs defaults,noatime,nosuid,mode=0755,size=100m 0 0 
 +tmpfs /tmp tmpfs defaults,noatime,nosuid,size=100m 0 0 
 +tmpfs /var/tmp tmpfs defaults,noatime,nosuid,size=30m 0 0 
 +tmpfs /var/log tmpfs defaults,noatime,nosuid,mode=0755,size=100m 0 0 
 +tmpfs /var/run tmpfs defaults,noatime,nosuid,mode=0755,size=2m 0 0 
 +tmpfs /var/spool/mqueue tmpfs defaults,noatime,nosuid,mode=0700,gid=12,size=30m 0 0
 </code> </code>
  
Line 37: Line 62:
 sudo apt-get install mpg123 sudo apt-get install mpg123
 sudo apt-get install sox sudo apt-get install sox
-sudo apt-get install python-pip +sudo apt-get install python3-pip 
-sudo pip install paho-mqtt+sudo pip install --break-system-packages paho-mqtt
 </code> </code>
  
-==== /etc/rc.local ====+==== systemd service ====
  
-These lines added before exit:+/etc/systemd/system/squawk.service
  
 <code> <code>
-su -c "mpg123 /home/pi/sounds/indyboot.mp3" pi & +[Unit
-su -c "/home/pi/respawn --syslog --max-backoff=10 /home/pi/squawk.py" pi & +Description=Runs the squawk listener script
-</code> +
-===== Scripts ===== +
- +
-Various scripts to make things happen. +
- +
-==== /home/pi/squawk.py ==== +
- +
-<code python> +
-#!/usr/bin/env python +
- +
-import paho.mqtt.client as mqtt +
-import subprocess +
-import os +
-import logging +
-import signal +
-import time +
-import random +
- +
-logging.basicConfig(level=logging.INFO) +
- +
-max_playtime  = 15 +
-sounds_path = "/home/pi/sounds" +
- +
-status = 'closed' +
- +
-# 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): +
- +
-    global status +
- +
-    if msg.topic == 'labstatus': +
-        if msg.payload == 'open': +
-           status = 'open' +
-        else: +
-           status = 'closed' +
- +
-    # 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('dongq.mp3'+
-        speak(msg.payload) +
- +
-    if msg.topic == 'sound/g1/announce': +
-        play('chime.mp3'+
-        speak(msg.payload)+
  
-    if msg.topic == 'display/doorbot/intercom': +[Service] 
-        if msg.payload == '1' and status == 'closed': +WorkingDirectory=/home/pi/newsquawk 
-            play('doorbell.mp3')+ExecStart=/home/pi/newsquawk/venv/bin/python /home/pi/newsquawk/main.py 
 +User=pi 
 +Restart=always 
 +RestartSec=3
  
-m = mqtt.Client() +[Install] 
-m.on_connect on_connect +WantedBy=multi-user.target
-m.on_message = on_message +
-m.connect("mqtt"+
-m.loop_forever()+
 </code> </code>
  
-==== /home/pi/respawn ====+...and enabled with
  
-<code python+<code> 
-#!/usr/bin/env python +$ sudo systemctl start squawk 
-+$ sudo systemctl enable squawk
-# 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')+
 </code> </code>
  
-==== /home/pi/pico.sh ====+===== Scripts =====
  
-<code bash> +The code for squawk is stored in <code>/home/pi/newsquawk</code>. TODO: this should probably be mirrored somewhere.
-#!/bin/bash +
-pico2wave -l en-GB -w /tmp/pico.wav "$1" +
-play -q /tmp/pico.wav +
-rm -f /tmp/pico.wav +
-</code>+
  
 ===== Audio files ===== ===== Audio files =====
  
-These live in /home/pi/sounds and can be wav or mp3. SCP new ones into here.+These live in /home/pi/sounds and can be wav or mp3. Generally prefer mp3. SCP new ones into here.
  
 <code> <code>
Line 350: Line 118:
 </code> </code>
  
-===== Pico TTS =====+===== Hardware =====
  
-<code> +{{ :photo_2016-10-10_16-35-17.jpg?direct&200|}} 
-mkdir ~/pico + 
-cd ~/pico +The "squawk" unit lives above the IRC terminal, to the right of the main door in G1. It consists of a Raspberry Pi B, a relay board, and a TPA3116 based amplifier board. 
-wget  http://incrediblepbx.com/picotts-raspi.tar.gz + 
-tar -zxf picotts-raspi.tar.gz +There are four speakers connected, located in each of the rooms on the ceilingThe speakers are 8 Ohm moisture resistant cheap ceiling speakers that have exceeded expectations and produce a surprisingly full soundThis opens up the potential for using them for background music in the future (e.gvia MPD)
-sudo cp -R usr / + 
-cd /usr/src/pico_build +A relay board selects which speakers a played sound is directed toThe left channel goes to two relays, and the right to two others, so by selecting channels and relays, the speakers can be selected
-sudo dpkg -i libttspico-data_1.0+git20110131-2_all.deb + 
-sudo dpkg -i libttspico0_1.0+git20110131-2_armhf.deb +Note: photo shows older version, prior to addition of relay board
-sudo dpkg -i libttspico-utils_1.0+git20110131-2_armhf.deb + 
-rm -rf ~/pico +==== Raspberry Pi ==== 
-</code>+ 
 +Model BPowered via the header rather than USB. Replaced by River Dec 2024 after previous one suffered a smoke containment issue related to repair of the power supply.
  
-===== Hardware =====+==== Amplifier ==== 
 + 
 +  * TPA3116 
 +  * "100 W" 
 +  * 12-24v (more volts more power) 
 +  * Volume pot 
 +  * 3-pin header input or 3.5mm socket 
 + 
 +Audio is input via the 3-pin header. 
 + 
 +Power is input on the centre screw terminal, with left and right speaker outputs either side. 
 + 
 +==== Power Supply ==== 
 + 
 +The system runs off a 19.5v laptop PSU located in the ceiling space. This is directly connected into the amplifier board, which is rated up to 24v. A spur from this connects to a DC-DC buck converter, supplying the Raspberry Pi with 5v. The original mini converter used was overheating and melting the heatshrink. It's now been replaced with a larger type which so far is running relatively cool. 
 + 
 +Care should be taken when re-wiring as connecting the laptop PSU accidentally reverse polarity, to the speaker terminals, or to the speakers themselves will likely result in one or more of those being instantly destroyed. 
 + 
 +==== Speakers ==== 
 + 
 +  * 8 Ohm 
 +  * 80 W 
 +  * Spade terminals 
 +  * Dual cone 
 + 
 +Located in the ceilings of G1, G2, G8, and G11. 
 + 
 +The wiring to G8 and G11 is in some really awful aluminium Cat 5 which can break if you touch it. 
 + 
 +{{:photo_2016-10-10_16-39-03.jpg?direct&200 |}} {{:photo_2016-10-10_16-39-09.jpg?direct&200 |}} {{:photo_2016-10-10_16-39-10.jpg?direct&200 |}} {{:photo_2016-10-10_16-39-12.jpg?direct&200 |}}
  
-Pi lives above door. Connected to shit amp board. Amp board and speaker being replaced very soon - will document this then. 
squawk.1476052089.txt.gz · Last modified: by tim

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki