Skip to content
April 11, 2011 / gus3

A Substitute Remote Control

A long-standing task for the Linux kernel has been the removal of the Big Kernel Lock, and it looks like version 2.6.39 will achieve that goal. However, user-space applications which use information about the BKL are now failing to build or run, including the Linux Infrared Remote Control project.

Naturally, this gave me an itch, and I wanted to scratch it. My favorite movie player is MPlayer, which just happens to support text-based commands from a named pipe. This allows other processes to act as controllers… including a custom-built interpreter for my Streamzap USB remote control. It isn’t exactly a shortcut, but it’s still faster than studying the entire LIRC code base just to figure out how to remove the references to the Big Kernel Lock.

The first step was to find the exit point of the data from the remote. On stock kernels, one of the files in /dev/input will report the event data. The system log will show the USB device as being “inputN“, which means the file is /dev/input/eventN. (This is true only until the receiving unit is unplugged and re-connected. The real path is found under the /sys directory; more below.) On a 32-bit system, this file emits 16 bytes per event, and 24 bytes per event on a 64-bit system. In both cases, the unique code for the button pressed is the 4th byte from the end.

I put the codes into streamzap-codes.h:

// These are the Streamzap remote control codes that appear at
// offset 12 (32-bit) or offset 16 (64-bit) of the input buffer
// at /dev/input/eventN.

#define DIGIT_ZERO 0xc0
#define DIGIT_ONE 0xc1
#define DIGIT_TWO 0xc2
#define DIGIT_THREE 0xc3
#define DIGIT_FOUR 0xc4
#define DIGIT_FIVE 0xc5
#define DIGIT_SIX 0xc6
#define DIGIT_SEVEN 0xc7
#define DIGIT_EIGHT 0xc8
#define DIGIT_NINE 0xc9
#define POWER 0xca
#define MUTE 0xcb
#define CH_UP 0xcc
#define VOL_UP 0xcd
#define CH_DN 0xce
#define VOL_DN 0xcf
#define ARROW_UP 0xd0
#define ARROW_LFT 0xd1
#define OK 0xd2
#define ARROW_RT 0xd3
#define ARROW_DN 0xd4
#define MENU 0xd5
#define EXIT 0xd6
#define PLAY 0xd7
#define PAUSE 0xd8
#define STOP 0xd9
#define SKIP_BK 0xda
#define SKIP_FW 0xdb
#define RECORD 0xdc
#define SEEK_BK 0xdd
#define SEEK_FW 0xde
// 0xdf unused
#define RED 0xe0
#define GREEN 0xe1
#define YELLOW 0xe2
#define BLUE 0xe3

Eventually, I was able to use these codes, along with some debouncing and a buffer for numeric input (up to 3 digits). Enough experimenting and clean-up resulted in streamzap-interpreter.c:

/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    See http://www.gnu.org/licenses/ for more information.
*/

#include <stdio.h>
#include <sys/time.h>
#include "streamzap-codes.h"

#if __WORDSIZE == 64
#define RCBUFWIDTH 24
#else
#define RCBUFWIDTH 16
#endif

static char digit_buffer[4] = "\0\0\0"; // up to "999"
#define clear_digits digit_buffer[3]=digit_buffer[2]=digit_buffer[1]=digit_buffer[0]='\0'

#ifdef DEBUG
#define debug(...) fprintf(stderr,__VA_ARGS__)
#else
#define debug(...) do { } while (0==1);
#endif

/* Actions have three components: an input code, a command to send
   to MPlayer, and a possible default value if the command is
   modifiable with a numeric parameter. For example, "pause" takes no
   parameter, but "seek" does. If a command has a modifiable parameter,
   it includes %s after the command, which (in printf() style) will be
   replaced with the default, or up to 3 digits received earlier. For
   example, just pressing the "seek back" button will send the command
   "seek -10", but pressing "2" "7" "seek back" will send the command
   "seek -27".
*/

struct action { int code;
                char *cmd;
                char *dflt;    // NULL if no adjustable parameter
};

