Tue, 26 Aug 2008 12:45:33 +0100
initial commit
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 int lpfk_open(const char *port, LPFK_CTX *ctx)
60 {
61 struct termios newtio;
62 int status;
63 int fd;
64 int i;
66 // open the serial port
67 fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
68 if (fd < 0) return LPFK_E_PORT_OPEN;
70 // save current port settings
71 tcgetattr(fd, &ctx->oldtio);
73 // set up new parameters
74 memset(&newtio, 0, sizeof(newtio));
75 // 9600 baud, 8 bits, parity enabled, odd parity
76 newtio.c_cflag = B9600 | CS8 | PARENB | PARODD | CLOCAL | CREAD;
77 newtio.c_iflag = 0;
78 newtio.c_oflag = 0;
80 // set input mode -- non canonical, no echo
81 newtio.c_lflag = 0;
83 // inter-character timer unused
84 newtio.c_cc[VTIME] = 0;
85 // read does not block waiting for characters if there are none in the buffer
86 newtio.c_cc[VMIN] = 0;
88 // flush input buffer
89 tcflush(fd, TCIFLUSH);
91 // set new port config
92 tcsetattr(fd, TCSANOW, &newtio);
94 // set RTS true to pull the LPFK out of reset
95 ioctl(fd, TIOCMGET, &status);
96 status |= TIOCM_RTS;
97 ioctl(fd, TIOCMSET, &status);
99 // wait a few seconds for the LPFK to become ready
100 sleep(2);
102 // 0x06: READ CONFIGURATION. LPFK sends 0x03 in response.
103 // Try five times to wake it up.
104 status = false;
105 for (i=0; i<5; i++) {
106 unsigned char buf;
107 time_t tm;
109 // Send 0x06: READ CONFIGURATION, loop on failure
110 if (write(fd, "\x06", 1) < 1) {
111 continue;
112 }
114 // save current time (in seconds)
115 tm = time(NULL);
117 // loop until 2 seconds have passed, or LPFK responds
118 status = false;
119 do {
120 // read data, loop if not successful
121 if (read(fd, &buf, 1) < 1) {
122 continue;
123 }
125 // we got some data, what is it?
126 if (buf == 0x03) {
127 // 0x03 -- correct response. we're done.
128 status = true;
129 }
130 } while (((time(NULL) - tm) < 2) && (!status));
132 // exit loop if we got the LPFK to talk
133 if (status) {
134 break;
135 }
136 }
138 // Did the LPFK respond?
139 if (!status) {
140 // LPFK isn't talking. Restore serial port state and exit.
141 tcsetattr(fd, TCSANOW, &ctx->oldtio);
142 close(fd);
144 return LPFK_E_NOT_PRESENT;
145 } else {
146 // Initialise LPFK context
147 ctx->led_mask = 0;
148 ctx->fd = fd;
150 // Enable the LPFK
151 write(fd, "\x08", 1);
152 ctx->enabled = true;
154 // Return OK status
155 return LPFK_E_OK;
156 }
157 }
159 int lpfk_close(LPFK_CTX *ctx)
160 {
161 int status;
163 // 0x09: DISABLE. Stop the LPFK responding to keystrokes.
164 write(ctx->fd, "\x09", 1);
166 // turn all the LEDs off
167 lpfk_set_leds(ctx, false);
169 // set RTS false to put the LPFK into reset
170 ioctl(ctx->fd, TIOCMGET, &status);
171 status &= ~TIOCM_RTS;
172 ioctl(ctx->fd, TIOCMSET, &status);
174 // Restore the port state and close the serial port.
175 tcsetattr(ctx->fd, TCSANOW, &ctx->oldtio);
176 close(ctx->fd);
178 // Done!
179 return LPFK_E_OK;
180 }
182 int lpfk_enable(LPFK_CTX *ctx, int val)
183 {
184 if (val) {
185 // val == true, enable the LPFK
186 if (write(ctx->fd, "\x08", 1) != 1) {
187 ctx->enabled = true;
188 return LPFK_E_COMMS;
189 }
190 } else {
191 // val == false, disable the LPFK
192 if (write(ctx->fd, "\x09", 1) != 1) {
193 return LPFK_E_COMMS;
194 }
195 }
197 // update the context, return success
198 ctx->enabled = val;
199 return LPFK_E_OK;
200 }
202 /**
203 * @brief Set or clear an LED on the LPFK.
204 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
205 * @param num LED/key number, from 0 to 31.
206 * @param state State, true for on, false for off.
207 * @return LPFK_E_OK on success, LPFK_E_PARAM on bad parameter, LPFK_E_COMMS
208 * on comms error.
209 */
210 int lpfk_set_led(LPFK_CTX *ctx, const int num, const int state)
211 {
212 int i;
213 time_t tm;
214 unsigned long mask, leds;
215 unsigned char buf[5];
216 unsigned char status;
218 // check parameters
219 if ((num < 0) || (num > 31)) {
220 return LPFK_E_PARAM;
221 }
223 // parameters OK, now build the LED mask
224 mask = (0x80 >> (num % 8)) << ((num / 8) * 8);
226 leds = ctx->led_mask;
228 // mask the specified bit
229 if (state) {
230 leds |= mask;
231 } else {
232 leds &= ~mask;
233 }
235 // send new LED mask to the LPFK
236 buf[0] = 0x94;
237 buf[1] = leds & 0xff;
238 buf[2] = leds >> 8;
239 buf[3] = leds >> 16;
240 buf[4] = leds >> 24;
242 // make 5 attempts to set the LEDs
243 for (i=0; i<5; i++) {
244 if (write(ctx->fd, &buf, 5) < 5) {
245 continue;
246 }
248 // check for response -- 0x81 = OK, 0x80 = retransmit
249 // save current time (in seconds)
250 tm = time(NULL);
252 // loop until 2 seconds have passed, or LPFK responds
253 status = 0x00;
254 do {
255 // read data, loop if not successful
256 if (read(ctx->fd, &status, 1) < 1) {
257 continue;
258 }
260 // we got some data, what is it?
261 if (status == 0x81) {
262 // 0x81 -- received successfully
263 break;
264 }
265 } while ((time(NULL) - tm) < 2);
267 // status OK?
268 if (status == 0x81) {
269 // 0x81: OK
270 break;
271 } else if (status == 0x80) {
272 // 0x80: Retransmit request
273 continue;
274 }
275 }
277 // update the context
278 ctx->led_mask = leds;
280 return LPFK_E_OK;
281 }
283 /**
284 * @brief Set or clear all the LEDs on the LPFK.
285 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
286 * @param state State, true for on, false for off.
287 * @return LPFK_E_OK on success, LPFK_E_PARAM on bad parameter, LPFK_E_COMMS
288 * on comms error.
289 */
290 int lpfk_set_leds(LPFK_CTX *ctx, const int state)
291 {
292 int i;
293 time_t tm;
294 unsigned long leds;
295 unsigned char buf[5];
296 unsigned char status;
298 if (state) {
299 // all LEDs on
300 leds = 0xFFFFFFFF;
301 } else {
302 // all LEDs off
303 leds = 0x00000000;
304 }
306 // send new LED mask to the LPFK
307 buf[0] = 0x94;
308 buf[1] = leds & 0xff;
309 buf[2] = leds >> 8;
310 buf[3] = leds >> 16;
311 buf[4] = leds >> 24;
313 // make 5 attempts to set the LEDs
314 for (i=0; i<5; i++) {
315 if (write(ctx->fd, &buf, 5) < 5) {
316 continue;
317 }
319 // check for response -- 0x81 = OK, 0x80 = retransmit
320 // save current time (in seconds)
321 tm = time(NULL);
323 // loop until 2 seconds have passed, or LPFK responds
324 status = 0x00;
325 do {
326 // read data, loop if not successful
327 if (read(ctx->fd, &status, 1) < 1) {
328 continue;
329 }
331 // we got some data, what is it?
332 if (status == 0x81) {
333 // 0x81 -- received successfully
334 break;
335 }
336 } while ((time(NULL) - tm) < 2);
338 // status OK?
339 if (status == 0x81) {
340 // 0x81: OK
341 break;
342 } else if (status == 0x80) {
343 // 0x80: Retransmit request
344 continue;
345 }
346 }
348 // update the context
349 ctx->led_mask = leds;
351 return LPFK_E_OK;
352 }
354 /**
355 * @brief Get the status of an LED on the LPFK.
356 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
357 * @param num LED/key number, from 0 to 31.
358 * @return true if LED is on, false otherwise.
359 */
360 int lpfk_get_led(LPFK_CTX *ctx, const int num)
361 {
362 unsigned long mask;
364 // check parameters
365 if ((num < 0) || (num > 31)) {
366 return false;
367 }
369 // parameters OK, now build the LED mask
370 mask = (0x80 >> (num % 8)) << ((num / 8) * 8);
371 if (ctx->led_mask & mask) {
372 return true;
373 } else {
374 return false;
375 }
376 }
378 /**
379 * @brief Read a key from the LPFK
380 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
381 * @return -1 if no keys in buffer, 0-31 for key 1-32 down.
382 */
383 int lpfk_read(LPFK_CTX *ctx)
384 {
385 int nbytes;
386 unsigned char key;
388 // try and read a byte (keycode) from the LPFK
389 nbytes = read(ctx->fd, &key, 1);
391 if ((nbytes < 1) || (key > 31)) {
392 // no keys buffered, or keycode invalid.
393 return -1;
394 } else {
395 // key buffered, pass it along.
396 return key;
397 }
398 }