Added support of bluetooth remote G20BTS PRO to my Volumio on RPI4

Added support for exellent bluetooth remote G20BTS PRO (available on Aliexpress) to my RPI4 based Volumio. Now both playing commands and gyro mouse on screen with touchscreen plugin work perfectly

Here are steps

  1. Enter your RPI4 via ssh and pair your remote with bluetoothctl (comes with Volumio)
  2. Copy file g20.py (below) to /home/volumio and give execution permission: chmod +x g20.py
  3. Under sudo create file /lib/systemd/system/g20.service (below)
  4. sudo systemctl enable g20
  5. sudo systemctl start g20

Enjoy
Service will automatically be started on Volumio startup

File g20.py:

#!/usr/bin/python3

import struct
import requests
import time

URL='http://localhost:3000/api/v1/commands/?cmd='

dev_list_filename = '/proc/bus/input/devices'

while True:
    dev_list_file = open(dev_list_filename, 'r')

    dev_found = False
    infile_path = "/dev/input/"
    for line in dev_list_file:
        if '=' in line:
            tokens = line.split('=')
            if  'G20BTS PRO Consumer Control' in tokens[1]:
                dev_found = True
            if dev_found and 'Handlers' in tokens[0]:
                tokens = tokens[1].split(' ')
                infile_path = infile_path + tokens[1]
                break

    dev_list_file.close()
    if dev_found:
        EVENT_SIZE = struct.calcsize("llHHIllHHIllHHI")
        file = open(infile_path, "rb")
        event = file.read(EVENT_SIZE)
        pressed = -1
        while event:
            (tv_sec1, tv_usec1, type1, code1, value1, tv_sec2, tv_usec2, type2, code2, value2, tv_sec2, tv_usec3, type3, code3, value3) = struct.unpack("llHHIllHHIllHHI", event)
            if pressed == -1:
                pressed = value1

                url = ''

                if value1 == 786666:
                    url=URL + 'volume&volume=minus'
                elif value1 == 786665:
                    url=URL + 'volume&volume=plus'
                elif value1 == 786637:
                    url = URL + 'toggle'
                elif value1 == 786658:
                    url = URL + 'volume&volume=toggle'
                elif value1 == 786614:
                    url = URL + 'prev'
                elif value1 == 786613:
                    url = URL + 'next'
                if len(url) > 0:
                    try:
                        requests.get(url=url, timeout=0.000000001)
                    except requests.exceptions.ReadTimeout:
                        pass
            elif pressed == value1:
                pressed = -1
            event = file.read(EVENT_SIZE)
    else:
        time.sleep(1)

File g20.service:

[Unit]
Description = G20BT remote support

[Service]
ExecStart=/home/volumio/g20.py
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=G20
User=volumio
Group=volumio
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

2 Likes

Thanks for sharing!

Just a a precaution.
This script might break the functionality of peppy_meter, due to the Python while loop.
If people want to use this with Peppy_meter, convert the loop to a bash script that calls this python script.

Thank you for valuable remark. I installed PeppyAlsa+PeppyMeter and saw a problem. When i created a very simple bash file:
#!/bin/bash
/home/volumio/g20.py

gave execution permission and wrote itā€™s name to g20.service, problem disappeared and both remote and PepperMeter work perfectly

I believe that in this way it is possible to use almost all kinds of both bluetooth and usb dongle based remotes (the only fix is proper vendor name and key codes)

1 Like

Yeah, I did test your script with:

Only part I needed to adapt was indeed the ā€œNameā€, key codes and infile_path = infile_path + tokens[2]
(depending on the location of value in ā€˜Handlersā€™)
But this depends on the type of BT remote.

So in short, a very usefull contribution :smile:

1 Like

Thank you very much for posting. Iā€™m looking for a remote volume control functionality. Looks like this will do it. Thanks. Can you please elaborate on step 1. I know how to ssh to the rpi, but how do i pair?
txs

How to pair your bluetooth remote from terminal: look here

