occasional missed keys and eliminate use of key_timeout which disrupts the main loop timing (by M. Jones).
1913 lines
54 KiB
C
1913 lines
54 KiB
C
/** \file server/drivers/picolcd.c
|
|
* LCDd \c picolcd driver for the Mini-Box.com USB LCD picoLCD displays.
|
|
*
|
|
* Currently the following devices are supported:
|
|
* \li picoLCD 20x2 (OEM), stand-alone version http://www.mini-box.com/picoLCD-20x2-OEM
|
|
* or pre-installed as in http://www.mini-box.com/Mini-Box-M300-LCD.
|
|
* \li picoLCD-4x20-sideshow, http://www.mini-box.com/picoLCD-4x20-sideshow.
|
|
*
|
|
* The picoLCD is USB connected and is driven via userspace using libusb
|
|
* library (http://www.libusb.org/).
|
|
*/
|
|
|
|
/*-
|
|
* Copyright (c) 2007 NitroSecurity, Inc.
|
|
* 2007-2008 Peter Marschall
|
|
* 2007-2008 Mini-Box.com, Nicu Pavel <npavel@ituner.com>
|
|
* 2008 Jack Cleaver
|
|
* 2009 Andries van Schie
|
|
* 2010-2011 Martin Jones <martin@brasskipper.org.uk>
|
|
*
|
|
* This file is released under the GNU General Public License. Refer to the
|
|
* COPYING file distributed with this package.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
/* Various odds and ends */
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
/* LCDproc includes */
|
|
#include "lcd.h"
|
|
#include "lcd_lib.h"
|
|
#include "adv_bignum.h"
|
|
#include "report.h"
|
|
#include "picolcd.h"
|
|
#include "timing.h"
|
|
|
|
#define NUM_CCs 8 /* max. number of custom characters */
|
|
#define KEY_BUFFER_SIZE 8 /* size of the key ring buffer */
|
|
|
|
#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_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;
|
|
unsigned char *lstframe;
|
|
/* device info struct */
|
|
picolcd_device *device;
|
|
/* For communicating with LIRC */
|
|
int IRenabled;
|
|
int lircsock;
|
|
struct sockaddr_in lircserver;
|
|
/* IR transcode results */
|
|
unsigned char result[512];
|
|
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_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 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
|
|
* to low-level functions.
|
|
*/
|
|
picolcd_device picolcd_device_ids[] = {
|
|
{
|
|
.device_name = "picoLCD20x2",
|
|
.description = "Driver for picoLCD 20x2 OEM and picoLCD found on M200/M300 cases",
|
|
.vendor_id = 0x04d8,
|
|
.device_id = 0x0002,
|
|
.bklight_max = 1,
|
|
.bklight_min = 0,
|
|
.contrast_max = 40,
|
|
.contrast_min = 0,
|
|
.width = 20,
|
|
.height = 2,
|
|
.write = picolcd_20x2_write,
|
|
.cchar = picolcd_20x2_set_char,
|
|
/* all keymap labels must be shorter than KEYPAD_LABEL_MAX */
|
|
.keymap = { NULL, "Plus", "Minus", "F1", "F2", "F3", "F4", "F5",
|
|
"Left", "Right", "Up", "Down", "Enter" },
|
|
.initseq = {},
|
|
},
|
|
{
|
|
.device_name = "picoLCD20x4",
|
|
.description = "Driver for picoLCD 20x4 desktop LCD",
|
|
.vendor_id = 0x04d8,
|
|
.device_id = 0xc001,
|
|
.bklight_max = 100,
|
|
.bklight_min = 100,
|
|
.contrast_max = 1,
|
|
.contrast_min = 0,
|
|
.width = 20,
|
|
.height = 4,
|
|
.write = picolcd_20x4_write,
|
|
.cchar = picolcd_20x4_set_char,
|
|
/* all keymap labels must be shorter than KEYPAD_LABEL_MAX */
|
|
.keymap = { NULL, "Escape", "F1", "F2", "F3", "Home", "Down",
|
|
"Enter", "Up", "", "", "", ""},
|
|
.initseq = { 0x94, 0x00, 0x07, 0x00, 0x32, 0x30, 0x00, 0x32,
|
|
0x30, 0x00, 0x32, 0x30, 0x00, 0x32, 0x38, 0x00,
|
|
0x32, 0x06, 0x00, 0x32, 0x0C, 0x07, 0xD0, 0x01},
|
|
},
|
|
{}, /* End list */
|
|
};
|
|
|
|
/* 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 mandatory functions */
|
|
|
|
/**
|
|
* Initialize the driver.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \retval 0 Success.
|
|
* \retval <0 Error.
|
|
*/
|
|
MODULE_EXPORT int
|
|
picoLCD_init(Driver *drvthis)
|
|
{
|
|
PrivateData *p;
|
|
#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;
|
|
int tmp;
|
|
|
|
p = (PrivateData *) malloc(sizeof(PrivateData));
|
|
if (p == NULL)
|
|
return -1;
|
|
|
|
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();
|
|
|
|
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)) {
|
|
report(RPT_INFO, "%s: found %s on bus %s device %s",
|
|
drvthis->name, picolcd_device_ids[id].device_name,
|
|
bus->dirname, dev->filename);
|
|
|
|
p->lcd = usb_open(dev);
|
|
p->device = &picolcd_device_ids[id];
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
|
|
if (p->lcd != NULL) {
|
|
debug(RPT_DEBUG, "%s: opening device succeeded", drvthis->name);
|
|
|
|
#ifdef LIBUSB_HAS_GET_DRIVER_NP
|
|
char driver[1024];
|
|
|
|
if (usb_get_driver_np(p->lcd, 0, driver, sizeof(driver)) == 0) {
|
|
report(RPT_WARNING, "%s: interface 0 already claimed by '%s' - detaching",
|
|
drvthis->name, driver);
|
|
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
|
|
if ((usb_detach_kernel_driver_np(p->lcd, 0) < 0))
|
|
report(RPT_ERR, "%s: unable to detach %s driver", drvthis->name, driver);
|
|
#endif
|
|
}
|
|
#endif
|
|
if (usb_claim_interface(p->lcd, 0) < 0) {
|
|
report(RPT_ERR, "%s: cannot claim interface!", drvthis->name);
|
|
usb_close(p->lcd);
|
|
return -1;
|
|
}
|
|
usleep(100);
|
|
if (usb_set_altinterface(p->lcd, 0) < 0)
|
|
report(RPT_WARNING, "%s: unable to set alternate configuration", drvthis->name);
|
|
}
|
|
else {
|
|
report(RPT_ERR, "%s: no device found", drvthis->name);
|
|
return -1;
|
|
}
|
|
#endif /* HAVE_LIBUSB_1_0 */
|
|
|
|
/* 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->height = p->device->height;
|
|
p->info = p->device->description;
|
|
p->cellwidth = LCD_DEFAULT_CELLWIDTH;
|
|
p->cellheight = LCD_DEFAULT_CELLHEIGHT;
|
|
p->ccmode = standard;
|
|
|
|
/* set contrast */
|
|
tmp = drvthis->config_get_int(drvthis->name, "Contrast", 0, DEFAULT_CONTRAST);
|
|
if ((tmp < 0) || (tmp > 1000)) {
|
|
report(RPT_WARNING, "%s: Contrast must be between 0 and 1000; using default %d",
|
|
drvthis->name, DEFAULT_CONTRAST);
|
|
tmp = DEFAULT_CONTRAST;
|
|
}
|
|
p->contrast = tmp;
|
|
|
|
/* set brightness */
|
|
tmp = drvthis->config_get_int(drvthis->name, "Brightness", 0, DEFAULT_BRIGHTNESS);
|
|
if ((tmp < 0) || (tmp > 1000)) {
|
|
report(RPT_WARNING, "%s: Brightness must be between 0 and 1000; using default %d",
|
|
drvthis->name, DEFAULT_BRIGHTNESS);
|
|
tmp = DEFAULT_BRIGHTNESS;
|
|
}
|
|
p->brightness = tmp;
|
|
|
|
/* set brightness while display is off */
|
|
tmp = drvthis->config_get_int(drvthis->name, "OffBrightness", 0, DEFAULT_OFFBRIGHTNESS);
|
|
if ((tmp < 0) || (tmp > 1000)) {
|
|
report(RPT_WARNING, "%s: OffBrightness must be between 0 and 1000; using default %d",
|
|
drvthis->name, DEFAULT_OFFBRIGHTNESS);
|
|
tmp = DEFAULT_OFFBRIGHTNESS;
|
|
}
|
|
p->offbrightness = tmp;
|
|
|
|
/* 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 (tmp = 0; tmp < KEYPAD_LIGHTS; tmp++) {
|
|
char configkey[32];
|
|
|
|
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)) {
|
|
report(RPT_WARNING, "%s: KeyTimeout must be between 0 and 1000; using default %d",
|
|
drvthis->name, DEFAULT_TIMEOUT);
|
|
tmp = DEFAULT_TIMEOUT;
|
|
}
|
|
p->key_timeout = tmp;
|
|
#endif
|
|
|
|
/* Allocate and clear frame buffers */
|
|
p->framebuf = (unsigned char *) malloc(p->width * p->height + 1);
|
|
if (p->framebuf == NULL) {
|
|
report(RPT_ERR, "%s: unable to create framebuf", drvthis->name);
|
|
return -1;
|
|
}
|
|
memset(p->framebuf, ' ', p->width * p->height);
|
|
p->framebuf[p->width * p->height] = '\0';
|
|
|
|
p->lstframe = (unsigned char *) malloc(p->width * p->height + 1);
|
|
if (p->lstframe == NULL) {
|
|
report(RPT_ERR, "%s: unable to create lstframe", drvthis->name);
|
|
return -1;
|
|
}
|
|
memset(p->lstframe, ' ', p->width * p->height);
|
|
p->lstframe[p->width * p->height] = '\0';
|
|
|
|
/* Apply config settings to the display */
|
|
if (p->backlight)
|
|
picoLCD_backlight(drvthis, 1);
|
|
else
|
|
picoLCD_backlight(drvthis, 0);
|
|
|
|
if (p->keylights)
|
|
set_key_lights(p->lcd, p->key_light, 1);
|
|
else
|
|
set_key_lights(p->lcd, p->key_light, 0);
|
|
|
|
picoLCD_set_contrast(drvthis, p->contrast);
|
|
|
|
/* setup LIRC */
|
|
lirchost = drvthis->config_get_string(drvthis->name, "LircHost", 0, NULL);
|
|
lircport = drvthis->config_get_int(drvthis->name, "LircPort", 0, DEFAULT_LIRCPORT);
|
|
/* LIRC is only enabled if a hostname is set */
|
|
p->IRenabled = (lirchost != NULL && *lirchost != '\0') ? 1 : 0;
|
|
|
|
tmp = drvthis->config_get_int(drvthis->name, "LircFlushThreshold", 0, DEFAULT_FLUSH_THRESHOLD_JIFFY);
|
|
/* 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! */
|
|
}
|
|
else if (p->flush_threshold > 0x7FFF) {
|
|
report(RPT_WARNING, "%s: flush threshold to large, using default");
|
|
tmp = DEFAULT_FLUSH_THRESHOLD_JIFFY;
|
|
}
|
|
p->flush_threshold = tmp;
|
|
|
|
/*
|
|
* 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 ((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;
|
|
}
|
|
|
|
/* Restrict LircPort to usable values */
|
|
if ((lircport < 1) || (lircport > 0xFFFF)) {
|
|
report(RPT_WARNING, "%s: invalid LircPort, using default");
|
|
lircport = DEFAULT_LIRCPORT;
|
|
}
|
|
|
|
/* 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_port = htons(lircport); /* server port */
|
|
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Close the driver (do necessary clean-up).
|
|
* \param drvthis Pointer to driver structure.
|
|
*/
|
|
MODULE_EXPORT void
|
|
picoLCD_close(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
if (p != NULL) {
|
|
#ifdef HAVE_LIBUSB_1_0
|
|
int error;
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/* lcd_logical_driver Essential output functions */
|
|
|
|
/**
|
|
* Return the display width in characters.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Number of characters the display is wide.
|
|
*/
|
|
MODULE_EXPORT int
|
|
picoLCD_width(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
return p->width;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the display height in characters.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Number of characters the display is high.
|
|
*/
|
|
MODULE_EXPORT int
|
|
picoLCD_height(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
return p->height;
|
|
}
|
|
|
|
|
|
/**
|
|
* Clear the screen.
|
|
* \param drvthis Pointer to driver structure.
|
|
*/
|
|
MODULE_EXPORT void
|
|
picoLCD_clear(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
memset(p->framebuf, ' ', p->width * p->height);
|
|
p->ccmode = standard;
|
|
|
|
debug(RPT_DEBUG, "%s: clear complete", drvthis->name);
|
|
}
|
|
|
|
|
|
/**
|
|
* Flush data on screen to the display.
|
|
* \param drvthis Pointer to driver structure.
|
|
*/
|
|
MODULE_EXPORT void
|
|
picoLCD_flush(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
unsigned char *fb = p->framebuf;
|
|
unsigned char *lf = p->lstframe;
|
|
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;
|
|
|
|
for (i = 0; i < p->width; i++) {
|
|
if (*fb++ != *lf++) {
|
|
strncpy((char *)text, (char *)p->framebuf + offset, p->width);
|
|
p->device->write(p->lcd, line, 0, text);
|
|
memcpy(p->lstframe + offset, p->framebuf + offset, p->width);
|
|
|
|
debug(RPT_DEBUG, "%s: flush wrote line %d (%s)",
|
|
drvthis->name, line + 1, text);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
debug(RPT_DEBUG, "%s: flush complete\n\t(%s)\n\t(%s)",
|
|
drvthis->name, p->framebuf, p->lstframe);
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a string on the screen at position (x,y).
|
|
* The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param x Horizontal character position (column).
|
|
* \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[])
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
unsigned char *dest;
|
|
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 */
|
|
|
|
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 */
|
|
}
|
|
|
|
dest = p->framebuf + (y * p->width + x);
|
|
memcpy(dest, string, len * sizeof(unsigned char));
|
|
|
|
debug(RPT_DEBUG, "%s: string complete (%s)", drvthis->name, string);
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a character on the screen at position (x,y).
|
|
* The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param x Horizontal character position (column).
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
debug(RPT_DEBUG, "%s: chr start (%c)", drvthis->name, c);
|
|
|
|
if ((y < 1) || (y > p->height) || (x < 1) || (x > p->width))
|
|
return;
|
|
|
|
x--;
|
|
y--; /* Convert 1-based to 0-based */
|
|
|
|
/*
|
|
* Map NUL to character 8 to avoid problems with string handling
|
|
* functions used elsewhere. The custom characters stored in pos. 0-7
|
|
* are repeated in pos. 8-15 (this is a feature of the controller
|
|
* used).
|
|
*/
|
|
if (c == 0)
|
|
c = 8;
|
|
|
|
p->framebuf[y * p->width + x] = c;
|
|
|
|
debug(RPT_DEBUG, "%s: chr complete (%c)", drvthis->name, c);
|
|
}
|
|
|
|
|
|
/* lcd_logical_driver User-defined character functions */
|
|
|
|
/**
|
|
* Define a custom character and write it to the LCD.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param n Custom character to define [0 - (NUM_CCs-1)].
|
|
* \param dat Array of 8 (=cellheight) bytes, each representing a pixel row
|
|
* starting from the top to bottom.
|
|
* 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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
p->device->cchar(drvthis, n, dat);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get total number of custom characters available.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Number of custom characters (always NUM_CCs).
|
|
*/
|
|
MODULE_EXPORT int
|
|
picoLCD_get_free_chars(Driver *drvthis)
|
|
{
|
|
return NUM_CCs;
|
|
}
|
|
|
|
|
|
/* lcd_logical_driver Extended output functions */
|
|
|
|
/**
|
|
* Draw a vertical bar bottom-up.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param x Horizontal character position (column) of the starting point.
|
|
* \param y Vertical character position (row) of the starting point.
|
|
* \param len Number of characters that the bar is high at 100%
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
if (p->ccmode != vbar) {
|
|
unsigned char vBar[p->cellheight];
|
|
int i;
|
|
|
|
if (p->ccmode != standard) {
|
|
/* Not supported(yet) */
|
|
report(RPT_WARNING, "%s: vbar: cannot combine two modes using user-defined characters",
|
|
drvthis->name);
|
|
return;
|
|
}
|
|
p->ccmode = vbar;
|
|
|
|
memset(vBar, 0x00, sizeof(vBar));
|
|
|
|
for (i = 1; i < p->cellheight; i++) {
|
|
/* add pixel line per pixel line ... */
|
|
vBar[p->cellheight - i] = 0xFF;
|
|
picoLCD_set_char(drvthis, i, vBar);
|
|
}
|
|
}
|
|
|
|
lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw a horizontal bar to the right.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param x Horizontal character position (column) of the starting point.
|
|
* \param y Vertical character position (row) of the starting point.
|
|
* \param len Number of characters that the bar is long at 100%
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
if (p->ccmode != hbar) {
|
|
unsigned char hBar[p->cellheight];
|
|
int i;
|
|
|
|
if (p->ccmode != standard) {
|
|
/* Not supported(yet) */
|
|
report(RPT_WARNING, "%s: hbar: cannot combine two modes using user-defined characters",
|
|
drvthis->name);
|
|
return;
|
|
}
|
|
|
|
p->ccmode = hbar;
|
|
|
|
memset(hBar, 0x00, sizeof(hBar));
|
|
|
|
for (i = 1; i <= p->cellwidth; i++) {
|
|
/* fill pixel columns from left to right. */
|
|
memset(hBar, 0x1F & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar));
|
|
picoLCD_set_char(drvthis, i, hBar);
|
|
}
|
|
}
|
|
|
|
lib_hbar_static(drvthis, x, y, len, promille, options, p->cellwidth, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Write a big number to the screen.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int do_init = 0;
|
|
|
|
if ((num < 0) || (num > 10))
|
|
return;
|
|
|
|
if (p->ccmode != bignum) {
|
|
if (p->ccmode != standard) {
|
|
/* Not supported (yet) */
|
|
report(RPT_WARNING, "%s: num: cannot combine two modes using user-defined characters",
|
|
drvthis->name);
|
|
return;
|
|
}
|
|
|
|
p->ccmode = bignum;
|
|
|
|
do_init = 1;
|
|
}
|
|
|
|
/* Lib_adv_bignum does everything needed to show the big numbers. */
|
|
lib_adv_bignum(drvthis, x, num, 0, do_init);
|
|
}
|
|
|
|
|
|
/**
|
|
* Place an icon on the screen.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param x Horizontal character position (column).
|
|
* \param y Vertical character position (row).
|
|
* \param icon symbolic value representing the icon.
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
static unsigned char heart_open[] =
|
|
{
|
|
b_______,
|
|
b___X_X_,
|
|
b__X_X_X,
|
|
b__X___X,
|
|
b__X___X,
|
|
b___X_X_,
|
|
b____X__,
|
|
b_______
|
|
};
|
|
|
|
static unsigned char heart_filled[] =
|
|
{
|
|
b_______,
|
|
b___X_X_,
|
|
b__XXXXX,
|
|
b__XXXXX,
|
|
b__XXXXX,
|
|
b___XXX_,
|
|
b____X__,
|
|
b_______
|
|
};
|
|
|
|
static unsigned char checkbox_gray[] =
|
|
{
|
|
b__X_X_X,
|
|
b_______,
|
|
b__X___X,
|
|
b____X__,
|
|
b__X___X,
|
|
b_______,
|
|
b__X_X_X,
|
|
b_______
|
|
};
|
|
|
|
static unsigned char checkbox_off[] =
|
|
{
|
|
b__XXXXX,
|
|
b__X___X,
|
|
b__X___X,
|
|
b__X___X,
|
|
b__X___X,
|
|
b__X___X,
|
|
b__XXXXX,
|
|
b_______
|
|
};
|
|
|
|
static unsigned char checkbox_on[] =
|
|
{
|
|
b__XXXXX,
|
|
b__X___X,
|
|
b__XX_XX,
|
|
b__X_X_X,
|
|
b__XX_XX,
|
|
b__X___X,
|
|
b__XXXXX,
|
|
b_______
|
|
};
|
|
|
|
/* Icons from the display's character map ROM */
|
|
switch (icon) {
|
|
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;
|
|
}
|
|
|
|
|
|
/* lcd_logical_driver Essential input functions */
|
|
|
|
/**
|
|
* 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 <keynum, 0>. 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 <remaining key,
|
|
* 0>. This will be followed by a final "all clear" key-up <0, 0>. If both
|
|
* keys are released simultaneously, then after <high key, low key>, you will
|
|
* receive <0, 0>. If the keys are pressed down in a staggered fashion, you
|
|
* will receive <first key, 0> followed by <high key, low key> 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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
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;
|
|
|
|
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);
|
|
|
|
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) {
|
|
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]];
|
|
}
|
|
else {
|
|
/* We got two keys */
|
|
static char keybuf[2 * KEYPAD_LABEL_MAX + 1];
|
|
|
|
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]]);
|
|
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) {
|
|
debug(RPT_NOTICE, "%s: get_key irdata, length=%d bytes",
|
|
drvthis->name, keydata.data[1]);
|
|
|
|
/* 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/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 enough for LIRC */
|
|
picolcd_lircsend(drvthis);
|
|
}
|
|
/* We got IR or otherwise bad data */
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
debug(RPT_DEBUG, "%s: get_key complete (%s)", drvthis->name, keystr);
|
|
|
|
if ((keystr != NULL) && (strlen(keystr) > 0))
|
|
return keystr;
|
|
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/* lcd_logical_driver Hardware functions */
|
|
|
|
/**
|
|
* Get current display contrast.
|
|
* This is only the locally stored contrast, the contrast value
|
|
* cannot be retrieved from the device.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Stored contrast in promille.
|
|
*/
|
|
MODULE_EXPORT int
|
|
picoLCD_get_contrast(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
return p->contrast;
|
|
}
|
|
|
|
|
|
/**
|
|
* Change display contrast.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param promille New contrast value in promille.
|
|
*/
|
|
MODULE_EXPORT void
|
|
picoLCD_set_contrast(Driver *drvthis, int promille)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
unsigned char packet[2] = {0x92}; /* set contrast id */
|
|
|
|
/*
|
|
* 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) {
|
|
/* picoLCD20x4 permits contrast as 0/1 value */
|
|
packet[1] = 0x00;
|
|
}
|
|
else {
|
|
int inv = 1000 - promille;
|
|
packet[1] = inv * p->device->contrast_max / 1000;
|
|
}
|
|
}
|
|
else if (promille > 1000) { /* Should not really happen */
|
|
p->contrast = 1000;
|
|
packet[1] = p->device->contrast_min;
|
|
}
|
|
else if (promille <= 0) {
|
|
p->contrast = 0;
|
|
packet[1] = p->device->contrast_max;
|
|
}
|
|
|
|
picolcd_send(p->lcd, packet, 2);
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve brightness.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
return (state == BACKLIGHT_ON) ? p->brightness : p->offbrightness;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set on/off brightness.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
if (promille < 0 || promille > 1000)
|
|
return;
|
|
|
|
if (state == BACKLIGHT_ON)
|
|
p->brightness = promille;
|
|
else
|
|
p->offbrightness = promille;
|
|
}
|
|
|
|
|
|
/**
|
|
* Turn the backlight on or off.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param state New backlight status.
|
|
*/
|
|
MODULE_EXPORT void
|
|
picoLCD_backlight(Driver *drvthis, int state)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
unsigned char packet[2] = {0x91}; /* set backlight id */
|
|
int s;
|
|
|
|
if (state == BACKLIGHT_ON) {
|
|
s = p->brightness / 10;
|
|
if (s > p->device->bklight_max)
|
|
s = p->device->bklight_max;
|
|
packet[1] = (unsigned char) s;
|
|
picolcd_send(p->lcd, packet, 2);
|
|
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;
|
|
if (s > p->device->bklight_min)
|
|
s = p->device->bklight_min;
|
|
packet[1] = (unsigned char) s;
|
|
picolcd_send(p->lcd, packet, 2);
|
|
if (p->linklights) {
|
|
/* Always turn key lights off */
|
|
set_key_lights(p->lcd, p->key_light, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Set output port(s). If the keypad is connected this controls the key lights.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param state Integer with bits representing port states.
|
|
*/
|
|
MODULE_EXPORT void
|
|
picoLCD_output(Driver *drvthis, int state)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int x;
|
|
int m;
|
|
|
|
for (x = 0, m = 1; x < KEYPAD_LIGHTS; x++, m <<= 1) {
|
|
p->key_light[x] = state & m;
|
|
}
|
|
set_key_lights(p->lcd, p->key_light, 1);
|
|
}
|
|
|
|
|
|
/* lcd_logical_driver Informational functions */
|
|
|
|
/**
|
|
* Provide general information about the LCD/VFD display.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Constant string with information.
|
|
*/
|
|
MODULE_EXPORT char *
|
|
picoLCD_get_info(Driver *drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
return p->info;
|
|
}
|
|
|
|
|
|
/* Private functions */
|
|
|
|
/**
|
|
* Transcode from picoLCD USB format to LIRC UDP format.
|
|
* LIRC UDP packets expect 16-bit intervals, with MSB set for space.
|
|
* Intervals are measured in jiffies (1/16384 s).
|
|
* PicoLCD USB packets contain 16-bit intervals, with value negated
|
|
* for space. Intervals are in microseconds.
|
|
* PicoLCD presents the bytes in network order, and they must be put back
|
|
* in that order for transmission via UDP.
|
|
* One jiffy == 61 us. 537 us == 9j.
|
|
*
|
|
* \param drvthis Pointer to driver structure [used for debug() and report()].
|
|
* \param data 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.
|
|
*/
|
|
static void
|
|
ir_transcode(Driver *drvthis, unsigned char *data, unsigned int cbdata)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int i;
|
|
int cIntervals = cbdata >> 1;
|
|
long w = (data[1] << 8) | data[0];
|
|
struct timeval now;
|
|
|
|
/* Check for odd buffer length (invalid buffer) */
|
|
if (cbdata & 1) {
|
|
return;
|
|
}
|
|
|
|
/* 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 %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)
|
|
*/
|
|
struct timeval time_gap;
|
|
int gap = 0x7FFF;
|
|
|
|
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 (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 %04x between %04x and %04x",
|
|
gap, p->lastval, -w & 0xFFFF);
|
|
*p->resptr++ = (unsigned char)(gap & 0xff);
|
|
*p->resptr++ = (unsigned char)((gap >> 8) & 0xff);
|
|
}
|
|
/* Check if there is enough 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++) {
|
|
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;
|
|
}
|
|
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 */
|
|
w |= 0x8000;
|
|
}
|
|
*p->resptr++ = (unsigned char)(w & 0xff);
|
|
*p->resptr++ = (unsigned char)((w >> 8) & 0xff);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
/* 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;
|
|
logptr += sprintf(logptr, " %04x", val);
|
|
}
|
|
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: sent %d bytes to lirc(udp)", len);
|
|
}
|
|
p->resptr = p->result;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Send raw data to the display using low level usb_interrupt_write.
|
|
* \param lcd pointer to device handle
|
|
* \param data pointer to data packet to send
|
|
* \param size number of bytes to send
|
|
*/
|
|
static void
|
|
picolcd_send(USB_DEVICE_HANDLE * lcd, unsigned char *data, int size)
|
|
{
|
|
if ((lcd == NULL) && (data == NULL))
|
|
return;
|
|
#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.
|
|
* \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_DEVICE_HANDLE * lcd, const int row, const int col, const unsigned char *data)
|
|
{
|
|
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}
|
|
};
|
|
int len = strlen((char *)data);
|
|
|
|
/* Cut off at display width */
|
|
if (len > 20)
|
|
len = 20;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Fill in an send packet */
|
|
packet[4] = len;
|
|
memcpy(packet + 5, data, len);
|
|
picolcd_send(lcd, packet, 5 + len);
|
|
}
|
|
|
|
|
|
/**
|
|
* Write function for 20x2 OEM displays.
|
|
* \param lcd pointer to device handle
|
|
* \param row Row to place the string at
|
|
* \param col Column to place the string at
|
|
* \param data pointer to NUL terminated string
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* FIXME: Is it possible that data is written beyond the end of
|
|
* the selected row? Shouldn't cut off happen at (20 - col)?
|
|
*/
|
|
if (len > 20)
|
|
len = 20;
|
|
|
|
/* prepare and send packet */
|
|
packet[1] = row;
|
|
packet[2] = col;
|
|
packet[3] = len;
|
|
|
|
memcpy(packet + 4, data, len);
|
|
|
|
picolcd_send(lcd, packet, 4 + len);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
unsigned char packet[10] = {0x9c}; /* define character */
|
|
unsigned char mask = (1 << p->cellwidth) - 1;
|
|
int row;
|
|
|
|
if ((n < 0) || (n >= NUM_CCs))
|
|
return;
|
|
if (!dat)
|
|
return;
|
|
|
|
packet[1] = n; /* Custom char to define. */
|
|
|
|
for (row = 0; row < p->cellheight; row++) {
|
|
packet[row + 2] = dat[row] & mask;
|
|
}
|
|
|
|
picolcd_send(p->lcd, packet, 10);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
if ((n < 0) || (n >= NUM_CCs))
|
|
return;
|
|
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 */
|
|
|
|
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 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)
|
|
{
|
|
int ret;
|
|
|
|
memset(packet->data, 0, 255);
|
|
packet->type = 0;
|
|
|
|
ret = usb_interrupt_read(lcd, USB_ENDPOINT_IN + 1, (char *)packet->data, PICOLCD_MAX_DATA_LEN, 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;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* 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_DEVICE_HANDLE * lcd, int keys[], int state)
|
|
{
|
|
unsigned char packet[2] = {0x81}; /* set led */
|
|
unsigned int leds = 0;
|
|
int i;
|
|
|
|
if (state) {
|
|
/* Only LEDs we want on */
|
|
for (i = 0; i < KEYPAD_LIGHTS; i++)
|
|
if (keys[i])
|
|
leds |= (1 << i);
|
|
else
|
|
leds &= ~(1 << i);
|
|
}
|
|
else {
|
|
/* All LEDs off */
|
|
leds = 0;
|
|
}
|
|
|
|
packet[1] = leds;
|
|
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 */
|