{ ^_^ } sinustrom Solving life, one problem at a time!

Sound Blaster X-Fi Surround 5.1 remote controller LIRC setup

2020-11-29
Author: Zoltan Puskas
Categories: linux
Tags:

I’ve used my USB sound card for almost a decade now, but somehow never got around to set up the remote controller until very recently. This post documents how to setup LIRC for the Creative Sound Blaster X-Fi Surround 5.1 (SB1090) external USB sound card with the Creative RM-850 remote controller under Linux.

Setup

This setup was originally done on a computer running Gentoo Linux, using the following component versions:

  • Linux kernel: 5.8.18
  • systemd: 247-rc2
  • udev: 232
  • lirc: 0.10.1

however it should be generic enough to be applicable to most recent Linux distributions. My sound card is attached via an USB 3.0 hub which is connected to a Thunderbolt docking station used by my laptop.

Creative Sound Blaster X-Fi Surround 5.1 (SB1090) external USB sound card with the Creative RM-850 remote controller

Kernel

Both the sound card and it’s remote control capabilities are enabled by the USB ALSA driver in the kernel. Additionally LIRC requires the user level driver support by default. The following kernel options have to be enabled:

  • CONFIG_INPUT_UINPUT
  • CONFIG_SND_USB_AUDIO
Device Drivers  --->
    <M> Sound card support  --->
        <M>   Advanced Linux Sound Architecture  --->
            [*]   USB sound devices  --->
                <M>   USB Audio/MIDI driver
        Input device support  --->
            Generic input layer  --->
            <*> Miscellaneous devices  --->
                <M> User level driver support

The Linux kernel will automatically load the snd_usb_audio module when the sound card is connected.

LIRC

Installing packages

Enable the lirc USE flag for PulseAudio and VLC, and if needed also adjust the flags for LIRC itself in /etc/portage/package.use

app-misc/lirc -gtk
media-sound/pulseaudio lirc
media-video/vlc lirc

Update packages by running:

# emerge -1 pulseaudio vlc

This will automatically pull in LIRC and all required dependencies.

Configuration

First LIRC needs to be told to use the right driver and hardware. The following fields need to be updated in the daemon section of /etc/lirc/lirc_options.conf to be:

[lircd]
driver = alsa_usb
device = hw:S51

Then a configuration for the remote controller must be placed under /etc/lirc.lircd.conf.d, while the default one needs to be disabled:

# mv devinput.lircd.conf devinput.lircd.conf.dist

The LIRC project hosts a collection of remote controller configuration files, however their version for the RM-850 remote did not work for me for some reason. Could it be that the EU version of the device is different to the US one?

After playing around with different configurations, the RM-1500 remote ended up partially working. So I took the general settings from there and mapped out all the keys of the RM-850 remote into a new configuration file. Once done, I saved it as /etc/lirc/lircd.conf.d/RM-850.lircd.conf:

# Config created with lirc-0.10.1 on 2020-11-08
# Created by Zoltan Puskas <zoltan|sinustrom.info>
#
# Brand:                               Creative
# Model no. of remote control:         RM-850
# Supported devices:                   Sound Blaster X-Fi Surround 5.1 SB1090
#
# This is for the integrated IR receiver in Creative USB audio devices
# when accessed with LIRC's alsa_usb driver (requires the snd-usb-audio
# driver from ALSA 1.0.9 or later).
#
 
begin remote
 
        name    RM-850
        bits    8
        gap 300000
 
        begin codes
          KEY_POWER                0x01                      #  power
          CMSS                     0x0c                      #  X-Fi CMSS-3D on|off
          KEY_MUTE                 0x0d                      #  mute
          KEY_RECORD               0x0e                      #  rec
          KEY_VOLUMEDOWN           0x0f                      #  volume -
          KEY_VOLUMEUP             0x10                      #  volume +
          KEY_STOPEJECT            0x11                      #  stop/eject
          KEY_PLAYPAUSE            0x12                      #  play/pause
          KEY_PREVIOUS             0x14                      #  previous
          KEY_NEXT                 0x15                      #  next
          KEY_OPTION               0x18                      #  option
          KEY_RETURN               0x1a                      #  return
          KEY_START                0x1b                      #  start
          KEY_CANCEL               0x1c                      #  cancel
          KEY_UP                   0x1d                      #  up
          KEY_OK                   0x1f                      #  ok
          KEY_DOWN                 0x21                      #  down
          KEY_CMSSPLUS             0x22                      #  CMSS +
          KEY_CMSSMINUS            0x23                      #  CMSS -
          KEY_CRYSTAL              0x24                      #  X-Fi crystalizer on/off
          KEY_CRYSTALPLUS          0x25                      #  crystalizer +
          KEY_CRYSTALMINUS         0x26                      #  crystalizer -
          KEY_LEFT                 0x27                      #  left
          KEY_RIGHT                0x28                      #  right
        end codes
 
