volumio-rotary-encoder-plugin
volumio-rotary-encoder-plugin copied to clipboard
Could not get it to work, resolved by using external program
Hi, as said before by others, it is very hard to get it to work. So I used a different approach (also used by others): a python script that catches the rotary events and changes the volume in Volumio. Problem here is that Volumio is not reponding fast enough to process all movement. This script keeps adjusting the volume until it matches the one set by the rotary.
Maybe it helps others.
#!/usr/bin/python3.4
import RPi.GPIO as GPIO
import time
import subprocess
from socketIO_client import SocketIO, LoggingNamespace
GPIO.setmode(GPIO.BCM)
socketIO = SocketIO('localhost', 3000)
rotA = 24
rotB = 23
buttons = [17, 27]
status = 'pause'
service = ''
volatile = True
actVolume = 50 # actual volume
pushVolume = 50 # to be volume
volumePushed = False # new volume pushed
volumeRot = False # new volume set by rotary
lastRot = None
levA = 0
levB = 0
def button_pressed(channel):
global status
if channel == 17:
print ('next')
socketIO.emit('next')
elif channel == 27:
print ('play/pause/stop')
print('state', status)
if status == 'play':
if service == 'mpd':
socketIO.emit('pause')
else:
socketIO.emit('stop')
else:
socketIO.emit('play')
else:
print ("unknown button", channel)
def rotary_turned(channel):
global rotA, rotB, lastRot, levA, levB, pushVolume, volumePushed, volumeRot
level = GPIO.input(channel)
if channel == rotA:
if levA == level: return # no change in level -> skip. Debounce on reversing direction
levA = level
else:
if levB == level: return # no change in level -> skip. Debounce on reversing direction
levB = level
# print(channel, lastRot ,GPIO.input(rotA),GPIO.input(rotB),levA,levB)
if channel == lastRot:
return
lastRot = channel
oldVolume = pushVolume
if channel == rotA and level == 1:
if levB == 1:
pushVolume += 1
elif channel == rotB and level == 1:
if levA == 1:
pushVolume -= 1
if pushVolume > 100: pushVolume = 100
if pushVolume < 0: pushVolume = 0
if pushVolume == oldVolume:
return
volumeRot = True
if volumePushed:
volumePushed = False
print(channel, levA, levB, 'pushVolume:', pushVolume)
equalize_volume()
def setup_channel(channel):
try:
print ("register %d" %channel)
GPIO.setup(channel, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(channel, GPIO.FALLING, callback = button_pressed, bouncetime = 400)
print ('success')
except (ValueError, RuntimeError) as e:
print ('ERROR:', e)
def setup_rotary(channel):
try:
print ("register %d" %channel)
GPIO.setup(channel, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(channel, GPIO.BOTH, callback = rotary_turned)
print ('success')
except (ValueError, RuntimeError) as e:
print ('ERROR:', e)
def equalize_volume():
global rotA, rotB, lastRot, levA, levB, pushVolume, actVolume, volumePushed, volumeRot
# print('Equalize_volume Start','act:',actVolume,'push:',pushVolume)
if actVolume != pushVolume:
if volumeRot:
socketIO.emit('volume',pushVolume)
else:
pushVolume = actVolume
else:
volumePushed = True
volumeRot = False
print('Equalize_volume Ended','act:',actVolume,'push:',pushVolume)
def on_push_state(*args):
# print('state', args)
global status, service, volatile, actVolume
# status = args[0]['status'].encode('ascii', 'ignore')
status = str(args[0]['status'])
if ('volatile' in args[0]):
volatile = str(args[0]['volatile']) == "True"
else: volatile = False
if ('service' in args[0]) and args[0]['service'] != '':
service = str(args[0]['service'])
else:
service = ''
if ('volume' in args[0]):
try:
actVolume = int(args[0]['volume'])
except:
actVolume = 0
else:
volume = 0
if ('stream' in args[0]):
print ('stream',str(args[0]['stream']))
print ('Push:','Status:', status, 'Service:', service, 'Volatile:', volatile, 'Volume:', actVolume)
equalize_volume()
for x in buttons:
setup_channel(x)
for x in [rotA,rotB]:
setup_rotary(x)
socketIO.on('pushState', on_push_state)
# get initial state
socketIO.emit('getState', '', on_push_state)
pushVolume = actVolume
volumePushed = True
try:
socketIO.wait()
except KeyboardInterrupt:
pass
GPIO.cleanup()
I've not read the script entirely and Python is definitely not my strong suit. But it looks like you are saving up the changes and "commit" then on the button push.
I'm now exploring whether or not ALSA can be called directly, this would make it more snappy. The only thing needed is to sync it to the Volumio UI. I'm guessing that's where the delay is at.
HW -> node JS -> UI
If you enable logging you will see that there is a snappy response from node JS when turning the knob, but calling Volumio (over the command line or socket) causes delays in the system.
The most sensible approach would be to have some visualisation (LCD) and buffer the changes before sending them to Volumio. My suggestion would be to update ALSA and have Volumio poll ALSA volume levels or subscribe to changes.
Note: this only fixes volume, there are other functions for the encoder ;)
Hi Saiyato,
Thanks for your reply.
The problem with the ALSA approach is that changes in ALSA mixer are not picked up by Volumio. Eg: If the volume is 80 and I change it to 50 in ALSA, it is still 80 in Volumio. Next time I decrease the volume by 10, it will be 70 if I do it in Volumio, and it will be 40 if I do it in Alsa. Quite a difference.
Direct control over ALSA mixer: https://github.com/iqaudio/tools/blob/master/IQ_rot.c
[...] HW -> node JS -> UI
If you enable logging you will see that there is a snappy response from node JS when turning the knob, but calling Volumio (over the command line or socket) causes delays in the system. [...] Maybe I had a slow Raspberry Pi 2 but in my experience there can be a delay in node JS. Big enough to miss most of the rotary movement.
Yeah, thats why I want to propagate the new volume to Volumio asynchronous, this will give you snappy response in terms of volume control, the GUI just lags behind a bit.
When I was working on the solution I did get good results from the log on my Pi3, not sure if I tried it on the Pi2 as well... I had to count detents carefully, but I didn't seem to miss any.
The direct control uses the same logic as I do for detent and direction detection, so that's a relieve for me :) Since I didn't experience misfire I was looking for node JS libraries to control alsa directly and subscribe to ALSA events. But that's mostly because I get javascript, Python however... not so much ;)