File: accent.c

package info (click to toggle)
lirc 0.10.1-6
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 8,364 kB
  • sloc: ansic: 26,717; cpp: 9,066; sh: 5,521; python: 4,362; makefile: 1,034; xml: 63
file content (409 lines) | stat: -rw-r--r-- 12,850 bytes parent folder | download | duplicates (2)
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
/****************************************************************************
** hw_accent.c *************************************************************
****************************************************************************
*
* LIRC driver for Kanam/Accent serial port remote control.
*
* The Kanam/Accent is a remote control with an IR receiver
* connecting to the serial port. The receiver communicate with the
* host system at 1200 8N1, so the standard serial driver provided
* by the Linux kernel is used.
*
* For each keypress on the remote control, a sequence of 13 or 14
* bytes is transmitted. We can consider just the first 8 bytes as
* significative. Each sequence begins with the three bytes: 0x90
* 0x46 0x42. If a key is held-down, a sequence of zeroes is
* transmitted. The gap between two different full codes is about
* 188500 microseconds. The gap between each zero on a key-hold is
* about 56000 microseconds.
*
* Sometimes the receiver jams, especially on very short key press.
* In this case a uninterrupted stream of zeroes is transmitted,
* without the gap of 56000 us. The stream is interrupted if
* another key is pressed on the remote or if the driver closes and
* reopen the serial port.
*
* Unfortunately the LIRC source code is not well documented, so I
* hope to have guessed well the workflow of lircd. Please, contact
* me if the comments in this source code are not accurate or
* clear.
*
* Author:	Niccolo Rigacci <niccolo@rigacci.org>
*
* Version:	1.1	2007-02-12
*
* Original routines from hw_pixelview.c and hw_pinsys.c.
* First working code for this remote from Leandro Dardini.
*
* Christoph Bartelmus <lirc@bartelmus.de>
* Bart Alewijnse <scarfboy@yahoo.com>
* Leandro Dardini <ldardini@tiscali.it>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*
*/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef LIRC_IRTTY
#define LIRC_IRTTY "/dev/ttyS0"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>

#include "lirc_driver.h"
#include "lirc/serial.h"

// Max number of bytes received in a sequence.
#define ACCENT_MAX_READ_BYTES 16
// Only the first bytes of the sequence are meaning.
#define ACCENT_MEANING_BYTES 8
// The meaning bytes are packed into an integer of this length.
#define ACCENT_CODE_LENGTH 64
// Baud rate for the serial port.
#define ACCENT_BAUD_RATE 1200
#define ACCENT_BAUD_RATE_CONST B1200

static const logchannel_t logchannel = LOG_DRIVER;

static unsigned char b[ACCENT_MAX_READ_BYTES];

// Timestamps of keypress start, keypress end and last pressed key.
static struct timeval start, end, last;

// Time (us) of a signal received from the remote control.
static lirc_t signal_length;

// The code of the pressed key and the previous one.
// Type ir_code is uint64_t.
static ir_code code, last_code = 0;

// Forwards
static int accent_decode(struct ir_remote* remote, struct decode_ctx_t* ctx);
static int accent_open_serial_port(const char* device);
static int accent_init(void);
static int accent_deinit(void);
static char* accent_rec(struct ir_remote* remotes);


const struct driver hw_accent = {
	.name		= "accent",
	.device		= LIRC_IRTTY,
	.features	= LIRC_CAN_REC_LIRCCODE,
	.send_mode	= 0,
	.rec_mode	= LIRC_MODE_LIRCCODE,
	.code_length	= ACCENT_CODE_LENGTH,
	.init_func	= accent_init,
	.deinit_func	= accent_deinit,
	.open_func	= default_open,
	.close_func	= default_close,
	.send_func	= NULL,
	.rec_func	= accent_rec,
	.decode_func	= accent_decode,
	.drvctl_func	= NULL,
	.readdata	= NULL,
	.resolution	= 300,
	.api_version	= 3,
	.driver_version = "0.10.0",
	.info		= "No info available",
	.device_hint    = "/dev/tty[0-9]*",
};

const struct driver* hardwares[] = { &hw_accent, (const struct driver*)NULL };


