/* barcode.c -- sample program to talk to the Socket Communications
   "In-Hand Scan" CompactFlash-form-factor Barcode Scanner.

   Copyright 2001 Mark W. Eichin <eichin@thok.org>

   This code is public domain, but I would appreciate being creditted
   so that I can get feedback and enhancements from people.

   This code has been tested on an iPAQ under linux, and on an x86
   laptop also under linux.  Note that on most arm-linux releases, you
   need to use

   # setserial /dev/ttyS0 baud_base 460800

   to force the baud_base to a value for which the baud rate settings
   actually work; hopefully this serial driver bug will be fixed at
   some point.


   Thanks to Jim Gettys (DEC/Compaq CRL) for loan of the iPAQ
   hardware, and Socket Communications for answering development-kit
   related questions.

*/

/* code based on mwe-sample.pl, from pdf version of 923 spec */
/* Serial code from my 1996 twconv.c for the twiddler */

#include <stdio.h>
#include <stdlib.h>		/* for malloc */
#include <sys/errno.h>		/* for EINTR */
#include <unistd.h>		/* for write, sleep */
#include <time.h>		/* for timespec */
#include <sys/ioctl.h>		/* for ioctl, TIOC* */

#include <sys/types.h>		/* for serial port open */
#include <sys/stat.h>		/* for serial port open */
#include <fcntl.h>		/* for serial port open */

#include <termios.h>		/* tcsetattr */

#include <stdarg.h>		/* for sendmsg */

/* evil global variables */
int scanner_fileno = -1;

/* dev macros */
#ifdef DEBUG
#define D fprintf
#else
/* GNU extension! define an inline stdarg function if you port */
#define D(...)
#endif

/* Scanner-specific data */
/* names from 923guide.pdf(6-3) */
enum scan_msg {
  AIM_ON, AIM_OFF, 
  BEEP, 
  CMD_ACK, CMD_NAK, 
  DECODE_DATA, EVENT, 
  LED_OFF, LED_ON, 
  PARAM_DEFAULTS, PARAM_REQUEST, PARAM_SEND,
  REPLY_REVISION, REQUEST_REVISION, 
  SCAN_DISABLE, SCAN_ENABLE, 
  SLEEP,
  START_DECODE, STOP_DECODE,
  WAKEUP,
};
typedef enum scan_msg scan_msg;

typedef struct scan_msg_entry {
  scan_msg verb;
  unsigned char *name;
  unsigned char opcode;
  enum argtype { NO_ARG, ONE_ARG, MANY_ARG, SPECIAL_WAKEUP } argtype;
} scan_msg_entry;

scan_msg_entry scanmsgs[] = {
  { AIM_ON,		"AIM_ON",	0xc4, NO_ARG, },
  { AIM_OFF,		"AIM_OFF",	0xc5, NO_ARG, },
  { BEEP,		"BEEP",		0xe6, ONE_ARG, }, /* beep code */
  { CMD_ACK,		"CMD_ACK",	0xd0, NO_ARG, },
  { CMD_NAK,		"CMD_NAK",	0xd1, ONE_ARG, }, /* type of error */
  { DECODE_DATA,	"DECODE_DATA",	0xf3, MANY_ARG, },
  { EVENT,		"EVENT",	0xf6, ONE_ARG, }, /* event tag */
  { LED_OFF,		"LED_OFF",	0xe8, ONE_ARG, }, /* led # (always 1) */
  { LED_ON,		"LED_ON",	0xe7, ONE_ARG, }, /* led # (always 1) */
  { PARAM_DEFAULTS,	"PARAM_DEFAULTS",	0xc8, NO_ARG, },
  { PARAM_REQUEST,	"PARAM_REQUEST",	0xc7, MANY_ARG, },
  { PARAM_SEND,		"PARAM_SEND",		0xc6, MANY_ARG, },
  { REPLY_REVISION,	"REPLY_REVISION",	0xa4, MANY_ARG, },
  { REQUEST_REVISION,	"REQUEST_REVISION",	0xa3, NO_ARG, },
  { SCAN_DISABLE,	"SCAN_DISABLE",	0xea, NO_ARG, },
  { SCAN_ENABLE,	"SCAN_ENABLE",	0xe9, NO_ARG, },
  { SLEEP,		"SLEEP",	0xeb, NO_ARG, },
  { START_DECODE,	"START_DECODE",	0xe4, NO_ARG, },
  { STOP_DECODE,	"STOP_DECODE",	0xe5, NO_ARG, },
  { WAKEUP,		"WAKEUP",	0,    SPECIAL_WAKEUP, },	/* just a NUL */
};

