/*
 * COM switch - control software
 * Compile command: cc -o comswitch comswitch.c
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef __linux__
#include <termio.h>
#else
#include <termios.h>
#endif /* of __linux__ */

void bail(int f,char*s);

int term_getpins(int fd)
{
// TODO: Borrow the Windows routines from PonyProg,  rs232int.cpp
	int flags=0,r;
	r = ioctl(fd, TIOCMGET, &flags);
	if ( r < 0 ) {bail(fd,"ERROR: IOCTL call TIOCMGET failed.");
	              return -1;}
	return flags;
}

int term_setpins(int fd,int dtr,int rts)
{
// TODO: Borrow the Windows routines from PonyProg,  rs232int.cpp
	int rval, r, i;
	int opins=0;
	if(dtr)opins|=TIOCM_DTR;
	if(rts)opins|=TIOCM_RTS;
	r = ioctl(fd, TIOCMSET, &opins);
	if ( r < 0 ) {bail(fd,"ERROR: IOCTL call TIOCMSET failed.");
	              return -1;}
	return 0;
}

int dcd=0,cts=0;

int getpins(int f)
{ int n;
  usleep(50000);
  n=term_getpins(f);
  dcd=n&TIOCM_CD;
  cts=n&TIOCM_CTS;
}

int verbose=0;
int force=0;
int duration=50; // default for switch-on time, 5 seconds
int checkloop=1;

int bail_recursive=0;

void bail(int f,char*s)
{ fprintf(stderr,"%s\n",s);
  if(!bail_recursive)
  {bail_recursive=1;	// prevent getting stuck in an ioctl error loop
   term_setpins(f,0,0);}
  bail_recursive=0;
  if(!force)exit(111);
}

void bailstr(char*s,char*s1)
{ fprintf(stderr,s,s1);
  exit(111);
}

void bailerrstr(char*s,char*s1)
{ char ss[256];
  snprintf(ss,255,s,s1);
  perror(ss);
  exit(111);
}

void printstates(int dsr,int rts,int dcd,int cts)
{ if(!verbose)return;
  if(dsr)dsr=1;
  if(rts)rts=1;
  if(dcd)dcd=1;
  if(cts)cts=1;
  printf("DSR=%i RTS=%i: DCD=%i CTS=%i\n",dsr,rts,dcd,cts);
}

void help()
{ printf("Usage: comswitch /dev/ttySx [-q] [-f] [n]\n"
         " where /dev/ttySx is the serial port device\n"
	 "       -v   be verbose\n"
	 "       -c   check loop only (default)\n"
	 "       -s   switch (mutually exclusive with -c)\n"
	 "       -f   does not stop on errors (for debug)\n"
	 "       n    is the number of tenths of seconds to keep the switch on (default 50)\n"
  );
  exit(0);
}


int main(int argc,char*argv[])
{ int f,t;
  int exitcode=0;
  char*devicename=NULL;
  for(t=1;t<argc;t++)
  {if(!strcmp(argv[t],"-v")){verbose=1;continue;}
   if(!strcmp(argv[t],"-c")){checkloop=1;continue;}
   if(!strcmp(argv[t],"-s")){checkloop=0;continue;}
   if(!strcmp(argv[t],"-f")){force=1;continue;}
   if(!strcmp(argv[t],"-h")){help();}
   if(!strcmp(argv[t],"--help")){help();}
   if(!strncmp(argv[t],"-")){bailstr("ERROR: Unknown parameter '%s'.\n",argv[t]);}
   if(atoi(argv[t])){duration=atoi(argv[t]);continue;}
   devicename=argv[t];
  }
  if(!devicename)help();
  if(!devicename[0])help();
  f=open(devicename,O_RDWR|O_NONBLOCK);
  if(f<0)bailerrstr("ERROR: Can not open device '%s'",devicename);
  // init
  term_setpins(f,0,0);
  getpins(f);
  printstates(0,0,dcd,cts);
  if(dcd||cts){if(checkloop){printf("-FAIL: Spurious input signals.\n");exit(3);}
               bail(f,"ERROR: DTR=0 and RTS=0, so DCD has to be 0 too.");exitcode=5;}
  // test
  term_setpins(f,1,1);
  getpins(f);
  printstates(1,1,dcd,cts);
  if(checkloop)
  {if(!cts){printf("-FAIL: Adapter not detected.\n");exitcode=2;}else
   if(!dcd){printf("-FAIL: Loop interrupted or not powered.\n");exitcode=1;}else
            printf("+OK\n");
  }
  else
  {
  if(!cts){bail(f,"ERROR: RTS=1 but CTS=0. COMswitch adapter not detected on the port.");exitcode=2;}
  if(!dcd){bail(f,"ERROR: RTS=1 but DCD=0. Check the loop power and integrity.");exitcode=1;}
  usleep(10000);
  // switch
  term_setpins(f,0,1);
  getpins(f);
  printstates(0,1,dcd,cts);
  if(!cts){bail(f,"ERROR: RTS=1 but CTS=0. COMswitch adapter not detected on the port. This is weird.");exitcode=2;}
  if(dcd) {bail(f,"ERROR: RTS=1 and DTR=0 but DCD=1. The loop is powered but the switch is not opened. Check the transistor T1 and optron O1.");exitcode=4;}
  usleep(duration*100000);
  }
  // switch all off
  term_setpins(f,0,0);
  getpins(f);
  printstates(0,0,dcd,cts);
  if(dcd||cts){bail(f,"ERROR: DTR=0 and RTS=0, so DCD has to be 0 too.");exitcode=5;}
  exit(exitcode);
}