Thank you for your donation!


Cloudsmith graciously provides open-source package management and distribution for our project.


[How To] Using the Satechi Bluetooth remote with moOde 5.4+
#1
[Revision 2 - add -M option to 'at' so it never try to send email to user]
[Revision 1 - add missing package, make scripts executable, change variable name in script, remind to reboot, add postscript]

The Satechi Bluetooth Multi-Media Remote is billed by Satechi as "the ultimate multi-media companion for your Bluetooth iOS device." (https://satechi.net/products/satechi-blu...dia-remote)

Irrespective of their marketing hype, it's a general-purpose BT remote which can be connected up to control a number of moOde playback functions. Its only shortcoming to my way of thinking is its price---as befits an "iOS" accessory, it costs as much as an RPi3B+---but I originally bought mine for a different purpose, so sunk cost and all that.

There are eight buttons on the device which I've mapped to play/pause, previous track and next track, volume up and volume down, mute/unmute, load Favorites, and load Default Playlist.

Prerequisites:
  • Raspbian Buster (for up-to-date Python3 and Python evdev module)
  • moOde Bluetooth controller present and enabled (I'm developing on an RPi3B+)
  • The Satechi remote paired with, connected to, and trusted by the moOde Bluetooth subsystem (using bluetoothctl). [See postscript] This need be done only once unless/until the moOde BT controller is reset. Rebooting is not an issue in this context.
Note: Once paired, the Satechi will show up on the moOde Bluez Config screen as "Bluetooth Media Control & Camera Shutter Click". Ignore this. Obviously, it is not an audio device. Do not attempt to connect via this screen.


Installation:

[The following assumes a moderate level of Linux literacy, such as recognizing I'm using the cat command to list the contents of files rather than showing the steps to create the files, knowing how to mark files executable, etc. If there's a demand, I'll pull together a build script which does it all.]

First, install the Python evdev package and the "at" package

Code:
sudo apt-get install python3-evdev at


Three additional files are needed. A udev rules file, a one-line utility startup shell script, and a small Python script. 
  • Contrary to what I conjectured in an earlier post about the Flirc USB, the udev rules turned out to be easy even though the Satechi goes to sleep after a period of inactivity. Using the "at now" construction in an intermediary startup shell script allows the invoked Python script to continue running after udev finishes and kills the startup script. This allowed me to avoid using systemd (although using systemd is considered the "right" approach by the purists).

The rules file goes into /etc/udev/rules.d


Code:
pi@moode54b2:~ $ ls -l /etc/udev/rules.d/42-satechi.rules
-rw-r--r-- 1 root root 316 Jul 12 13:10 /etc/udev/rules.d/42-satechi.rules

pi@moode54b2:~ $ cat /etc/udev/rules.d/42-satechi.rules
ACTION=="add", KERNEL=="event*", SUBSYSTEM=="input", ATTRS{name}=="Bluetooth Media Control & Camera Shutter Click Consumer Control", SYMLINK+="SBMMR"
ACTION=="add", KERNEL=="event*", SUBSYSTEM=="input", ATTRS{name}=="Bluetooth Media Control & Camera Shutter Click Consumer Control", RUN+="/usr/local/bin/satechi.sh"

[Note added in post-edit: This forum's formatting software folded the long lines, making it appear this file has four lines. In actuality, there are only two lines, each beginning "ACTION".]

Why 42? If you know the Hitchhiker's Guide to the Galaxy you needn't ask.

The utility startup shell script and the Python script both go into /usr/local/bin (Linux-heads know this is not the only place they could go, but the rules file and shell script have to be adjusted accordingly). Make them executable, of course.


Code:
pi@moode54b2:~ $ ls -l /usr/local/bin/sa*
-rwxr-xr-x 1 root root 3275 Jul 12 12:58 /usr/local/bin/satechi.py
-rwxr-xr-x 1 root root   50 Jul 12 12:58 /usr/local/bin/satechi.sh

The utility startup shell script is simply

Code:
pi@moode54b2:~ $ cat /usr/local/bin/satechi.sh
#!/bin/sh
echo /usr/local/bin/satechi.py | at -M now

The Python script is

Code:
pi@moode54b2:~ $ cat /usr/local/bin/satechi.py
#!/usr/bin/python3
import evdev,subprocess,time

# Hackery by TheOldPresbyope, 20190712
# - Control some moOde playback functions using a Satechi Bluetooth Multi-Media Remote

# Prerequisites:
# - Raspbian Buster (for up-to-date Python3 and Python evdev module)
# - moOde Bluetooth controller present and enabled (I'm developing on an RPi3B+)
# - The Satechi remote is paired with, connected to, and trusted by the
#       moOde Bluetooth subsystem (using bluetoothctl). This need be done
#       only once unless/until the moOde BT controller is reset.

# Basics:
# - Initially, the Satechi remote is asleep.
# - A companion udev rule is triggered when the Satechi is awakened by a
#     button press and the signal is detected by moOde's BT controller;
#     the rule creates a symlink /dev/SBMMR for convenience and
#     invokes a companion helper script which in turn starts this  script
# - When the Satechi goes back to sleep after a period of inactivity, udev
#     removes the symlink and this script dies (error ends, actually) when
#     it can no longer find it.
# - The indirect methon used to invoke this Python script allows it to
#      run forever as long as the Satechi is awake, or until the
#      script is killed either by the root user or by rebooting.
# NOTE: it seems to take some seconds after an initial button-press before
#         all the machinery is working and moOde starts responding.
#         Not sure why. Sadly, the first press or two is lost in the shuffle.
#         Maybe there's a fix, maybe not.  
# Revision 1: change object-instance name to satechi for clarity
 
satechi=evdev.InputDevice('/dev/SBMMR')

# Make sure Raspbian doesn't consume the inputs before we do
satechi.grab()

# Loop forever looking for key-down events from the Satechi, mapping the
#   resulting keycodes into moOde operations.
# The keycodes being transmitted were determined through testing.
# The keycodes are burned into the Satechi microcode and can't be changed.
# Mostly the button-icons are self-explanatory. The two which aren't are a
#   a button whose icon looks like an open rectangle, whose keycode I mapped
#   to "load Favorites" and a button whose icon looks vaguely like a
#   keyboard whose keycode I mapped to "load Default Playlist".

for event in satechi.read_loop():
    if event.type == evdev.ecodes.EV_KEY:
        attrib = evdev.categorize(event)
        if attrib.keystate == 1:
            if attrib.keycode == 'KEY_NEXTSONG':
                subprocess.run(['mpc','next'])
            elif attrib.keycode == 'KEY_PREVIOUSSONG':
                subprocess.run(['mpc','prev'])
            elif attrib.keycode == 'KEY_VOLUMEUP':
                subprocess.run(['/var/www/vol.sh','-up','10'])
            elif attrib.keycode == 'KEY_VOLUMEDOWN':
                subprocess.run(['/var/www/vol.sh','-dn','10'])
            elif attrib.keycode == 'KEY_PLAYPAUSE':
                subprocess.run(['mpc','toggle'])
            elif attrib.keycode == 'KEY_HOMEPAGE':
                subprocess.run(['mpc','clear'])
                time.sleep(0.1)
                subprocess.run(['mpc','load','Default Playlist'])
            elif attrib.keycode == 'KEY_EJECTCD':
                subprocess.run(['mpc','clear'])
                time.sleep(0.1)
                subprocess.run(['mpc','load', 'Favorites'])
            # careful: the Satechi returns two keycodes in a Python list
            #          when the mute button is pressed
            elif 'KEY_MUTE' in attrib.keycode:
                subprocess.run(['/var/www/vol.sh','-mute'])


As I mentioned in my earlier post about the Flirc USB, these cascading if/elif statements are basically just one big case statement, Python style. 

If one wanted to use a different Bluetooth remote, one would have to determine the keycodes being output and adjust the mappings in the Python script accordingly. (With luck, all multi-media remotes emit the same basic set of keycodes but ya never know.) As well, one would have to determine the specifics of the new remote's BT attributes and adjust the rules file accordingly.

Now reboot to set the machinery in motion. (Linux pros know it's only necessary to force udev to reload the rules but let's keep it simple.) 

Final thought

Read my comments and note in the Python script. A shortcoming of the Satechi going to sleep is that it takes a moment for everything to start up on an initial button press. The first press or two get lost in the shuffle, so to speak, but within a few seconds moOde is responsive to the remote. I haven't thought of a fix. I don't believe this hiatus can be blamed on Python compiling the script into its intermediary form on each invocation but someday I'll get around to testing this conjecture. Meanwhile, it's nice having a remote which doesn't need to be pointed at a sensor.

Regards,
Kent

Postscript: By default, bluetoothctl requires the entry of a PIN from the Satechi remote during pairing. Handily, the Satechi has a pull-down cover revealing a miniature number keypad for doing exactly that! I seem to recall there's a way to bypass this challenge-response scheme---it may even have been discussed in another thread last year---but I didn't try.
Reply
#2
Hi Kent,

Very cool :-) Given that Bluetooth is built into >= 3B it might be worth spending the time to integrate this particular peripheral. What do you think?

Couple of questions:

1) Is the name "flirc" a holdover from your previous script?
2) I don't see the "at" command in stock Buster Lite. Did you apt-get a package that contains it?
- You may be able to use the "eval" command in place of "at now"
3) How does the event loop work?