end remote

Above key map also works for the wheel on the sound card itself, which emits volume up/down when rotated and mute when pressed.

Starting the service

Starting the LIRC service socket:

# systemctl start lircd.socket

In order to have it started upon boot we also have to enable it:

# systemctl enable lircd.socket

The lircd.service will be automatically started once something tries to connect to the socket and use it.

User action configuration

Actions need to be assigned to each button event, which can be done through configuration local to the user. Below is minimalistic example configuration mapping volume actions to the global volume events (i.e. as if we used the keyboard), saved to ~/.config/lircrc:

# Control volume the same as if it was done from the keyboard
begin
   remote = RM-850
   prog = irexec
   config = /usr/bin/xdotool key XF86AudioLowerVolume
   button = KEY_VOLUMEDOWN
   repeat = 1
end
 
begin
   remote = RM-850
   prog = irexec
   config = /usr/bin/xdotool key XF86AudioRaiseVolume
   button = KEY_VOLUMEUP
   repeat = 1
end
 
begin
   remote = RM-850
   prog = irexec
   config = /usr/bin/xdotool key XF86AudioMute
   button = KEY_MUTE
end

Above configuration assumes x11-misc/xdotool is installed on the system. It’s also best to use absolute paths for the binaries passed to irexec, to avoid potential issues.

After starting irexec by running:

$ irexec -d

the remote controller should trigger the configured actions.

Automating cold- and hot plug

At this point the remote controller setup works, but only for the current boot session. It would be nice if the setup could also handle:

  • future boots, when the card is connected (i.e. cold plug)
  • future boots, when the card is disconnected (i.e. on the move)
  • plugging in the sound card at any time (i.e. hot plug)
  • unplugging the sound card at any time

UDEV

First let’s create an udev rule that will handle the sound card and save it as /etc/udev/rules.d/77-sbxfi51.rules:

# When the sound card USB device is plugged in make sure lircd.socket service is started if it's not running yet
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idProduct}=="3042", ATTRS{idVendor}=="041e", TAG+="systemd", ENV{SYSTEMD_WANTS}+="lircd.socket"
# When the device is recognized as SB X-Fi 5.1 card let's add a systemd device named dev-snd-sbxfi51.device and ensure our user service is started
ACTION=="add", SUBSYSTEMS=="sound", ATTRS{id}=="S51", TAG+="systemd", SYMLINK+="snd/sbxfi51", ENV{SYSTEMD_ALIAS}="/dev/snd/sbxfi51", ENV{SYSTEMD_WANTS}+="lircd.socket", ENV{SYSTEMD_USER_WANTS}+="irexec.service"
# When the device is unplugged stop all LIRC services
ACTION=="remove", SUBSYSTEM=="usb", ENV{PRODUCT}=="41e/3042/*", TAG+="systemd"

The reason we need to start lircd.socket is because it could be stopped after an unplug event, despite it being started by default on boot.

Also note that as per udev(7) “Starting daemons or other long-running processes is not allowed; the forked processes, detached or not, will be unconditionally killed after the event handling has finished. In order to activate long-running processes from udev rules, provide a service unit and pull it in from a udev device using the SYSTEMD_WANTS device property.”, i.e. any services we want to start should be done so through a systemd unit.

We use the SYSTEMD_ALIAS to have a predictable systemd device name, as the default name depends on the USB port the sound card has been plugged into and thus subject to change.

Finally we stop lircd.socket during an unplug event to save system resources as well as ensure proper operation during a subsequent hot plug event.

Once the udev rule is put in place it’s time to let udev know about it:

# udevadm control --reload-rules

System services

Next we need to adjust the default LIRC services for a more optimal operation. First let’s ensure that lircd.socket is stopped when the sound card is unplugged:

