/* * picoLCD driver for lcdPROC * * (c) 2007 NitroSecurity, Inc. * Written by Gatewood Green or * (c) 2007 Peter Marschall - adapted coding style and reporting to LCDproc * * License: GPL (same as usblcd and lcdPROC) * * picoLCD: http://www.mini-box.com/picoLCD-20x2-OEM * Can be purchased separately or preinstalled in units such as the * M300 http://www.mini-box.com/Mini-Box-M300-LCD * * This driver (key lables and arrangement) is based on the M300 implementation * of the picoLCD * * The picoLCD is usb connected and is driven (currently) via userspace * libraries using the Mini-box.com usblcd library (not to be confused with the * Linux usblcd module which does NOT support this device). The usblcd library * rides atop libusb and libhid (both of which are required for this driver to * operate). * * libusb: http://libusb.sf.net * libhid: http://libhid.alioth.debian.org * usblcd: http://www.mini-box.com/picoLCD-20x2-OEM * * The usblcd library is very haphazardly written and directly writes to * stdout and stderr instead of returning the result for most functions * (including read_events). Eventually it would be a good idea to eliminate * the need for usblcd and drive the hardware via libusb and libhid directly. * Such a conversion has the opportunity to provide meaningful return values * for all fucntions (instead of stab and hope) and allow for use of multiple * picoLCD devices. * * Due to the way libusblcd's read_events prints keys to stderr instead of * returning a struct or some such, you will find my own get_key_events below. * * ### WARNING ###: libusblcd.so sets a handler for SIGTERM. Because most * applications would set up their signal handling early on (before calling * new_usblcd_operations()), this can result in a condition that will prevent * a handler your application installed from executing. If your handler was * responsible for cleaning up logs, syncing, etc, it can result in lost data. * */ /* lcdPROC includes */ #include "lcd.h" #include "picolcd.h" /* Debug mode: un-comment to turn on debugging messages in the server */ /* #define DEBUG 1 */ #include "report.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif /* These three includes are the Mini-box.com libusblcd (usblcd) and company. */ #include #include #include /* Various odds and ends */ #include #include #include #include #include /* 12 keys plus a 0 placeholder */ #define KEYPAD_MAX 13 #define KEYPAD_LIGHTS 6 #define DEFAULT_CONTRAST 1000 /* Full */ #define DEFAULT_BACKLIGHT 1 /* On */ #define DEFAULT_KEYLIGHTS 1 /* On */ #define DEFAULT_TIMEOUT 500 /* Half second */ /* PrivateData struct */ typedef struct pd { usblcd_operations *lcd; // Reference to the LCD instance int width; int height; int key_timeout; int contrast; int backlight; int keylights; int key_light[KEYPAD_LIGHTS]; char *key_matrix[KEYPAD_MAX]; char *info; unsigned char *framebuf; unsigned char *lstframe; } PrivateData; static char * keymap[KEYPAD_MAX] = { NULL, "Plus", "Minus", "F1", "F2", "F3", "F4", "F5", "Left", "Right", "Up", "Down", "Enter" }; /* Private function definitions */ void get_key_event (usblcd_operations *self, lcd_packet *packet, int timeout); void set_key_lights (usblcd_operations *self, int keys[], int state); /* lcd_logical_driver Variables */ MODULE_EXPORT char *api_version = API_VERSION; MODULE_EXPORT int stay_in_foreground = 0; MODULE_EXPORT int supports_multiple = 1; MODULE_EXPORT char *symbol_prefix = "picoLCD_"; /* lcd_logical_driver Manditory functions */ MODULE_EXPORT int picoLCD_init(Driver *drvthis) { PrivateData *pd; int x; pd = (PrivateData *) malloc(sizeof(PrivateData)); if (! pd) return -1; if (drvthis->store_private_ptr(drvthis, pd)) return -1; pd->lcd = new_usblcd_operations(); pd->lcd->init(pd->lcd); pd->width = 20; /* hard coded (mfg spec) */ pd->height = 2; /* hard coded (mfg spec) */ pd->info = "picoLCD: Supports the LCD as installed on the M300 (http://www.mini-box.com/Mini-Box-M300-LCD) "; for (x = 0; x < KEYPAD_LIGHTS; x++) pd->key_light[x] = 1; /* individual lights on */ pd->contrast = drvthis->config_get_int( drvthis->name, "Contrast", 0, DEFAULT_CONTRAST ); pd->backlight = drvthis->config_get_bool(drvthis->name, "BackLight", 0, DEFAULT_BACKLIGHT); pd->keylights = drvthis->config_get_bool(drvthis->name, "KeyLights", 0, DEFAULT_KEYLIGHTS); /* key lights with LCD Backlight? */ pd->key_timeout = drvthis->config_get_int( drvthis->name, "KeyTimeout", 0, DEFAULT_TIMEOUT ); /* These allow individual lights to be disabled */ pd->key_light[0] = drvthis->config_get_bool(drvthis->name, "Key0Light", 0, 1); /* Directional PAD */ pd->key_light[1] = drvthis->config_get_bool(drvthis->name, "Key1Light", 0, 1); /* F1 */ pd->key_light[2] = drvthis->config_get_bool(drvthis->name, "Key2Light", 0, 1); /* F2 */ pd->key_light[3] = drvthis->config_get_bool(drvthis->name, "Key3Light", 0, 1); /* F3 */ pd->key_light[4] = drvthis->config_get_bool(drvthis->name, "Key4Light", 0, 1); /* F4 */ pd->key_light[5] = drvthis->config_get_bool(drvthis->name, "Key5Light", 0, 1); /* F5 */ for (x = 0; x < KEYPAD_MAX; x++) pd->key_matrix[x] = keymap[x]; pd->framebuf = (unsigned char *) malloc(pd->width * pd->height + 1); if (pd->framebuf == NULL) { report(RPT_ERR, "%s: unable to create framebuf.\n", __FUNCTION__); return -1; } memset(pd->framebuf, ' ', pd->width * pd->height); pd->framebuf[pd->width * pd->height] = 0; pd->lstframe = (unsigned char *) malloc(pd->width * pd->height + 1); if (pd->lstframe == NULL) { report(RPT_ERR, "%s: unable to create lstframe.\n", __FUNCTION__); return -1; } memset(pd->lstframe, ' ', pd->width * pd->height); pd->lstframe[pd->width * pd->height] = 0; if (pd->backlight) picoLCD_backlight(drvthis, 1); if (! pd->keylights) set_key_lights(pd->lcd, pd->key_light, 0); else picoLCD_backlight(drvthis, 0); picoLCD_set_contrast(drvthis, pd->contrast); report(RPT_INFO, "picolcd: init complete\n", __FUNCTION__); return 0; } MODULE_EXPORT void picoLCD_close(Driver *drvthis) { PrivateData *pd = drvthis->private_data; pd->lcd->close(pd->lcd); /* This actually does not do anything in usblcd (yet?) */ debug(RPT_DEBUG, "picolcd: close complete\n"); } /* lcd_logical_driver Essential output functions */ MODULE_EXPORT int picoLCD_width(Driver *drvthis) { PrivateData *pd = drvthis->private_data; return pd->width; } MODULE_EXPORT int picoLCD_height(Driver *drvthis) { PrivateData *pd = drvthis->private_data; return pd->height; } MODULE_EXPORT void picoLCD_clear(Driver *drvthis) { PrivateData *pd = drvthis->private_data; memset(pd->framebuf, ' ', pd->width * pd->height); debug(RPT_DEBUG, "picolcd: clear complete\n"); } MODULE_EXPORT void picoLCD_flush(Driver *drvthis) { PrivateData *pd = drvthis->private_data; unsigned char *fb = pd->framebuf; unsigned char *lf = pd->lstframe; static char text[48]; int i, line, offset; debug(RPT_DEBUG, "picolcd: flush started\n"); for (line = 0; line < pd->height; line++) { memset(text, 0, 48); offset = line * pd->width; fb = pd->framebuf + offset; lf = pd->lstframe + offset; for (i = 0; i < pd->width; i++) { if (*fb++ != *lf++) { strncpy(text, pd->framebuf + offset, pd->width); pd->lcd->settext(pd->lcd, line, 0, text); memcpy(pd->lstframe + offset, pd->framebuf + offset, pd->width); debug(RPT_DEBUG, "picolcd: flush wrote line %d (%s)\n", line + 1, text); break; } } } debug(RPT_DEBUG, "picolcd: flush complete\n\t(%s)\n\t(%s)\n", pd->framebuf, pd->lstframe); } MODULE_EXPORT void picoLCD_string(Driver *drvthis, int x, int y, char *str) { PrivateData *pd = drvthis->private_data; char *dest; int len; debug(RPT_DEBUG, "picolcd: string start (%s)\n", str); if (y < 1 || y > pd->height) return; if (x < 1 || x > pd->width) return; len = strlen(str); if (len + x > pd->width) { debug(RPT_DEBUG, "picolcd: string overlength (>%d). Start: %d Length: %d (%s)\n", pd->width, x, len ,str); len = pd->width - x; /* Copy what we can */ } x--; y--; /* Convert 1-based to 0-based */ dest = pd->framebuf + (y * pd->width + x); memcpy(dest, str, len * sizeof(char)); debug(RPT_DEBUG, "picolcd: string complete (%s)\n", str); } MODULE_EXPORT void picoLCD_chr(Driver *drvthis, int x, int y, char chr) { PrivateData *pd = drvthis->private_data; char *dest; debug(RPT_DEBUG, "picolcd: chr start (%c)\n", chr); if (y < 1 || y > pd->height) return; if (x < 1 || x > pd->width) return; x--; y--; /* Convert 1-based to 0-based */ dest = pd->framebuf + (y * pd->width + x); memcpy(dest, &chr, sizeof(char)); debug(RPT_DEBUG, "picolcd: chr complete (%c)\n", chr); } /* lcd_logical_driver Essential input functions */ MODULE_EXPORT char *picoLCD_get_key(Driver *drvthis) { PrivateData *pd = drvthis->private_data; lcd_packet *keydata; char *keystr; int keys_read = 0; int key_pass = 0; int two_keys = 0; debug(RPT_DEBUG, "picolcd: get_key start (timeout %d)\n", pd->key_timeout); keydata = malloc(sizeof(lcd_packet)); while (! keys_read) { get_key_event(pd->lcd, keydata, pd->key_timeout); debug(RPT_DEBUG, "picolcd: get_key got an event\n"); if (keydata->type == IN_REPORT_KEY_STATE) { if (! keydata->data[1] && key_pass) { debug(RPT_DEBUG, "picolcd: get_key got all clear\n"); /* 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) { debug(RPT_DEBUG, "picolcd: get_key got one key\n"); /* We got one key (but not after a two key event and before and all clear) */ keystr = pd->key_matrix[keydata->data[1]]; } else { /* We got two keys */ debug(RPT_DEBUG, "picolcd: get_key got two keys\n"); two_keys++; sprintf(keystr, "%s+%s", pd->key_matrix[keydata->data[1]], pd->key_matrix[keydata->data[2]]); } key_pass++; /* This hack allows us to deal with receiving left over <0,0> first */ } else { debug(RPT_DEBUG, "picolcd: get_key got non-key data or timeout\n"); /* We got IR or otherwise bad data */ return NULL; } } free(keydata); debug(RPT_DEBUG, "picolcd: get_key complete (%s)\n", keystr); if (! strlen(keystr)) return NULL; return keystr; /* * 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: . * * Key ID numbers: * 0 = (no key) * 1 = + (plus) * 2 = - (minus) * 3 = F1 * 4 = F2 * 5 = F3 * 6 = F4 * 7 = F5 * 8 = Left * 9 = Right * 10 = Up * 11 = Down * 12 = Enter * * 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. */ } /* lcd_logical_driver Extended output functions */ /* lcd_logical_driver User-defined character functions */ /* lcd_logical_driver Hardware functions */ /*MODULE_EXPORT int picoLCD_get_contrast(Driver *drvthis) { PrivateData *pd = drvthis->private_data; }*/ MODULE_EXPORT int picoLCD_set_contrast(Driver *drvthis, int promille) { PrivateData *pd = drvthis->private_data; int inv; /* The hardware seems to go dark on higher values, so we turn it around */ if (promille <= 1000 && promille > 0) { inv = 1000 - promille; pd->lcd->contrast(pd->lcd, (int) (inv / 1000 * 40)); return 0; } else if (promille > 1000) { pd->lcd->contrast(pd->lcd, 0); return 0; } else if (promille <= 0) { pd->lcd->contrast(pd->lcd, 40); return 0; } else { return -1; } } /*MODULE_EXPORT int picoLCD_get_brightness(Driver *drvthis, int state) { PrivateData *pd = drvthis->private_data; }*/ /*MODULE_EXPORT int picoLCD_set_brightness(Driver *drvthis, int state, int promille) { PrivateData *pd = drvthis->private_data; }*/ MODULE_EXPORT void picoLCD_backlight(Driver *drvthis, int state) { PrivateData *pd = drvthis->private_data; if (state == 0) { pd->lcd->backlight(pd->lcd, state); set_key_lights(pd->lcd, pd->key_light, state); return; } if (state == 1) { pd->lcd->backlight(pd->lcd, state); if (pd->keylights) set_key_lights(pd->lcd, pd->key_light, state); return; } return; } /*MODULE_EXPORT int picoLCD_output(Driver *drvthis, int state) { PrivateData *pd = drvthis->private_data; }*/ /* lcd_logical_driver Informational functions */ MODULE_EXPORT char *picoLCD_get_info(Driver *drvthis) { PrivateData *pd = drvthis->private_data; return pd->info; } /* Private functions */ void get_key_event (usblcd_operations *self, lcd_packet *packet, int timeout) { int ret; memset(packet->data, 0, 255); packet->type = 0; ret = usb_interrupt_read(self->hid->hiddev->handle, USB_ENDPOINT_IN + 1, packet->data, _USBLCD_MAX_DATA_LEN, timeout); if (ret > 0) { 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; } } } } void set_key_lights (usblcd_operations *self, int keys[], int state) { if (state) { /* Only LEDs we want on */ if (keys[0]) self->setled(self, 0, 1); if (keys[1]) self->setled(self, 1, 1); if (keys[2]) self->setled(self, 2, 1); if (keys[3]) self->setled(self, 3, 1); if (keys[4]) self->setled(self, 4, 1); if (keys[5]) self->setled(self, 5, 1); } else { /* All LEDs off */ self->setled(self, 0, 0); self->setled(self, 1, 0); self->setled(self, 2, 0); self->setled(self, 3, 0); self->setled(self, 4, 0); self->setled(self, 5, 0); } }