-Tim
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply
#3
(07-12-2019, 08:48 PM)Tim Curtis Wrote: Hi Kent,

Very cool :-) Given that Bluetooth is built into >= 3B it might be worth spending the time to integrate this particular peripheral. What do you think?

Couple of questions:

1) Is the name "flirc" a holdover from your previous script?
2) I don't see the "at" command in stock Buster Lite. Did you apt-get a package that contains it?
- You may be able to use the "eval" command in place of "at now"
3) How does the event loop work?

-Tim

1) yeah. I don't recommend late-night editing! It's just the name I gave to the object instance but I'll fix the script and the How To.

2) oops, I forgot I'd installed the package earlier when I started the Flirc project. Again, I'll fix the How To to include "sudo apt-get install at"

3) The python-evdev module aka package is very cool. From the package docs intro

Quote:This package provides bindings to the generic input event interface in Linux. The evdev interface serves the purpose of passing events generated in the kernel directly to userspace through character devices that are typically located in /dev/input/.

This package also comes with bindings to uinput, the userspace input subsystem. Uinput allows userspace programs to create and handle input devices that can inject events directly into the input subsystem.

The read loop waits for input events generated in the kernel (via /dev/input/---my symlink resolves ultimately to some /dev/input/eventx). 

If the type of an event is EV_KEY, then test if it's attribute keystate indicates a keydown event ("1"). If so, match the keycode if possible against one of the values known to the script. If a match is found, use subprocess.run() to call an external program, like mpc, with appropriate arguments. Obviously the sky is the limit here.

