#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <termios.h>
#include <sys/ioctl.h>  // for TIOCMGET, TIOCM_CTS and friends

#include <signal.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

/*
  V1.1  Released under public domain, do whatever you like with it.
  
  Read irda serial data from electric meter, in my case an Elster A100C.  Displays per line:
   1. The E character.
   2. Current system time in UNIX time.
   3. WattHours used.
   4. Two unknown but surely meaningful bytes. Does anybody know?
   5. An estimate of the Watts (ie. energy per second) consumed since the WHs counter changed.
and when a open to close gas meter signal arrives, it outputs in the format:
   1. The G character.
   2. System time in UNIX time of the previous open to close transition, with millisecond resolution.
   3. Duration of the open to close, to, open to close. ie. Seconds between digit rotations.
   4. Based on duration, average number of cubic meters used in that period.

  ToDo: Add option to enable continuous full width hex output.
  
  Data is sent at 2400 baud, 110 bytes per frame including a 1 byte 
  checksum. The data is sent once every second, regardless of 
  anything listening, so it is physically irda but seems to lack
  any of the irda layers - also there is no 9600 baud component,
  which is supposed be part of irda negociation.

  Each 110 byte frame includes:
  1. The make and model of the eletric meter.
  2. The serial number of the electric meter.
  3. The number of units used, in WattHours i.e., 1000 times the 
  resolution of the usual kWattHours displayed on the lcd screen.
  4. The total eletric meter runtime, or something like it, in hours.
  5. Some other hour counter.
  6. Two unknown bytes loosely related to power consumption, but not.
  7. Checksum of the frame, which is the sum of all previous 109 bytes mod 256.

  It is not known how consistent this format is across electric meters.

  Example serial port code was nicked from a man page.

 */


#define BAUDRATE B2400
#define MODEMDEVICE "/dev/ttyS0"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1

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

int main(int argc, char **argv)
 {
   int fd, i, first=0, last=0, endof=1023, wrapped=0;
   struct termios oldtio,newtio;
   unsigned char buf[255], circ[1024];
   int seenmatch=0, showhex=0;   // start printing hex dump if the search pattern fails after endof bytes of input

   if(argc!=3) 
    {
      fprintf(stderr,"%s <serial device> <key>\n",argv[0]);
      fprintf(stderr,"eg. %s /dev/ttyS1 Elster\n",argv[0]);
      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 );
 
   long lastwhs=-1, lasttime=-1, estupdates=0;
   float lastwestimate=0;

   struct timeval tv_now={0,0}, tv_z2o={0,0}, tv_o2z={0,0}, tv_old={0,0};
   int cts_prev=-1;

   //int cts_status;
   //for(cts_prev=0;cts_prev<100000000;cts_prev++)   // runs at 3.3M samples/second on my Core2, ~50% of the time is in system space
   //   ioctl(fd,TIOCMGET,&cts_status);

   int res=1;

   time_t time_lastread=time(NULL);

   int showgas=1;
   int showelec=1;

   do /* loop for input */
    {

      fd_set rfds;
      struct timeval tv_select;
      int retval;

      /* Watch fd to see when it has input. */
      FD_ZERO(&rfds);
      if(showelec)FD_SET(fd, &rfds);
      /* Wait up to five seconds. */
      tv_select.tv_sec = 0;
      tv_select.tv_usec = 100000;

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

      //int status;
      //ioctl(fd,TIOCMGET,&status);
      //printf("status=%04x\n",status & TIOCM_CTS);  // Set if CTS shorted to RTS

      int cts_status;
      long long cts_period=-1;

      ioctl(fd,TIOCMGET,&cts_status);
      //printf("status=%04x\n",cts_status & TIOCM_CTS);  // Set if CTS shorted to RTS
      if(cts_status&TIOCM_CTS)cts_status=1; else cts_status=0;
      //printf("%d",cts_status); fflush(stdout);

      if( showgas && cts_status!=cts_prev )
       {
         gettimeofday(&tv_now, NULL);

         if( cts_prev>=0 )
          {
            if( cts_status )
             {
               long long diff=tv_now.tv_sec - tv_z2o.tv_sec;
               diff*=1000000;
               diff+=tv_now.tv_usec - tv_z2o.tv_usec;
  	       cts_period=diff;
               //printf("%10lld 1\n",diff);
	       tv_old.tv_sec=tv_z2o.tv_sec, tv_old.tv_usec=tv_z2o.tv_usec;
               tv_z2o.tv_sec=tv_now.tv_sec, tv_z2o.tv_usec=tv_now.tv_usec;
             }
            else
             {
               long long diff=tv_now.tv_sec - tv_o2z.tv_sec;
      	       diff*=1000000;
	       diff+=tv_now.tv_usec - tv_o2z.tv_usec;
               //printf("%10lld 0\n",diff);
               tv_o2z.tv_sec=tv_now.tv_sec, tv_o2z.tv_usec=tv_now.tv_usec;
             }
            cts_prev=cts_status;
          }
         else
            cts_prev=cts_status;
       }
      if( tv_old.tv_sec>0 && cts_period>=0 )
       {
         double LitresPerSecond=1000000.0/( (long double)cts_period );
	 printf("G\t%ld.%ld\t%0.3f\t%f\n",tv_old.tv_sec,tv_old.tv_usec/1000, ((float)(cts_period/1000))/1000, LitresPerSecond);
         cts_period=-1;
       }
      
      if( !showelec )
        ;
      else if( retval>=0 && FD_ISSET(fd, &rfds) )
       {

         res = read(fd,buf,255);   /* returns after 255 chars have been input, or less */
         if( res>0 )
          {
            /*printf(" %d ",res );*/
            for( i=0;i<res;i++)
	     {
	       if( (++last)>endof ){ last=0; wrapped=1; if(!seenmatch)showhex=1; }
	       if( last==first )first++;
	       if( first>endof )first=0;
	       circ[last]=buf[i];

               int len=0;
   	       if(wrapped)len=endof; else len=last;

               if(showhex)
	        {
		  int j,o;
		  printf("Search pattern looking for Make+Model number failed, hex dump of raw output is:\n");
                  for(j=1;j<endof/2;j+=16)
		   {
                     for(o=0;o<16;o++)
		        printf(" %02x",circ[j+o]);
		     printf(" ");
                     for(o=0;o<16;o++)
		      {
		        int c=circ[j+o];
			if( c<32 || c>=127 )c='.';
  		        printf("%c",c);
	              }
                     printf("\n");
		   }
		  exit(1);
		}

               //printf("first=%d, last=%d,len=%d\n",first,last,len);
               
	       if(len>125)
	        {
		  int spos=last-125;  // don't start searching at the beginning
		  if(spos<0)spos+=endof;
		  int pos=spos,j;
		  for(j=0;j<strlen(argv[2]);j++)
		   {
		     if(pos>endof)pos=0;
   		     if( circ[pos]!=argv[2][j] )
		        break;
	             pos++;
	           }
		  if( j==strlen(argv[2]) )
		   {
		     seenmatch=1;
		     spos=last-129;
		     if(spos<0)spos+=endof;
		     //printf("Match found, last=%d\n",last);
		     int checksum=0,sum=0;
		     long whs=0, unknown1=0, unknown2=0;
		     for(pos=spos,j=0;j<109;j++,pos++)
		      {
			if(pos>endof)pos=0;
			checksum+=circ[pos];
			if(j>=49 && j<=53){ char cbuf[10]; sprintf(cbuf,"%02x",circ[pos]); whs=whs*100+strtoul(cbuf,NULL,10); }
			else if(j==79)unknown1=circ[pos];
			else if(j==80)unknown2=circ[pos];
		        checksum&=0xff;
		      }
         	     if(pos>endof)pos=0;
	             sum=circ[pos];
		     if(0)       // set to 1 to print the full hex with positions
		      {          // and skip printing the regular short information
		        for(pos=spos,j=0;j<110;j++,pos++)
		         {                   
		  	   if(pos>endof)pos=0;
			   unsigned char c=circ[pos];  if( c<32 || c>=127 )c='.';
		           printf(" (%d)(%c)%02x",j+1,c,circ[pos]);
			 }
		        printf(" %2x %2x\n",checksum,sum);
		      }
		     else 
		      {
  		        if( sum==checksum)
		         {
		           long ttime=time(NULL);
                           if( lastwhs!=whs )
	 		    {
			      float dt=ttime-lasttime;
			      float dwh=whs-lastwhs;
			      if(lasttime>0 && lastwhs>0 && estupdates>1)lastwestimate=(dwh/dt)*3600;
			      lasttime=ttime;
		  	      lastwhs=whs;
			      estupdates++;
			    }

			   printf("E\t%ld\t",ttime);
		           printf("%ld\t%02lx\t%02lx",whs,unknown1,unknown2);
		           if(lastwestimate>0)printf("\t%0.1f\n",lastwestimate); else printf("\n");
			   lastwestimate=0.0;
		         }
		        else
		          fprintf(stderr,"bad checksum, %02x!=%02x\n",checksum,sum);
		      }
		   }
		}
	     }

            fflush(stdout);
            time_lastread=time(NULL);
          }
       }
      else
       {
         time_t now=time(NULL);
         if( now>time_lastread+5 )
	  {
            fprintf(stderr,"No serial data within five seconds, retrying\n");
	    time_lastread=now;
	  }
       }
    } while( res>0 );

   tcsetattr(fd,TCSANOW,&oldtio);

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


