/****************************************************/
/* TransHex (Windows/Linux)                         */
/* Version 1.3                                      */
/****************************************************/
/* Author:		Michael Kapp                        */
/* History:		02.07.2001 Linux version            */
/*				12.10.2001 Windows version added    */
/*				04.06.2003 Upload for Windows added */
/****************************************************/

#ifdef _WIN32

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>

#define SYSTEM_NAME "Windows"
#define DEFAULT_DEVICE "COM1"
#define RTS 1
#define DTR 2

static void configure_terminal(void) {}

#else

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <string.h>

#define SYSTEM_NAME "Linux"
#define DEFAULT_DEVICE "/dev/ttyS0"
#define RTS TIOCM_RTS
#define DTR TIOCM_DTR
#define HANDLE int

static struct termios termios1;
static char ch1 = 0;

/* Restore terminal configuration.  Called from exit()
 * automatically.  */
static void restore_terminal(void)
{
  tcsetattr(fileno(stdin), TCSANOW, &termios1);
}

/* Configure terminal for non-blocking input. */
static void configure_terminal(void)
{
  struct termios termios2;
  tcgetattr(fileno(stdin), &termios1);
  memcpy(&termios2, &termios1, sizeof(struct termios));
  termios2.c_lflag &= ~(ICANON | ECHO);
  termios2.c_cc[VMIN] = 0;
  termios2.c_cc[VTIME] = 0;
  tcsetattr(fileno(stdin), TCSANOW, &termios2);
  atexit(restore_terminal);
}

/* Poll keyboard.  Return non-zero if a key has been pressed, or zero
 * otherwise.  */
static int kbhit(void)
{
  return read(fileno(stdin), &ch1, sizeof(char));
}

/* Read keyboard.  Return ASCII character for key that was
 * pressed.  */
static int getch(void)
{
  int ch2;

  /* If necessary, wait for a key to be pressed. */
  if(ch1 == 0)
    while(!kbhit()) {}

  /* Return the most recent key read by kbhit(). */
  ch2 = ch1;
  ch1 = 0;
  return ch2;
}

#endif

/* Verzoegerung (< 1000 ms!) */

void Delay(int ms)
{
#ifdef _WIN32
	Sleep(ms);
#else
	usleep(ms*1000);
#endif
}


/* Serielle Schnittstelle oeffnen */

HANDLE OpenSerial(const char *device)
{
	HANDLE h;
	int error = 0;

#ifdef _WIN32
	h = CreateFile(device,                // pointer to name of the file
                   GENERIC_READ|GENERIC_WRITE, // access mode
	               0,                     // comm devices must be opened w/exclusive-access
	               NULL,                  // no security attributs
	               OPEN_EXISTING,         // comm devices must use OPEN_EXISTING
	               0,                     // flags
	               NULL);                 // hTemplate must be NULL for comm devices
	if (h==INVALID_HANDLE_VALUE) error = 1;
#else
	h = open(device, O_RDWR | O_NOCTTY);
	if (h<0) error = 1;
#endif

	if (error)
	{
		fprintf(stderr, "Could not open device %s\n", device);
		exit(1);
	}
	return h;
}


/* Serielle Schnittstelle schliessen */

void CloseSerial(HANDLE h)
{
#ifdef _WIN32
	CloseHandle(h);
#else
	close(h);
#endif
}


/* Serielle Schnittstelle konfigurieren */