Regards,
Kent
Reply
#4
As long as the event loop is not equivalent to a "while true with no sleep" that hogs the CPU, then all is good :-)
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply
#5
Well, Python is single-threaded and this loop blocks its thread. There's machinery in the evdev package to do this asynchronously within the Python program. AFAICT the extra effort gains me nothing in this single-purpose script. I hope to test it soon.

I see the process pop up in top/htop when it starts but it then falls off the bottom of the chart in terms of CPU usage. None of the 4 RPi3B+ CPUs is running even close to 10 percent.

You asked if it makes sense to integrate this device into moOde. I withhold judgment until I get a better handle on the apparently slow startup.

It would be nice if we had a solution which applies to more than one make/model remote.

If we assume other BT multimedia remotes emit the same keycodes (ala the USB spec) then the script could be generalized. I think this may be a safe assumption since these devices seem interchangeable with consumer devices (like smart phones). 

I'm uncertain about the udev rules, though. I specialized mine to the advertised name of the endpoint "Bluetooth Media Control & Camera Shutter Click Consumer Control" cuz it was low-hanging fruit. If it turns out that media remotes have a common signature then the rules could be generalized too.

Regards,
Kent
Reply
#6
Isn't the slow startup due to the remote having to re-connect after coming out of sleep mode? The initial button press is probably lost because it serves only to wake up the remote and reinitiate the BT connection.

For example if my Mac Air is sleeping and I key in my login password the first keypress is lost and just serves to display the login screen. I end up having to reenter my password after the login screen appears.
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply
#7
Sure that’s a part of the time delay. I just have no idea how much time it should take for the different steps in the process. Taking baby steps as I find time to run little tests.


By the way, it should be obvious that this code could be revised slightly to run on one host and control a moOde player on a different host, e.g., turn either a BT remote or an IR remote into a WiFi remote!
Reply
#8
In your OP you mentioned using bluetoothctl to do the paring.

Can it also be done using either the BlueZ screen or just letting the Paring Agent automatically accept the pairing?
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply
#9
(07-14-2019, 12:46 PM)Tim Curtis Wrote: In your OP you mentioned using bluetoothctl to do the paring.

Can it also be done using either the BlueZ screen or just letting the Paring Agent automatically accept the pairing?

I assume the Bluez screen coupled with the Pairing Agent solution will work but I haven't tried. I was too enamored with the cute keypad! 

Adding to my ToDo list.

It's kind of depressing to see that @f3rn4nd0d mentioned a BT device similar to mine on the diyaudio.com moOde thread three years ago (and I responded enthusiastically at the time!). He never followed up and obviously neither did I.
Reply
#10
lol, my TODO list has some pretty old items as well. Things like Pi-4 and Buster all of sudden popping up cause a lot of disruption.
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply


Forum Jump: