Thank you for your donation!


Using the Flirc USB for remote control with mOde 5.4+
#1
[Update 20200217: The addition of triggerhappy in moOde 6.4.2 may render this Python approach unnecessary. See Richard's post. Stay tuned.]

[For the time being this is an on-going exploration. It will be converted to a proper HowTo when some odds-n-ends have been taken care of. This note assumes you are moderately Linux literate.]


The Flirc USB is an IR-to-USB adapter which "allows you to pair any remote control with your computer or media center. Just walk through our super simple cross platform pairing application, and you're done. Use your previously paired remote with no additional software on any machine with flirc" [quoting from the flirc.tv website].

Well, I didn't exactly find it super simple---there were some interesting bumps in the road---but I'm well along my way to a solution which allows me to control a number of moOde's playback functions from an IR-remote I had on hand (an older-model Roku IR-controller, as it happens, with 12 buttons).

I've implemented the usual play/pause, previous/next track, volume up/down, mute/unmute, load default playlist, load favorites playlist functions, and still have four buttons left over for functions I haven't thought about yet.

My approach differs from @didier31's FLIRC-USB for remote control in that I'm running a headless moOde installation without local display and wanted to avoid adding X-stuff I knew I didn't need. I chose to take advantage of a useful Python module instead. Candidly, I took this approach because I wanted to see if I could make it work and not because I thought there was anything wrong with his prior work. Courses for horses, as they say.

There's nothing special about my particular IR-remote and nothing special about the specific functions I chose to implement.

The Flirc presents to the operating system as a USB-keyboard input device. This makes life pretty simple---mostly.

Summary of steps:

  1. program the Flirc to output specific keycodes in response to button presses on the remote.
  2. write a Python script to react to events with these keycodes, invoking mpc or Tim's utility scripts.
  3. use systemd/udev to start the script when the Flirc is present at boot or hotplugged.
I've got 1) and 2) in hand and am working on a solution for 3) I can be happy with.

Some Details:

Nope, I'm not going to turn this into a travelogue of how I got from point A to point B by meandering through the forest. I'll put the gory details somewhere, maybe a personal blog, for those who care.

But I have to point out one detail. The Flirc documentation and software implies you can mix-and-match mimicking standard keyboard keys (like a-z), which they colorfully call "valid commands", and media-control keys (like play/pause). Well, yeah, you can but this turns out to involve two different USB endpoints, one for media-control and one for keyboard. 

At this stage of the game I wanted to deal with only one endpoint, and there weren't enough media-control codes available from Flirc (or at least I could find no way to get to them) for my needs, so I programmed all my buttons to emit keyboard codes (KEY_A, KEY_B, KEY_C, etc.), as you'll see in the script below. No one but the script sees the actual values so it doesn't matter unless you intend to move the Flirc back and forth between moOde and some other computer or media control center which expects to receive actual media-control commands as defined in the USB spec.

The script itself is an anticlimax:

Code:
pi@moode54b2:~ $ more Flirc-moode.py
#!/usr/bin/python3
import os, asyncio,evdev,subprocess,time

# Hackery by TheOldPresbyope
# - control some moOde playback functions from a Flirc USB

# the following endpoint tracks media keys:
#flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-event-if01')
# the following endpoint tracks "valid command" keys
flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd')

# make sure Raspbian doesn't consume the keyboard input before we do
flirc.grab()

# paired with my Roku remote to have the following outputs
# *** had to rework to use only "valid command" keys so could watch
# *** just one endpoint
#
#   Roku button                 Flirc output     moOde function
# "left arrow button"           -> "KEY_A"       => favorites
# "home button"                 -> "KEY_B"       => default playlist
# "up angle button"             -> "KEY_C"       => volume up
# "left-angle bracket button"   -> "KEY_D"       => previous
# "OK button"                   -> "KEY_E"       => mute/unmute
# "right angle bracket button"  -> "KEY_F"       => next
# "down angle button"           -> "KEY_G"       => volume down
# "redo button"                 -> "KEY_H"       => nothing yet
# "asterisk button"             -> "KEY_I"       => nothing yet
# "double-left diamond button"  -> "KEY_J"       => nothing yet
### careful: flirc_util 3.22.4 says "k" is not a valid letter!!!
# "play/pause button"           -> "KEY_L"       => play/pause
# "double-right diamond button" -> "KEY_M"       => nothing yet

for event in flirc.read_loop():
    if event.type == evdev.ecodes.EV_KEY:
        attrib = evdev.categorize(event)
        if attrib.keystate == 1:
            if attrib.keycode == 'KEY_F':
                subprocess.run(['mpc','next'])
            elif attrib.keycode == 'KEY_D':
                subprocess.run(['mpc','prev'])
            elif attrib.keycode == 'KEY_C':
                subprocess.run(['/var/www/vol.sh','-up','10'])
            elif attrib.keycode == 'KEY_G':
                subprocess.run(['/var/www/vol.sh','-dn','10'])
            elif attrib.keycode == 'KEY_L':
                subprocess.run(['mpc','toggle'])
            elif attrib.keycode == 'KEY_A':
                subprocess.run(['mpc','clear'])
                time.sleep(0.1)
                subprocess.run(['mpc','load','Favorites'])
            elif attrib.keycode == 'KEY_B':
                subprocess.run(['mpc','clear'])
                time.sleep(0.1)
                subprocess.run(['mpc','load','Default Playlist'])
            elif attrib.keycode == 'KEY_E':
                subprocess.run(['/var/www/vol.sh','-mute'])



Easy peasy. As you can see, this is mostly a simple case statement, except Python doesn't have a case statement! There's alternatives to the cascading if/elif construction but they're too razzle-dazzle for me, at least at this stage. The ordering of the tests got scrambled because of my reworking from media-control keycodes. It doesn't matter programmatically but it looks odd and will get fixed before the HowTo appears. The script loops endlessly until you kill it explicitly, you unplug the Flirc, or you reboot moOde.

So, to try it out, you first need to program your Flirc on another host to generate the keycodes you want with the IR-remote you have (see flirc.tv for software and docs). If you choose a different scheme from mine you'll need to do your homework on the keycodes actually emitted (hint: they're not in the Flirc docs).