struct action actions[] = {
    {DIGIT_ZERO,"0",NULL},
    {DIGIT_ONE,"1",NULL},
    {DIGIT_TWO,"2",NULL},
    {DIGIT_THREE,"3",NULL},
    {DIGIT_FOUR,"4",NULL},    
    {DIGIT_FIVE,"5",NULL},
    {DIGIT_SIX,"6",NULL},
    {DIGIT_SEVEN,"7",NULL},
    {DIGIT_EIGHT,"8",NULL},
    {DIGIT_NINE,"9",NULL},
    {POWER,"quit",NULL},
    {MUTE,"mute",NULL},
    {CH_UP,"audio_delay 0.1",NULL},
    {VOL_UP,"volume 1",NULL},
    {CH_DN,"audio_delay -0.1",NULL},
    {VOL_DN,"volume -1",NULL},
    {ARROW_UP,"seek %s","600"},
    {ARROW_LFT,"seek -%s","300"},
    {OK,"vo_fullscreen",NULL},
    {ARROW_RT,"seek %s","300"},
    {ARROW_DN,"seek -%s","600"},
    {MENU,"menu",NULL},
    {EXIT,"exit",NULL},
    {PLAY,"pause",NULL},
    {PAUSE,"pause",NULL},
    {STOP,"pause",NULL},
    {SKIP_BK,"seek -%s","60"},
    {SKIP_FW,"seek %s","60"},
    {RECORD,"rec",NULL},
    {SEEK_BK,"seek -%s","10"},
    {SEEK_FW,"seek %s","10"},
    {RED,"red",NULL},
    {GREEN,"green",NULL},
    {YELLOW,"yellow",NULL},
    {BLUE,"blue",NULL},
    {0,NULL,NULL}    // safety catch
};

void save_digit(char s) {
    // very fast strlen()--find last non-null char
    int len = digit_buffer[2] ? 3 :
              digit_buffer[1] ? 2 :
              digit_buffer[0] ? 1 : 0;
    // no more than 3 digits, and no leading zeros
    if ((len < 3) && ((len > 0) || (s > '0')))
        digit_buffer[len] = s;
    debug("Digit %c, buffer now %s\n", s, digit_buffer);
}

int main(int argc, char *argv[]) {
    unsigned char rcbuf[25];
    struct timeval ts, prev_ts, diff_ts;
    FILE *fp;
    unsigned char rcvcode;
    struct action *a;
    
    if (argc < 2) {
        fprintf(stderr, "Usage: %s /dev/input/event-file\n", argv[0]);
        return 255;
    }
    if ((fp = fopen(argv[1], "r")) == NULL) {
        fprintf(stderr, "Open %s for input failed\n", argv[1]);
        return 255;
    }

    // initialize "previous" timestamp
    if (gettimeofday(&prev_ts, NULL)) {
        fprintf(stderr, "gettimeofday() failed\n");
        return 255;
    }
    
    while (0==0) {
        if (fread(rcbuf, 1, RCBUFWIDTH, fp) != RCBUFWIDTH) {
            fprintf(stderr, "Short read, aborting\n");
            return 255;
        }
        
        // how long since previous read?
        gettimeofday(&ts, NULL);
        timersub(&ts, &prev_ts, &diff_ts);
        prev_ts = ts;
        if ((0 == diff_ts.tv_sec) && (140000 > diff_ts.tv_usec))
        // at least 0.14 sec or reject
            continue;

        // now we have input, get code
        rcvcode = rcbuf[RCBUFWIDTH - 4];
        debug("Code: %#2x  Action: ", rcvcode);
        a = actions;
        // find matching entry
        while (a->code && (a->code != rcvcode)) a++;
        // alert if not in list, then ignore
        if (!(a->code)) {
            debug("No code %x!\n", rcvcode);
            continue;
        }

        switch (a->code) {
        // These codes should be enumerated explicitly. If they
        // are not contiguous, a range check won't work.
            case DIGIT_ZERO:
            case DIGIT_ONE:
            case DIGIT_TWO:
            case DIGIT_THREE:
            case DIGIT_FOUR:
            case DIGIT_FIVE:
            case DIGIT_SIX:
            case DIGIT_SEVEN:
            case DIGIT_EIGHT:
            case DIGIT_NINE:
                debug("digit %c\n", *(a->cmd));
                save_digit(*(a->cmd));
                continue;
            default:
                break;    // to make the compiler happy
        }

        // If command contains %s, then digits (or default) will
        // be emitted; without %s, no digits or default emitted, but
        // we'll pass them anyway. Without %s and no digits in buffer means
        // NULL passed, but not interpreted. What's a few microseconds?
        debug(a->cmd, (*digit_buffer) ? digit_buffer : a->dflt);
        debug("\n");
        printf(a->cmd, (*digit_buffer) ? digit_buffer : a->dflt);
        printf("%s", "\n");
        fflush(NULL);
        clear_digits;
        if (POWER == rcvcode)  // power button
            return 0;        // means exit
    }
}

The simple way to build it, is simply to type

gcc -o streamzap-interpreter streamzap-interpreter.c