typedef enum token_tag {
  NAK_RESEND, NAK_BAD_CONTEXT, NAK_DENIED,
  DECODER_SE1200, DECODER_SE1200LR, DECODER_SE1200WA, DECODER_SE1200HV,
  DECODER_SE1200C1, DECODER_SE1200VHD, DECODER_SE900, DECODER_SE900C1, 
  CODE_NA, 
  CODE_39, CODE_93, CODE_128, 
  CODE_CODABAR, 
  CODE_DISCRETE_2_OF_5, CODE_IATA_2_of_5, CODE_INTERLEAVED_2_OF_5, 
  CODE_MSI_PLESSEY, 
  CODE_TRIOPTIC_39, 
  CODE_BOOKLAND_EAN, CODE_COUPON, 
  CODE_UPC_A,        CODE_UPC_E0,        CODE_UPC_E1, 
  CODE_UPC_A_PLUS_2, CODE_UPC_E0_PLUS_2, CODE_UPC_E1_PLUS_2, 
  CODE_UPC_A_PLUS_5, CODE_UPC_E0_PLUS_5, CODE_UPC_E1_PLUS_5, 
  CODE_EAN_8,        CODE_EAN_13, CODE_EAN_128,
  CODE_EAN_8_PLUS_2, CODE_EAN_13_PLUS_2, 
  CODE_EAN_8_PLUS_5, CODE_EAN_13_PLUS_5, 

  PARAM_BEEP_TONE, 
  PARAM_LASER_TIME, PARAM_AIM_TIME, 
  PARAM_POWER_MODE, PARAM_TRIGGER_MODE, 
  PARAM_DUP_TIMEOUT, PARAM_BEEP_ON_GOOD, 
  PARAM_XMIT_NR, PARAM_PERMIT_SCANNING, 
  PARAM_LINEAR_SECURITY, PARAM_TWOWAY_REP,
  PARAM_UPC_A, PARAM_UPC_E, PARAM_UPC_E1, 
  PARAM_EAN_8, PARAM_EAN_13, PARAM_BOOKLAND, 
  PARAM_DECODE_PLUS, PARAM_DECODE_PLUS_REP, 
  PARAM_UPC_A_CHECK, PARAM_UPC_E_CHECK, PARAM_UPC_E1_CHECK, 
  PARAM_UPC_A_PRE, PARAM_UPC_E_PRE, PARAM_UPC_E1_PRE, 
  PARAM_UPC_E_AS_A, PARAM_UPC_E1_AS_A, 
  PARAM_EAN_8_ZERO, PARAM_EAN_8_AS_13, 
  PARAM_UPC_SECURITY, PARAM_UPC_COUPON,
  PARAM_USS_128, PARAM_EAN_128, PARAM_ISBT_128, 
  PARAM_CODE_39, PARAM_TRIOPTIC_39, PARAM_CODE_39_AS_32, 
  PARAM_CODE_39_PRE, PARAM_CODE_39_LENGTH_1, PARAM_CODE_39_LENGTH_2, 
  PARAM_CODE_39_VERIFY, PARAM_CODE_39_CHECK, PARAM_CODE_39_ASCII,
  PARAM_CODE_93, PARAM_CODE_93_LENGTH_1, PARAM_CODE_93_LENGTH_2,
  PARAM_INTERLEAVED_2_OF_5, 
  PARAM_INTERLEAVED_2_OF_5_LENGTH_1, PARAM_INTERLEAVED_2_OF_5_LENGTH_2, 
  PARAM_INTERLEAVED_2_OF_5_VERIFY, PARAM_INTERLEAVED_2_OF_5_CHECK, 
  PARAM_INTERLEAVED_2_OF_5_AS_EAN_13,
  PARAM_DISCRETE_2_OF_5, 
  PARAM_DISCRETE_2_OF_5_LENGTH_1, PARAM_DISCRETE_2_OF_5_LENGTH_2,
  PARAM_CODABAR, PARAM_CODABAR_LENGTH_1, PARAM_CODABAR_LENGTH_2, 
  PARAM_CLSI_EDIT, PARAM_NOTIS_EDIT,
  PARAM_MSI_PLESSEY, 
  PARAM_MSI_PLESSEY_LENGTH_1, PARAM_MSI_PLESSEY_LENGTH_2, 
  PARAM_MSI_PLESSEY_VERIFY, PARAM_MSI_PLESSEY_CHECK, PARAM_MSI_PLESSEY_ALGO,
  PARAM_XMIT_CODE_ID,
  PARAM_DECODE_PREFIX, PARAM_DECODE_SUFFIX_1, PARAM_DECODE_SUFFIX_2, 
  PARAM_DECODE_PACKET, PARAM_SCAN_DATA_FORMAT,
  PARAM_BAUD, PARAM_PARITY, PARAM_STOP_BITS, PARAM_HANDSHAKE, 
  PARAM_HOST_RESPONSE_TIMEOUT, PARAM_INTERCHAR_DELAY, PARAM_HOST_CHAR_TIMEOUT,
  PARAM_EVENT_PREFIX, 
  PARAM_EVENT_DECODE, PARAM_EVENT_BOOTUP, PARAM_EVENT_PARAM,
  PARAM_SCAN_ANGLE, 
  PARAM_ALL,
} token_tag;

typedef enum token_class {
  NAK, DECODER, CODE, PARAM, PARAM1, 
} token_class;

typedef struct token_name_entry {
  token_class tcl;
  token_tag tag;
  unsigned char *name;
  unsigned char value;
} token_name_entry;

token_name_entry token_names[] = {
  { NAK, NAK_RESEND,		"RESEND (bad cksum)",	1 },
  { NAK, NAK_BAD_CONTEXT,	"BAD_CONTEXT (bug?)",	2 },
  { NAK, NAK_DENIED,		"DENIED",		6 },
  { DECODER, DECODER_SE1200,    "SE 1200 Standard", 0x00 },
  { DECODER, DECODER_SE1200LR, 	"SE 1200LR (Long Range)", 0x01 },
  { DECODER, DECODER_SE1200WA, 	"SE 1200WA (Wide Angle)", 0x02 },
  { DECODER, DECODER_SE1200HV, 	"SE 1200HV (High Visibility)", 0x03 },
  { DECODER, DECODER_SE1200C1, 	"SE 1200C1 (Class 1)", 0x04 },
  { DECODER, DECODER_SE1200VHD, "SE 1200VHD (Very High Density)", 0x05 },
  { DECODER, DECODER_SE900,  	"SE 900 Standard", 0x28 },
  { DECODER, DECODER_SE900C1,	"SE 900C1 IEC Class 1", 0x2a },
  /* 6-16, table 6-6, "Supported Code Types" */
  { CODE, CODE_NA,		"Not Applicable", 0x00 },
  { CODE, CODE_39,		"Code 39", 0x01 },
  { CODE, CODE_CODABAR,		"Codabar", 0x02 },
  { CODE, CODE_128,		"Code 128", 0x03 },
  { CODE, CODE_DISCRETE_2_OF_5,	"Discrete 2 of 5", 0x04 },
  { CODE, CODE_IATA_2_of_5,	"IATA 2 of 5", 0x05 },
  { CODE, CODE_INTERLEAVED_2_OF_5,	"Interleaved 2 of 5", 0x06 },
  { CODE, CODE_93,		"Code 93", 0x07 },
  { CODE, CODE_UPC_A,		"UPC A", 0x08 },
  { CODE, CODE_UPC_E0,		"UPC E0", 0x09 },
  { CODE, CODE_EAN_8,		"EAN 8", 0x0A },
  { CODE, CODE_EAN_13,		"EAN 13", 0x0B },
  { CODE, CODE_MSI_PLESSEY,	"MSI Plessey", 0x0E },
  { CODE, CODE_EAN_128,		"EAN 128", 0x0F },
  { CODE, CODE_UPC_E1,		"UPC E1", 0x10 },
  { CODE, CODE_TRIOPTIC_39,	"Trioptic Code 39", 0x15 },
  { CODE, CODE_BOOKLAND_EAN,	"Bookland EAN", 0x16 },
  { CODE, CODE_COUPON,		"Coupon Code", 0x17 },
  { CODE, CODE_EAN_8_PLUS_2,	"EAN 8 with 2 Supps.", 0x4A },
  { CODE, CODE_EAN_13_PLUS_2,	"EAN 13 with 2 Supps.", 0x4B },
  { CODE, CODE_UPC_A_PLUS_2,	"UPC A with 2 Supps.", 0x48 },
  { CODE, CODE_UPC_E0_PLUS_2,	"UPC E0 with 2 Supps.", 0x49 },
  { CODE, CODE_UPC_E1_PLUS_2,	"UPC E1 with 2 Supps.", 0x50 },
  { CODE, CODE_UPC_A_PLUS_5,	"UPC A with 5 Supps.", 0x88 },
  { CODE, CODE_UPC_E0_PLUS_5,	"UPC E0 with 5 Supps.", 0x89 },
  { CODE, CODE_EAN_8_PLUS_5,	"EAN 8 with 5 Supps.", 0x8A },
  { CODE, CODE_EAN_13_PLUS_5,	"EAN 13 with 5 Supps.", 0x8B },
  { CODE, CODE_UPC_E1_PLUS_5,	"UPC E1 with 5 Supps.", 0x90 },
  /* 5-2, table 5-1, parameters */
  { PARAM, PARAM_BEEP_TONE,	"Beeper Tone", 0x91 }, /* Medium Frequency 5-9 */
  { PARAM, PARAM_LASER_TIME,	"Laser On Time", 0x88 }, /* 3.0 sec 5-10 */
  { PARAM, PARAM_AIM_TIME,	"Aim Duration", 0xED }, /* 0.0 sec 5-11 */
  { PARAM, PARAM_POWER_MODE,	"Power Mode", 0x80 }, /* Low Power 5-12 */
  { PARAM, PARAM_TRIGGER_MODE,	"Trigger Mode", 0x8A }, /* Level 5-13 */
  { PARAM, PARAM_DUP_TIMEOUT,	"Time-out Between Same Symbol", 0x89 }, /* 1.0 sec 5-15 */
  { PARAM, PARAM_BEEP_ON_GOOD,	"Beep After Good Decode", 0x38 }, /* Enable 5-16 */
  { PARAM, PARAM_XMIT_NR,	"Transmit  No Read  Message", 0x5E }, /* Disable 5-17 */
  { PARAM, PARAM_PERMIT_SCANNING,	"Parameter Scanning", 0xEC }, /* Enable 5-18 */
  { PARAM, PARAM_LINEAR_SECURITY,	"Linear Code Type Security Levels", 0x4E }, /* 1 5-19 */
  { PARAM, PARAM_TWOWAY_REP,	"Bi-directional Redundancy", 0x43 }, /* Disable 5-22 */
  /* UPC/EAN */
  { PARAM, PARAM_UPC_A,		"UPC-A", 0x01 }, /* Enable 5-23 */
  { PARAM, PARAM_UPC_E,		"UPC-E", 0x02 }, /* Enable 5-24 */
  { PARAM, PARAM_UPC_E1,	"UPC-E1", 0x0C }, /* Disable 5-25 */
  { PARAM, PARAM_EAN_8,		"EAN-8", 0x04 }, /* Enable 5-26 */
  { PARAM, PARAM_EAN_13,	"EAN-13", 0x03 }, /* Enable 5-27 */
  { PARAM, PARAM_BOOKLAND,	"Bookland EAN", 0x53 }, /* Disable 5-28 */
  { PARAM, PARAM_DECODE_PLUS,	"Decode UPC/EAN Supplementals", 0x10 }, /* Ignore 5-30 */
  { PARAM, PARAM_DECODE_PLUS_REP,	"Decode UPC/EAN Supplemental Redundancy", 0x50 }, /* 7 5-31 */
  { PARAM, PARAM_UPC_A_CHECK,	"Transmit UPC-A Check Digit", 0x28 }, /* Enable 5-32 */
  { PARAM, PARAM_UPC_E_CHECK,	"Transmit UPC-E Check Digit", 0x29 }, /* Enable 5-33 */
  { PARAM, PARAM_UPC_E1_CHECK,	"Transmit UPC-E1 Check Digit", 0x2A }, /* Enable 5-34 */
  { PARAM, PARAM_UPC_A_PRE,	"UPC-A Preamble", 0x22 }, /* System Character 5-35 */
  { PARAM, PARAM_UPC_E_PRE,	"UPC-E Preamble", 0x23 }, /* System Character 5-36 */
  { PARAM, PARAM_UPC_E1_PRE,	"UPC-E1 Preamble", 0x24 }, /* System Character 5-37 */
  { PARAM, PARAM_UPC_E_AS_A,	"Convert UPC-E to A", 0x25 }, /* Disable 5-38 */
  { PARAM, PARAM_UPC_E1_AS_A,	"Convert UPC-E1 to A", 0x26 }, /* Disable 5-39 */
  { PARAM, PARAM_EAN_8_ZERO,	"EAN-8 Zero Extend", 0x27 }, /* Disable 5-40 */
  { PARAM, PARAM_EAN_8_AS_13,	"Convert EAN-8 to EAN-13 Type", 0xE0 }, /* Type is EAN-13 5-41 */
  { PARAM, PARAM_UPC_SECURITY,	"UPC/EAN Security Level", 0x4D }, /* 0 5-42 */
  { PARAM, PARAM_UPC_COUPON,	"UPC/EAN Coupon Code", 0x55 }, /* Disable 5-44 */
  /* Code 128 */
  { PARAM, PARAM_USS_128,	"USS-128", 0x08 }, /* Enable 5-45 */
  { PARAM, PARAM_EAN_128,	"UCC/EAN-128", 0x0E }, /* Enable 5-46 */
  { PARAM, PARAM_ISBT_128,	"ISBT 128", 0x54 }, /* Enable 5-47 */
  { PARAM, PARAM_CODE_39,	"Code 39", 0x00 }, /* Enable 5-48 */
  { PARAM, PARAM_TRIOPTIC_39,	"Trioptic Code 39", 0x0D }, /* Disable 5-49 */
  { PARAM, PARAM_CODE_39_AS_32,	"Convert Code 39 to Code 32", 0x56 }, /* Disable 5-50 */
  { PARAM, PARAM_CODE_39_PRE,	"Code 32 Prefix", 0xE7 }, /* Disable 5-51 */
  { PARAM, PARAM_CODE_39_LENGTH_1,	"Set Length 1 for Code 39", 0x12 }, /* 2..55, 5-53  */
  { PARAM, PARAM_CODE_39_LENGTH_2,	"Set Length 2 for Code 39", 0x13 }, /* 2..55, 5-53 */
  { PARAM, PARAM_CODE_39_VERIFY,	"Code 39 Check Digit Verification", 0x30 }, /* Disable 5-54 */
  { PARAM, PARAM_CODE_39_CHECK,	"Transmit Code 39 Check Digit", 0x2B }, /* Disable 5-55 */
  { PARAM, PARAM_CODE_39_ASCII,	"Code 39 Full ASCII Conversion", 0x11 }, /* Disable 5-56 */
  /* Code 93 */
  { PARAM, PARAM_CODE_93,	"Code 93", 0x09 }, /* Disable 5-57 */
  { PARAM, PARAM_CODE_93_LENGTH_1,	"Set Length 1 for Code 93", 0x1A }, /* 4..55, 5-58 */
  { PARAM, PARAM_CODE_93_LENGTH_2,	"Set Length 2 for Code 93", 0x1B }, /* 4..55, 5-58 */
  /* Interleaved 2 of 5 */
  { PARAM, PARAM_INTERLEAVED_2_OF_5,	"Interleaved 2 of 5", 0x06 }, /* Enable 5-60 */
  { PARAM, PARAM_INTERLEAVED_2_OF_5_LENGTH_1,	"Set Length 1 for Interleaved 2 of 5", 0x16 }, /* 14 5-61 */
  { PARAM, PARAM_INTERLEAVED_2_OF_5_LENGTH_2,	"Set Length 2 for Interleaved 2 of 5", 0x17 }, /* 14 5-61 */
  { PARAM, PARAM_INTERLEAVED_2_OF_5_VERIFY,	"Interleaved 2 of 5 Check Digit Verification", 0x31 }, /* Disable 5-63 */
  { PARAM, PARAM_INTERLEAVED_2_OF_5_CHECK,	"Transmit Interleaved 2 of 5 Check Digit", 0x2C }, /* Disable 5-64 */
  { PARAM, PARAM_INTERLEAVED_2_OF_5_AS_EAN_13,	"Convert Interleaved 2 of 5 to EAN 13", 0x52 }, /* Disable 5-65 */
  /* Discrete 2 of 5 */
  { PARAM, PARAM_DISCRETE_2_OF_5,	"Discrete 2 of 5", 0x05 }, /* Disable 5-66 */
  { PARAM, PARAM_DISCRETE_2_OF_5_LENGTH_1,	"Set Length 1 for Discrete 2 of 5", 0x14 }, /* 12 5-67 */
  { PARAM, PARAM_DISCRETE_2_OF_5_LENGTH_2,	"Set Length 2 for Discrete 2 of 5", 0x15 }, /* 12 5-67 */
  /* Codabar */ 
  { PARAM, PARAM_CODABAR,	"Codabar", 0x07 }, /* Disable 5-69 */
  { PARAM, PARAM_CODABAR_LENGTH_1,	"Set Length 1 for Codabar", 0x18 }, /* 5-55 */
  { PARAM, PARAM_CODABAR_LENGTH_2,	"Set Length 2 for Codabar", 0x19 }, /* 5-71 */
  { PARAM, PARAM_CLSI_EDIT,	"CLSI Editing", 0x36 }, /* Disable 5-72 */
  { PARAM, PARAM_NOTIS_EDIT,	"NOTIS Editing", 0x37 }, /* Disable 5-73 */
  /* MSI Plessey */
  { PARAM, PARAM_MSI_PLESSEY,	"MSI Plessey", 0x0B }, /* Disable 5-74 */
  { PARAM, PARAM_MSI_PLESSEY_LENGTH_1,	"Set Length 1 for MSI Plessey", 0x1E }, /* 6..55 5-76 */
  { PARAM, PARAM_MSI_PLESSEY_LENGTH_2,	"Set Length 2 for MSI Plessey", 0x1F }, /* 6..55 5-76 */
  { PARAM, PARAM_MSI_PLESSEY_VERIFY,	"MSI Plessey Check Digits", 0x32 }, /* One 5-77 */
  { PARAM, PARAM_MSI_PLESSEY_CHECK,	"Transmit MSI Plessey Check Digit", 0x2E }, /* Disable 5-78 */
  { PARAM, PARAM_MSI_PLESSEY_ALGO,	"MSI Plessey Check Digit Algorithm", 0x33 }, /* Mod 10/Mod 10 5-79 */
  /* Data Options */
  { PARAM, PARAM_XMIT_CODE_ID,	"Transmit Code ID Character", 0x2D }, /* None 5-81 */
  /* Prefix/Suffix Values */
  { PARAM, PARAM_DECODE_PREFIX,	"Prefix", 0x69 },   /* NULL 5-82 */
  { PARAM, PARAM_DECODE_SUFFIX_1,	"Suffix 1", 0x68 }, /* LF 5-82 */
  { PARAM, PARAM_DECODE_SUFFIX_2,	"Suffix 2", 0x6A }, /* CR 5-82 */
  { PARAM, PARAM_SCAN_DATA_FORMAT,	"Scan Data Transmission Format", 0xEB }, /* Data as is 5-84 */
  /* Serial Interface */
  { PARAM, PARAM_BAUD,	"Baud Rate", 0x9C }, /* 9600 5-88 */
  { PARAM, PARAM_PARITY,	"Parity", 0x9E }, /* None 5-89 */
  { PARAM, PARAM_HANDSHAKE,	"Software Handshaking", 0x9F }, /* Enable 5-91 */
  { PARAM, PARAM_DECODE_PACKET,	"Decode Data Packet Format", 0xEE }, /* Unpacketed 5-92 */
  { PARAM, PARAM_HOST_RESPONSE_TIMEOUT,	"Host Serial Response Time-out (in tenths of a second)", 0x9B }, /* 2 sec 5-93 */
  { PARAM, PARAM_STOP_BITS,	"Stop Bit Select", 0x9D }, /* 1 5-94 */
  { PARAM, PARAM_INTERCHAR_DELAY,	"Intercharacter Delay", 0x6E }, /* 0 5-95 */
  { PARAM, PARAM_HOST_CHAR_TIMEOUT,	"Host Character Time-out (in 10msec units)", 0xEF }, /* 200 msec 5-96 */
  /* Event Reporting */
  /* note that these are two byte, we just cheat */
  { PARAM, PARAM_EVENT_PREFIX,	"Event Prefix ", 0xF0 },
  { PARAM1, PARAM_EVENT_DECODE,	"Decode Event",    0x00 }, /*  Disable 5-98 */
  { PARAM1, PARAM_EVENT_BOOTUP,	"Boot Up Event",   0x02 }, /*  Disable 5-99 */
  { PARAM1, PARAM_EVENT_PARAM,	"Parameter Event", 0x03 }, /*  Disable 5-100 */
  /* Scan Angle */
  { PARAM, PARAM_SCAN_ANGLE,	"Scan Angle", 0xBF }, /* Normal Width 5-101 */
  { PARAM, PARAM_ALL,		"Request ALL Values", 0xFE },
};

