
#ifndef ICOMS_H

/* 
 * Argyll Color Correction System
 *
 * An abstracted instrument serial and USB communication class.
 *
 * Author: Graeme W. Gill
 * Date:   2006/4/20
 *
 * Copyright 1996 - 2007 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 * 
 * Derived from serio.h
 */

/*

	Notes:	The user keyboard/interrupt handling is really broken.
	It's not noticed most of the time because mostly keys are only
	hit at the expected times. Serial instruments (X-Rite) are more
	forgiving in interrupting coms to/from the instrument, as well
	as being long winded and needing abort. For USB insruments it's
	not necessarily robust to interrupt or terminate after a give
	USB transaction. Really, the instrument driver should deterimine
	at what points an operation can be aborted, and how to recover.
	
*/

#ifdef ENABLE_USB
#include "usb.h"
#endif /* ENABLE_USB */

#if defined (UNIX) || defined(__APPLE__)
#include <pthread.h>
#endif

#undef QUIET_MEMCHECKERS		/* #define to memset coms read buffers before reading */

/* Serial related stuff */

/* Flow control */
typedef enum {
	fc_nc = 0,			/* not configured/default */
	fc_none,
	fc_XonXOff,
	fc_Hardware
} flow_control;

/* baud rate available on all systems */
typedef enum {
	baud_nc      = 0,		/* Not Configured */
	baud_110     = 1,
	baud_300     = 2,
	baud_600     = 3,
	baud_1200    = 4,
	baud_2400    = 5,
	baud_4800    = 6,
	baud_9600    = 7,
	baud_14400   = 8,
	baud_19200   = 9,
	baud_38400   = 10,
	baud_57600   = 11,
	baud_115200  = 12
} baud_rate;

/* Possible parity */
typedef enum {
	parity_nc,
	parity_none,
	parity_odd,
	parity_even
} parity;

/* Possible stop bits */
typedef enum {
	stop_nc,
	stop_1,
	stop_2
} stop_bits;

/* Possible word length */
typedef enum {
	length_nc,
	length_5,
	length_6,
	length_7,
	length_8
} word_length;

/* USB open/handling/buf workaround flags */
typedef enum {
	icomuf_none                = 0x0000,
	icomuf_detach              = 0x0001,	/* Attempt to detach from system driver */
	icomuf_no_open_clear       = 0x0002,	/* Don't send a clear_halt after opening the port */
	icomuf_reset_not_close     = 0x0004,	/* Don't release and close port, reset it */
	icomuf_resetep_before_read = 0x0008		/* Do a usb_resetep before each ep read */
} icomuflags;

/* Status bits/return values */
#define ICOM_OK	    0x00000		/* No error */

#define ICOM_USER 	0x10000		/* User abort operation */
#define ICOM_TERM 	0x20000		/* User terminated operation */
#define ICOM_TRIG 	0x30000		/* User trigger */
#define ICOM_CMND 	0x40000		/* User command */
#define ICOM_USERM 	0xF0000		/* Mask of user interrupts */

#define ICOM_NOTS 	0x01000		/* Not supported */
#define ICOM_TO		0x02000		/* Timed out */
#define ICOM_SHORT	0x04000		/* Number of bytes wasn't read/written */

#define ICOM_USBR	0x00100		/* Unspecified USB read error */
#define ICOM_USBW	0x00200		/* Unspecified USB write error */
#define ICOM_SERR	0x00400		/* Unspecified Serial read error */
#define ICOM_SERW	0x00800		/* Unspecified Serial write error */

#define ICOM_XRE	0x00040		/* Xmit shift reg empty */
#define ICOM_XHE	0x00020		/* Xmit hold reg empty */
#define ICOM_BRK	0x00010		/* Break detected */
#define ICOM_FER	0x00008		/* Framing error */
#define ICOM_PER	0x00004		/* Parity error */
#define ICOM_OER	0x00002		/* Overun error */
#define ICOM_DRY	0x00001		/* Recv data ready */

/* Store information about a possible instrument communication path */
typedef struct {
	char *path;					/* Serial port path, or USB device description */
#ifdef ENABLE_USB
	unsigned int vid, pid; 		/* USB vendor and product id's */
	struct usb_device *dev;		/* USB port, NULL if not USB */
	struct _hid_device *hev;	/* HID port, NULL if not HID */
	instType itype;				/* Type of instrument on the USB or HID port */
#endif /* ENABLE_USB */
} icompath;

