/* com.cpp
 * Classe per gestione seriale di basso livello
 * compatibili Linux/Win
 */
/*
 * Seriale portabile:
 * Articolo http://www.sics.se/~mn/datamekatronik/datamekatronik2005/lab2/serialport_20050216.c

The system calls used are
Linux:                          Windows:

open                            CreateFile               
tcgetattr                       GetCommState             
tcsetattr                       SetCommState             
ioctl (TIOCMGET, TIOCMSET)      EscapeCommFunction       
ioctl (TIOCMGET)                GetCommModemStatus       
close                           CloseHandle

 */
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>

#include "platform.h"	// Define the "__WXMSW__" symbol on non wxWidgets
#include "exception.h"

#ifdef __WXMSW__
/* Dev C++ che usa MingWin sotto Windows */
#include <conio.h>
#else
/* Linux */
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#endif

#include "com.h"

#ifndef __WXMSW__
struct termios Com::saved_attributes;	// Console mode at entry
#endif

/* ************************************************************ */
/* Funzioni di servizio - geastione seriale                     */
/* ************************************************************ */
Com::Com (Printer *p)
{
#ifdef __WXMSW__
	com = INVALID_HANDLE_VALUE;
#else
	com = -1;
#endif
	prt = p;
}

/* Apre la seriale indicata dal parametro e la imposta in modo non canonico
 */