unsigned char *lookup_opcode_name(unsigned char opcode) {
  int i;

  for (i=0; i< sizeof(scanmsgs)/sizeof(*scanmsgs); i++) {
    if(scanmsgs[i].opcode == opcode) {
      return scanmsgs[i].name;
    }
  }
  return 0;
}

unsigned char lookup_scan_opcode(scan_msg verb) {
  int i;

  for (i=0; i< sizeof(scanmsgs)/sizeof(*scanmsgs); i++) {
    if(scanmsgs[i].verb == verb) {
      return scanmsgs[i].opcode;
    }
  }
  return 0;
}

unsigned char *lookup_token_value(token_class tcl, unsigned char value) {
  int i;

  for (i=0; i< sizeof(token_names)/sizeof(*token_names); i++) {
    if(token_names[i].tcl == tcl && token_names[i].value == value) {
      return token_names[i].name;
    }
  }
  return 0;
}

unsigned char lookup_tag_value(token_tag tag) {
  int i;

  for (i=0; i< sizeof(token_names)/sizeof(*token_names); i++) {
    if(token_names[i].tag == tag) {
      return token_names[i].value;
    }
  }
  return 0;
}

token_tag lookup_value_tag(token_class tcl, unsigned char value) {
  int i;

  for (i=0; i< sizeof(token_names)/sizeof(*token_names); i++) {
    if(token_names[i].tcl == tcl && token_names[i].value == value) {
      return token_names[i].tag;
    }
  }
  return 0;
}