//-------------------------------------------------------------------------
// This function is called by the LIRC daemon during the transform of a
// received code into an lirc event.
//
// It gets the global variable code (remote keypress code).
//
// It returns:
//      prep            Code prefix (zero for this LIRC driver)
//      codep           Code of keypress
//      postp           Trailing code (zero for this LIRC dirver)
//      repeat_flagp    True if the keypress is a repeated keypress
//      remaining_gapp  Extimated time gap remaining before next code?
//-------------------------------------------------------------------------
int accent_decode(struct ir_remote* remote, struct decode_ctx_t* ctx)
{
	log_trace("Entering accent_decode(), code = %016llx\n", code);

	log_trace("accent_decode() is calling map_code()");
	if (!map_code(remote, ctx, 0, 0, ACCENT_CODE_LENGTH, code, 0, 0))
		return 0;

	map_gap(remote, ctx, &start, &last, signal_length);

	log_trace("Exiting accent_decode()");

	return 1;
}

//-------------------------------------------------------------------------
// Lock and initialize the serial port.
// This function is called by the LIRC daemon when the first client
// registers itself.
// Return 1 on success, 0 on error.
//-------------------------------------------------------------------------
int accent_init(void)
{
	log_trace("Entering accent_init()");

	// Calculate the time length of a remote signal (in microseconds):
	// (bits + total_stop_bits) * 1000000 / bitrate
	signal_length = (drv.code_length + (drv.code_length / 8)) * 1000000 / ACCENT_BAUD_RATE;

	if (!tty_create_lock(drv.device)) {
		log_error("Could not create the lock file");
		return 0;
	}
	drv.fd = accent_open_serial_port(drv.device);
	if (drv.fd  < 0) {
		log_error("Could not open the serial port");
		accent_deinit();
		return 0;
	}
	return 1;
}

//-------------------------------------------------------------------------
// Close and release the serial line.
//-------------------------------------------------------------------------
int accent_deinit(void)
{
	log_trace("Entering accent_deinit()");
	close(drv.fd);
	tty_delete_lock();
	return 1;
}

//-------------------------------------------------------------------------
// Receive a code (bytes sequence) from the remote.
// This function is called by the LIRC daemon when I/O is pending
// from a registered client, e.g. irw.
//-------------------------------------------------------------------------
char* accent_rec(struct ir_remote* remotes)
{
	char* m;
	int i, j;

	log_trace("Entering accent_rec()");

	// Timestamp of the last pressed key.
	last = end;
	// Timestamp of key press start.
	gettimeofday(&start, NULL);

	// Loop untill read ACCENT_MAX_READ_BYTES or sequence timeout.
	for (i = 0; i < ACCENT_MAX_READ_BYTES; i++) {
		// The function accent_rec() is called when some data
		// is already available to read, so we don't wait on
		// the first byte.
		if (i > 0) {
			// Each of the following bytes must be
			// received within some timeout.  7500 us is
			// the standard time for receiving a byte
			// (wait at least this time) 56000 us is the
			// min time gap between two code sequences
			// (don't wait so much)
			if (waitfordata(45000) == 0) {
				// waitfordata() timed out: the
				// sequence is complete.
				log_trace("waitfordata() timeout waiting for byte %d", i);
				break;
			}
		}
		// Some data available to read.
		if (read(drv.fd, &b[i], 1) == -1) {
			log_perror_err("read() failed at byte %d", i);
			return NULL;
		}
		log_trace("read() byte %d: %02x", i, b[i]);
	}                       // End for

	// Timestamp of key press end.
	gettimeofday(&end, NULL);

	// The bytes sequence is complete, check its validity.
	log_trace("Received a sequence of %d bytes", i);

	// Just one byte with zero value: repeated keypress?
	if (i == 1 && b[0] == 0) {
		if (last_code && (start.tv_sec - last.tv_sec < 2)) {
			// A previous code exists and the time gap is
			// lower than 2 seconds.
			log_info("Received repeated key");
			code = last_code;
			tcflush(drv.fd, TCIFLUSH);
			m = decode_all(remotes);
			return m;
		}
		log_trace("Previos code not set, invalid repeat key");
		last_code = 0;
		return NULL;
	}
	// Sequence too short?
	if (i < ACCENT_MEANING_BYTES) {
		log_notice("Invalid sequence: too short");
		last_code = 0;
		return NULL;
	}
	// A valid code begins with bytes 0x90 0x46 0x42
	// and it is long not more than ACCENT_MEANING_BYTES.
	if (b[0] == 0x90 && b[1] == 0x46 && b[2] == 0x42) {
		code = 0;
		if (sizeof(code) >= ACCENT_MEANING_BYTES) {
			// We have plenty of space to store the full sequence.
			code |= b[0];
			code <<= 8;
			code |= b[1];
			code <<= 8;
			code |= b[2];
			code <<= 8;
			code |= b[3];
			code <<= 8;
			code |= b[4];
			code <<= 8;
			code |= b[5];
			code <<= 8;
			code |= b[6];
			code <<= 8;
			code |= b[7];
		} else {
			// No much space, keep only the differentiating part.
			code |= b[3];
			code <<= 8;
			code |= b[4];
			code <<= 8;
			code |= b[5];
			code <<= 8;
			code |= b[6];
		}
		log_trace("sizeof(code) = %d", sizeof(code));
		log_trace("Received code -> 0x%016llx", code);
		last_code = code;
		tcflush(drv.fd, TCIFLUSH);
		m = decode_all(remotes);
		return m;
	}
	// Sometimes the receiver goes crazy, it starts to send to the
	// serial line a sequence of zeroes with no pauses at all.
	// This jam terminates only if the user press a new button on
	// the remote or if we close and re-open the serial port.
	if (i == ACCENT_MAX_READ_BYTES) {
		for (j = 0; j < ACCENT_MAX_READ_BYTES; j++)
			if (b[j] != 0)
				break;
		if (j == ACCENT_MAX_READ_BYTES) {
			// All the received bytes are zeroes, without gaps.
			log_warn("Receiver jam! Reopening the serial port");
			close(drv.fd);
			drv.fd = accent_open_serial_port(drv.device);
			if (drv.fd < 0) {
				log_error("Could not reopen the serial port");
				raise(SIGTERM);
			}
			last_code = 0;
			return NULL;
		}
	}
	// Should never reach this point.
	log_notice("Received an invalid sequence");
	for (j = 0; j < i; j++)
		log_trace(" b[%d] = %02x", j, b[j]);
	last_code = 0;
	return NULL;
}

//-------------------------------------------------------------------------
// Open the serial line and set the discipline (do the low level work).
// Return the file descriptor or -1 on error.
//-------------------------------------------------------------------------
int accent_open_serial_port(const char* device)
{
	int fd;
	struct termios options;

	log_debug("Entering accent_open_serial_port(), device = %s", device);

	// Open the serial device.
	fd = open(device, O_RDWR | O_NONBLOCK | O_NOCTTY | O_SYNC);
	if (fd  < 0) {
		log_perror_err("Could not open the serial port");
		return -1;
	}
	// Get the parameters associated with the serial line.
	if (tcgetattr(fd, &options) < 0) {
		log_error("Could not get serial port attributes");
		log_perror_err("tcgetattr() failed");
		return -1;
	}
	// Set the line in raw mode (no control chars, etc.)
	cfmakeraw(&options);
	// Apply the changes after all the output has been transmitted.
	// Discard input before the change is made.
	if (tcsetattr(fd, TCSAFLUSH, &options) < 0) {
		log_error("Could not set serial port with cfmakeraw()");
		log_perror_err("tcsetattr() failed");
		return -1;
	}
	// Gets the parameters associated with the serial line.
	if (tcgetattr(fd, &options) < 0) {
		log_error("Could not get serial port attributes");
		log_perror_err("tcgetattr() failed");
		return -1;
	}
	// Set input and output baud rate to 1200.
	cfsetispeed(&options, ACCENT_BAUD_RATE_CONST);
	cfsetospeed(&options, ACCENT_BAUD_RATE_CONST);
	// Disable RTS/CTS (hardware) flow control.
	options.c_cflag &= ~CRTSCTS;
	// Set one stop bit.
	options.c_cflag &= ~CSTOPB;
	// Ignore modem control lines.
	options.c_cflag |= CLOCAL;
	// Enable receiver.
	options.c_cflag |= CREAD;
	// Disable parity checking for input.
	options.c_cflag &= ~PARENB;
	if (tcsetattr(fd, TCSAFLUSH, &options) < 0) {
		log_error("Could not set serial port line discipline");
		log_perror_err("tcsetattr() failed");
		return -1;
	}
	// Discards data received but not read.
	if (tcflush(fd, TCIFLUSH) < 0) {
		log_error("Could not flush input buffer");
		log_perror_err("tcflush() failed");
		return -1;
	}

	return fd;
}