#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#include <signal.h>

/*
  Version 1.0

  Compile with: gcc -O2 libert.c -o libert
  Run with: ./libert /dev/ttyS0

  Notice important fields, such as On Battery and Low Battery are missing,
  there was no way of testing for this at the time, the ups was connected
  to a live system.

  Example serial port code was nicked from a man page.
 */

/*
Initially decoded with:
slsnif and some bash/awk/sed:
tail -n 1000000 -f delme.log | awk -- '/Host|Device/ {printf "%s\t",$1; for(i=2;i<=NF;i++)if(match($i,"(([0-9][0-9][0-9]))",a)>0)printf "<%s>",a[1]; printf "\n";}' | awk -- 'BEGIN {c=0; system("rm -f cmd2col.txt");} /^Host/ {command=$2; if(!($2 in col)){seen[$2]=1; col[$2]=c++; printf "%d\t%s\n",col[$2],$2 >> "cmd2col.txt"; fflush("cmd2col.txt");} else seen[$2]++;} /^Device/ {printf "%d\t%d\t%s\t%s\n",col[command],seen[command],command,$2;}' | sed "s/[<>]/ /g" | awk -- '{printf "%s\t",$0; if($14>=32 && $14<=127)printf "%c",$14; else printf "_"; if($15>=32 && $15<=127)printf " %c",$15; else printf " _"; printf "\n"; }' | awk -- '{if(colshort[$1]!=$14*256+$15){printf "%3d\t%03d %03d (%5d) -> %03d %03d (%5d)\n",$1,colp1[$1],colp2[$1],colshort[$1],$14,$15,$14*256+$15; } colp1[$1]=$14; colp2[$1]=$15; colshort[$1]=$14*256+$15;}'

*/

#define BAUDRATE B2400
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1

void ignore(int status)
 {
   fprintf(stderr,"Signaled\n");
 }

unsigned char cksum(unsigned char *buf, int len)
 {
   unsigned char sum=0;
   while(len-->0)
      sum+=*buf++;
   return(sum);
 }



int len_modelname=15,
    len_firmwarev=8,
    len_serialnum=10,
    len_dateomanu=4;
  
unsigned char 
   cmd_modelname[]={ 1,136,2,1,4,144 }, // Model name, ie. "GXT2-3000RT230                " 
   cmd_firmwarev[]={ 1,136,2,1,19,159 }, // Firmware version, ie. "GXT2MR15D-2K3K  "
   cmd_serialnum[]={ 1,136,2,1,27,167 }, // Serial number, ie. "2343010092FB591     "
   cmd_dateomanu[]={ 1,136,2,1,37,177 }, // Date of manufacture, ie. "07OCT02 " for 7th October 2002
   cmd_runtime[]={ 1,149,2,1,1,154 }, // Expected runtime, minutes
   cmd_batvolt[]={ 1,149,2,1,2,155 }, // Battery voltage, Volts, *10
   cmd_batchar[]={ 1,149,2,1,4,157 }, // Battery charge, percent (unconfirmed)
   cmd_realpow[]={ 1,149,2,1,5,158 }, // Real power, Watts
   cmd_appapow[]={ 1,149,2,1,6,159 }, // Apparent power, VA
   cmd_outload[]={ 1,149,2,1,7,160 }, // Output load, percent
   cmd_freq1[]={ 1,149,2,1,8,161 }, // Frequency, Hz, *10
   cmd_freq2[]={ 1,149,2,1,9,162 }, // Frequency, Hz, *10
   cmd_freq3[]={ 1,149,2,1,10,163 }, // Frequency, Hz, *10
   cmd_temp[]={ 1,149,2,1,14,167 }, // Temperature, C, *10
   cmd_involts[]={ 1,144,2,1,1,149 }, // Input voltage, Volts, *10 (unconfirmed)
   cmd_outvolts[]={ 1,144,2,1,3,151 }, // Output voltage, *10
   cmd_outcurr[]={ 1,144,2,1,4,152 }, // Output current?, *10
   cmd_byvolts[]={ 1,144,2,1,5,153 }, // Bypass voltage, *10
   cmd_maxload[]={ 1,161,2,1,8,173 }, // Max load, VA
   cmd_nombatvolts[]={ 1,161,2,1,13,178 }; // Nominal battery voltage, Volts, *10



