diff --git a/.asoundrc b/.asoundrc new file mode 100644 index 0000000..d2d68a3 --- /dev/null +++ b/.asoundrc @@ -0,0 +1,15 @@ +pcm.my_usb_sound_device { + type hw + card USB + device 0 +} + +pcm.my_usb_sound_device_with_converter { + type plug + slave { + pcm my_usb_sound_device + format S16_LE + channels 2 + rate 48000 + } +} diff --git a/README.md b/README.md index 6d967c6..9417b4c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,84 @@ -# dog-trainer +Dog Trainer +=========== -A device that allows dogs to speak \ No newline at end of file +A device that allows dogs to speak. Click on the picture to see the video with the `dog-trainer` in action + +![dog-trainer-photo](assets/dog-trainer-photo.png "https://peertube.virtonline.eu/w/2MXWQPC1zd2pgms5HQE1Ma") + +Hardware +-------- +Connect buttons to the Rarspberry pi GPIO as shown in the schema to debounce noisy buttons + +![dog-trainer-schema](assets/dog-trainer-schema.png) + + +Operating system +---------------- +Install `Raspberry Pi Imager` into your notebook. Launch it and choose `Raspberry Pi OS Lite` to write into the microSD card + +Power on the Raspberry pi and finish the installation process. Log into its console using either: +- ethernet/wifi from your notebook: `ssh pi@raspberrypi` +- connected keyboard and monitor +- serial port + +How to install +-------------- + +Once you are logged into the `Raspberry pi` console clone the `dog-trainer` source code into your home directory +> `git clone https://gitea.virtonline.eu/2HoursProject/dog-trainer.git --depth 1` + +Go into the newly created directory +> `cd ~/dog-trainer` + +Dependencies +------------ + +The recordings must be edited a little bit so some audio processing tool is needed. Install `ffmpeg` which will do the job +> `apt install -y ffmpeg` + +Configuration +------------- + +If your sound device is other then USB type then modify and adapt the line `hw:CARD=USB,DEV=0` in ALSA config +> `nano .asoundrc` + +Install the ALSA config file +> `mv .asoundrc ~/` + +Run it! +------- + +Just run +> `python3 dog-trainer.py` + +Pres `Ctrl+c` to exit + +Make it a service to automaticaly run in the backgroud +------------------------------------------------------ + +If your `$HOME` directory is other then `/home/pi` then modify the systemd service file +> `sed -i "s#/home/pi#$HOME#g" dog-trainer.service` + +If your username and group is other then `pi` and `pi` then also run +> `sed -i "s#User=pi#User=$USER#" dog-trainer.service` +`sed -i "s#Group=pi#Group=$(id -gn)#" dog-trainer.service` + +Install the systemd service file +> `sudo mv dog-trainer.service /etc/systemd/system/` + +Reload systemd manager configuration +> `systemctl daemon-reload` + +Enable the `dog-trainer` service at boot time and also run it +> `systemctl enable --now dog-trainer` + +If it does not work check logs +> `journalctl -f -u dog-trainer` + +Set volume for playback and recording +------------------------------------- + +Launch soundcard mixer +> `alsamixer` + +Press F6 to select a soundcard. Find your USB sound device in the list e.g. `Jabra Speak 410 USB`. Press F4 and use up/down arrow keys to set recording volume and repeat also for the playback (F3). Press ESC fo exit. \ No newline at end of file diff --git a/Windows_95.wav b/Windows_95.wav new file mode 100644 index 0000000..4999098 Binary files /dev/null and b/Windows_95.wav differ diff --git a/assets/dog-trainer-photo.png b/assets/dog-trainer-photo.png new file mode 100644 index 0000000..b73bab7 Binary files /dev/null and b/assets/dog-trainer-photo.png differ diff --git a/assets/dog-trainer-schema.png b/assets/dog-trainer-schema.png new file mode 100644 index 0000000..51e2cbc Binary files /dev/null and b/assets/dog-trainer-schema.png differ diff --git a/beep.wav b/beep.wav new file mode 100644 index 0000000..d1b259e Binary files /dev/null and b/beep.wav differ diff --git a/dog-trainer.py b/dog-trainer.py new file mode 100755 index 0000000..9d232c2 --- /dev/null +++ b/dog-trainer.py @@ -0,0 +1,139 @@ +import RPi.GPIO as GPIO +import time +from datetime import datetime +import subprocess +import sys + +PLAY_DEVICE = "my_usb_sound_device_with_converter" # as defined in '~/.asoundrc' +REC_DEVICE = "my_usb_sound_device" # as defined in '~/.asoundrc' +MAX_RECORDING_TIME = 30 # limit recordings to only e.g. 30 seconds long +TRIM_RECORDING_END = 0.8 # remove the clicking noise from the end of recordings +RECORD_BUTTON_PRESS_TIME = 2 # pressing and holding a button longer then e.g. 2 seconds triggers recording otherwise playing +PINS = [17, 23] # listen to changes on this GPIOs e.g. [4, 17, 23, 0, 5, 6] +GPIO.setmode(GPIO.BCM) # use BCM pin layout +GPIO.setwarnings(False) + + +def setup_pins(): + for pin in PINS: + GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF) + + +def terminate_recording(proc, pin): + print("Terminating recording...") + proc.terminate() + proc.wait(timeout=5) + print(f"Cutting last {TRIM_RECORDING_END} second (click noise) from the recording") + audio_duration = subprocess.run( + [ + "ffprobe", + "-v", + "error", + "-select_streams", + "a:0", + "-show_entries", + "stream=duration", + "-of", + "default=noprint_wrappers=1:nokey=1", + f"recording_temp_{pin}.wav", + ], + stdout=subprocess.PIPE, + ).stdout.decode("utf-8") + subprocess.call( + [ + "ffmpeg", + "-hide_banner", + "-y", + "-i", + f"recording_temp_{pin}.wav", + "-t", + f"{float(audio_duration) - TRIM_RECORDING_END}", + "-acodec", + "copy", + f"recording_{pin}.wav", + ] + ) + DATE_TIME = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + print(f"Archiving old sound to archive/recording_{pin}_{DATE_TIME}.wav") + subprocess.call( + ["mv", f"recording_temp_{pin}.wav", f"archive/recording_{pin}__{DATE_TIME}.wav"] + ) + play_sound(pin) + + +def play(file): + subprocess.call(["aplay", "-D", PLAY_DEVICE, file]) + + +def play_sound(pin): + play(f"recording_{pin}.wav") + + +def play_beep(): + play("beep.wav") + + +def play_intro(): + play("Windows_95.wav") + + +def record_sound(pin): + print(f"GPIO {pin} LOW - Starting recording_temp_{pin}.wav") + proc = subprocess.Popen( + [ + "arecord", + "-D", + REC_DEVICE, + "-f", + "S16_LE", + "-r", + "16000", + "-c", + "1", + "-d", + f"{MAX_RECORDING_TIME}", + f"recording_temp_{pin}.wav", + ] + ) + print(f"Recording in progress (max {MAX_RECORDING_TIME} seconds)...") + wait_for_button_release_or_timeout(pin, MAX_RECORDING_TIME) + print("GPIO went HIGH - terminating recording") + play_beep() + terminate_recording(proc, pin) + + +def wait_for_button_release_or_timeout(pin, timeout): + timeout_start = time.time() + while time.time() < timeout_start + timeout: + if GPIO.input(pin) == GPIO.HIGH: + break + time.sleep(0.05) + + +def handle_pin(pin): + print(f"GPIO {pin} LOW - event triggered") + timeout_start = time.time() + wait_for_button_release_or_timeout(pin, RECORD_BUTTON_PRESS_TIME) + + if time.time() - timeout_start >= RECORD_BUTTON_PRESS_TIME: + # Pin was LOW for longer time; recording + play_beep() + record_sound(pin) + else: + # Pin was LOW for shorter time; playing + play_sound(pin) + + +try: + setup_pins() + subprocess.call(["mkdir", "-p", "archive"]) + play_intro() + for pin in PINS: + GPIO.add_event_detect(pin, GPIO.FALLING, callback=handle_pin, bouncetime=1000) + while True: + time.sleep(0.01) +except Exception: + print("Cought exception...") + sys.exit() +finally: + GPIO.cleanup() diff --git a/dog-trainer.service b/dog-trainer.service new file mode 100644 index 0000000..90c1040 --- /dev/null +++ b/dog-trainer.service @@ -0,0 +1,16 @@ +[Unit] +Description=Dog Trainer +After=sound.target + +[Service] +Type=simple +User=pi +Group=pi +WorkingDirectory=/home/pi/dog-trainer +ExecStart=/usr/bin/python3 -u /home/pi/dog-trainer/dog-trainer.py +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target + diff --git a/recording_17.wav b/recording_17.wav new file mode 100644 index 0000000..c95d75a Binary files /dev/null and b/recording_17.wav differ diff --git a/recording_23.wav b/recording_23.wav new file mode 100644 index 0000000..a1eddc3 Binary files /dev/null and b/recording_23.wav differ