void shortsleep(double s) {
  struct timespec ts;

  ts.tv_sec = (int)s;
  ts.tv_nsec = (s-(double)(ts.tv_sec))*1e9;
  while(nanosleep(&ts, &ts) < 0) { /* didn't complete? */
    if (errno == EINTR) continue; /* ok if interrupt */
    break;			/* maybe not ok */
  }
}

void raise_rts_not() {
  int gmi = 0;
  int st;
  st = ioctl(scanner_fileno,TIOCMGET, &gmi);
  if(st) perror("tiocmget");
  gmi |= TIOCM_RTS;
  st = ioctl(scanner_fileno,TIOCMSET, &gmi);
  if(st) perror("tiocmset");
  D(stderr, "[raised rtsnot]");
  return;
}

void drop_rts_not() {
  int gmi = 0;
  int st;
  st = ioctl(scanner_fileno,TIOCMGET, &gmi);
  if(st) perror("tiocmget");
  gmi &= ~TIOCM_RTS;
  st = ioctl(scanner_fileno,TIOCMSET, &gmi);
  if(st) perror("tiocmset");
  D(stderr, "[dropped rtsnot]");
  return;
}

void wait_raise_cts_not() {
  int gmi = 0;
  int st;
  int cnt = 0;
  for (;cnt < 10000000;cnt++) {
    st = ioctl(scanner_fileno,TIOCMGET, &gmi);
    if(st) perror("tiocmget");
    if(gmi & TIOCM_CTS) { 
      D(stderr, "[ctsnot up (%d)]", cnt); 
      return; 
    }
  }
  D(stderr, "[ctsnot up timeout (%d)]\n", cnt); 
  return;
}

void wait_drop_cts_not() {
  int gmi = 0;
  int st;
  int cnt = 0;
  for (;cnt < 10000000;cnt++) {
    st = ioctl(scanner_fileno,TIOCMGET, &gmi);
    if(st) perror("tiocmget");
    if(!(gmi & TIOCM_CTS)) { 
      D(stderr, "[ctsnot down (%d)]", cnt); 
      return;
    }
  }
  D(stderr, "[ctsnot down timeout (%d)]\n", cnt); 
  return;
}

void emit_char(unsigned char c) {
  /*  shortsleep(0.001); */
  write(scanner_fileno,&c,sizeof c); /* just goes to stdout for now */
}

void fetch_char(unsigned char *c) {
  int st;
  for(;;) {
    st = read(scanner_fileno, c, sizeof(*c));
    if (st == sizeof(*c)) return;
    if (st < 0) { perror("read"); exit(2); }
    /* relies on VTIME setting on scanner_fileno */
    D(stderr,"null read?\n");
    
    drop_rts_not();
    wait_drop_cts_not();
    shortsleep(0.1);
    raise_rts_not();
    wait_raise_cts_not();
  }
}

void send_ack(void);

void pkt_read() {
  unsigned char len;
  unsigned char opcode;
  unsigned char source;
  unsigned char status;
  unsigned short cksum_accum = 0;
  unsigned char *dat = 0;
  int i = 0;

  /* shortsleep(0.01); */

  D(stderr, "starting pkt_read\n");
  fetch_char(&len); cksum_accum -= len; len--;	/* len counts! */
  fetch_char(&opcode); cksum_accum -= opcode; len--; 
  D(stderr, "got %d bytes, opcode %02x\n", len+2, opcode);
  {
    unsigned char *msg= lookup_opcode_name(opcode);
    if(msg)fprintf(stderr,"Opcode=%s\n",msg);
  }


  fetch_char(&source); cksum_accum -= source; len--;
  D(stderr, "source %02x\n", source);
  if(source != 0) fprintf(stderr, "source(%02x) != decoder(0)\n", source);
  fetch_char(&status); cksum_accum -= status; len--;
  D(stderr, "status %02x\n", status);
  dat = malloc(len+1);

  while(len > 0) {
    /* fetch_char handles timeout-and-bounce-status */
    fetch_char(&dat[i]); cksum_accum -= dat[i]; i++; len--;
    D(stderr, "%02x.(%d)", dat[i-1], len);
    fprintf(stderr, "<");
  }

  dat[i] = 0;
  D(stderr, "got %d\n",len);
  /* opcode specific processing */
  if (opcode == lookup_scan_opcode(CMD_NAK)) {
    unsigned char *msg=lookup_token_value(NAK, dat[0]);
    if(msg)fprintf(stderr,"NAK type=%s\n",msg);
  } else if (opcode == lookup_scan_opcode(REPLY_REVISION)) {
    int j;
    unsigned char *msg=0;
    fprintf(stderr, "REPLY_REVISION: Software rev[");
    for (j = 0; j<i; j++) {
      if (dat[j] == ' ') break;
      fprintf(stderr, "%c", dat[j]);
    }
    fprintf(stderr, "] board type ");
    j++;
    switch(dat[j]) {
    case 'N': fprintf(stderr, "[%c (Non-flash)]", dat[j]);  break;
    case 'F': fprintf(stderr, "[%c (Flash)]", dat[j]);      break;
    default: 
      fprintf(stderr, "[%02x(%c) unknown]", dat[j], dat[j]); 
      break;
    }
    j++;
    if (dat[j] != ' ') 
      fprintf(stderr, "%c(%02x) not space!\n", dat[j], dat[j]);
    j++;
    fprintf(stderr, "\n\tDecoder Type");
    msg = lookup_token_value(DECODER, dat[j]);
    if (msg)
      fprintf(stderr, " %02x <%s>", dat[j], msg);
    else
      fprintf(stderr, " unknown/reserved type %02x", dat[j]);
    j++;
    fprintf(stderr, " Decoder Checksum %02x%02x\n", dat[j], dat[j+1]);
  } else if (opcode == lookup_scan_opcode(DECODE_DATA)) {
    unsigned char *codetype = lookup_token_value(CODE, dat[0]);
    if (codetype) 
      fprintf(stderr, "DECODE_DATA: type %s, scan [%s]\n", codetype, dat+1);
    else
      fprintf(stderr, "DECODE_DATA with INVALID type\n");
  } else if (opcode == lookup_scan_opcode(PARAM_SEND)) {
    /* probably response to PARAM_REQUEST */
    /* just loop through... */
    int j;
    unsigned char* msg;
    fprintf(stderr, "PARAM_SEND from device (beep code 0x%x):\n", dat[0]);
    fprintf(stderr, "Value\t\tName\n");
    fprintf(stderr, "-------\t\t-------------------\n");
    for (j = 1; j < i; j++) { /* d[i] == trailing 0 */
      switch(lookup_value_tag(PARAM, dat[j])) {
      case PARAM_EVENT_PREFIX:
	msg = lookup_token_value(PARAM1, dat[++j]); 
	if (!msg) msg="UNDOCUMENTED EXTENDED FIELD";
	break;
      default:
	msg = lookup_token_value(PARAM, dat[j]); break;
      }
      if (!msg) msg="UNDOCUMENTED FIELD";
      j++;
      fprintf(stderr, "0x%x(%d)\t%s\n", dat[j], dat[j], msg);
    }
  } /* add more decoders here */

  {
    unsigned short cksum;
    unsigned char dat;
    fetch_char(&dat);
    /* D(stderr, "{cksum hi %02x}", dat); */
    cksum = dat << 8;
    fetch_char(&dat);
    /* D(stderr, "{cksum lo %02x}", dat); */
    cksum |= dat;
    D(stderr, "[cksum %04x/accum %04x]\n", cksum, cksum_accum);
    if (cksum != cksum_accum) 
      fprintf(stderr, "Checksum mismatch! [cksum %04x/accum %04x]\n",
	      cksum, cksum_accum);
  }
  shortsleep(0.01);

  /* now ack things */
  if (opcode == lookup_scan_opcode(CMD_ACK)) {
  } else if (opcode == lookup_scan_opcode(CMD_NAK)) {
  } else {
    D(stderr, "Sending ACK for 0x%02x\n", opcode);
    send_ack();
  }
}