void Com::setupCom (const char *comname)
{
#ifdef __WXMSW__
/* Dev C++ che usa MingWin sotto Windows */
DCB dcb;
COMMTIMEOUTS comTimeOut;                   
    com = CreateFile(comname, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
    if (com == INVALID_HANDLE_VALUE) {
    	throw (new Exception (errno, "Couldn't open serial device!"));
    }
    // Get current communication state
    GetCommState(com, &dcb);
    dcb.BaudRate = 9600;          // Specify buad rate of communicaiton.
    dcb.StopBits = 0;             // 0,1,2 = 1, 1.5, 2 (default = 0) Specify stopbit of communication.
    dcb.Parity = 0;               // 0-4= no, odd, even, mark, space (default = 0) Specify parity of communication.
    dcb.ByteSize = 8;             // Specify  byte of size of communication.
    // Turn off hardware handshake
    dcb.fDtrControl = DTR_CONTROL_ENABLE;
    dcb.fRtsControl = RTS_CONTROL_ENABLE;
    // Set communication state
    SetCommState(com, &dcb);
#else
struct termios tattr;
int fattrs;

    // com = open (comname,O_RDWR | O_SYNC);
    com = open (comname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (com == -1)
    {
    	throw (new Exception (errno, "Couldn't open serial device!"));
    }
    fattrs = fcntl (com, F_GETFL);
//    fattrs |= O_SYNC;
    fattrs &= ~O_NONBLOCK;
    fcntl (com, F_SETFL, fattrs);
    /* Set the funny terminal modes. */
    tcgetattr (com, &tattr);
    cfmakeraw (&tattr);
    cfsetispeed (&tattr, B9600);
    cfsetospeed (&tattr, B9600);
    tattr.c_cc[VMIN] = 1;
    tattr.c_cc[VTIME] = 5;

    // Tratto da strace di minicom
    // ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {c_iflags=0x1, c_oflags=0, c_cflags=0x8bd, c_lflags=0, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=5, c_cc="\x03\x1c\x7f\x15\x01\x05\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
    // ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {c_iflags=0x1, c_oflags=0, c_cflags=0xcbd, c_lflags=0, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=5, c_cc="\x03\x1c\x7f\x15\x01\x05\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
    tattr.c_cflag=0xcbd;
  
    tattr.c_iflag |= IGNBRK;
//    tcsetattr (com, TCSAFLUSH, &tattr);
    tcsetattr (com, TCSANOW, &tattr);
#endif
}

void Com::closeCom ()
{
//  dtrOff ();    // Toglie l'alimentazione
//  rtsOff ();    // Spegne il segnale di Reset
#ifdef __WXMSW__
	CloseHandle (com);
	com = INVALID_HANDLE_VALUE;
#else
	close (com);
	com = -1;
#endif
}

int Com::isConnected ()
{
#ifdef __WXMSW__
    return com != INVALID_HANDLE_VALUE;
#else
	return com != -1;
#endif
}

     
#ifndef __WXMSW__
/* Use this variable to remember original terminal attributes. */

void Com::reset_input_mode (void)
{
  printf ("\nProgramma terminato\n");
  tcsetattr (fileno (stdin), TCSANOW, &saved_attributes);
}

void Com::set_input_mode (void)
{
struct termios tattr;
     
  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (fileno (stdin), &saved_attributes);
  atexit (reset_input_mode);
    
  /* Set the funny terminal modes. */
  tcgetattr (fileno (stdin), &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
      
  /* Set the funny terminal modes. */
  tcgetattr (fileno (stdin), &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (fileno (stdin), TCSAFLUSH, &tattr);
  fflush (stdin);
}

#endif


void Com::term ()
{
#ifdef __WXMSW__
int c;
COMMTIMEOUTS comTimeOut;
DWORD l;
  // Svuota immediatamente tutti i caratteri gia' letti
  comTimeOut.ReadIntervalTimeout = MAXDWORD;      // Specify time-out between charactor for receiving.
  comTimeOut.ReadTotalTimeoutMultiplier = 0;      // Always single byte read
  comTimeOut.ReadTotalTimeoutConstant = 0;        // Always single byte reads
  comTimeOut.WriteTotalTimeoutMultiplier = 0;     // No write timeout
  comTimeOut.WriteTotalTimeoutConstant = 0;       // No write timeout
  SetCommTimeouts(com,&comTimeOut);
  prt->printf ("Modalita' terminale: Per uscire premere ^C (Control-C)\n");
  while (1) {
    if (kbhit ()) { // Tasto dalla tastiera
      c = getch ();
      if (c == 3) { // Control C
        prt->printf ("\nProgramma terminato\n");
        break;
      }
      comPut (c);
    }
    ReadFile(com,   // handle of file to read
      &c,        // handle of file to read
      1,           // number of bytes to read
      &l,           // pointer to number of bytes read
      NULL);        // pointer to structure for data
    if (l) {
      putchar (c);
    }
  }
#else
fd_set set;
unsigned char c;

  prt->printf ("Modalita' terminale: Per uscire premere ^C (Control-C)\n");
  set_input_mode ();
  while (1) {
    /* Initialize the file descriptor set. */
    FD_ZERO (&set);
    FD_SET (com, &set);
    FD_SET (fileno (stdin), &set);
    setvbuf (stdout, NULL, _IONBF, 0);
     
    /* `select' returns 0 if timeout, 1 if input available, -1 if error. */
    if (!select (FD_SETSIZE, &set, NULL, NULL, NULL))
    {
      reset_input_mode ();
      return;
    }
    if (FD_ISSET (fileno (stdin), &set)) {
      read (fileno (stdin),&c,1);
      write (com, &c, 1);
    }
    if (FD_ISSET (com, &set)) {
      read (com,&c,1);
      putchar (c);
    }
  }
#endif
}

/* Svuota le code di ingresso/uscita della seriale */
void Com::flushInput (void)
{
#ifdef __WXMSW__
/* Dev C++ che usa MingWin sotto Windows */
COMMTIMEOUTS comTimeOut;
DWORD l;
unsigned char flbuf [20];
  // Svuota immediatamente tutti i caratteri gia' letti
  comTimeOut.ReadIntervalTimeout = MAXDWORD;      // Specify time-out between charactor for receiving.
  comTimeOut.ReadTotalTimeoutMultiplier = 0;      // Always single byte read
  comTimeOut.ReadTotalTimeoutConstant = 0;        // Always single byte reads
  comTimeOut.WriteTotalTimeoutMultiplier = 0;     // No write timeout
  comTimeOut.WriteTotalTimeoutConstant = 0;       // No write timeout
  SetCommTimeouts(com,&comTimeOut);
  ReadFile(com,   // handle of file to read
    flbuf,        // handle of file to read
    20,           // number of bytes to read
    &l,           // pointer to number of bytes read
    NULL);        // pointer to structure for data
#else
  tcflush (com, TCIOFLUSH);
#endif
}

/*
 *
 *        #include <termios.h>
 *
 *       int ioctl(int fd, int cmd, ...);
 *
 *   Modem control
 *       TIOCMGET  int *argp
 *              get the status of modem bits.
 *
 *       TIOCMSET  const int *argp
 *              set the status of modem bits.
 *
 *       TIOCMBIC  const int *argp
 *              clear the indicated modem bits.
 *
 *       TIOCMBIS  const int *argp
 *              set the indicated modem bits.
 *
 *       Bits used by these four ioctls:
 *
 *      Pin   Circuit   Description                 Direction
 *      --------------------------------------------------------------
 *       1     CF       Data carrier detect, DCD    from DCE
 *       2     BB       Receive data, RD            from DCE
 *       3     BA       Transmit data, TD           to DCE
 *       4     CD       Data Terminal ready, DTR    to DCE
 *       5     AB       Signal ground, SG           n/a
 *       6     CC       Data set ready, DSR         from DCE
 *       7     CA       Request to send, RTS        to DCE
 *       8     CB       Clear to send, CTS          from DCE
 *       9     CE       Ring indicator, RI          from DCE
 *
 */

void Com::dtrOn ()           // Set pin 4, DTR
{
#ifdef __WXMSW__
  EscapeCommFunction(com, SETDTR);
#else
int i;
  if (ioctl (com, TIOCMGET, &i)) {  // Leggo stato bit
    perror("DTR On - lettura flag");
  }
	i |= TIOCM_DTR;
  if (ioctl (com, TIOCMSET, &i)) {  // Imposto bit
    perror("DTR On - impostazione flag");
  }
#endif
}

void Com::dtrOff ()          // Clear pin 4, DTR
{
#ifdef __WXMSW__
  EscapeCommFunction(com, CLRDTR);
#else
int i;
  if (ioctl (com, TIOCMGET, &i)) {  // Leggo stato bit
    perror("DTR Off - lettura flag");
  }
	i &= ~TIOCM_DTR;
  if (ioctl (com, TIOCMSET, &i)) {  // Imposto bit
    perror("DTR Off - impostazione flag");
  }
#endif
}

void Com::rtsOn ()           // Set pin 7, RTS
{
#ifdef __WXMSW__
  EscapeCommFunction(com, SETRTS);
#else
int i;
  if (ioctl (com, TIOCMGET, &i)) {  // Leggo stato bit
    perror("RTS On - lettura flag");
  }
	i |= TIOCM_RTS;
  if (ioctl (com, TIOCMSET, &i)) {  // Imposto bit
    perror("RTS On - impostazione flag");
  }
#endif
}

void Com::rtsOff ()          // Set pin 7, RTS
{
#ifdef __WXMSW__
  EscapeCommFunction(com, CLRRTS);
#else
int i;
  if (ioctl (com, TIOCMGET, &i)) {  // Leggo stato bit
    perror("RTS Off - lettura flag");
  }
	i &= ~TIOCM_RTS;
  if (ioctl (com, TIOCMSET, &i)) {  // Imposto bit
    perror("RTS Off - impostazione flag");
  }
#endif
}

void Com::mysleep (int delay)
{
#ifdef __WXMSW__
  Sleep ((delay+999)/1000);
#else
  usleep (delay);
#endif
}

#ifdef __WXMSW__
int Com::comPut (const unsigned char c)
{
DWORD l;
  if (WriteFile(com,  // handle to file to write to
    &c,             // pointer to data to write to file
    1,              // number of bytes to write
    &l,NULL) == 0) {// pointer to number of bytes written
   	prt->printf ("Errore di scrittura!!!\n");
    txErr ();
    return -100;
  }
  return (0);
}
#endif

/* Invia un singolo carattere sulla seriale, alla scheda */
void Com::schPutc (const unsigned char c)
{
int r0,r;
#ifdef __WXMSW__
/* Dev C++ che usa MingWin sotto Windows */
  if (comPut (c)) {
    throw (new Exception ("schPutc: Sending Character"));
  }
#else
  write (com, &c, 1);
#endif
const char *errm = NULL;
  try {
	  errm = "schPutc: [%02x](--) -2 Scheda sconnessa o spenta";
	  r0 = schGetc (STDTIMEOUT);
	  if (r0 != c) {
	    txErr ();
	    errm = "schPutc: [%02x](%02x) -3 Errore nel loopback!!!";
	    throw (new Exception ("Errore nel loopback!!!"));
	  }
	  errm = "schPutc: [%02x](%02x)(--) -4 Processore mancante o non in monitor mode";
	  r = schGetc (STDTIMEOUT);
	  if (r != c) {
	    txErr ();
	    errm = "schPutc: [%02x](%02x)(%02x) -5 Errore di comunicazione";
	  }
  } catch (Exception *e) {
    txErr ();
    e->reCode (errm, c, r0, r);
    throw (e);
  }
}

/* Ritorna un carattere dalla seriale
 * con un timeout di 25 millisecondi
 * Se il carattere non arriva, ritorna -1;
 */
int Com::schGetc (int timeout)
{
unsigned char c;
#ifdef __WXMSW__
DWORD l = 0;
/* Dev C++ che usa MingWin sotto Windows */
COMMTIMEOUTS comTimeOut;                   
    
  comTimeOut.ReadIntervalTimeout = (timeout + 999)/1000;  // Specify time-out between charactor for receiving.
  comTimeOut.ReadTotalTimeoutMultiplier = (timeout + 999)/1000;      // Always single byte read
  comTimeOut.ReadTotalTimeoutConstant = (timeout + 999)/1000;        // Always single byte reads
  comTimeOut.WriteTotalTimeoutMultiplier = 0;     // No write timeout
  comTimeOut.WriteTotalTimeoutConstant = 0;       // No write timeout
  SetCommTimeouts(com,&comTimeOut);
  ReadFile(com,  // handle of file to read
    &c,          // handle of file to read
    1,           // number of bytes to read
    &l,          // pointer to number of bytes read
    NULL);       // pointer to structure for data
  if (l == 0) {  // Non ha letto caratteri
    throw (new Exception ("schGetc: timeout"));
  }
#else
fd_set set;
struct timeval to;
int numread;

  /* Initialize the file descriptor set. */
  FD_ZERO (&set);
  FD_SET (com, &set);
     
  /* Initialize the timeout data structure. */
  to.tv_sec = 0;
  to.tv_usec = timeout;  // 25 mS
  /* `select' returns 0 if timeout, 1 if input available, -1 if error. */
  if (!select (FD_SETSIZE, &set, NULL, NULL, &to))
  {
    throw (new Exception ("schGetc: timeout"));
  }
  if ((numread = read (com,&c,1)) != 1) {
    printf ("Errno %d - %d ", errno, numread);
    perror("Lettura carattere");
  }
#endif
  return c & 0xff;
}

void Com::txErr (void)
{
//  rtsOn ();
//  rtsOff ();  // Impulso su RTS (pin 7) per debug hardware
  mysleep (50000);  // Attendo 50 mS
  flushInput ();   // Svuoto il buffer
}

