• 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

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
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:
OP
M

melomane13

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
Location
France
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:
OP
M

melomane13

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
Location
France
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
OP
M

melomane13

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
Location
France
#### 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
OP
M

melomane13

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
Location
France
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

phofman

Addicted to Fun and Learning
Joined
Apr 13, 2021
Messages
502
Likes
326
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.
 
OP
M

melomane13

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
Location
France
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.
 

MCH

Major Contributor
Joined
Apr 10, 2021
Messages
2,658
Likes
2,266
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.
 
OP
M

melomane13

Member
Joined
Jul 24, 2023
Messages
99
Likes
70
Location
France
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: 31
Last edited:
Top Bottom