Author Topic: IR remote receiver for Linux  (Read 4704 times)

0 Members and 1 Guest are viewing this topic.

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
IR remote receiver for Linux
« on: December 29, 2022, 06:08:13 pm »
I'm looking for a Linux infrared receiver that can learn the codes from a domestic remote control, and use the IR remote's buttons to control a Linux media player.  I was expecting this would be a standard and trivial setup, but apparently it is not.

Search engines return the first 1-2 pages full with LIRC (Linux Infrared Remote Control).  That used to work some years ago, then part of LIRC was integrated in the Linux kernel, though with less functionality than the original, and now LIRC doesn't work any longer with the current Ubuntu 22.04 LTS.  I've found a workaround by installing the much older LIRC from Ubuntu 16.04 (from about 5-10 years ago) but that doesn't create configuration files.

Meanwhile some players dropped the LIRC support expecting any IR control to be done through other Linux mechanisms.

I need a DIY IR receiver.  Already have TSOP1738 IR receiver IC, and CH340 or FT232R serial to USB bridges.  Also have ATmega8 or Arduino UNO/nano/DUE to connect the TSOP1738 to the desktop's USB.  I don't have any madia center installed, all I need is play/pause and volume for casual media players.

This is the second afternoon trying to get IR receiver functionality and nothing works when it comes to install LIRC.  Maybe LIRC is a thing of the past but I'm in a bubble search that only shows LIRC based solutions.  :-//

Anybody knows a tested and working setup for an easy to set IR receiver, please?



LATER EDIT:
==========
Solved (almost) by using a version of LIRC from many years ago, v0.9.0, like this:
https://www.eevblog.com/forum/networking/ir-remote-receiver-for-linux/msg4612618/#msg4612618

More setting for executing shell commands from an IR remote (with LIRC and irexec), and other settings to control the 'mpv' player with the help of 'socat':
https://www.eevblog.com/forum/networking/ir-remote-receiver-for-linux/msg4615912/#msg4615912

Even more settings and reconfigure the irexec service to control the last active player through MPRIS
https://www.eevblog.com/forum/networking/ir-remote-receiver-for-linux/msg4624810/#msg4624810
« Last Edit: January 08, 2023, 10:19:58 am by RoGeorge »
 

Online Twoflower

  • Frequent Contributor
  • **
  • Posts: 735
  • Country: de
Re: IR remote receiver for Linux
« Reply #1 on: December 29, 2022, 06:33:36 pm »
That's a painful topic you're facing. Had painful time of learning there (and probably it is till not perfect). Some things I don't understand 100% and I still see an error message that a file is not found, but it works for me. Eventually you can use that stuff as starting-point. The things I did to get it up and running on an RasPi is:

Enable the gpio-ir overlay (not sure how this will be done for UEFI boot procedures), and don't load the lirc-rpi overlay:
Code: [Select]
dtoverlay=gpio-ir,gpio_pin=23,rc-map-name=apple_remoteThe string apple_remote can freely chosen filename and the number is the IO-Pin I used (not all can be used!). After reboot this provides a new device under /dev/input/event0 (eventually the number might be different in your system). You can try cat  /dev/input/event0 to see if the remote is recognized already.

In /etc/rc_keymaps/ you create a file with the name you used above. There are many pre-defined keymaps you can try as well (here for the simplistic white Apple remote):
Code: [Select]
# table apple_remote, type: NEC
0x87eec30b KEY_UP
0x87eec30d KEY_DOWN
0x87eec308 KEY_LEFT
0x87eec307 KEY_RIGHT
0x87eec304 KEY_ENTER
0x87eec302 KEY_ESC