For moOde, you need Raspbian Buster for its up-to-date Python3 and evdev module. This means Tim's moOde 5.4b2 soon-to-be moOde 6.

Install evdev

Code:
sudo apt-get install python3-evdev


Plug in the Flirc on your moOde player. Copy the script to somewhere convenient like pi's home directory, calling it what you want; change the key values to match your Flirc configration; and make the script executable (chmod +x) for convenience. Since I don't yet have a systemd/udev solution, you'll have to start the script manually or add it to moOde's startup activity (not recommended but if you never remove the Flirc this would work). For mine, this means:

Code:
./Flirc-moode.py

or

./Flirc-moode.py &

(where the script runs in the terminal in the first instance and persists in the background in the second)

Start pressing buttons and watch moOde responds. Note that you'll also start seeing the output from mpc commands since you've invoked the script from the command line. That'll go away with systemd/udev. You can play with redirecting output to the bit bucket if desired.

Final thought:

Almost exactly the same script works with my Satechi Bluetooth Multi-Media Remote. Unfortunately, the systemd/udev work is harder here because of the intermediary Bluetooth controller and because the Satechi goes to sleep after a period of inactivity. More to come.

Regards,
Kent
Reply
#2
Hi Kent, I thought to give your How-to a try with my Flirc (original) usb device. Pi 3 running MoOde 6.4, Kali and Piano Dac.

Errors when running ./Flirc-moode.py

Code:
./Flirc-moode.py
Traceback (most recent call last):
 File "/usr/lib/python3/dist-packages/evdev/device.py", line 125, in __init__
   fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK)