Looking for some help please. I think i figured out the name (G20BTS and the MAC address.
I was able to pair the remote. Only the off button works. I cannot control the volume with volume buttons. Looking for some help please ! :blush: I ran the python script and it gives me this, "No such file or directory: ā€˜/dev/input/kbdā€™ seems like its looking for a keyboard? anyways Iā€™m totally stuck now.

txs

Please post the output of:
cat /proc/bus/input/devices

volumio@pi4version3:~$ cat /proc/bus/input/devices                                                  
I: Bus=0005 Vendor=1d5a Product=c081 Version=0000
N: Name="G20BTS Keyboard"
P: Phys=B8:27:EB:21:47:5E
S: Sysfs=/devices/virtual/misc/uhid/0005:1D5A:C081.0001/input/input0
U: Uniq=AB:6B:70:5B:57:CF
H: Handlers=sysrq kbd leds event0
B: PROP=0
B: EV=12001f
B: KEY=306ff 0 0 0 0 483ffff 17aff32d bfd44446 0 0 3ff 130ff3 8b17c007 ffff7bfa d9415fff febeffdf ffefffff ffffffff fffffffe
B: REL=1040
B: ABS=1 0
B: MSC=10
B: LED=1f

I: Bus=0005 Vendor=1d5a Product=c081 Version=0000
N: Name="G20BTS Mouse"
P: Phys=B8:27:EB:21:47:5E
S: Sysfs=/devices/virtual/misc/uhid/0005:1D5A:C081.0001/input/input1
U: Uniq=AB:6B:70:5B:57:CF
H: Handlers=mouse0 event1
B: PROP=0
B: EV=17
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=903
B: MSC=10

I: Bus=0005 Vendor=1d5a Product=c081 Version=0000
N: Name="G20BTS"
P: Phys=B8:27:EB:21:47:5E
S: Sysfs=/devices/virtual/misc/uhid/0005:1D5A:C081.0001/input/input2
U: Uniq=AB:6B:70:5B:57:CF
H: Handlers=event2
B: PROP=0
B: EV=100001

Next:
cat /dev/input/event0
Shake mouse or hit buttons and see if there is a response and report back.

cat /dev/input/event1
Shake mouse or hit buttons and see if there is a response and report back.

cat /dev/input/event2
Shake mouse or hit buttons and see if there is a response and report back.

Yes I get a response. Like weird characters on the screen. on event0 only.
jkā–’eā–’sjkā–’eā–’ā–’
like this.

try if this does the trick. Just a guess as I donā€™t have the hardware.

while True:
    dev_list_file = open(dev_list_filename, 'r')

    dev_found = False
    infile_path = "/dev/input/"
    for line in dev_list_file:
        if '=' in line:
            tokens = line.split('=')
            if  'G20BTS Keyboard' in tokens[1]:
                dev_found = True
            if dev_found and 'Handlers' in tokens[0]:
                tokens = tokens[1].split(' ')
                infile_path = infile_path + tokens[3]
                break

    dev_list_file.close()
    if dev_found:
        EVENT_SIZE = struct.calcsize("llHHIllHHIllHHI")
        file = open(infile_path, "rb")
        event = file.read(EVENT_SIZE)
        pressed = -1
        while event:
            (tv_sec1, tv_usec1, type1, code1, value1, tv_sec2, tv_usec2, type2, code2, value2, tv_sec2, tv_usec3, type3, code3, value3) = struct.unpack("llHHIllHHIllHHI", event)
            if pressed == -1:
                pressed = value1

                url = ''

                if value1 == 786666:
                    url=URL + 'volume&volume=minus'
                elif value1 == 786665:
                    url=URL + 'volume&volume=plus'
                elif value1 == 786637:
                    url = URL + 'toggle'
                elif value1 == 786658:
                    url = URL + 'volume&volume=toggle'
                elif value1 == 786614:
                    url = URL + 'prev'
                elif value1 == 786613:
                    url = URL + 'next'
                if len(url) > 0:
                    try:
                        requests.get(url=url, timeout=0.000000001)
                    except requests.exceptions.ReadTimeout:
                        pass
            elif pressed == value1:
                pressed = -1
            event = file.read(EVENT_SIZE)
    else:
        time.sleep(1)

A different approach, as many USB ā€œAir-mouseā€ devices have 1 to many events, the code will not work. As it is only looking at one event.

This code can be extended to multiple events.

#!/usr/bin/python3

import time
import asyncio
import requests
from evdev import InputDevice, categorize, ecodes   # sudo apt-get install python3-evdev

URL='http://localhost:3000/api/v1/commands/?cmd='

# If mouse has more then one event, comment out what is not needed
event0 = InputDevice('/dev/input/event0')
#event1 = InputDevice('/dev/input/event1')
event2 = InputDevice('/dev/input/event2')
#event3 = InputDevice('/dev/input/event3')

STATUS = 0

# Keys see: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
EV_VAL_PRESSED = 1
EV_VAL_RELEASED = 0
# event0:
KEY_ENTER =      28
KEY_LEFT =       105
KEY_RIGHT =      106
KEY_UP =         103
KEY_DOWN =       108
KEY_PAGEDOWN =   109
KEY_PAGEUP =	 104
# event1:
# event2:
KEY_VOLUMEDOWN = 114
KEY_VOLUMEUP =	 115
KEY_PLAYPAUSE =  164
# event2:

print(event0)
#print(event1)
print(event2)
#print(event3)
print('=== Start ===')

async def print_events(device):
    global  STATUS            
    async for event in device.async_read_loop():
        if event.type == ecodes.EV_KEY:
            if event.value == EV_VAL_PRESSED:
                if event.value == EV_VAL_PRESSED:
                    if event.code == KEY_PLAYPAUSE:
                        print('<Play/Pause> Pressed')
                        url = URL + 'volume&volume=toggle'
                    elif event.code == KEY_LEFT:
                        print('<LEFT> Pressed')
                        url = URL + 'prev'
                    elif event.code == KEY_RIGHT:
                        print('<RIGHT> Pressed')
                        url = URL + 'next'
                    elif event.code == KEY_VOLUMEUP:
                        print('<RIGHT> Pressed')
                        url=URL + 'volume&volume=plus'
                    elif event.code == KEY_VOLUMEDOWN:
                        print('<RIGHT> Pressed')
                        url=URL + 'volume&volume=minus'                        
                if len(url) > 0:
                    try:
                        requests.get(url=url, timeout=0.000000001)
                    except requests.exceptions.ReadTimeout:
                        pass      
                print(device.path, categorize(event), sep=': ')          
                print('---')

for device in event0, event2:
    asyncio.ensure_future(print_events(device))

loop = asyncio.get_event_loop()
loop.run_forever()

No joy with changing to g20bts keyboard.
a couple of things. I do not have a screen on this device. does it need a screen maybe to work?
also if I run the python script I get this :
File ā€œg20.pyā€, line 29, in
file = open(infile_path, ā€œrbā€)
FileNotFoundError: [Errno 2] No such file or directory: ā€˜/dev/input/kbdā€™
Maybe thatā€™s a hint to whats going on.
Also, the usb code you sent is way above my capabilities.
Well hopefully you have other thoughts. Thanks!

Seems youā€™re not following my instruction and still run the code from the opening post.
Iā€™ve adapted that part in Added support of bluetooth remote G20BTS PRO to my Volumio on RPI4 - #14 by Wheaten

You say that event0 was providing output:

N: Name="G20BTS Keyboard"
H: Handlers=sysrq kbd leds event0
TOKEN:        0   1   2    3

So you need to count. Hence I gave you this piece

if  'G20BTS Keyboard' in tokens[1]:
                dev_found = True
            if dev_found and 'Handlers' in tokens[0]:
                tokens = tokens[1].split(' ')
                infile_path = infile_path + tokens[3]

As you mention:

FileNotFoundError: [Errno 2] No such file or directory: ā€˜/dev/input/kbdā€™

Seems your code is still from the opening post:
infile_path = infile_path + tokens[1]
So it reads token[1] => kbd, but you need token[3] => event0

Many thanks, your right, I did not have the change you suggested entered correctly.
Unfortunately still no joy. I am going to take a break and then start over and go over everything very carefully. However I do feel I have it all correct.
It would be easier to just get a ā€œJustBoomā€ remote, but here in Canada it is not available. I really am hoping I can get this going because it will increase my Volumio experience substantially! Maybe I have a directory problem. When I first ssh to Volumio, how do I navigate to /home/volumio?

The I would suggest to take the script form my post:

You only need to identify the buttons that populate event0

Iā€™m sorry, I donā€™t know how to do all that. Thank you for all the help.
I donā€™t see a way forward, I checked everything and I donā€™t see anything wrong.
just doesnā€™t work. unless Iā€™m suppose to have a screen installed. Could the problem be key codes? never did any changes thereā€¦

Anyways, I got triggerhappy working with this remote!!! so yeah. just started playing with it. but it looks like i can do what I want!

I bought another remote for my other system. it has the latest volumio image 3.6 but the remote never shows up when I do a scan on within bluetoothctl.
very frustrarting!

Actually it show up the first time from a fresh sd volumio image write. Fails to connect and then you never see it again unless you do a factory reset.