• WANTED: Happy members who like to discuss audio and other topics related to our interest. Desire to learn and share knowledge of science required. There are many reviews of audio hardware and expert members to help answer your questions. Click here to have your audio equipment measured for free!

Display, remote and trigger power for camilladsp streamer and preamp, ( alternative to mdsimon2’s implementation )

melomane13

Active Member
Joined
Jul 24, 2023
Messages
120
Likes
98
Location
France
First of all, I would especially like to thank @HenrikEnquist for developing CamillaDSP and @mdsimon2 for his tutorial, the starting point of this work and without which I could not have done anything!
Thanks also to @MCH for the interest he showed in another thread that I explain.

Second, I assume that you have a properly functioning camilladsp system, ubuntu or raspberrypi OS. If not, go to mdsimon2 tutorial.

Third, the python script that I am going to show you is used for the remote control, display and the power trigger at the same time. you can not use the power trigger, but the remote and the display are inseparable, because the command sent to camilladsp is also sent to the display. Note that only commands sent via the remote control are reflected, very quickly, on the display.

Material used:

TM1637 LED Display 0.56 inch 6 Digits , 4€ send from aliexpress
I1MG_20240307_143116.jpg


2 usb cable male female with dupont connector. I have make mines, by cutting a 2 meter USB extension cable and putting in the Dupont connectors that I already had.
They already exist manifactured.
Connect red wire to VCC, black to GND, green to DIO , white to CLK on the display.
Connect red wire to 3,3 or 5V ( brightness - + ) black wire to ground, green gpio 24 , white to gpio 23 on the RPI.

musb.jpg
femusb.jpg





I have used two layer of plexiglass to make the display box, was the most simple solution for me, but plenty of solutions are possible : only important thing is to block the wires.

IMG_20240307_142322.jpg


Remote bluetooth 4 € send
You can use the remote you want, bluetooth, 2.4GHz receptor or Ir with flirc. Remember than it is possible that keys names on script need to be modified if you use another remote than this.

1telecom.jpg

2 Relay 5v boards, 5 € send
one plastic box, power strip, jack socket and jack cable for trigger.
One relay put in a small plastic box for subwoofer main power , the seconds is for my aiyama A07 modified.
If your trigger require 12v, 5v to 12v converter is mandatory.


relay.jpg

in the script you can read:
PowerGpio = 4 #### GPIO power trigger
connect RPI gpio 4 to "in" pin of relay, dc + to 5v , dc - to ground.

now connect the output of relay to main or use it for 12v trigger
 
Last edited:
The python script:

remote6.py

##### 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 = 'event3' ##### 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 DIO = 24 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.mute.main() == 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(0 + cdsp.volume.main())).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()): 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() - 1 >= -80: cdsp.volume.set_main(cdsp.volume.main() - 1) else: cdsp.volume.set_main(-80) display_all = 1 counter = 0 elif attrib.keycode == 'KEY_VOLUMEUP': if cdsp.volume.main() + 1 < 0: cdsp.volume.set_main(cdsp.volume.main() + 1) else: cdsp.volume.set_main(0) display_all = 1 counter = 0 elif attrib.keycode == ['KEY_MIN_INTERESTING', 'KEY_MUTE']: if cdsp.mute.main() == False: cdsp.mute.set_main(True) else: cdsp.mute.set_main(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() - 1 >= -80: cdsp.volume.set_main(cdsp.volume.main() -1 ) else: cdsp.volume.set_main(-80) display_all = 1 counter = 0 elif attrib.keycode == 'KEY_VOLUMEUP': counter = counter + 1 if counter == 2 : if cdsp.volume.main() + 1 < 0: cdsp.volume.set_main(cdsp.volume.main() + 1) else: cdsp.volume.set_main(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] 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 print("ok") keydown = 0 if display_all == 1: display_all = 0 if cdsp.mute.main() == 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(0 + cdsp.volume.main())).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()
 
Last edited:
SSH to your RPI

#### install necessary python addons, create scripts and services:
sudo apt install python3-evdev
pip3 install raspberrypi-tm1637
pip3 install glob2

#### create scripts directory
mkdir ~/scripts

