#!/bin/bash #------------------------------------------- # GENERAL NOTES # #------------------------------------------- # # Fairly major improvements added as of # 2020-02-24 though this is still very much # a very crude and simple simplex repeater # script written in bash. Requires sox, # espeak, sed, tr, grep, morse (or cw), # and multimon-ng. # # This version allows for remote DTMF # controls. (2020/02/22) # # Now has automagic conversion of # espeak spoken call sign into NATO # Phonetics. (2020/02/23) # # Added several more variables to control # settings for various parts. (2020/02/23) # # Made the terminal output a bit more bold # so you can see what the parrot script # is doing at a glance. (2020/02/24) #------------------------------------------- # Save in its own directory as simplex.sh # and run: # # ./simplex.sh #------------------------------------------- # Use pavucontrol to select input and output # or however is easiest/best for your # working environment. # # Raspberry Pi may use alsa instead of # pulse. You'll have to configure things # with alsamixer or similar. It does seem # to use pulseaudio nicely when pulse is # installed. #------------------------------------------- # Code by Phillip J Rhoades #------------------------------------------- #------------------------------------------- # HARDWARE NOTES # #------------------------------------------- # I use an HT with an APRS cable and a # mic/headphones splitter on one end # to patch the radio into a Linux computer # or a Raspberry Pi. The Pi will need a # cheap USB soundcard. A USB soundcard # could also help with ground issues # (buzzing) on computers/laptops. # # You'll have to adjust input and output # volumes on the computer and the output # volume on the radio. I also set the # radio to use VOX in this setup. # # You could improve on this basic setup # in a number of ways but that's outside # the scope of this "simple" project. # # If you're reading this script in monospace # then this rough ASCII diagram of the setup # might help you get a clearer idea: # # -->[PC Mic In] # [HT]<-APRS->SPLIT-|| # <--[PC Sound Card Out] # # In some cases, a ground loop noise # isolator or two might help with buzzing # sounds between the computer and the HT. #------------------------------------------- #------------------------------------------- # VARIABLES TO CONTROL THINGS # #------------------------------------------- # Set SECONDS to greater than 600 to trigger # repeater ID at startup. #------------------------------------------- # Set DTMFCONTROLS to 1 in order to be able # to control remotely via DTMF commands or # set DTMFCONTROLS to 0 in order to disable # that functionality. #------------------------------------------- # Leave IDLOOPING set to 1 in most cases. # Since identifying a station every 10 # minutes is legally required. # # You might want to turn off the ID Loop # while testing on your sound card only. # To do that, set IDLOOPING to 0. #------------------------------------------- # Set CALLSIGN to your call sign. #------------------------------------------- # Set CALLSIGN_SPEAK to 1 to have espeak # say your call sign in NATO Phonetics # during each 10 minute ID announcement. #------------------------------------------- # Set CALLSIGN_MORSE to 1 to have morse # (usually from the bsdgames package) # or cw beep out your call sign during each # 10 minute ID announcement. #------------------------------------------- # Set MORSEPROG to whichever text to morse # program you prefer to use. You can # uncomment for EITHER morse from bsdgames # or cw from the cw package. #------------------------------------------- # Set MULTIMONPROG to either multimon or # multimon-ng, depending on what you have # installed or prefer. Both seem to work # just fine for DTMF decoding. # (2020/02/23) #------------------------------------------- # Setting PARROT to 0 will disable the # repeat of audio received by the script. # Setting PARROT to 1 will enable the # repeat of audio received by the script. # # This way, a menu item in dtmfactions # can be used to turn on and off just the # audio repeat part of this script while # keeping the DTMF processing in place. # (2020/02/24) #------------------------------------------- # Some radios and radio/computer combos # seem to have a slow vox. In those cases # activating a sound just loud enough to # trip the vox on can help the radio be # ready to transmit the first second of # the recorded audio. # # Set PREVOX to 1 in order to activate this # feature or set PREVOX to 0 in order to # deactivate it. (2020/02/25) #------------------------------------------- # It might be useful to save/log all audio # traffic that passes through your simplex # repeater. In order to keep the size down # I convert the wav file to ogg in the # background. You could easily set that to # mp3 instead or you could skip conversion # and just keep the original wavs, if you # have plenty of disk space. # # Setting SAVERECS to 1 activates this # feature and setting SAVERECS to 0 # disables it. (2020/02/26) #------------------------------------------- # Just a few refinements, more comments, # and some more DTMF command examples. # (2020/02/29) #------------------------------------------- # Considering a "Dead Man's Switch" option # that would require a DTMF sequence every # N amount of time in order to keep the # repeater running. That way, no spurious # parrots continue operation if they # can't be reached within a certain time. # (2020/02/29) #------------------------------------------- # Considering a rec -t wav | play -t wav # pass-through option for use of this script # as a duplex repeater controller... # (2020/02/29) #------------------------------------------- SECONDS=1000 DTMFCONTROLS=1 PARROT=1 PREVOX=1 SAVERECS=0 CALLSIGN="YOURCALLSIGN" IDLOOPING=1 CALLSIGN_SPEAK=1 CALLSIGN_MORSE=0 #MORSEPROG="cw" MORSEPROG="morse" MULTIMONPROG="multimon-ng" #MULTIMONPROG="multimon" TIMEOUT=120 #------------------------------------------- # MORE "ELEGANT" EXITING # (but still pretty much a hammer) # # Trap ctrl-c and call ctrl_c() # to exit somewhat gracefully... #------------------------------------------- trap ctrl_c INT function ctrl_c() { voxy if test -f "./recording.wav"; then rm recording.wav fi echo echo "#-------------------------------" echo "# Terminating the simplex Parrot" echo "#-------------------------------" espeak "Terminating the simplex parrot" #------------------------------------------- # Kill the parrot and its parent process # since exiting any other way via DTMF # commands seems to leave the script # running in the background right now. # Will try for more elegance later. # (2020/02/23) # # Could issue shutdown -h instead in order # to shut down Linux/Pi computer completely. #------------------------------------------- kill -9 $PPID #shutdown -h now } #------------------------------------------- # VOX ACTIVATOR # # See notes in the settings area about # the PREVOX variable which turns this # on and off. # # Adjust this sound to something that # is not annoying/bothersome/crude. # # Sine sounds a tone and "brownnoise" has # the "benefit" of sounding like radio # static... So... A bit more natural? # # Less lossy. More bossy. (2020/02/25) # #------------------------------------------- voxy () { if [ $PREVOX -eq 1 ]; then play -V1 -n -c1 synth .4 sine 179.9 #time and tone to activate vox are radio dependent #play -n -c1 synth .4 brownnoise fi } #------------------------------------------- # NATO PHONETICS # #------------------------------------------- # Pipe output into this function and it will # return the string as NATO Phonetics, at # least for letters and numbers. #------------------------------------------- # Used for espeak to speak CALLSIGN # in NATO Phonetics #------------------------------------------- natophonetics () { echo $* | sed -e 's/\(.\)/\1\n/g' | natophonetics_sub } natophonetics_sub () { while read data; do case "$data" in A) echo -n "Alpha " ;; B) echo -n "Bravo " ;; C) echo -n "Charlie " ;; D) echo -n "Delta " ;; E) echo -n "Echo " ;; F) echo -n "Foxtrot " ;; G) echo -n "Golf " ;; H) echo -n "Hotel " ;; I) echo -n "India " ;; J) echo -n "Juliet " ;; K) echo -n "Kilo " ;; L) echo -n "Lima " ;; M) echo -n "Mike " ;; N) echo -n "November " ;; O) echo -n "Oscar " ;; P) echo -n "Papa " ;; Q) echo -n "Quebec " ;; R) echo -n "Romeo " ;; S) echo -n "Sierra " ;; T) echo -n "Tango " ;; U) echo -n "Uniform " ;; V) echo -n "Victor " ;; W) echo -n "Whiskey " ;; X) echo -n "X-ray " ;; Y) echo -n "Yankee " ;; Z) echo -n "Zulu " ;; 0) echo -n "Zero " ;; 1) echo -n "One " ;; 2) echo -n "Two " ;; 3) echo -n "Three " ;; 4) echo -n "Four " ;; 5) echo -n "Five " ;; 6) echo -n "Six " ;; 7) echo -n "Seven " ;; 8) echo -n "Eight " ;; 9) echo -n "Nine " ;; " ") echo -n ". " ;; esac done } #------------------------------------------- # DTMF CONTROLS (using multimon-ng) # #------------------------------------------- # scandtmf runs after every recording to # check for dtmf codes and passes those to # dtmfactions to be processed. #------------------------------------------- # Complex DTMF Actions could have their own # functions to call. #------------------------------------------- scandtmf () { dtmfactions $($MULTIMONPROG -q -t wav -a DTMF ./recording.wav 2>/dev/null |grep "DTMF" |grep -v "Enabled" |sed 's/^.\{6\}//'|tr -d '\n') } dtmfactions () { if [ $# -eq 1 ]; then rm recording.wav voxy echo echo "#-------------------------------" echo "# Received DTMF Command: $1" echo "#-------------------------------" espeak "Received D T M F Command: $(echo $1| sed -e 's/\(.\)/\1 /g')" if [ $1 = "#0" ]; then espeak "The DTMF commands are" espeak "#0 for help" espeak "#1 for date and time" espeak "#2 will toggle saving recordings" espeak "#3 Weather report" espeak "#4 Repeater controller uptime" espeak "#73 to terminate the simplex parrot" espeak "#99 to toggle the audio parrot" return fi if [ $1 = "#1" ]; then espeak "`date +"%A, %B %d, %Y, %I:%M %p"`" return fi if [ $1 = "#2" ]; then if [ $SAVERECS -eq 1 ]; then espeak "Recordings will not be saved." SAVERECS=0 else espeak "Recordings will be saved." SAVERECS=1 fi return fi if [ $1 = "#3" ]; then theweather #call the weather function return fi if [ $1 = "#4" ]; then espeak "This parrot has been `uptime -p | sed "s/^up /squawking for /g"`" return fi if [ $1 = "#73" ]; then ctrl_c exit fi if [ $1 = "#99" ]; then if [ $PARROT -eq 1 ]; then espeak "Parrot will now be silent." PARROT=0 else espeak "Parrot will now repeat traffic." PARROT=1 fi return fi espeak "No such code. Send D T M F Code #0 for help." #should say if no matching DTMF code fi } # Just an example of calling another # function for a received DTMF command. # DTMF Command "#3" theweather () { espeak "`ansiweather -p false -a false -s false -l "Kalamazoo, MI" -u imperial |tr "-" "\n" \ |tr "=>" "\n" |sed -e 's/°F/degrees/g' |sed -e 's/mph/miles per hour/g' |sed -e 's/$/./'`" return } #------------------------------------------- # REPEATER ID LOOP # #------------------------------------------- # Loop in background to identify repeater # no matter what else is going on. #------------------------------------------- while [ $IDLOOPING -eq 1 ]; do if [ $SECONDS -gt 600 ]; then #------------------------------------------- # By checking the value of SECONDS we can # identify the repeater every 10 minutes # (600 seconds). # # Why the loop and not just sleep for 600 # seconds? This way an "emergency" ID could # be triggered via menu option etc. #------------------------------------------- #------------------------------------------- # Identify repeater using espeak, morse, # or both (or cw instead of morse). #------------------------------------------- if [ $CALLSIGN_SPEAK -eq 1 ] || [ $CALLSIGN_MORSE -eq 1 ]; then voxy fi if [ $CALLSIGN_SPEAK -eq 1 ]; then espeak "This is simplex repeater $(natophonetics $CALLSIGN) / R" & fi if [ $CALLSIGN_MORSE -eq 1 ]; then echo "$CALLSIGN/R" |$MORSEPROG & fi #------------------------------------------- # Always reset SECONDS to 0 here # so we can begin watching for 600 seconds # again and ID every 10-ish minutes. #------------------------------------------- SECONDS=0 fi sleep 30 #If we loop this less often, it's easier on the CPU done & #------------------------------------------- # REPEATER PARROT LOOP # #------------------------------------------- # Loop until ctrl-c on keyboard or the # DTMF command to terminate the repeater #------------------------------------------- while [ 1 ]; do echo echo "#-------------------------------" echo "# WAITING FOR AUDIO INPUT:" echo "#-------------------------------" rec -V1 -c1 recording.wav rate 64k silence 1 0.1 1% 1 3.0 1% trim 0 $TIMEOUT # Potential command for pass-through for duplex repeater # while still recording a wav for scandtmf to check and # still enforcing the transmit timeout. # Some latency dependent on computer, but basically live. # Making the mic hot in alsa or pulse would have # less latency but other issues. # #rec -V1 -c1 -t wav - rate 64k silence 1 0.1 1% 1 3.0 1% trim 0 $TIMEOUT |tee recording.wav |play -t wav - if [ $DTMFCONTROLS -eq 1 ]; then scandtmf fi if [ $PARROT -eq 1 ]; then if test -f "./recording.wav"; then #sleep 2 # wait a second or two (radio dependent) so receive closes first (not always needed) voxy echo echo "#-------------------------------" echo "# PLAYING BACK CAPTURED AUDIO:" echo "#-------------------------------" play -V1 recording.wav #play back what was said, activating vox if [ $SAVERECS = 1 ]; then #If you decided to save the recordings TIMESTAMP=$(date) mv recording.wav "./$TIMESTAMP.wav" #moving a file on the same filesystem is fast and cheap (ffmpeg -loglevel quiet -i "./$TIMESTAMP.wav" "./$TIMESTAMP.ogg"; rm "./$TIMESTAMP.wav") & #Takes time. Do in background else rm recording.wav #cleanup before restarting loop fi fi fi done