diff --git a/ChangeLog b/ChangeLog index 26bba52..eabf82a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,6 +20,7 @@ v0.5dev (ongoing development) + glcd driver: new connection type 'serdisplib' + glcd driver: new connection type 'glcd2usb' * lcdproc client: On FreeBSD show only unique processes in 'S' screen + * picolcd: Use libusb-1.0 asynchronous transfers to fix missed keys (M. Jones) v0.5.5 + sed1330 driver: Add support for HG25504 (L. Lagendijk) diff --git a/LCDd.conf b/LCDd.conf index 9876a95..ade7ce0 100644 --- a/LCDd.conf +++ b/LCDd.conf @@ -926,6 +926,9 @@ Reboot=no ## Mini-box.com picoLCD (usblcd) driver ## [picolcd] +# KeyTimeout is only used if the picoLCD driver is built with libusb-0.1. When +# built with libusb-1.0 key and IR data is input asynchronously so there is no +# need to wait for the USB data. # KeyTimeout is the time in ms that LCDd spends waiting for a key press before # cycling through other duties. Higher values make LCDd use less CPU time and # make key presses more detectable. Lower values make LCDd more responsive @@ -933,6 +936,18 @@ Reboot=no # and a balanced value. [default: 500; legal: 0 - 1000] KeyTimeout=500 +# Key auto repeat is only available if the picoLCD driver is built with +# libusb-1.0. Use KeyRepeatDelay and KeyRepeatInterval to configure key auto +# repeat. +# +# Key auto repeat delay (time in ms from first key report to first repeat). Use +# zero to disable auto repeat. [default: 300; legal: 0 - 3000] +KeyRepeatDelay=300 + +# Key auto repeat interval (time in ms between repeat reports). Only used if +# KeyRepeatDelay is not zero. [default: 200; legal: 0 - 3000] +KeyRepeatInterval=200 + # Sets the initial state of the backlight upon start-up. # [default: on; legal: on, off] #Backlight=on @@ -948,6 +963,9 @@ Brightness=1000 # Set the initial contrast [default: 1000; legal: 0 - 1000] Contrast=1000 +# Link the key lights to the backlight? [default: on; legal: on, off] +#LinkLights=off + # Light the keys? [default: on; legal: on, off] Keylights=on diff --git a/acinclude.m4 b/acinclude.m4 index 6ec6eb5..dde0eb8 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -381,6 +381,11 @@ dnl else if test "$enable_libusb" = yes ; then DRIVERS="$DRIVERS picolcd${SO}" actdrivers=["$actdrivers picolcd"] + if test "$enable_libusb_1_0" = yes ; then + AC_MSG_RESULT([The picolcd driver is using the libusb-1.0 library.]) + else + AC_MSG_RESULT([The picolcd driver is using the libusb-0.1 library.]) + fi else AC_MSG_WARN([The picolcd driver needs the libusb library.]) fi diff --git a/configure.in b/configure.in index 40e0e9d..4a6eae9 100644 --- a/configure.in +++ b/configure.in @@ -270,6 +270,29 @@ fi AC_SUBST(LIBUSB_LIBS) AC_SUBST(LIBUSB_CFLAGS) +dnl ###################################################################### +dnl libusb-1.0 support +dnl ###################################################################### +AC_MSG_CHECKING([if libusb-1-0 support has been enabled]); +AC_ARG_ENABLE(libusb_1_0, + [AS_HELP_STRING([--disable-libusb-1-0],[disable USB support using libusb-1.0])], + [ if test "$enableval" != "no"; then + enable_libusb_1_0="yes" + fi ], + [ enable_libusb_1_0=yes ] +) +AC_MSG_RESULT($enable_libusb_1_0) + +if test "$enable_libusb_1_0" = "yes"; then + ifdef([PKG_CHECK_MODULES], + [PKG_CHECK_MODULES(LIBUSB_1_0, libusb-1.0 >= 1.0, + [AC_DEFINE(HAVE_LIBUSB_1_0, [1], [Define to 1 if you have libusb-1.0])], + [ enable_libusb_1_0=no ])], + [AC_MSG_WARN([pkg-config not (fully) installed; drivers requiring libusb-1.0 may not be built])]) +fi +AC_SUBST(LIBUSB_1_0_LIBS) +AC_SUBST(LIBUSB_1_0_CFLAGS) + dnl ###################################################################### dnl libftdi support dnl ###################################################################### diff --git a/docs/lcdproc-user/drivers/picolcd.docbook b/docs/lcdproc-user/drivers/picolcd.docbook index ddf7ebc..a6d06d5 100644 --- a/docs/lcdproc-user/drivers/picolcd.docbook +++ b/docs/lcdproc-user/drivers/picolcd.docbook @@ -56,7 +56,10 @@ Finally, the picoLCD 20x2 (OEM) supports 8 general purpose outputs and 10 custom splash screens. - Although these features are not supported by this driver, the + When the keypad is connected the outputs control the key LEDs. The + output command and KeyLight settings below can be used to control the + outputs. + Although splash screens are not supported by this driver, the splash screens can be changed using the usblcd tool, that can be built from the Linux SDK available on the picoLCD web page. @@ -115,7 +118,7 @@ The Brightness and OffBrightness settings only have an effect on the 20x4 device. With the 20x2 device the backlight can only be set on (any value - greater than 1000) or off (0). + 1 or greater) or off (0). @@ -167,6 +170,15 @@ + + + LinkLights = ¶meters.yesdefno; + + + Allow key LEDs to be turned on or off with the backlight. Default is yes. + + + KeyLights = ¶meters.yesdefno; @@ -243,14 +255,60 @@ KeyTimeout = DURATION - + + + KeyTimeout is only used if the picoLCD driver is built with libusb-0.1, when + built with libusb-1.0 key and IR data is input asynchronously so there is no + need to wait for the USB data thus allowing LCDd to process other inputs at + the correct rate. + + This value controls how long LCDd waits for a key press when get_key() is called. The value represents milliseconds and the default is 500 or .5 seconds. Lowering this value will make LCDd more responsive but also causes LCDd to use more CPU time and, as the timeout grows shorter, key presses become harder to detect. Large values make key presses more reliable but may slow down LCDd. Values in the range 0-1000 (1s) are allowed. - + + + + + + + KeyRepeatDelay = + DURATION + + + + KeyRepeatDelay is only used if the picoLCD driver is built with libusb-1.0, when + built with libusb-0.1 key input blocks all other processing until the key is released. + + + This value controls how long LCDd waits from when a key is + pressed and reported before generating the first repeat. The value represents milliseconds + and the default is 300 (0.3 second). Use zero to disable auto repeat. + Values in the range 0-3000 (3s) are allowed. + + + + + + + KeyRepeatInterval = + DURATION + + + + KeyRepeatInterval is only used if the picoLCD driver is built with libusb-1.0, when + built with libusb-0.1 key input blocks all other processing until the key is released. + + + This value controls how long LCDd waits between key reports + after generating the first repeat. The value represents milliseconds and the default + is 200 (0.2 second). Use zero to disable auto repeat. + Values in the range 0-3000 (3s) are allowed. + + @@ -320,6 +378,10 @@ deal with this in a sane way and toss out all key-up events for now. The hardware is touchy and both combo key-down and key-up actions may be reported as multiple events if the user is more than a tenth of a second (maybe less?) off in motions. + The hardware is not "touchy" it reports what it receives. Two key + presses or releases may appear simultaneous to a human but they are always some time apart. + The hardware probably samples the keys for every USB transfer cycle; that is every 10ms, + significantly faster than the typical human response time of a few hundred milliseconds! diff --git a/server/drivers/Makefile.am b/server/drivers/Makefile.am index 7de728a..349e880 100644 --- a/server/drivers/Makefile.am +++ b/server/drivers/Makefile.am @@ -32,7 +32,7 @@ i2500vfd_CFLAGS = @LIBFTDI_CFLAGS@ $(AM_CFLAGS) IOWarrior_CFLAGS = @LIBUSB_CFLAGS@ $(AM_CFLAGS) lis_CFLAGS = @LIBUSB_CFLAGS@ @LIBFTDI_CFLAGS@ $(AM_CFLAGS) mdm166a_CFLAGS = @LIBHID_CFLAGS@ $(AM_CFLAGS) -picolcd_CFLAGS = @LIBUSB_CFLAGS@ $(AM_CFLAGS) +picolcd_CFLAGS = @LIBUSB_CFLAGS@ @LIBUSB_1_0_CFLAGS@ $(AM_CFLAGS) shuttleVFD_CFLAGS = @LIBUSB_CFLAGS@ $(AM_CFLAGS) ula200_CFLAGS = @LIBFTDI_CFLAGS@ $(AM_CFLAGS) xosd_CFLAGS = @LIBXOSD_CFLAGS@ $(AM_CFLAGS) @@ -63,7 +63,7 @@ mtc_s16209x_LDADD = libLCD.a MtxOrb_LDADD = libLCD.a libbignum.a mx5000_LDADD = @LIBMX5000@ NoritakeVFD_LDADD = libbignum.a -picolcd_LDADD = @LIBUSB_LIBS@ libLCD.a libbignum.a +picolcd_LDADD = @LIBUSB_LIBS@ @LIBUSB_1_0_LIBS@ libLCD.a libbignum.a pyramid_LDADD = libLCD.a libbignum.a serialPOS_LDADD = libbignum.a serialVFD_LDADD = libLCD.a libbignum.a diff --git a/server/drivers/picolcd.c b/server/drivers/picolcd.c index 6dca612..06f8742 100644 --- a/server/drivers/picolcd.c +++ b/server/drivers/picolcd.c @@ -16,45 +16,12 @@ * 2007-2008 Mini-Box.com, Nicu Pavel * 2008 Jack Cleaver * 2009 Andries van Schie - * 2010 Martin Jones + * 2010-2011 Martin Jones * * This file is released under the GNU General Public License. Refer to the * COPYING file distributed with this package. */ -/*- - * Driver history: - * - * 2007 NitroSecurity, Inc. - * - First version written by Gatewood Green - * 2007-2008 Peter Marschall - * - adapted coding style and reporting to LCDproc - * 2007 Mini-Box.com, Nicu Pavel - * - removed libusblcd and hid dependency - * - added vbar, hbar, custom char, bignum support - * 2008 Jack Cleaver - * - add LIRC connection - * 2008 Mini-Box.com Nicu Pavel - * - Added support for 4x20 picoLCD - * 2009 Andries van Schie - * - Bugfix RC-5 for picoLCD 20x2 - * - Changed to dynamic IR sync(space) injection, by timing time between end - * and start pulse. - * - Queueing IR data to prevent timeouts by LIRC (sending by timeout) - * - Removed usb_clear_halt, because it breaks picoLCD 20x2 (1.57) communication - * 2010 Martin Jones - * - Use module output function to control key LEDs. - * 2011 Markus Dolze - * - Clean-up includes and move all defaults to header file. - * - Fix heartbeat and bignum by mapping character 0 to 8. - * - Fix backlight and contrast handling (by M. T. Jones). - * - Add more icons (by M. T. Jones). - * - Add OffBrightness for 20x4 (idea by S. Crane), IMPORTANT: The meaning - * of 'bklight_min' changed. It now is the maximum value allowed if - * brightness is set off. - * - Add range checking for config settings. - */ - #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -65,12 +32,10 @@ #include #include #include -#include #include #include #include #include -#include /* LCDproc includes */ #include "lcd.h" @@ -78,23 +43,52 @@ #include "adv_bignum.h" #include "report.h" #include "picolcd.h" +#include "timing.h" -#define NUM_CCs 8 /* max. number of custom characters */ +#define NUM_CCs 8 /* max. number of custom characters */ +#define KEY_BUFFER_SIZE 8 /* size of the key ring buffer */ -/* PrivateData struct */ +#ifdef HAVE_LIBUSB_1_0 +/** + * This structure holds the keys reported in a key event. high_key is set if + * a single key is pressed or it holds the first key if two keys are pressed. + * low_key holds the second key pressed (if any). + */ +typedef struct { + unsigned char high_key; + unsigned char low_key; +} keys; +#endif + +/* + * The current decision to use a separate select statement for libusb was + * made to restrict the use of hardware related libraries to the drivers. + * Ultimately it would be better if all socket and time event processing was + * in one place. See: + * http://libusb.sourceforge.net/api-1.0/group__asyncio.html#asyncevent + * + * If using a single select statement for all event processing libusb-1.0 + * initialisation, deinitialisation and event processing needs to be + * performed by LCDd core code. See comments in picoLCD_init(), + * picoLCD_close() and picoLCD_get_key(). It may be convenient to use the + * default context. + */ +#undef USE_LIBUSB_SINGLE_SELECT + +/** Private data for the picoLCD driver */ typedef struct picolcd_private_data { - usb_dev_handle *lcd; - int width; - int height; - int cellwidth; - int cellheight; - int key_timeout; - int contrast; - int backlight; - int brightness; - int offbrightness; - int keylights; - int key_light[KEYPAD_LIGHTS]; + USB_DEVICE_HANDLE *lcd; + int width; + int height; + int cellwidth; + int cellheight; + int contrast; + int backlight; + int brightness; + int offbrightness; + int keylights; + int key_light[KEYPAD_LIGHTS]; + int linklights; CGmode ccmode; char *info; unsigned char *framebuf; @@ -107,22 +101,46 @@ typedef struct picolcd_private_data { struct sockaddr_in lircserver; /* IR transcode results */ unsigned char result[512]; - unsigned char* resptr; + unsigned char *resptr; struct timeval lastmsg; int lastval; int flush_threshold; +#ifdef HAVE_LIBUSB_1_0 + /* Pointer to libusb 1.0 session */ + libusb_context *lib_ctx; + /* data buffer for the asynchronous USB transfer */ + unsigned char input_buffer[PICOLCD_MAX_DATA_LEN]; + /* structure for the details of the asynchronous USB transfer */ + struct libusb_transfer *input_transfer; + int libusb_status; + /* buffer for the key press data */ + keys key_buffer[KEY_BUFFER_SIZE]; + int key_read_index; /* Read index in the key_buffer */ + int key_write_index; /* Write index in the key_buffer */ + keys reported_keys; /* keys reported during last get_key */ + int key_repeat_delay; + int key_repeat_interval; + struct timeval *key_wait_time; +#else + int key_timeout; +#endif } PrivateData; /* Private function definitions */ -static void picolcd_send(usb_dev_handle *lcd, unsigned char *data, int size); -static void picolcd_20x2_write(usb_dev_handle *lcd, const int row, const int col, const unsigned char *data); -static void picolcd_20x4_write(usb_dev_handle *lcd, const int row, const int col, const unsigned char *data); +static void picolcd_send(USB_DEVICE_HANDLE *lcd, unsigned char *data, int size); +static void picolcd_20x2_write(USB_DEVICE_HANDLE *lcd, const int row, const int col, const unsigned char *data); +static void picolcd_20x4_write(USB_DEVICE_HANDLE *lcd, const int row, const int col, const unsigned char *data); static void picolcd_20x2_set_char(Driver *drvthis, int n, unsigned char *dat); static void picolcd_20x4_set_char(Driver *drvthis, int n, unsigned char *dat); -static void get_key_event(usb_dev_handle *lcd, lcd_packet *packet, int timeout); -static void set_key_lights(usb_dev_handle *lcd, int keys[], int state); +static void set_key_lights(USB_DEVICE_HANDLE *lcd, int keys[], int state); static void picolcd_lircsend(Driver *drvthis); static void ir_transcode(Driver *drvthis, unsigned char *data, unsigned int cbdata); +#ifdef HAVE_LIBUSB_1_0 +static void key_buffer_put(Driver *drvthis, unsigned char high_key, unsigned char low_key); +static void usb_cb_input(struct libusb_transfer *transfer); +#else +static void get_key_event(USB_DEVICE_HANDLE *lcd, lcd_packet *packet, int timeout); +#endif /** * Table describing various features of known picoLCD devices and pointers @@ -184,12 +202,16 @@ MODULE_EXPORT char *symbol_prefix = "picoLCD_"; * \retval 0 Success. * \retval <0 Error. */ -MODULE_EXPORT int picoLCD_init(Driver *drvthis) +MODULE_EXPORT int +picoLCD_init(Driver *drvthis) { PrivateData *p; - int x; +#ifdef HAVE_LIBUSB_1_0 + int error = 0; +#else struct usb_bus *bus; struct usb_device *dev; +#endif const char *lirchost; int lircport; int id; @@ -202,21 +224,110 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) if (drvthis->store_private_ptr(drvthis, p)) return -1; + p->lcd = NULL; + p->device = NULL; + +#ifdef HAVE_LIBUSB_1_0 +#ifndef USE_LIBUSB_SINGLE_SELECT + /* + * When using a single select statement libusb-1.0 should be + * initialised once for all usb drivers so code equivalent to that + * below should be somewhere common, before init_drivers() perhaps. + */ + error = libusb_init(&p->lib_ctx); + if (error) { + report(RPT_ERR, "%s: libusb_init error %d", drvthis->name, error); + return -1; + } + libusb_set_debug(p->lib_ctx, 3); +#endif + + p->libusb_status = LIBUSB_SUCCESS; + p->input_transfer = NULL; + p->key_read_index = 0; + p->key_write_index = 0; + + /* + * Try to find picolcd device the new way, this opens the first + * picoLCD found if you need to handle more than one picoLCD you'll + * need something more sophisticated. + */ + for (id = 0; picolcd_device_ids[id].device_name != NULL; id++) { + report(RPT_INFO, "%s: looking for device %s ", drvthis->name, + picolcd_device_ids[id].device_name); + p->lcd = libusb_open_device_with_vid_pid(p->lib_ctx, + picolcd_device_ids[id].vendor_id, + picolcd_device_ids[id].device_id); + if ((p->lcd) != NULL) { + p->device = &picolcd_device_ids[id]; + debug(RPT_INFO, "%s: opening device %s succeeded", drvthis->name, + picolcd_device_ids[id].device_name); + break; + } + } + if (p->lcd == NULL) { + report(RPT_ERR, "%s: no device found", drvthis->name); + return -1; + } + + if (libusb_kernel_driver_active(p->lcd, 0)) { + debug(RPT_DEBUG, "%s: libusb_kernel_driver_active returned true", drvthis->name); + error = libusb_detach_kernel_driver(p->lcd, 0); + if (error) { + report(RPT_ERR, "%s: libusb_detach_kernel_driver error %d", drvthis->name, error); + return -1; + } + } + else { + debug(RPT_DEBUG, "%s: libusb_kernel_driver_active returned false", drvthis->name); + } + error = libusb_claim_interface(p->lcd, 0); + if (error) { + report(RPT_ERR, "%s: libusb_claim_interface error %d", drvthis->name, error); + return -1; + } + /* + * FIXME: Is this the libusb-1.0 equivalent to if + * (usb_set_altinterface(p->lcd, 0) < 0) report(RPT_WARNING, "%s: + * unable to set alternate configuration", drvthis->name); I always + * get error -5 (LIBUSB_ERROR_NOT_FOUND the requested alternate + * setting does not exist) Is this needed? Has it ever worked? lsusb + * reports one configuration with one interface and no alternate + * settings. + */ + error = libusb_set_interface_alt_setting(p->lcd, 1, 0); + if (error) { + report(RPT_WARNING, "%s: libusb_set_interface_alt_setting error %d", drvthis->name, error); + } + p->input_transfer = libusb_alloc_transfer(0); + if (p->input_transfer == NULL) { + report(RPT_ERR, "%s: libusb_alloc_transfer failed", drvthis->name); + return -1; + } + libusb_fill_interrupt_transfer(p->input_transfer, p->lcd, LIBUSB_ENDPOINT_IN + 1, p->input_buffer, + sizeof(p->input_buffer), usb_cb_input, (void *)drvthis, 0); + error = libusb_submit_transfer(p->input_transfer); + if (error) { + report(RPT_ERR, "%s: libusb_submit_transfer error %d", drvthis->name, error); + libusb_free_transfer(p->input_transfer); + p->input_transfer = NULL; + return -1; + } + +#else /* The libusb 0.1 way */ + /* Try to find picolcd device */ usb_init(); usb_find_busses(); usb_find_devices(); - p->lcd = NULL; - p->device = NULL; - for (id = 0; picolcd_device_ids[id].device_name != NULL; ++id) { report(RPT_INFO, "%s: looking for device %s ", drvthis->name, picolcd_device_ids[id].device_name); for (bus = usb_get_busses(); bus != NULL; bus = bus->next) { for (dev = bus->devices; dev != NULL; dev = dev->next) { if ((dev->descriptor.idVendor == picolcd_device_ids[id].vendor_id) && - (dev->descriptor.idProduct == picolcd_device_ids[id].device_id)) { + (dev->descriptor.idProduct == picolcd_device_ids[id].device_id)) { report(RPT_INFO, "%s: found %s on bus %s device %s", drvthis->name, picolcd_device_ids[id].device_name, bus->dirname, dev->filename); @@ -228,7 +339,7 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) } } } - done: +done: if (p->lcd != NULL) { debug(RPT_DEBUG, "%s: opening device succeeded", drvthis->name); @@ -258,11 +369,12 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) report(RPT_ERR, "%s: no device found", drvthis->name); return -1; } +#endif /* HAVE_LIBUSB_1_0 */ - /* if the device has a init sequence sent it to device */ + /* if the device has a init sequence send it to device */ picolcd_send(p->lcd, p->device->initseq, PICOLCD_MAX_DATA_LEN); - p->width = p->device->width; + p->width = p->device->width; p->height = p->device->height; p->info = p->device->description; p->cellwidth = LCD_DEFAULT_CELLWIDTH; @@ -299,15 +411,50 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) /* Backlight and key lights enable/disable */ p->backlight = drvthis->config_get_bool(drvthis->name, "Backlight", 0, DEFAULT_BACKLIGHT); p->keylights = drvthis->config_get_bool(drvthis->name, "KeyLights", 0, DEFAULT_KEYLIGHTS); + p->linklights = drvthis->config_get_bool(drvthis->name, "LinkLights", 0, DEFAULT_LINKLIGHTS); /* allow individual lights to be set */ - for (x = 0; x < KEYPAD_LIGHTS; x++) { + for (tmp = 0; tmp < KEYPAD_LIGHTS; tmp++) { char configkey[32]; - sprintf(configkey, "Key%dLight", x); - p->key_light[x] = drvthis->config_get_bool(drvthis->name, configkey, 0, 1); + sprintf(configkey, "Key%dLight", tmp); + p->key_light[tmp] = drvthis->config_get_bool(drvthis->name, configkey, 0, 1); } +#ifdef HAVE_LIBUSB_1_0 + /* Aquire persistent time structure */ + p->key_wait_time = malloc(sizeof(struct timeval)); + if (p->key_wait_time == NULL) { + report(RPT_ERR, "%s: error allocating memory", drvthis->name); + return -1; + } + timerclear(p->key_wait_time); + + /* Get key auto repeat delay */ + tmp = drvthis->config_get_int(drvthis->name, "KeyRepeatDelay", 0, DEFAULT_REPEAT_DELAY); + if (tmp < 0 || tmp > 3000) { + report(RPT_WARNING, "%s: KeyRepeatDelay must be between 0-3000; using default %d", + drvthis->name, DEFAULT_REPEAT_DELAY); + tmp = DEFAULT_REPEAT_DELAY; + } + p->key_repeat_delay = tmp; + + /* Get key auto repeat interval */ + tmp = drvthis->config_get_int(drvthis->name, "KeyRepeatInterval", 0, DEFAULT_REPEAT_INTERVAL); + if (tmp < 0 || tmp > 3000) { + report(RPT_WARNING, "%s: KeyRepeatInterval must be between 0-3000; using default %d", + drvthis->name, DEFAULT_REPEAT_INTERVAL); + tmp = DEFAULT_REPEAT_INTERVAL; + } + p->key_repeat_interval = tmp; + + report(RPT_WARNING, "%s: Key repeat: delay %d, interval %d", + drvthis->name, p->key_repeat_delay, p->key_repeat_interval); + + p->reported_keys.high_key = 0; + p->reported_keys.low_key = 0; + +#else /* Get Timeout for USB read of key presses */ tmp = drvthis->config_get_int(drvthis->name, "KeyTimeout", 0, DEFAULT_TIMEOUT); if ((tmp < 0) || (tmp > 1000)) { @@ -316,6 +463,7 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) tmp = DEFAULT_TIMEOUT; } p->key_timeout = tmp; +#endif /* Allocate and clear frame buffers */ p->framebuf = (unsigned char *) malloc(p->width * p->height + 1); @@ -357,7 +505,7 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) /* Prevent small 'foolish' values these will disable the check also! */ if (p->flush_threshold < 16) { report(RPT_WARNING, "%s: flush threshold to small - disabled"); - tmp = 0x8000; /* Disabled, send check will always fail! */ + tmp = 0x8000; /* Disabled, send check will always fail! */ } else if (p->flush_threshold > 0x7FFF) { report(RPT_WARNING, "%s: flush threshold to large, using default"); @@ -365,11 +513,12 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) } p->flush_threshold = tmp; - /* Simulate that the last value send was a PULSE, - * so we start with sending a SPACE to make LIRC happy + /* + * Simulate that the last value send was a PULSE, so we start with + * sending a SPACE to make LIRC happy */ p->lastval = 0; - p->resptr = p->result; + p->resptr = p->result; gettimeofday(&p->lastmsg, NULL); if (p->IRenabled) { @@ -377,7 +526,7 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) struct hostent *hostinfo = gethostbyname(lirchost); if (hostinfo == NULL) { - report (RPT_ERR, "%s: unknown LIRC host %s", drvthis->name, lirchost); + report(RPT_ERR, "%s: unknown LIRC host %s", drvthis->name, lirchost); return -1; } if ((p->lircsock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { @@ -411,12 +560,65 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) * Close the driver (do necessary clean-up). * \param drvthis Pointer to driver structure. */ -MODULE_EXPORT void picoLCD_close(Driver *drvthis) +MODULE_EXPORT void +picoLCD_close(Driver *drvthis) { PrivateData *p = drvthis->private_data; + if (p != NULL) { +#ifdef HAVE_LIBUSB_1_0 + int error; - usb_release_interface(p->lcd, 0); - usb_close(p->lcd); + if (p->input_transfer != NULL) { + /* Need to cancel transfer before it is freed */ + libusb_cancel_transfer(p->input_transfer); + while (p->libusb_status != LIBUSB_TRANSFER_CANCELLED) { + struct timeval timeout; + /* + * Wait for the cancellation to complete, the + * call-back will then have freed the + * transfer. + */ + report(RPT_INFO, "%s: waiting for usb transfer to be cancelled", drvthis->name); + timeout.tv_sec = 1; + timeout.tv_usec = 0; + libusb_handle_events_timeout(p->lib_ctx, &timeout); + } + } + + error = libusb_release_interface(p->lcd, 0); + if (error) { + report(RPT_ERR, "%s: usb_release_interface error %d", drvthis->name, error); + } + + /* FIXME: Does it make sense to re-attach a kernel driver? */ + error = libusb_attach_kernel_driver(p->lcd, 0); + if (error) { + report(RPT_ERR, "%s: libusb_attach_kernel_driver error %d", drvthis->name, error); + } + + libusb_close(p->lcd); + if (p->key_wait_time != NULL) + free(p->key_wait_time); +#ifndef USE_LIBUSB_SINGLE_SELECT + /* + * When using a single select statement the code below should + * be called after closing all open devices and before the + * application terminates this should be somewhere common, + * after drivers_unload_all() perhaps. + */ + libusb_exit(p->lib_ctx); +#endif +#else /* The libusb 0.1 way */ + usb_release_interface(p->lcd, 0); + usb_close(p->lcd); +#endif + if (p->framebuf != NULL) + free(p->framebuf); + if (p->lstframe != NULL) + free(p->lstframe); + free(p); + } + drvthis->store_private_ptr(drvthis, NULL); debug(RPT_DEBUG, "%s: close complete", drvthis->name); } @@ -429,7 +631,8 @@ MODULE_EXPORT void picoLCD_close(Driver *drvthis) * \param drvthis Pointer to driver structure. * \return Number of characters the display is wide. */ -MODULE_EXPORT int picoLCD_width(Driver *drvthis) +MODULE_EXPORT int +picoLCD_width(Driver *drvthis) { PrivateData *p = drvthis->private_data; @@ -442,7 +645,8 @@ MODULE_EXPORT int picoLCD_width(Driver *drvthis) * \param drvthis Pointer to driver structure. * \return Number of characters the display is high. */ -MODULE_EXPORT int picoLCD_height(Driver *drvthis) +MODULE_EXPORT int +picoLCD_height(Driver *drvthis) { PrivateData *p = drvthis->private_data; @@ -454,7 +658,8 @@ MODULE_EXPORT int picoLCD_height(Driver *drvthis) * Clear the screen. * \param drvthis Pointer to driver structure. */ -MODULE_EXPORT void picoLCD_clear(Driver *drvthis) +MODULE_EXPORT void +picoLCD_clear(Driver *drvthis) { PrivateData *p = drvthis->private_data; @@ -469,21 +674,22 @@ MODULE_EXPORT void picoLCD_clear(Driver *drvthis) * Flush data on screen to the display. * \param drvthis Pointer to driver structure. */ -MODULE_EXPORT void picoLCD_flush(Driver *drvthis) +MODULE_EXPORT void +picoLCD_flush(Driver *drvthis) { - PrivateData *p = drvthis->private_data; + PrivateData *p = drvthis->private_data; unsigned char *fb = p->framebuf; unsigned char *lf = p->lstframe; - static unsigned char text[48]; - int i, line, offset; + static unsigned char text[48]; + int i, line, offset; debug(RPT_DEBUG, "%s: flush started", drvthis->name); for (line = 0; line < p->height; line++) { memset(text, 0, sizeof(text)); offset = line * p->width; - fb = p->framebuf + offset; - lf = p->lstframe + offset; + fb = p->framebuf + offset; + lf = p->lstframe + offset; for (i = 0; i < p->width; i++) { if (*fb++ != *lf++) { @@ -512,25 +718,27 @@ MODULE_EXPORT void picoLCD_flush(Driver *drvthis) * \param y Vertical character position (row). * \param string String that gets written. */ -MODULE_EXPORT void picoLCD_string(Driver *drvthis, int x, int y, unsigned char string[]) +MODULE_EXPORT void +picoLCD_string(Driver *drvthis, int x, int y, unsigned char string[]) { PrivateData *p = drvthis->private_data; unsigned char *dest; - int len; + int len; debug(RPT_DEBUG, "%s: string start (%s)", drvthis->name, string); if ((y < 1) || (y > p->height) || (x < 1) || (x > p->width)) return; - x--; y--; /* Convert 1-based to 0-based */ + x--; + y--; /* Convert 1-based to 0-based */ len = strlen((char *)string); if (len + x > p->width) { debug(RPT_DEBUG, "%s: string overlength (>%d). Start: %d Length: %d (%s)", drvthis->name, p->width, x, len, string); - len = p->width - x; /* Copy what we can */ + len = p->width - x; /* Copy what we can */ } dest = p->framebuf + (y * p->width + x); @@ -548,7 +756,8 @@ MODULE_EXPORT void picoLCD_string(Driver *drvthis, int x, int y, unsigned char s * \param y Vertical character position (row). * \param c Character that gets written. */ -MODULE_EXPORT void picoLCD_chr(Driver *drvthis, int x, int y, unsigned char c) +MODULE_EXPORT void +picoLCD_chr(Driver *drvthis, int x, int y, unsigned char c) { PrivateData *p = drvthis->private_data; @@ -557,7 +766,8 @@ MODULE_EXPORT void picoLCD_chr(Driver *drvthis, int x, int y, unsigned char c) if ((y < 1) || (y > p->height) || (x < 1) || (x > p->width)) return; - x--; y--; /* Convert 1-based to 0-based */ + x--; + y--; /* Convert 1-based to 0-based */ /* * Map NUL to character 8 to avoid problems with string handling @@ -573,6 +783,7 @@ MODULE_EXPORT void picoLCD_chr(Driver *drvthis, int x, int y, unsigned char c) debug(RPT_DEBUG, "%s: chr complete (%c)", drvthis->name, c); } + /* lcd_logical_driver User-defined character functions */ /** @@ -584,7 +795,8 @@ MODULE_EXPORT void picoLCD_chr(Driver *drvthis, int x, int y, unsigned char c) * The bits in each byte represent the pixels where the LSB * (least significant bit) is the rightmost pixel in each pixel row. */ -MODULE_EXPORT void picoLCD_set_char (Driver *drvthis, int n, unsigned char *dat) +MODULE_EXPORT void +picoLCD_set_char(Driver *drvthis, int n, unsigned char *dat) { PrivateData *p = drvthis->private_data; @@ -597,11 +809,13 @@ MODULE_EXPORT void picoLCD_set_char (Driver *drvthis, int n, unsigned char *dat) * \param drvthis Pointer to driver structure. * \return Number of custom characters (always NUM_CCs). */ -MODULE_EXPORT int picoLCD_get_free_chars (Driver *drvthis) +MODULE_EXPORT int +picoLCD_get_free_chars(Driver *drvthis) { return NUM_CCs; } + /* lcd_logical_driver Extended output functions */ /** @@ -613,7 +827,8 @@ MODULE_EXPORT int picoLCD_get_free_chars (Driver *drvthis) * \param promille Current height level of the bar in promille. * \param options Options (currently unused). */ -MODULE_EXPORT void picoLCD_vbar (Driver *drvthis, int x, int y, int len, int promille, int options) +MODULE_EXPORT void +picoLCD_vbar(Driver *drvthis, int x, int y, int len, int promille, int options) { PrivateData *p = drvthis->private_data; @@ -624,7 +839,7 @@ MODULE_EXPORT void picoLCD_vbar (Driver *drvthis, int x, int y, int len, int pro if (p->ccmode != standard) { /* Not supported(yet) */ report(RPT_WARNING, "%s: vbar: cannot combine two modes using user-defined characters", - drvthis->name); + drvthis->name); return; } p->ccmode = vbar; @@ -632,7 +847,7 @@ MODULE_EXPORT void picoLCD_vbar (Driver *drvthis, int x, int y, int len, int pro memset(vBar, 0x00, sizeof(vBar)); for (i = 1; i < p->cellheight; i++) { - // add pixel line per pixel line ... + /* add pixel line per pixel line ... */ vBar[p->cellheight - i] = 0xFF; picoLCD_set_char(drvthis, i, vBar); } @@ -651,7 +866,8 @@ MODULE_EXPORT void picoLCD_vbar (Driver *drvthis, int x, int y, int len, int pro * \param promille Current length level of the bar in promille. * \param options Options (currently unused). */ -MODULE_EXPORT void picoLCD_hbar (Driver *drvthis, int x, int y, int len, int promille, int options) +MODULE_EXPORT void +picoLCD_hbar(Driver *drvthis, int x, int y, int len, int promille, int options) { PrivateData *p = drvthis->private_data; @@ -662,7 +878,7 @@ MODULE_EXPORT void picoLCD_hbar (Driver *drvthis, int x, int y, int len, int pro if (p->ccmode != standard) { /* Not supported(yet) */ report(RPT_WARNING, "%s: hbar: cannot combine two modes using user-defined characters", - drvthis->name); + drvthis->name); return; } @@ -672,7 +888,7 @@ MODULE_EXPORT void picoLCD_hbar (Driver *drvthis, int x, int y, int len, int pro for (i = 1; i <= p->cellwidth; i++) { /* fill pixel columns from left to right. */ - memset(hBar, 0xFF & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar)); + memset(hBar, 0x1F & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar)); picoLCD_set_char(drvthis, i, hBar); } } @@ -687,7 +903,8 @@ MODULE_EXPORT void picoLCD_hbar (Driver *drvthis, int x, int y, int len, int pro * \param x Horizontal character position (column). * \param num Character to write (0 - 10 with 10 representing ':') */ -MODULE_EXPORT void picoLCD_num (Driver *drvthis, int x, int num) +MODULE_EXPORT void +picoLCD_num(Driver *drvthis, int x, int num) { PrivateData *p = drvthis->private_data; int do_init = 0; @@ -699,7 +916,7 @@ MODULE_EXPORT void picoLCD_num (Driver *drvthis, int x, int num) if (p->ccmode != standard) { /* Not supported (yet) */ report(RPT_WARNING, "%s: num: cannot combine two modes using user-defined characters", - drvthis->name); + drvthis->name); return; } @@ -722,15 +939,18 @@ MODULE_EXPORT void picoLCD_num (Driver *drvthis, int x, int num) * \retval 0 Icon has been successfully defined/written. * \retval <0 Server core shall define/write the icon. */ -MODULE_EXPORT int picoLCD_icon (Driver *drvthis, int x, int y, int icon) +MODULE_EXPORT int +picoLCD_icon(Driver *drvthis, int x, int y, int icon) { + PrivateData *p = drvthis->private_data; + static unsigned char heart_open[] = { b_______, b___X_X_, b__X_X_X, b__X___X, - b__XX_XX, + b__X___X, b___X_X_, b____X__, b_______ @@ -784,38 +1004,59 @@ MODULE_EXPORT int picoLCD_icon (Driver *drvthis, int x, int y, int icon) b_______ }; + /* Icons from the display's character map ROM */ switch (icon) { - case ICON_BLOCK_FILLED: - picoLCD_chr(drvthis, x, y, 255); - break; - case ICON_HEART_FILLED: - picoLCD_set_char(drvthis, 0, heart_filled); - picoLCD_chr(drvthis, x, y, 0); - break; - case ICON_HEART_OPEN: - picoLCD_set_char(drvthis, 0, heart_open); - picoLCD_chr(drvthis, x, y, 0); - break; - case ICON_ARROW_LEFT: - picoLCD_chr(drvthis, x, y, 127); - break; - case ICON_ARROW_RIGHT: - picoLCD_chr(drvthis, x, y, 126); - break; - case ICON_CHECKBOX_GRAY: - picoLCD_set_char(drvthis, 5, checkbox_gray); - picoLCD_chr(drvthis, x, y, 5); - break; - case ICON_CHECKBOX_ON: - picoLCD_set_char(drvthis, 6, checkbox_on); - picoLCD_chr(drvthis, x, y, 6); - break; - case ICON_CHECKBOX_OFF: - picoLCD_set_char(drvthis, 7, checkbox_off); - picoLCD_chr(drvthis, x, y, 7); - break; - default: - return -1; /* Let the core do other icons */ + case ICON_BLOCK_FILLED: + picoLCD_chr(drvthis, x, y, 255); + return 0; + case ICON_ARROW_LEFT: + picoLCD_chr(drvthis, x, y, 127); + return 0; + case ICON_ARROW_RIGHT: + picoLCD_chr(drvthis, x, y, 126); + return 0; + } + + /* + * Icons loaded to the display's custom character RAM. Horizontal & + * vertical bars do not use RAM address zero so the heart can be used + * in these modes. Big numbers use all eight locations. + */ + if ((p->ccmode != custom) + && !((p->ccmode == hbar || p->ccmode == vbar) + && (icon == ICON_HEART_FILLED || icon == ICON_HEART_OPEN))) { + if (p->ccmode != standard) { + /* Combined custom character modes not supported */ + report(RPT_WARNING, "%s: icon: cannot combine two modes using user-defined characters", + drvthis->name); + return -1; /* Let the core do the icon */ + } + p->ccmode = custom; + } + + switch (icon) { + case ICON_HEART_FILLED: + picoLCD_set_char(drvthis, 0, heart_filled); + picoLCD_chr(drvthis, x, y, 0); + break; + case ICON_HEART_OPEN: + picoLCD_set_char(drvthis, 0, heart_open); + picoLCD_chr(drvthis, x, y, 0); + break; + case ICON_CHECKBOX_GRAY: + picoLCD_set_char(drvthis, 5, checkbox_gray); + picoLCD_chr(drvthis, x, y, 5); + break; + case ICON_CHECKBOX_ON: + picoLCD_set_char(drvthis, 6, checkbox_on); + picoLCD_chr(drvthis, x, y, 6); + break; + case ICON_CHECKBOX_OFF: + picoLCD_set_char(drvthis, 7, checkbox_off); + picoLCD_chr(drvthis, x, y, 7); + break; + default: + return -1; /* Let the core do other icons */ } return 0; } @@ -825,15 +1066,137 @@ MODULE_EXPORT int picoLCD_icon (Driver *drvthis, int x, int y, int icon) /** * Handle input from keyboard. + * + * Key events come back in such a way as to report up to two simultaneous keys + * pressed. The highest numbered key always comes back as the first key and + * the lower numbered key follows. If only one key was pressed, the second + * key is 0. The picoLCD also sends key-up events (both key parameters zero). + * + * On a single key press, the return is . The key-up event is a + * read that returns <0, 0> (all clear). On a dual key press, if one key is + * released later than the other key, the first key-up event is . This will be followed by a final "all clear" key-up <0, 0>. If both + * keys are released simultaneously, then after , you will + * receive <0, 0>. If the keys are pressed down in a staggered fashion, you + * will receive followed by followed by + * key-up events as already detailed. + * + * For keymapping see the picolcd_device structs. + * * \param drvthis Pointer to driver structure. * \return String representation of the key; * \c NULL if nothing available / unmapped key */ -MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) +MODULE_EXPORT char * +picoLCD_get_key(Driver *drvthis) { PrivateData *p = drvthis->private_data; - lcd_packet keydata; char *keystr = NULL; + +#ifdef HAVE_LIBUSB_1_0 + int high_key; + int low_key; + struct timeval current_time, delay_time; + +#ifndef USE_LIBUSB_SINGLE_SELECT + /* + * When using a single select statement polling for libusb-1.0 events + * needs to be integrated into the LCDd main loop. See: + * http://libusb.sourceforge.net/api-1.0/group__poll.html#pollmain + */ + struct timeval timeout; + /* + * FIXME: It is not efficient to call this at 32Hz, it is only needed + * if a key has been pressed or IR data has been received. Process + * any outstanding USB events for our session. + */ + timeout.tv_sec = 0; + timeout.tv_usec = 0; + libusb_handle_events_timeout(p->lib_ctx, &timeout); +#endif + + /* + * Read any key events from the buffer and report, do not wait for + * key up events so that the main loop timing is not disrupted; thus + * the behaviour is somewhat different from the previous version. + */ + if (p->key_read_index == p->key_write_index) { + /* No new key, check if it is time to repeat a key */ + if (p->reported_keys.high_key && timerisset(p->key_wait_time)) { + gettimeofday(¤t_time, NULL); + + /* Time for repeated key report? */ + if (timercmp(¤t_time,p->key_wait_time,>)) { + high_key = p->reported_keys.high_key; + low_key = p->reported_keys.low_key; + + /* Set timer for next key */ + delay_time.tv_sec = p->key_repeat_interval / 1000; + delay_time.tv_usec = (p->key_repeat_interval % 1000) * 1000; + timeradd(¤t_time,&delay_time,p->key_wait_time); + } + else { + return NULL; + } + } + else { + return NULL; + } + } + else { + /* Get new key data */ + high_key = p->key_buffer[p->key_read_index].high_key; + low_key = p->key_buffer[p->key_read_index].low_key; + debug(RPT_DEBUG, "%s: got %d, %d from key_buffer %d", + drvthis->name, high_key, low_key, p->key_read_index); + + /* Advance read buffer, wrapping around if necessary */ + p->key_read_index++; + if (KEY_BUFFER_SIZE <= p->key_read_index) + p->key_read_index = 0; + + /* Store the reported keys for repeat */ + p->reported_keys.high_key = high_key; + p->reported_keys.low_key = low_key; + /* Set the time for repeated key press if enabled */ + if (p->key_repeat_delay > 0) { + gettimeofday(¤t_time, NULL); + delay_time.tv_sec = p->key_repeat_interval / 1000; + delay_time.tv_usec = (p->key_repeat_interval % 1000) * 1000; + timeradd(¤t_time,&delay_time,p->key_wait_time); + } + } + + if (low_key) { /* Two keys have been pressed */ + static char keybuf[2 * KEYPAD_LABEL_MAX + 1]; + /* + * The order here is important for clients that are + * interested in multi-key presses. The key pairs are + * reported in the opposite order to their position in the + * key-map thus if a client wants to be informed when keys F1 + * & F2 are both pressed it will have to send the command + * "client_add_key [-exclusively|-shared] F2+F1". It would be + * more logical to change the order but this is consistent + * with the previous version. + */ + sprintf(keybuf, "%s+%s", p->device->keymap[high_key], + p->device->keymap[low_key]); + keystr = keybuf; + } + else { /* Only one key pressed */ + keystr = p->device->keymap[high_key]; + } + + debug(RPT_DEBUG, "%s: get_key complete (%s)", drvthis->name, keystr); + + if ((keystr != NULL) && (strlen(keystr) > 0)) + return keystr; + else + return NULL; + +#else /* the libusb 0.1 way */ + + lcd_packet keydata; int keys_read = 0; int key_pass = 0; int two_keys = 0; @@ -841,6 +1204,10 @@ MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) debug(RPT_DEBUG, "%s: get_key start (timeout %d)", drvthis->name, p->key_timeout); + /* + * Due to how key events are reported, we need to keep reading key + * presses until we get the all clear (all keys up) event. + */ while (! keys_read) { get_key_event(p->lcd, &keydata, p->key_timeout); debug(RPT_DEBUG, "%s: get_key got an event", drvthis->name); @@ -895,33 +1262,7 @@ MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) return keystr; return NULL; - -/* - * Due to how key events are reported, we need to keep reading key presses - * until we get the all clear (all keys up) event. - * - * Key events come back in such a way to report up to two simultanious keys - * pressed. The highest numbered key always comes back as the first key and - * the lower numbered key follows. If only one key was pressed, the second - * key is 0. I will refer to a key event as: . - * - * The picoLCD also sends key-up events. - * - * On a single key press, the return is . The key-up event is a - * read that returns <0, 0> (all clear). On a dual key press, if one key is - * released later than the other key, the first key-up event is - * . This will be followed by a final "all clear" key-up - * <0, 0>. If both keys are release simultaniously, then after , - * you will receive <0, 0>. If the keys are pressed down in a staggard - * fashion, you will receive followed by followed by - * key-up events as already detailed. - * - * What this means is that we need to keep reading key presses until we get - * the <0, 0> all clear. - * - * For keymapping see the picolcd_device structs. - */ - +#endif } /* lcd_logical_driver Hardware functions */ @@ -933,7 +1274,8 @@ MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) * \param drvthis Pointer to driver structure. * \return Stored contrast in promille. */ -MODULE_EXPORT int picoLCD_get_contrast(Driver *drvthis) +MODULE_EXPORT int +picoLCD_get_contrast(Driver *drvthis) { PrivateData *p = drvthis->private_data; @@ -946,23 +1288,26 @@ MODULE_EXPORT int picoLCD_get_contrast(Driver *drvthis) * \param drvthis Pointer to driver structure. * \param promille New contrast value in promille. */ -MODULE_EXPORT void picoLCD_set_contrast(Driver *drvthis, int promille) +MODULE_EXPORT void +picoLCD_set_contrast(Driver *drvthis, int promille) { PrivateData *p = drvthis->private_data; - unsigned char packet[2] = { 0x92 }; /* set contrast id */ + unsigned char packet[2] = {0x92}; /* set contrast id */ /* - * Higher values for promille will result in less contrast. So reverse - * the meaning of promille. + * Higher values for promille will result in less contrast. So + * reverse the meaning of promille. */ if (promille > 0 && promille <= 1000) { p->contrast = promille; - if (p->device->contrast_max == 1) - packet[1] = 0x00; /* picoLCD20x4 permits contrast as 0/1 value */ + if (p->device->contrast_max == 1) { + /* picoLCD20x4 permits contrast as 0/1 value */ + packet[1] = 0x00; + } else { int inv = 1000 - promille; - packet[1] = inv * p->device->contrast_max / 1000; + packet[1] = inv * p->device->contrast_max / 1000; } } else if (promille > 1000) { /* Should not really happen */ @@ -984,7 +1329,8 @@ MODULE_EXPORT void picoLCD_set_contrast(Driver *drvthis, int promille) * \param state Brightness state (on/off) for which we want the value. * \return Stored brightness in promille. */ -MODULE_EXPORT int picoLCD_get_brightness(Driver *drvthis, int state) +MODULE_EXPORT int +picoLCD_get_brightness(Driver *drvthis, int state) { PrivateData *p = drvthis->private_data; @@ -998,7 +1344,8 @@ MODULE_EXPORT int picoLCD_get_brightness(Driver *drvthis, int state) * \param state Brightness state (on/off) for which we want to store the value. * \param promille New brightness in promille. */ -MODULE_EXPORT void picoLCD_set_brightness(Driver *drvthis, int state, int promille) +MODULE_EXPORT void +picoLCD_set_brightness(Driver *drvthis, int state, int promille) { PrivateData *p = drvthis->private_data; @@ -1017,10 +1364,11 @@ MODULE_EXPORT void picoLCD_set_brightness(Driver *drvthis, int state, int promil * \param drvthis Pointer to driver structure. * \param state New backlight status. */ -MODULE_EXPORT void picoLCD_backlight(Driver *drvthis, int state) +MODULE_EXPORT void +picoLCD_backlight(Driver *drvthis, int state) { PrivateData *p = drvthis->private_data; - unsigned char packet[2] = { 0x91 }; /* set backlight id */ + unsigned char packet[2] = {0x91}; /* set backlight id */ int s; if (state == BACKLIGHT_ON) { @@ -1029,9 +1377,11 @@ MODULE_EXPORT void picoLCD_backlight(Driver *drvthis, int state) s = p->device->bklight_max; packet[1] = (unsigned char) s; picolcd_send(p->lcd, packet, 2); - /* Only enable key lights if enabled by user */ - if (p->keylights) - set_key_lights(p->lcd, p->key_light, state); + if (p->linklights) { + /* Only enable key lights if enabled by user */ + if (p->keylights) + set_key_lights(p->lcd, p->key_light, state); + } } else if (state == BACKLIGHT_OFF) { s = p->offbrightness / 10; @@ -1039,8 +1389,10 @@ MODULE_EXPORT void picoLCD_backlight(Driver *drvthis, int state) s = p->device->bklight_min; packet[1] = (unsigned char) s; picolcd_send(p->lcd, packet, 2); - /* Always turn ley lights off */ - set_key_lights(p->lcd, p->key_light, state); + if (p->linklights) { + /* Always turn key lights off */ + set_key_lights(p->lcd, p->key_light, state); + } } } @@ -1050,7 +1402,8 @@ MODULE_EXPORT void picoLCD_backlight(Driver *drvthis, int state) * \param drvthis Pointer to driver structure. * \param state Integer with bits representing port states. */ -MODULE_EXPORT void picoLCD_output(Driver *drvthis, int state) +MODULE_EXPORT void +picoLCD_output(Driver *drvthis, int state) { PrivateData *p = drvthis->private_data; int x; @@ -1070,7 +1423,8 @@ MODULE_EXPORT void picoLCD_output(Driver *drvthis, int state) * \param drvthis Pointer to driver structure. * \return Constant string with information. */ -MODULE_EXPORT char *picoLCD_get_info(Driver *drvthis) +MODULE_EXPORT char * +picoLCD_get_info(Driver *drvthis) { PrivateData *p = drvthis->private_data; @@ -1092,24 +1446,25 @@ MODULE_EXPORT char *picoLCD_get_info(Driver *drvthis) * * \param drvthis Pointer to driver structure [used for debug() and report()]. * \param data Buffer of integers to be transcoded. - * \param cbdata Buffer of integers to be transcoded. + * \param cbdata Length of data to be transcoded. * * \note The picoLCD introduces two issues: - * \note 1. Every read contains a maximum of 10 samples (20 bytes), - * sending the converted samples direct to LIRC will lead to timeouts, - * in LIRC while we are still waiting for the rest of the samples. - * To fix this I queue the samples and send it when a sync is detected or by a timeout. - * \note 2. The sync (long space) are not send by the picoLCD. - * To fix this we look for a pulse at the end of the last message and a pulse at the - * begin new message, we then flush the queue and start with a (sync) space, with - * the duration of the time between the last and current message. - * - * \note To make LIRC happy I send the queued samples with the sync space a the begin, - * and not at the end (the next 'calculated' sync is put at the begin of the next message), - * this is because LIRC requires a space at the begin but will solves the missing space - * with a timeout at the end. + * \note 1. Every read contains a maximum of 10 samples (20 bytes), sending the + * converted samples direct to LIRC will lead to timeouts, in LIRC + * while we are still waiting for the rest of the samples. To fix this I + * queue the samples and send it when a sync is detected or by a timeout. + * \note 2. The sync (long space) are not send by the picoLCD. To fix this we + * look for a pulse at the end of the last message and a pulse at the + * begin new message, we then flush the queue and start with a (sync) + * space, with the duration of the time between the last and current + * message. + * \note To make LIRC happy I send the queued samples with the sync space a the + * begin, and not at the end (the next 'calculated' sync is put at the + * begin of the next message), this is because LIRC requires a space at + * the begin but will solves the missing space with a timeout at the end. */ -static void ir_transcode(Driver *drvthis, unsigned char* data, unsigned int cbdata) +static void +ir_transcode(Driver *drvthis, unsigned char *data, unsigned int cbdata) { PrivateData *p = drvthis->private_data; int i; @@ -1122,34 +1477,45 @@ static void ir_transcode(Driver *drvthis, unsigned char* data, unsigned int cbda return; } - /* Get time needed to calculate the time between 2 ir data messages */ + /* Get time needed to calculate the time between 2 IR data messages */ gettimeofday(&now, 0); /* Check for a missing SPACE since the last message */ - debug(RPT_INFO, "picolcd: last 0x04x first %04x", p->lastval, (-w & 0xFFFF)); + debug(RPT_INFO, "picolcd: last %04x first %04x", p->lastval, (-w & 0xFFFF)); if (((p->lastval & 0x8000) == 0) && ((-w & 0x8000) == 0)) { - /* Calculate the time passed from the last ir message to now - * and use that time for the missing space (sync) */ - int secs = now.tv_sec - p->lastmsg.tv_sec; + /* + * Calculate the time passed from the last IR message to now + * and use that time for the missing space (sync) + */ + struct timeval time_gap; int gap = 0x7FFF; - /* previous message is complete send it, without the added space */ + timersub(&now, &p->lastmsg, &time_gap); + + /* previous message is complete send it without the added space */ debug(RPT_INFO, "picolcd: missing sync detected, flushing queue before adding sync"); picolcd_lircsend(drvthis); - /* Prevent the overflow (2 secs = 32678 jiffies), but allow 2.99 seconds to reach the max */ - if (secs <= 2) { - /* microseconds to jiffies (same as (16384/1000000) but no possible int32 overflow) */ - gap = ((now.tv_usec - p->lastmsg.tv_usec + secs * 1000000) * 256) / 15625; - /* Check overflow */ - if (gap >= 0x8000) { - gap = 0x7FFF; - } + /* + * Prevent the overflow (2 secs = 32678 jiffies), but allow + * 2.99 seconds to reach the max + */ + if (2 <= time_gap.tv_sec) { + /* + * microseconds to jiffies (same as (16384/1000000) + * but no possible int32 overflow) + */ + gap = ((time_gap.tv_sec * 1000000 + time_gap.tv_usec) * 256) / 15625; + } + + /* Saturate on 15 bit overflow */ + if (gap >= 0x8000) { + gap = 0x7FFF; } /* Make it a space */ gap |= 0x8000; - debug(RPT_INFO, "picolcd: injecting space %04hx between %04hx and %04hx", + debug(RPT_INFO, "picolcd: injecting space %04x between %04x and %04x", gap, p->lastval, -w & 0xFFFF); *p->resptr++ = (unsigned char)(gap & 0xff); *p->resptr++ = (unsigned char)((gap >> 8) & 0xff); @@ -1168,7 +1534,7 @@ static void ir_transcode(Driver *drvthis, unsigned char* data, unsigned int cbda /* IF w is negative THEN negate. E.g. 0xDCA1 (-9055) -> 9055. */ w = 0x10000 - w; /* scale: orig is usec, new is jiffy. E.g. 9055usec = 148 jiffy. */ - w = (w * 16384/ 1000000) & 0xFFFF; + w = (w * 16384 / 1000000) & 0xFFFF; } else { /* Scale */ @@ -1185,7 +1551,10 @@ static void ir_transcode(Driver *drvthis, unsigned char* data, unsigned int cbda } p->lastval = w; p->lastmsg = now; - /* Look for a short buffer (a full buffer has 10 samples) with a terminal PULSE */ + /* + * Look for a short buffer (a full buffer has 10 samples) with a + * terminal PULSE + */ if ((cIntervals < 10) && ((w & 0x8000) == 0)) { debug(RPT_INFO, "picolcd: IR data end detected sending lirc data now"); picolcd_lircsend(drvthis); @@ -1196,18 +1565,20 @@ static void ir_transcode(Driver *drvthis, unsigned char* data, unsigned int cbda * Send any queued IR samples to LIRC * \param drvthis Pointer to driver structure. */ -static void picolcd_lircsend(Driver *drvthis) +static void +picolcd_lircsend(Driver *drvthis) { PrivateData *p = drvthis->private_data; int len = p->resptr - p->result; if (len > 0) { #ifdef DEBUG - debug(RPT_INFO, "picolcd: sending LIRCD %d samples", len/2); + debug(RPT_INFO, "picolcd: sending LIRCD %d samples", len / 2); { unsigned char *ptr = p->result; unsigned char *endptr = ptr + len; - char logbuf[sizeof(p->result) * 3]; /* every 2 bytes become 5 bytes " xxxx" */ - char* logptr = logbuf; + /* every 2 bytes become 5 bytes " xxxx" */ + char logbuf[sizeof(p->result) * 3]; + char *logptr = logbuf; while (ptr < endptr) { unsigned int val = *ptr++; val |= *ptr++ << 8; @@ -1217,13 +1588,14 @@ static void picolcd_lircsend(Driver *drvthis) } #endif if (sendto(p->lircsock, p->result, len, 0, - (struct sockaddr *) &(p->lircserver), sizeof(p->lircserver)) == -1) { + (struct sockaddr *)&(p->lircserver), sizeof(p->lircserver)) == -1) { /* Ignore not connected errors when lirc has gone away */ if (errno != ECONNREFUSED) { report(RPT_WARNING, "picolcd: failed to send IR data, reason: %s", strerror(errno)); } - } else { - debug(RPT_DEBUG, "picolcd: send %d bytes to lirc(udp)", len); + } + else { + debug(RPT_DEBUG, "picolcd: sent %d bytes to lirc(udp)", len); } p->resptr = p->result; } @@ -1236,32 +1608,45 @@ static void picolcd_lircsend(Driver *drvthis) * \param data pointer to data packet to send * \param size number of bytes to send */ -static void picolcd_send(usb_dev_handle *lcd, unsigned char *data, int size) +static void +picolcd_send(USB_DEVICE_HANDLE * lcd, unsigned char *data, int size) { if ((lcd == NULL) && (data == NULL)) return; - - usb_interrupt_write(lcd, USB_ENDPOINT_OUT + 1, (char *) data, size, 1000); +#ifdef HAVE_LIBUSB_1_0 + int error = 0; + int transferred = 0; + unsigned int timeout = 1000; /* milliseconds */ + error = libusb_interrupt_transfer(lcd, LIBUSB_ENDPOINT_OUT + 1, data, size, &transferred, timeout); + if (error) { + /* can't use report here */ + fprintf(stderr, "libusb_interrupt_transfer error %d, sent %d of %d bytes\n", + error, transferred, size); + } +#else + usb_interrupt_write(lcd, USB_ENDPOINT_OUT + 1, (char *)data, size, 1000); +#endif } /** - * Write function for 20x4 desktop displays + * Write function for 20x4 desktop displays. * \param lcd pointer to device handle * \param row Row to place the string at * \param col ignored * \param data pointer to NUL terminated string */ -static void picolcd_20x4_write(usb_dev_handle *lcd, const int row, const int col, const unsigned char *data) +static void +picolcd_20x4_write(USB_DEVICE_HANDLE * lcd, const int row, const int col, const unsigned char *data) { - unsigned char packet[64] = { 0x95, 0x01, 0x00, 0x01 }; + unsigned char packet[64] = {0x95, 0x01, 0x00, 0x01}; unsigned char lineset[4][6] = { - { 0x94, 0x00, 0x01, 0x00, 0x64, 0x80 }, - { 0x94, 0x00, 0x01, 0x00, 0x64, 0xC0 }, - { 0x94, 0x00, 0x01, 0x00, 0x64, 0x94 }, - { 0x94, 0x00, 0x01, 0x00, 0x64, 0xD4 } + {0x94, 0x00, 0x01, 0x00, 0x64, 0x80}, + {0x94, 0x00, 0x01, 0x00, 0x64, 0xC0}, + {0x94, 0x00, 0x01, 0x00, 0x64, 0x94}, + {0x94, 0x00, 0x01, 0x00, 0x64, 0xD4} }; - int len = strlen((char *) data); + int len = strlen((char *)data); /* Cut off at display width */ if (len > 20) @@ -1269,11 +1654,21 @@ static void picolcd_20x4_write(usb_dev_handle *lcd, const int row, const int col /* Send command to select row */ switch (row) { - case 0: picolcd_send(lcd, lineset[0], 6); break; - case 1: picolcd_send(lcd, lineset[1], 6); break; - case 2: picolcd_send(lcd, lineset[2], 6); break; - case 3: picolcd_send(lcd, lineset[3], 6); break; - default: picolcd_send(lcd, lineset[0], 6); break; + case 0: + picolcd_send(lcd, lineset[0], 6); + break; + case 1: + picolcd_send(lcd, lineset[1], 6); + break; + case 2: + picolcd_send(lcd, lineset[2], 6); + break; + case 3: + picolcd_send(lcd, lineset[3], 6); + break; + default: + picolcd_send(lcd, lineset[0], 6); + break; } /* Fill in an send packet */ @@ -1284,16 +1679,17 @@ static void picolcd_20x4_write(usb_dev_handle *lcd, const int row, const int col /** - * Write function for 20x2 OEM displays + * Write function for 20x2 OEM displays. * \param lcd pointer to device handle * \param row Row to place the string at - * \param col ignored + * \param col Column to place the string at * \param data pointer to NUL terminated string */ -static void picolcd_20x2_write(usb_dev_handle *lcd, const int row, const int col, const unsigned char *data) +static void +picolcd_20x2_write(USB_DEVICE_HANDLE * lcd, const int row, const int col, const unsigned char *data) { - unsigned char packet[64] = { 0x98 }; - int len = strlen((char *) data); + unsigned char packet[64] = {0x98}; + int len = strlen((char *)data); /* * FIXME: Is it possible that data is written beyond the end of @@ -1314,15 +1710,16 @@ static void picolcd_20x2_write(usb_dev_handle *lcd, const int row, const int col /** - * Custom character define function for 20x2 OEM displays + * Custom character define function for 20x2 OEM displays. * \param drvthis Pointer to driver structure * \param n Index of custom character to update * \param dat Pointer to array of pixel data */ -static void picolcd_20x2_set_char(Driver *drvthis, int n, unsigned char *dat) +static void +picolcd_20x2_set_char(Driver *drvthis, int n, unsigned char *dat) { PrivateData *p = drvthis->private_data; - unsigned char packet[10] = { 0x9c }; /* define character */ + unsigned char packet[10] = {0x9c}; /* define character */ unsigned char mask = (1 << p->cellwidth) - 1; int row; @@ -1331,7 +1728,7 @@ static void picolcd_20x2_set_char(Driver *drvthis, int n, unsigned char *dat) if (!dat) return; - packet[1] = n; /* Custom char to define. */ + packet[1] = n; /* Custom char to define. */ for (row = 0; row < p->cellheight; row++) { packet[row + 2] = dat[row] & mask; @@ -1342,12 +1739,13 @@ static void picolcd_20x2_set_char(Driver *drvthis, int n, unsigned char *dat) /** - * Custom character define function for 20x4 desktop displays + * Custom character define function for 20x4 desktop displays. * \param drvthis Pointer to driver structure * \param n Index of custom character to update * \param dat Pointer to array of pixel data */ -static void picolcd_20x4_set_char(Driver *drvthis, int n, unsigned char *dat) +static void +picolcd_20x4_set_char(Driver *drvthis, int n, unsigned char *dat) { PrivateData *p = drvthis->private_data; @@ -1356,24 +1754,29 @@ static void picolcd_20x4_set_char(Driver *drvthis, int n, unsigned char *dat) if (!dat) return; - unsigned char command[6] = { OUT_REPORT_CMD, 0x00, 0x01, 0x00, 0x64, 0x40+8*n }; /* 0x94 */ - unsigned char data[13] = { OUT_REPORT_DATA, 0x01, 0x00, 0x01, 0x08, - dat[0], dat[1], dat[2], dat[3], - dat[4], dat[5], dat[6], dat[7]}; /* 0x95 */ + unsigned char command[6] = { + OUT_REPORT_CMD, 0x00, 0x01, 0x00, 0x64, 0x40 + 8 * n + }; /* 0x94 */ + unsigned char data[13] = { + OUT_REPORT_DATA, 0x01, 0x00, 0x01, 0x08, + dat[0], dat[1], dat[2], dat[3], + dat[4], dat[5], dat[6], dat[7] + }; /* 0x95 */ picolcd_send(p->lcd, command, 6); picolcd_send(p->lcd, data, 13); } - +#ifndef HAVE_LIBUSB_1_0 /** * Read a key or IR event from the display into one packet. - * \param lcd pointer to device handle + * \param lcd Pointer to device handle * \param packet Pointer to packet structure which is filled with data * read from the display * \param timeout Read timeout in ms */ -static void get_key_event(usb_dev_handle *lcd, lcd_packet *packet, int timeout) +static void +get_key_event(usb_dev_handle * lcd, lcd_packet * packet, int timeout) { int ret; @@ -1384,30 +1787,31 @@ static void get_key_event(usb_dev_handle *lcd, lcd_packet *packet, int timeout) if (ret > 0) { /* Set packet type */ switch (packet->data[0]) { - case IN_REPORT_KEY_STATE: { - packet->type = IN_REPORT_KEY_STATE; - } break; - case IN_REPORT_IR_DATA: { - packet->type = IN_REPORT_IR_DATA; - } break; - default: { - packet->type = 0; - } + case IN_REPORT_KEY_STATE: + packet->type = IN_REPORT_KEY_STATE; + break; + case IN_REPORT_IR_DATA: + packet->type = IN_REPORT_IR_DATA; + break; + default: + packet->type = 0; } } } +#endif /** - * Set lights for individual keys - * \param lcd pointer to device handle - * \param keys array indicating which key number to turn on + * Set lights for individual keys. + * \param lcd Pointer to device handle + * \param keys Array indicating which key number to turn on * \param state 0 to turn all LEDs off, 1 to turn them on according to * values set in 'keys' array */ -static void set_key_lights(usb_dev_handle *lcd, int keys[], int state) +static void +set_key_lights(USB_DEVICE_HANDLE * lcd, int keys[], int state) { - unsigned char packet[2] = { 0x81 }; /* set led */ + unsigned char packet[2] = {0x81}; /* set led */ unsigned int leds = 0; int i; @@ -1417,7 +1821,7 @@ static void set_key_lights(usb_dev_handle *lcd, int keys[], int state) if (keys[i]) leds |= (1 << i); else - leds &= ~ (1 << i); + leds &= ~(1 << i); } else { /* All LEDs off */ @@ -1428,4 +1832,81 @@ static void set_key_lights(usb_dev_handle *lcd, int keys[], int state) picolcd_send(lcd, packet, 2); } + +#ifdef HAVE_LIBUSB_1_0 +/** + * Store key press and release events in a buffer ready for the get key function. + * If the buffer is full key codes are discarded. + * + * \param drvthis Pointer to driver structure + * \param high_key Highest numbered key pressed + * \param low_key The second key if pressed + */ +static void +key_buffer_put(Driver *drvthis, unsigned char high_key, unsigned char low_key) +{ + PrivateData *p = drvthis->private_data; + int space; + + space = ((p->key_read_index > p->key_write_index) ? 0 : KEY_BUFFER_SIZE) + p->key_read_index - p->key_write_index; + + /* Store events if there is space or if it's a key-up event */ + if ((space > 1) || ((space == 1) && (high_key == 0) && (low_key == 0))) { + debug(RPT_DEBUG, "%s: key_buffer put %d, %d @ %d", + drvthis->name, high_key, low_key, p->key_write_index); + /* Store the keys */ + p->key_buffer[p->key_write_index].high_key = high_key; + p->key_buffer[p->key_write_index].low_key = low_key; + /* Advance write index, wrap around if necessary) */ + p->key_write_index++; + if (p->key_write_index >= KEY_BUFFER_SIZE) + p->key_write_index = 0; + } +} + +/** + * Call-back for USB input. Either calls key_buffer_put to process key events + * or ir_trancode to process events from IR receiver. + * + * \param transfer Structure containing the USB data + */ +static void +usb_cb_input(struct libusb_transfer *transfer) +{ + static const char *status[] = { + "COMPLETED", "ERROR", "TIMED_OUT", "CANCELLED", "STALL", + "NO_DEVICE", "OVERFLOW" + }; + Driver *drvthis = (Driver *)transfer->user_data; + PrivateData *p = drvthis->private_data; + + if (transfer->status != LIBUSB_TRANSFER_COMPLETED) { + report(RPT_ERR, "%s: input transfer status: %s", drvthis->name, status[transfer->status]); + p->libusb_status = transfer->status; + libusb_free_transfer(transfer); + p->input_transfer = NULL; + return; + } + + switch (transfer->buffer[0]) { + case IN_REPORT_KEY_STATE: + debug(RPT_INFO, "%s: USB input call-back key", drvthis->name); + key_buffer_put(drvthis, transfer->buffer[1], transfer->buffer[2]); + break; + case IN_REPORT_IR_DATA: + debug(RPT_INFO, "%s: USB input call-back IR length %i", drvthis->name, transfer->buffer[1]); + if (p->IRenabled) + ir_transcode(drvthis, &transfer->buffer[2], transfer->buffer[1]); + break; + default: + report(RPT_ERR, "%s: input transfer unexpected data %d", drvthis->name, transfer->buffer[0]); + } + + /* Re-transmit the input request transfer */ + p->libusb_status = libusb_submit_transfer(p->input_transfer); + if (p->libusb_status != LIBUSB_SUCCESS) + report(RPT_ERR, "%s: input transfer submit status %d", drvthis->name, p->libusb_status); +} +#endif + /* EOF */ diff --git a/server/drivers/picolcd.h b/server/drivers/picolcd.h index 9e5de7e..4b969f5 100644 --- a/server/drivers/picolcd.h +++ b/server/drivers/picolcd.h @@ -14,6 +14,14 @@ #ifndef PICOLCD_H #define PCIOLCD_H +#ifdef HAVE_LIBUSB_1_0 +# include +# define USB_DEVICE_HANDLE libusb_device_handle +#else +# include +# define USB_DEVICE_HANDLE usb_dev_handle +#endif + /* 12 keys plus a 0 placeholder */ #define KEYPAD_MAX 13 #define KEYPAD_LIGHTS 8 @@ -34,7 +42,10 @@ #define DEFAULT_OFFBRIGHTNESS 0 /* Off */ #define DEFAULT_BACKLIGHT 1 /* On */ #define DEFAULT_KEYLIGHTS 1 /* On */ +#define DEFAULT_LINKLIGHTS 1 /* On */ #define DEFAULT_TIMEOUT 500 /* Half second */ +#define DEFAULT_REPEAT_DELAY 300 /* milliseconds */ +#define DEFAULT_REPEAT_INTERVAL 200 /* milliseconds */ typedef struct _lcd_packet { @@ -56,7 +67,7 @@ typedef struct _picolcd_device { int width; /* width of lcd screen */ int height; /* height of lcd screen */ /* Pointer to function that writes data to the LCD format */ - void (*write) (usb_dev_handle *lcd, const int row, const int col, const unsigned char *data); + void (*write) (USB_DEVICE_HANDLE *lcd, const int row, const int col, const unsigned char *data); /* Pointer to function that defines a custom character */ void (*cchar) (Driver *drvthis, int n, unsigned char *dat); } picolcd_device;