Now the file needs to be announced in the /etc/rc_maps.cfg by adding that at the end of the file:
Code: [Select]
* * apple_remote
This should already create the keystrokes mentioned in the /etc/rc_keymaps/* file.

(Probably you'll find that somewhere in the web with some ranting in a blog with some additional information)
 
The following users thanked this post: RoGeorge

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: IR remote receiver for Linux
« Reply #2 on: December 29, 2022, 10:27:35 pm »
I need a DIY IR receiver.  Already have TSOP1738 IR receiver IC, and CH340 or FT232R serial to USB bridges.
Serial to USB bridges?  Oh noooooooo....

If you had used a microcontroller with native USB, you could have done everything within the microcontroller, and simply send the events as HID keypresses (multimedia keys).  The result is an USB IR receiver that works in all OSes without any drivers, because it is an USB HID keyboard device.

Personally, I would have used a Teensy LC because I have one, they're cheap (difficult to obtain right now due to chipageddon, though), and because Teensyduino includes the IRremote library (but see Ken Shirriff's latest version at github:/Arduino-IRremote/Arduino-IRremote).

I'd first use the Receive example on the Teensyduino page to map out all the remotes I want to use and the codes they send.
Then, I'd write the couple of dozen lines needed to implement a Teensy USB HID Keyboard to emit the multimedia keypresses (KEY_MEDIA_PLAY, KEY_MEDIA_PAUSE, KEY_MEDIA_STOP, KEY_MEDIA_VOLUME_INC, KEY_MEDIA_VOLUME_DEC, etc.).

You do not actually need a Teensy one for this, of course.

You can buy one of the ATmega32u4 based Arduino clones (Pro Micro clones, look for Pro Micro silkscreened on the board) off eBay from European sellers for under 15€ shipped.  I've used the ones with USB Micro B connector, and they work just fine; they're clones off the SparkFun Pro Micro or Arduino Pro Micro ATmega32u4 board.
They use the Arduino Leonardo bootloader, so within the Arduino environment, you treat them as Arduino Leonardos.  For the pinout,  but use the Arduino Pro Micro pinout.  In Arduino, make sure you have both the Keyboard and IRremote libraries installed, and the same code shown at the Teensy pages above should work.

(In theory, it should be possible to use an ATtiny85 DigiSpark clone for this, but I haven't actually tried this myself.  These bit-bang the USB connection (low-speed USB HID), calibrating the internal clock to 16.5 MHz.  The latest versions of the IRremote and DigiSpark cores do have an interrupt conflict as well, so it definitely isn't as straightforward.  This is why I recommend using an Arduino-programmable MCU with hardware USB support instead.)
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #3 on: December 30, 2022, 12:45:29 pm »
Made some progress with LIRC, got a FTDI based receiver working, but with the ancient version of LIRC v0.9.0 from ~6 years ago (LIRC from the Ubuntu 16.04 Xenial repositories, but installed in current Ubuntu 22.04 Jammy).  It was tedious, and still don't know how to add a random remote.  :horse:

FTDI FT232R, which is a USB to serial converter, can be used to receive IR with the help of an IR demodulator like TSOP1738.  The FTDI chips are using a special ftdi USB driver.  That allows FTDI chips to work with interrupts if needed, they have some parallel bits, too, apart from the standard RS232 signals, they can bitbang with timing and on any pin (this last mode I suppose is used by LIRC), etc.

I am using one, and the Out pin from TSOP is connected to the RxD pin of the FTDI (default in LIRC, the pin can be wired on other FTDI pins, too, then configured in LIRC accordingly).  Something like this:


Schematic from https://www.huitsing.nl/irftdi/

There is another hardware dongle with the TSOP Out pin wired to the RxD of an RS232 COM port (port with 16550 UART chip, not USB COM).  That is why CH340G (USB to TTL serial) might not work.

The Arduino based are implementing a small GIRS (General InfraRed Server) which can only work with very few remotes, and GIRS is yet another layer, and apart from lirc it will require some firmware to compile/maintain over years.

About 10 years ago, some guy Igor wrote a bitbang USB1.1 firmware for AVR chips, like ATmega or even ATtiny.  That firmware allows 2 casual GPIO pins of the MCU to become D+ and D- data lines for USB.  Many projects used Igor's AVRUSB to connect a casual AVR chip to a PC through USB and control MCU GPIO from PC, log MCU ADC data, etc.  It was a total surprise back than, first time when an MCU was able to connect to USB without FTDI or alike USB to TTL bridges.  I think this is the current page, and a link with some demo projs:
https://www.obdev.at/products/vusb/index.html
https://www.obdev.at/products/vusb/projects.html
Based on this AVRUSB firmware there is also an IR receiver suported by LIRC (Igor's plug USB LIRC).  Again, yet another FW to take care in time.



I would prefer LIRC because many media players like Rhythmbox, VLC, etc. have LIRC support.
Also happens to have an unused FT232RL and a TFM(S)1380, which are now wired together and could close Rhythmbox from some leftover IR remote from a former Leadtek WinFast FM+TVtuner.

There are many config settings to match:
- proper driver (here ftdi)
- proper lircd (daemon config)
- proper IR remote model config
- proper LIRC plugins config (specific to each media player that supports LIRC)
- proper scripts for each IR remote's button and each media player
- proper scripts for other normal application (not media players, LIRC can send events to X, sned keystrokes, emulate mouse move, etc. huge pile of features and config details)  :scared:



Now that the hardware+drivers+configs are known as working, next would be to try to redo the LIRC setup using the current LIRC from the Ubuntu 20.04 Jammy repositories, then to write down simple steps and commands to get LIRC working in Ubuntu with a FTDI receiver.
« Last Edit: December 30, 2022, 12:50:14 pm by RoGeorge »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: IR remote receiver for Linux
« Reply #4 on: December 30, 2022, 11:16:39 pm »
I would prefer LIRC because many media players like Rhythmbox, VLC, etc. have LIRC support.
All GUI players I've tested support multimedia keys out of the box.  It is because the Logitech et. al keyboards with such physical keys are quite common.
(Although, volume up and down is actually wired to the global volume control, not to the application volume.)

If this is a non-GUI installation, you do need a helper daemon to switch between applications, for example by switching between TTYs (with each appliance running on their own TTY, possibly on raw framebuffer/DRI).  If you use X, you can always use a keyboard mapping in X to eg. change applications (specific remote buttons switching to a specific application).

I really do not see why you're so invested in LIRC, when the HID approach is so much more common and actively used.
 
The following users thanked this post: RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #5 on: December 31, 2022, 07:52:43 am »
For why LIRC, at this point the sunk-cost fallacy alone would be a strong enough reason.  ;D

The goal is to have dedicated physical buttons for volume and play/pause when playing music, movies or YouTube.
    - Has to work on a Kubuntu desktop (with KDE Plasma)
    - Has to work headless, without pointing or clicking a GUI, and without having to bring the player into focus
    - Has to be an IR remote, a keyboard with media buttons is too big to carry in a nearby seat (for example when soldering)
    - Has to be maintenance free on long term, that's why I'm avoiding brewing my own software
    - No buying, already have all the parts (USB IR receiver with FT232R and TSOP1738, plenty of IR remotes left from long gone appliances)

Could install something else other than LIRC, just that when searching for alternatives couldn't find any.

The pitch from the LIRC devs is this:
Quote
Why should I use LIRC?

Recent Linux kernels have built-in support for IR remotes. Using that, pressing an up-arrow on the remote works the same way as pressing the up-arrow on a keyboard. This is a modern "just works" solution. On the other hand, LIRC is an old style linux application which can be tweaked to do almost anything, but is tricky to setup. So, why would you use LIRC?

    You might have a remote which is supported by LIRC but not the kernel.
    If you have a remote which isn't supported at all, LIRC is probably your best bet to get it running.
    You might be on a non-Linux platform supporting lirc e. g., MacOS.
    You might have an application which is more or less designed to use LIRC.
    You might need LIRC's capabilities e. g., modes where a single remote button can be teached to deliver different keys to the application. Handling input to multiple program is also easier with lirc
    You might want to send IR signals to other devices (IR blasting).
    You might want to use lirc's applications e. g., irexec(1) which can run arbitrary commands in parallel with an application such as mythtv or kodi.

So, while the kernel built-in handling works out of the box in many cases, there are still scenarios when LIRC is the right tool.
Source:  https://www.lirc.org/html/configuration-guide.html
« Last Edit: December 31, 2022, 08:14:05 am by RoGeorge »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: IR remote receiver for Linux
« Reply #6 on: December 31, 2022, 10:18:10 am »
For why LIRC, at this point the sunk-cost fallacy alone would be a strong enough reason.  ;D
Ok, sure.  I'm just telling you that that approach has waned to basically nothing, because the Linux input subsystem provides the same functionality in an easier to manage form, and is always built-in in Linux.

The userspace API is described here, although the binary interface is much stabler than the linked-to library there.  It is a trivial interface to use from C, if you need more precise control than using multimedia keys on an attached keyboard would be.

To repeat, the small USB microcontroller would receive the IR codes from the remote, and map them to USB HID multimedia keys or longer macros.  These are provided to the currently active terminal (typically X) just like normally attached keyboard keypresses are.

If you need additional control, for example map the keypresses to specific events to specific applications running under X, you can use a simple X application that grabs that particular input event device, consumes the events (so they're no longer forwarded to X), and sends the X events to the target applications as needed.  You can write this in C, C++, or even Python.  The only "trick" is to add an udev rule to allow the user this application is run as full access to the input event device, and create a symlink so that it does not need to look the event device name up.

The pitch from the LIRC devs is this:
You do realize that web page was last modified on 2017-06-09?

I am basically saying that LIRC is rarely used anymore, because everyone just uses the Linux Input Subsystem instead.  Even when one needs to translate the keypresses to X events and direct them to specific applications, the code is easier than dealing with LIRC, and utterly stable: your only dependency in C is the X11 library (libX11).

If you want, I can write you a simple C program that maps serial port inputs into Linux input events, using the uinput device.  (Linux allows one to programmatically create USB HID devices that behave exactly like the physical device would.)  I'm just saying that with a MCU with a native USB interface, like on ATmega32u4, you don't need such daemons or drivers at all, just program it to support whatever IR remotes you like, and off you go.

Also, once you go this route, and find out how easy it is to make all sorts of USB-connected widgets using MCUs with native USB support, you'll never go back to USB-serial bridges and the like.  That's what happened to me, you see.



A local store does have TSOP1738 in stock (for under 2€ apiece), but I do not currently have any IR remotes at all.  Otherwise, I could have posted an actual example of how to do this.

Would it help if I create an uinput example program?  For example, one that reads single-character "commands" from a serial port, and injects them into the Linux input subsystem just like a hardware USB HID device would?  This would allow you to use your current hardware without buying anything, but leverage the input subsystem instead of relying on LIRC.

Note that both the serial and Linux input subsystems are part of the stable linux ABI: such C code written today will run in the future, because this part has not changed in well over a decade at all, and is something Torvalds et. al. insists should not be broken unless absolutely necessary. (There are some silly people, including a couple of kernel devs, who do push libevdev, which I recommend one does not use.  The same ones pushed the interface from read()/write() to ioctl()s, and really, the changes should be reverted...)

You don't even need to use any external libraries, and can compile the binary statically, if you want.
Even though this is "homebrew" code, it is stabler and much easier to maintain than anything you'll use that uses libraries or services, because it only interfaces to stable kernel interfaces.
 
The following users thanked this post: DiTBho

Offline Karel

  • Super Contributor
  • ***
  • Posts: 2214
  • Country: 00
Re: IR remote receiver for Linux
« Reply #7 on: December 31, 2022, 12:34:32 pm »
I'm using a TSOP38238 connected to a gpio pin of an RPI3b with Libreelec (Kodi) and it's working fine for years.
 
The following users thanked this post: DiTBho

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #8 on: January 01, 2023, 11:37:35 am »
I've tried one more day to configure the current LIRC v0.10.1 (the one from the Ubuntu 22.04/Jammy repositories) and couldn't make it run :horse:, so I've purged it all then installed/configured again the old LIRC v0.9.0 (from Ubuntu 16.04/Xenial).



Meanwhile managed to add a new/unknown IR remote (in LIRC v0.9.0), then made the general volume to respond to this:


That's a strange IR remote apparently never seen before ;D
https://www.eevblog.com/forum/repair/what-is-this-remote-for-(ir-big-round-blue-with-4-big-buttons)/msg4603183/#msg4603183

Not only the shape is unusual at that remote, its IR pulses looks different than those from other IR remotes.  The IC near it is only a banana for scale.



That's how I've installed and configured LIRC v0.9.0 tin Kubuntu 22.04/Jammy to work with a FTDI IR receiver like the one from the schematic posted a couple of messages above:
Code: [Select]
        # add xenial repo to the sources.list (temporary, will delete it after install)
        echo 'deb [trusted=yes] http://ca.archive.ubuntu.com/ubuntu/ xenial universe' | sudo tee -a /etc/apt/sources.list > /dev/null
        sudo apt update
       
        # install good old LIRC and lock it against further upgrades
        sudo apt install lirc/xenial
        sudo apt-mark hold lirc

        # at install time, a configure text wizard should appear, if that didn't happen do
        sudo dpkg-reconfigure lirc
        # choose the FTDI driver, that will create proper config files in /etc/lirc/

        # remove the temporary added xenial repo from /etc/apt/sources.list
        sudo truncate -s -"$(tail -n1 /etc/apt/sources.list | wc -c)" /etc/apt/sources.list
        sudo apt update


        # to configure lirc
        # download a config file for your IR remote (mine is Leadtek from a former WinFast tuner)
        # download the remote from [url]https://sourceforge.net/p/lirc-remotes/code/ci/master/tree/remotes/leadtek/Y04G0033.lircd.conf[/url]
        wget [url]https://sourceforge.net/p/lirc-remotes/code/ci/master/tree/remotes/leadtek/Y04G0033.lircd.conf?format=raw[/url]

        # add the downloaded remote as remote(s) config file       
        sudo mv /etc/lirc/lircd.conf /etc/lirc/lircd.conf.original
        sudo cp ./Y04G0033.lircd.conf /etc/lirc/lircd.conf
       
        # restart the lirc service (or reboot the PC) for changes to take effect
        sudo /etc/init.d/lirc restart

        # if all OK, irw should tell the received button pressed in the IR remote
        irw
00000000c03f30cf 00 KEY_UP Leadtek_Cool_Command_Y04G0033
00000000c03f08f7 00 KEY_DOWN Leadtek_Cool_Command_Y04G0033
00000000c03f08f7 01 KEY_DOWN Leadtek_Cool_Command_Y04G0033
00000000c03fc837 00 KEY_ENTER Leadtek_Cool_Command_Y04G0033
00000000c03f6a95 00 KEY_TV Leadtek_Cool_Command_Y04G0033
00000000c03fea15 00 KEY_RADIO Leadtek_Cool_Command_Y04G0033
00000000c03fea15 01 KEY_RADIO Leadtek_Cool_Command_Y04G0033
00000000c03f1ae5 00 KEY_DVD Leadtek_Cool_Command_Y04G0033
00000000c03f1ae5 01 KEY_DVD Leadtek_Cool_Command_Y04G0033

To make the Pulse Audio general volume to respond to the IR remote buttons
- create ~/.lircrc text file to map remote buttons to programs
- install the Pulse Audio LIRC plugin
- add a line in the Pulse Audio config so the plugin will start each time
- restart

Content of '~/.lircrc' file
Code: [Select]
# note KEY_LEFT denotes a button from the IR remote, not the arrow key button from the PC keyboard
begin
   remote = Leadtek_Cool_Command_Y04G0033
   prog = pulseaudio
   config = volume-down
   button = KEY_LEFT
   repeat = 0
end
 
begin
   remote = Leadtek_Cool_Command_Y04G0033
   prog = pulseaudio
   config = volume-up
   button = KEY_RIGHT
   repeat = 0
end
 
begin
   remote = Leadtek_Cool_Command_Y04G0033
   prog = pulseaudio
   config = mute-toggle
   button = KEY_DOWN
end

# this last one is to toggle pause/play in Rhythmbox
#   (you must also go to settings in the Rhythmbox GUI and enable its LIRC plugin)
begin
        prog = Rhythmbox
        button = KEY_ENTER
        config = playpause
end

The line to add in PulseAudio config file '/etc/pulse/default.pa'
Code: [Select]
load-module module-lirc
# if you have multiple PulseAudio sinks, to specify which one will listen to IR buttons
# load-module module-lirc sink=[your sink name]

Now reboot the computer to make sure all the changes will take effect.

If all went OK and I didn't forget to write down any step, after reboot the general volume should respond to the IR remote buttons.  Open Rhythmbox and play some music.  Music should pause/play from the IR remote button.



To make LIRC "learn" a new/unknown IR remote, use 'irrecord'
Code: [Select]
irrecord -Hftdi some_new_remote
# then follow the text wizard
#    and at the end irrecord will produce a text file 'some_new_remote' in the local directory
#    open file 'some_new_remote' in a text editor and copy/add its content inside the file /etc/lirc/lircd.conf
#    save edited /etc/lirc/lircd.conf (you'll need admin rights)
#
#    open ~/.lircrc file and add inside new buttons from the new remote
#
#    then restart the lirc daemon or reboot the PC



In case anything goes wrong, read the the manual.  This is the architecture, then the signal path.  Might help when debugging:

       ----------         ---------------                     ----------
       |        |         | Linux input |                     |        |
       |        |---->----| layer       |---------->----------| Appli- |
       |        |         |             |  /dev/input/eventX  | cation |
       |        |         ---------------                     |        |
--->---|        |                |                            ----------
remote | kernel |       devinput v
       |        |                |
       |        |                |                            ----------
       |        |         ---------------                     | Appli- |
       |        |---->----|    lirc     |---------->----------| cation |--
       |        |         |             | /var/run/lirc/lircd |        | |
       ----------         ---------------                     ---------- |--
                                                                |        | |
                                                                ---------- |
                                                                  |        |
                                                                  ----------

-----------------------------------------------------------------------------

      ------------
      |  remote  |
      ------------

        (air gap)

      ------------
      ! capture  !
      ! device   !
      ------------
           |
           v
           |
      ------------
      ! kernel   !                   Sometimes needs
      ! driver   !                   modprobe(1) configuration.
      ------------
           |
           v  IR pulse data          Device like /dev/lirc0, /dev/ttyACM0.
           |                         or /dev/ttyS0.
      ------------
      |  lirc    |                   Configure lirc_options.conf
      |  driver  |                   with driver and usually also device.
      ------------
           |
           v  IR pulse data          Use mode2(1) to debug
           |
   ----------------
   |  lirc pass 1 |                  lircd.conf config file.
   ----------------
           |
           v  Key symbols            Output socket e. g.,
           |                         /var/run/lirc/lircd. Use irw(1) to debug.
           |
   ----------------
   |  lirc pass 2 |                  lircrc config file.
   ----------------
           |
           v  Application strings    Use ircat(1) to debug.
           |

      Applications


The above text diagrams and more info from https://www.lirc.org/html/configuration-guide.html


References:
[1] Make LIRC work in Ubuntu 18.04, 20.04, or 22.04
https://twosortoftechguys.wordpress.com/2018/07/24/make-lirc-work-in-ubuntu-18-04/
[2] PulseAudio and lirc [Update] https://possiblelossofprecision.net/?p=791
[3] Rhythmbox: LIRC plugin does not work plugin https://ubuntuforums.org/showthread.php?t=1066766&p=6728540#post6728540
[4] LIRC Manual https://www.lirc.org/html/index.html
« Last Edit: January 04, 2023, 11:23:23 pm by RoGeorge »
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #9 on: January 01, 2023, 12:29:16 pm »
I do not currently have any IR remotes at all.

But how?!?
I have 17 of them if I include the blue round one and a DIY one for Nikon, placed inside a lighter:


Picture from proj https://hackaday.io/project/1293-turn-a-cigarette-lighter-into-an-ir-nikon-remote




Would it help if I create an uinput example program?  For example, one that reads single-character "commands" from a serial port, and injects them into the Linux input subsystem just like a hardware USB HID device would?  This would allow you to use your current hardware without buying anything, but leverage the input subsystem instead of relying on LIRC.

An example would help a lot as a generic way to interface MCUs with a PC.
If that's not too much to ask, yes, thank you, an example would be great!  :-+



Maybe reading bytes from a serial port would not be applicable to receiving IR signals from my current hardware.  Saying this because I suspect the FT232R USB to serial chip (in LIRC) is in fact used as a 1 bit GPIO, and not as a serial port.  If a 'cat /dev/ttyUSB0' is issued then start pressing remote buttons, nothing is displayed.  I guess a normal COM will give a timing error, because the protocol of an IR remote is different from an RS232 serial.  Another MCU could translate the IR protocol, but that would mean to write MCU code and reflash the firmware in order to add a new remote.

Another way would be to poll the signaling pins of a COM port instead of Rx/Tx data pins.  That's how LIRC is reading a TSOP sensor attached to the RTS pin of a COM port, i.e. https://www.lirc.org/receivers.html  I'm not sure if casual USB to TTL serial adapters (non FTDI, like the ones based on CH340) have the RTS pin and can be polled with a precise enough timing to make sense of the IR signals.
« Last Edit: January 01, 2023, 12:33:50 pm by RoGeorge »
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #10 on: January 03, 2023, 06:49:34 am »
Quick note (more settings, specific for LIRC v0.9.0 for Ubuntu16.04/Xenial when installed into current Ubuntu 20.04/Jammy):

Settings needed to execute a shell command at the press of an IR remote button
(with an example of how to control a program that is not aware of LIRC, here the 'mpv' player)

'irexec' will listen to LIRC for pressed IR buttons, and will lookup inside '~./lircrc' file for IR buttons to shell command associations.  'irexec' can be started as a command from a terminal, as a daemon (irexec -d) or as a service with systemd.  This last method is preferred, and can be set to start at each boot.
Code: [Select]
- to send shell commands from the IR remote, start irexec
    https://linuxaudiofoundation.org/category/irexec/
    to start irexec automatically at each boot
    create a unit file for systemd, '/lib/systemd/system/irexec.service',
    so systemd will start irexec at boot, as a service
   
        # sudo nano /lib/systemd/system/irexec.service
            [Unit]
            Documentation=man:irexec(1)
            Documentation=http://lirc.org/html/configure.html
            Documentation=http://lirc.org/html/configure.html#lircrc_format
            Description=Handle events from IR remotes decoded by lircd(8)
            After=network.target
        # note the 'Wants=' for lirc 0.9.0 is called 'lirc', while in 0.10.1 'lircd'
            Wants=lirc.service

            [Service]
            Type=simple
        # when not specified, the default lircrc is expected to be in /home/<your_username>/.lircrc
        #    use hardcoded path to avoid variable substitutions and shell expansion uncertainty
        #    ExecStart=/usr/bin/irexec /etc/lirc/irexec.lircrc
            ExecStart=/usr/bin/irexec /home/<your_username>/.lircrc
            ExecStart=/usr/bin/irexec
            Restart=on-failure
            SuccessExitStatus=3 4
            RestartForceExitStatus=3 4

            [Install]
            WantedBy=multi-user.target
           
- to enable loading irexec at boot

        sudo systemctl daemon-reload
        sudo systemctl enable irexec
       
        reboot   

The mapping of IR buttons to shell commands to be executed is specified inside the '/home/<your_username>/.lircrc' file, i.e.
Code: [Select]
- when 'irexec' is running (either from a terminal, as a daemon or as a service)
    'irexec' will listen to LIRC and will emit
    the commands specified inside the LIRC button mapings file, '~/.lircrc', e.g.
   
        begin
            remote = EBODA_DVD_black
            prog   = irexec
            button = KEY_PAUSE
        # use hardcoded path, system variables mught not work
        #   config = echo cycle pause | socat - "$HOME/.config/mpv/socket"
            config = echo cycle pause | socat - "/home/<your_username>/.config/mpv/socket"
        end
       
For this to work, the mpv player needs to be instructed to create that socket and listen to it (by adding the following inside file '~/.config/mpv/mpv.conf' then restart mpv)
Code: [Select]
   
        # add the following line(s) inside file ~/.config/mpv/mpv.conf (create the file if it's missing)

        # for mpv to receive inputs from a command-line (while mpv is running)
        # (LIRC irexec daemon will use this socket to control mpv from an IR remote)
        input-ipc-server=/home/<your_username>/.config/mpv/socket

Any changes in the '.lircrc' file will only take effect after lirc and irexec services are restarted.  Any consumers of the events has to be restarted, too, for example the running player(s), pulseaudio, lircd, etc.  Something like this:
Code: [Select]
sudo systemctl restart lirc && sudo systemctl restart irexec && pulseaudio -kA reboot might be easier, to be sure no components to be restarted were forgotten.

To preserve the LIRC settings:
Code: [Select]
- customised files to backup for lirc v0.9.0
        /etc/lirc/hardware.conf
        /etc/lirc/lircd.conf
        /home/<your_username>/.lircrc
        /lib/systemd/system/irexec.service
        /home/<your_username>/.config/mpv/mpv.conf
        any other files you created with irrecord for unknown IR remotes
« Last Edit: January 04, 2023, 11:49:09 pm by RoGeorge »
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3796
  • Country: gb
Re: IR remote receiver for Linux
« Reply #11 on: January 03, 2023, 03:34:42 pm »
RoGeorge, why do you want to over-complicate simple stuff?  :o :o :o

The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3796
  • Country: gb
Re: IR remote receiver for Linux
« Reply #12 on: January 03, 2023, 04:09:12 pm »
If you want, I can write you a simple C program that maps serial port inputs into Linux input events, using the uinput device.  (Linux allows one to programmatically create USB HID devices that behave exactly like the physical device would.)  I'm just saying that with a MCU with a native USB interface, like on ATmega32u4, you don't need such daemons or drivers at all, just program it to support whatever IR remotes you like, and off you go.

Yes, please.

I have to add some IR remote capabilities to my training setup.

Nothing special, just using a small a Creative HiFi stick for two things
  • 1: controlling VLC { volume up, down, play, pause, stop, next song/video, ...)
  • 2: controlling the street load { more sports warm-up before performance, increase, decrease }
Your example will cover 2, but also 1

it's a kind of training machine, a computer-controlled mechanism attached to the rear wheel of a bicycle frame to simulate a real road load. The IR stick is attached to the bicycle handlebar, the remote computer is 5 meters in front of the frame, so it's somehow like pointing a TV screen from the sofa.

The weather condition is usually too bad for training outdoors, so… let's train indoors ;D
« Last Edit: January 03, 2023, 04:14:26 pm by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #13 on: January 03, 2023, 04:10:42 pm »
why do you want to over-complicate simple stuff?

What else to use then?

LIRC is working now, only laying down the settings.  For example, fiddled today with the controls for the mpv player.  MPV doesn't have a LIRC plugin, yet mpv knows to open a socket and listen to text commands from that socket.

LIRC has a tool 'irexec' that can run bash commands.  Thus, with the help of 'socat' it is possible to echo commands to the mpv player while mpv is running.  To map buttons from the IR remote to various actions in mpv player, all LIRC needs is to insert the following into the '`/.lircrc' file:

IR remotes buttons mapping to MPV
Code: [Select]
##
# mpv IR keys binding (mpv has no LIRC plugin)
#   
# https://stackoverflow.com/questions/19536799/lirc-how-to-use-as-keyboard-command

# https://alexherbo2.github.io/config/mpv/control-mpv-from-the-command-line/
#   echo cycle pause | socat - "$XDG_CONFIG_HOME/mpv/socket"
#      $XDG_CONFIG_HOME might not be defined, use "$HOME/.config" instead
#
#   # better use a hardcoded already expanded path
#   echo cycle pause | socat - "/home/aaa/.config/mpv/socket"
#   echo playlist-next | socat - "$XDG_CONFIG_HOME/mpv/socket"
#   echo playlist-prev | socat - "$XDG_CONFIG_HOME/mpv/socket"
#
#   !!! DO NOT USE SYSTEM VARIABLES or ~, use hardcoded paths instead !!!
#
#   mpv commands format
#   echo '{ "command": ["set_property", "pause", true] }' | socat - /tmp/mpvsocket
#   for example
#   {"command": ["cycle", "pause"]}
#
#   see also e.g. 'mpv --list-properties | grep fullscreen'
#                  mpv --list-options | less
#   # note this will stop the currently running mpv to respond to MPC (socat) commands, restart the mpv to recover
##

# mpv cycle pause       KEY_PAUSE
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_PAUSE
        config = echo cycle pause | socat - "home/aaa/.config/mpv/socket"
end

# mpv cycle pause       KEY_PLAY
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_PLAY
        config = echo cycle pause | socat - "home/aaa/.config/mpv/socket"
end

# mpv playlist-next     KEY_RIGHT
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_RIGHT
        config = echo playlist-next | socat - "home/aaa/.config/mpv/socket"
end

# mpv playlist-prev     KEY_LEFT
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_LEFT
        config = echo playlist-prev | socat - "home/aaa/.config/mpv/socket"
end

# mpv cycle fullscreen  KEY_ENTER
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_ENTER
        config = echo cycle fullscreen | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle fullscreen  KEY_ZOOM
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_ZOOM
        config = echo cycle fullscreen | socat - "/home/aaa/.config/mpv/socket"
end

# mpv show subtitles    KEY_SUBTITLE
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_SUBTITLE
        config = echo cycle sub-visibility | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle subtitles   KEY_OPEN_CLOSE
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_OPEN_CLOSE
        config = echo cycle sid | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle audio       KEY_LANGUAGE
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_LANGUAGE
        config = echo cycle aid | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle audio       KEY_PROGRAM
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_PROGRAM
        config = echo cycle vid | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle aspect ratio       KEY_ANGLE
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_ANGLE
        config = echo cycle aspect | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle osd time    KEY_OSD
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_OSD
        config = echo cycle osd-level | socat - "/home/aaa/.config/mpv/socket"
end

# mpv cycle osd time    KEY_TIME
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_TIME
        config = echo cycle osd-level | socat - "/home/aaa/.config/mpv/socket"
end

# mpv seek back -3s     KEY_REWIND
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_REWIND
        config = echo seek -3 | socat - "/home/aaa/.config/mpv/socket"
end

# mpv seek forward +1s  KEY_FORWARD
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_FORWARD
        config = echo seek +1 | socat - "/home/aaa/.config/mpv/socket"
end

# mpv play faster       KEY_NEXT
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_NEXT
        config = echo add speed +0.1 | socat - "/home/aaa/.config/mpv/socket"
end

# mpv play slower       KEY_PREVIOUS
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_PREVIOUS
        config = echo add speed -0.1 | socat - "/home/aaa/.config/mpv/socket"
end

# mpv volume up         KEY_VOL_PLUS
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_VOL_PLUS
        config = echo add volume +5 | socat - "/home/aaa/.config/mpv/socket"
end

# mpv volume down       KEY_VOL_MINUS
begin
        remote = EBODA_DVD_black
        prog   = irexec
        button = KEY_VOL_MINUS
        config = echo add volume -5 | socat - "/home/aaa/.config/mpv/socket"
end

 :D
« Last Edit: January 04, 2023, 11:57:26 pm by RoGeorge »
 
The following users thanked this post: DiTBho

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3796
  • Country: gb
Re: IR remote receiver for Linux
« Reply #14 on: January 03, 2023, 04:15:07 pm »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: IR remote receiver for Linux
« Reply #15 on: January 04, 2023, 07:25:57 am »
Here is a quick single-file example.  Note that it needs access to both the TTY and the /dev/uinput devices, so for testing purposes, I recommend running it as root.

Whenever it reads '+', it will generate a Volume Up keypress; whenever it reads '-', it will generate a Volume Down keypress.

Specify a serial port or a TTY device, and the vendor:product/name for the synthetic keyboard device, on the command line.  For example, if you run
    sudo ./binary $(tty) 1234:5678/Example
then you can press + and - on the terminal to change the volume.

While the daemon is running, you can observe it in the input event device list.  For example, ls -laF /dev/input/ and sudo evtest .

Type
    sudo killall -TERM binary
to exit the daemon.  (The daemon sets the serial port or TTY to raw mode, so when reading from the same terminal, Ctrl+C no longer generates an INT signal; that's why you cannot exit it by simply pressing Ctrl+C when running the above command.  If you run it in a terminal using some other tty or serial port, then Ctrl+C will work just fine.)

Code: [Select]
// SPDX-License-Identifier: CC0-1.0
//
// Compile using for example
//     gcc -Wall -O2 thisfile.c -o binary

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <linux/uinput.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>

// Signal handling for graceful restart

static volatile sig_atomic_t  restart = 0;

void handle_restart(int signum)
{
    (void)signum;
    restart = 1;
}

int install_restart(int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);

    act.sa_handler = handle_restart;
    act.sa_flags = 0;  // No SA_RESTART flag!

    return sigaction(signum, &act, NULL);
}

// Signal handling for graceful exit

static volatile sig_atomic_t  done = 0;

void handle_done(int signum)
{
    (void)signum;
    done = 1;
}

int install_done(int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);

    act.sa_handler = handle_done;
    act.sa_flags = 0;  // No SA_RESTART flag!

    return sigaction(signum, &act, NULL);
}

// Helper function, since signal delivery can cause an EINTR "error".  Async-signal safe, too.
int writeall(int fd, const void *buf, size_t len)
{
    const int                  saved_errno = errno;
    int                        err = 0;
    const unsigned char *const end = (const unsigned char *)buf + len;
    const unsigned char       *src = (const unsigned char *)buf;

    while (src < end) {
        ssize_t  n = write(fd, src, (size_t)(end - src));
        if (n > 0) {
            src += n;
        } else
        if (n != -1) {
            err = EIO;
            break;
        } else
        if (errno != EINTR) {
            err = errno;
            break;
        }
    }

    errno = saved_errno;
    return err;
}

// Emit a keypress and release

void keypress(int fd, int code)
{
    struct input_event  ev[2];

    // Timestamp is ignored
    ev[0].time.tv_sec = 0;
    ev[0].time.tv_usec = 0;
    ev[1].time = ev[0].time;

    // Keypress
    ev[0].type = EV_KEY;
    ev[0].code = code;
    ev[0].value = 1;  // Keypress

    ev[1].type = EV_SYN;
    ev[1].code = SYN_REPORT;
    ev[1].value = 0;

    writeall(fd, ev, sizeof ev);

    // Key release
    ev[0].value = 0;  // Release

    writeall(fd, ev, sizeof ev);
}

int parse_usbid(const char *src, struct uinput_setup *to)
{
    unsigned long  val;
    const char    *end;

    if (!src || !*src || !to)
        return -1;

    memset(to, 0, sizeof *to);
    to->id.bustype = 0;
    to->id.vendor = 0;
    to->id.product = 0;
    to->id.version = 0;

    // Vendor
    end = src;
    errno = 0;
    val = strtoul(src, (char **)&end, 16);
    if (errno || end == src || *end != ':' || val > 0xFFFF)
        return -1;
    to->id.vendor = val;

    // Product
    src = ++end;
    errno = 0;
    val = strtoul(src, (char **)&end, 16);
    if (errno || end == src || val > 0xFFFF)
        return -1;
    to->id.product = val;

    // Version (optional)
    if (*end == ':') {
        src = ++end;
        errno = 0;
        val = strtoul(src, (char **)&end, 0);
        if (errno || end == src || val > 0xFFFF)
            return -1;
        to->id.version = val;
    }

    // Name (optional)
    if (*end == '/') {
        src = ++end;
        end = src + strlen(src);
        if ((size_t)(end - src) >= UINPUT_MAX_NAME_SIZE)
            return -1;
        strncpy(to->name, src, UINPUT_MAX_NAME_SIZE - 1);
    }

    if (*end)
        return -1;

    return 0;
}

int main(int argc, char *argv[])
{
    const char         *arg0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
    struct uinput_setup setup;
    struct termios      oldsettings, settings;
    int                 uinputfd, devfd = -1;

    // Check command-line arguments
    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", arg0);
        fprintf(stderr, "       %s DEVICE-PATH VENDOR:PRODUCT[:VERSION][/NAME]\n", arg0);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }
    if (parse_usbid(argv[2], &setup)) {
        fprintf(stderr, "%s: Invalid synthetic USB HID device identifier.\n", argv[2]);
        return EXIT_FAILURE;
    }

    // Install signal handlers
    if (install_done(SIGINT) ||
        install_done(SIGTERM) ||
        install_restart(SIGHUP) ||
        install_restart(SIGUSR1)) {
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // Construct new input event device
    uinputfd = open("/dev/uinput", O_RDWR | O_CLOEXEC);
    if (uinputfd == -1) {
        fprintf(stderr, "/dev/uinput: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }
    // List event types and events this can produce
    ioctl(uinputfd, UI_SET_EVBIT, EV_KEY);
    ioctl(uinputfd, UI_SET_KEYBIT, KEY_VOLUMEDOWN);
    ioctl(uinputfd, UI_SET_KEYBIT, KEY_VOLUMEUP);
    // Create the new synthetic input device
    ioctl(uinputfd, UI_DEV_SETUP, &setup);
    ioctl(uinputfd, UI_DEV_CREATE);

    while (1) {

        // Close already open serial port
        if (devfd != -1) {
            tcflush(devfd, TCIOFLUSH);
            tcsetattr(devfd, TCSANOW, &oldsettings);
            close(devfd);
        }
        if (done)
            break;

        // Open serial port or TTY device,
        devfd = open(argv[1], O_RDWR | O_CLOEXEC);
        if (devfd == -1) {
            fprintf(stderr, "%s: Cannot open: %s.\n", argv[1], strerror(errno));
            close(uinputfd);
            return EXIT_FAILURE;
        }
        // obtain its current configuration,
        if (tcgetattr(devfd, &oldsettings) == -1) {
            fprintf(stderr, "%s: Not a TTY or serial port: %s.\n", argv[1], strerror(errno));
            close(devfd);
            close(uinputfd);
            return EXIT_FAILURE;
        }
        // and configure it.
        settings = oldsettings;
        settings.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
        settings.c_oflag &= ~(OPOST | OCRNL | ONOCR | ONLRET);
        settings.c_cflag &= ~(CSIZE | PARENB);
        settings.c_cflag |= CS8 | CREAD;
        settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | IEXTEN);
        settings.c_cc[VMIN] = 1;
        settings.c_cc[VTIME] = 0;
        if (tcsetattr(devfd, TCSANOW, &settings) == -1) {
            fprintf(stderr, "%s: Cannot configure: %s.\n", argv[1], strerror(errno));
            tcflush(devfd, TCIOFLUSH);
            tcsetattr(devfd, TCSANOW, &oldsettings);
            close(devfd);
            close(uinputfd);
            return EXIT_FAILURE;
        }

        // We have now restarted.
        restart = 0;

        while (!done && !restart) {
            unsigned char   buf[512];
            ssize_t         n = read(devfd, buf, sizeof buf);
            if (n > 0) {
                unsigned char *const end = buf + n;
                unsigned char       *src = buf;
                while (src < end) {
                    switch (*(src++)) {

                    case '+':
                        keypress(uinputfd, KEY_VOLUMEUP);
                        break;

                    case '-':
                        keypress(uinputfd, KEY_VOLUMEDOWN);
                        break;

                    }
                }
            } else
            if (n != -1) {
                // End of input from serial device; reopen.
                break;
            } else
            if (errno != EINTR) {
                // Other error from serial device; reopen.
                break;
            }
        }
    }

    close(uinputfd);
    return EXIT_SUCCESS;
}

Majority of the code is signal handling (TERM and INT will gracefully exit, HUP and USR1 causes it to reopen the serial device), and serial device handling.

Note that because all signal handlers have been installed without the SA_RESTART flag, signal delivery to the userspace handler will interrupt any blocking I/O calls (read() and write() in particular) with errno==EINTR.  The way the inner loop is written, this program mostly sits quietly waiting (blocking) on a read() from the serial port, with a signal delivery interrupting that.  However, the signal could also arrive at a later time; particularly during writeall() (writing events to the uinput device), and we want to complete those.

This does have a short race window wrt. signals and the blocking read: a signal could be delivered after the flag has been checked but just before the blocking read().  We could avoid that by having the signal handler call tcsetattr() to make the serial device non-blocking (by setting .c_cc[VMIN]=0, .c_cc[VTIME]=0, tcsetattr() being async-signal safe), but generally, standard POSIX signals are not queued and do not have any guarantees, so such short race windows are generally considered acceptable.  (Correspondingly, service management daemons are expected to send at least two TERM signals with a delay in between to tell a service to exit, before KILLing the service forcefully.  The only downside of killing this program abruptly is that the serial port termios settings are not restored to originals.)

The uinput stuff is quite trivial:
  • Open /dev/uinput (write-only suffices, I recommend close-on-exec so you don't accidentally leak the open descriptor)
  • For each event type it can produce, call ioctl(fd, UI_SET_EVBIT, EV_type);
  • For each event it can produce, call ioctl(fd, UI_SET_KEYBIT, type_event);
  • Construct a struct uinput_setup, with struct input_id as the id member defining the bus (BUS_USB), vendor:product, optional version, and the name member defining the device name (as if detected internally by the linux kernel) –– the name is NOT the device node name, but describes the device as if obtained from the USB descriptor or parsed by the Linux kernel.
    Then, call ioctl(fd, UI_DEV_SETUP, &setup);
  • Create the device node using ioctl(fd, UI_DEV_CREATE);
    Note that this is asynchronous: the device node may not exist yet when the call returns, because the udev daemon takes a fraction of a second to create the device node, assign its properties, and so on.  You can apply udev rules to this synthetic device exactly like it was a physical USB HID device.
  • From this point forwards, you emit USB HID events by writing one or more struct input_event structures to fd (the uinput device descriptor above).
  • When you close fd (the uinput device descriptor), the synthetic device is disconnected and removed (by udev) automatically.
    If you generate multiple devices, you need to open uinput separately (dup() won't suffice) for each generated device.
It really is this simple.  The Linux Kernel uinput module documentation describes the interface in even more detail, as well as the Linux Input Subsystem userspace API; you'll also want to check the include/linux/input.h, include/linux/input-event-codes.h, and include/linux/uinput.h header files exported from the Linux kernel and available as <linux/input.h> (which auto-includes <linux/input-event-codes.h>) and <input/uinput.h> for userspace programs in C.

There are two ways of starting such daemons.  One is to have udev start and stop them whenever the corresponding device (serial port device) is connected.  The other is to have the daemon run always; when the device node does not exist, use inotify to wait for IN_CREATE, IN_ATTRIB, IN_MOVED_TO events in /dev/., attempting to open the device whenever such events occur for the entry with the same name.  When the device has been successfully opened, the inotify descriptor (and its watch) can be closed.  This consumes minimum resources, and very, very little CPU time, because such events in /dev/. occur only when new devices are attached.
Sure, you could use the udev API instead, and become dependent on systemd-udevd; I don't want to, as I want the same to work with for example eudevd, and therefore use the inotify way.



If you do an X-less appliance, with mpv or vlc running on raw accelerated framebuffer (DRI+VA_API or whatever), put each one on a separate console (/dev/ttyN).  For example, in most Ubuntu variants, X runs on /dev/tty7, and a login (via agetty) on /dev/tty1.  The chvt utility changes the foreground terminal, very similar to how LeftAlt+FN or Ctrl+LeftAlt+FN changes to /dev/ttyN.
You can then have all of the appliance applications running at the same time, and switch between them: both physical USB HID and uinput keypresses above will switch the foreground terminal.

If you use X and fullscreen applications, use a minimal window manager with multiple desktops, and start each application on a separate desktop.  Depending on the window manager, there will be some keypresses to switch to a specific desktop.  So, for multimedia controls, if you want to target a specific fullscreen X application, send first the desktop key shortcut, wait for the X server to start to act on the event (50ms should be plenty), and then send the multimedia keypress.

Whatever way you generate events, and regardless of whether they are X or Linux Input subsystem events, do remember that each keystroke is actually a pair of events: a keypress, and a key release.  For the Linux Input subsystem events, events are grouped, with ALWAYS a EV_SYN, SYN_REPORT as the last event in a group.  So, to generate say Shift+F1, you generate KEY_LEFTSHIFT:1, SYN_REPORT, KEY_F1:1, SYN_REPORT, KEY_F1:0, KEY_LEFTSHIFT:0, SYN_REPORT sequence.  (For keypresses, there are three .values: 0=release, 1=press, 2=autorepeat.  The Linux kernel and X servers will handle autorepeat for you, though.)
« Last Edit: January 04, 2023, 07:33:47 am by Nominal Animal »
 
The following users thanked this post: RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #16 on: January 04, 2023, 09:41:20 am »
Wow, thank you!  :-+

It compiles (Kubuntu 20.04/Jammy).  Now I have so many questions...  ::)
Will first search online for the trivial ones, then ask here what still couldn't figure.

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: IR remote receiver for Linux
« Reply #17 on: January 04, 2023, 10:32:53 am »
Now I have so many questions...  ::)
Please, ask away!

Full disclosure: for the last ten years or so, I've been quite a proponent for using the Linux Input subsystem.  In fact, ever since I got my first Teensy 2.0++ (AT90USB1286), and made my first microcontroller project, a Linux keyboard arcade board (Arcade cabinet digital joystick and microcontroller buttons on a sturdy 50cm by 20cm baltic birch stick-laminated board, to play online Flash puzzle and platform games in a browser window, I've loved USB Human Interface Device specification.  It Just Works.

The Linux Input subsystem is modelled somewhat after the USB HID specification, but so that the same subsystem can support all known non-USB human interface devices as well.  Vojtek Pavlik, the primary developer (working for SuSE then), really did a good job here.  The subsystem is over twenty years old right now, and it still works perfectly well for new devices, with full backwards compatibility with code written two decades ago.

For us developers, all we need to know is that each human interface device generates an input event device (/sys/class/input/eventN symlinks), and udev creates the symlinks and device nodes in /dev/input/.  Linux consoles (/dev/ttyN) and X by default read from all keyboard and mouse-like devices, but one can also use the EVIOCGRAB ioctl() to "grab" the device so that the events it generates are only provided to the grabbing process, and no others.  There are a few ioctls to control e.g. keyboard repeat rates and such, but mostly we just read() struct input_event structures from the device stream.  (Each read will return one or more structures, but only a full number of structures, never partial structures.)
The /dev/uinput interface can be used as I showed earlier to synthesize any such device, under software control.
We do need to be aware of the security implications, however: anyone able to inject keystrokes while somebody else is logged in using the local keyboard, can inject commands as well.  Similarly, an application that can read input event devices, can monitor keystrokes and mouse movement.

When using microcontrollers with native USB, and creating an USB HID widget, the USB HID Usage Tables are indispensable, especially the Keyboard/Keypad page, because it includes both the codes related to each keypress (which all happen to fit in one byte, and each keyboard USB event listing up to six simultaneously pressed keys, plus Shift/Ctrl/Alt/etc. key states; but also when it is supported (which kind of keyboard should the USB HID device export itself as).

I can warmly recommend any Teensy boards (Teensy LC in particular for their low price), as well as all Pro Micro clones based on ATmega32u4 (using the Arduino Leonardo bootloader) for HID device development in the Arduino environment.  With Teensys, after you select one of the Teensy boards (Tools > Board menu), the Teensyduino add-on adds USB Type: to your Tools menu, so you can trivially do e.g. a Serial+Keyboard+Mouse+Joystic HID device (with Serial used for e.g. debug output), stick Keyboard.begin() in setup(), and you can do e.g. Keyboard.press(KEY_MEDIA_VOLUME_INC);Keyboard.release(KEY_MEDIA_VOLUME_INC); in loop() to generate a keystroke event pair (press and release).  Literally just a few lines of code to make an USB keyboard!  And it is a true keyboard/mouse/joystick device, too: no special drivers are needed in any OS.  Just plug it in, wait for a second for USB enumeration etc., and it works.
(Also, in Teensyduino, if (Serial) Serial.println("Stuff"); will only format and send the string when an application has the USB serial port (tty) open.  When no application has the serial port open, (Serial) evaluates to false.)

EDIT: As of 2023-01-04, exp-tech.de claims to have Teensy LC's in stock; a bit higher price than what they normally go for, though (15€ + VAT + shipping).  For anyone interested in experimenting with USB HID devices, I can warmly recommend it; the board itself is okay, but the Teensyduino support in the Arduino environment for such development is superb.
« Last Edit: January 04, 2023, 10:43:36 am by Nominal Animal »
 
The following users thanked this post: RoGeorge

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3796
  • Country: gb
Re: IR remote receiver for Linux
« Reply #18 on: January 04, 2023, 11:38:35 am »
We do need to be aware of the security implications

about security, ... many years ago, before Arduino was invented, someone put an MPU inside a ps/2 keyboard in a university computer room, MPU able to look at what you are typing and log everything follow the words { "root", "admin", "toor", ... }

someone just typed "root", oh see, the next word will probably be the password, let's log into a EEprom  :o :o :o

I was there at the time (2001), when the head of the computer room found the hw Trojan horse. I remember he laughed out loud and made a joke to make fun of the shocking discovery. He said that paranoia is never enough, so to be more paranoid you should always carry your personal keyboard with you, always unplug it and take it with you to the bathroom, to the bar, NEVER leave the keyboard alone, never type on other keyboards and always put your personal keyboard in the safe when you're not using it.

Even better, periodically disassemble and inspect, just in case ...  ;D


p.s.
thanks for the code!
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Karel

  • Super Contributor
  • ***
  • Posts: 2214
  • Country: 00
Re: IR remote receiver for Linux
« Reply #19 on: January 04, 2023, 12:23:43 pm »
There'll be no security at all if people have physical access. Period.
 
The following users thanked this post: DiTBho

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12807
Re: IR remote receiver for Linux
« Reply #20 on: January 04, 2023, 02:43:56 pm »
Physical access == Game Over, you have been pwn3d

Anyone sufficiently determined and skilled could acquire an identical keyboard to that of the target machine, and design a new controller board for it, which could appear identical to the genuine one, but with the USB MCU of their choice, black-topped and remarked to look genuine.  As long as the package is the same, the differences in pinout could be handled with a multi-layer via in pad  PCB, or if its got a cheap and nasty COB, you don't even have to do that - just goop potting compound over a slim QFN and do your routing within the blob perimeter.   Probably the biggest difficulty would be matching the appearance of the typical single sided phenolic keyboard controller PCB!

Then its simply a matter of swapping the keyboard controller PCB, so asset tracking, and serial no. stickers are unchanged, and it isn't suspiciously cleaner than before.   If necessary swap the keyboards, take the original to the dunny to install the trojan board, then swap them back.  The trojan keyboard would be able to pass any scrutiny short of inspection of its PCB under a microscope or by XRAY.   

The trojan can log to FLASH or internal EEPROM  -  if it logs lets say the first 32 keystrokes after a significant period of inactivity, it will get most people's passwords, and spit them back out when a highly obscure sequence of key combinations is pressed, either as lightly encrypted  text into any convenient text area, or over a one way optical link using one of the keyboard LEDs. 

The worrying thing is that fancier keyboards may not even need this effort - if the controller MCU is reflashable over USB without needing pins jumpered on its PCB, installing the trojan may be as simple as briefly plugging it into a malicious personal device with an OTG port and a DFU flasher utility!
« Last Edit: January 10, 2023, 01:54:16 pm by Ian.M »
 
The following users thanked this post: DiTBho

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #21 on: January 07, 2023, 11:57:44 am »
ask

Most of the questions are about something else, related with but not exactly about the example code.  I don't have yet a clean view of how the signals travel in general.  There are too many new things at once (for me).  Was expecting any standards to be more generic, but the kernel org docs show some very narrow standardisation.  I was expecting something like the RFC specs for networking, yet the kernel input system refers to very specific items like i.e. joystick or a multi-touch.

Even more confusing, there is LIRC documentation in the kernel.org
https://www.kernel.org/doc/html/latest/userspace-api/media/rc/lirc-dev.html
https://www.kernel.org/doc/html/latest/userspace-api/media/rc/remote_controllers.html
https://www.kernel.org/doc/html/latest/userspace-api/media/

Only found that this morning, will have to read the LIRC kernel docs, too.

For now, the biggest inconvenient in using the Linux kernel media keys is that the controls (play/pause) are sent to the active window.  Often the player is in the background or minimized, and in the foreground is some other window open and active.

How do I map the media keys (for example the play/pause) to always go to the media player?
Is there any Linux mechanism to tell which player was last playing (in case there are more players open and paused)?

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: IR remote receiver for Linux
« Reply #22 on: January 07, 2023, 01:17:38 pm »
I don't have yet a clean view of how the signals travel in general.
The logic is rather simple: all human input device drivers map their inputs to the Linux Input subsystem.  Kernel-internal stuff, like SysRq, console switching, and so on is on top of that.  The userspace input event devices expose the features.  X11 reads (but does not grab) by default all available devices (which is why you can just plug in a second or third keyboard and mouse, and they'll all work the same by default), and directs the events to the currently focused application.

I was expecting something like the RFC specs for networking, yet the kernel input system refers to very specific items like i.e. joystick or a multi-touch.
That's because that is exactly how the USB Human Interface Device specification does it.

For now, the biggest inconvenient in using the Linux kernel media keys is that the controls (play/pause) are sent to the active window.  Often the player is in the background or minimized, and in the foreground is some other window open and active.

How do I map the media keys (for example the play/pause) to always go to the media player?
Like I said earlier, I'd put each player on their own virtual desktop (AKA workspace), maximized.
Use your Keyboard Shortcuts to assign a hotkey to select a specific desktop, which automatically focuses the (only) application on that desktop.

Is there any Linux mechanism to tell which player was last playing (in case there are more players open and paused)?
Depends on the window manager.  Cinnamon (fork from Gnome 2) even has a toolbar widget for that.

In general, you can use wmctrl utility to control windows (size, fullscreen, modality, stacking order), focus (wmctrl -a window-spec), and stuff like list desktops/workspaces (wmctrl -d) and applications (wmctrl -l).  Using Keyboard Shortcuts, you can map any keypress to a script (to be executed), and that script can use wmctrl to manipulate the windows.  (xprop and xwininfo utilities are very useful when writing such scripts.)
 
The following users thanked this post: RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #23 on: January 07, 2023, 01:40:51 pm »
Makes more sense now, thank you.  Though, I still can't understand the mindset of having dedicated media buttons on a desktop keyboard, yet those buttons won't work if I click on another window than the player. 

I have a commercial A4Tech WiFi combo, keyb+mouse, where the keyboard has 10 extra keys, sideways, like this:


Photo from:  https://www.a4tech.com/product.aspx?id=160

One button is for Play/Pause.  It only works when the player window is in focus.  ???

Is my Plasma KDE set wrong?  Why making a Linux kernel specification in such a lousy way that a click into another window will render useless any input controller with physical media buttons on it, like this play/pause button on a PC keyboard?  :-//

Shouldn't such a standard specify to play/pause the last player in focus?
- specify in the standard to send the multimedia events to the last player that was in focus
- specify a switch current player mechanism (like the Alt+TAB for windows, but for players only), in case more players are already open
« Last Edit: January 07, 2023, 01:47:36 pm by RoGeorge »
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6145
  • Country: ro
Re: IR remote receiver for Linux
« Reply #24 on: January 07, 2023, 03:05:57 pm »
I'd put each player on their own virtual desktop (AKA workspace), maximized

That would work well with a dedicated media center setup, but I'm using a normal desktop.
My typical usage is this, from a workstation with Plasma KDE:
- start some media that doesn't need 100% attention (a podcast, a music playlist, a light movie, etc.)
- meanwhile open and work with other programs, while the media started earlier keeps running, either as audio only or in a 1/4 screen window (not in focus)
- at some point I want to pause the media, or to skip a song, and I want to do that without digging through all the open windows, and without pointing the mouse to specific icons
- often I need to control the player while sitting in a nearby chair and working at an electronics project

I expect a physical IR remote to interact with whatever it is playing now in the desktop, no mater the player.  This works as expected for system volume and system mute (because LIRC was set to control the pulseaudio mute/volume), but not for play/pause/next/previous.



Meanwhile looked for alternative ways to get the last active player.  Found this:
https://unix.stackexchange.com/questions/477823/use-dbus-to-get-name-of-mediaplayer-currently-playing

No idea if that's the right way.  There are two methods there, one by looking at the pulse audio stream names, the other by looking at dbus.  There seems to be a D-Bus standard, MPRIS (Media Player Remote Interfacing Specification).  MPRIS looks very close to what I need, just that it doesn't work with any random media player.  Arch wiki has a list of known working players https://wiki.archlinux.org/title/MPRIS

MPV, which is my default video player, doesn't show in dbus (tried with qdbus | grep -i mpv  :-\)



Later edit:
There's an MPV plugin available in most linux repos, https://github.com/hoyon/mpv-mpris
Installed it with sudo apt install mpv-mpris, and the mpv player now shows up in qdbus | grep -i mpv:D
« Last Edit: January 07, 2023, 03:17:17 pm by RoGeorge »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf