327 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "tic.h"
 | |
| #include "logger.h"
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <sys/fcntl.h>
 | |
| #include <termios.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #define TIC_FRAME_MAX 2048   // enough for all possible data groups in standard mode
 | |
| #define TIC_DATA_MAX  128
 | |
| 
 | |
| #define TIC_STX      0x02
 | |
| #define TIC_ETX      0x03
 | |
| #define TIC_SGR      0x0a
 | |
| #define TIC_SEP_STD  0x09
 | |
| #define TIC_SEP_HIS  0x20
 | |
| #define TIC_EGR      0x0d
 | |
| 
 | |
| 
 | |
| struct tic_info_s {
 | |
|   int fd;
 | |
|   int is_tty;
 | |
|   int mode;
 | |
| 
 | |
|   tic_cb_data_f *cb_data;
 | |
| 
 | |
|   int  frame_len;
 | |
|   char frame[TIC_FRAME_MAX];
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| static void tic_init_tty(int fd, int speed)
 | |
| {
 | |
|   struct termios termios;
 | |
| 
 | |
|   tcgetattr(fd, &termios);
 | |
| 
 | |
|   // input and output speed
 | |
|   cfsetispeed(&termios, speed);
 | |
|   cfsetospeed(&termios, speed);
 | |
| 
 | |
|   // set input modes:
 | |
|   //  - Disable XON/XOFF flow control on input.
 | |
|   //  - Do not translate carriage return to newline on input.
 | |
|   //  - Enable input parity checking.
 | |
|   //  - Strip off eighth bit.
 | |
|   termios.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);
 | |
|   termios.c_iflag |= INPCK | ISTRIP;
 | |
| 
 | |
|   // set output modes:
 | |
|   //  - Disable implementation-defined output processing (raw mode).
 | |
|   termios.c_oflag &= ~OPOST;
 | |
| 
 | |
|   // set control modes:
 | |
|   //  - Enable receiver.
 | |
|   //  - Ignore modem control lines.
 | |
|   //  - 7 bit.
 | |
|   //  - 1 stop bit.
 | |
|   //  - Enable parity generation on output and parity checking for input.
 | |
|   //  - Disable RTS/CTS (hardware) flow control.
 | |
|   termios.c_cflag |= CLOCAL | CREAD;
 | |
|   termios.c_cflag &= ~(CSIZE | PARODD | CSTOPB);
 | |
|   termios.c_cflag |=   CS7   | PARENB;
 | |
|   termios.c_cflag &= ~CRTSCTS;
 | |
| 
 | |
|   // set local modes:
 | |
|   //  - Do bot generate signal when the characters INTR, QUIT, SUSP or DSUSP are received.
 | |
|   //  - Disable canonical mode.
 | |
|   //  - Do not echo input characters.
 | |
|   termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE);
 | |
| 
 | |
|   // set special characters:
 | |
|   //  - Timeout set to 1 s.
 | |
|   //  - Minimum number of characters for noncanonical read set to 1.
 | |
|   termios.c_cc[VTIME] = 10;
 | |
|   termios.c_cc[VMIN]  = 0;
 | |
|   tcsetattr(fd, TCSANOW, &termios);
 | |
| 
 | |
|   tcflush(fd, TCIFLUSH);
 | |
| }
 | |
| 
 | |
| /* check received stream
 | |
|  * returns:
 | |
|  *  - 0:  ok, received at least 20 valid control chars without any
 | |
|  *        illegal characters
 | |
|  *  - -1: read() error
 | |
|  *  - 1:  timeout, didn't received enough valid chars in the allowed
 | |
|  *        time (2s).
 | |
|  */
 | |
| static int tic_check_read(int fd)
 | |