FileNotFoundError: [Errno 2] No such file or directory: '/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
 File "./Flirc-moode.py", line 10, in <module>
   flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd')
 File "/usr/lib/python3/dist-packages/evdev/device.py", line 127, in __init__
   fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK)
FileNotFoundError: [Errno 2] No such file or directory: '/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd'
 Script used as per your details but modded for HP remote..

Code:
#!/usr/bin/python3
import os, asyncio,evdev,subprocess,time

# Hackery by TheOldPresbyope
# - control some moOde playback functions from a Flirc USB

# the following endpoint tracks media keys:
#flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-event-if01')
# the following endpoint tracks "valid command" keys
flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd')

# make sure Raspbian doesn't consume the keyboard input before we do
flirc.grab()

# paired with my Roku remote to have the following outputs
# *** had to rework to use only "valid command" keys so could watch
# *** just one endpoint
#
#  HP button                 Flirc output     moOde function
# "Power button"                 -> "KEY_A"       => Shutdown
# "Play button"                  -> "KEY_B"       => Play
# "Pause button"                 -> "KEY_C"       => Pause
# "Previous button"              -> "KEY_D"       => Previous
# "Next button"                  -> "KEY_E"       => Next
# "Stop button"                  -> "KEY_F"       => Stop
# "1 button"                     -> "KEY_G"       => Radio 1
# "2 button"                     -> "KEY_H"       => Radio 2
# "3 button"                     -> "KEY_I"       => Radio 3
# "4 button"                     -> "KEY_J"       => Radio 4
### careful: flirc_util 3.22.4 says "k" is not a valid letter!!!
# "5 button"                     -> "KEY_L"       => Radio 5
# "6 button"                     -> "KEY_M"       => Radio 6
# "7 button"             -> "KEY_N"      => Radio 7
# "8 button"             -> "KEY_O"      => Radio 8
# "9 button"             -> "KEY_P"      => Radio 9
# "0 button"             -> "KEY_Q"      => Radio 0
# "Mute button"             -> "KEY_R"      => Mute/Unmute
# "Win button"             -> "KEY_S"      => Toggle Play/Pause
# "RedTV button"         -> "KEY_T"      => Favourites
    
for event in flirc.read_loop():
   if event.type == evdev.ecodes.EV_KEY:
       attrib = evdev.categorize(event)
       if attrib.keystate == 1:
           if attrib.keycode == 'KEY_A':
               subprocess.run(['mpc','shutdown'])
           elif attrib.keycode == 'KEY_B':
               subprocess.run(['mpc','play'])
           elif attrib.keycode == 'KEY_C':
               subprocess.run(['mpc','pause'])
           elif attrib.keycode == 'KEY_D':
               subprocess.run(['mpc','prev'])
           elif attrib.keycode == 'KEY_E':
               subprocess.run(['mpc','next'])
           elif attrib.keycode == 'KEY_F':
               subprocess.run(['mpc','stop'])
Code:
$ lsusb
Bus 001 Device 004: ID 20a0:0001 Clay Logic


Anything jump out there ?

Cheers,
BoB
Reply
#3
@DRONE7

Hi, Bob.

Thinking this project was "done and dusted" I moved on to try some other types of remotes. ATM I've got an OSMC RF remote on my desk I want to poke at.

I'll need to revisit what I did with the FLIRC. IIRC I did a little detective work with Python to see how it was enumerated in the /dev tree and then just assumed it would always be the same. Maybe this assumption falls down with an "original" FLIRC (if that is different from mine) or with other input devices also connected.

What else do you have connected to your RPi?

Regards,
Kent
Reply
#4
(12-15-2019, 02:33 PM)TheOldPresbyope Wrote: @DRONE7

Hi, Bob.

Thinking this project was "done and dusted" I moved on to try some other types of remotes. ATM I've got an OSMC RF remote on my desk I want to poke at.

I'll need to revisit what I did with the FLIRC. IIRC I did a little detective work with Python to see how it was enumerated in the /dev tree and then just assumed it would always be the same. Maybe this assumption falls down with an "original" FLIRC (if that is different from mine) or with other input devices also connected.

