Re: How to use /dev/lirc0 in VDR?

Marko Mäkelä <[email protected]> Sun, 24 Jul 2022 12:46:33 +0300
Newsgroups gmane.linux.vdr
Message-ID <Yt0U+VHekNeFeyLd@jyty>
Wed, Jul 20, 2022 at 12:34:19PM +0300, Marko Mäkelä wrote:
>Mon, Jul 18, 2022 at 09:36:55AM +0300, Marko Mäkelä wrote:
>>TL;DR: How could I connect VDR to the kernel-provided /dev/lirc0 
>>device?  Is there a dummy lircd implementation that would simply 
>>open /dev/lirc0 in LIRC_MODE_SCANCODE and relay its contents over a 
>>socket?
>
>I wrote a simple converter (attached) that is compatible with vdr 
>--lirc=/dev/shm/lirc. Alas, as far as I can tell, the socket interface 
>does not currently allow any "repeat" flag to be passed explicitly.  
>Instead, lirc.c will detect long keypresses by itself based on some 
>time stamps or timeouts, and then pass the parameter to 
>cRemote::Put().

I took a further step along this path and wrote an experimental patch 
that makes --lirc=/dev/lirc0 work (while completely breaking 
compatibility with lircd). The attached patch is only a proof of 
concept; a final version of this would have to be tied to a dedicated 
command line option.

I did not clean up the timer logic yet. The lirc_scancode events already 
contain monotonic timestamps; those could be used in order to reduce the 
number of system calls.

This patch assumes that the /dev/lirc0 interface is setting the 
LIRC_SCANCODE_FLAG_REPEAT for subsequent messages sent for a long key 
press, as in 
https://patchwork.linuxtv.org/project/linux-media/list/?series=8338
(and before that, only for some RCUs that send special "repeat" 
messages). The flag could be simulated in userspace by keeping track of 
the timestamps.

>The Linux kernel documentation gave me the impression that the 
>/dev/input/event interface is the modern way to handle any input.
>
>I wonder if a /dev/input/event interface could be implemented natively 
>in the VDR core. It would remove the need for both lircd and 
>vdr-plugin-remote in many typical installations.
>
>It seems that in any case, I'd better write a converter from 
>/dev/lirc0 to /dev/uinput, to have the key events generated in my way, 
>slightly differently from the kernel's built-in /dev/input/event 
>driver.

I experimented with a program that would convert /dev/lirc0 to 
/dev/uinput, but it quickly got too complex for my taste, starting from 
the fact that all potential keycodes have to be declared upfront. While 
searching for information, I learned that lircd could already generate 
input events: https://www.lirc.org/html/lircd-uinput.html

The /dev/input/event driver in the kernel rc-core has (in my opinion) a 
design problem that the key-repeat events are triggered by an 
independent timer and not directly by the reception of IR messages from 
the RCU. When multiple clock sources are involved, timers will easily 
get out of sync, and in this case cause input lag: For example, a 
perceivable time (such as 0.1s) after a button was already released, the 
independent timer might fire one more key-repeat event.

VDR's existing LIRC interface avoids such input lag by always triggering 
Put() calls by the reception of IR messages. It merely "filters out" 
some IR messages by enforcing RcRepeatDelay and RcRepeatDelta.

Best regards,

	Marko

_______________________________________________
vdr mailing list
[email protected]
https://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr
lirc-wip.patch (text/x-diff, 4.7 KB)
diff --git a/lirc.c b/lirc.c
index 9cc287a6..d7797009 100644
--- a/lirc.c
+++ b/lirc.c
@@ -10,19 +10,13 @@
  */
 
 #include "lirc.h"
-#include <netinet/in.h>
-#include <sys/socket.h>
-
-#define RECONNECTDELAY 3000 // ms
+#include <sys/ioctl.h>
 
 cLircRemote::cLircRemote(const char *DeviceName)
 :cRemote("LIRC")
 ,cThread("LIRC remote control")
 {
-  addr.sun_family = AF_UNIX;
-  strn0cpy(addr.sun_path, DeviceName, sizeof(addr.sun_path));
-  if (!Connect())
-     f = -1;
+  Connect(DeviceName);
   Start();
 }
 
@@ -35,18 +29,17 @@ cLircRemote::~cLircRemote()
      close(fh);
 }
 
