#include "tic.h" #include "logger.h" #include #include #include #include #include #include #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); } }