/* This is the LCDproc driver for Cwlinux devices (http://www.cwlinux.com)
Applicable Data Sheets:
- http://www.cwlinux.com/downloads/cw1602/cw1602-manual.pdf
- http://www.cwlinux.com/downloads/lcd/cw12232-manual.pdf
Copyright (C) 2002, Andrew Ip
2003, David Glaude
2006,7 Peter Marschall
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
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
/*
Feedback from Tomislav Secen, who tested it with a 1602:
Great, this is much better:
- icons are displayed nicely
- bars are OK (at least the few I've seen)
- heartbeat icon flashes nicely in the top right corner.
Only issue I encountered was (similar came up before) - when setting some menu
options, or just entering a certain menu (like Options->CwLnx->OnBrightness,
lcdvc client menu), the LCD becomes garbled (i.e. displays two blinking hearts,
boxes, '%' symbols), sometimes starts displaying just '%' symbols over the whole
LCD while I'm pressing Left/Right keys (is this the screen-saver?), scrolling
from right to left. Even the "Thank you for using ..." message is garbled (each
time in a different way) if I kill the daemon after that. Pressing 'X' when the
garbled characters occur exits the menu and the other (client) screens are
displayed correctly after that. So only the menus are affected by this issue -
it seems that this screen-saver mode kicks in in the wrong time, and tries to
write to LCD faster than it can process chars/commands.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "lcd.h"
#include "CwLnx.h"
#include "report.h"
#include "lcd_lib.h"
/* for the icon definitions & the big numbers */
#include "adv_bignum.h"
#define ValidX(x) if ((x) > p->width) { (x) = p->width; } else (x) = (x) < 1 ? 1 : (x);
#define ValidY(y) if ((y) > p->height) { (y) = p->height; } else (y) = (y) < 1 ? 1 : (y);
#define MaxKeyMap 6
static char *defaultKeyMap[MaxKeyMap] = { "Up", "Down", "Left", "Right", "Enter", "Escape" };
typedef enum {
standard, /* only char 0 is used for heartbeat */
vbar, /* vertical bars */
hbar, /* horizontal bars */
custom, /* custom settings */
bignum, /* big numbers */
bigchar /* big characters */
} CGmode;
typedef struct driver_private_data {
int fd;
int have_keypad;
int keypad_test_mode;
char *KeyMap[MaxKeyMap];
int model;
/* dimensions */
int width, height;
int cellwidth, cellheight;
/* framebuffer and buffer for old LCD contents */
unsigned char *framebuf;
unsigned char *backingstore;
/* defineable characters */
CGmode ccmode;
char saved_backlight; /* current state of the backlight */
char backlight; /* state of the backlight at next flush */
int saved_brightness; /* brightness as displayed on the LCD currently */
int brightness; /* brightness as it will be displayed at next flush */
} PrivateData;
/* API: Vars for the server core */
MODULE_EXPORT char *api_version = API_VERSION;
MODULE_EXPORT int stay_in_foreground = 0; /* For testing only */
MODULE_EXPORT int supports_multiple = 1;
MODULE_EXPORT char *symbol_prefix = "CwLnx_";
static void CwLnx_linewrap(int fd, int on);
static void CwLnx_autoscroll(int fd, int on);
static void CwLnx_hidecursor(int fd);
#define LCD_CMD 254
#define LCD_CMD_END 253
#define LCD_INIT_CHINESE_T 56
#define LCD_INIT_CHINESE_S 55
#define LCD_LIGHT_ON 66
#define LCD_LIGHT_OFF 70
#define LCD_LIGHT_BRIGHTNESS 65
#define LCD_CLEAR 88
#define LCD_SET_INSERT 71
#define LCD_INIT_INSERT 72
#define LCD_SET_BAUD 57
#define LCD_ENABLE_WRAP 67
#define LCD_DISABLE_WRAP 68
#define LCD_SETCHAR 78
#define LCD_ENABLE_SCROLL 81
#define LCD_DISABLE_SCROLL 82
#define LCD_OFF_CURSOR 72
#define LCD_PUT_PIXEL 112
#define LCD_CLEAR_PIXEL 113
#define DELAY 2000 /* 2 milli sec */
#define UPDATE_DELAY 0 /* 1 imicro sec */
#define SETUP_DELAY 1 /* 2 micro sec */
static int Write_LCD(int fd, char *c, int size)
{
int rc;
int retries = 30;
do {
rc = write(fd, c, size);
if (rc == size)
break;
usleep(DELAY);
} while (--retries > 0);
return rc;
}
/*********************************************
* Real hardware function.
* Will be called by API function.
*/
/* Hardware function */
static void Enable_Backlight(int fd)
{
char cmd[] = { LCD_CMD, LCD_LIGHT_ON, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Disable_Backlight(int fd)
{
char cmd[] = { LCD_CMD, LCD_LIGHT_OFF, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Enable_Pixel(int fd, int x, int y)
{
char cmd[] = { LCD_CMD, LCD_PUT_PIXEL, 0, 0, LCD_CMD_END };
cmd[2] = (char) x;
cmd[3] = (char) y;
Write_LCD(fd, cmd, 5);
}
/* Hardware function */
static void Disable_Pixel(int fd, int x, int y)
{
char cmd[] = { LCD_CMD, LCD_CLEAR_PIXEL, 0, 0, LCD_CMD_END };
cmd[2] = (char) x;
cmd[3] = (char) y;
Write_LCD(fd, cmd, 5);
}
/* Hardware function */
static void Backlight_Brightness(int fd, int brightness)
{
if (brightness == 1) {
Disable_Backlight(fd);
} else if (brightness == 7) {
Enable_Backlight(fd);
} else {
char cmd[] = { LCD_CMD, LCD_LIGHT_BRIGHTNESS, 0, LCD_CMD_END };
cmd[2] = (char) brightness;
Write_LCD(fd, cmd, 4);
}
}
/* Hardware function */
static void Enable_Scroll(int fd)
{
char cmd[] = { LCD_CMD, LCD_ENABLE_SCROLL, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Disable_Scroll(int fd)
{
char cmd[] = { LCD_CMD, LCD_DISABLE_SCROLL, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Clear_Screen(int fd)
{
char cmd[] = { LCD_CMD, LCD_CLEAR, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
usleep(UPDATE_DELAY);
}
/* Hardware function */
static void Enable_Wrap(int fd)
{
char cmd[] = { LCD_CMD, LCD_ENABLE_WRAP, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Disable_Wrap(int fd)
{
char cmd[] = { LCD_CMD, LCD_DISABLE_WRAP, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Disable_Cursor(int fd)
{
char cmd[] = { LCD_CMD, LCD_OFF_CURSOR, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
/* Hardware function */
static void Init_Port(fd)
{
/* Posix - set baudrate to 0 and back */
struct termios tty, old;
tcgetattr(fd, &tty);
tcgetattr(fd, &old);
cfsetospeed(&tty, B0);
cfsetispeed(&tty, B0);
tcsetattr(fd, TCSANOW, &tty);
usleep(SETUP_DELAY);
tcsetattr(fd, TCSANOW, &old);
}
/* Hardware function */
static void Setup_Port(int fd, speed_t speed)
{
struct termios portset;
tcgetattr(fd, &portset);
cfsetospeed(&portset, speed);
cfsetispeed(&portset, speed);
portset.c_iflag = IGNBRK;
portset.c_lflag = 0;
portset.c_oflag = 0;
portset.c_cflag |= CLOCAL | CREAD;
portset.c_cflag &= ~CRTSCTS;
portset.c_cc[VMIN] = 1;
portset.c_cc[VTIME] = 5;
tcsetattr(fd, TCSANOW, &portset);
}
/* Hardware function */
static void Set_9600(int fd)
{
char cmd[] = { LCD_CMD, LCD_SET_BAUD, 0x20, LCD_CMD_END };
Write_LCD(fd, cmd, 4);
}
/* Hardware function */
static void Set_19200(int fd)
{
char cmd[] = { LCD_CMD, LCD_SET_BAUD, 0x0F, LCD_CMD_END };
Write_LCD(fd, cmd, 4);
}
/* Hardware function */
static void Set_Insert(int fd, int row, int col)
{
if (row == 0 && col == 0) {
char cmd[] = { LCD_CMD, LCD_INIT_INSERT, LCD_CMD_END };
Write_LCD(fd, cmd, 3);
}
else {
char cmd[] = { LCD_CMD, LCD_SET_INSERT, 0, 0, LCD_CMD_END };
cmd[2] = (char) col;
cmd[3] = (char) row;
Write_LCD(fd, cmd, 5);
}
}
/**
* Toggle the built-in linewrapping feature
*/
static void
CwLnx_linewrap(int fd, int on)
{
if (on)
Enable_Wrap(fd);
else
Disable_Wrap(fd);
}
/**
* Toggle the built-in automatic scrolling feature
*/
static void
CwLnx_autoscroll(int fd, int on)
{
if (on)
Enable_Scroll(fd);
else
Disable_Scroll(fd);
}
/**
* Get rid of the blinking curson
*/
static void
CwLnx_hidecursor(int fd)
{
Disable_Cursor(fd);
}
/*****************************************************
* Here start the API function
*/
/**
* Initialize the driver.
* \param drvthis Pointer to driver structure.
* \retval 0 Success.
* \retval <0 Error.
*/
MODULE_EXPORT int
CwLnx_init(Driver *drvthis)
{
struct termios portset_save;
char device[200] = DEFAULT_DEVICE;
int speed = DEFAULT_SPEED;
char size[200] = DEFAULT_SIZE;
int default_speed = DEFAULT_SPEED;
char *default_size = DEFAULT_SIZE;
int tmp;
int w;
int h;
const char *s;
PrivateData *p;
/* Allocate and store private data */
p = (PrivateData *) malloc(sizeof(PrivateData));
if (p == NULL)
return -1;
if (drvthis->store_private_ptr(drvthis, p))
return -1;
/* Initialise the PrivateData structure */
p->fd = -1;
p->cellwidth = DEFAULT_CELL_WIDTH;
p->cellheight = DEFAULT_CELL_HEIGHT;
p->ccmode = standard;
p->saved_backlight = -1;
p->backlight = DEFAULT_BACKLIGHT;
p->saved_brightness = -1;
p->brightness = DEFAULT_BRIGHTNESS;
debug(RPT_INFO, "%s: init(%p)", drvthis->name, drvthis);
/* Read config file */
/* Which model is it (1602 or 12232)? */
tmp = drvthis->config_get_int(drvthis->name, "Model", 0, 12232);
debug(RPT_INFO, "%s: Model (in config) is '%d'", __FUNCTION__, tmp);
if ((tmp != 1602) && (tmp != 12232)) {
tmp = 12232;
report(RPT_WARNING, "%s: Model must be 12232 or 1602; using default %d",
drvthis->name, tmp);
}
p->model = tmp;
/* Which size & cell dimensions */
if (p->model == 1602) {
default_size = DEFAULT_SIZE_1602;
default_speed = DEFAULT_SPEED_1602;
p->cellwidth = DEFAULT_CELL_WIDTH_1602;
p->cellheight = DEFAULT_CELL_HEIGHT_1602;
} else if (p->model == 12232) {
default_size = DEFAULT_SIZE_12232;
default_speed = DEFAULT_SPEED_12232;
p->cellwidth = DEFAULT_CELL_WIDTH_12232;
p->cellheight = DEFAULT_CELL_HEIGHT_12232;
}
/* Which device should be used */
strncpy(device, drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE), sizeof(device));
device[sizeof(device) - 1] = '\0';
report(RPT_INFO, "%s: using Device %s", drvthis->name, device);
/* Which size */
strncpy(size, drvthis->config_get_string(drvthis->name, "Size", 0, default_size), sizeof(size));
size[sizeof(size) - 1] = '\0';
if ((sscanf(size, "%dx%d", &w, &h) != 2)
|| (w <= 0) || (w > LCD_MAX_WIDTH)
|| (h <= 0) || (h > LCD_MAX_HEIGHT)) {
report(RPT_WARNING, "%s: cannot read Size: %s; using default %s",
drvthis->name, size, default_size);
sscanf(default_size, "%dx%d", &w, &h);
}
p->width = w;
p->height = h;
/* Contrast of the LCD can be changed by adjusting the trimpot R7 */
/* Which speed */
tmp = drvthis->config_get_int(drvthis->name, "Speed", 0, default_speed);
switch (tmp) {
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
default:
speed = B19200;
report(RPT_WARNING, "%s: Speed must be 9600 or 19200. Using default %d",
drvthis->name, default_speed);
}
/* do we have a keypad? */
if (drvthis->config_get_bool(drvthis->name , "Keypad", 0, 0)) {
report(RPT_INFO, "%s: Config tells us we have a keypad", drvthis->name);
p->have_keypad = 1;
}
/* keypad test mode? */
if (drvthis->config_get_bool(drvthis->name , "keypad_test_mode", 0, 0)) {
report(RPT_INFO, "%s: Config tells us to test the keypad mapping", drvthis->name);
p->keypad_test_mode = 1;
stay_in_foreground = 1;
}
/* read the keypad mapping only if we have a keypad. */
if (p->have_keypad) {
int x;
/* Read keymap */
for (x = 0; x < MaxKeyMap; x++) {
char buf[40];
/* First fill with default value */
p->KeyMap[x] = defaultKeyMap[x];
/* The line above make a warning... the code is comming from hd44780.c */
/* printf("%s-%s\n", defaultKeyMap[x], p->KeyMap[x]); */
/* Read config value */
sprintf(buf, "KeyMap_%c", x+'A');
s = drvthis->config_get_string(drvthis->name, buf, 0, NULL);
/* Was a key specified in the config file ? */
if (s != NULL) {
p->KeyMap[x] = strdup(s);
report(RPT_INFO, "%s: Key '%c' to \"%s\"", drvthis->name, x+'A', s);
}
}
}
/* End of config file parsing */
/* Allocate framebuffer memory */
p->framebuf = (unsigned char *) malloc(p->width * p->height);
if (p->framebuf == NULL) {
report(RPT_ERR, "%s: unable to create framebuffer", drvthis->name);
return -1;
}
memset(p->framebuf, ' ', p->width * p->height);
/* make sure the framebuffer backing store is there... */
p->backingstore = (unsigned char *) malloc(p->width * p->height);
if (p->backingstore == NULL) {
report(RPT_ERR, "%s: unable to create backingstore", drvthis->name);
return -1;
}
memset(p->backingstore, ' ', p->width * p->height);
/* Set up io port correctly, and open it... */
debug(RPT_DEBUG, "%s: Opening device: %s", drvthis->name, device);
p->fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (p->fd == -1) {
report(RPT_ERR, "%s: open(%s) failed (%s)", drvthis->name, device, strerror(errno));
return -1;
}
report(RPT_INFO, "%s: opened display on %s", drvthis->name, device);
Init_Port(p->fd);
tcgetattr(p->fd, &portset_save);
speed = B19200;
Setup_Port(p->fd, speed);
Set_9600(p->fd);
close(p->fd);
p->fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (p->fd == -1) {
report(RPT_ERR, "%s: open(%s) failed (%s)", drvthis->name, device, strerror(errno));
return -1;
}
report(RPT_INFO, "%s: opened display on %s", drvthis->name, device);
Init_Port(p->fd);
speed = B9600;
Setup_Port(p->fd, speed);
CwLnx_hidecursor(p->fd);
CwLnx_linewrap(p->fd, 1);
CwLnx_autoscroll(p->fd, 0);
CwLnx_backlight(drvthis, 1); /* WHY force the backlight to on ? */
/* What is the default brightness ? */
Clear_Screen(p->fd);
CwLnx_clear(drvthis);
usleep(SETUP_DELAY);
report(RPT_DEBUG, "%s: init() done", drvthis->name);
return 1;
}
/**
* Close the driver (do necessary clean-up).
* \param drvthis Pointer to driver structure.
*/
MODULE_EXPORT void
CwLnx_close(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
if (p != NULL) {
if (p->fd >= 0)
close(p->fd);
if (p->framebuf != NULL)
free(p->framebuf);
p->framebuf = NULL;
if (p->backingstore != NULL)
free(p->backingstore);
p->backingstore = NULL;
free(p);
}
drvthis->store_private_ptr(drvthis, NULL);
debug(RPT_DEBUG, "CwLnx: closed");
}
/**
* Return the display width in characters.
* \param drvthis Pointer to driver structure.
* \return Number of characters the display is wide.
*/
MODULE_EXPORT int
CwLnx_width(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
debug(RPT_DEBUG, "CwLnx: returning width");
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
CwLnx_height(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
debug(RPT_DEBUG, "CwLnx: returning height");
return p->height;
}
/**
* Return the width of a character in pixels.
* \param drvthis Pointer to driver structure.
* \return Number of pixel columns a character cell is wide.
*/
MODULE_EXPORT int
CwLnx_cellwidth(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
debug(RPT_DEBUG, "CwLnx: returning cellwidth");
return p->cellwidth;
}
/**
* Return the height of a character in pixels.
* \param drvthis Pointer to driver structure.
* \return Number of pixel lines a character cell is high.
*/
MODULE_EXPORT int
CwLnx_cellheight(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
debug(RPT_DEBUG, "CwLnx: returning cellheight");
return p->cellheight;
}
/**
* Flush data on screen to the LCD.
* \param drvthis Pointer to driver structure.
*/
MODULE_EXPORT void
CwLnx_flush(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
int i, j;
int move = 1;
unsigned char *q = p->framebuf;
unsigned char *r = p->backingstore;
for (i = 0; i < p->height; i++) {
for (j = 0; j < p->width; j++) {
if ((*q == *r) && !((0 < *q) && (*q < 16))) {
move = 1;
}
else {
/* Draw characters that have changed, as well
* as custom characters. We know not if a custom
* character has changed. */
if (move == 1) {
Set_Insert(p->fd, i, j);
move = 0;
}
Write_LCD(p->fd, (char *) q, 1);
}
q++;
r++;
}
}
memcpy(p->backingstore, p->framebuf, p->width * p->height);
}
/**
* 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
CwLnx_chr(Driver *drvthis, int x, int y, char c)
{
PrivateData *p = drvthis->private_data;
int offset;
ValidX(x);
ValidY(y);
y--;
x--;
offset = (y * p->width) + x;
p->framebuf[offset] = c;
debug(RPT_DEBUG, "CwLnx: writing character %02X to position (%d,%d)", c, x, y);
}
/*
* The CwLinux hardware does not support contrast setting by software,
* but only by changing the hardware configuration.
* Since the get_contrast and set_contrast are not mandatory in the API,
* it is better not to implement a dummy version.
*/
/*
* CwLnx support 7 level of brightness
* 1 is the minimum and correspond to backlight OFF
* 7 is the maximum.
*
* When the backlight is turned off, we remember the brightness
* so that when the backlight is turned back on, we have the previous
* brightness.
*
* Backlight and Brightness in this driver will not be set real-time,
* but we will wait for the flush command. The same way we use a framebuffer,
* and we update the LCD only at flush time.
*/
/**
* Turn the LCD backlight on or off.
* \param drvthis Pointer to driver structure.
* \param on New backlight status.
*/
MODULE_EXPORT void
CwLnx_backlight(Driver *drvthis, int on)
{
PrivateData *p = drvthis->private_data;
p->backlight = on;
}
/**
* 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
CwLnx_get_brightness(Driver *drvthis, int state)
{
PrivateData *p = drvthis->private_data;
return p->saved_brightness;
}
/**
* 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
CwLnx_set_brightness(Driver *drvthis, int state, int promille)
{
PrivateData *p = drvthis->private_data;
p->brightness = promille;
}
/**
* 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
CwLnx_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;
CwLnx_set_char(drvthis, i+1, vBar);
}
}
lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, 1);
}
/**
* 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
CwLnx_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;
for (i = 1; i <= p->cellwidth; i++) {
// fill pixel columns from left to right.
memset(hBar, 0xFF & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar));
CwLnx_set_char(drvthis, i+1, hBar);
}
}
lib_hbar_static(drvthis, x, y, len, promille, options, p->cellwidth, 1);
}
/**
* 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
CwLnx_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 bignumbers.
lib_adv_bignum(drvthis, x, num, 1, do_init);
}
/**
* Get total number of custom characters available.
* \param drvthis Pointer to driver structure.
* \return Number of custom characters (always NUM_CCs).
*/
MODULE_EXPORT int
CwLnx_get_free_chars(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
return (p->model == 1602) ? 8 : 16;
}
/**
* Define a custom character and write it to the LCD.
* \param drvthis Pointer to driver structure.
* \param n Custom character to define [1 - free_chars].
* \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
CwLnx_set_char(Driver *drvthis, int n, unsigned char *dat)
{
PrivateData *p = drvthis->private_data;
char c;
int rc;
if ((n <= 0) || (n > CwLnx_get_free_chars(drvthis)))
return;
if (!dat)
return;
c = LCD_CMD;
rc = Write_LCD(p->fd, &c, 1);
c = LCD_SETCHAR;
rc = Write_LCD(p->fd, &c, 1);
c = (char) n;
rc = Write_LCD(p->fd, &c, 1);
if (p->model == 1602) { // the character model
unsigned char mask = (1 << p->cellwidth) - 1;
int row;
for (row = 0; row < p->cellheight; row++) {
c = dat[row] & mask;
Write_LCD(p->fd, &c, 1);
}
} else if (p->model == 12232) { // the graphical model
int col;
for (col = p->cellwidth - 1; col >= 0; col--) {
int letter = 0;
int row;
for (row = p->cellheight - 1; row >= 0; row--) {
letter <<= 1;
letter |= ((dat[row] >> col) & 1);
}
/* restrict width to 5 pixels */
c = (col < p->cellwidth - 1) ? letter : '\0';
Write_LCD(p->fd, &c, 1);
}
}
c = LCD_CMD_END;
rc = Write_LCD(p->fd, &c, 1);
}
/**
* 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 synbolic value representing the icon.
* \return Information whether the icon is handled here or needs to be handled by the server core.
*/
MODULE_EXPORT int
CwLnx_icon(Driver *drvthis, int x, int y, int icon)
{
PrivateData *p = drvthis->private_data;
static unsigned char heart_open[] =
{ b__XXXXX,
b__X_X_X,
b_______,
b_______,
b_______,
b__X___X,
b__XX_XX,
b__XXXXX };
static unsigned char heart_filled[] =
{ b__XXXXX,
b__X_X_X,
b___X_X_,
b___XXX_,
b___XXX_,
b__X_X_X,
b__XX_XX,
b__XXXXX };
static unsigned char arrow_up[] =
{ b____X__,
b___XXX_,
b__X_X_X,
b____X__,
b____X__,
b____X__,
b____X__,
b_______ };
static unsigned char arrow_down[] =
{ b____X__,
b____X__,
b____X__,
b____X__,
b__X_X_X,
b___XXX_,
b____X__,
b_______ };
/*
static unsigned char arrow_left[] =
{ b_______,
b____X__,
b___X___,
b__XXXXX,
b___X___,
b____X__,
b_______,
b_______ };
static unsigned char arrow_right[] =
{ b_______,
b____X__,
b_____X_,
b__XXXXX,
b_____X_,
b____X__,
b_______,
b_______ };
*/
static unsigned char checkbox_off[] =
{ b_______,
b_______,
b__XXXXX,
b__X___X,
b__X___X,
b__X___X,
b__XXXXX,
b_______ };
static unsigned char checkbox_on[] =
{ b____X__,
b____X__,
b__XXX_X,
b__X_XX_,
b__X_X_X,
b__X___X,
b__XXXXX,
b_______ };
static unsigned char checkbox_gray[] =
{ b_______,
b_______,
b__XXXXX,
b__X_X_X,
b__XX_XX,
b__X_X_X,
b__XXXXX,
b_______ };
/*
static unsigned char selector_left[] =
{ b___X___,
b___XX__,
b___XXX_,
b___XXXX,
b___XXX_,
b___XX__,
b___X___,
b_______ };
static unsigned char selector_right[] =
{ b_____X_,
b____XX_,
b___XXX_,
b__XXXX_,
b___XXX_,
b____XX_,
b_____X_,
b_______ };
static unsigned char ellipsis[] =
{ b_______,
b_______,
b_______,
b_______,
b_______,
b_______,
b__X_X_X,
b_______ };
*/
static unsigned char block_filled[] =
{ b__XXXXX,
b__XXXXX,
b__XXXXX,
b__XXXXX,
b__XXXXX,
b__XXXXX,
b__XXXXX,
b__XXXXX };
/* Yes we know, this is a VERY BAD implementation */
switch (icon) {
case ICON_BLOCK_FILLED:
CwLnx_set_char(drvthis, 7, block_filled);
CwLnx_chr(drvthis, x, y, 7);
break;
case ICON_HEART_FILLED:
CwLnx_set_char(drvthis, 1, heart_filled);
CwLnx_chr(drvthis, x, y, 1);
break;
case ICON_HEART_OPEN:
CwLnx_set_char(drvthis, 1, heart_open);
CwLnx_chr(drvthis, x, y, 1);
break;
case ICON_ARROW_UP:
CwLnx_set_char(drvthis, 2, arrow_up);
CwLnx_chr(drvthis, x, y, 2);
break;
case ICON_ARROW_DOWN:
CwLnx_set_char(drvthis, 3, arrow_down);
CwLnx_chr(drvthis, x, y, 3);
break;
case ICON_ARROW_LEFT:
if (p->model == 1602)
CwLnx_chr(drvthis, x, y, 0x7F);
else
return -1;
break;
case ICON_ARROW_RIGHT:
if (p->model == 1602)
CwLnx_chr(drvthis, x, y, 0x7E);
else
return -1;
break;
case ICON_CHECKBOX_OFF:
CwLnx_set_char(drvthis, 4, checkbox_off);
CwLnx_chr(drvthis, x, y, 4);
break;
case ICON_CHECKBOX_ON:
CwLnx_set_char(drvthis, 5, checkbox_on);
CwLnx_chr(drvthis, x, y, 5);
break;
case ICON_CHECKBOX_GRAY:
CwLnx_set_char(drvthis, 6, checkbox_gray);
CwLnx_chr(drvthis, x, y, 6);
break;
default:
return -1; /* Let the core do other icons */
}
return 0;
}
/**
* Clear the screen.
* \param drvthis Pointer to driver structure.
*/
MODULE_EXPORT void
CwLnx_clear(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
memset(p->framebuf, ' ', p->width * p->height);
p->ccmode = standard;
debug(RPT_DEBUG, "CwLnx: cleared framebuffer");
}
/**
* 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
CwLnx_string(Driver *drvthis, int x, int y, const char string[])
{
PrivateData *p = drvthis->private_data;
int offset, siz;
ValidX(x);
ValidY(y);
x--;
y--;
offset = (y * p->width) + x;
siz = (p->width * p->height) - offset;
siz = siz > strlen(string) ? strlen(string) : siz;
memcpy(p->framebuf + offset, string, siz);
/*
This is another way to check for buffer overflow
for (int i = 0; string[i]; i++) {
if ((y * p->width) + x + i > (p->width * p->height))
break;
CwLnx->framebuf[(y * p->width) + x + i] = string[i];
}
*/
debug(RPT_DEBUG, "CwLnx: printed string at (%d,%d)", x, y);
}
/**
* Get next key from the KeyRing.
* \param drvthis Pointer to driver structure.
* \return String representation of the key.
*/
MODULE_EXPORT const char *
CwLnx_get_key(Driver *drvthis)
{
PrivateData *p = drvthis->private_data;
char key = '\0';
read(p->fd, &key, 1);
if (key != '\0') {
if ((key >= 'A') && (key <= 'F')) {
return p->KeyMap[key-'A'];
}
else {
report(RPT_INFO, "%s: Untreated key 0x%02X", drvthis->name, key);
}
}
return NULL;
}
syntax highlighted by Code2HTML, v. 0.9.1