Updated to use the latest CamillaDSP 3 on RaspberrypiOs ( debian 12)
Setup:
Assumes than you have working install of RaspberrypiOs and camilladsp 3, see
https://github.com/mdsimon2/RPi-CamillaDSP
to use config selectors buttons ( KEY_BACK and KEY_HOMEPAGE ) the name must start with "_ " and "| " , 3 chars at least, exemple:
_Streamer.yml , display "Str"
|TV .yml , display Tv ( notice the space )
#### SSH to your RPI
#### install necessary python addons, create scripts and services:
python -m venv ~/camilladsp/.venv
source ~/camilladsp/.venv/bin/activate
pip3 install raspberrypi-tm1637
pip3 install glob2
pip3 install evdev
deactivate
#### create scripts directory
mkdir ~/scripts
#### create script for remote and display:
nano ~/scripts/remote6.py
past this code and save
Python:
##### python -m evdev.evtest to get keys #####
# KEY_VOLUMEUP
# KEY_VOLUMEDOWN
# 'KEY_MIN_INTERESTING', 'KEY_MUTE'
# KEY_PLAYPAUSE
# KEY_PREVIOUSSONG
# KEY_NEXTSONG
# KEY_POWER
# KEY_UP ## Treble+
# KEY_DOWN ## Treble-
# KEY_RIGHT ## Bass+
# KEY_LEFT ## Bass-
# KEY_ENTER ## Bypass tones
# KEY_BACK ## Select Configs "_"
# KEY_HOMEPAGE ## Select Configs "|"
#############################################
from time import sleep
import os
event = 'event2' ##### event attended for remote
while True:
sleep(1)
inputlist = os.listdir('/dev/input/')
if event in inputlist:
break
import subprocess
import evdev
import asyncio
import glob
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
PowerGpio = 4 #### GPIO power trigger
GPIO.setup(PowerGpio,GPIO.OUT, initial=GPIO.HIGH)
delay = 180 ### seconds before power off
from camilladsp import CamillaClient
cdsp = CamillaClient("127.0.0.1",1234)
cdsp.connect()
remote=evdev.InputDevice('/dev/input/' + event) # bluettoth remote dont have id name
knob=evdev.InputDevice('/dev/input/event0') # evdev.InputDevice('/dev/input/by-id/usb-TERRATEC_AUREON_XFIRE8.0_HD-event-if05')
remote.grab()
knob.grab()
configdir = os.path.expanduser( '~' ) + '/camilladsp/' + 'configs/'
lmscmd = os.path.expanduser( '~' ) + '/scripts/./lmscmd.sh'
import tm1637
CLK = 23 ### white
DIO = 24 ### green
tm = tm1637.TM1637(clk=CLK, dio=DIO)
mini = 0
med = 2
maxi = 7
tm.brightness(mini)
last_display = 0
keydown = 4
CtlGpio = 1
def swap(segs):
length = len(segs)
if length == 4 or length == 5:
segs.extend(bytearray([0] * (6 - length)))
segs[0], segs[2] = segs[2], segs[0]
if length >= 4:
segs[3], segs[5] = segs[5], segs[3]
return segs
async def display_standard():
global last_display
global keydown
while True:
await asyncio.sleep(1)
if last_display == 0 :
keydown += 1
if keydown == 5:
if cdsp.volume.main_mute() == True :
tm.write(swap(tm.encode_string( cdsp.config.file_path().replace(configdir,'')[1:4] + "---" )))
else:
tm.write(swap(tm.encode_string( cdsp.config.file_path().replace(configdir,'')[1:4] + str(round(cdsp.volume.main_volume())).rjust(3, " ") )))
last_display = 1
keydown = 0
async def auto_poweroff():
global CtlGpio
global last_display
global keydown
was_off = 0
counterPower = 0
while True:
await asyncio.sleep(1)
if CtlGpio == 1:
### if "RUNNING" in str(cdsp.general.state()):
if not "-1000.0" in str (cdsp.levels.capture_rms()):
counterPower = 0
GPIO.output(PowerGpio,GPIO.HIGH)
if was_off == 1 :
last_display = 0
keydown = 4
was_off = 0
else:
if GPIO.input(PowerGpio) == 1:
counterPower = counterPower + 1
if counterPower == delay:
GPIO.output(PowerGpio,GPIO.LOW)
tm.write(swap(tm.encode_string("PW OFF")))
await asyncio.sleep(3)
tm.write(swap(tm.encode_string(" ")))
was_off = 1
else:
GPIO.output(PowerGpio,GPIO.LOW)
async def restart_error19():
while True:
await asyncio.sleep(1)
if "[Errno 19] No such device" in str(subprocess.Popen(["journalctl", "-u" , "remote.service" , "-n 1"], stdout=subprocess.PIPE).stdout.read()) :
subprocess.call(["sudo" , "systemctl" , "restart" , "remote"])
await asyncio.sleep(1)
async def remote_events(device):
cdspconf = cdsp.config.active()
global last_display
global keydown
global CtlGpio
display_all = 0
display_tones = 0
display_volume = 0
treble_gain = 0
bass_gain = 0
bass_gain_prev = 0
treble_gain_prev = 0
counter = 0
async for event in device.async_read_loop():
if event.type == evdev.ecodes.EV_KEY:
attrib = evdev.categorize(event)
if attrib.keystate == 1:
if attrib.keycode == 'KEY_VOLUMEDOWN':
if cdsp.volume.main_volume() - 1 >= -80:
cdsp.volume.set_main_volume(cdsp.volume.main_volume() - 1)
else:
cdsp.volume.set_main_volume(-80)
display_all = 1
counter = 0
elif attrib.keycode == 'KEY_VOLUMEUP':
if cdsp.volume.main_volume() + 1 < 0:
cdsp.volume.set_main_volume(cdsp.volume.main_volume() + 1)
else:
cdsp.volume.set_main_volume(0)
display_all = 1
counter = 0
elif attrib.keycode == ('KEY_MIN_INTERESTING', 'KEY_MUTE'):
if cdsp.volume.main_mute() == False:
cdsp.volume.set_main_mute(True)
else:
cdsp.volume.set_main_mute(False)
display_all = 1
elif attrib.keycode == 'KEY_PLAYPAUSE':
os.popen(lmscmd + ' pause')
display_all = 1
elif attrib.keycode == 'KEY_PREVIOUSSONG':
os.popen(lmscmd + ' "playlist index -1\n"')
display_all = 1
elif attrib.keycode == 'KEY_NEXTSONG':
os.popen(lmscmd + ' "playlist index +1\n"')
display_all = 1
elif attrib.keycode == 'KEY_UP':
if 'Treble' in str(cdspconf):
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
if treble_gain + 1 <= 6:
treble_gain = treble_gain + 1
else:
treble_gain = 6
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
elif attrib.keycode == 'KEY_DOWN':
if 'Treble' in str(cdspconf):
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
if treble_gain - 1 >= -6:
treble_gain = treble_gain - 1
else:
treble_gain = -6
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
elif attrib.keycode == 'KEY_RIGHT':
if 'Bass' in str(cdspconf):
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
if bass_gain + 1 <= 6:
bass_gain = bass_gain + 1
else:
bass_gain = 6
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
elif attrib.keycode == 'KEY_LEFT':
if 'Bass' in str(cdspconf):
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
if bass_gain - 1 >= -6:
bass_gain = bass_gain - 1
else:
bass_gain = -6
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
if attrib.keystate == 2:
if attrib.keycode == 'KEY_VOLUMEDOWN':
counter = counter + 1
if counter == 2 :
if cdsp.volume.main_volume() - 1 >= -80:
cdsp.volume.set_main_volume(cdsp.volume.main_volume() -1 )
else:
cdsp.volume.set_main_volume(-80)
display_all = 1
counter = 0
elif attrib.keycode == 'KEY_VOLUMEUP':
counter = counter + 1
if counter == 2 :
if cdsp.volume.main_volume() + 1 < 0:
cdsp.volume.set_main_volume(cdsp.volume.main_volume() + 1)
else:
cdsp.volume.set_main_volume(0)
display_all = 1
counter = 0
elif attrib.keycode == 'KEY_UP':
if 'Treble' in str(cdspconf):
counter = counter + 1
if counter == 4 :
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
if treble_gain + 1 <= 6:
treble_gain = treble_gain + 1
else:
treble_gain = 6
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
counter = 0
elif attrib.keycode == 'KEY_DOWN':
if 'Treble' in str(cdspconf):
counter = counter + 1
if counter == 4 :
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
if treble_gain - 1 >= -6:
treble_gain = treble_gain - 1
else:
treble_gain = -6
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
counter = 0
elif attrib.keycode == 'KEY_RIGHT':
if 'Bass' in str(cdspconf):
counter = counter + 1
if counter == 4 :
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
if bass_gain + 1 <= 6:
bass_gain = bass_gain + 1
else:
bass_gain = 6
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
counter = 0
elif attrib.keycode == 'KEY_LEFT':
if 'Bass' in str(cdspconf):
counter = counter + 1
if counter == 4 :
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
if bass_gain - 1 >= -6:
bass_gain = bass_gain - 1
else:
bass_gain = -6
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain
cdsp.config.set_active(cdspconf)
display_tones = 1
counter = 0
elif attrib.keycode == 'KEY_POWER' :
keydown = keydown + 1
if keydown == 20:
if CtlGpio == 0:
CtlGpio = 1
tm.write(swap(tm.encode_string("PW ON ")))
else:
CtlGpio = 0
tm.write(swap(tm.encode_string("PW OFF")))
await asyncio.sleep(3)
display_all = 1
elif attrib.keycode == 'KEY_ENTER':
keydown = keydown + 1
if keydown == 10:
if ('Bass' in str(cdspconf) and 'Treble' in str(cdspconf)):
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
if bass_gain == 0 and treble_gain == 0 :
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain_prev
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain_prev
bass_gain_now = bass_gain_prev
treble_gain_now = treble_gain_prev
bass_gain_prev = 0
treble_gain_prev = 0
else:
cdspconf['filters']['Bass']['parameters']['gain'] = 0
cdspconf['filters']['Treble']['parameters']['gain'] = 0
bass_gain_prev = bass_gain
treble_gain_prev = treble_gain
bass_gain_now = 0
treble_gain_now = 0
cdsp.config.set_active(cdspconf)
display_tones = 1
if attrib.keystate == 0:
if attrib.keycode == 'KEY_POWER' :
if keydown < 10:
if tm.brightness() == mini:
brt = med
elif tm.brightness() == maxi:
brt = mini
elif tm.brightness() == med:
brt = maxi
tm.brightness(brt)
elif attrib.keycode == 'KEY_ENTER':
if keydown < 10:
if last_display == 1 :
display_tones = 1
else:
display_all = 1
elif attrib.keycode == 'KEY_BACK':
if keydown < 10:
if ('Bass' in str(cdspconf) and 'Treble' in str(cdspconf)):
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
config = glob.glob(configdir + '_*')
current = cdsp.config.file_path()
if len(config) > 0 :
for i in range(len(config)):
if i == len(config)-1:
if current == config[i]:
newconfig = config[0]
else:
if current == config[i]:
newconfig = config[i+1]
try:
config.index(current)
except ValueError:
newconfig = config[0]
if newconfig != current :
cdsp.config.set_file_path(newconfig)
cdsp.general.reload()
await asyncio.sleep(0.1)
cdspconf = cdsp.config.active()
if ('Bass' in str(cdspconf) and 'Treble' in str(cdspconf)):
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain
cdsp.config.set_active(cdspconf)
display_all = 1
elif attrib.keycode == 'KEY_HOMEPAGE' :
if keydown < 10:
if ('Bass' in str(cdspconf) and 'Treble' in str(cdspconf)):
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
config = glob.glob(configdir + '|*')
current = cdsp.config.file_path()
if len(config) > 0 :
for i in range(len(config)):
if i == len(config)-1:
if current == config[i]:
newconfig = config[0]
else:
if current == config[i]:
newconfig = config[i+1]
try:
config.index(current)
except ValueError:
newconfig = config[0]
if newconfig != current :
cdsp.config.set_file_path(newconfig)
cdsp.general.reload()
await asyncio.sleep(0.1)
cdspconf = cdsp.config.active()
if ('Bass' in str(cdspconf) and 'Treble' in str(cdspconf)):
cdspconf['filters']['Bass']['parameters']['gain'] = bass_gain
cdspconf['filters']['Treble']['parameters']['gain'] = treble_gain
cdsp.config.set_active(cdspconf)
display_all = 1
keydown = 0
if display_all == 1:
display_all = 0
if cdsp.volume.main_mute() == True :
tm.write(swap(tm.encode_string( cdsp.config.file_path().replace(configdir,'')[1:4] + "---" )))
else:
tm.write(swap(tm.encode_string( cdsp.config.file_path().replace(configdir,'')[1:4] + str(round(cdsp.volume.main_volume())).rjust(3, " ") )))
last_display = 1
if display_tones == 1:
display_tones = 0
if ('Bass' in str(cdspconf) and 'Treble' in str(cdspconf)):
bass_gain = cdspconf['filters']['Bass']['parameters']['gain']
treble_gain = cdspconf['filters']['Treble']['parameters']['gain']
tm.write(swap(tm.encode_string(( str(round(bass_gain)).rjust(2," ")+"B" + str(round(treble_gain)).rjust(2," ")+"T" ))))
last_display = 0
for device in remote, knob:
asyncio.ensure_future(remote_events(device))
loop = asyncio.get_event_loop()
loop.create_task(auto_poweroff())
loop.create_task(restart_error19())
loop.create_task(display_standard())
loop.run_forever()
#### create script for LMS commands
nano ~/scripts/lmscmd.sh
####paste this and save, SSHOST= the name of your LMS server:
Bash:
#!/bin/bash
# https://github.com/ralph-irving/squeezelite/issues/100
# LMS Server
SSHOST=name-of-your-LMS-server
# Try to find this Squeezelite's MAC address automatically.
# We use the MAC of the device that currently handles the default route.
# This is most reliable in cases where more than one interface is UP.
SLMAC="-m $(cat /sys/class/net/$(ip route show default | awk '/default/ {print $5}')/address)"
# Replace : with %3A in MAC address
MAC=`echo -n ${SLMAC} | awk '{printf "%s", $2}' | tr '[:upper:]' '[:lower:]' | sed 's/:/%3A/g'`
# Exit if empty
if [ -z ${MAC} ]; then
echo "MAC Empty"
exit
fi
SSTCP="/dev/tcp/$SSHOST/9090"
exec 8<>${SSTCP}
echo -e "$MAC $1\r\nexit\r\n" >&8
RESPONSE=`cat <&8`
#### make script executable:
chmod +x ~/scripts/lmscmd.sh
#### create remote service
sudo nano /lib/systemd/system/remote.service
#### copy and paste this code, enter your USERNAME ( 3 times) :
Code:
[Unit]
After=multi-user.target
[Service]
User=USERNAME
Type=simple
ExecStart=/home/USERNAME/camilladsp/.venv/bin/python3 /home/USERNAME/scripts/remote6.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=remote
[Install]
WantedBy=multi-user.target
#### pair the remote bluetooth with bluetoothctl
https://www.baeldung.com/linux/bluetooth-via-terminal
bluetoothctl
power on
discoverable on
scan on
pair ## MAC address of HID Remote or your remote
#### when done execute
/home/USERNAME/camilladsp/.venv/bin/python3 -m evdev.evtest
#### you get something like this:
python -m evdev.evtest
ID Device Name Phys Uniq
------------------------------------------------------------------------------------------------------------------
0 /dev/input/event0 Aureon 7.1 USB usb-0000:01:00.0-1.3/input3
1 /dev/input/event1 HID Remote01 Mouse dc:a6:32:3c:1c:27 ff:23:10:10:21:25
2 /dev/input/event2 HID Remote01 Keyboard dc:a6:32:3c:1c:27 ff:23:10:10:21:25
------------------------------------------------------------------------------------------------------------------
#### event0 is my primary sound card, it has button for volume and mute.
#### event2 is the remote bluethooth
### in remote6.py find line 21 and set correct number, for exemple:
event = 'event2' ##### event attended for remote
#### at line 45 :
knob=evdev.InputDevice('/dev/input/event0')
#### is for the knob or buttons on your soundcard, if exist. verify if the number is correct. if your soudcard dont have volume / mute command , newermind.
#### if you use FLIRC or 2.4ghz hardware receptor, you can use the name, not possible with virtual device as bluettoth. find the hardware name at /dev/input/by-id
for exemple, with flirc line 44 is:
remote=evdev.InputDevice(
'/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd')
#### if you use the same remote as me, you can exit the python script.
#### if not, select your remote device and test your keys:
#### i get something like this when hit the OK key on my remote:
time 1709850825.70846 type 4 (EV_MSC), code 4 (MSC_SCAN), value 458792
time 1709850825.70846 type 1 (EV_KEY), code 28 (KEY_ENTER), value 0
time 1709850825.70846 --------- SYN_REPORT --------
#### KEY_ENTER is mapped at line 339 and 378 in remote6.py, if your remote dont send KEY_ENTER you can change the name to a new key.
#### when mapping key is ok:
sudo systemctl enable remote
sudo systemctl start remote
#### end of setup.
Edit, add quick set up for lyrion server and squeezelite
######### install squeezelite
sudo apt install squeezelite
echo -e 'SL_SOUNDCARD="hw:Loopback,1"\nSB_EXTRA_ARGS="-W -C 5 -r 48000-48000 -R hLE:::28"' | sudo tee -a /etc/default/squeezelite > /dev/null
sudo service squeezelite restart
######### install Lyrion media server
wget
https://downloads.lms-community.org/LyrionMusicServer_v9.0.1/lyrionmusicserver_9.0.1_all.deb -O lyrionmusicserver.deb
sudo apt install ./lyrionmusicserver.deb
######### usb mounted at startup
lsblk -o name,fstype,uuid,size # find driver
sudo su
mkdir /mnt/usb
echo "UUID=xxxx-xxxx /mnt/usb vfat nofail,defaults 0 0" >> /etc/fstab
exit
sudo mount /dev/xxx /mnt/usb
reboot
######### setup sound card
alsamixer
sudo alsactl store