void SetSerial(HANDLE h, long baud, int handshake)
{
	int error = 0;

#ifdef _WIN32
	DCB dcb;

	if (baud!=2400 && baud!=9600 && baud!=19200 && baud!=38400 && baud!=57600 && baud!=115200)
	{
		fprintf(stderr, "Illegal baud rate (%ld)\n", baud);
		exit(1);
	}

	GetCommState(h, &dcb);
	dcb.BaudRate = baud;
	dcb.ByteSize = 8;
	dcb.Parity   = NOPARITY;
	dcb.StopBits = ONESTOPBIT;
	dcb.fOutxCtsFlow = handshake? TRUE : FALSE;
	dcb.fOutxDsrFlow = FALSE;
	dcb.fDtrControl = DTR_CONTROL_ENABLE;		// IRmate benötigt ENABLE
	dcb.fDsrSensitivity = FALSE;
	dcb.fOutX = FALSE;
	dcb.fInX = FALSE;
	dcb.fNull = FALSE;
	dcb.fRtsControl = RTS_CONTROL_ENABLE;		// IRmate benötigt ENABLE
	dcb.fAbortOnError = FALSE;
	//dc.b.EofChar = ??
	if (!SetCommState(h, &dcb)) error = 1;

#else
	int baudcode;
	struct termios tio;

	/* aktuelle Einstellungen lesen */
	tcgetattr(h, &tio);

	switch (baud)
	{
		case   2400: baudcode = B2400; break;
		case   9600: baudcode = B9600; break;
		case  19200: baudcode = B19200; break;
		case  38400: baudcode = B38400; break;
		case  57600: baudcode = B57600; break;
		case 115200: baudcode = B115200; break;
		default:
			fprintf(stderr, "Illegal baud rate (%ld)\n", baud);
			exit(1);
	}
	/* Baudrate, 8N1, Steuerleitungen ignorieren, Empfang ermoeglichen, Flusskontrolle */
	tio.c_cflag = baudcode | CS8 | CLOCAL | CREAD;
	if (handshake) tio.c_cflag |= CRTSCTS;

	/* Frame/Parity-Fehler ignorieren */
	tio.c_iflag = IGNPAR;

	/* Raw output */
	tio.c_oflag = 0;

	/* Kein Echo */
	tio.c_lflag = 0;

	/* Control chars */
	tio.c_cc[VINTR]    = 0;     /* Ctrl-c */
	tio.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
	tio.c_cc[VERASE]   = 0;     /* del */
	tio.c_cc[VKILL]    = 0;     /* @ */
	tio.c_cc[VEOF]     = 0;     /* Ctrl-d */
	tio.c_cc[VSWTC]    = 0;     /* '\0' */
	tio.c_cc[VSTART]   = 0;     /* Ctrl-q */
	tio.c_cc[VSTOP]    = 0;     /* Ctrl-s */
	tio.c_cc[VSUSP]    = 0;     /* Ctrl-z */
	tio.c_cc[VEOL]     = 0;     /* '\0' */
	tio.c_cc[VREPRINT] = 0;     /* Ctrl-r */
	tio.c_cc[VDISCARD] = 0;     /* Ctrl-u */
	tio.c_cc[VWERASE]  = 0;     /* Ctrl-w */
	tio.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
	tio.c_cc[VEOL2]    = 0;     /* '\0' */
	/* Use non-blocking input.  This is necessary to allow the
	 * keyboard to be polled while uploading. */
	tio.c_cc[VMIN] = 0;
	tio.c_cc[VTIME] = 0;

	/* Einstellungen setzen */
	if (tcsetattr(h, TCSADRAIN, &tio) < 0) error = 1;
#endif

	if (error)
	{
		fprintf(stderr, "Serial initialization failed\n");
		exit(1);
	}
}

/* Timeout für den blockierenden Empfang von Daten setzen */
void SetTimeout(HANDLE h, int seconds)
{
#ifdef _WIN32
	COMMTIMEOUTS Timeouts;
	char res;
		
	res = GetCommTimeouts(h, &Timeouts);
	if(res)
	{
		Timeouts.ReadTotalTimeoutConstant = 1000*seconds;
		res = SetCommTimeouts(h, &Timeouts);
		if(res)
			return;
	}
	fprintf(stderr, "Serial timeout could not be set\n");
	exit(1);
#endif
}


/* Byte ueber serielle Schnittstelle ausgeben */

void WriteSerial(HANDLE h, unsigned char c)
{
	int error = 0;

#ifdef _WIN32
	DWORD written;
	WriteFile(h, &c, 1, &written, NULL);
	if (written != 1) error = 1;
#else
	if (write(h, &c, 1) != 1) error = 1;
#endif

	if (error)
	{
		fprintf(stderr, "Serial write failed\n");
		exit(1);
	}
}


