diff --git a/ChangeLog b/ChangeLog index 0f79d89..fb45cda 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,7 @@ v.0.5dev (ongoing development) * Include instead of everywhere (found by S. Klauer) * configure: Fix de-selecting of drivers * Build system: Add a check testing if SA_RESTART flag is available. + * picolcd driver: Fix RC-5 for picoLCD 20x2 (A. van Schie) v0.5.3 + lcdexec: notification when called program finishes diff --git a/LCDd.conf b/LCDd.conf index 1ed9eab..ff28e25 100644 --- a/LCDd.conf +++ b/LCDd.conf @@ -864,13 +864,9 @@ Key5Light=on # UDP port on which LIRC is listening [default: 8765; legal: 1 - 65535] LircPort=8765 -# Duration in jiffies of the synthesized sync pulse [default: 64; 0 to suppress] -LircSync=64 - -# Duration in jiffies of the synthesized gap; 0 to suppress, default 2048. -# This is really the fixed duration of an entire IR command; individual pulse/space -# durations are deducted from this to determine the duration of the trailing gap. -LircLength=2048 +# Threshold in jiffies of synthesized gap that triggers flushing the IR data to lirc +# [default: 100 (6.1ms); 0 to suppress] +LircFlushThreshold=100 diff --git a/docs/lcdproc-user/drivers/picolcd.docbook b/docs/lcdproc-user/drivers/picolcd.docbook index bfae242..5739820 100644 --- a/docs/lcdproc-user/drivers/picolcd.docbook +++ b/docs/lcdproc-user/drivers/picolcd.docbook @@ -264,30 +264,16 @@ - LircSync = + LircFlushThreshold = DURATION - This value determines the length in jiffies (1/16384 seconds) of the synthesized sync pulse added - to the beginning of the UDP packet sent to LIRC. Values up to 255 are permitted. Setting a length - of zero will suppress the synthesized pulse (I can't make it work without it, though). The default is - 64 jiffies (3.9ms). - - - - - - - LircLength = - DURATION - - - - This value determines the length in jiffies (1/16384 seconds) of the entire LIRC code for a single remote - property. It depends on the notion that all propertys are of the same duration, and are padded with a trailing - gap (space) to make up the time. No such gap is emitted by the picolcd hardware. Values up to MAXINT are permitted. - Setting a length of zero will suppress the synthesized gap. The default is 2048 jiffies (125ms). + This value is the length in jiffies (1/16384 seconds) of the synthesized sync space that will + trigger sending the queued IR data to LIRC. Values up to 32767 (2s) are permitted, values lower + than 16 will suppress the flushing IR data during processing. The default is + 100 jiffies (6.1ms). It's should only be needed to change this value when + when lircd.conf has a gap < 6100 or the samples > 6300. diff --git a/server/drivers/picolcd.c b/server/drivers/picolcd.c index abab903..fc8b740 100644 --- a/server/drivers/picolcd.c +++ b/server/drivers/picolcd.c @@ -12,6 +12,10 @@ * (c) 2008 Jack Cleaver - add LIRC connection * (c) 2008 Mini-Box.com Nicu Pavel * - Added support for 4x20 picoLCD + * (c) 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 * License: GPL (same as usblcd and lcdPROC) * * picoLCD: http://www.mini-box.com/picoLCD-20x2-OEM @@ -45,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -93,9 +98,10 @@ typedef struct picolcd_private_data { struct sockaddr_in lircserver; /* IR transcode results */ unsigned char result[512]; - int sync; - int preset_gap; - int gap; + unsigned char* resptr; + struct timeval lastmsg; + int lastval; + int flush_threshold; } PrivateData; /* Private function definitions */ @@ -106,8 +112,8 @@ 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 int ir_transcode(Driver *drvthis, unsigned char *data, unsigned int cbdata, - unsigned char *result, int cbresult); +static void picolcd_lircsend(Driver *drvthis); +static void ir_transcode(Driver *drvthis, unsigned char *data, unsigned int cbdata); picolcd_device picolcd_device_ids[] = { { @@ -290,19 +296,28 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) lirchost = drvthis->config_get_string(drvthis->name, "LircHost", 0, NULL); lircport = drvthis->config_get_int(drvthis->name, "LircPort", 0, DEFAULT_LIRCPORT); - p->sync = drvthis->config_get_int(drvthis->name, "LircSync", 0, DEFAULT_SYNC_JIFFY); - p->preset_gap = drvthis->config_get_int(drvthis->name, "LircLength", 0, DEFAULT_LENGTH_JIFFY); + p->flush_threshold = drvthis->config_get_int(drvthis->name, "LircFlushThreshold", 0, + DEFAULT_FLUSH_THRESHOLD_JIFFY); + if (p->flush_threshold < 16) /* Prevent small 'foolish' values these will disable the check also! */ + p->flush_threshold = 0x8000; /* Disabled, send check will always fail! */ p->IRenabled = (lirchost != NULL && *lirchost != '\0') ? 1 : 0; + /* 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; + gettimeofday(&p->lastmsg, NULL); + if (p->IRenabled) { /* Initialize communication with LIRC */ struct hostent *hostinfo = gethostbyname(lirchost); - if (hostinfo == NULL) { - report (RPT_ERR, "%s: unknown LIRC host %s", drvthis->name, lirchost); - return -1; - } + if (hostinfo == NULL) { + report (RPT_ERR, "%s: unknown LIRC host %s", drvthis->name, lirchost); + return -1; + } if ((p->lircsock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { report(RPT_ERR, "%s: failed to create socket to send data to LIRC", drvthis->name); return -1; @@ -311,11 +326,11 @@ MODULE_EXPORT int picoLCD_init(Driver *drvthis) /* Construct the server sockaddr_in structure */ memset(&p->lircserver, 0, sizeof(p->lircserver)); /* Clear struct */ p->lircserver.sin_family = AF_INET; /* Internet/IP */ - p->lircserver.sin_addr = *(struct in_addr *) hostinfo->h_addr; /* IP address */ + p->lircserver.sin_addr = *(struct in_addr *) hostinfo->h_addr; /* IP address */ p->lircserver.sin_port = htons(lircport); /* server port */ - report(RPT_INFO, "%s: IR events will be sent to LIRC on %s:%d, with sync=%d and length=%d", - drvthis->name, lirchost, lircport, p->sync, p->preset_gap); + report(RPT_INFO, "%s: IR events will be sent to LIRC on %s:%d, with flush threshold=%d", + drvthis->name, lirchost, lircport, p->flush_threshold); } report(RPT_INFO, "%s: init complete", drvthis->name); @@ -650,12 +665,12 @@ MODULE_EXPORT int picoLCD_icon (Driver *drvthis, int x, int y, int icon) picoLCD_chr(drvthis, x, y, 255); break; case ICON_HEART_FILLED: - p->ccmode = custom; + p->ccmode = custom; picoLCD_set_char(drvthis, 7, heart_filled); picoLCD_chr(drvthis, x, y, 7); break; case ICON_HEART_OPEN: - p->ccmode = custom; + p->ccmode = custom; picoLCD_set_char(drvthis, 7, heart_open); picoLCD_chr(drvthis, x, y, 7); break; @@ -684,38 +699,29 @@ MODULE_EXPORT int picoLCD_icon (Driver *drvthis, int x, int y, int icon) MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) { PrivateData *p = drvthis->private_data; - lcd_packet *keydata; + lcd_packet keydata; char *keystr = NULL; int keys_read = 0; int key_pass = 0; int two_keys = 0; - int ret; debug(RPT_DEBUG, "%s: get_key start (timeout %d)", drvthis->name, p->key_timeout); - keydata = malloc(sizeof(lcd_packet)); - - if (keydata == NULL) { - report(RPT_ERR, "%s: get_key keydata malloc(%d) failed", - drvthis->name, sizeof(lcd_packet)); - return NULL; - } - while (! keys_read) { - get_key_event(p->lcd, keydata, p->key_timeout); + get_key_event(p->lcd, &keydata, p->key_timeout); debug(RPT_DEBUG, "%s: get_key got an event", drvthis->name); - if (keydata->type == IN_REPORT_KEY_STATE) { - if (! keydata->data[1] && key_pass) { + if (keydata.type == IN_REPORT_KEY_STATE) { + if (! keydata.data[1] && key_pass) { debug(RPT_DEBUG, "%s: get_key got all clear", drvthis->name); /* Got a <0, 0> key-up event after reading a valid key press event */ keys_read++; /* All clear */ } - else if (! keydata->data[2] && ! two_keys) { + else if (! keydata.data[2] && ! two_keys) { debug(RPT_DEBUG, "%s: get_key got one key", drvthis->name); /* We got one key (but not after a two key event and before and all clear) */ - keystr = p->device->keymap[keydata->data[1]]; + keystr = p->device->keymap[keydata.data[1]]; } else { /* We got two keys */ @@ -723,56 +729,33 @@ MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) debug(RPT_DEBUG, "%s: get_key got two keys", drvthis->name); two_keys++; - sprintf(keybuf, "%s+%s", p->device->keymap[keydata->data[1]], - p->device->keymap[keydata->data[2]]); + sprintf(keybuf, "%s+%s", p->device->keymap[keydata.data[1]], + p->device->keymap[keydata.data[2]]); keystr = keybuf; } key_pass++; /* This hack allows us to deal with receiving left over <0,0> first */ } - else if (p->IRenabled && keydata->type == IN_REPORT_IR_DATA) { - int cbres; - + else if (p->IRenabled && keydata.type == IN_REPORT_IR_DATA) { debug(RPT_NOTICE, "%s: get_key irdata, length=%d bytes", - drvthis->name, keydata->data[1]); + drvthis->name, keydata.data[1]); - cbres = ir_transcode(drvthis, (keydata->data)+2, keydata->data[1], p->result, sizeof(p->result)); - debug(RPT_NOTICE, "%s: get_key irdata transcoded, count=%d bytes", - drvthis->name, cbres); - - if (cbres < 0) { - report(RPT_ERR, "%s: could not transcode buffer, length=%d", - drvthis->name, keydata->data[1]); - } - else { - debug(RPT_NOTICE, "%s: sending packet to lirc, length=%d", - drvthis->name, cbres); - ret = sendto(p->lircsock, p->result, cbres, 0, - (struct sockaddr *) &(p->lircserver), sizeof(p->lircserver)); - if (ret == -1) { - report(RPT_ERR, "%s: error sending UDP packet, errno=%d", - drvthis->name, errno); - } - else if (ret != cbres) { - report(RPT_ERR, "%s: mismatch in number of bytes sent (%d!=%d)", - drvthis->name, cbres, ret); - } - else { - debug(RPT_NOTICE, "%s: packet sent to lirc.", drvthis->name); - } - } + /* transcoded data is queued and send when complete or by a timeout */ + ir_transcode(drvthis, keydata.data + 2, keydata.data[1]); } else { - debug(RPT_DEBUG, "%s: get_key got non-key data or timeout", drvthis->name); + debug(RPT_DEBUG, "%s: get_key got non-key/ir data or timeout", drvthis->name); + if (p->result < p->resptr) { + debug(RPT_INFO, "picolcd: timeout %d send lirc data now", p->key_timeout); + /* Send data maybe is enhough for LIRC */ + picolcd_lircsend(drvthis); + } /* We got IR or otherwise bad data */ - free(keydata); return NULL; } } - free(keydata); - debug(RPT_DEBUG, "%s: get_key complete (%s)", drvthis->name, keystr); if ((keystr != NULL) && (strlen(keystr) > 0)) @@ -970,113 +953,140 @@ 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 result Buffer to receive the transcoded values. - * \param cbresult Buffer to receive the transcoded values. - * \return Number of bytes placed in result buffer. + * + * \note The picoLCD introduces two issues: + * 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. + * 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 'calcutated' 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 int ir_transcode(Driver *drvthis, unsigned char *data, unsigned int cbdata, - unsigned char *result, int cbresult) +static void ir_transcode(Driver *drvthis, unsigned char* data, unsigned int cbdata) { PrivateData *p = drvthis->private_data; int i; int cIntervals = cbdata >> 1; - int resptr = 0; - long w = (data[1] << 8) + data[0]; + long w = (data[1] << 8) | data[0]; + struct timeval now; - //Check for odd buffer length (invalid buffer) + /* Check for odd buffer length (invalid buffer) */ if (cbdata & 1) { - return -1; + return; } - /* - * Look for an initial long PULSE, and frig it for LIRC's benefit: - * add a sync SPACE in front. - */ - if (w & 0x8000) { /* SPACE */ - w = 65536 - w; - if (w > 8000) { - /* - * The signal was a space longer than 8000 ms, i.e. probably a sync. We - * now expect from picoLCD either a repeat-code, or a long pulse followed - * by a full code. - * Lirc expects a sync (SPACE) followed by either a header (pulse/space) - * or a repeat-code. - * Lirc is also expecting active-low signalling. So we emit the required - * sync, then the signals from picoLCD with arity inverted. - * Emit sync space to LIRC (0x8040, = 64 jiffies, = 3900usec). - */ - if (p->sync) { - result[resptr++] = p->sync; - result[resptr++] = 0x80; + /* 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)); + 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; + int gap = 0x7FFF; + + /* 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; } - p->gap = p->preset_gap; - debug(RPT_DEBUG, "%s: preset gap, length=%d jiffies", - drvthis->name, p->gap); } + /* Make it a space */ + gap |= 0x8000; + + debug(RPT_INFO, "picolcd: injecting space %04hx between %04hx and %04hx", + gap, p->lastval, -w & 0xFFFF); + *p->resptr++ = (unsigned char)(gap & 0xff); + *p->resptr++ = (unsigned char)((gap >> 8) & 0xff); + } + /* Check if there is enhough space left in buffer to store all new samples */ + else if (cbdata >= (&p->result[sizeof(p->result)] - p->resptr)) { + /* This should never happen but just to be sure. */ + debug(RPT_INFO, "picolcd: buffer almost full send lirc data now"); + picolcd_lircsend(drvthis); } for (i = 0; i < cIntervals; i++) { - long w = (data[i*2 + 1] << 8) + data[i*2]; + w = *data++; + w |= *data++ << 8; if (w & 0x8000) { //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; - p->gap -= w; + w = (w * 16384/ 1000000) & 0xFFFF; } else { //Scale. w = w * 16384 / 1000000; + if (w >= p->flush_threshold) { + report(RPT_INFO, "picolcd: detected sync space sending lirc data now"); + picolcd_lircsend(drvthis); + } //Set the space bit. - p->gap -= w; w |= 0x8000; } - if (resptr + 2 < cbresult) { - result[resptr++] = w & 0xFF; - result[resptr++] = (w >> 8) & 0xFF; - } - else { - resptr = -1; - break; - } + *p->resptr++ = (unsigned char)(w & 0xff); + *p->resptr++ = (unsigned char)((w >> 8) & 0xff); } - /* - * Look for a short buffer with a terminal PULSE, and frig it for LIRC's benefit: - * add a gap SPACE after. This is an ugly gash; it won't work if the code ends with - * a buffer containing exactly ten intervals. - */ - if (resptr > 0 && cIntervals < 10) { - if (resptr + 2 < cbresult) { - int last = cIntervals -1; + p->lastval = w; + p->lastmsg = now; + /* 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); + } +} - w = (data[last*2 + 1] << 8) + data[last*2]; - if (p->gap > 0) { - if (w & 0x8000) { - //terminal pulse - debug(RPT_DEBUG, "%s: appending gap, length=%d jiffies", - drvthis->name, p->gap); - p->gap |= 0x8000; - result[resptr++] = p->gap & 0xFF; - result[resptr++] = (p->gap >>8) & 0xFF; - } - else { - debug(RPT_DEBUG, "%s: terminal space=[%04x]; not appending gap (length=%d jiffies)", (unsigned int)(w & 0xffff), drvthis->name, p->gap); - } +/** + * Send any queued IR samples to LIRC + * \param drvthis Pointer to driver structure. + */ +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); + { + 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; + while (ptr < endptr) { + unsigned int val = *ptr++; + val |= *ptr++ << 8; + logptr += sprintf(logptr, " %04x", val); } - else { - debug(RPT_DEBUG, "%s: not appending gap because it would be negative (length=%d jiffies)", drvthis->name, p->gap); + debug(RPT_DEBUG, "picolcd: data:%s", logbuf); + } +#endif + if (sendto(p->lircsock, p->result, len, 0, + (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 { - //Result buffer would overflow - resptr = -1; - } + p->resptr = p->result; } - else { - debug(RPT_DEBUG, "%s: cIntervals=%d; not appending gap (length=%d jiffies)", - drvthis->name, cIntervals, p->gap); - } - return resptr; } @@ -1197,13 +1207,6 @@ static void get_key_event(usb_dev_handle *lcd, lcd_packet *packet, int timeout) } break; case IN_REPORT_IR_DATA: { packet->type = IN_REPORT_IR_DATA; - /* - * clears the halt status on the usb endpoint - * picoLCD 20x4 keeps last ir state without clearing the - * status on endpoint, meaning that we will get same IR data - * over and over till we clear the status manually. - */ - usb_clear_halt(lcd, USB_ENDPOINT_IN + 1); } break; default: { packet->type = 0; diff --git a/server/drivers/picolcd.h b/server/drivers/picolcd.h index 1ee3bb1..86f65de 100644 --- a/server/drivers/picolcd.h +++ b/server/drivers/picolcd.h @@ -47,8 +47,7 @@ #define PICOLCD_MAX_DATA_LEN 24 #define DEFAULT_LIRCPORT 8765 -#define DEFAULT_SYNC_JIFFY 64 -#define DEFAULT_LENGTH_JIFFY 2048 +#define DEFAULT_FLUSH_THRESHOLD_JIFFY 100 /* 6.1 millisec */ typedef struct _lcd_packet {