#### create script for remote and display:
nano ~/scripts/remote6.py
####paste the script in the post above and save.

#### create script for LMS commands
nano ~/scripts/lmscmd.sh
####paste this and save, SSHOST= the name of your LMS server:

#!/bin/bash # https://github.com/ralph-irving/squeezelite/issues/100 # LMS Server SSHOST=raspberrypi # 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, enter your USERNAME:
[Unit] After=multi-user.target [Service] User=pi Type=simple ExecStart=python3 /home/USERNAME/scripts/remote6.py Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=remote [Install] WantedBy=multi-user.target
 
Last edited:
  • Like
Reactions: MCH
#### pair the remote bluetooth with
bluetoothctl

#### when done execute
python -m evdev.evtest

#### you get something like this:

pi@pi4:~/scripts $ 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 TT AUDIO TT USB AUDIO usb-0000:01:00.0-1.4/input3 0
2 /dev/input/event2 HID Remote01 Mouse dc:a6:32:3c:1c:27 ff:23:10:10:21:25
3 /dev/input/event3 HID Remote01 Keyboard dc:a6:32:3c:1c:27 ff:23:10:10:21:25

Select devices [0-3]:
------------------------------------------------------------------------------------------------------------------
#### event0 is my primary sound card, it has button for volume and mute.
#### event3 is the remote bluethooth

### in remote6.py find line 21 and set correct number, for exemple:
event = 'event3' ##### 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.
 
Last edited:
  • Like
Reactions: MCH
Somes explanations of behavoir:

volume, mute play next previous work as you can imagine.
volume repetition is active on key maintained down, speed is configurable

key up/ down is for treble , left /right for bass, repetition is active on key maintained down, speed is configurable.

Short hit on OK display bass and treble values, another short hit display default ( volume and config), same automatically after 5 seconds.
Long hit on OK toggle bypass tone. Values of bass and trebles are maintained on configuration change.

KEY_BACK Select Configs starting with "_"
KEY_HOMEPAGE Select Configs startitng with "|"
the name of config must be at least 3 chars, space is allowed , exemple: TV .yml TV1.yml
when change config, bass and treble value are reported.

short hit on power key change display brightness , 3 levels.
long hit of power key disable automatic trigger power ( force power off ) , another long hit enable auto trigger. status change is displayed.

the trigger use the status of camilladsp for power on / off , set value for silence is mandatory, for exemple 10 seconds . when camilladsp go to paused, the script wait for X second , see at line 38 :
delay = 180 ### seconds before power off

display is allway on, go black when trigger is : power off.
 
Last edited:
  • Like
Reactions: MCH
Very nice. Please have you considered placing your code onto github? It would allow you to make and track changes easily, unlike keeping it in posts here.
 
Very nice. Please have you considered placing your code onto github? It would allow you to make and track changes easily, unlike keeping it in posts here.
Thank you. No, for the moment i have not considered github, not sure to have the time.
Also the code is stable and not sure than more people implemented it... so, i wait.
 
this is fantastic @melomane13 ; very simple and only two gpios for the display.
Great as well that you implemented trigger outputs.

Have you ever considered turning on/off completely your system - including the pi? i do it with a separate pi pico microcontroller working with the same IR receptor (i just split the cable coming from the receptor to the main pi and the pico and it works perfectly) that activates a switch on sequence via the "global en" pin of the pi. Not that it hurts to have a raspberry pi working 24/7 but i find it neat to leave it resting the 90% of the time that is doing nothing. What i have not figured out yet is how to prevent that it turns on when there is a power cut.
 
Thanks @MCH

no, for the moment i haven't implemented power on off RPI, for differents raisons.

But here's how I would do it:

no microcontroller , and prevented that it turns on when there is a power cut.

using 2 relay board, which one with programmable IR receptor :

the drawing may not be clear enough.

when the RPI power brick is connected to main, the 2 relays are powered.

The relay controlled by remote control is programmed to the same code as the RPI power button.

By pressing it, the rpi is powered on, then at the end of the start, via the gpio, the first relay bypasses the controlled relay, leaving the possibility of using the remote power button for shutdown without the rpi being cut off.