What else do you have connected to your RPi?

Regards,
Kent


Done and dusted? I see some promises of more work in my orginal post. Some shiny new thing must have caught my eye back in July and I was off in another direction!

Anyway, our two FLIRCs have different USB ids: For mine it's "20a0:0006" and for yours it's "20a0:0001". 

What do you see in /dev/input/by-id? I just spun up a fresh copy of moOde6.4.0 on an rPi3B+ and got

Code:
pi@moode:~ $ lsusb
Bus 001 Device 004: ID 20a0:0006 Clay Logic
Bus 001 Device 005: ID 0424:7800 Standard Microsystems Corp.
Bus 001 Device 003: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 002: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

pi@moode:~ $ ls -l /dev/input/by-id
total 0
lrwxrwxrwx 1 root root 9 Nov 26 12:54 usb-flirc.tv_flirc-event-if01 -> ../event1
lrwxrwxrwx 1 root root 9 Nov 26 12:54 usb-flirc.tv_flirc-if01-event-kbd -> ../event0


I'm not sure it will help but you might also post the verbose lsusb output. Here's the beginning of the output for my particular device id


Code:
pi@moode:~ $ sudo lsusb -v -d 20a0:0006

Bus 001 Device 004: ID 20a0:0006 Clay Logic
Device Descriptor:
 bLength                18
 bDescriptorType         1
 bcdUSB               2.00
 bDeviceClass            0
 bDeviceSubClass         0
 bDeviceProtocol         0
 bMaxPacketSize0         8
 idVendor           0x20a0 Clay Logic
 idProduct          0x0006
 bcdDevice            2.00
 iManufacturer           1 flirc.tv
 iProduct                2 flirc
 iSerial                 0
 bNumConfigurations      1
...
 
Regards,
Kent
Reply
#5
Rpi3-Kali-Piano Dac... nothing else connected.

Code:
$ lsusb
Bus 001 Device 004: ID 20a0:0001 Clay Logic
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Code:
ls -l /dev/input/by-id
total 0
lrwxrwxrwx 1 root root 9 Dec 15 20:38 usb-flirc.tv_flirc-event-if00 -> ../event1
lrwxrwxrwx 1 root root 9 Dec 15 20:38 usb-flirc.tv_flirc-event-kbd -> ../event0

Code:
$ sudo lsusb -v -d 20a0:0001

Bus 001 Device 004: ID 20a0:0001 Clay Logic
Device Descriptor:
 bLength                18
 bDescriptorType         1
 bcdUSB               1.10
 bDeviceClass            0
 bDeviceSubClass         0
 bDeviceProtocol         0
 bMaxPacketSize0         8
 idVendor           0x20a0 Clay Logic
 idProduct          0x0001
 bcdDevice            1.00
 iManufacturer           1 flirc.tv
 iProduct                2 flirc
 iSerial                 0
 bNumConfigurations      1
 Configuration Descriptor:
   bLength                 9
   bDescriptorType         2
   wTotalLength       0x0032
   bNumInterfaces          2
   bConfigurationValue     1
   iConfiguration          0
   bmAttributes         0xa0
     (Bus Powered)
     Remote Wakeup
   MaxPower              100mA
   Interface Descriptor:
     bLength                 9
     bDescriptorType         4
     bInterfaceNumber        0
     bAlternateSetting       0
     bNumEndpoints           1
     bInterfaceClass         3 Human Interface Device
     bInterfaceSubClass      0
     bInterfaceProtocol      1 Keyboard
     iInterface              0
       HID Device Descriptor:
         bLength                 9
         bDescriptorType        33
         bcdHID               1.01
         bCountryCode            0 Not supported
         bNumDescriptors         1
         bDescriptorType        34 Report
         wDescriptorLength     148
        Report Descriptors:
          ** UNAVAILABLE **
     Endpoint Descriptor:
       bLength                 7
       bDescriptorType         5
       bEndpointAddress     0x81  EP 1 IN
       bmAttributes            3
         Transfer Type            Interrupt
         Synch Type               None
         Usage Type               Data
       wMaxPacketSize     0x0008  1x 8 bytes
       bInterval              50
   Interface Descriptor:
     bLength                 9
     bDescriptorType         4
     bInterfaceNumber        1
     bAlternateSetting       0
     bNumEndpoints           1
     bInterfaceClass       255 Vendor Specific Class
     bInterfaceSubClass    255 Vendor Specific Subclass
     bInterfaceProtocol    255 Vendor Specific Protocol
     iInterface              0
     Endpoint Descriptor:
       bLength                 7
       bDescriptorType         5
       bEndpointAddress     0x83  EP 3 IN
       bmAttributes            3
         Transfer Type            Interrupt
         Synch Type               None
         Usage Type               Data
       wMaxPacketSize     0x0008  1x 8 bytes
       bInterval              50
