* Do not include system headers in shared files (stdlib.h). * Don't include lcd.h only once in each driver. * Remove some unnecessary headers from driver (e.g. syslog.h) * Include <poll.h> instead of <sys/poll.h> * Spelling fixes.
1019 lines
26 KiB
C
1019 lines
26 KiB
C
/** \file server/drivers/SureElec.c
|
|
*
|
|
* Driver for the LCD modules (actually the controller board) available from
|
|
* the 'SURE electronics' shop (http://www.sureelectronics.net).
|
|
*
|
|
* These devices are PIC based controlled, using a serial communication
|
|
* protocol with the host. The actual connection to host is done through
|
|
* USB through a serial <-> USB converter (CP2102 USB to UART Bridge)
|
|
* integrated on the board.
|
|
*/
|
|
|
|
/*-
|
|
* Copyright (C) 2009, Laurent Latil
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/*
|
|
* It should be noted that the communication protocol, while somewhat
|
|
* documented in the LCD user's manual, is not complete (ie. the
|
|
* initialization sequence is not described), and inacurate on some points.
|
|
* This driver is so based on the spying of serial communication using the
|
|
* officially supported client softwares (LCD Smarties & SureLCD).
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <termios.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "lcd.h"
|
|
#include "lcd_lib.h"
|
|
#include "SureElec.h"
|
|
#include "adv_bignum.h"
|
|
|
|
#include "report.h"
|
|
|
|
/* API: variables for the server core */
|
|
MODULE_EXPORT char *api_version = API_VERSION;
|
|
MODULE_EXPORT int stay_in_foreground = 0;
|
|
MODULE_EXPORT int supports_multiple = 0;
|
|
MODULE_EXPORT char *symbol_prefix = "SureElec_";
|
|
|
|
|
|
typedef enum {
|
|
SURE_ELEC_EDITION1 = 1,
|
|
SURE_ELEC_EDITION2 = 2,
|
|
SURE_ELEC_EDITION3 = 3,
|
|
} SureElec_edition;
|
|
|
|
/** private data for the \c SureElec driver */
|
|
typedef struct SureElec_private_data {
|
|
int fd; /* Serial port file descriptor */
|
|
|
|
int width, height; /* Screen size */
|
|
int cellwidth, cellheight; /* Cell size */
|
|
|
|
/* framebuffer and buffer for old LCD contents */
|
|
unsigned char *framebuf;
|
|
unsigned char *backingstore;
|
|
|
|
/* definable characters */
|
|
CGmode ccmode;
|
|
|
|
int output_state; /**< current output state */
|
|
int contrast; /**< current contrast */
|
|
int brightness;
|
|
int offbrightness;
|
|
int backlight;
|
|
|
|
SureElec_edition edition;
|
|
|
|
} PrivateData;
|
|
|
|
static int open_port(Driver * drvthis, const char *device);
|
|
static void get_device_screen_size(Driver * drvthis, int *width, int *height);
|
|
static int write_(Driver * drvthis, const unsigned char *buf, size_t count);
|
|
static int read_(Driver * drvthis, void *buf, size_t count);
|
|
|
|
|
|
/**
|
|
* Initialize the driver.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \retval 0 Success.
|
|
* \retval <0 Error.
|
|
*/
|
|
MODULE_EXPORT int
|
|
SureElec_init(Driver * drvthis)
|
|
{
|
|
char device[256] = DEFAULT_DEVICE;
|
|
char size[256] = DEFAULT_SIZE;
|
|
int tmp;
|
|
int user_width = -1, user_height = -1;
|
|
int device_width = -1, device_height = -1;
|
|
const char *sedition;
|
|
|
|
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->edition = SURE_ELEC_EDITION2; /* Assume an edition 2
|
|
* version */
|
|
|
|
p->cellwidth = CELL_WIDTH;
|
|
p->cellheight = CELL_HEIGHT;
|
|
|
|
p->framebuf = NULL;
|
|
p->backingstore = NULL;
|
|
|
|
p->backlight = 0;
|
|
|
|
debug(RPT_INFO, "SureElec driver: init(%p)", drvthis);
|
|
|
|
/* READ CONFIG FILE */
|
|
|
|
/* Get serial device to use */
|
|
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);
|
|
|
|
if (open_port(drvthis, device) == -1) {
|
|
return -1;
|
|
}
|
|
/* Get edition version */
|
|
sedition = drvthis->config_get_string(drvthis->name, "Edition", 0, "");
|
|
|
|
if (strlen(sedition) != 0) {
|
|
int n = sedition[0] - '0';
|
|
if (strlen(sedition) != 1 || n < 1 || n > 3) {
|
|
report(RPT_ERR, "%s: unknown edition version %s; must be one of 1, 2, or 3",
|
|
drvthis->name, sedition);
|
|
return -1;
|
|
}
|
|
p->edition = (SureElec_edition) n;
|
|
}
|
|
|
|
/* Get the user-specified display size */
|
|
strncpy(size, drvthis->config_get_string(drvthis->name, "Size", 0, ""), sizeof(size));
|
|
size[sizeof(size) - 1] = '\0';
|
|
if (strlen(size) != 0) {
|
|
if ((sscanf(size, "%dx%d", &user_width, &user_height) != 2)
|
|
|| (user_width <= 0) || (user_width > LCD_MAX_WIDTH)
|
|
|| (user_height <= 0) || (user_height > LCD_MAX_HEIGHT)) {
|
|
report(RPT_ERR, "%s: invalid screen size: %s",
|
|
drvthis->name, size);
|
|
return -1;
|
|
}
|
|
}
|
|
if (p->edition != SURE_ELEC_EDITION1) {
|
|
/* For edition >1 the device can report screen size */
|
|
get_device_screen_size(drvthis, &device_width, &device_height);
|
|
|
|
if (user_width != -1) {
|
|
if (user_width != device_width || user_height != device_height) {
|
|
report(RPT_WARNING, "%s: device and config settings disagree for screen size: %d x %d <> %d x %d", drvthis->name, device_width, device_height, user_width, user_height);
|
|
report(RPT_WARNING, "%s: config specified size applied.", drvthis->name);
|
|
}
|
|
device_width = user_width;
|
|
device_height = user_height;
|
|
}
|
|
} else {
|
|
device_width = user_width;
|
|
device_height = user_height;
|
|
}
|
|
|
|
if (device_width == -1) {
|
|
report(RPT_ERR, "%s: screen size must be specified",
|
|
drvthis->name);
|
|
return -1;
|
|
}
|
|
/* set screen size */
|
|
p->width = device_width;
|
|
p->height = device_height;
|
|
report(RPT_INFO, "%s: screen size %d x %d.", drvthis->name, p->width, p->height);
|
|
|
|
/* Get contrast */
|
|
tmp = drvthis->config_get_int(drvthis->name, "Contrast", 0, DEFAULT_CONTRAST);
|
|
debug(RPT_INFO, "%s: Contrast (in config) is '%d'", drvthis->name, tmp);
|
|
if ((tmp < 0) || (tmp > 1000)) {
|
|
report(RPT_WARNING, "%s: Contrast value must be between 0 and 1000; using default %d",
|
|
drvthis->name, DEFAULT_CONTRAST);
|
|
tmp = DEFAULT_CONTRAST;
|
|
}
|
|
SureElec_set_contrast(drvthis, tmp);
|
|
|
|
/* Get backlight */
|
|
tmp = drvthis->config_get_int(drvthis->name, "Brightness", 0, DEFAULT_BRIGHTNESS);
|
|
debug(RPT_INFO, "%s: Brightness (in config) is '%d'", drvthis->name, tmp);
|
|
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;
|
|
|
|
/* Which backlight-off "brightness" */
|
|
tmp = drvthis->config_get_int(drvthis->name, "OffBrightness", 0, DEFAULT_OFFBRIGHTNESS);
|
|
debug(RPT_INFO, "%s: OffBrightness (in config) is '%d'", __FUNCTION__, tmp);
|
|
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;
|
|
|
|
/* Allocate the frame buffer. */
|
|
p->framebuf = (unsigned char *)calloc(p->width * p->height, 1);
|
|
if (p->framebuf == NULL) {
|
|
report(RPT_ERR, "%s: unable to create framebuffer", drvthis->name);
|
|
return -1;
|
|
}
|
|
memset(p->framebuf, ' ', p->width * p->height);
|
|
|
|
/* Allocate the backing store. */
|
|
p->backingstore = (unsigned char *)malloc(p->width * p->height);
|
|
if (p->backingstore == NULL) {
|
|
report(RPT_ERR, "%s: unable to create framebuffer backing store", drvthis->name);
|
|
return -1;
|
|
}
|
|
memset(p->backingstore, '#', p->width * p->height);
|
|
|
|
SureElec_clear(drvthis);
|
|
SureElec_flush(drvthis);
|
|
|
|
report(RPT_DEBUG, "%s: init() done", drvthis->name);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Close the driver (do necessary clean-up).
|
|
* \param drvthis Pointer to driver structure.
|
|
*/
|
|
MODULE_EXPORT void
|
|
SureElec_close(Driver * drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
if (p != NULL) {
|
|
if (p->fd >= 0)
|
|
close(p->fd);
|
|
|
|
/* Free allocated framebuffers */
|
|
if (p->framebuf)
|
|
free(p->framebuf);
|
|
if (p->backingstore)
|
|
free(p->backingstore);
|
|
|
|
/* Free our private data */
|
|
free(p);
|
|
}
|
|
drvthis->store_private_ptr(drvthis, NULL);
|
|
debug(RPT_DEBUG, "SureElec: closed");
|
|
}
|
|
|
|
/**
|
|
* Return the display width in characters.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Number of characters the display is wide.
|
|
*/
|
|
MODULE_EXPORT int
|
|
SureElec_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
|
|
SureElec_height(Driver * drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
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
|
|
SureElec_cellwidth(Driver * drvthis)
|
|
{
|
|
return CELL_WIDTH;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
SureElec_cellheight(Driver * drvthis)
|
|
{
|
|
return CELL_HEIGHT;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
SureElec_string(Driver * drvthis, int x, int y, const char string[])
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int i;
|
|
|
|
/* Convert 1-based coords to 0-based... */
|
|
x--;
|
|
y--;
|
|
|
|
/* Check coord. validity */
|
|
if ((y < 0) || (y >= p->height))
|
|
return;
|
|
|
|
for (i = 0; (string[i] != '\0') && (x < p->width); i++, x++) {
|
|
p->framebuf[(y * p->width) + x] = string[i];
|
|
}
|
|
debug(RPT_DEBUG, "SureElec: printed string at (%d,%d)", x, y);
|
|
}
|
|
|
|
|
|
/**
|
|
* Clear the screen.
|
|
* \param drvthis Pointer to driver structure.
|
|
*/
|
|
MODULE_EXPORT void
|
|
SureElec_clear(Driver * drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
/* set hbar/vbar/bignum mode back to normal character display */
|
|
p->ccmode = standard;
|
|
|
|
/* replace all chars in framebuf with spaces */
|
|
memset(p->framebuf, ' ', (p->width * p->height));
|
|
|
|
debug(RPT_DEBUG, "SureElec: cleared screen");
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
SureElec_chr(Driver * drvthis, int x, int y, char c)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
/* Convert 1-based coords to 0-based... */
|
|
x--;
|
|
y--;
|
|
|
|
if ((x >= 0) && (y >= 0) && (x < p->width) && (y < p->height))
|
|
p->framebuf[(y * p->width) + x] = c;
|
|
|
|
debug(RPT_DEBUG, "SureElec: writing character %02X to position (%d,%d)", c, x, y);
|
|
}
|
|
|
|
/**
|
|
* Flush data on screen to the LCD.
|
|
* \param drvthis Pointer to driver structure.
|
|
*/
|
|
MODULE_EXPORT void
|
|
SureElec_flush(Driver * drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int modified = 0;
|
|
int i, j;
|
|
unsigned char cmd[4] = {'\xFE', '\x47', '\x01', /* line */ 0};
|
|
|
|
for (i = 0; i < p->height; i++) {
|
|
/* Check if line content changed */
|
|
for (j = 0; j < p->width && p->framebuf[p->width * i + j] ==
|
|
p->backingstore[p->width * i + j]; j++) {
|
|
;
|
|
}
|
|
|
|
if (j < p->width) {
|
|
/*
|
|
* Line content has been changed, need to output the
|
|
* line on screen
|
|
*/
|
|
cmd[3] = i + 1;
|
|
if (write_(drvthis, cmd, sizeof(cmd)) == -1 ||
|
|
write_(drvthis, &(p->framebuf[p->width * i]), p->width) == -1) {
|
|
return;
|
|
}
|
|
modified = 1;
|
|
}
|
|
}
|
|
|
|
if (modified) {
|
|
/* If something changed on screen, update the backingstore */
|
|
memcpy(p->backingstore, p->framebuf, p->width * p->height);
|
|
}
|
|
debug(RPT_DEBUG, "SureElec: frame buffer flushed");
|
|
}
|
|
|
|
/**
|
|
* Get current LCD contrast.
|
|
* This is only the locally stored contrast, the contrast value
|
|
* cannot be retrieved from the LCD.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \return Stored contrast in promille.
|
|
*/
|
|
|
|
MODULE_EXPORT int
|
|
SureElec_get_contrast(Driver * drvthis)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
return p->contrast;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the LCD contrast.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param promille New contrast value in promille.
|
|
*/
|
|
MODULE_EXPORT void
|
|
SureElec_set_contrast(Driver * drvthis, int promille)
|
|
{
|
|
unsigned char cmd[3] = {'\xFE', '\x50', 0};
|
|
PrivateData *p = drvthis->private_data;
|
|
int real_contrast;
|
|
|
|
/* Check it */
|
|
if ((promille < 0) || (promille > 1000))
|
|
return;
|
|
|
|
/* Store it */
|
|
p->contrast = promille;
|
|
|
|
real_contrast = (int)((long)promille * MAX_CONTRAST / 1000);
|
|
|
|
/* And do it */
|
|
cmd[2] = real_contrast;
|
|
|
|
write_(drvthis, cmd, sizeof(cmd));
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
SureElec_get_brightness(Driver * drvthis, int state)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
return (state == BACKLIGHT_ON) ? p->brightness : 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
SureElec_set_brightness(Driver * drvthis, int state, int promille)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
/* Check value */
|
|
if (promille < 0 || promille > 1000)
|
|
return;
|
|
|
|
/* store the software value */
|
|
if (state == BACKLIGHT_ON)
|
|
p->brightness = promille;
|
|
else
|
|
p->offbrightness = promille;
|
|
}
|
|
|
|
/**
|
|
* Turn the LCD backlight on or off.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param on New backlight status.
|
|
*/
|
|
MODULE_EXPORT void
|
|
SureElec_backlight(Driver * drvthis, int on)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int promille;
|
|
|
|
if (BACKLIGHT_ON == on) {
|
|
promille = p->brightness;
|
|
} else {
|
|
promille = p->offbrightness;
|
|
}
|
|
|
|
if (promille == 0 && p->backlight) {
|
|
/* Turn BL off */
|
|
unsigned char cmd[2] = {'\xFE', '\x46'};
|
|
if (write_(drvthis, cmd, sizeof(cmd)) == -1) {
|
|
return;
|
|
}
|
|
report(RPT_INFO, "SureElec: BL turned off");
|
|
p->backlight = 0;
|
|
}
|
|
if (promille > 0) {
|
|
unsigned char cmd_bl[3] = {'\xFE', '\x42', '\x0'};
|
|
unsigned char cmd_brightness[3] = {'\xFE', '\x98', 0};
|
|
int level;
|
|
|
|
/* Turn BL on & set brightness level */
|
|
if (!p->backlight) {
|
|
if (write_(drvthis, cmd_bl, sizeof(cmd_bl)) == -1) {
|
|
return;
|
|
}
|
|
report(RPT_INFO, "SureElec: BL turned on");
|
|
p->backlight = 1;
|
|
}
|
|
level = (int)((long)promille * MAX_BACKLIGHT / 1000);
|
|
cmd_brightness[2] = level;
|
|
|
|
write_(drvthis, cmd_brightness, sizeof(cmd_brightness));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
SureElec_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;
|
|
SureElec_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
|
|
SureElec_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, 0xFF & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar));
|
|
SureElec_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
|
|
SureElec_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, 0, 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
|
|
SureElec_get_free_chars(Driver * drvthis)
|
|
{
|
|
return NUM_CC;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
SureElec_set_char(Driver * drvthis, int n, unsigned char *dat)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
unsigned char cmd[11] = {'\xFE', '\x4E', 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
unsigned char mask = (1 << CELL_WIDTH) - 1;
|
|
int row;
|
|
|
|
if ((unsigned int) n >= NUM_CC)
|
|
return;
|
|
if (!dat)
|
|
return;
|
|
|
|
cmd[2] = n; /* Custom char to define. */
|
|
|
|
for (row = 0; row < p->cellheight; row++) {
|
|
cmd[row + 3] = dat[row] & mask;
|
|
}
|
|
write_(drvthis, cmd, sizeof(cmd));
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
* \retval 0 Icon has been successfully defined/written.
|
|
* \retval <0 Server core shall define/write the icon.
|
|
*/
|
|
MODULE_EXPORT int
|
|
SureElec_icon(Driver * drvthis, int x, int y, int icon)
|
|
{
|
|
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 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_______
|
|
};
|
|
|
|
unsigned char *pattern = NULL;
|
|
char charnum = 0;
|
|
|
|
switch (icon) {
|
|
case ICON_BLOCK_FILLED:
|
|
charnum = 0xFF;
|
|
break;
|
|
case ICON_ARROW_LEFT:
|
|
charnum = 0x7F;
|
|
break;
|
|
case ICON_ARROW_RIGHT:
|
|
charnum = 0x7E;
|
|
break;
|
|
case ICON_ARROW_UP:
|
|
charnum = 1;
|
|
pattern = arrow_up;
|
|
break;
|
|
case ICON_ARROW_DOWN:
|
|
charnum = 2;
|
|
pattern = arrow_down;
|
|
break;
|
|
case ICON_HEART_FILLED:
|
|
pattern = heart_filled;
|
|
break;
|
|
case ICON_HEART_OPEN:
|
|
pattern = heart_open;
|
|
break;
|
|
case ICON_CHECKBOX_OFF:
|
|
charnum = 3;
|
|
pattern = checkbox_off;
|
|
break;
|
|
case ICON_CHECKBOX_ON:
|
|
charnum = 4;
|
|
pattern = checkbox_on;
|
|
break;
|
|
case ICON_CHECKBOX_GRAY:
|
|
charnum = 5;
|
|
pattern = checkbox_gray;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
if (NULL != pattern) {
|
|
SureElec_set_char(drvthis, charnum, pattern);
|
|
}
|
|
SureElec_chr(drvthis, x, y, charnum);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Open the communication port and sends the initialization 'magic' sequence
|
|
* to the device
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param device Name of the device to use.
|
|
* \retval 0 Success
|
|
* \retval <0 Error opening port or writing init sequence
|
|
*/
|
|
static int
|
|
open_port(Driver * drvthis, const char *device)
|
|
{
|
|
PrivateData *p = drvthis->private_data;
|
|
int fd;
|
|
struct termios portset;
|
|
|
|
unsigned char cmd[3] = {'\xFE', '\x56', 0};
|
|
unsigned char init_seq[7] = {'\x54', '\x58', '\x4B', '\x52', '\x44', '\x41', '\x60'};
|
|
int i;
|
|
|
|
|
|
fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
|
|
if (fd == -1) {
|
|
report(RPT_ERR, "%s: open(%s) failed (%s)", drvthis->name, device, strerror(errno));
|
|
if (errno == EACCES) {
|
|
report(RPT_ERR, "%s: %s device could not be opened... (check your device and / or config file)", drvthis->name, device);
|
|
}
|
|
return -1;
|
|
}
|
|
tcgetattr(fd, &portset);
|
|
|
|
/* Set port parameters */
|
|
portset.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
|
|
| INLCR | IGNCR | ICRNL | IXON);
|
|
portset.c_oflag &= ~OPOST;
|
|
portset.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
portset.c_cflag &= ~(CSIZE | PARENB | CRTSCTS | CSTOPB);
|
|
portset.c_cflag |= CS8 | CREAD | CLOCAL;
|
|
portset.c_cc[VMIN] = 1;
|
|
portset.c_cc[VTIME] = 10;
|
|
|
|
/* Set port speed */
|
|
cfsetospeed(&portset, B9600);
|
|
cfsetispeed(&portset, B9600);
|
|
|
|
/* Do it... */
|
|
tcsetattr(fd, TCSANOW, &portset);
|
|
|
|
/* Set port file desc. in private data */
|
|
p->fd = fd;
|
|
|
|
/* Send initialization sequence */
|
|
for (i = 1; i <= 8; i++) {
|
|
cmd[2] = i;
|
|
if (write_(drvthis, cmd, sizeof(cmd)) == -1)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(init_seq); i++) {
|
|
if (write_(drvthis, (const unsigned char *)"\x0FE", 1) == -1
|
|
|| write_(drvthis, &(init_seq[i]), 1) == -1)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the display size from the device.
|
|
* \param drvthis Pointer to driver structure.
|
|
* \param width Pointer to variable storing display width
|
|
* \param height Pointer to variable storing display height
|
|
*/
|
|
static void
|
|
get_device_screen_size(Driver * drvthis, int *width, int *height)
|
|
{
|
|
unsigned char cmd[2] = {'\xFE', '\x76'};
|
|
char result[11];
|
|
|
|
if (write_(drvthis, cmd, sizeof(cmd)) == -1)
|
|
return;
|
|
|
|
if (read_(drvthis, result, sizeof(result)) != -1) {
|
|
*width = (int)(result[0] - '0') * 10 + (int)(result[1] - '0');
|
|
*height = (int)(result[2] - '0') * 10 + (int)(result[3] - '0');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes count bytes from a buffer to the device.
|
|
* \param drvthis Pointer to driver structure
|
|
* \param buf Pointer to the buffer
|
|
* \param count Number of bytes to write
|
|
* \retval -1 upon error
|
|
*/
|
|
static int
|
|
write_(Driver * drvthis, const unsigned char *buf, size_t count)
|
|
{
|
|
int write_count = 0, written;
|
|
PrivateData *p = drvthis->private_data;
|
|
|
|
while (write_count < count) {
|
|
written = write(p->fd, &buf[write_count], count - write_count);
|
|
if (written == -1) {
|
|
report(RPT_ERR, "SureElec: cannot write to port");
|
|
return -1;
|
|
}
|
|
write_count += written;
|
|
}
|
|
return write_count;
|
|
}
|
|
|
|
/**
|
|
* Reads bytes in a buffer from the device.
|
|
* \param drvthis Pointer to driver structure
|
|
* \param buf Pointer to the buffer
|
|
* \param count Number of bytes to read
|
|
* \retval -1 upon error
|
|
*/
|
|
static int
|
|
read_(Driver * drvthis, void *buf, size_t count)
|
|
{
|
|
fd_set rfds;
|
|
struct timeval tv;
|
|
PrivateData *p = drvthis->private_data;
|
|
int read_count;
|
|
|
|
/* Watch fd to see when it has input. */
|
|
FD_ZERO(&rfds);
|
|
FD_SET(p->fd, &rfds);
|
|
|
|
read_count = 0;
|
|
while (read_count < count) {
|
|
int retval;
|
|
|
|
/*
|
|
* For compatibility, better to reset the timeout delay (may
|
|
* be modified in select() call).
|
|
*/
|
|
tv.tv_sec = 1; /* seconds */
|
|
tv.tv_usec = 0; /* microseconds */
|
|
|
|
retval = select(p->fd + 1, &rfds, NULL, NULL, &tv);
|
|
if (retval) {
|
|
int read_result = read(p->fd, ((char *)buf) + read_count, count - read_count);
|
|
if (read_result < 0) {
|
|
return -1;
|
|
} else {
|
|
read_count += read_result;
|
|
}
|
|
} else {
|
|
report(RPT_ERR, "SureElec: No answer from device");
|
|
return -1;
|
|
}
|
|
}
|
|
return read_count;
|
|
}
|