in the directory with streamzap-interpreter.c and streamzap-codes.h. If you want to see some debugging output, add -DDEBUG after before the -o parameter. To test it, become root, then type

./streamzap-interpreter /dev/input/eventN

replacing N with the correct number for your system. If you then press the digit “5” and any right-pointing arrow, you should see “seek 5″ on the output. Press the red power button at the upper left of the remote to exit the program cleanly. Once you are satisfied that the program works, copy it to /usr/local/bin/.

One catch to using the Linux input event interface, is that the event files by default are readable only by root. Changing the permissions on the event file is effective only until the next reboot; an alternative is to install the streamzap-interpreter with set-UID enabled. Normally, this would be considered a security hazard; the difference here is that the restricted input is managed so that the odds of overflow are somewhere between razor-thin and non-existent.

chown root:root /usr/local/bin/streamzap-interpreter
chmod 4755 /usr/local/bin/streamzap-interpreter

Of course, the commands emitted for each button are arbitrary; any button can be configured to emit anything. However, the digit buttons are handled as a special case (literally), so they should retain their digit meanings. The explanation of using digit prefixes for command buttons is found in the comment block starting at line 22 of streamzap-interpreter.c above.

Once I had the interpreter working, I decided to simplify matters with a shell script. The result was zapplayer.sh:

#!/bin/sh
# This script is in the public domain, as all interpreted scripts should be.

# Crude but effective.
# BUG: disconnection and re-connection can break correspondence
# of input number path and event number file.
#N=`dmesg | grep zap.*input | tail -n 1 | sed 's/^.*input//'`
#if [ -z $N ] ; then
#    echo "No mention of Streamzap in the system log; aborting."
#    exit 255
#fi
#EVFILE=/dev/input/event$N
if [ ! -d /sys/module/streamzap ] ; then
    echo "streamzap module missing; aborting."
    exit 255
fi
# get event file from module info
EVFILE=/dev/input/`basename /sys/module/streamzap/drivers/*:*/*:*/rc/*/input*/event*`
FIFO=/tmp/mplayer-fifo-$$
INTERPRETER=`which streamzap-interpreter`

if [ -z $INTERPRETER ] ; then
    echo "streamzap-interpreter not found in PATH; aborting."
    exit 255
fi
if [ ! -u $INTERPRETER ] ; then
    echo "$INTERPRETER needs to be set-UID root."
    exit 255
fi

# Must use a named pipe, rather than simply piping
# output to stdin. MPlayer reads stdin as keystroke
# commands, not what we want.
mkfifo $FIFO
eval $INTERPRETER $EVFILE > $FIFO &
INTERPRETER_PID=$!
# pass other parameters to MPlayer
mplayer -input file=$FIFO $*
# when it exits, clean up
kill $INTERPRETER_PID
rm -f $FIFO

This takes care of finding the Streamzap input event file, setting up the FIFO, and launching MPlayer. Any parameters are passed verbatim to MPlayer. It installs easily as /usr/local/bin/zapplayer.sh.

If you have a USB remote control that uses different codes in its event input, feel free to post them. I have tried to make this code reasonably flexible, for both buttons and resulting output. In the meantime, we LIRC fans eagerly await its update for the new Linux 2.6.39 kernel.

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

See http://www.gnu.org/licenses/ for more information.
*/

#include <stdio.h>
#include <sys/time.h>
#include “streamzap-codes.h”

#if __WORDSIZE == 64
#define RCBUFWIDTH 24
#else
#define RCBUFWIDTH 16
#endif

static char digit_buffer[4] = “”; // up to “999”
#define clear_digits digit_buffer[3]=digit_buffer[2]=digit_buffer[1]=digit_buffer[0]=”

#ifdef DEBUG
#define debug(…) fprintf(stderr,__VA_ARGS__)
#else
#define debug(…) do { } while (0==1);
#endif

/* Actions have three components: an input code, a command to send
to MPlayer, and a possible default value if the command is
modifiable with a numeric parameter. For example, “pause” takes no
parameter, but “seek” does. If a command has a modifiable parameter,
it includes %s after the command, which (in printf() style) will be
replaced with the default, or up to 3 digits received earlier. For
example, just pressing the “seek back” button will send the command
“seek -10″, but pressing “2” “7” “seek back” will send the command
“seek -27″.
*/

struct action { int code;
char *cmd;
char *dflt;    // NULL if no adjustable parameter
};