/* Byte ueber serielle Schnittstelle einlesen */
/* Return 1, wenn 1 Zeichen empgangen wurde, 0 wenn ein Timeour auftrat (KEIN FEHLER!!) */

int ReadSerial(HANDLE h, unsigned char *c)
{
	int error = 0;

#ifdef _WIN32
	DWORD read;
	if(!ReadFile(h, c, 1, &read, NULL))
		error = 1;
	else if(read == 1) 
		return 1;
#else
	if (read(h, c, 1) == 1) return 1;
	// Todo errordetection
	// if(errno != 0) error = 1;
#endif

	if (error)
	{
		fprintf(stderr, "Serial read failed\n");
		exit(1);
	}
	return 0;
}



/* RTS/DTR setzen */

void SetLines(HANDLE h, int rtsdtr)
{
	int error = 0;

#ifdef _WIN32
	if (rtsdtr & RTS) if (!EscapeCommFunction(h, SETRTS)) error = 1;
	if (rtsdtr & DTR) if (!EscapeCommFunction(h, SETDTR)) error = 1;
#else
	if (ioctl(h, TIOCMBIS, &rtsdtr) < 0) error = 1;
#endif

	if (error)
	{
		fprintf(stderr, "Could not access RTS/DTR lines\n");
		exit(1);
	}
}


/* RTS/DTR loeschen */

void ClrLines(HANDLE h, int rtsdtr)
{
	int error = 0;

#ifdef _WIN32
	if (rtsdtr & RTS) if (!EscapeCommFunction(h, CLRRTS)) error = 1;
	if (rtsdtr & DTR) if (!EscapeCommFunction(h, CLRDTR)) error = 1;
#else
	if (ioctl(h, TIOCMBIC, &rtsdtr) < 0) error = 1;
#endif

	if (error)
	{
		fprintf(stderr, "Could not access RTS/DTR lines\n");
		exit(1);
	}
}


/* IRmate zuruecksetzen */

void ResetIRmate(HANDLE h)
{
	SetLines(h, RTS | DTR);
	Delay(100);
	ClrLines(h, DTR);
	Delay(2);
	SetLines(h, DTR);
	Delay(2);
}


/* IRmate-Baudrate setzen */

void SetIRmateSpeed(HANDLE h, long baud)
{
	unsigned char ctrlbyte;

	switch (baud)
	{
		case   2400: ctrlbyte = 8; break;
		case   9600: ctrlbyte = 4; break;
		case  19200: ctrlbyte = 3; break;
		case  38400: ctrlbyte = 2; break;
		case  57600: ctrlbyte = 1; break;
		case 115200: ctrlbyte = 0; break;
		default:
			fprintf(stderr, "Illegal baud rate (%ld)\n", baud);
			exit(1);
	}
	/* ctrlbyte |= 16  fuer kurze Pulse */

	ClrLines(h, RTS);
	Delay(2);
	WriteSerial(h, ctrlbyte);
	Delay(100);
	SetLines(h, RTS);
}


/* Hauptprogramm */

