src/liblpfk.c

Tue, 26 Aug 2008 12:45:33 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Tue, 26 Aug 2008 12:45:33 +0100
changeset 0
745037d69d81
child 1
3e775df109e6
permissions
-rw-r--r--

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 }