struct action actions[] = {
{DIGIT_ZERO,”0″,NULL},
{DIGIT_ONE,”1″,NULL},
{DIGIT_TWO,”2″,NULL},
{DIGIT_THREE,”3″,NULL},
{DIGIT_FOUR,”4″,NULL},
{DIGIT_FIVE,”5″,NULL},
{DIGIT_SIX,”6″,NULL},
{DIGIT_SEVEN,”7″,NULL},
{DIGIT_EIGHT,”8″,NULL},
{DIGIT_NINE,”9″,NULL},
{POWER,”quit”,NULL},
{MUTE,”mute”,NULL},
{CH_UP,”audio_delay 0.1″,NULL},
{VOL_UP,”volume 1″,NULL},
{CH_DN,”audio_delay -0.1″,NULL},
{VOL_DN,”volume -1″,NULL},
{ARROW_UP,”seek %s”,”600″},
{ARROW_LFT,”seek -%s”,”300″},
{OK,”vo_fullscreen”,NULL},
{ARROW_RT,”seek %s”,”300″},
{ARROW_DN,”seek -%s”,”600″},
{MENU,”menu”,NULL},
{EXIT,”exit”,NULL},
{PLAY,”pause”,NULL},
{PAUSE,”pause”,NULL},
{STOP,”pause”,NULL},
{SKIP_BK,”seek -%s”,”60″},
{SKIP_FW,”seek %s”,”60″},
{RECORD,”rec”,NULL},
{SEEK_BK,”seek -%s”,”10″},
{SEEK_FW,”seek %s”,”10″},
{RED,”red”,NULL},
{GREEN,”green”,NULL},
{YELLOW,”yellow”,NULL},
{BLUE,”blue”,NULL},
{0,NULL,NULL}    // safety catch
};

void save_digit(char s) {
// very fast strlen()–find last non-null char
int len = digit_buffer[2] ? 3 :
digit_buffer[1] ? 2 :
digit_buffer[0] ? 1 : 0;
// no more than 3 digits, and no leading zeros
if ((len < 3) && ((len > 0) || (s > ‘0’)))
digit_buffer[len] = s;
debug(“Digit %c, buffer now %s\n”, s, digit_buffer);
}

int main(int argc, char *argv[]) {
unsigned char rcbuf[25];
struct timeval ts, prev_ts, diff_ts;
FILE *fp;
unsigned char rcvcode;
struct action *a;

if (argc < 2) {
fprintf(stderr, “Usage: %s /dev/input/event-file\n”, argv[0]);
return 255;
}
if ((fp = fopen(argv[1], “r”)) == NULL) {
fprintf(stderr, “Open %s for input failed\n”, argv[1]);
return 255;
}

// initialize “previous” timestamp
if (gettimeofday(&prev_ts, NULL)) {
fprintf(stderr, “gettimeofday() failed\n”);
return 255;
}

while (0==0) {
if (fread(rcbuf, 1, RCBUFWIDTH, fp) != RCBUFWIDTH) {
fprintf(stderr, “Short read, aborting\n”);
return 255;
}

// how long since previous read?
gettimeofday(&ts, NULL);
timersub(&ts, &prev_ts, &diff_ts);
prev_ts = ts;
if ((0 == diff_ts.tv_sec) && (140000 > diff_ts.tv_usec))
// at least 0.14 sec or reject
continue;

// now we have input, get code
rcvcode = rcbuf[RCBUFWIDTH – 4];
debug(“Code: %#2x  Action: “, rcvcode);
a = actions;
// find matching entry
while (a->code && (a->code != rcvcode)) a++;
// alert if not in list, then ignore
if (!(a->code)) {
debug(“No code %x!\n”, rcvcode);
continue;
}

switch (a->code) {
// These codes should be enumerated explicitly. If they
// are not contiguous, a range check won’t work.
case DIGIT_ZERO:
case DIGIT_ONE:
case DIGIT_TWO:
case DIGIT_THREE:
case DIGIT_FOUR:
case DIGIT_FIVE:
case DIGIT_SIX:
case DIGIT_SEVEN:
case DIGIT_EIGHT:
case DIGIT_NINE:
debug(“digit %c\n”, *(a->cmd));
save_digit(*(a->cmd));
continue;
default:
break;    // to make the compiler happy
}

// If command contains %s, then digits (or default) will
// be emitted; without %s, no digits or default emitted, but
// we’ll pass them anyway. Without %s and no digits in buffer means
// NULL passed, but not interpreted. What’s a few microseconds?
debug(a->cmd, (*digit_buffer) ? digit_buffer : a->dflt);
debug(“\n”);
printf(a->cmd, (*digit_buffer) ? digit_buffer : a->dflt);
printf(“%s”, “\n”);
fflush(NULL);
clear_digits;
if (POWER == rcvcode)  // power button
return 0;        // means exit
}
}

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: