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);
|
||
|
}
|
||
|
}
|