int GetValue(int fd, unsigned char *cmd)
 {
   unsigned char buf[8];
   cmd[5]=cksum(cmd,5);
   //printf("%03d,%03d,%03d,%03d,%03d,%03d\n",cmd[0],cmd[1],cmd[2],cmd[3],cmd[4],cmd[5]);
   int res=write(fd,cmd,6);
   if(res!=6)return -1;
   res=read(fd,buf,8);
   if(res!=8)return -1;
   if(buf[7]!=cksum(buf,7))return -1;
   return ((unsigned short)buf[5])*256+buf[6];
 }

int GetChars(int fd, unsigned char *cmd, unsigned char *chrs, int len)
 {
   unsigned char buf[8], lcmd[7];
   memcpy(lcmd,cmd,6);
   for(;len>0;len--)
    {
      lcmd[5]=cksum(lcmd,5);
      //printf("%03d,%03d,%03d,%03d,%03d,%03d\n",lcmd[0],lcmd[1],lcmd[2],lcmd[3],lcmd[4],lcmd[5]);
      int wres=write(fd,lcmd,6);
      if(wres!=6)return -1;
      int rres=read(fd,buf,8);
      if(rres!=8)return -1;
      *chrs++=buf[6];
      *chrs++=buf[5];
      //printf("%d %d %03d == %03d\n",wres,rres,buf[7],cksum(buf,7) );
      if(buf[7]!=cksum(buf,7))return -1;
      lcmd[4]++;
    }
   *chrs=0;
   return 0;
 }


int main(int argc, char **argv)
 {
   int fd, res, i;
   struct termios oldtio,newtio;
   unsigned char buf[255];

   if(argc!=2)
    {
      fprintf(stderr,"libert <serial device>\n");
      exit(1);
    }

   fd = open(argv[1], O_RDWR | O_NOCTTY );
   if (fd <0) {perror(argv[1]); exit(-1); }

   tcgetattr(fd,&oldtio); /* save current port settings */

   bzero(&newtio, sizeof(newtio));
   newtio.c_cflag = BAUDRATE /*| CRTSCTS*/ | CS8 | CLOCAL | CREAD;
   newtio.c_iflag = IGNPAR | IGNBRK;
   newtio.c_oflag = 0;

   /* set input mode (non-canonical, no echo,...) */
   newtio.c_lflag = 0;

   newtio.c_cc[VTIME]    = 0;   /* inter-character timer unused */
   newtio.c_cc[VMIN]     = 1;   /* blocking read until 5 chars received */

   tcflush(fd, TCIFLUSH);
   tcsetattr(fd,TCSANOW,&newtio);

   /*signal( SIGINT, &ignore );
   signal( SIGHUP, &ignore );
   signal( SIGTERM, &ignore );
   signal( SIGQUIT, &ignore );*/
   signal( SIGALRM, &ignore );

   // ask for some value a few times, or until it seems to work
   for(i=0;i<10 && GetValue(fd, cmd_runtime )<0;i++)
      ;
   if( i==10 )
    {
      fprintf(stderr,"Error getting an example value while trying to synchronise serial comms.\n");
      exit(1);
    }

   res=GetChars(fd, cmd_modelname, buf, len_modelname);  if(!res)printf("Model name: %s\n",buf);  else printf("Error getting model name\n");
   res=GetChars(fd, cmd_firmwarev, buf, len_firmwarev);  if(!res)printf("Firmware version: %s\n",buf); else printf("Error getting firmware version\n");
   res=GetChars(fd, cmd_serialnum, buf, len_serialnum);  if(!res)printf("Serial #: %s\n",buf); else printf("Error getting serial number\n");
   res=GetChars(fd, cmd_dateomanu, buf, len_dateomanu);  if(!res)printf("Date of manufacture: %s\n",buf); else printf("Error getting date of manufacture\n");
   res=GetValue(fd, cmd_runtime );   if(res>=0)printf("Estimated runtime: %d mins\n",res); else printf("Error getting estimated runtime value\n");

   res=GetValue(fd, cmd_batvolt );   if(res>=0)printf("Battery voltage: %0.1f Volts\n",((float)res)/10 ); else printf("Error getting battery voltage value\n");
   res=GetValue(fd, cmd_batchar );   if(res>=0)printf("Battery charge: %d %%\n",res); else printf("Error getting battery charge value\n");
   res=GetValue(fd, cmd_realpow);    if(res>=0)printf("Real power: %d Watts\n",res ); else printf("Error getting real power value\n");
   res=GetValue(fd, cmd_appapow );   if(res>=0)printf("Apparent power: %d VA\n",res ); else printf("Error getting apparent power value\n");
   res=GetValue(fd, cmd_outload );   if(res>=0)printf("Output load: %d %%\n",res); else printf("Error getting output load value\n");
   res=GetValue(fd, cmd_freq1 );     if(res>=0)printf("Frequency1: %0.1f Hz\n",((float)res)/10); else printf("Error getting frequency1 value\n");
   res=GetValue(fd, cmd_freq2 );     if(res>=0)printf("Frequency2: %0.1f Hz\n",((float)res)/10); else printf("Error getting  frequency2 value\n");
   res=GetValue(fd, cmd_freq3 );     if(res>=0)printf("Frequency3: %0.1f Hz\n",((float)res)/10); else printf("Error getting  frequency3 value\n");
   res=GetValue(fd, cmd_temp );      if(res>=0)printf("Tempurature: %0.1f C\n",((float)res)/10); else printf("Error getting tempurature value\n");
   res=GetValue(fd, cmd_involts );   if(res>=0)printf("Input voltage: %0.1f Volts\n",((float)res)/10); else printf("Error getting input voltage value\n");
   res=GetValue(fd, cmd_outvolts );  if(res>=0)printf("Output voltage: %0.1f Volts\n",((float)res)/10); else printf("Error getting output voltage value\n");
   res=GetValue(fd, cmd_outcurr );   if(res>=0)printf("Output current: %0.1f Amps\n",((float)res)/10); else printf("Error getting output current value\n");
   res=GetValue(fd, cmd_byvolts );   if(res>=0)printf("Bypass voltage: %0.1f Volts\n",((float)res)/10); else printf("Error getting bypass voltage value\n");

   res=GetValue(fd, cmd_maxload );   if(res>=0)printf("Maximum load: %d VA\n",res); else printf("Error getting maximum load value\n");
   res=GetValue(fd, cmd_nombatvolts );  if(res>=0)printf("Nominal battery voltage: %0.1f Volts\n",((float)res)/10); else printf("Error getting nominal voltage value\n");

   /*

   fprintf(stderr,"Waiting for input\n");
   silent=0;
   do // loop for input 
    {

      fd_set rfds;
      struct timeval tv;
      int retval;

      // Watch fd to see when it has input. 
      FD_ZERO(&rfds);
      FD_SET(fd, &rfds);
      // Wait up to five seconds.
      tv.tv_sec = 5;
      tv.tv_usec = 0;

      retval = select(fd+1, &rfds, NULL, NULL, &tv);
      // Don't rely on the value of tv now!

      
      if( retval>=0 && FD_ISSET(fd, &rfds) )
       {
         res = read(fd,buf,255);   // returns after 255 chars have been input 
         if( res>0 )
          {
            printf(" %d ",res );
            for( i=0;i<res;i++)
             {
	       printf("%03d ",buf[i]);
             }
	    s=((unsigned short)buf[5])*256+buf[6];
            fflush(stdout);
            printf("%d\n",s);
            sprintf((char*)buf,"%c%c%c%c%c%c",1,149,2,1,5,158);
            write(fd,buf,6);
          }
       }
      else
       {
	 int i;
         printf("No data within five seconds, best do something to wake it up, datread=%d.\n",datread);
	 tcsetattr(fd,TCSANOW,&oldtio);
	 exit(1);
       }
    } while( res>0 );
   */

   tcsetattr(fd,TCSANOW,&oldtio);

   exit(0);
 } /* main() */


