initial release

This commit is contained in:
Gilles Grandou 2021-08-04 12:24:08 +02:00
parent fe267a81e2
commit bb9da629fb
12 changed files with 883 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
tic2mqtt
*.d
*.o

24
Makefile Normal file
View File

@ -0,0 +1,24 @@
SRCS = tic2mqtt.c tic.c mqtt.c homeassistant.c logger.c
OBJS = $(SRCS:%.c=%.o)
LIBS = -lmosquitto -ljson-c
CFLAGS += -Wall -Werror
DEPFLAGS = -MT $@ -MMD -MP -MF .$*.d
DEPS=$(SRCS:%.c=.%.d)
CFLAGS += $(DEPFLAGS)
all: tic2mqtt
tic2mqtt: $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
clean:
rm -f tic2mqtt $(OBJS) $(DEPS)
$(OBJS): $(MAKEFILE_LIST)
-include $(DEPS)
.PHONY: all clean

View File

@ -5,4 +5,15 @@ tic2mqtt is a MQTT client for Linky Electricity Meters installed by
Enedis in France. Hence the rest of this document is in french. Enedis in France. Hence the rest of this document is in french.
References
==========
Librement inspiré du code de pmelly93: https://github.com/pmelly93/tic2mqtt
Specification Téléinformation Client - Enedis:
* Linky: https://www.enedis.fr/sites/default/files/Enedis-NOI-CPT_54E.pdf
* Pre-linky: https://www.enedis.fr/sites/default/files/Enedis-NOI-CPT_02E.pdf
Spécification du champ ADS:
https://euridis.org/wp-content/uploads/IdentifiantsEuridisListeCCTTV304A_143027.pdf

112
homeassistant.c Normal file
View File

@ -0,0 +1,112 @@
#include "homeassistant.h"
#include "mqtt.h"
#include "logger.h"
#include <json-c/json.h>
#include <stddef.h>
#include <stdio.h>
typedef struct {
const char *tag; // TIC Tag
const char *state_class; // HA State Class
const char *device_class; // HA Device Class
const char *name; // HA name
const char *unit; // HA unit
const bool last_reset; // HA LastReset present
const char *value_template; // HA Template for value
} ha_config_desc;
static ha_config_desc ha_config_descs[] = {
{ "PAPP", "measurement", "power", "Puissance Apparente", "VA", },
{ "IINST", "measurement", "current", "Intensité Instantanée", "A", },
{ "HCHP", "measurement", "energy", "Energie Heures Pleines", "kWh", true, "{{ (value | float) / 1000 }}" },
{ "HCHC", "measurement", "energy", "Energie Heures Creuses", "kWh", true, "{{ (value | float) / 1000 }}" },
{ "PTEC", NULL, NULL, "Période Tarifaire en cours", NULL, false,
"{% if value == \"TH..\" %}Toutes les Heures"
"{% elif value == \"HC..\" %}Heures Creuses"
"{% elif value == \"HP..\" %}Heures Pleines"
"{% elif value == \"HN..\" %}Heures Normales"
"{% elif value == \"PM..\" %}Heures de Pointe Mobile"
"{% else %}{{value}}"
"{% endif %}" },
{ NULL }
};
/* workaround for libjson-c 0.12 which always escape
* strings while serializing
*/
static void unescape_str(const char *str)
{
const char *s;
char *d;
for (s = str, d = (char*)str; *s; s++, d++) {
if (*s == '\\' && *(s+1) == '/')
s++;
*d = *s;
}
*d = '\0';
}
void ha_config_init(const char *tic_name, struct mosquitto *mosq_tic)
{
ha_config_desc *desc;
// char payload[512];
json_object *device = json_object_new_object();
json_object *dev_ids = json_object_new_array();
json_object_array_add(dev_ids, json_object_new_string(tic_name));
json_object_object_add(device, "identifiers", dev_ids);
json_object_object_add(device, "manufacturer", json_object_new_string("Enedis"));
json_object_object_add(device, "model", json_object_new_string("Linky Monophasé"));
json_object_object_add(device, "name", json_object_new_string(tic_name));
json_object_object_add(device, "sw_version", json_object_new_string("0.0.1"));
for (desc = &ha_config_descs[0]; desc->tag != NULL; desc++) {
char topic[TOPIC_MAXLEN+1];
char vtopic[TOPIC_MAXLEN+1];
char uid[TOPIC_MAXLEN+1];
char name[TOPIC_MAXLEN+1];
snprintf(topic, TOPIC_MAXLEN, "homeassistant/sensor/%s/%s/config", tic_name, desc->tag);
snprintf(vtopic, TOPIC_MAXLEN, "tic2mqtt/%s/%s", tic_name, desc->tag);
snprintf(name, TOPIC_MAXLEN, "%s %s", tic_name, desc->name);
snprintf(uid, TOPIC_MAXLEN, "tic2mqtt_%s_%s", tic_name, desc->tag);
json_object *obj = json_object_new_object();
json_object_object_add(obj, "device", json_object_get(device));
json_object_object_add(obj, "name", json_object_new_string(name));
json_object_object_add(obj, "unique_id", json_object_new_string(uid));
json_object_object_add(obj, "state_topic", json_object_new_string(vtopic));
if (desc->state_class)
json_object_object_add(obj, "state_class", json_object_new_string(desc->state_class));
if (desc->device_class)
json_object_object_add(obj, "device_class", json_object_new_string(desc->device_class));
if (desc->unit)
json_object_object_add(obj, "unit_of_measurement", json_object_new_string(desc->unit));
if (desc->value_template)
json_object_object_add(obj, "value_template", json_object_new_string(desc->value_template));
if (desc->last_reset) {
json_object_object_add(obj, "last_reset_topic", json_object_new_string(vtopic));
json_object_object_add(obj, "last_reset_value_template", json_object_new_string("1970-01-01T00:00:00+00:00"));
}
const char *payload = json_object_to_json_string(obj);
unescape_str(payload);
log_info("%s\n%s\n", topic, payload);
if (mosq_tic) {
int res = mqtt_publish(mosq_tic, topic, NULL, payload, TIC_QOS);
if (res != 0)
log_error("Cannot publish topic %s: %s\n", topic, mqtt_strerror(res));
}
json_object_put(obj);
}
json_object_put(device);
}

8
homeassistant.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef __HOME_ASSISTANT_H__
#define __HOME_ASSISTANT_H__
#include "mosquitto.h"
extern void ha_config_init(const char *tic_name, struct mosquitto *mosq_tic);
#endif

32
logger.c Normal file
View File

@ -0,0 +1,32 @@
#include "logger.h"
#include <stdio.h>
#include <stdarg.h>
static char* log_level_str[] = {
[LOG_DEBUG] = "DEBUG",
[LOG_INFO] = "INFO",
[LOG_WARNING] = "WARNING",
[LOG_ERROR] = "ERROR",
};
static int log_level;
void set_loglevel(int level)
{
log_level = level;
}
void logger(int priority, const char *fmt, ...)
{
if (priority < log_level)
return;
printf("%s: ", log_level_str[priority]);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}

17
logger.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __LOGGER_H__
#define __LOGGER_H__
#define LOG_DEBUG 1
#define LOG_INFO 2
#define LOG_WARNING 3
#define LOG_ERROR 4
#define log_debug(...) logger(LOG_DEBUG, __VA_ARGS__)
#define log_info(...) logger(LOG_INFO, __VA_ARGS__)
#define log_warning(...) logger(LOG_WARNING, __VA_ARGS__)
#define log_error(...) logger(LOG_ERROR, __VA_ARGS__)
extern void set_loglevel(int level);
extern void logger(int priority, const char *fmt, ...);
#endif

91
mqtt.c Normal file
View File

