File: vlsys_m428.c

package info (click to toggle)
lcdproc 0.5.9-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 5,064 kB
  • sloc: ansic: 59,645; sh: 1,740; perl: 681; makefile: 417
file content (450 lines) | stat: -rw-r--r-- 11,788 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
/** \file server/drivers/vlsys_m428.c
 * LCDd \c vlsys_m428 driver for the VLSystem VFD/IR combination in the
 * computer case "Moneual MonCaso 320". The precise name of the VFD is
 * unknown, but it reports itself as "M428".
 *
 * The command set and its parameters have been discovered by
 * monitoring the serial port communication of the Windows driver.
 */

/*-
 * Copyright (c) 2011 Wolfgang Hauck <wolfgang.hauck@gmx.de>.
 * Derived from imon-module.
 *
 * This source code is being released under the GPL.
 * Please see the file COPYING in this package for details.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>

#include "lcd.h"
#include "shared/report.h"
#include "vlsys_m428.h"


/** Default device path. */
#define DEFAULT_DEVICE	"/dev/ttyUSB0"
/** Default serial baud rate. */
#define DEFAULT_BAUD_RATE B57600
/** Display width in characters. */
#define DEFAULT_WIDTH	14
/** Display height in characters. */
#define DEFAULT_HEIGHT	1
/** Frame buffer size. */
#define DEFAULT_SIZE    (DEFAULT_WIDTH * DEFAULT_HEIGHT)

/** Maximal size of device path. */
#define PATH_SIZE 256

/* Command set and parameters */
/** Initialise display for IR receive. Unused by this driver. */
#define CMD_INIT     0x96
/** Enable icon on top of display. */
#define CMD_ICON     0x97
/** Print a character sequence starting from a specified display position */
#define CMD_PRINT    0x93
/** Complete print action */
#define CMD_COMPLETE 0x09
/** Switch power LED. */
#define CMD_LED      0x9B

/** Disable icon; parameter to command CMD_ICON. */
#define PAR_ICON_DISABLE 0x01
/** Enable icon; parameter to command CMD_ICON. */
#define PAR_ICON_ENABLE  0x02
/** Switch LED off; parameter to command CMD_LED. */
#define PAR_LED_OFF      0x02
/** Switch LED on; parameter to command CMD_LED. */
#define PAR_LED_ON       0x01


/* Vars 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 = "vlsys_m428_";

/** Private data for the \c vlsystem_m428 driver */
typedef struct vlsystem_m428_private_data {
	int fd;				/**< File descriptor of serial port */
	char framebuf[DEFAULT_SIZE];	/**< Frame buffer */
} PrivateData;

/* Internal functions */
static int dev_print_chr(PrivateData *p, char x, const char *s, unsigned int n);
static int dev_send_bytes(PrivateData *p, const char *s, unsigned int n);
static int initialise_display(PrivateData *p);
static int initialise_port(int fd);
static char map(char c);


/**
 * Sends an array of bytes to the display.
 * \param p    Pointer to private data.
 * \param s    Pointer to array of bytes.
 * \param n    Number of bytes to be sent.
 * \retval 0   Success.
 * \retval <0  Error (errno is set).
 */
static int
dev_send_bytes(PrivateData *p, const char *s, unsigned int n)
{
	int l;

	while (n > 0) {
		l = write(p->fd, s, n);
		if (l < 0) {
			return (-1);
		}
		s += l;
		n -= l;
	}

	return 0;
}


/**
 * Prints an array of characters on the display.
 * \param p    Pointer to private data.
 * \param x    Column position (1, 2, 3, ..., DEFAULT_WIDTH).
 * \param s    Pointer to array of characters.
 * \param n    Number of characters to be printed.
 * \retval 0   Success.
 * \retval <0  Error (errno is set).
 */
static int
dev_print_chr(PrivateData *p, char x, const char *s, unsigned int n)
{
	char c;

	c = CMD_PRINT;
	if (dev_send_bytes(p, &c, 1) < 0) {
		return (-1);
	}
	if (dev_send_bytes(p, &x, 1) < 0) {
		return (-1);
	}
	if (dev_send_bytes(p, s, n) < 0) {
		return (-1);
	}
	c = CMD_COMPLETE;
	if (dev_send_bytes(p, &c, 1) < 0) {
		return (-1);
	}

	return 0;
}