# systemctl edit lircd.socket

and add

[Unit]
BindsTo=dev-snd-sbxfi51.device
StopWhenUnneeded=yes

Then also let’s make sure lircd.service depends on lircd.socket, so it will be automatically stopped when the socket goes away:

# systemctl edit lircd.service

and add

[Unit]
Requires=lircd.socket

Let’s not forget to reload systemd, so it can pick up the new configuration:

# systemctl daemon-reload

User service

Finally let’s create a user systemd service that will start and stop irexec based on the sound card’s availability while the user is logged in. Let’s create the service directory fist:

$ mkdir -p ~/.config/systemd/user 

and then place the user service configuration there as ~/.config/systemd/user/irexec.service:

[Unit]
Documentation=man:irexec(1)
Documentation=man:lircrc(5)
Description=LIRC executor - handle events from IR remotes decoded by lircd(8)
After=lircd.socket
BindsTo=dev-snd-sbxfi51.device
StopWhenUnneeded=yes
ConditionPathIsSymbolicLink=/dev/snd/sbxfi51
 
[Service]
Type=simple
Environment=DISPLAY=:0
Environment=XAUTHORITY=%h/.Xauthority
ExecStart=/usr/bin/irexec %h/.config/lircrc
 
[Install]
WantedBy=default.target

This service depends on both the socket being active as well as the sound card device being present, which are handled by the previously set up udev rules. It will also shut itself down when the device is unplugged. Note that while irexec would exit by itself when lircd.service dies, it’s cleaner and more explicit this way.

The environment settings are needed as the unit starts before the desktop environment is done fully initializing, so pointing it to the right X session is required, otherwise the service will not work after logging in even though it has been launched.

Once the configuration is in place we can let systemd know about the new unit and also enable it:

$ systemctl --user daemon-reload
$ systemctl --user enable irexec.service

After this all the services should automatically start and stop in the correct order as the sound card is plugged in or removed, ensuring that not only we have a working remote controller but also that we only run services on demand.

Debugging and testing

Getting to the above working configuration actually was not that straightforward and involved quite a bit of reading and experimentation. So if you plan to make your own custom setup or just got stuck while trying to replicate this one, these tips might help you out.

Reading

Reading man pages cleared up a lot of confusion and helped to understand the behaviour of the sytem components better. Recommended man pages for customizing the setup:

Additional pages worth reading:

IR receiver testing

Testing is done with irw by simply running it and then pressing buttons and checking if everything works. This needs the LIRC service to be running. Example result:

$ irw
0000000000000010 00 KEY_VOLUMEUP RM-850
000000000000001d 00 KEY_UP RM-850
000000000000001d 00 KEY_UP RM-850
0000000000000012 00 KEY_PLAYPAUSE RM-850
0000000000000011 00 KEY_STOPEJECT RM-850
0000000000000010 00 KEY_VOLUMEUP RM-850
0000000000000010 01 KEY_VOLUMEUP RM-850
000000000000000f 00 KEY_VOLUMEDOWN RM-850

UDEV

Monitoring udev events:

$ udevadm monitor --environment --udev

When writing “remove” rules it’s important to use udev environment variables, as by the time this rule runs the device is already removed from the system and the usual attributes are not available any more (e.g. what was used to match on the addition rules)! The above command will help to find out the relevant valid fields and environment variables.

Information on the device, it’s tree, and all attributes (replace device path with yours):

$ udevadm info -a -n /dev/snd/by-id/usb-Creative_Technology_SB_X-Fi_Surround_5.1__blank_-00

Systemd

Monitor the status of systemd services as the device is plugged in and out:

$ watch -n1 systemctl status lircd.service lircd.socket
$ watch -n1 systemctl --user status irexec.service

List devices recognized by systemd:

$ systemctl --all --full -t device

For example the sound card will appear as dev-snd-by\x2dpath-pci\x2d0000:0c:00.0\x2dusb\x2d0:3.2:1.0.device by default, however after adding the custom udev rule the dev-snd-sbxfi51.device alias will also appear with loaded, active, and plugged statuses.

In case of failures it’s worth to look at the logs too:

$ journalctl --unit lircd.socket
$ journalctl --unit lircd.service
$ journalctl --user --user-unit irexec.service

Content