1 /*
2  #  Copyright (C) 2011,2012 Alois Schloegl, IST Austria <alois.schloegl@ist.ac.at>
3  #
4  #    This program is free software; you can redistribute it and/or modify
5  #    it under the terms of the GNU General Public License as published by
6  #    the Free Software Foundation; either version 3 of the License, or
7  #    (at your option) any later version.
8  #
9  #    This program is distributed in the hope that it will be useful,
10  #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  #    GNU General Public License for more details.
13  #
14  #    You should have received a copy of the GNU General Public License
15  #    along with this program; If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19    Device supported:
20         VPFlowMate inline from VPInstruments
21 
22   DONE(+)/TODO(-):
23     + units of flow l/min oder m^3/h ??
24     + graceful handling of exit (close all handles even when stopped with <CTRL>-C
25     + one file per day, appending
26     + autostart
27     - file management, data compression
28     - init.d (flowmon start/stop)
29     - fix appending to *.log.gdf file after restart
30     - configure serial number, type etc.
31     + graceful handling of exit (close all handles even when stopped with <CTRL>-C
32     + one file per day
33 */
34 
35 
36 #include "../biosig.h"
37 
38 #include <math.h>
39 #include <inttypes.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <time.h>
44 #include <sys/time.h>
45 
46 #include <termios.h>
47 #include <unistd.h>
48 #include <fcntl.h>
49 #include <sys/signal.h>
50 
51 
52 #define LENBUF 100
53 #define BAUDRATE B9600
54 #define MODEMDEVICE "/dev/ttyS0"
55 #define _POSIX_SOURCE 1         //POSIX compliant source
56 #define FALSE 0
57 #define TRUE 1
58 
59 
60 int  wait_flag=TRUE;                     //TRUE while no signal received
61 void signal_handler_IO (int status);    //definition of signal handler
62 char buf[LENBUF+1];
63 
64 
65 int fd;
66 FILE *fid=NULL;
67 FILE *fid2=NULL;
68 // biosig
69 HDRTYPE *hdr = NULL;
70 struct termios oldtio, newtio;       //place for old and new port settings for serial port
71 
stop()72 void stop() {
73         // reset terminal
74         if ( (fd>2) && memcmp(&oldtio,&newtio,sizeof(oldtio)) ) tcsetattr(fd,TCSANOW,&oldtio);
75         if (fid) fclose(fid);
76 
77         // close gdf file
78         if (hdr) destructHDR(hdr);
79 
80         // close debug file
81         if (fid2) fclose(fid2);
82 }
83 
84 
main(int argc,char * argv[])85 int main(int argc, char *argv[]) {
86 
87 const char *devicename = "/dev/ttyS0";
88 const char *outFile = NULL;
89 const char *debugFile = NULL;
90 struct sigaction saio;               //definition of signal action
91 long BAUD;                      // derived baud rate from command line
92 long DATABITS;
93 long STOPBITS;
94 long PARITYON;
95 long PARITY;
96 int Data_Bits = 8;              // Number of data bits
97 int Stop_Bits = 1;              // Number of stop bits
98 int Parity = 0;                 // Parity as follows:
99                   // 00 = NONE, 01 = Odd, 02 = Even, 03 = Mark, 04 = Space
100 struct timeval tv;
101 struct timezone tz;
102 struct tm *tm;
103 struct tm T;
104 uint32_t oldDay=0, newDay;
105 gdf_time gdfTime;
106 char flag_GZIP = 0;
107 
108 char logfile[] = "flowmonYYYYMMDD.log.gdf";
109 char debugfile[] = "flowmonDD.log.txt";
110 
111 
112         /***************************************************************************
113          *
114          *    input arguments
115          *
116          ***************************************************************************/
117 
118 	const char help[]=
119 		"FLOWMON reads data of the flow sensor through the serial terminal and stores it into a data file for archiving.\n"
120 		"  This software supports the device 'VPFlowMate inline from VPInstruments'.\n\n"
121 		"Usage: flowmon -d devicename [-o outfile] [-D debugfile] [-V#]\n"
122 		"   devicename: default value is /dev/ttyS0\n"
123 		"   outfile:    logs the recorded data\n"
124                 "               If no outfile is provided, the data will be logged into daily files named flowmon<$date>.log.gdf \n"
125                 "   debugfile:  logs the data in ascii text"
126                 "               If no outfile is provided, the data will be logged into daily files named flowmon<$day-of-month>.log.txt \n"
127                 "   -V#		verbose level #=0 is no messages, #=9 is highest level(debugging) messages\n"
128                 "   -z		save outfile in gzipped format"
129                 " \n\n"
130 	;
131 	if (argc<2) {
132 		fprintf(stdout,"%s",help);
133 //		exit(0);
134 	}
135 
136 	/* Sanity checks of input arguments */
137 	int k = 0;
138 	while (k<argc) {
139 
140                 if (VERBOSE_LEVEL>3) fprintf(stdout,"%i/%i\t%s\n",k,argc,argv[k]);
141 
142                 if (0) {
143 		}
144 		else if (!strcmp(argv[k],"-d")) {
145 			devicename = argv[++k];
146 		}
147 		else if (!strcmp(argv[k],"-o")) {
148 			k++;
149 			outFile = argv[k];
150                 }
151 		else if (!strcmp(argv[k],"-D")) {
152 			k++;
153 			debugFile = argv[k];
154 		}
155 		else if (!strncmp(argv[k],"-V",2)) {
156 	                char c = argv[k][2];
157 	                if ('0'<=c && c<='9')
158 	                        VERBOSE_LEVEL = c-'0';
159 		}
160 		else if (!strcmp(argv[k],"-z")) {
161 		        flag_GZIP = 1;
162 		}
163 		k++;
164 	}
165 
166         /***************************************************************************
167          *
168          *    initialization
169          *
170          ***************************************************************************/
171 
172         // clean up at exit
173         atexit(&stop);
174 
175         gettimeofday(&tv, &tz);
176         tm = gmtime(&tv.tv_sec);
177         gdfTime = tm_time2gdf_time(gmtime(&tv.tv_sec)) + (uint64_t)ldexp(tv.tv_usec*1e-6/(24*3600),32);
178         newDay = gdfTime>>32;
179         gdf_time2tm_time_r(gdfTime, &T);
180 
181 	if (debugFile)
182 	        fid2 = fopen(debugFile,"a");
183         else {
184 		sprintf(debugfile,"flowmon%02d.log.txt",tm->tm_mday);
185                 fid2 = fopen(debugfile,"a");
186         }
187 
188         {
189 		hdr = constructHDR(4,0);
190 		hdr->SampleRate = 1;
191 		hdr->SPR     =  1;
192 		hdr->NRec    = -1;
193 		hdr->EVENT.N =  0;
194 		hdr->FILE.COMPRESSION = 0;
195                 {
196                         // channel 0: time stamp
197 			CHANNEL_TYPE *hc = hdr->CHANNEL + 0;
198 			hc->LeadIdCode = 0;
199 			strcpy(hc->Label,"time ");
200 			hc->GDFTYP  = 8;	// uint64
201 			hc->SPR     = hdr->SPR;
202 			hc->PhysMax = ldexp(1,32);
203 			hc->PhysMin = 0;
204 			hc->DigMax  = ldexp(1,64);
205 			hc->DigMin  = 0;
206                         hc->PhysDimCode = PhysDimCode("d"); 	// days
207                         hdr->AS.bpb += GDFTYP_BITS[hc->GDFTYP]>>3;
208 		}
209                 {
210                         // channel 1: volume
211 			CHANNEL_TYPE *hc = hdr->CHANNEL + 1;
212 			hc->LeadIdCode = 0;
213 			strcpy(hc->Label,"total volume ");
214 			hc->GDFTYP  = 6;        // uint32
215 			hc->SPR     = hdr->SPR;
216 			hc->PhysMax = ldexp(1,32);
217 			hc->PhysMin = 0;
218 			hc->DigMax  = ldexp(1,32);
219 			hc->DigMin  = 0;
220                         hc->PhysDimCode = PhysDimCode("l");
221                         hdr->AS.bpb += GDFTYP_BITS[hc->GDFTYP]>>3;
222 		}
223                 {
224                         // channel 2: flow
225 			CHANNEL_TYPE *hc = hdr->CHANNEL + 2;
226 			hc->LeadIdCode = 0;
227 			strcpy(hc->Label,"flow ");
228 			hc->GDFTYP  = 3;        // int16
229 			hc->SPR     = hdr->SPR;
230 			hc->PhysMax = (ldexp(1,15)-1)/10;
231 			hc->PhysMin =-ldexp(1,15)/10;
232 			hc->DigMax  = ldexp(1,15)-1;
233 			hc->DigMin  =-ldexp(1,15);
234                         hc->PhysDimCode = 3072; // "l min-1"
235                         hdr->AS.bpb += GDFTYP_BITS[hc->GDFTYP]>>3;
236 		}
237                 {
238                         // channel 3: type of gas
239 			CHANNEL_TYPE *hc = hdr->CHANNEL + 3;
240 			hc->LeadIdCode = 0;
241 			strcpy(hc->Label,"typ of gas");
242 			hc->GDFTYP  = 2;        // uint8
243 			hc->SPR     = hdr->SPR;
244 			hc->PhysMax = 255;
245 			hc->PhysMin = 0;
246 			hc->DigMax  = 255;
247 			hc->DigMin  = 0;
248                         hc->PhysDimCode = 0;
249                         hdr->AS.bpb += GDFTYP_BITS[hc->GDFTYP]>>3;
250 		}
251 		for (k=0; k<hdr->NS; k++) {
252 		      CHANNEL_TYPE *hc = hdr->CHANNEL + k;
253 		      hc->LeadIdCode = 0;
254 		      hc->SPR = 1;
255 		}
256 		hdr->AS.rawdata = (uint8_t*)realloc(hdr->AS.rawdata,hdr->AS.bpb);
257 		hdr->ID.Manufacturer.Name  = "VPInstruments";
258 		hdr->ID.Manufacturer.Model = "VPFlowMate";
259 		hdr->ID.Manufacturer.Version = "VPF-R0030-M050-D1-S110-E200";
260 		hdr->ID.Manufacturer.SerialNumber = "103569";
261 		hdr->FLAG.UCAL = 1;
262 		hdr->TYPE      = GDF;
263 		hdr->VERSION   = 2.22;
264 		hdr->FileName  = outFile;
265         }
266 
267         if (outFile) {
268                 // open once write all data into single log file
269                 hdr->FILE.COMPRESSION = flag_GZIP;
270 		hdr = sopen(outFile, "a", hdr);
271 //		hdr2ascii(hdr,stdout,4);
272 
273                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 010 %i\n", (int)hdr->NRec);
274 
275 		if (serror2(hdr)) {
276 			destructHDR(hdr);
277 			return(EXIT_FAILURE);
278 		}
279 
280                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 020\n");
281 
282 //		if (VERBOSE_LEVEL>6) hdr2ascii(hdr, stdout, 3);
283 		if (hdr->FILE.OPEN < 2) {
284 			destructHDR(hdr);
285 			hdr = NULL;
286 			fprintf(stderr,"Could not open output file  %s\n", outFile);
287 			exit(-1);
288 		}
289 
290                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 030\n");
291 
292 		hdr->AS.rawdata = (uint8_t*)realloc(hdr->AS.rawdata,hdr->AS.bpb);
293 		if (hdr->NRec < 0) hdr->NRec = 0;
294 	}
295 
296 	if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 090\n");
297 
298         //open the device(com port) to be non-blocking (read will return immediately)
299         fd = open(devicename, O_RDWR | O_NOCTTY);
300         if (fd < 0) {
301                 perror(devicename);
302                 exit(EXIT_FAILURE);
303         }
304 
305         //install the serial handler before making the device asynchronous
306         saio.sa_handler = signal_handler_IO;
307         sigemptyset(&saio.sa_mask);   //saio.sa_mask = 0;
308         saio.sa_flags = 0;
309         saio.sa_restorer = NULL;
310         sigaction(SIGIO,&saio,NULL);
311 
312         // allow the process to receive SIGIO
313         fcntl(fd, F_SETOWN, getpid());
314         // Make the file descriptor asynchronous (the manual page says only
315         // O_APPEND and O_NONBLOCK, will work with F_SETFL...)
316         fcntl(fd, F_SETFL, FASYNC);
317 
318         tcgetattr(fd,&oldtio); // save current port settings
319         // set new port settings for canonical input processing
320         // newtio.c_cflag = BAUD | CRTSCTS | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD;
321         newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
322         newtio.c_iflag = IGNPAR;
323         newtio.c_oflag = 0;
324         newtio.c_lflag = 0;       //ICANON;
325         newtio.c_cc[VMIN] = 1;
326         newtio.c_cc[VTIME]= 0;
327         tcflush(fd, TCIFLUSH);
328         tcsetattr(fd,TCSANOW,&newtio);
329 
330         /***************************************************************************
331          *
332          *    processing: data is continuosly read from serial interface and written to log and debug file
333          *
334          ***************************************************************************/
335 
336         fid = fdopen(fd, "r");
337         double data[4];
338         while (1) {
339                 /***
340                         get data
341                 ***/
342                 fgets(buf, LENBUF, fid);
343                 gettimeofday(&tv, &tz);
344                 tm = gmtime(&tv.tv_sec);
345                 gdfTime = tm_time2gdf_time(gmtime(&tv.tv_sec)) + (uint64_t)ldexp(tv.tv_usec*1e-6/(24*3600),32);
346                 newDay = gdfTime>>32;
347                 gdf_time2tm_time_r(gdfTime, &T);
348                 if ( (newDay != oldDay) && !outFile) {
349                         // open/close daily log file
350 
351                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 110\n");
352 
353                         sclose(hdr);
354 			hdr->NRec = -1;
355 			hdr->CHANNEL[2].PhysDimCode = 2976; // make sure stored unit is in "m3/h"
356                         sprintf(logfile,"flowmon%04i%02i%02i.log.gdf",T.tm_year+1900,T.tm_mon+1,T.tm_mday);
357                         hdr->FILE.COMPRESSION = flag_GZIP;
358                         hdr = sopen(logfile, "a", hdr);
359 
360                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 120 %p\n", hdr);
361 
362                         if (serror2(hdr)) {
363 				destructHDR(hdr);
364 				return(EXIT_FAILURE);
365                         }
366 
367                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 125 %i\n", (int)hdr->NRec);
368 
369                         if (!hdr->FILE.OPEN) {
370 				destructHDR(hdr);
371 				hdr = NULL;
372         			fprintf(stderr,"Could not open output file  %s\n", logfile);
373 			}
374 
375                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 130\n");
376 
377 			hdr->AS.rawdata = (uint8_t*)realloc(hdr->AS.rawdata,hdr->AS.bpb);
378 			if (hdr->NRec<0) hdr->NRec = 0;
379                 }
380 
381                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 140\n");
382 
383                 if ( (newDay != oldDay) && !debugFile) {
384                         // open/close daily debug file
385 			if (fid2>0) fclose(fid2);
386 			sprintf(debugfile,"flowmon%02d.log.txt",T.tm_mday);
387 			fid2 = fopen(debugfile,"a");
388                 }
389 
390                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 150\n");
391 
392                 if (newDay != oldDay) {
393                         oldDay = newDay;
394                 };
395 
396 
397                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 190\n");
398 
399                 /***
400                         parse data
401                 ***/
402                 size_t i=0,pos[4];
403                 pos[0]=0;
404 		pos[1]=0;
405                 pos[2]=0;
406                 pos[3]=strlen(buf);
407                 while (i<strlen(buf)) {
408                         if ( (uint8_t)buf[i+1]==0x47 && (uint8_t)buf[i]==0xfe )
409                         switch ( *(uint16_t*)(buf+i+2) ) {
410                         case 0x0101: pos[0] = i; break;
411                         case 0x0108: pos[1] = i; break;
412                         case 0x0201: pos[2] = i; break;
413                         }
414                         i++;
415                 }
416 
417 
418                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 210\n");
419 
420 
421                 buf[pos[1]] = 0;
422                 buf[pos[2]] = 0;
423                 buf[pos[3]-2] = 0;
424 //                fprintf(stdout,"%i\t%x\t%c\t%i\t%i\t%i\n",i, (uint8_t)buf[i], buf[i],pos[0],pos[1],pos[2]);
425                 if (pos[2] > 0) {
426                         fprintf(fid2,"|%s|\t|%s|\t|%s|\n",buf+4,buf+pos[1]+4,buf+pos[2]+4);
427                         // volume:  data[1] = atof(&(buf[pos[2]+4]));
428                         // flow:    data[2] = atof(buf+4)*10;
429                         char *t = strchr(buf+4,'.'); t[0]=t[1]; t[1]='.'; // shift decimal point by one digit, multiply by 10
430                         // uint16_t flow10 = atol(buf+4);
431                         // gas:    data[3] = buf[pos[1]+4];
432 
433                         memcpy(hdr->AS.rawdata+hdr->CHANNEL[0].bi,&gdfTime,8);
434                         *(uint32_t*)(hdr->AS.rawdata+hdr->CHANNEL[1].bi) = (uint32_t)atol(buf+pos[2]+4);
435                         *(uint16_t*)(hdr->AS.rawdata+hdr->CHANNEL[2].bi) = (uint16_t)atof(buf+4);
436                         *(uint8_t *)(hdr->AS.rawdata+hdr->CHANNEL[3].bi) = (uint8_t)buf[pos[1]+4];
437 
438                 /***
439                         write data
440                 ***/
441                         fprintf(fid2,"%04d-%02d-%02d %02d:%02d:%02d.%02d \t%u\t%lu\n",tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec, data[2], data[1]);
442                         fprintf(fid2,"%f\t%u\t%u\t%u\n",ldexp(gdfTime,-32),*(uint32_t*)(hdr->AS.rawdata+hdr->CHANNEL[1].bi),*(uint16_t*)(hdr->AS.rawdata+hdr->CHANNEL[2].bi),*(uint8_t *)(hdr->AS.rawdata+hdr->CHANNEL[3].bi));
443                         //if (hdr) swrite(data,1,hdr);
444 
445                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 220\n");
446 
447                         if (hdr) {
448 
449                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 230 %i, %i, %i\n", (int)hdr->NRec, hdr->AS.bpb, hdr->FILE.OPEN);
450 
451                                 hdr->NRec += ifwrite(hdr->AS.rawdata, hdr->AS.bpb, 1, hdr);
452                                 ifflush(hdr);
453 
454                 if (VERBOSE_LEVEL>7) fprintf(stdout,"FLOWMON 290 %i\n", (int)hdr->NRec);
455 
456                         }
457                 }
458         }
459         stop();
460 
461 return(0);
462 }
463 
464 
465 
466 /***************************************************************************
467 * signal handler. sets wait_flag to FALSE, to indicate above loop that     *
468 * characters have been received.                                           *
469 ***************************************************************************/
470 
signal_handler_IO(int status)471 void signal_handler_IO (int status)
472 {
473 //      printf("received SIGIO signal.\n");
474         wait_flag = FALSE;
475 }
476 
477