/**
 * Maps a character encoded in ISO 8859-1 to display encoding.
 * Note: Encoding is not complete; some characters
 *       are approximated by similar ones.
 * - Approximates for 0xA6, 0xAD, 0xB4, 0xB7, 0xCD, 0xFA.
 * - No character for 0xA8, 0xA9, 0xAC, 0xAE, 0xAF, 0xB8.
 * \param c  Character (ISO 8859-1).
 * \return   Character (display encoding or an approximation),
 *           zero if map value is unknown.
 */
static char
map(char c)
{
	static const char display[] = {
		/* 0     1     2     3     4     5     6     7     8     9     A     B     C     D     E     F */
		0x20, 0xAD, 0xBD, 0x9C, 0xCF, 0xBE, 0x7C, 0xF5, 0x00, 0x00, 0xA6, 0xAE, 0x00, 0x2D, 0x00, 0x00,	/* 0xA? */
		0xF8, 0xF1, 0xFD, 0xFC, 0x27, 0x16, 0xF4, 0x2E, 0x00, 0xFB, 0xA7, 0xAF, 0xAC, 0xAB, 0xF3, 0xA8,	/* 0xB? */
		0xB7, 0xB5, 0xB6, 0xC7, 0x8E, 0x8F, 0x92, 0x80, 0xD4, 0x90, 0xD2, 0xD3, 0xD6, 0x49, 0xD7, 0xD8,	/* 0xC? */
		0xD1, 0xA5, 0xE3, 0xE0, 0xE2, 0xE5, 0x99, 0x9E, 0xBF, 0xEB, 0xE9, 0xEA, 0x9A, 0xED, 0xE7, 0xE1,	/* 0xD? */
		0x85, 0xA0, 0x83, 0xC6, 0x84, 0x86, 0x91, 0x87, 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B,	/* 0xE? */
		0xD0, 0xA4, 0x95, 0xA2, 0x93, 0xE4, 0x94, 0xF6, 0xCB, 0x97, 0x75, 0x96, 0x81, 0xEC, 0xE8, 0x98	/* 0xF? */
	};
	unsigned char d = c;

	if (d >= 0x20 && d <= 0x7F) {
		return d;	/* standard ASCII */
	}
	else if (d >= 0xA0) {
		return display[d - 0xA0];	/* ISO 8859-1 extension */
	}
	else {
		return 0;
	}
}


/**
 * Initialises the serial port.
 * \param fd   File descriptor of serial port.
 * \retval 0   Success.
 * \retval <0  Error.
 */
static int
initialise_port(int fd)
{
	struct termios options;

	if (tcgetattr(fd, &options) < 0) {
		return -1;
	}
#ifdef HAVE_CFMAKERAW
	cfmakeraw(&options);
#else
	options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
			     | INLCR | IGNCR | ICRNL | IXON);
	options.c_oflag &= ~OPOST;
	options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
	options.c_cflag &= ~(CSIZE | PARENB | CRTSCTS);
	options.c_cflag |= CS8 | CREAD | CLOCAL;
#endif
	cfsetispeed(&options, DEFAULT_BAUD_RATE);
	cfsetospeed(&options, DEFAULT_BAUD_RATE);
	if (tcsetattr(fd, TCSAFLUSH, &options) < 0) {
		return -1;
	}

	return 0;
}


/**
 * Initialises the display.
 * Switches red LED on and off such that a short flash is visible;
 * useful for LED test and debugging purposes.
 * Note: All commands sent do not trigger a response on the serial line,
 * therefore other read-only drivers are not disturbed.
 * \param p    Pointer to private data.
 * \retval 0   Success.
 * \retval <0  Error.
 */
static int
initialise_display(PrivateData *p)
{
	const char on[] = {CMD_LED, PAR_LED_ON};
	const char off[] = {CMD_LED, PAR_LED_OFF};
	int i;			/* icon index */

	/* Switch red LED on */
	if (dev_send_bytes(p, on, sizeof(on))) {
		return -1;
	}
	/* Send output initialisation sequence (non-intrusive, no response) */
	for (i = 1; i < DEFAULT_SIZE; i++) {
		char icon[] = {CMD_ICON, 0, PAR_ICON_DISABLE};
		icon[1] = i;
		if (dev_send_bytes(p, icon, sizeof(icon))) {
			return -1;
		}
	}
	/* Switch red LED off */
	if (dev_send_bytes(p, off, sizeof(off))) {
		return -1;
	}

	return 0;
}


/**
 * Initialises the driver.
 * \param drvthis  Pointer to driver structure.
 * \retval 0       Success.
 * \retval <0      Error.
 */
MODULE_EXPORT int
vlsys_m428_init(Driver *drvthis)
{
	PrivateData *p = NULL;
	char path[PATH_SIZE];	/* device path (serial port) */

	/* Allocate and initialise private data */
	p = (PrivateData *) calloc(1, sizeof(PrivateData));
	if (p == NULL) {
		report(RPT_ERR, "%s: failed to allocate private data", drvthis->name);
		return -1;
	}
	p->fd = -1;		/* fail safe value */
	memset(p->framebuf, ' ', sizeof(p->framebuf));

	/* Store pointer to private data */
	if (drvthis->store_private_ptr(drvthis, p)) {
		report(RPT_ERR, "%s: failed to store private data pointer", drvthis->name);
		vlsys_m428_close(drvthis);
		return -1;
	}

	/* Get device path from configuration file */
	strncpy(path,
		drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE),
		sizeof(path));
	path[sizeof(path) - 1] = '\0';
	report(RPT_INFO, "%s: using device %s", drvthis->name, path);

	/* Open device for writing (reading would interfere with LIRC) */
	if ((p->fd = open(path, O_WRONLY)) < 0) {
		report(RPT_ERR,
		       "%s: could not open %s (%s)",
		       drvthis->name, path, strerror(errno));
		vlsys_m428_close(drvthis);
		return -1;
	}
	/* Serial port configuration */
	else if (initialise_port(p->fd) < 0) {
		report(RPT_ERR, "%s: could not configure %s (%s)",
		       drvthis->name, path, strerror(errno));
		vlsys_m428_close(drvthis);
		return -1;
	}
	/* Initialise display for output */
	else if (initialise_display(p) < 0) {
		report(RPT_ERR, "%s: could not initialise display", drvthis->name);
		vlsys_m428_close(drvthis);
		return -1;
	}
	/* Erase display */
	else if (dev_print_chr(p, 1, p->framebuf, DEFAULT_SIZE) < 0) {
		report(RPT_ERR, "%s: could not erase display", drvthis->name);
		vlsys_m428_close(drvthis);
		return -1;
	}

	report(RPT_DEBUG, "%s: init() done", drvthis->name);

	return 0;
}


/**
 * Closes the driver (do necessary clean-up).
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
vlsys_m428_close(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	/* Clean up resources */
	if (p != NULL) {
		/* Close serial port */
		if (p->fd >= 0) {
			close(p->fd);
		}
		free(p);
	}
	/* Invalidate local driver structure */
	drvthis->store_private_ptr(drvthis, NULL);

	report(RPT_DEBUG, "%s: close() done", drvthis->name);
}


/**
 * Provides some information about this driver.
 * \param drvthis  Pointer to driver structure.
 * \return         Constant string with information.
 */
MODULE_EXPORT const char *
vlsys_m428_get_info(Driver *drvthis)
{
	const char *info_string = "Driver for VLSystem VFD/IR combination in Moneual MonCaso 320";

	return info_string;
}


/**
 * Clears the screen.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
vlsys_m428_clear(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	memset(p->framebuf, ' ', DEFAULT_SIZE);
}


/**
 * Flushes data on screen to the display.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
vlsys_m428_flush(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	dev_print_chr(p, 1, p->framebuf, DEFAULT_SIZE);
}


/**
 * Prints a string on the screen at position (x,y).
 * The upper-left corner is (1,1),
 * the lower-right corner is (DEFAULT_WIDTH, DEFAULT_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
vlsys_m428_string(Driver *drvthis, int x, int y, const char string[])
{
	int i;

	for (i = 0; string[i] != '\0'; i++)
		vlsys_m428_chr(drvthis, x + i, y, string[i]);
}


/**
 * Puts a character on the screen at position (x,y).
 * The upper-left corner is (1,1),
 * the lower-right corner is (DEFAULT_WIDTH, DEFAULT_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
vlsys_m428_chr(Driver *drvthis, int x, int y, char c)
{
	PrivateData *p = drvthis->private_data;

	if ((x >= 1) && (y >= 1) && (x <= DEFAULT_WIDTH) && (y <= DEFAULT_HEIGHT)) {
		p->framebuf[x - 1] = map(c);
	}
}


/**
 * Returns the display width in characters.
 * \param drvthis  Pointer to driver structure.
 * \return         Number of characters the display is wide.
 */
MODULE_EXPORT int
vlsys_m428_width(Driver *drvthis)
{
	return DEFAULT_WIDTH;
}


/**
 * Returns the display height in characters.
 * \param drvthis  Pointer to driver structure.
 * \return         Number of characters the display is high.
 */
MODULE_EXPORT int
vlsys_m428_height(Driver *drvthis)
{
	return DEFAULT_HEIGHT;
}