/*
* Simulate a linux input device via uinput
* Get lirc remote events, decode with IRMP and inject them via uinput
*
* (C) 2012 Stefan Seyfried
*
* 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 2
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/* the C++ compiler did not like this code, so let's put it into a
* separate file and compile with gcc insead of g++...
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "lirmp_input.h"
extern "C" {
#include "irmp.h"
}
static uint8_t IRMP_PIN;
#include
#define hal_debug(args...) _hal_debug(HAL_DEBUG_INIT, NULL, args)
#define hal_info(args...) _hal_info(HAL_DEBUG_INIT, NULL, args)
/* same defines as in neutrino's rcinput.h */
#define KEY_TTTV KEY_FN_1
#define KEY_TTZOOM KEY_FN_2
#define KEY_REVEAL KEY_FN_D
/* only defined in newer kernels / headers... */
#ifndef KEY_ZOOMIN
#define KEY_ZOOMIN KEY_FN_E
#endif
#ifndef KEY_ZOOMOUT
#define KEY_ZOOMOUT KEY_FN_F
#endif
typedef struct
{
uint16_t ir; /* IR command */
int code; /* input key code */
} key_map_t;
static const key_map_t key_map[] =
{
{ 0x13, KEY_0 },
{ 0x1a, KEY_1 },
{ 0x1f, KEY_2 },
{ 0x58, KEY_3 },
{ 0x16, KEY_4 },
{ 0x1b, KEY_5 },
{ 0x54, KEY_6 },
{ 0x12, KEY_7 },
{ 0x17, KEY_8 },
{ 0x50, KEY_9 },
{ 0x5f, KEY_OK },
{ 0x59, KEY_TIME },
{ 0x43, KEY_FAVORITES },
{ 0x4f, KEY_SAT },
{ 0x0f, KEY_NEXT }, /* V.Format */
{ 0x1e, KEY_POWER },
{ 0x5a, KEY_MUTE },
{ 0x1c, KEY_MENU },
{ 0x5d, KEY_EPG },
{ 0x07, KEY_INFO },
{ 0x60, KEY_EXIT },
{ 0x48, KEY_PAGEUP },
{ 0x44, KEY_PAGEDOWN },
{ 0x02, KEY_LEFT },
{ 0x40, KEY_RIGHT },
{ 0x03, KEY_UP },
{ 0x5e, KEY_DOWN },
{ 0x0a, KEY_VOLUMEUP },
{ 0x06, KEY_VOLUMEDOWN },
{ 0x49, KEY_RED },
{ 0x4e, KEY_GREEN },
{ 0x11, KEY_YELLOW },
{ 0x4a, KEY_BLUE },
{ 0x4c, KEY_TV }, /* TV/Radio */
{ 0x5c, KEY_VIDEO }, /* FIND */
{ 0x19, KEY_AUDIO }, /* FOLDER */
/* KEY_AUX,
KEY_TEXT,
KEY_TTTV,
KEY_TTZOOM,
KEY_REVEAL,
*/
{ 0x01, KEY_REWIND },
{ 0x53, KEY_FORWARD },
{ 0x22, KEY_STOP },
{ 0x4d, KEY_PAUSE },
{ 0x15, KEY_PLAY },
{ 0x20, KEY_PREVIOUS },
{ 0x23, KEY_NEXT },
// KEY_EJECTCD,
{ 0x10, KEY_RECORD }
};
static const int key_list[] =
{
KEY_0,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_OK,
KEY_TIME,
KEY_FAVORITES,
KEY_SAT,
KEY_ZOOMOUT,
KEY_ZOOMIN,
KEY_NEXT,
KEY_POWER,
KEY_MUTE,
KEY_MENU,
KEY_EPG,
KEY_INFO,
KEY_EXIT,
KEY_PAGEUP,
KEY_PAGEDOWN,
KEY_LEFT,
KEY_RIGHT,
KEY_UP,
KEY_DOWN,
KEY_VOLUMEUP,
KEY_VOLUMEDOWN,
KEY_RED,
KEY_GREEN,
KEY_YELLOW,
KEY_BLUE,
KEY_TV,
KEY_VIDEO,
KEY_AUDIO,
// KEY_AUX,
// KEY_TEXT,
// KEY_TTTV,
// KEY_TTZOOM,
// KEY_REVEAL,
KEY_REWIND,
KEY_STOP,
KEY_PAUSE,
KEY_PLAY,
KEY_FORWARD,
KEY_PREVIOUS,
KEY_NEXT,
// KEY_EJECTCD,
KEY_RECORD,
-1
};
static pthread_t thread;
static int thread_running;
static void *input_thread(void *)
{
int uinput;
struct input_event u;
struct uinput_user_dev ud;
FILE *f;
int lircfd;
int pulse;
int i = 0;
int last_pulse = 1;
int last_code = -1;
uint32_t lircdata; /* lirc_t to be correct... */
unsigned int count = 0; /* how many timeouts? */
unsigned int nodec = 0; /* how many timeouts since last decoded? */
int aotom_fd = -1;
IRMP_DATA d;
hal_info("LIRC/IRMP input converter thread starting...\n");
/* modprobe does not complain if the module is already loaded... */
system("/sbin/modprobe uinput");
do
{
usleep(100000); /* mdev needs some time to create the device? */
uinput = open("/dev/uinput", O_WRONLY | O_NDELAY);
}
while (uinput < 0 && ++count < 100);
if (uinput < 0)
{
hal_info("LIRC/IRMP input thread: unable to open /dev/uinput (%m)\n");
thread_running = 2;
return NULL;
}
fcntl(uinput, F_SETFD, FD_CLOEXEC);
ioctl(uinput, UI_SET_EVBIT, EV_KEY);
/* do not use kernel repeat EV_REP since neutrino will be confused by the
* generated SYN_REPORT events...
ioctl(uinput, UI_SET_EVBIT, EV_REP);
*/
/* register keys */
for (i = 0; key_list[i] != -1; i++)
ioctl(uinput, UI_SET_KEYBIT, key_list[i]);
/* configure the device */
memset(&ud, 0, sizeof(ud));
strncpy(ud.name, "Neutrino LIRC/IRMP to Input Device converter", UINPUT_MAX_NAME_SIZE);
ud.id.version = 0x42;
ud.id.vendor = 0x1234;
ud.id.product = 0x5678;
ud.id.bustype = BUS_I2C; /* ?? */
write(uinput, &ud, sizeof(ud));
if (ioctl(uinput, UI_DEV_CREATE))
{
hal_info("LIRC/IRMP input thread UI_DEV_CREATE: %m\n");
close(uinput);
return NULL;
}
/* this is ugly: parse the new input device from /proc/...devices
* and symlink it to /dev/input/nevis_ir... */
#define DEVLINE "I: Bus=0018 Vendor=1234 Product=5678 Version=0042"
f = fopen("/proc/bus/input/devices", "r");
if (f)
{
int found = 0;
int evdev = -1;
size_t n = 0;
char *line = NULL;
char *p;
char newdev[20];
while (getline(&line, &n, f) != -1)
{
switch (line[0])
{
case 'I':
if (strncmp(line, DEVLINE, strlen(DEVLINE)) == 0)
found = 1;
break;
case 'H':
if (! found)
break;
p = strstr(line, " event");
if (! p)
{
evdev = -1;
break;
}
evdev = atoi(p + 6);
sprintf(newdev, "event%d", evdev);
hal_info("LIRC/IRMP input thread: symlink /dev/input/nevis_ir to %s\n", newdev);
unlink("/dev/input/nevis_ir");
symlink(newdev, "/dev/input/nevis_ir");
break;
default:
break;
}
if (evdev != -1)
break;
}
fclose(f);
free(line);
}
u.type = EV_KEY;
u.value = 0; /* initialize: first event wil be a key press */
lircfd = open("/dev/lirc", O_RDONLY);
if (lircfd < 0)
{
hal_info("%s: open /dev/lirc: %m\n", __func__);
goto out;
}
IRMP_PIN = 0xFF;
/* 50 ms. This should be longer than the longest light pulse */
#define POLL_MS (100 * 1000)
#define LIRC_PULSE 0x01000000
#define LIRC_PULSE_MASK 0x00FFFFFF
hal_info("LIRC/IRMP input converter going into main loop...\n");
aotom_fd = open("/dev/vfd", O_RDONLY);
/* TODO: ioctl to find out if we have a compatible LIRC_MODE2 device */
thread_running = 1;
while (thread_running)
{
fd_set fds;
struct timeval tv;
int ret;
FD_ZERO(&fds);
FD_SET(lircfd, &fds);
tv.tv_sec = 0;
tv.tv_usec = POLL_MS;
/* any singal can interrupt select. we rely on the linux-only feature
* that the timeout is automatcally recalculated in this case! */
do
{
ret = select(lircfd + 1, &fds, NULL, NULL, &tv);
}
while (ret == -1 && errno == EINTR);
if (ret == -1)
{
/* errno != EINTR... */
hal_info("%s: lirmp: lircfd select: %m\n", __func__);
break;
}
if (ret == 0)
{
count++;
nodec++;
lircdata = POLL_MS; /* timeout */
pulse = !last_pulse; /* lirc sends data on signal change */
if (last_code != -1 && nodec > 1)
{
// fprintf(stderr, "timeout!\n");
u.code = last_code;
u.value = 0; /* release */
write(uinput, &u, sizeof(u));
last_code = -1;
}
}
else
{
if (read(lircfd, &lircdata, sizeof(lircdata)) != sizeof(lircdata))
{
perror("read");
break;
}
pulse = (lircdata & LIRC_PULSE); /* we got light... */
last_pulse = pulse;
lircdata &= LIRC_PULSE_MASK; /* how long the pulse was in microseconds */
}
if (ret && count)
{
if (count * POLL_MS > lircdata)
lircdata = 0;
else
lircdata -= count * POLL_MS;
count = 0;
}
//printf("lircdata: ret:%d c:%d %d\n", ret, ch - '0', lircdata);
lircdata /= (1000000 / F_INTERRUPTS);
if (pulse)
IRMP_PIN = 0x00;
else
IRMP_PIN = 0xff;
do
{
(void) irmp_ISR(IRMP_PIN);
if (irmp_get_data(&d))
{
nodec = 0;
hal_debug("irmp_get_data proto: %2d addr: 0x%04x cmd: 0x%04x fl: %d\n",
d.protocol, d.address, d.command, d.flags);
/* todo: do we need to complete the loop if we already
* detected the singal in this pulse? */
if (d.protocol == IRMP_NEC_PROTOCOL && d.address == 0xba45)
{
for (i = 0; i < (int)(sizeof(key_map) / sizeof(key_map_t)); i++)
{
if (key_map[i].ir == d.command)
{
if (last_code != -1 && last_code != key_map[i].code)
{
u.code = last_code;
u.value = 0;
write(uinput, &u, sizeof(u));
}
u.code = key_map[i].code;
u.value = (d.flags & 0x1) + 1;
//hal_debug("uinput write: value: %d code: %d\n", u.value, u.code);
last_code = u.code;
write(uinput, &u, sizeof(u));
if (aotom_fd > -1)
{
struct aotom_ioctl_data vfd_data;
vfd_data.u.led.led_nr = 1;
vfd_data.u.led.on = 10;
ioctl(aotom_fd, VFDSETLED, &vfd_data);
}
break;
}
}
}
}
}
while (lircdata-- > 0);
}
/* clean up */
close(lircfd);
if (aotom_fd > -1)
close(aotom_fd);
out:
ioctl(uinput, UI_DEV_DESTROY);
return NULL;
}
void start_input_thread(void)
{
if (pthread_create(&thread, 0, input_thread, NULL) != 0)
{
hal_info("%s: LIRC/IRMP input thread pthread_create: %m\n", __func__);
thread_running = 0;
return;
}
/* wait until the device is created before continuing */
while (! thread_running)
usleep(1000);
if (thread_running == 2) /* failed... :-( */
thread_running = 0;
}
void stop_input_thread(void)
{
if (! thread_running)
return;
thread_running = 0;
pthread_join(thread, NULL);
}