When the shutdown is completed, the system returns to the initial state.
Perhaps it will be necessary to put a capacitor at the 5v output of the relays, in the event that there is a micro-cut when the state of the relays changes.
 

Attachments

  • IMG_20240309_114521.jpg
    IMG_20240309_114521.jpg
    111.8 KB · Views: 97
Last edited:
Thanks for a great tutorial!

I have a question about wiring a 5V to 12V converter for the 12V trigger. Currently, I’ve connected the positive (tip of the mono jack) of the converter to the NC terminal and the ground (sleeve of the mono jack) to the COM terminal of the relay.
As I understand it, the relay only opens or closes the circuit. I’m unsure how the amplifier handles the 12V trigger. Does it supply 12V through its trigger input and detect whether the circuit is open, or does the 12V trigger input require an external 12V source to activate the amplifier?
 
First look how work your amplifier trigger: some amplifier work with 5v and 12v, some with 12v.
if 5v is ok, use power of raspberry , if not converter is necessary. not sure to have answered to your question.
 
  • Like
Reactions: m-a
Thanks for a great tutorial!

I have a question about wiring a 5V to 12V converter for the 12V trigger. Currently, I’ve connected the positive (tip of the mono jack) of the converter to the NC terminal and the ground (sleeve of the mono jack) to the COM terminal of the relay.
As I understand it, the relay only opens or closes the circuit. I’m unsure how the amplifier handles the 12V trigger. Does it supply 12V through its trigger input and detect whether the circuit is open, or does the 12V trigger input require an external 12V source to activate the amplifier?
ok, I think I understood:
when you have 12v or 5v (look at your amp) on the tip of the jack, the amp turns on. So put the + of the converter on the COMMON of the relay output, the NO goes to tip of the jack, ground of jack to negative of converter. the relay is powered by the 5v of the Pi and the gpio goes to the input of trigger.
 

Attachments

  • IMG_20250129_184757.jpg
    IMG_20250129_184757.jpg
    123.8 KB · Views: 15
Last edited:
  • Like
Reactions: m-a
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
 
Last edited:
I have been trying to set up the remote and trigger according to your tutorial, but I run into problems. I don't have the tm1637 display, so I think that might be the case. Is there a simple way to disable the display in the script?

user@rbpi:~ $ python3 ~/scripts/remote6.py
Traceback (most recent call last):
File "/home/user/scripts/remote6.py", line 52, in <module>
import tm1637
ModuleNotFoundError: No module named 'tm1637'
 
Not have the tm1637 display isn't the problem. The problem is than raspberrypi-tm1637 library not installed or not find

if you have used first version of script for raspberrypiOS debian 11 and Camilladsp 2, install :

pip3 install raspberrypi-tm1637

else you use raspberrypiOS debian 12 & Camilladsp 3

try reinstall necessary python addons:

python -m venv ~/camilladsp/.venv
source ~/camilladsp/.venv/bin/activate
pip3 install raspberrypi-tm1637
pip3 install glob2
pip3 install evdev
deactivate


and when try start with /home/user/camilladsp/.venv/bin/python3 /home/user/scripts/remote6.py
 
Last edited:
  • Like
Reactions: m-a
Not have the tm1637 display isn't the problem. The problem is than raspberrypi-tm1637 library not installed or not find

if you have used first version of script for raspberrypiOS debian 11 and Camilladsp 2, install :

pip3 install raspberrypi-tm1637

else you use raspberrypiOS debian 12 & Camilladsp 3

try reinstall necessary python addons:

python -m venv ~/camilladsp/.venv
source ~/camilladsp/.venv/bin/activate
pip3 install raspberrypi-tm1637
pip3 install glob2
pip3 install evdev
deactivate


and when try start with /home/user/camilladsp/.venv/bin/python3 /home/user/scripts/remote6.py
Error, this is ok

for raspberrypiOS debian 12 & Camilladsp 3

try reinstall necessary python addons:

sudo apt install -y python3-evdev python3-glob2
python -m venv --system-site-packages ~/camilladsp/.venv
source ~/camilladsp/.venv/bin/activate
pip3 install --use-pep517 raspberrypi-tm1637
deactivate
 
Back
Top Bottom