qnapd/lcd.c

446 lines
10 KiB
C
Raw Normal View History

2011-04-17 16:59:51 +02:00
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
2011-04-25 13:26:34 +02:00
#include <sys/socket.h>
2011-04-17 16:59:51 +02:00
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include "event.h"
#include "lcd.h"
#include "logging.h"
#define _LCD_DEBUG 1
2011-04-18 21:59:49 +02:00
#define LCD_RESET_TIMEOUT_US 250000 /* 250ms */
#define LCD_RESET_RETRY_TIMEOUT 10 /* 10s */
2011-04-17 16:59:51 +02:00
#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
2011-04-18 21:59:49 +02:00
enum lcdstate {
LCD_STATE_NOT_INITIALIZED = 0x00,
LCD_STATE_INITIALIZING,
LCD_STATE_INITIALIZING_FAILED,
LCD_STATE_READY,
};
2011-04-17 16:59:51 +02:00
struct lcddev {
int fd;
2011-04-25 13:26:34 +02:00
struct event_fd *fakedevice_event;
2011-04-17 16:59:51 +02:00
struct termios oldtio;
2011-04-18 21:59:49 +02:00
enum lcdstate state;
2011-04-22 14:55:36 +02:00
int backlight_state;
int (*event_callback)(struct lcddev *dev, int event, void *privdata);
void *event_privdata;
2011-04-17 16:59:51 +02:00
2011-04-18 21:59:49 +02:00
const char *line_data[2];
2011-04-22 14:55:36 +02:00
int line_length[2];
int scroll_speed;
int scroll_pos;
2011-04-18 21:59:49 +02:00
2011-04-17 16:59:51 +02:00
struct event_fd *read_event;
2011-04-18 21:59:49 +02:00
struct event_timeout *reset_timeout;
struct event_timeout *backlight_timeout;
struct event_timeout *scroll_timeout;
2011-04-17 16:59:51 +02:00
};
2011-04-17 17:37:38 +02:00
void lcd_close(struct lcddev *dev)
2011-04-17 16:59:51 +02:00
{
2011-05-21 12:07:02 +02:00
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->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;
}
2011-04-25 13:26:34 +02:00
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);
}
2011-04-17 16:59:51 +02:00
close(dev->fd);
2011-05-21 12:07:02 +02:00
free(dev);
2011-04-17 16:59:51 +02:00
}
static int lcd_open(struct lcddev *dev, const char *device)
{
2011-04-18 21:59:49 +02:00
dev->fd = open(device, O_RDWR);
if (dev->fd < 0) {
2011-04-17 16:59:51 +02:00
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;
}
2011-04-25 13:26:34 +02:00
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;
}
2011-04-22 14:55:36 +02:00
#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
2011-04-17 16:59:51 +02:00
static int lcd_read(struct lcddev *dev, const char *buf, int len)
{
2011-04-22 14:55:36 +02:00
int retval = 0, cnt = 0;
2011-04-17 16:59:51 +02:00
while (cnt < len) {
retval = read(dev->fd, (char *)buf + cnt, len - cnt);
if (retval <= 0)
break;
cnt += retval;
}
2011-04-22 14:55:36 +02:00
#if (_LCD_DEBUG > 1)
lcd_dump("lcd_read", cnt, len, buf);
2011-04-17 16:59:51 +02:00
#endif
return (cnt != 0) ? cnt : retval;
}
static int lcd_write(struct lcddev *dev, char *buf, int len)
{
2011-04-22 14:55:36 +02:00
int retval = write(dev->fd, buf, len);
2011-04-18 21:59:49 +02:00
2011-04-22 14:55:36 +02:00
#if (_LCD_DEBUG > 1)
lcd_dump("lcd_write", retval, len, buf);
2011-04-17 16:59:51 +02:00
#endif
return retval;
}
2011-04-18 21:59:49 +02:00
static int lcd_reset_retry_timeout_cb(void *privdata)
2011-04-17 16:59:51 +02:00
{
2011-04-18 21:59:49 +02:00
struct lcddev *dev = (struct lcddev *)privdata;
2011-04-22 14:55:36 +02:00
dev->reset_timeout = NULL;
2011-04-17 16:59:51 +02:00
2011-04-18 21:59:49 +02:00
lcd_reset(dev);
return -1; /* singleshot */
2011-04-17 16:59:51 +02:00
}
2011-04-18 21:59:49 +02:00
static int lcd_reset_timeout_cb(void *privdata)
2011-04-17 16:59:51 +02:00
{
2011-04-18 21:59:49 +02:00
struct lcddev *dev = (struct lcddev *)privdata;
2011-04-17 16:59:51 +02:00
2011-04-18 21:59:49 +02:00
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 */
2011-04-17 16:59:51 +02:00
}
2011-04-18 21:59:49 +02:00
void lcd_reset(struct lcddev *dev)
2011-04-17 16:59:51 +02:00
{
2011-04-22 14:55:36 +02:00
#if (_LCD_DEBUG > 0)
fprintf(stderr, "lcd_reset()\n");
#endif
2011-04-18 21:59:49 +02:00
char cmd[] = A125_CMD_RESET;
lcd_write(dev, cmd, sizeof(cmd));
2011-04-17 16:59:51 +02:00
2011-04-22 14:55:36 +02:00
dev->backlight_state = -1;
2011-04-18 21:59:49 +02:00
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);
}
2011-04-17 16:59:51 +02:00
2011-04-18 21:59:49 +02:00
static int lcd_backlight(struct lcddev *dev, int mode)
{
if (dev->state != LCD_STATE_READY)
return -1;
2011-04-22 14:55:36 +02:00
#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;
2011-04-17 16:59:51 +02:00
return 0;
}
static int lcd_backlight_timeout_cb(void *privdata)
{
struct lcddev *dev = (struct lcddev *)privdata;
2011-04-18 21:59:49 +02:00
dev->backlight_timeout = NULL;
2011-04-17 16:59:51 +02:00
lcd_backlight(dev, 0);
2011-04-22 14:55:36 +02:00
dev->event_callback(dev, LCD_EVENT_BACKLIGHT, dev->event_privdata);
2011-04-17 16:59:51 +02:00
return -1; /* singleshot */
}
2011-04-18 21:59:49 +02:00
int lcd_trigger_backlight(struct lcddev *dev, int timeout)
2011-04-17 16:59:51 +02:00
{
2011-04-18 21:59:49 +02:00
if (dev->backlight_timeout != NULL) {
event_remove_timeout(dev->backlight_timeout);
dev->backlight_timeout = NULL;
2011-04-17 16:59:51 +02:00
}
2011-04-22 14:55:36 +02:00
int retval = lcd_backlight(dev, (timeout == 0) ? 0 : 1);
2011-04-18 21:59:49 +02:00
2011-04-22 14:55:36 +02:00
if (timeout != -1) {
struct timeval tv = { .tv_sec = timeout };
dev->backlight_timeout = event_add_timeout(&tv, lcd_backlight_timeout_cb, dev);
}
2011-04-18 21:59:49 +02:00
return retval;
2011-04-17 16:59:51 +02:00
}
static int lcd_read_cb(int fd, void *privdata)
{
struct lcddev *dev = (struct lcddev *)privdata;
2011-04-18 21:59:49 +02:00
2011-04-17 16:59:51 +02:00
char buf[4];
2011-04-18 21:59:49 +02:00
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 */
2011-04-22 14:55:36 +02:00
dev->event_callback(dev, LCD_EVENT_INIT, dev->event_privdata);
2011-04-18 21:59:49 +02:00
}
} 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) {
2011-04-22 14:55:36 +02:00
dev->event_callback(dev, LCD_EVENT_BUTTON1, dev->event_privdata);
2011-04-17 16:59:51 +02:00
2011-04-18 21:59:49 +02:00
} else if (memcmp(buf, expect2, sizeof(buf)) == 0) {
2011-04-22 14:55:36 +02:00
dev->event_callback(dev, LCD_EVENT_BUTTON2, dev->event_privdata);
2011-04-18 21:59:49 +02:00
}
2011-04-17 16:59:51 +02:00
}
2011-04-18 21:59:49 +02:00
return 0;
}
2011-04-22 14:55:36 +02:00
static int lcd_setline(struct lcddev *dev, int line, int len, const char *buf)
2011-04-18 21:59:49 +02:00
{
if (dev->state != LCD_STATE_READY)
return -1;
2011-04-17 17:37:38 +02:00
2011-04-22 14:55:36 +02:00
char cmd[20 +1] = A125_CMD_SETLINE;
2011-04-18 21:59:49 +02:00
cmd[2] = line;
2011-04-17 16:59:51 +02:00
2011-04-18 21:59:49 +02:00
memset(cmd +4, ' ', 16);
memcpy(cmd +4, buf, (len > 16) ? 16 : len);
2011-04-22 14:55:36 +02:00
cmd[20] = '\0';
2011-04-18 21:59:49 +02:00
2011-04-22 14:55:36 +02:00
#if (_LCD_DEBUG > 0)
fprintf(stderr, "lcd_setline(%d, '%-16s')\n", line, cmd +4);
#endif
lcd_write(dev, cmd, 20);
2011-04-18 21:59:49 +02:00
return 0;
}
static int lcd_scroll_timeout_cb(void *privdata)
{
2011-04-22 14:55:36 +02:00
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++;
}
2011-04-18 21:59:49 +02:00
2011-04-22 14:55:36 +02:00
/* 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;
2011-04-18 21:59:49 +02:00
}
2011-04-22 14:55:36 +02:00
int lcd_setlines(struct lcddev *dev, int scroll_speed, const char *line1, const char *line2)
2011-04-18 21:59:49 +02:00
{
dev->line_data[0] = line1;
2011-04-22 14:55:36 +02:00
dev->line_length[0] = (line1 != NULL) ? strlen(line1) : 0;
lcd_setline(dev, 0, dev->line_length[0], dev->line_data[0]);
2011-04-18 21:59:49 +02:00
dev->line_data[1] = line2;
2011-04-22 14:55:36 +02:00
dev->line_length[1] = (line2 != NULL) ? strlen(line2) : 0;
lcd_setline(dev, 1, dev->line_length[1], dev->line_data[1]);
2011-04-18 21:59:49 +02:00
2011-04-22 14:55:36 +02:00
int scroll_enable = (dev->line_length[0] > 16 && dev->line_length[1] > 16);
if (dev->scroll_timeout && ((dev->scroll_speed != scroll_speed) || !scroll_enable)) {
2011-04-18 21:59:49 +02:00
event_remove_timeout(dev->scroll_timeout);
dev->scroll_timeout = NULL;
2011-04-17 17:37:38 +02:00
}
2011-04-22 14:55:36 +02:00
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 };
2011-04-18 21:59:49 +02:00
dev->scroll_timeout = event_add_timeout(&tv, lcd_scroll_timeout_cb, dev);
2011-04-17 16:59:51 +02:00
}
return 0;
}
2011-04-17 17:37:38 +02:00
struct lcddev * lcd_init(const char *devicename,
2011-04-22 14:55:36 +02:00
int (*event_callback)(struct lcddev *dev, int button, void *privdata),
void *event_privdata)
2011-04-17 16:59:51 +02:00
{
struct lcddev *dev = malloc(sizeof(struct lcddev));
if (dev == NULL) {
log_print(LOG_ERROR, "lcd_init(): out of memory");
2011-04-17 17:37:38 +02:00
return NULL;
2011-04-17 16:59:51 +02:00
}
memset(dev, 0, sizeof(struct lcddev));
2011-04-25 13:26:34 +02:00
int retval;
2011-05-21 12:07:02 +02:00
if (strncmp(devicename, "dummy", 5) == 0) {
2011-04-25 13:26:34 +02:00
retval = lcd_fakedevice_open(dev);
2011-05-21 12:07:02 +02:00
} else {
retval = lcd_open(dev, devicename);
2011-04-25 13:26:34 +02:00
}
if (retval < 0) {
2011-04-17 16:59:51 +02:00
free(dev);
2011-04-17 17:37:38 +02:00
return NULL;
2011-04-17 16:59:51 +02:00
}
2011-04-22 14:55:36 +02:00
dev->event_callback = event_callback;
dev->event_privdata = event_privdata;
2011-04-17 16:59:51 +02:00
dev->read_event = event_add_readfd(NULL, dev->fd, lcd_read_cb, dev);
2011-04-18 21:59:49 +02:00
lcd_reset(dev);
2011-04-17 17:37:38 +02:00
return dev;
2011-04-17 16:59:51 +02:00
}