#ifdef ENABLE_USB

/* Information about each end point */
typedef struct {
	int valid;			/* Flag, nz if this endpoint is valid */
	int addr;			/* Address of end point */
	int packetsize;		/* The max packet size */
	int type;			/* 2 = bulk, 3 = interrupt */	
} usb_ep;

#define ICOM_EP_TYPE_BULK 2
#define ICOM_EP_TYPE_INTERRUPT 3

#endif

#if defined (NT)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

struct _icoms {
  /* Private: */
	icompath *ppath;			/* path to selected port */
	
	int port;					/* io port number: 1, 2, 3, 4 .. n */
	int is_open;				/* Flag, NZ if this port is open */
	int is_usb;					/* Flag, NZ if this is a USB port */
	int is_hid;					/* Flag, NZ if this is an HID port */

	/* Serial port parameters */
#if defined (MSDOS)
	unsigned short porta;		/* Hardware port address */
#endif
#if defined (NT)
	HANDLE phandle;				/* NT handle */
#endif
#if defined (UNIX) || defined(__APPLE__)
	int fd;						/* Unix file descriptor */
#endif
	flow_control fc;
	baud_rate	br;
	parity		py;
	stop_bits	sb;
	word_length	wl;

#ifdef ENABLE_USB
	/* USB port parameters */
	struct usb_device *usbd;
	struct usb_dev_handle *usbh;
	int cnfg;				/* Configuration */
	int nifce;				/* Number of interfaces */
	int wr_ep, rd_ep;		/* Default end points to use for "serial" read/write */
	int rd_qa;				/* Read quanta size */

	usb_ep ep[32];			/* Information about each end point for general usb i/o */
	icomuflags uflags;		/* Bug workaround flags */

/* Macro to access end point information */
#define EPINFO(addr) ep[((addr >> 3) & 0x10) + (addr & 0x0f)]

	/* HID port parameters */
	struct _hid_device *hidd;	/* HID port, NULL if not HID */

#endif /* ENABLE_USB */

	/* General parameters */
	int lerr;					/* Last error code */
	int tc;						/* Current serial parser termination character (-1 if not set) */
	int npaths;
	icompath **paths;			/* Paths if any */
	int debug;					/* Flag, nz to print instrument io info to stderr */

	int uih[256];				/* User interrupt handling key table. Value can be: */
								/* ICOM_OK, ICOM_USER, ICOM_TERM, ICOM_TRIG, ICOM_CMND */
	int cut;					/* The character that caused the termination */

	/* Linked list to automate SIGKILL cleanup */
	struct _icoms *next;
	
  /* Public: */

	/* Return a list of instrument io communication paths */
	/* Return NULL on failure. */
	icompath ** (*get_paths)(		/* Return pointer to list of icompath */
		struct _icoms *p 			/* End is marked with NULL pointer, */
	);								/* Storage will be freed with icoms object */

	/* Return the instrument type if the port number is USB, instUnknown if not */
	instType (*is_usb_portno)(
		struct _icoms *p, 
		int 	 	 port);		/* Enumerated port number, 1..n */

	/* Return the instrument type if the port number is HID, */
	/* and instUnknown if it is not. */
	instType (*is_hid_portno)(
		struct _icoms *p, 
		int port);		/* Enumerated port number, 1..n */

	/* Select the serial communications port and characteristics */
	void (*set_ser_port)(
		struct _icoms *p, 
		int 	 	 port,		/* Enumerated port number, 1..n */
		flow_control fc,		/* Flow control */
		baud_rate	 baud,
		parity		 parity,
		stop_bits	 stop_bits,
		word_length	 word_length);

	/* Select the USB communications port and characteristics */
	void (*set_usb_port)(
		struct _icoms *p, 
		int            port,		/* Enumerated port number, 1..n */
	    int            config,		/* Configuration */
	    int            wr_ep,		/* "serial" write end point */
	    int            rd_ep,		/* "serial" read end point */
		icomuflags usbflags);		/* Any special handling flags */

	/* Select the HID communications port and characteristics */
	void (*set_hid_port)(
		struct _icoms *p, 
		int            port,		/* Enumerated port number, 1..n */
		icomuflags usbflags);		/* Any special handling flags */