-bool cLircRemote::Connect(void)
+inline void cLircRemote::Connect(const char *DeviceName)
 {
-  if ((f = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) {
-     if (connect(f, (struct sockaddr *)&addr, sizeof(addr)) >= 0)
-        return true;
-     LOG_ERROR_STR(addr.sun_path);
+  unsigned mode = LIRC_MODE_SCANCODE;
+  f = open(DeviceName, O_RDONLY, 0);
+  if (f < 0)
+     LOG_ERROR_STR(DeviceName);
+  else if (ioctl(f, LIRC_SET_REC_MODE, &mode)) {
+     LOG_ERROR_STR(DeviceName);
      close(f);
      f = -1;
      }
-  else
-     LOG_ERROR_STR(addr.sun_path);
-  return false;
 }
 
 bool cLircRemote::Ready(void)
@@ -56,50 +49,30 @@ bool cLircRemote::Ready(void)
 
 void cLircRemote::Action(void)
 {
+  if (f < 0)
+     return;
   cTimeMs FirstTime;
   cTimeMs LastTime;
   cTimeMs ThisTime;
-  char buf[LIRC_BUFFER_SIZE];
-  char LastKeyName[LIRC_KEY_BUF] = "";
+  uint32_t LastKeyCode = 0;
   bool pressed = false;
   bool repeat = false;
   int timeout = -1;
 
   while (Running()) {
-
-        bool ready = f >= 0 && cFile::FileReady(f, timeout);
-        int ret = ready ? safe_read(f, buf, sizeof(buf)) : -1;
-
-        if (f < 0 || ready && ret <= 0) {
-           esyslog("ERROR: lircd connection broken, trying to reconnect every %.1f seconds", float(RECONNECTDELAY) / 1000);
-           if (f >= 0)
-              close(f);
-           f = -1;
-           while (Running() && f < 0) {
-                 cCondWait::SleepMs(RECONNECTDELAY);
-                 if (Connect()) {
-                    isyslog("reconnected to lircd");
-                    break;
-                    }
-                 }
-           }
+        lirc_scancode sc;
+        bool ready = cFile::FileReady(f, timeout);
+        int ret = ready ? safe_read(f, &sc, sizeof sc) : -1;
 
         if (ready && ret > 0) {
-           buf[ret - 1] = 0;
-           int count;
-           char KeyName[LIRC_KEY_BUF];
-           if (sscanf(buf, "%*x %x %29s", &count, KeyName) != 2) { // '29' in '%29s' is LIRC_KEY_BUF-1!
-              esyslog("ERROR: unparsable lirc command: %s", buf);
-              continue;
-              }
            int Delta = ThisTime.Elapsed(); // the time between two subsequent LIRC events
            ThisTime.Set();
-           if (count == 0) { // new key pressed
-              if (strcmp(KeyName, LastKeyName) == 0 && FirstTime.Elapsed() < (uint)Setup.RcRepeatDelay)
+           if (!(sc.flags & LIRC_SCANCODE_FLAG_REPEAT)) { // new key pressed
+              if (sc.keycode == LastKeyCode && FirstTime.Elapsed() < (uint)Setup.RcRepeatDelay)
                  continue; // skip keys coming in too fast
               if (repeat)
-                 Put(LastKeyName, false, true); // generated release for previous repeated key
-              strn0cpy(LastKeyName, KeyName, sizeof(LastKeyName));
+                 Put(LastKeyCode, false, true); // generated release for previous repeated key
+              LastKeyCode = sc.keycode;
               pressed = true;
               repeat = false;
               FirstTime.Set();
@@ -116,15 +89,15 @@ void cLircRemote::Action(void)
               }
            if (pressed) {
               LastTime.Set();
-              Put(KeyName, repeat);
+              Put(sc.keycode, repeat);
               }
            }
         else {
            if (pressed && repeat) // the last one was a repeat, so let's generate a release
-              Put(LastKeyName, false, true);
+              Put(LastKeyCode, false, true);
            pressed = false;
            repeat = false;
-           *LastKeyName = 0;
+           LastKeyCode = 0;
            timeout = -1;
            }
         }
diff --git a/lirc.h b/lirc.h
index 3c4735a8..ff6b01c9 100644
--- a/lirc.h
+++ b/lirc.h
@@ -10,17 +10,15 @@
 #ifndef __LIRC_H
 #define __LIRC_H
 
-#include <sys/un.h>
+#include <linux/lirc.h>
 #include "remote.h"
 #include "thread.h"
 
 class cLircRemote : public cRemote, private cThread {
 private:
-  enum { LIRC_KEY_BUF = 30, LIRC_BUFFER_SIZE = 128 };
   int f;
-  struct sockaddr_un addr;
   virtual void Action(void);
-  bool Connect(void);
+  inline void Connect(const char *DeviceName);
 public:
   cLircRemote(const char *DeviceName);
   virtual ~cLircRemote();