| {
 | |
|   char c;
 | |
|   int  nb_ok = 0;
 | |
|   time_t stamp = time(NULL);
 | |
| 
 | |
|   tcflush(fd, TCIFLUSH);
 | |
| 
 | |
|   while (nb_ok < 20) {
 | |
|     int ret = read(fd, &c, 1);
 | |
| 
 | |
|     if (ret < 0)
 | |
|       return -1;
 | |
| 
 | |
|     if (ret == 1) {
 | |
|       switch (c) {
 | |
|         case TIC_STX:
 | |
|         case TIC_ETX:
 | |
|         case TIC_SGR:
 | |
|         case TIC_SEP_STD:
 | |
|         case TIC_EGR:
 | |
|           nb_ok++;
 | |
|           break;
 | |
|         default:
 | |
|           if (c < 0x20 || c > 0x7f)
 | |
|             nb_ok = 0;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (difftime(time(NULL), stamp) > 2)
 | |
|       return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| tic_info_t* tic_init(const char *fname)
 | |
| {
 | |
|   tic_info_t* tic = calloc(1, sizeof(tic_info_t));
 | |
|   if (tic == NULL) {
 | |
|     log_error("cannot allocate memory\n");
 | |
|     return NULL;
 | |
|   }
 | |
|   tic->cb_data = NULL;
 | |
| 
 | |
|   log_info("opening %s\n", fname);
 | |
| 
 | |
|   tic->fd = open(fname, O_RDWR | O_NOCTTY);
 | |
|   if (tic->fd < 0) {
 | |
|     log_error("Cannot open %s: %s\n", fname, strerror(errno));
 | |
|     free(tic);
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   tic->is_tty = isatty(tic->fd);
 | |
| 
 | |
|   if (tic->is_tty) {
 | |
|     int ret;
 | |
| 
 | |
|     log_debug("trying standard mode\n");
 | |
|     tic_init_tty(tic->fd, B9600);
 | |
| 
 | |
|     ret = tic_check_read(tic->fd);
 | |
|     if (ret == 0) {
 | |
|       tic->mode = TIC_MODE_STD;
 | |
|       log_info("TIC standard mode detected\n");
 | |
|     } else {
 | |
|       log_debug("trying historical mode\n");
 | |
|       tic_init_tty(tic->fd, B1200);
 | |
| 
 | |
|       ret = tic_check_read(tic->fd);
 | |
|       if (ret == 0) {
 | |
|         tic->mode = TIC_MODE_HIST;
 | |
|         log_info("TIC historical mode detected\n");
 | |
|       } else {
 | |
|         log_error("no valid TIC stream detected\n");
 | |
|         free(tic);
 | |
|         return NULL;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (tic_read_frame(tic)) {
 | |
|     free(tic);
 | |
|     return NULL;
 | |
|   }
 | |
|   tic_process_frame(tic);
 | |
| 
 | |
|   return tic;
 | |
| }
 | |
| 
 | |
| 
 | |
| int tic_mode(tic_info_t *tic)
 | |
| {
 | |
|   return tic->mode;
 | |
| }
 | |
| 
 | |
| void tic_set_cb_data(tic_info_t *tic, tic_cb_data_f *func)
 | |
| {
 | |
|   tic->cb_data = func;
 | |
| }
 | |
| 
 | |
| 
 | |
| int tic_read_frame(tic_info_t *tic)
 | |
| {
 | |
|   char c;
 | |
| 
 | |
|   do {
 | |
|     if (read(tic->fd, &c, 1) <= 0) {
 | |
|       log_error("read error (waiting for STX)\n");
 | |
|       return -1;
 | |
|     }
 | |
|   } while (c != TIC_STX);
 | |
| 
 | |
|   for (int i = 0; i < TIC_FRAME_MAX; i++) {
 | |
|     if (read(tic->fd, &c, 1) <= 0) {
 | |
|       log_error("read error (reading frame)\n");
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|     tic->frame[i] = c;
 | |
|     if (c == TIC_ETX) {
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   log_error("frame too long\n");
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline int twodigits2int(const char *digits)
 | |
| {
 | |
|   return (digits[0]-'0')*10 + (digits[1]-'0');
 | |
| }
 | |
| 
 | |
| static time_t tic_horodate_to_time(const char *horodate)
 | |
| {
 | |
|   if (horodate == NULL)
 | |
|     return (time_t)0L;
 | |
| 
 | |
|   // format: "SAAMMJJhhmmss" Saison-year-month-day-hour-minutes-seconds
 | |
|   //          01 3 5 7 9 11
 | |
|   struct tm tm;
 | |
|   tm.tm_sec  = twodigits2int(&horodate[11]);
 | |
|   tm.tm_min  = twodigits2int(&horodate[9]);
 | |
|   tm.tm_hour = twodigits2int(&horodate[7]);
 | |
|   tm.tm_mday = twodigits2int(&horodate[5]);
 | |
|   tm.tm_mon  = twodigits2int(&horodate[3]);
 | |
|   tm.tm_year = twodigits2int(&horodate[1]) + 2000 - 1900;
 | |
|   switch(horodate[0]) {
 | |
|     case 'E': tm.tm_isdst = 1; break;
 | |
|     case 'H': tm.tm_isdst = 0; break;
 | |
|     default:  tm.tm_isdst = -1;
 | |
|   }
 | |
|   return mktime(&tm);
 | |
| }
 | |
| 
 | |
| void tic_process_frame(tic_info_t *tic)
 | |
| {
 | |
|   char *frame = tic->frame;
 | |
| 
 | |
|   for (int i = 0;;) {
 | |
|     int grp_start;
 | |
|     int grp_end;
 | |
| 
 | |
|     if (frame[i] == TIC_ETX)
 | |
|       return;
 | |
| 
 | |
|     while (frame[i++] != '\n') {
 | |
|       if (i >= TIC_FRAME_MAX)
 | |
|         return;
 | |
|     }
 | |
|     grp_start = i;
 | |
| 
 | |
|     while (frame[i++] != '\r') {
 | |
|       if (i >= TIC_FRAME_MAX)
 | |
|         return;
 | |
|     }
 | |
|     grp_end = i - 1;
 | |
| 
 | |
|     int grp_end_sum = tic->mode == TIC_MODE_STD ? grp_end - 1 : grp_end - 2;
 | |
|     char sum = 0;
 | |
|     for (int j = grp_start; j < grp_end_sum; j++)
 | |
|       sum += frame[j];
 | |
|     sum = (sum & 0x3f) + 0x20;
 | |
| 
 | |
|     if (sum != frame[grp_end - 1]) {
 | |
|       log_warning("checksum error\n");
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     char sep = tic->mode == TIC_MODE_STD ? '\t' : ' ';
 | |
|     int nsep = 0;
 | |
|     char *p_label = &frame[grp_start];
 | |
|     char *p_horodate;
 | |
|     char *p_data;
 | |
|     for (int j = grp_start; j < grp_end - 1; j++) {
 | |
|       if (frame[j] == sep) {
 | |
|         frame[j] = '\0';
 | |
|         nsep++;
 | |
|         switch(nsep) {
 | |
|           case 1: p_horodate = &frame[j+1]; break;
 | |
|           case 2: p_data = &frame[j+1]; break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (nsep > 3 || (nsep == 3 && tic->mode == TIC_MODE_HIST) || nsep < 2) {
 | |
|       log_warning("bad format group\n");
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (nsep == 2) {
 | |
|       p_data = p_horodate;
 | |
|       p_horodate = NULL;
 | |
|     }
 | |
| 
 | |
|     if (tic->cb_data) {
 | |
|       time_t date = tic_horodate_to_time(p_horodate);
 | |
|       (*tic->cb_data)(p_label, p_data, date);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| void tic_exit(tic_info_t *tic)
 | |
| {
 | |
|   if (tic) {
 | |
|     close(tic->fd);
 | |
|     free(tic);
 | |
|   }
 | |
| }
 |