#include #include #include #include #include #include #include #include #include #include "event.h" #include "lcd.h" #include "logging.h" #define _LCD_DEBUG 1 #define LCD_RESET_TIMEOUT_US 250000 /* 250ms */ #define LCD_RESET_RETRY_TIMEOUT 10 /* 10s */ #define A125_CMD_GETBUTTON { 0x4D, 0x06 } // not tried #define A125_CMD_SETLINE { 0x4D, 0x0C, 0x00, 0x10 } // [2] is line, append 16 chars #define A125_CMD_CLEAR { 0x4D, 0x0D } // works, but slow #define A125_CMD_BACKLIGHT { 0x4D, 0x5E, 0x00 } // [2] is on/off #define A125_CMD_RESET { 0x4D, 0xFF } #define A125_EVENT_ID { 0x53, 0x01 } // never seen #define A125_EVENT_BUTTON1 { 0x53, 0x05, 0x00, 0x01 } #define A125_EVENT_BUTTON2 { 0x53, 0x05, 0x00, 0x02 } #define A125_EVENT_VERSION { 0x53, 0x08 } // never seen #define A125_EVENT_ACTINFO { 0x53, 0xAA } #define A125_EVENT_INVALID { 0x53, 0xFB } // never seen enum lcdstate { LCD_STATE_NOT_INITIALIZED = 0x00, LCD_STATE_INITIALIZING, LCD_STATE_INITIALIZING_FAILED, LCD_STATE_READY, }; struct lcddev { int fd; struct event_fd *fakedevice_event; struct termios oldtio; enum lcdstate state; int backlight_state; int (*event_callback)(struct lcddev *dev, int event, void *privdata); void *event_privdata; const char *line_data[2]; int line_length[2]; int scroll_speed; int scroll_pos; struct event_fd *read_event; struct event_timeout *reset_timeout; struct event_timeout *backlight_timeout; struct event_timeout *scroll_timeout; }; void lcd_close(struct lcddev *dev) { if (dev->fakedevice_event == NULL) { tcsetattr(dev->fd, TCSANOW, &dev->oldtio); } else { int fd = event_get_fd(dev->fakedevice_event); event_remove_fd(dev->fakedevice_event); dev->fakedevice_event = NULL; close(fd); } close(dev->fd); } static int lcd_open(struct lcddev *dev, const char *device) { dev->fd = open(device, O_RDWR); if (dev->fd < 0) { log_print(LOG_ERROR, "failed to open '%s'", device); return -1; } tcgetattr(dev->fd, &dev->oldtio); struct termios newtio; memset(&newtio, 0, sizeof(newtio)); newtio.c_iflag |= IGNBRK; newtio.c_lflag &= ~(ISIG | ICANON | ECHO); newtio.c_cflag = B1200 | CS8 | CLOCAL | CREAD; newtio.c_cc[VMIN] = 1; newtio.c_cc[VTIME] = 0; cfsetospeed(&newtio, B1200); cfsetispeed(&newtio, B1200); int err = tcsetattr(dev->fd, TCSAFLUSH, &newtio); if (err < 0) { log_print(LOG_ERROR, "failed to set termios"); close(dev->fd); return -1; } return 0; } static int lcd_fakedevice_reply(int fd, void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; char buf[32]; if (read(fd, buf, sizeof(buf)) <= 0) { dev->fakedevice_event = NULL; return -1; } char reset_expect[] = A125_CMD_RESET; if (memcmp(buf, reset_expect, sizeof(reset_expect)) == 0) { char actinfo_cmd[] = A125_EVENT_ACTINFO; write(fd, actinfo_cmd, sizeof(actinfo_cmd)); } return 0; } static int lcd_fakedevice_open(struct lcddev *dev) { int fd[2]; if (socketpair(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0 , fd) < 0) { log_print(LOG_ERROR, "socketpair() failed"); return -1; } dev->fd = fd[0]; dev->fakedevice_event = event_add_readfd(NULL, fd[1], lcd_fakedevice_reply, dev); return 0; } #if (_LCD_DEBUG > 1) static void lcd_dump(const char *prefix, int len, int size, const char *buf) { int i; fprintf(stderr, "%s:[%d/%d]: ", prefix, len, size); for (i = 0; i < len; i++) fprintf(stderr, "0x%X ", (unsigned char)buf[i]); fprintf(stderr, "\n"); } #endif static int lcd_read(struct lcddev *dev, const char *buf, int len) { int retval = 0, cnt = 0; while (cnt < len) { retval = read(dev->fd, (char *)buf + cnt, len - cnt); if (retval <= 0) break; cnt += retval; } #if (_LCD_DEBUG > 1) lcd_dump("lcd_read", cnt, len, buf); #endif return (cnt != 0) ? cnt : retval; } static int lcd_write(struct lcddev *dev, char *buf, int len) { int retval = write(dev->fd, buf, len); #if (_LCD_DEBUG > 1) lcd_dump("lcd_write", retval, len, buf); #endif return retval; } static int lcd_reset_retry_timeout_cb(void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; dev->reset_timeout = NULL; lcd_reset(dev); return -1; /* singleshot */ } static int lcd_reset_timeout_cb(void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; log_print(LOG_ERROR, "failed to initalize LCD"); dev->state = LCD_STATE_INITIALIZING_FAILED; struct timeval tv = { .tv_sec = LCD_RESET_RETRY_TIMEOUT }; dev->reset_timeout = event_add_timeout(&tv, lcd_reset_retry_timeout_cb, dev); return -1; /* singleshot */ } void lcd_reset(struct lcddev *dev) { #if (_LCD_DEBUG > 0) fprintf(stderr, "lcd_reset()\n"); #endif char cmd[] = A125_CMD_RESET; lcd_write(dev, cmd, sizeof(cmd)); dev->backlight_state = -1; dev->state = LCD_STATE_INITIALIZING; struct timeval tv = { .tv_usec = LCD_RESET_TIMEOUT_US }; dev->reset_timeout = event_add_timeout(&tv, lcd_reset_timeout_cb, dev); } static int lcd_backlight(struct lcddev *dev, int mode) { if (dev->state != LCD_STATE_READY) return -1; #if (_LCD_DEBUG > 0) fprintf(stderr, "lcd_backlight(%d)\n", mode); #endif if (dev->backlight_state != mode) { char cmd[] = A125_CMD_BACKLIGHT; cmd[2] = (mode) ? 0x01 : 0x00; lcd_write(dev, cmd, sizeof(cmd)); } dev->backlight_state = mode; return 0; } static int lcd_backlight_timeout_cb(void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; dev->backlight_timeout = NULL; lcd_backlight(dev, 0); dev->event_callback(dev, LCD_EVENT_BACKLIGHT, dev->event_privdata); return -1; /* singleshot */ } int lcd_trigger_backlight(struct lcddev *dev, int timeout) { if (dev->backlight_timeout != NULL) { event_remove_timeout(dev->backlight_timeout); dev->backlight_timeout = NULL; } int retval = lcd_backlight(dev, (timeout == 0) ? 0 : 1); if (timeout != -1) { struct timeval tv = { .tv_sec = timeout }; dev->backlight_timeout = event_add_timeout(&tv, lcd_backlight_timeout_cb, dev); } return retval; } static int lcd_read_cb(int fd, void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; char buf[4]; int size = (dev->state != LCD_STATE_INITIALIZING) ? sizeof(buf) : 2; int len = lcd_read(dev, buf, size); if (dev->state == LCD_STATE_INITIALIZING) { char expect[] = A125_EVENT_ACTINFO; if (len != sizeof(expect)) return 0; if (memcmp(buf, expect, sizeof(expect)) == 0) { event_remove_timeout(dev->reset_timeout); dev->reset_timeout = NULL; dev->state = LCD_STATE_READY; /* trigger application to set data */ dev->event_callback(dev, LCD_EVENT_INIT, dev->event_privdata); } } else if (dev->state == LCD_STATE_READY) { char expect1[] = A125_EVENT_BUTTON1; char expect2[] = A125_EVENT_BUTTON2; if (len != sizeof(expect1) && len != sizeof(expect2)) return 0; if (memcmp(buf, expect1, sizeof(buf)) == 0) { dev->event_callback(dev, LCD_EVENT_BUTTON1, dev->event_privdata); } else if (memcmp(buf, expect2, sizeof(buf)) == 0) { dev->event_callback(dev, LCD_EVENT_BUTTON2, dev->event_privdata); } } return 0; } static int lcd_setline(struct lcddev *dev, int line, int len, const char *buf) { if (dev->state != LCD_STATE_READY) return -1; char cmd[20 +1] = A125_CMD_SETLINE; cmd[2] = line; memset(cmd +4, ' ', 16); memcpy(cmd +4, buf, (len > 16) ? 16 : len); cmd[20] = '\0'; #if (_LCD_DEBUG > 0) fprintf(stderr, "lcd_setline(%d, '%-16s')\n", line, cmd +4); #endif lcd_write(dev, cmd, 20); return 0; } static int lcd_scroll_timeout_cb(void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; int i, reset_pos = 0; for (i = 0; i < 2; i++) { int line_pos = dev->scroll_pos; if (line_pos < 0) line_pos = 0; /* mark line as complete if one space is visible after message */ if ((dev->line_length[i] - line_pos) <= 15) { reset_pos++; } /* do not scroll further if message is not visible */ if ((dev->line_length[i] - line_pos) < 0) { line_pos = dev->line_length[i]; } lcd_setline(dev, i, dev->line_length[i] - line_pos, dev->line_data[i] + line_pos); } dev->scroll_pos++; if (reset_pos == 2) dev->scroll_pos = -1; /* disable scrolling if backlight is off (display is not visible) */ if (dev->backlight_state == 0) { dev->scroll_timeout = NULL; return -1; } /* periodic */ return 0; } int lcd_setlines(struct lcddev *dev, int scroll_speed, const char *line1, const char *line2) { dev->line_data[0] = line1; dev->line_length[0] = (line1 != NULL) ? strlen(line1) : 0; lcd_setline(dev, 0, dev->line_length[0], dev->line_data[0]); dev->line_data[1] = line2; dev->line_length[1] = (line2 != NULL) ? strlen(line2) : 0; lcd_setline(dev, 1, dev->line_length[1], dev->line_data[1]); int scroll_enable = (dev->line_length[0] > 16 && dev->line_length[1] > 16); if (dev->scroll_timeout && ((dev->scroll_speed != scroll_speed) || !scroll_enable)) { event_remove_timeout(dev->scroll_timeout); dev->scroll_timeout = NULL; } dev->scroll_speed = scroll_speed; dev->scroll_pos = 0; if ((dev->scroll_timeout == NULL) && scroll_enable && (scroll_speed > 0)) { struct timeval tv = { .tv_usec = 1000 * dev->scroll_speed }; dev->scroll_timeout = event_add_timeout(&tv, lcd_scroll_timeout_cb, dev); } return 0; } struct lcddev * lcd_init(const char *devicename, int (*event_callback)(struct lcddev *dev, int button, void *privdata), void *event_privdata) { struct lcddev *dev = malloc(sizeof(struct lcddev)); if (dev == NULL) { log_print(LOG_ERROR, "lcd_init(): out of memory"); return NULL; } memset(dev, 0, sizeof(struct lcddev)); int retval; if (devicename != NULL) { retval = lcd_open(dev, devicename); } else { retval = lcd_fakedevice_open(dev); } if (retval < 0) { free(dev); return NULL; } dev->event_callback = event_callback; dev->event_privdata = event_privdata; dev->read_event = event_add_readfd(NULL, dev->fd, lcd_read_cb, dev); lcd_reset(dev); return dev; }