Initial version
This commit is contained in:
15
.asoundrc
Normal file
15
.asoundrc
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
85
README.md
85
README.md
@@ -1,3 +1,84 @@
|
|||||||
# dog-trainer
|
Dog Trainer
|
||||||
|
===========
|
||||||
|
|
||||||
A device that allows dogs to speak
|
A device that allows dogs to speak. Click on the picture to see the video with the `dog-trainer` in action
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Hardware
|
||||||
|
--------
|
||||||
|
Connect buttons to the Rarspberry pi GPIO as shown in the schema to debounce noisy buttons
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
BIN
Windows_95.wav
Normal file
BIN
Windows_95.wav
Normal file
Binary file not shown.
BIN
assets/dog-trainer-photo.png
Normal file
BIN
assets/dog-trainer-photo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 713 KiB |
BIN
assets/dog-trainer-schema.png
Normal file
BIN
assets/dog-trainer-schema.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
139
dog-trainer.py
Executable file
139
dog-trainer.py
Executable file
@@ -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()
|
||||||
16
dog-trainer.service
Normal file
16
dog-trainer.service
Normal file
@@ -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
|
||||||
|
|
||||||
BIN
recording_17.wav
Normal file
BIN
recording_17.wav
Normal file
Binary file not shown.
BIN
recording_23.wav
Normal file
BIN
recording_23.wav
Normal file
Binary file not shown.
Reference in New Issue
Block a user