void wakeup(void) {
  unsigned char wakestr[] = { 0 /*, 0, 0 */ };
  write(scanner_fileno,&wakestr,sizeof wakestr); 
}

void spew(size_t s_len, unsigned char* s) {
  size_t i;
  for (i = 0; i<s_len; i++) {
    /* D(stderr, "%02x.", s[i]); */
    D(stderr, ">");
    emit_char(s[i]);
    /* shortsleep(0.2); */
  }
  D(stderr, "\n");
}

int global_rexmit = 0;

void spew_makemsg(unsigned char opcode, unsigned char *s, size_t s_len) {
  unsigned char hdr[] = { 0,	/* opcode */
			  4,	/* source = host */
			  0 };	/* status */
  unsigned char* msg = malloc(sizeof(hdr) + s_len
			      + 1 /* len */ + 2 /* cksum */);
  int i;
  unsigned short cksum = 0;	/* uint_16_t... */

  if (global_rexmit) { hdr[2] = 1; global_rexmit = 0; }

  hdr[0] = opcode;
  msg[0] = 1 + s_len + sizeof(hdr);
  memcpy(msg+1, hdr, sizeof(hdr));
  memcpy(msg+1+sizeof(hdr), s, s_len);
  for (i = 0; i < 1+sizeof(hdr)+s_len; i++) {
    cksum += msg[i];
  }

  cksum = 65536 - cksum;
  msg[1+sizeof(hdr)+s_len] = cksum >> 8;
  msg[1+sizeof(hdr)+s_len+1] = cksum & 255;

  drop_rts_not();
  wait_drop_cts_not();
  shortsleep(0.1);
  spew(1+sizeof(hdr)+s_len+2, msg);
  shortsleep(0.1);
  raise_rts_not();
  wait_raise_cts_not();
  free(msg);
}


void send_ack() {
  D(stderr, "Sending ACK\n");
  wait_raise_cts_not();		/* should be true */
  {
    unsigned char s[] = { };
    spew_makemsg(0xd0, s, sizeof(s)); /* CMD_ACK */
  }
}

void sendmsg(scan_msg verb, ...) {
  va_list ap;
  int i;
  scan_msg_entry *thismsg = 0;
  unsigned short cksum;
  unsigned char *freeme = 0;
  unsigned char *workmsg = 0;
  unsigned char basemsg[] = { 0, /* length */
			      0, /* opcode */
			      4, /* source = host */
			      0, /* status */
			      0, /* optional 1-byte content */
			      0, 0, }; /* cksum */
  va_start(ap, verb);
  workmsg = basemsg;

  for (i=0; i< sizeof(scanmsgs)/sizeof(*scanmsgs); i++) {
    if(scanmsgs[i].verb == verb) {
      thismsg = &scanmsgs[i];
      break;
    }
  }
  if (!thismsg) return;		/* error */

  workmsg[1] = thismsg->opcode;
  switch (thismsg->argtype) {
  case NO_ARG:
    workmsg[0] = 4;
    break;
  case ONE_ARG:
    workmsg[0] = 5;
    workmsg[4] = va_arg(ap, int);
    break;
  case MANY_ARG:		/* -1 term */
    {
      int len = 4;		/* starting size+offset */
      int bytearg;
      freeme = malloc(len);	/* error check! */
      memcpy(freeme, workmsg, len);

      while( (bytearg = va_arg(ap, int)) != -1) {
	freeme = realloc(freeme, len+1);
	freeme[len++] = bytearg;
      }
      freeme[0] = len;
      freeme = realloc(freeme, len+2); /* room for cksum */
      workmsg = freeme;
    }
    break;
  default: 
    abort(); 
    break;
  }

  cksum = 0;
  for (i = 0; i < workmsg[0]; i++) {
    cksum += workmsg[i];
  }
  cksum = 65536 - cksum;
  workmsg[workmsg[0]] = cksum >> 8;
  workmsg[workmsg[0]+1] = cksum & 255;

  /* brag about it */
  fprintf(stderr, "Sending: %s (%d bytes)\n", thismsg->name, workmsg[0]);

  /* actually send it */
  drop_rts_not();
  wait_drop_cts_not();
  shortsleep(0.1);
  spew(workmsg[0]+2, workmsg);
  shortsleep(0.1);
  raise_rts_not();
  wait_raise_cts_not();

  if (freeme) free(freeme);

}

void usage(char *argv0) {
  fprintf(stderr, "Usage: %s [--diag] [--port dev] [--help]\n", argv0);
  fprintf(stderr, "\t--diag\tRun more debugging queries like PARAM_REQUEST(ALL)\n");
  fprintf(stderr, "\t--port dev\tSpecify the serial port to which the scanner is attached\n");
}