@ -0,0 +1,91 @@
#include "mqtt.h"
#include "logger.h"
#include <mosquitto.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
static void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
{
switch (level) {
case MOSQ_LOG_WARNING: log_warning("%s", str); break;
case MOSQ_LOG_ERR: log_error("%s", str); break;
}
}
struct mosquitto *mqtt_open(const char *host, int port, int keepalive)
{
struct mosquitto *mosq;
int res;
mosquitto_lib_init();
/* Create MQTT client. */
mosq = mosquitto_new(NULL, 1, NULL);
if (mosq == NULL) {
log_error("Cannot create mosquitto client: %s", strerror(errno));
return NULL;
}
mosquitto_log_callback_set(mosq, mosq_log_callback);
/* Connect to broker. */
res = mosquitto_connect(mosq, host, port, keepalive);
if (res != MOSQ_ERR_SUCCESS) {
log_error("Unable to connect to MQTT broker %s:%d: %s", host, port, mosquitto_strerror(res));
return NULL;
}
/* Start network loop. */
res = mosquitto_loop_start(mosq);
if (res != MOSQ_ERR_SUCCESS) {
log_error("Unable to start loop: %s", mosquitto_strerror(res));
return NULL;
}
return mosq;
}
void mqtt_close(struct mosquitto *mosq)
{
mosquitto_loop_stop(mosq, 1);
mosquitto_disconnect(mosq);
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
}
int mqtt_publish(struct mosquitto *mosq, const char *topic_prefix, const char *topic_suffix, const void *payload, int qos)
{
char topic[TOPIC_MAXLEN + 1];
int res;
if (topic_prefix != NULL) {
if (topic_suffix != NULL) {
sprintf(topic, "%s%s", topic_prefix, topic_suffix);
} else {
sprintf(topic, "%s", topic_prefix);
}
} else {
if (topic_suffix != NULL) {
sprintf(topic, "%s", topic_suffix);
} else {
return -1;
}
}
res = mosquitto_publish(mosq, NULL, topic, strlen(payload), payload, qos, 1);
if (res != 0)
log_error("Cannot publish topic %s: %s\n", topic, mosquitto_strerror(res));
return res;
}
const char *mqtt_strerror(int err)
{
return mosquitto_strerror(err);
}

13
mqtt.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef __MQTT_H__
#define __MQTT_H__
#define TOPIC_MAXLEN 255
#define TIC_QOS 0
extern struct mosquitto *mqtt_open(const char *host, int port, int keepalive);
extern void mqtt_close(struct mosquitto *mosq);
extern int mqtt_publish(struct mosquitto *mosq, const char *topic_prefix, const char *topic_suffix, const void *payload, int qos);
extern const char *mqtt_strerror(int err);
#endif

326
tic.c Normal file
View File

@ -0,0 +1,326 @@
#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);
}
}

19
tic.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef __TIC_H__
#define __TIC_H__
#include <time.h>
#define TIC_MODE_HIST 0
#define TIC_MODE_STD 1
typedef struct tic_info_s tic_info_t;
typedef void (tic_cb_data_f)(const char *label, const char *data, time_t date);
extern tic_info_t* tic_init(const char *fname);
extern void tic_set_cb_data(tic_info_t *tic, tic_cb_data_f *func);
extern int tic_mode(tic_info_t *tic);
extern int tic_read_frame(tic_info_t *tic);
extern void tic_process_frame(tic_info_t *tic);
extern void tic_exit(tic_info_t *tic);
#endif

227
tic2mqtt.c Normal file
View File

