/*************************************************************************** * Copyright (C) 05/2011 by Olaf Rempel * * razzor@kopf-tisch.de * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "event.h" #include "lcd.h" #include "list.h" #include "logging.h" #define _LCD_DEBUG 0 #define _LCD_DUMMY 1 #define LCD_SCROLL_SPEED 750 /* 750ms */ #define LCD_RESET_TIMEOUT 250 /* 250ms */ #define LCD_RESET_RETRY_TIMEOUT 10000 /* 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 struct lcdpage { struct list_head list; int priority; int (*callback)(struct lcddev *dev, int event, void *privdata); void *privdata; }; enum lcdstate { LCD_STATE_NOT_INITIALIZED = 0x00, LCD_STATE_INITIALIZING, LCD_STATE_INITIALIZING_FAILED, LCD_STATE_READY, }; struct lcddev { int fd; #if (_LCD_DUMMY) struct event_fd *fakedevice_event; #endif /* (_LCD_DUMMY) */ struct termios oldtio; enum lcdstate state; int backlight_enabled; int backlight_timeout_ms; struct list_head page_list; struct lcdpage *current_page; const char *line_data[2]; int line_length[2]; int scroll_pos; int update_ms; struct event_fd *read_event; struct event_timeout *reset_timeout; struct event_timeout *backlight_timeout; struct event_timeout *scroll_timeout; struct event_timeout *update_timeout; }; static void lcd_pagecallback(struct lcddev *dev, int event); void lcd_close(struct lcddev *dev) { if (dev->current_page) { dev->current_page->callback(dev, LCDPAGE_EVENT_EXIT, dev->current_page->privdata); dev->current_page = NULL; } struct lcdpage *search, *tmp; list_for_each_entry_safe(search, tmp, &dev->page_list, list) { free(search); } if (dev->reset_timeout) { event_remove_timeout(dev->reset_timeout); dev->reset_timeout = NULL; } if (dev->backlight_timeout) { event_remove_timeout(dev->backlight_timeout); dev->backlight_timeout = NULL; } if (dev->update_timeout) { event_remove_timeout(dev->update_timeout); dev->update_timeout = NULL; } if (dev->scroll_timeout) { event_remove_timeout(dev->scroll_timeout); dev->scroll_timeout = NULL; } if (dev->read_event) { event_remove_fd(dev->read_event); dev->read_event = NULL; } #if (_LCD_DUMMY) if (dev->fakedevice_event != NULL) { int fd = event_get_fd(dev->fakedevice_event); event_remove_fd(dev->fakedevice_event); dev->fakedevice_event = NULL; close(fd); } else #endif /* (_LCD_DUMMY) */ { tcsetattr(dev->fd, TCSANOW, &dev->oldtio); } close(dev->fd); free(dev); } static int lcd_realdevice_open(struct lcddev *dev, const char *device) { dev->fd = open(device, O_RDWR | O_NOCTTY); if (dev->fd < 0) { log_print(LOG_ERROR, "%s(): failed to open '%s'", __FUNCTION__, device); return -1; } if (fcntl(dev->fd, F_SETFD, FD_CLOEXEC) < 0) { log_print(LOG_ERROR, "%s(): fcntl(FD_CLOEXEC)", __FUNCTION__); close(dev->fd); 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, "%s(): failed to set termios", __FUNCTION__); close(dev->fd); return -1; } return 0; } #if (_LCD_DUMMY) 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, 0 , fd) < 0) { log_print(LOG_ERROR, "%s(): socketpair() failed", __FUNCTION__); return -1; } if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0) { log_print(LOG_ERROR, "%s(): fcntl(FD_CLOEXEC)", __FUNCTION__); close(fd[0]); close(fd[1]); return -1; } if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) < 0) { log_print(LOG_ERROR, "%s(): fcntl(FD_CLOEXEC)", __FUNCTION__); close(fd[0]); close(fd[1]); return -1; } dev->fd = fd[0]; dev->fakedevice_event = event_add_readfd(NULL, fd[1], lcd_fakedevice_reply, dev); return 0; } #endif /* (_LCD_DUMMY) */ #if (_LCD_DEBUG > 1) static void lcd_dump(const char *prefix, int len, int size, const char *data) { int i; int pos = 0; char buf[256]; for (i = 0; i < len; i++) { pos += snprintf(buf, sizeof(buf) - pos, "0x%X ", (unsigned char)data[i]); } log_print(LOG_DEBUG, "%s:[%d/%d]: %s", prefix, len, size, buf); } #endif /* (_LCD_DEBUG > 1) */ 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(__FUNCTION__, cnt, len, buf); #endif /* (_LCD_DEBUG > 1) */ 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(__FUNCTION__, retval, len, buf); #endif /* (_LCD_DEBUG > 1) */ return retval; } static void lcd_reset(struct lcddev *dev); static int lcd_reset_retry_timeout_cb(int timerid, void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; dev->reset_timeout = NULL; lcd_reset(dev); return -1; /* singleshot */ } static int lcd_reset_timeout_cb(int timerid, void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; log_print(LOG_ERROR, "failed to initalize LCD"); dev->state = LCD_STATE_INITIALIZING_FAILED; dev->reset_timeout = event_add_timeout_ms(LCD_RESET_RETRY_TIMEOUT, lcd_reset_retry_timeout_cb, 0, dev); return -1; /* singleshot */ } static void lcd_reset(struct lcddev *dev) { #if (_LCD_DEBUG > 0) log_print(LOG_DEBUG, "%s()", __FUNCTION__); #endif /* (_LCD_DEBUG > 0) */ char cmd[] = A125_CMD_RESET; lcd_write(dev, cmd, sizeof(cmd)); /* force next backlight command */ dev->backlight_enabled = -1; dev->state = LCD_STATE_INITIALIZING; dev->reset_timeout = event_add_timeout_ms(LCD_RESET_TIMEOUT, lcd_reset_timeout_cb, 0, dev); } static int lcd_backlight(struct lcddev *dev, int enable) { if (dev->state != LCD_STATE_READY) return -1; #if (_LCD_DEBUG > 0) log_print(LOG_DEBUG, "%s(%d)", __FUNCTION__, enable); #endif /* (_LCD_DEBUG > 0) */ if (dev->backlight_enabled != enable) { char cmd[] = A125_CMD_BACKLIGHT; cmd[2] = (enable) ? 0x01 : 0x00; lcd_write(dev, cmd, sizeof(cmd)); dev->backlight_enabled = enable; } return 0; } static int lcd_backlight_timeout_cb(int timerid, void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; dev->backlight_timeout = NULL; lcd_set_backlight(dev, 0); return -1; /* singleshot */ } int lcd_set_backlight(struct lcddev *dev, int enable) { if (dev->backlight_timeout != NULL) { event_remove_timeout(dev->backlight_timeout); dev->backlight_timeout = NULL; } if (!enable) { lcd_pagecallback(dev, LCDPAGE_EVENT_BACKLIGHT); /* callback re-enabled backlight */ if (dev->backlight_timeout != NULL) { return 0; } } int retval = lcd_backlight(dev, enable); if (enable) { /* start backlight timeout */ dev->backlight_timeout = event_add_timeout_ms(dev->backlight_timeout_ms, lcd_backlight_timeout_cb, 0, dev); } else { /* disable updates (display is not visible) */ if (dev->update_timeout != NULL) { event_remove_timeout(dev->update_timeout); dev->update_timeout = NULL; } /* disable scrolling (display is not visible) */ if (dev->scroll_timeout != NULL) { event_remove_timeout(dev->scroll_timeout); dev->scroll_timeout = NULL; } } return retval; } static int lcd_update_timeout_cb(int timerid, void *privdata) { struct lcddev *dev = (struct lcddev *)privdata; dev->update_timeout = NULL; lcd_pagecallback(dev, LCDPAGE_EVENT_UPDATE); return -1; /* singleshot */ } static void lcd_update_start(struct lcddev *dev, int update_ms) { dev->update_ms = update_ms; if (dev->update_timeout != NULL) { event_remove_timeout(dev->update_timeout); dev->update_timeout = NULL; } if (update_ms > 0) { dev->update_timeout = event_add_timeout_ms(update_ms, lcd_update_timeout_cb, 0, dev); } } 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 */ lcd_pagecallback(dev, LCDPAGE_EVENT_ENTER); } } 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) { lcd_pagecallback(dev, LCDPAGE_EVENT_BUTTON1); } else if (memcmp(buf, expect2, sizeof(buf)) == 0) { lcd_pagecallback(dev, LCDPAGE_EVENT_BUTTON2); } } 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) log_print(LOG_DEBUG, "%s(%d, '%-16s')", __FUNCTION__, line, cmd +4); #endif /* (_LCD_DEBUG > 0) */ lcd_write(dev, cmd, 20); return 0; } static int lcd_scroll_timeout_cb(int timerid, 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; /* periodic */ return 0; } static void lcd_scroll_start(struct lcddev *dev) { if (dev->scroll_timeout != NULL) { event_remove_timeout(dev->scroll_timeout); dev->scroll_timeout = NULL; } if ((dev->line_length[0] > 16) || (dev->line_length[1] > 16)) { dev->scroll_timeout = event_add_timeout_ms(LCD_SCROLL_SPEED, lcd_scroll_timeout_cb, 0, dev); } } int lcd_setlines(struct lcddev *dev, const char *line1, const char *line2) { int i; int line_length[2] = { 0, 0 }; const char *line[2] = { line1, line2 }; for (i = 0; i < 2; i++) { if (line[i] != NULL) { line_length[i] = strlen(line[i]); if ((line_length[i] == dev->line_length[i]) && (dev->line_data[i] != NULL)) { if (memcmp(line[i], dev->line_data[i], line_length[i]) == 0) { /* same data, no update needed */ continue; } } } if (dev->line_data[i] != NULL) { free((void *)dev->line_data[i]); dev->line_data[i] = NULL; } if (line[i] != NULL) { dev->line_data[i] = strdup(line[i]); } dev->line_length[i] = line_length[i]; if (dev->backlight_enabled) { lcd_setline(dev, i, dev->line_length[i], dev->line_data[i]); dev->scroll_pos = 0; lcd_scroll_start(dev); } } return 0; } static void lcd_pagecallback(struct lcddev *dev, int event) { if (dev->state != LCD_STATE_READY) { return; } if (dev->current_page == NULL) { if (list_empty(&dev->page_list)) { return; } dev->current_page = list_entry(dev->page_list.next, struct lcdpage, list); } if ((dev->backlight_enabled == 0x00) && ((event == LCDPAGE_EVENT_BUTTON1) || (event == LCDPAGE_EVENT_BUTTON2))) { lcd_set_backlight(dev, 1); event = LCDPAGE_EVENT_UPDATE; /* restart scrolling if needed */ lcd_scroll_start(dev); } int next; do { #if (_LCD_DEBUG > 0) log_print(LOG_DEBUG, "%s: cb(%p, 0x%x)", __FUNCTION__, dev->current_page, event); #endif int retval = dev->current_page->callback(dev, event, dev->current_page->privdata); #if (_LCD_DEBUG > 0) log_print(LOG_DEBUG, "%s: cb(%p, 0x%x) => 0x%x", __FUNCTION__, dev->current_page, event, retval); #endif next = (retval == LCDPAGE_COMMAND_NEXT); if (next) { struct lcdpage *page = dev->current_page; if (page->list.next != &dev->page_list) { page = list_entry(page->list.next, struct lcdpage, list); } else { page = list_entry(page->list.next->next, struct lcdpage, list); } /* no other page ready */ if (page == dev->current_page) { break; } /* remove update interval for next page */ if (dev->update_timeout != NULL) { event_remove_timeout(dev->update_timeout); dev->update_timeout = NULL; } /* remove scroll interval for next page */ if (dev->scroll_timeout != NULL) { event_remove_timeout(dev->scroll_timeout); dev->scroll_timeout = NULL; } dev->current_page->callback(dev, LCDPAGE_EVENT_EXIT, dev->current_page->privdata); dev->current_page = page; event = LCDPAGE_EVENT_ENTER; /* retval is update interval */ } else if ((event != LCDPAGE_EVENT_BACKLIGHT) && (event != LCDPAGE_EVENT_EXIT)) { lcd_update_start(dev, retval); } } while (next); } int lcd_addpage_cb(struct lcddev *dev, int priority, int (*event_callback)(struct lcddev *dev, int event, void *privdata), void *event_privdata) { struct lcdpage *page = malloc(sizeof(struct lcdpage)); if (page == NULL) { log_print(LOG_ERROR, "%s(): out of memory", __FUNCTION__); return -1; } page->priority = priority; page->callback = event_callback; page->privdata = event_privdata; int inserted = 0; struct lcdpage *search; list_for_each_entry(search, &dev->page_list, list) { if (page->priority > search->priority) { list_add_tail(&page->list, &search->list); inserted = 1; break; } } if (!inserted) { list_add_tail(&page->list, &dev->page_list); } if (dev->current_page == NULL) { lcd_pagecallback(dev, LCDPAGE_EVENT_ENTER); } return 0; } struct lcddev * lcd_open(const char *devicename, int backlight_timeout) { struct lcddev *dev = malloc(sizeof(struct lcddev)); if (dev == NULL) { log_print(LOG_ERROR, "%s(): out of memory", __FUNCTION__); return NULL; } memset(dev, 0, sizeof(struct lcddev)); INIT_LIST_HEAD(&dev->page_list); int retval; #if (_LCD_DUMMY) if (strncmp(devicename, "dummy", 5) == 0) { retval = lcd_fakedevice_open(dev); } else #endif /* (_LCD_DUMMY) */ { retval = lcd_realdevice_open(dev, devicename); } if (retval < 0) { free(dev); return NULL; } dev->read_event = event_add_readfd(NULL, dev->fd, lcd_read_cb, dev); dev->backlight_timeout_ms = backlight_timeout * 1000; lcd_reset(dev); return dev; }