int main(int argc, char**argv) {
  int i;
  int diag_mode = 0;
  char *scanner_port_name = "/dev/ttyS2";

  /* parse some args */
  for (i = 1; i < argc; i++) {
    if (!strcmp(argv[i], "--diag")) {
      diag_mode = 1; 
      continue;
    } else if (!strcmp(argv[i], "--port")) {
      i++;
      if (i < argc) {
	scanner_port_name = argv[i];
	continue;
      }
    } else if (!strcmp(argv[i], "--help")) {
      usage(argv[0]);
      exit(0);
    }
    usage(argv[0]);
    exit(1);
  }
  

  /*  system("stty raw -iexten -onlcr -echo -echoe -echok -echoctl -echoke 9600 < /dev/ttyS2"); */
  scanner_fileno = open(scanner_port_name, O_NOCTTY|O_RDWR|O_SYNC);

  {
    int st;
    struct termios tio;

    st = tcgetattr(scanner_fileno, &tio);
    if (st < 0) { perror("tcgetattr"); exit(1); }
    tio.c_cflag = 0;     tio.c_lflag = 0;
    cfmakeraw(&tio);
    cfsetospeed(&tio, B9600);
    cfsetispeed(&tio, B9600);
    tio.c_oflag &= (~ONLCR);
    tio.c_cflag |= (CLOCAL|CREAD|HUPCL);
    D(stderr, "iflag 0x%x oflag 0x%x cflag 0x%x lflag 0x%x\n", 
	    tio.c_iflag, tio.c_oflag, tio.c_cflag, tio.c_lflag);
    memset(&tio.c_cc, 0, sizeof(tio.c_cc));
    tio.c_cc[VMIN] = 0;
    tio.c_cc[VTIME] = 2;	/* PARAM_HOST_RESPONSE_TIMEOUT */
    st = tcsetattr(scanner_fileno, TCSANOW, &tio);
    if (st < 0) { perror("tcsetattr"); exit(1); }
  }

  if (scanner_fileno < 0) { perror("scanner open"); exit(2); }
  fprintf(stderr, "============================\n");

  /* must turn on the LED */
  sendmsg(LED_ON, 1);
  pkt_read();

  /* param_request(ALL) */
  
#define L(x) lookup_tag_value(x)
#define END (-1)

  if(diag_mode) {
    sendmsg(PARAM_REQUEST, L(PARAM_ALL), END);
    pkt_read();

    /* param_send(beep: 0x0f, boot-event(f0 02)=1 */
    /* sendmsg(PARAM_SEND, 0xff, 0xf0, 0x02, 0, 0xf0, 0x00, 0, -1); */
    sendmsg(PARAM_SEND, 0xff, 
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_BOOTUP), 0,
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_DECODE), 0,
	    END);
    pkt_read();

    /* param_request(boot-event, decode-event) */
    sendmsg(PARAM_REQUEST, 
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_BOOTUP),
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_DECODE),
	    END);
    pkt_read();

    sendmsg(LED_ON, 1);
    pkt_read();

    sendmsg(REQUEST_REVISION);
    pkt_read();
    
    sendmsg(BEEP, 0x0e);
    pkt_read();

    /* 1 byte params */
    if(0) for (i=0; i< 0xf0; ) {
      sendmsg(PARAM_REQUEST, i+0, i+1, i+2, i+3, i+4, i+5, i+6, i+7, END);
      pkt_read();
      i+=8;
    }

    /* 2 byte params */
    sendmsg(PARAM_REQUEST, 
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_BOOTUP),
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_DECODE),
	    L(PARAM_EVENT_PREFIX), L(PARAM_EVENT_PARAM), 
	    END);
    pkt_read();
    

    /* param_send(beep: 0x16, trigger: host */
    sendmsg(PARAM_SEND, 0x16, L(PARAM_TRIGGER_MODE), 8, END);
    pkt_read();

    /* param_send(beep: 0x17, laser on max 9.9s */
    sendmsg(PARAM_SEND, 0xff, L(PARAM_LASER_TIME), 99, END);
    pkt_read();

    /* param_send(beep: none, xmit NR */
    sendmsg(PARAM_SEND, 0xff, L(PARAM_XMIT_NR), 1, END);
    pkt_read();

    /* param_send(send packeted decode data) */
    sendmsg(PARAM_SEND, 0xff, L(PARAM_DECODE_PACKET), 1, END);
    pkt_read();

    /* param_send(beep on good) */
    sendmsg(PARAM_SEND, 0xff, L(PARAM_BEEP_ON_GOOD), 1, END);
    pkt_read();

    /* param_send(beep freq high) */
    sendmsg(PARAM_SEND, 0xff, L(PARAM_BEEP_TONE), 0, END);
    pkt_read();

    /* param_request(packet mode, trigger mode, NR, beep-on-good, beep freq) */
    sendmsg(PARAM_REQUEST, 
	    L(PARAM_DECODE_PACKET), L(PARAM_TRIGGER_MODE), 
	    L(PARAM_XMIT_NR), L(PARAM_BEEP_ON_GOOD), L(PARAM_BEEP_TONE), 
	    END);
    pkt_read();
  } else {
    /* just set up the scan */
    /* param_send(beep: none, trigger: host */
    sendmsg(PARAM_SEND, 0xff, 
	    L(PARAM_TRIGGER_MODE), 8, /* let the host fire it */
	    L(PARAM_XMIT_NR), 1,      /* transmit NR for no scan */
	    L(PARAM_DECODE_PACKET), 1, /* send packet-form decoded data */
	    END);
    pkt_read();
  }


  sendmsg(SCAN_ENABLE);
  pkt_read();

  sendmsg(START_DECODE);
  pkt_read();
  /* stall for a decode */
  pkt_read();

  exit(0);
}