@ -0,0 +1,227 @@
#include "logger.h"
#include "tic.h"
#include "mqtt.h"
#include "homeassistant.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define TIC2MQTT_VERSION "1.0.1"
#define DEFAULT_TTY "/dev/ttyUSB0"
#define DEFAULT_PORT 1883
#define DEFAULT_KEEPALIVE 60
#define DEFAULT_TIC_NAME "Linky"
static tic_info_t *tic_info = NULL;
struct tag_desc {
const char *tag; // Name of tag.
const int len; // Length of data.
time_t stamp; // last time received
char *data; // Last data received.
};
static struct tag_desc tag_descs[] =
{
{ "ADCO", 12 },
{ "OPTARIF", 4 },
{ "ISOUSC", 2 },
{ "BASE", 9 },
{ "HCHC", 9 },
{ "HCHP", 9 },
{ "EJPHN", 9 },
{ "EJPHPM", 9 },
{ "BBRHCJB", 9 },
{ "BBRHPJB", 9 },
{ "BBRHCJW", 9 },
{ "BBRHPJW", 9 },
{ "BBRHCJR", 9 },
{ "BBRHPJR", 9 },
{ "PEJP", 2 },
{ "PTEC", 4 },
{ "DEMAIN", 4 },
{ "IINST", 3 },
{ "ADPS", 3 },
{ "IMAX", 3 },
{ "PAPP", 5 },
{ "HHPHC", 1 },
{ "MOTDETAT", 6 },
{ NULL, 0 } /* End of table marker. */
};
static struct mosquitto *mosq_tic = NULL;
static int log_level = LOG_WARNING;
static int ha_config = 0;
char *tic_name = DEFAULT_TIC_NAME;
static void tic2mqtt_process_group(const char *tag, const char *data, time_t date)
{
struct tag_desc *ptag_desc;
for (ptag_desc = tag_descs; ptag_desc->tag != NULL; ptag_desc++) {
if (strcmp(ptag_desc->tag, tag) == 0) {
if (ptag_desc->data == NULL) {
ptag_desc->data = calloc(1, ptag_desc->len + 1);
if (ptag_desc->data == NULL) {
log_error("Cannot alloc data for tag %s: %s\n", tag, strerror(errno));
}
}
time_t stamp = time(NULL);
double elapsed = difftime(stamp, ptag_desc->stamp);
if (elapsed >= 60 || strcmp(ptag_desc->data, data)) {
ptag_desc->stamp = stamp;
strncpy(ptag_desc->data, data, ptag_desc->len);
char topic[TOPIC_MAXLEN + 1];
snprintf(topic, TOPIC_MAXLEN, "tic2mqtt/%s/%s", tic_name, tag);
log_info("%ld %s %s\n", stamp, topic, data);
if (mosq_tic) {
int res = mqtt_publish(mosq_tic, topic, NULL, data, TIC_QOS);
if (res != 0)
log_error("Cannot publish topic %s: %s\n", topic, mqtt_strerror(res));
}
}
return; // No more processing.
}
}
}
static void sighandler(int signum)
{
log_info("Catch signal #%d (%s)\n", signum, strsignal(signum));
exit(EXIT_SUCCESS);
}
static void cleanup(void)
{
tic_exit(tic_info);
if (mosq_tic)
mqtt_close(mosq_tic);
}
static void usage(const char *progname)
{
fprintf(stderr, "Usage: %s [-Hv] [-a] [-t tty] [-n name] [-h host] [-p port] [-k keepalive]\n", progname);
}
static void set_progname(char *argv0)
{
char *p;
if ((p = strrchr(argv0, '/')) != NULL)
strcpy(argv0, p + 1); // argv[0] contains a slash.
}
int main(int argc, char *argv[])
{
int opt;
const char *tty = DEFAULT_TTY;
const char *host = NULL;
int port = DEFAULT_PORT;
int keepalive = DEFAULT_KEEPALIVE;
set_progname(argv[0]);
/* Decode options. */
opterr = 1;
while ((opt = getopt(argc, argv, "vdat:n:h:p:k:H")) != -1) {
switch (opt) {
case 'v':
log_level = LOG_INFO;
break;
case 'd':
log_level = LOG_DEBUG;
break;
case 'a':
ha_config = 1;
break;
case 't':
tty = optarg;
break;
case 'n':
tic_name = optarg;
break;
case 'h':
host = optarg;
break;
case 'p':
port = atoi(optarg);
break;
case 'k':
keepalive = atoi(optarg);
break;
case 'H':
printf("version " TIC2MQTT_VERSION "\n");
usage(argv[0]);
exit(EXIT_SUCCESS);
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
break;
}
}
atexit(cleanup);
signal(SIGINT, sighandler);
signal(SIGQUIT, sighandler);
signal(SIGTERM, sighandler);
signal(SIGHUP, sighandler);
if (host) {
mosq_tic = mqtt_open(host, port, keepalive);
if (mosq_tic == NULL)
return EXIT_FAILURE;
}
tic_info = tic_init(tty);
if (tic_info == NULL)
return EXIT_FAILURE;
if (ha_config)
ha_config_init(tic_name, mosq_tic);
tic_set_cb_data(tic_info, tic2mqtt_process_group);
for (;;) {
if (tic_read_frame(tic_info) < 0)
return EXIT_FAILURE;
tic_process_frame(tic_info);
}
return EXIT_SUCCESS;
}