int main(int argc, char *argv[])
{
	char *DeviceName = DEFAULT_DEVICE;
	long BaudRate = 115200;
	int NoHandshake = 0;
	int IRmate = 0;
	char *FileName = NULL;
	int ReceiveMode = 0;

	HANDLE h;
	FILE *hexfile;
	long size;
	long count;
	int percent, old_percent;
	int i;
	unsigned char c;

	configure_terminal();

	/* Parameter parsen */
	i = 1;
	while (i<argc)
	{
		if (strcmp(argv[i], "-c")==0)
		{
			i++;
			if (i<argc)
				DeviceName = argv[i];
			else
			{
				fprintf(stderr, "Device name missing\n");
				exit(1);
			}
		}
		else if (strcmp(argv[i], "-b")==0)
		{
			i++;
			if (i<argc)
				BaudRate = atol(argv[i]);
			else
			{
				fprintf(stderr, "Baud rate missing\n");
				exit(1);
			}
		}
		else if (strcmp(argv[i], "-r")==0)
		{
			ReceiveMode = 1;
			i++;
			if (i<argc)
			{
				if(!strcmp(argv[i],"NULL"))
					ReceiveMode = 2;
				else
					i--;
			}
		}
		else if (strcmp(argv[i], "-nohandshake")==0)
		{
			NoHandshake = 1;
		}
		else if (strcmp(argv[i], "-irmate")==0)
		{
			IRmate = 1;
			NoHandshake = 1;
		}
		else
		{
			if (argv[i][0]=='-')
			{
				fprintf(stderr, "Unknown option: %s\n", argv[i]);
				exit(1);
			}
			FileName = argv[i];
		}

		i++;
	}

	if (FileName==NULL)
	{
		fprintf(stderr, "TransHex 1.3 for " SYSTEM_NAME "\n"
			"(c) 2001 Michael Kapp, Michael Kasper, Klaus Schmitt\n"
			"Usage: %s [OPTIONS] <FILE>\n\n"
			"Available OPTIONS:\n"
			"-c <DEVICE>    Serial device to use\n"
			"-b <BAUD>      Transmission speed. Must be one of the following values:\n"
			"               2400, 9600, 19200, 38400, 57600, 115200\n"
			"-r [MODE]      Receive file. The reception will allways stop upon key press\n"
			"               MODE='NULL' stops receiving upon reception of an 0x0-character\n"
			"-nohandshake   Don't use RTS/CTS handshaking\n"
			"-irmate        Enable transmission via IRmate (includes -nohandshake)\n\n"
			"Default values: -c " DEFAULT_DEVICE " -b 115200\n",
			argv[0]);
		exit(1);
	}


	/* Schnittstelle initialisieren */
	h = OpenSerial(DeviceName);

	if (IRmate)
	{
		printf("Initializing IRmate...\n");
		SetSerial(h, 9600, 0);
		ResetIRmate(h);
		SetIRmateSpeed(h, BaudRate);
	}
	SetSerial(h, BaudRate, !NoHandshake);


	// Soll eine Datei gesendet oder empfangen werden?
	if(ReceiveMode)
	{
		// Damit nicht unendlich gewartet wird bis Daten kommen (Lesen ist blockierend!!) kehre immer wieder zurück
		SetTimeout(h,1);
		
		/* File empfangen */
		hexfile = fopen(FileName, "wb");
		if (hexfile==NULL)
		{
			fprintf(stderr, "Could not open file %s\n", FileName);
			exit(1);
		}
		errno = 0;
		count = 0;

		setbuf(stdout, NULL);	/* ungepufferte Ausgabe fuer Fortschrittsanzeige */

		printf("Press key to stop upload.\n");
		printf("Starting upload: 0 Bytes\r");
		while(!kbhit())
		{
			if(ReadSerial(h, &c))
			{
				if((ReceiveMode == 2) && (c==0))
				{
					printf("\nEOF detected, closing file...\n");
					break;
				}
				fputc(c, hexfile);
				count++;
				printf("Starting upload: %ld Bytes\r", count);
			}
		}
		if(kbhit())
			getch();
		printf("\n");
		fclose(hexfile);
	}
	// Datei versenden
	else
	{
		/* File uebertragen */
		hexfile = fopen(FileName, "rb");
		if (hexfile==NULL)
		{
			fprintf(stderr, "Could not open file %s\n", FileName);
			exit(1);
		}
		errno = 0;
		fseek(hexfile, 0, SEEK_END);
		size = ftell(hexfile);
		fseek(hexfile, 0, SEEK_SET);
		if (errno!=0)
		{
			fprintf(stderr, "Could not determine size of file %s\n", FileName);
			exit(1);
		}
		old_percent = -1;
		count = 0;

		setbuf(stdout, NULL);	/* ungepufferte Ausgabe fuer Fortschrittsanzeige */

		while ((i=fgetc(hexfile))!=EOF)
		{
			c = i;
			WriteSerial(h, c);
			count++;
			percent = count*100/size;
			if (percent!=old_percent)
			{
				printf("Starting download: %ld%%\r", (long int)percent);
				old_percent = percent;
			}
		}
		printf("\n");

		fclose(hexfile);
	}

	CloseSerial(h);
	return 0;
}