Device Status:     0x0000
 (Bus Powered)

would it be as simple as changing usb-flirc.tv_flirc-event-if01 to usb-flirc.tv_flirc-event-if00 in the appropriate file..?
Reply
#6
Interesting.

Yup. In Flirc-moode.py, try changing

Code:
# the following endpoint tracks media keys:
#flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-event-if01')
# the following endpoint tracks "valid command" keys
flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-if01-event-kbd')

to 

Code:
# the following endpoint tracks media keys:
#flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-event-if00')
# the following endpoint tracks "valid command" keys
flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-event-kbd')


Note I was using only the "valid command" keys, hence the script is using the kbd endpoint.

Regards,
Kent
Reply
#7
Ok now I have...

Code:
$ ls -l /dev/input/by-id
total 0
lrwxrwxrwx 1 root root 9 Dec 16 07:28 usb-flirc.tv_flirc-event-if00 -> ../event1
lrwxrwxrwx 1 root root 9 Dec 16 07:28 usb-flirc.tv_flirc-event-kbd -> ../event0
 and after a reboot still throwing the same errors


Code:
$ ./Flirc-moode.py
Traceback (most recent call last):
 File "/usr/lib/python3/dist-packages/evdev/device.py", line 125, in __init__
   fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK)
FileNotFoundError: [Errno 2] No such file or directory: '/dev/input/by-id/usb-flirc.tv_flirc-if00-event-kbd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
 File "./Flirc-moode.py", line 10, in <module>
   flirc=evdev.InputDevice('/dev/input/by-id/usb-flirc.tv_flirc-if00-event-kbd')
 File "/usr/lib/python3/dist-packages/evdev/device.py", line 127, in __init__
   fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK)
FileNotFoundError: [Errno 2] No such file or directory: '/dev/input/by-id/usb-flirc.tv_flirc-if00-event-kbd'

I'm now off to complete the building of a 'submarine' engine for a coffin.... so will check back tonight.
Cheers,
Bob.
Reply
#8
@DRONE7

Sorry, there's no '-if00-' in the kbd endpoint.
Reply
#9
(12-15-2019, 06:45 PM)TheOldPresbyope Wrote: @DRONE7

Sorry, there's no '-if00-' in the kbd endpoint.
Just had a break waiting for glue and paint to dry....and

Smile  Yes, removed '-if00-' and that got the Flirc V1 active Big Grin  and I'm getting output for some buttons. Progress !


Next break I will redo my mpc configs in ..
Code:
Flirc-moode.py

Thanks !!
Reply
#10
@DRONE7

Glad to hear you're making progress. I'm trying to understand USB better. ATM I'm trying to understand how names like "usb-flirc.tv_flirc-event-kbd" get generated from the low-level USB-descriptor information I see. I shouldn't have to cut-n-paste it into my script. Haven't a clue yet but maybe I just haven't read enough background material.

This will yield somewhat redundant information, but I'd appreciate you at some point sending me the output from


Code:
sudo cat /sys/kernel/debug/usb/devices

Regards,
Kent
Reply


Forum Jump: