Tue, 26 Aug 2008 13:18:18 +0100
added: Vim folding markers to all functions
added: Cached LED write (TODO: add to test app)
added: LPFK keys no longer active by default
TODO: build Doxyfile for Doxygen documentation generation
1 /****************************************************************************
2 * Project: liblpfk
3 * Purpose: Driver library for the IBM 6094-020 Lighted Program Function
4 * Keyboard.
5 * Version: 1.0
6 * Author: Philip Pemberton <philpem@philpem.me.uk>
7 *
8 * The latest version of this library is available from
9 * <http://www.philpem.me.uk/code/liblpfk/>.
10 *
11 * Copyright (c) 2008, Philip Pemberton
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 *
18 * * Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * * Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 * * Neither the name of the project nor the names of its
25 * contributors may be used to endorse or promote products derived
26 * from this software without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
31 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
32 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
33 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
35 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
36 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
37 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
38 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ****************************************************************************/
41 /**
42 * @file liblpfk.c
43 * @brief liblpfk library, main source
44 */
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <fcntl.h>
49 #include <unistd.h>
50 #include <termios.h>
51 #include <sys/ioctl.h>
52 #include <time.h>
53 #include <string.h>
54 #include <stdbool.h>
55 #include <stdio.h>
57 #include "liblpfk.h"
59 /* lpfk_open {{{ */
60 int lpfk_open(const char *port, LPFK_CTX *ctx)
61 {
62 struct termios newtio;
63 int status;
64 int fd;
65 int i;
67 // open the serial port
68 fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
69 if (fd < 0) return LPFK_E_PORT_OPEN;
71 // save current port settings
72 tcgetattr(fd, &ctx->oldtio);
74 // set up new parameters
75 memset(&newtio, 0, sizeof(newtio));
76 // 9600 baud, 8 bits, parity enabled, odd parity
77 newtio.c_cflag = B9600 | CS8 | PARENB | PARODD | CLOCAL | CREAD;
78 newtio.c_iflag = 0;
79 newtio.c_oflag = 0;
81 // set input mode -- non canonical, no echo
82 newtio.c_lflag = 0;
84 // inter-character timer unused
85 newtio.c_cc[VTIME] = 0;
86 // read does not block waiting for characters if there are none in the buffer
87 newtio.c_cc[VMIN] = 0;
89 // flush input buffer
90 tcflush(fd, TCIFLUSH);
92 // set new port config
93 tcsetattr(fd, TCSANOW, &newtio);
95 // set RTS true to pull the LPFK out of reset
96 ioctl(fd, TIOCMGET, &status);
97 status |= TIOCM_RTS;
98 ioctl(fd, TIOCMSET, &status);
100 // wait a few seconds for the LPFK to become ready
101 sleep(2);
103 // 0x06: READ CONFIGURATION. LPFK sends 0x03 in response.
104 // Try five times to wake it up.
105 status = false;
106 for (i=0; i<5; i++) {
107 unsigned char buf;
108 time_t tm;
110 // Send 0x06: READ CONFIGURATION, loop on failure
111 if (write(fd, "\x06", 1) < 1) {
112 continue;
113 }
115 // save current time (in seconds)
116 tm = time(NULL);
118 // loop until 2 seconds have passed, or LPFK responds
119 status = false;
120 do {
121 // read data, loop if not successful
122 if (read(fd, &buf, 1) < 1) {
123 continue;
124 }
126 // we got some data, what is it?
127 if (buf == 0x03) {
128 // 0x03 -- correct response. we're done.
129 status = true;
130 }
131 } while (((time(NULL) - tm) < 2) && (!status));
133 // exit loop if we got the LPFK to talk
134 if (status) {
135 break;
136 }
137 }
139 // Did the LPFK respond?
140 if (!status) {
141 // LPFK isn't talking. Restore serial port state and exit.
142 tcsetattr(fd, TCSANOW, &ctx->oldtio);
143 close(fd);
145 return LPFK_E_NOT_PRESENT;
146 } else {
147 // Initialise LPFK context
148 ctx->led_mask = 0;
149 ctx->fd = fd;
151 // Enable the LPFK
152 write(fd, "\x08", 1);
153 ctx->enabled = true;
155 // Return OK status
156 return LPFK_E_OK;
157 }
158 }
160 /* }}} */
162 /* lpfk_close {{{ */
163 int lpfk_close(LPFK_CTX *ctx)
164 {
165 int status;
167 // 0x09: DISABLE. Stop the LPFK responding to keystrokes.
168 write(ctx->fd, "\x09", 1);
170 // turn all the LEDs off
171 lpfk_set_leds(ctx, false);
173 // set RTS false to put the LPFK into reset
174 ioctl(ctx->fd, TIOCMGET, &status);
175 status &= ~TIOCM_RTS;
176 ioctl(ctx->fd, TIOCMSET, &status);
178 // Restore the port state and close the serial port.
179 tcsetattr(ctx->fd, TCSANOW, &ctx->oldtio);
180 close(ctx->fd);
182 // Done!
183 return LPFK_E_OK;
184 }
185 /* }}} */
187 /* lpfk_enable {{{ */
188 int lpfk_enable(LPFK_CTX *ctx, int val)
189 {
190 if (val) {
191 // val == true, enable the LPFK
192 if (write(ctx->fd, "\x08", 1) != 1) {
193 ctx->enabled = true;
194 return LPFK_E_COMMS;
195 }
196 } else {
197 // val == false, disable the LPFK
198 if (write(ctx->fd, "\x09", 1) != 1) {
199 return LPFK_E_COMMS;
200 }
201 }
203 // update the context, return success
204 ctx->enabled = val;
205 return LPFK_E_OK;
206 }
208 /* }}} */
210 /* lpfk_set_led_cached {{{ */
211 int lpfk_set_led_cached(LPFK_CTX *ctx, const int num, const int state)
212 {
213 int i;
214 time_t tm;
215 unsigned long mask, leds;
216 unsigned char buf[5];
217 unsigned char status;
219 // check parameters
220 if ((num < 0) || (num > 31)) {
221 return LPFK_E_PARAM;
222 }
224 // parameters OK, now build the LED mask
225 mask = (0x80 >> (num % 8)) << ((3 - (num / 8)) * 8);
227 // mask the specified bit
228 if (state) {
229 ctx->led_mask |= mask;
230 } else {
231 ctx->led_mask &= ~mask;
232 }
234 return LPFK_E_OK;
235 }
236 /* }}} */
238 /* lpfk_set_leds_cached {{{ */
239 int lpfk_set_leds_cached(LPFK_CTX *ctx, const int state)
240 {
241 int i;
242 time_t tm;
243 unsigned long leds;
244 unsigned char buf[5];
245 unsigned char status;
247 if (state) {
248 // all LEDs on
249 ctx->led_mask = 0xFFFFFFFF;
250 } else {
251 // all LEDs off
252 ctx->led_mask = 0x00000000;
253 }
255 return LPFK_E_OK;
256 }
257 /* }}} */
259 /* lpfk_update_leds {{{ */
260 int lpfk_update_leds(LPFK_CTX *ctx)
261 {
262 int i;
263 time_t tm;
264 unsigned char buf[5];
265 unsigned char status;
267 // send new LED mask to the LPFK
268 buf[0] = 0x94;
269 buf[1] = ctx->led_mask >> 24;
270 buf[2] = ctx->led_mask >> 16;
271 buf[3] = ctx->led_mask >> 8;
272 buf[4] = ctx->led_mask & 0xff;
274 // make 5 attempts to set the LEDs
275 for (i=0; i<5; i++) {
276 if (write(ctx->fd, &buf, 5) < 5) {
277 continue;
278 }
280 // check for response -- 0x81 = OK, 0x80 = retransmit
281 // save current time (in seconds)
282 tm = time(NULL);
284 // loop until 2 seconds have passed, or LPFK responds
285 status = 0x00;
286 do {
287 // read data, loop if not successful
288 if (read(ctx->fd, &status, 1) < 1) {
289 continue;
290 }
292 // we got some data, what is it?
293 if (status == 0x81) {
294 // 0x81 -- received successfully
295 break;
296 }
297 } while ((time(NULL) - tm) < 2);
299 // status OK?
300 if (status == 0x81) {
301 // 0x81: OK
302 break;
303 } else if (status == 0x80) {
304 // 0x80: Retransmit request
305 continue;
306 }
307 }
309 return LPFK_E_OK;
310 }
311 /* }}} */
313 /* lpfk_set_led {{{ */
314 int lpfk_set_led(LPFK_CTX *ctx, const int num, const int state)
315 {
316 lpfk_set_led_cached(ctx, num, state);
317 return lpfk_update_leds(ctx);
318 }
319 /* }}} */
321 /* lpfk_set_leds {{{ */
322 int lpfk_set_leds(LPFK_CTX *ctx, const int state)
323 {
324 lpfk_set_leds_cached(ctx, state);
325 return lpfk_update_leds(ctx);
326 }
327 /* }}} */
329 /* lpfk_get_led {{{ */
330 int lpfk_get_led(LPFK_CTX *ctx, const int num)
331 {
332 unsigned long mask;
334 // check parameters
335 if ((num < 0) || (num > 31)) {
336 return false;
337 }
339 // parameters OK, now build the LED mask
340 mask = (0x80 >> (num % 8)) << ((3 - (num / 8)) * 8);
341 if (ctx->led_mask & mask) {
342 return true;
343 } else {
344 return false;
345 }
346 }
347 /* }}} */
349 /* lpfk_read {{{ */
350 int lpfk_read(LPFK_CTX *ctx)
351 {
352 int nbytes;
353 unsigned char key;
355 // make sure the LPFK is enabled before trying to read a scancode
356 if (!ctx->enabled) {
357 return LPFK_E_NOT_ENABLED;
358 }
360 // try and read a byte (keycode) from the LPFK
361 nbytes = read(ctx->fd, &key, 1);
363 if ((nbytes < 1) || (key > 31)) {
364 // no keys buffered, or keycode invalid.
365 return -1;
366 } else {
367 // key buffered, pass it along.
368 return key;
369 }
370 }
371 /* }}} */