	/* Reset user interrupt handling to default (Esc, ^C, q or 'Q' = Abort) */
	void (*reset_uih)(struct _icoms *p);

	/* Set a key range to the given handling type */
	/* min & max are between 0 and 255, status is one of */
	/* ICOM_OK, ICOM_USER, ICOM_TERM, ICOM_TRIG, ICOM_CMND */
	void (*set_uih)(struct _icoms *p, int min, int max, int status);

	/* Get the character that caused the user interrupt */
	/* Clear it to 0x00 after reading it. */
	int (*get_uih_char)(struct _icoms *p);

	/* "Serial" write the characters in the buffer out */
	/* Data will be written up to the terminating nul. */
	/* Return error code & set error state. */
	int (*write)(
		struct _icoms *p,
		char *buf,
		double tout);		/* Timeout in seconds */

	/* "Serial" read characters into the buffer */
	/* Return error code & set error state. */
	/* The returned data will be terminated by a nul. */
	int (*read)(
		struct _icoms *p,
		char *buf,			/* Buffer to store characters read */
		int bsize,			/* Buffer size */
		char tc,			/* Terminating character */
		int ntc,			/* Number of terminating characters */
		double tout);		/* Timeout in seconds */

	/* "Serial" write and read */
	/* Return error code & set error state. */
	int (*write_read)(
		struct _icoms *p,
		char *wbuf,			/* Write puffer */
		char *rbuf,			/* Read buffer */
		int bsize,			/* Buffer size */
		char tc,			/* Terminating characer */
		int ntc,			/* Number of terminating characters */
		double tout);		/* Timeout in seconds */

	/* For a USB device, do a control message */
	/* Return error code (don't set error state). */
	int (*usb_control_th)(struct _icoms *p,
		int requesttype,		/* 8 bit request type (USB bmRequestType) */
		int request,			/* 8 bit request code (USB bRequest) */
		int value,				/* 16 bit value (USB wValue, sent little endian) */
		int index,				/* 16 bit index (USB wIndex, sent little endian) */
		unsigned char *rwbuf,	/* Read or write buffer */
		int rwsize,				/* Bytes to read or write */
		double tout,			/* Timeout in seconds */
		int debug,				/* debug flag value */
		int *cut,				/* Character that caused termination */
		int checkabort);		/* check for user abort */

	/* Same as above, but set error state */
	int (*usb_control)(struct _icoms *p,
		int requesttype,		/* 8 bit request type (USB bmRequestType) */
		int request,			/* 8 bit request code (USB bRequest) */
		int value,				/* 16 bit value (USB wValue, sent little endian) */
		int index,				/* 16 bit index (USB wIndex, sent little endian) */
		unsigned char *rwbuf,	/* Read or write buffer */
		int rwsize,				/* Bytes to read or write */
		double tout);			/* Timeout in seconds */

	/* For a USB device, do a bulk or interrupt read from an end point */
	/* Return error code (don't set error state). */
	int (*usb_read_th)(struct _icoms *p,
		int ep,					/* End point address */
		unsigned char *buf,		/* Read buffer */
		int bsize,				/* Bytes to read or write */
		int *bread,				/* Bytes read */
		double tout,			/* Timeout in seconds */
		int debug,				/* debug flag value */
		int *cut,				/* Character that caused termination */
		int checkabort);		/* check for user abort */

	/* Same as above, but set error state */
	int (*usb_read)(struct _icoms *p,
		int ep,					/* End point address */
		unsigned char *buf,		/* Read buffer */
		int bsize,				/* Bytes to read or write */
		int *bread,				/* Bytes read */
		double tout);			/* Timeout in seconds */

	/* For a USB device, do a bulk or interrupt write to an end point */
	/* Return error code (don't set error state). */
	int (*usb_write_th)(struct _icoms *p,
		int ep,					/* End point address */
		unsigned char *wbuf,	/* Write buffer */
		int wsize,				/* Bytes to or write */
		int *bwritten,			/* Bytes written */
		double tout,			/* Timeout in seconds */
		int debug,				/* debug flag value */
		int *cut,				/* Character that caused termination */
		int checkabort);		/* check for user abort */

	/* Same as above, but set error state */
	int (*usb_write)(struct _icoms *p,
		int ep,					/* End point address */
		unsigned char *wbuf,	/* Write buffer */
		int wsize,				/* Bytes to or write */
		int *bwritten,			/* Bytes written */
		double tout);			/* Timeout in seconds */

	/* Reset and end point toggle state to 0 */
	int (*usb_resetep)(struct _icoms *p,
		int ep);				/* End point address */

	/* Clear an end point halt */
	int (*usb_clearhalt)(struct _icoms *p,
		int ep);				/* End point address */

	/* For an HID device, read a message from the device. */
	/* Set error state on error */
	int (*hid_read)(struct _icoms *p,
		unsigned char *buf,		/* Read buffer */
		int bsize,				/* Bytes to read or write */
		int *bread,				/* Bytes read */
		double tout);			/* Timeout in seconds */

	/* For an HID device, write a message to the device */
	/* Set error state on error */
	int (*hid_write)(struct _icoms *p,
		unsigned char *wbuf,	/* Write buffer */
		int wsize,				/* Bytes to or write */
		int *bwritten,			/* Bytes written */
		double tout);			/* Timeout in seconds */

	/* Destroy ourselves */
	void (*del)(struct _icoms *p);

}; typedef struct _icoms icoms;

/* Constructor */
extern icoms *new_icoms(void);

/* - - - - - - - - */
/* Other functions */

/* Create and return a list of available serial ports or USB instruments for this system. */
/* We look at the registry key "HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM" */
/* to determine serial ports, and use libusb to discover USB instruments. */
/* Return NULL if function fails */
icompath **icoms_get_paths(icoms *p); 

/* - - - - - - - - - - - - - - - - - - -- */
/* System dependent convenience functions */
/* Should probably move these to somewhere more accessible.. */

/* wait for and then return the next character from the keyboard */
int next_con_char(void);

/* If there is one, return the next character from the keyboard, else return 0 */
int poll_con_char(void);

/* Poll for a user abort, terminate, trigger or command. */
/* Wait for a key rather than polling, if wait != 0 */
/* Return: */
/* ICOM_OK if no key has been hit, */
/* ICOM_USER if User abort has been hit, */
/* ICOM_TERM if User terminate has been hit. */
/* ICOM_TRIG if User trigger has been hit */
/* ICOM_CMND if User command has been hit */
int icoms_poll_user(icoms *p, int wait);

/* Empty the console of any pending characters */
void empty_con_chars(void);

/* Sleep for the given number of msec */
void msec_sleep(unsigned int msec);

/* Return the current time in msec since */
/* the process started. */
unsigned int msec_time();

/* Activate the system beeper after a delay */
/* (Note frequancy and duration may not be honoured on all systems) */
void msec_beep(int delay, int freq, int msec);

void normal_beep(); /* Emit a "normal" beep */
void good_beep(); /* Emit a "good" beep */
void bad_beep(); /* Emit a "bad" double beep */

/* - - - - - - - - - - - - - - - - - - -- */

#ifdef NEVER	/* Not currently needed, or effective */

/* Set the current threads priority */
/* return nz if this fails */
int set_interactive_priority();

int set_normal_priority();

#endif /* NEVER */

/* - - - - - - - - - - - - - - - - - - -- */

/* An Argyll thread. */
struct _athread {
#if defined (NT)
	HANDLE th;				/* Thread */
	DWORD thid;				/* Thread ID */
#endif
#if defined (UNIX) || defined(__APPLE__)
	pthread_t thid;			/* Thread ID */
#endif
	int result;				/* Return code from thread function */

	/* Thread function to call */
	int (*function)(void *context);

	/* And the context to call it with */
	void *context;

    /* Kill the thread and delete the object */
    void (*del)(struct _athread *p);

}; typedef struct _athread athread;

/* Create and start a thread */
/* Thread function should only return on completion or error. */
/* It should return 0 on completion or exit, nz on error. */
athread *new_athread(int (*function)(void *context), void *context);

/* Delete a file */
void delete_file(char *fname);

/* - - - - - - - - - - - - - - - - - - -- */
/* Utilities */

/* Convert control chars to ^[A-Z] notation in a string */
char *icoms_fix(unsigned char *s);

/* Convert a limited binary buffer to a list of hex */
char *icoms_tohex(unsigned char *s, int len);

#define ICOMS_H
#endif /* ICOMS_H */
