Can read and decode TIC data (badly tested)

This commit is contained in:
Théophile Bastian 2023-09-23 21:49:18 +02:00
parent e9ffb4a032
commit 6c84324fc4
17 changed files with 758 additions and 2 deletions

195
TIC.raw.sample Normal file
View file

@ -0,0 +1,195 @@
SE 000051519
PTEC TH.. $
IINS
ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051521 Y
PTEC TH.. $
IINST 001 X
IMAX 090 H
PAPP 00250 (
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051521 Y
PTEC TH.. $
IINST 001 X
IMAX 090 H
PAPP 00250 (
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051521 Y
PTEC TH.. $
IINST 001 X
IMAX 090 H
PAPP 00250 (
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051521 Y
PTEC TH.. $
IINST 005 \
IMAX 090 H
PAPP 01250 )
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051521 Y
PTEC TH.. $
IINST 005 \
IMAX 090 H
PAPP 01160 )
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051521 Y
PTEC TH.. $
IINST 004 [
IMAX 090 H
PAPP 00980 2
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051522 Z
PTEC TH.. $
IINST 004 [
IMAX 090 H
PAPP 01020 $
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051522 Z
PTEC TH.. $
IINST 004 [
IMAX 090 H
PAPP 01020 $
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051522 Z
PTEC TH.. $
IINST 004 [
IMAX 090 H
PAPP 01010 #
HHPHC A ,
MOTDETAT 000000 B

ADCO 042375023610 8
OPTARIF BASE 0
ISOUSC 30 9
BASE 000051522 Z
PTEC TH.. $

View file

@ -1,2 +1,2 @@
idf_component_register(SRCS "main.c" idf_component_register(SRCS "tic.c" "uart.c" "main.c"
INCLUDE_DIRS ".") INCLUDE_DIRS ".")

44
main/Kconfig.projbuild Normal file
View file

@ -0,0 +1,44 @@
menu "Enedis TIC configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config TIC_UART_PORT_NUM
int "UART port number"
range 0 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3
default 1 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3
range 0 1
default 1
help
UART communication port number towards Enedis' TIC
config TIC_UART_BAUD_RATE
int "UART communication speed"
range 1200 115200
default 1200
help
UART communication speed for TIC. 1200 for historique, 9600 for
standard
config TIC_UART_RXD
int "UART RXD pin number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
default 5
help
GPIO number for UART RX pin. See UART documentation for more information
about available pin numbers for UART.
# config TIC_UART_TXD
# int "UART TXD pin number"
# range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
# default 4
# help
# GPIO number for UART TX pin. See UART documentation for more information
# about available pin numbers for UART.
config TIC_UART_TASK_STACK_SIZE
int "UART task stack size"
range 1024 16384
default 2048
help
Defines stack size for UART echo example. Insufficient stack size can cause crash.
endmenu

View file

@ -1,6 +1,14 @@
#include <stdio.h> #include <stdio.h>
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "uart.h"
#include "esp_log.h"
static const char *TAG = "MAIN";
void app_main(void) void app_main(void)
{ {
ESP_LOGI(TAG, "Arriving in main\n");
xTaskCreate(uart_listen_task, "uart_listen_task", TIC_TASK_STACK_SIZE, NULL, 10, NULL);
} }

174
main/tic.c Normal file
View file

@ -0,0 +1,174 @@
#include <string.h>
#include <stdlib.h>
#include "tic.h"
static const char FRAME_START = 0x02;
static const char GROUP_START = 0x0a;
static const char FIELD_SEP = 0x20;
DecodeState* create_decode_state(size_t queue_size) {
DecodeState* out = malloc(sizeof(DecodeState));
out->cur_frame = NULL;
out->frame_queue = xQueueCreate(queue_size, sizeof(InfoFrame*));
out->checksum = 0;
out->fill_step = GFS_NotStarted;
out->postpone_buffer[0] = '\0';
out->postpone_buffer_size = 0;
return out;
}
void free_decode_state(DecodeState* st) {
vQueueDelete(st->frame_queue);
if(st->cur_frame != NULL) {
tic_delete_frame(st->cur_frame);
free(st->cur_frame);
}
free(st);
}
void tic_delete_group(InfoGroup* group) {
free(group->time_str);
if(group->data_type == GROUP_DATA_STR)
free(group->data.str);
}
void tic_delete_frame(InfoFrame* frame) {
for(size_t g_id=0; g_id < frame->group_count; ++g_id)
tic_delete_group(frame->groups + g_id);
}
char* tic_discard_until_frame(char* buffer, size_t buf_size) {
size_t pos=0;
for(pos=0; pos < buf_size; ++pos) {
if(buffer[pos] == FRAME_START) {
break;
}
}
if(pos == buf_size)
return NULL;
return buffer + pos;
}
const uint32_t BAD_STR_TO_INT = ~0U;
uint32_t str_to_int(char* buffer, const char* buffer_end) {
uint64_t res = 0;
for(char* chr=buffer; chr != buffer_end; ++chr) {
if('0' <= *chr && *chr <= '9') {
res *= 10;
res += (*chr - '0');
} else {
return BAD_STR_TO_INT;
}
}
if(res > BAD_STR_TO_INT)
return BAD_STR_TO_INT;
return res;
}
static void get_data_pointers(char** start, char** end, const char* buffer, char* pos, DecodeState* state) {
if(*start == NULL) { // Postponed data
*start = state->postpone_buffer;
size_t copy_size = pos - buffer;
if(copy_size + state->postpone_buffer_size > POSTPONE_BUFFER_MAXSIZE)
copy_size = POSTPONE_BUFFER_MAXSIZE - state->postpone_buffer_size;
char* postpone_end = state->postpone_buffer
+ state->postpone_buffer_size;
memcpy(
postpone_end,
buffer,
copy_size);
*end = postpone_end + copy_size;
} else { // No postponed data
*end = pos;
}
}
int tic_decode(char* buffer, size_t buf_size, DecodeState* state) {
int frames_decoded = 0;
char* start = NULL;
char* data_end = NULL;
for(char* pos = buffer; pos != buffer + buf_size; ++pos) {
if(state->fill_step == GFS_Checksum) { // End of frame -- verify checksum
if(((state->checksum & 0x3F) + 0x20) != *pos) {
state->fill_step = GFS_Invalid;
continue;
}
state->fill_step = GFS_Done;
continue;
}
state->checksum += *pos; // Update checksum
if(*pos == FRAME_START) {
if(state->cur_frame != NULL) {
frames_decoded += 1;
if(uxQueueSpacesAvailable(state->frame_queue) == 0) {
tic_delete_frame(state->cur_frame);
free(state->cur_frame);
}
else
xQueueSend(state->frame_queue, state->cur_frame, 0);
}
state->cur_frame = malloc(sizeof(InfoFrame));
memset(state->cur_frame, 0, sizeof(InfoFrame));
state->cur_frame->group_count = 0;
}
else if(*pos == GROUP_START) {
// Reset checksum
state->checksum = 0;
if(state->fill_step == GFS_Done) {
state->cur_frame->group_count++;
state->fill_step = GFS_NotStarted;
} else if(state->fill_step == GFS_Invalid) {
memset(state->cur_frame->groups + state->cur_frame->group_count,
'0', sizeof(InfoGroup));
state->fill_step = GFS_NotStarted;
} else if(state->fill_step == GFS_NotStarted) {
state->fill_step = GFS_Tag;
start = pos + 1;
} else {
state->fill_step = GFS_Invalid;
}
}
else if(*pos == FIELD_SEP) {
InfoGroup* cur_group =
state->cur_frame->groups + state->cur_frame->group_count;
if(state->fill_step == GFS_Tag) {
get_data_pointers(&start, &data_end, buffer, pos, state);
size_t label_size = data_end - start + 1;
if(label_size > INFO_GROUP_LABEL_MAX_SIZE)
label_size = INFO_GROUP_LABEL_MAX_SIZE;
memcpy(cur_group->label, start, label_size-1);
cur_group->label[label_size-1] = '\0';
state->fill_step = GFS_Data;
start = pos + 1;
}
else if(state->fill_step == GFS_Data) {
get_data_pointers(&start, &data_end, buffer, pos, state);
uint32_t int_val = str_to_int(start, data_end);
if(int_val == BAD_STR_TO_INT) {
size_t len = data_end - start + 1;
cur_group->data_type = GROUP_DATA_STR;
cur_group->data.str = malloc(sizeof(char) * len);
memcpy(cur_group->data.str, start, len-1);
cur_group->data.str[len-1] = '\0';
} else {
cur_group->data_type = GROUP_DATA_NUM;
cur_group->data.num = int_val;
}
state->fill_step = GFS_Checksum;
state->checksum -= *pos;
}
}
}
if((state->fill_step == GFS_Tag || state->fill_step == GFS_Data) && start != NULL) {
size_t postpone_size = (buffer + buf_size) - start;
if(postpone_size > POSTPONE_BUFFER_MAXSIZE)
postpone_size = POSTPONE_BUFFER_MAXSIZE;
memcpy(state->postpone_buffer, start, postpone_size);
state->postpone_buffer_size = postpone_size;
}
return frames_decoded;
}

75
main/tic.h Normal file
View file

@ -0,0 +1,75 @@
#pragma once
#include <stdint.h>
#ifndef CPU_TEST
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#else
#include "cpu_test_harness.h"
#endif
#define INFO_GROUP_LABEL_MAX_SIZE 12lu
typedef struct {
char label[INFO_GROUP_LABEL_MAX_SIZE];
char* time_str;
union {
uint32_t num;
char* str;
} data;
enum {
GROUP_DATA_NUM,
GROUP_DATA_STR
} data_type;
} InfoGroup;
#define INFO_FRAME_MAX_GROUPS 12lu
typedef struct {
InfoGroup groups[INFO_FRAME_MAX_GROUPS];
uint8_t group_count;
} InfoFrame;
typedef QueueHandle_t InfoFrameQueue;
typedef enum {
GFS_NotStarted,
GFS_Tag,
GFS_Data,
GFS_Checksum,
GFS_Done,
GFS_Invalid
} GroupFillStep;
#define POSTPONE_BUFFER_MAXSIZE 32lu
typedef struct {
InfoFrameQueue frame_queue;
InfoFrame* cur_frame;
char checksum;
GroupFillStep fill_step;
char postpone_buffer[POSTPONE_BUFFER_MAXSIZE];
size_t postpone_buffer_size;
} DecodeState;
/// Initialize a decode state
DecodeState* create_decode_state(size_t queue_size);
/// Frees a DecodeState
void free_decode_state(DecodeState*);
/// Deletes the contents of an InfoGroup
void tic_delete_group(InfoGroup* group);
/** Deletes the contents of an InfoFrame. The call to free() itself must be
* done by the user */
void tic_delete_frame(InfoFrame* frame);
/**
* Discard characters until just before a frame is started, and return a
* pointer to the beginning of the next frame (including the start of frame
* marker), or NULL if no frame was started.
*/
char* tic_discard_until_frame(char* buffer, size_t buf_size);
/**
* Decode the buffer, updating the decode state and emitting frames as they are
* finished.
* Returns how many groups were decoded.
*/
int tic_decode(char* buffer, size_t buf_size, DecodeState* state);

102
main/uart.c Normal file
View file

@ -0,0 +1,102 @@
/* UART reception file -- reads data from Linky's TIC */
#include <stdio.h>
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_sleep.h"
#include "esp_log.h"
#include "tic.h"
/**
* This is an example which echos any data it receives on configured UART back to the sender,
* with hardware flow control turned off. It does not use UART driver event queue.
*
* - Port: configured UART
* - Receive (Rx) buffer: on
* - Transmit (Tx) buffer: off
* - Flow control: off
* - Event queue: off
* - Pin assignment: see defines below (See Kconfig)
*/
#define TIC_RXD (CONFIG_TIC_UART_RXD)
#define TIC_RTS (UART_PIN_NO_CHANGE)
#define TIC_CTS (UART_PIN_NO_CHANGE)
#define TIC_UART_PORT_NUM (CONFIG_TIC_UART_PORT_NUM)
#define TIC_UART_BAUD_RATE (CONFIG_TIC_UART_BAUD_RATE)
static const char *TAG = "TIC UART";
#define BUF_SIZE (1024)
#define TIC_QUEUE_SIZE 64
static void uart_light_sleep() {
// TODO
/*
ESP_LOGI(TAG, "Entering sleep.\n");
vTaskDelay(10/portTICK_PERIOD_MS);
ESP_ERROR_CHECK(esp_light_sleep_start());
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Woken up: %d\n", wakeup_cause);
*/
}
void uart_listen_task(void *arg)
{
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = TIC_UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
int intr_alloc_flags = 0;
#if CONFIG_UART_ISR_IN_IRAM
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif
ESP_ERROR_CHECK(uart_param_config(TIC_UART_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(TIC_UART_PORT_NUM, UART_PIN_NO_CHANGE, TIC_RXD, TIC_RTS, TIC_CTS));
ESP_ERROR_CHECK(uart_driver_install(TIC_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
//ESP_ERROR_CHECK(uart_set_wakeup_threshold(TIC_UART_PORT_NUM, 3));
//ESP_ERROR_CHECK(esp_sleep_enable_uart_wakeup(TIC_UART_PORT_NUM));
// Configure a temporary buffer for the incoming data
char *data = (char *) malloc(BUF_SIZE);
// Setup the TIC decoding
DecodeState* tic_state = create_decode_state(TIC_QUEUE_SIZE);
int initialized = 0;
// Discard any data already buffered
ESP_ERROR_CHECK(uart_flush(TIC_UART_PORT_NUM));
ESP_LOGI(TAG, "Start receiving.\n");
while (1) {
int len = uart_read_bytes(TIC_UART_PORT_NUM, data, (BUF_SIZE - 1), 30 / portTICK_PERIOD_MS);
if (len) {
if(!initialized) {
char* init_begin = tic_discard_until_frame(data, len);
if(init_begin != NULL) {
size_t discarded = init_begin - data;
initialized = 1;
tic_decode(init_begin, len - discarded, tic_state);
}
} else {
tic_decode(data, len, tic_state);
}
data[len] = '\0';
ESP_LOGI(TAG, "Recv str: %s", (char *) data);
}
else {
uart_light_sleep();
}
}
free_decode_state(tic_state);
}

6
main/uart.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "sdkconfig.h"
#define TIC_TASK_STACK_SIZE (CONFIG_TIC_UART_TASK_STACK_SIZE)
void uart_listen_task(void* arg);

18
test_cpu/Makefile Normal file
View file

@ -0,0 +1,18 @@
ESP_DIR=../main/
COMMON_OBJS=cpu_test_harness.o
ESP_OBJS=tic.o
CFLAGS=-Wextra -O2 -g -I$(ESP_DIR) -I. -DCPU_TEST
CC=gcc
TARGETS=test_tic.bin
all: $(TARGETS)
test_%.bin: test_%.o $(COMMON_OBJS) $(ESP_OBJS)
$(CC) $(CFLAGS) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: $(ESP_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@

2
test_cpu/README.md Normal file
View file

@ -0,0 +1,2 @@
Harness to test relevant parts of the code on a standard CPU instead of the
ESP32.

View file

@ -0,0 +1,31 @@
#include <stdlib.h>
#include <stdio.h>
#include "cpu_test_harness.h"
#include "tic.h"
QueueHandle_t xQueueCreate(int a, int b) {
return NULL;
}
void vQueueDelete(QueueHandle_t _) {}
int uxQueueSpacesAvailable(QueueHandle_t _) {
return 1;
}
void xQueueSend(QueueHandle_t _, void* elt, int _b) {
InfoFrame* frame = elt;
printf(">> SENT: Frame with %u groups.\n", frame->group_count);
for(size_t grp_id=0; grp_id < frame->group_count; ++grp_id) {
InfoGroup* grp = frame->groups + grp_id;
printf("Group %02lu: %s %s ", grp_id, grp->label,
(grp->data_type == GROUP_DATA_NUM) ? "num" : "str");
if(grp->data_type == GROUP_DATA_NUM)
printf("%u\n", grp->data.num);
else
printf("%s\n", grp->data.str);
}
tic_delete_frame(frame);
free(frame);
}

View file

@ -0,0 +1,11 @@
#pragma once
#ifndef CPU_TEST
#define CPU_TEST
#endif
// ========== FreeRtos queues ==========
typedef void* QueueHandle_t;
QueueHandle_t xQueueCreate(int, int);
int uxQueueSpacesAvailable(QueueHandle_t);
void xQueueSend(QueueHandle_t, void* elt, int);
void vQueueDelete(QueueHandle_t);

BIN
test_cpu/cpu_test_harness.o Normal file

Binary file not shown.

BIN
test_cpu/test_tic.bin Executable file

Binary file not shown.

36
test_cpu/test_tic.c Normal file
View file

@ -0,0 +1,36 @@
#include <stdio.h>
#include <stdlib.h>
#include "tic.h"
#define BUF_SIZE 32
int main(void) {
DecodeState* state = create_decode_state(42);
FILE* tic_handle = fopen("../TIC.raw.sample", "rb");
//int initialized = 0;
char buffer[BUF_SIZE];
int buf_size = 0;
while(1) {
buf_size = fread(buffer, sizeof(char), BUF_SIZE, tic_handle);
fprintf(stderr, "Read %d bytes\n", buf_size);
if(buf_size == 0)
break;
/*
if(!initialized) {
char* init_begin = tic_discard_until_frame(buffer, buf_size);
if(init_begin != NULL) {
size_t discarded = (init_begin - buffer);
initialized = 1;
tic_decode(init_begin, buf_size - discarded, state);
}
} else {
*/
tic_decode(buffer, buf_size, state);
//}
}
free_decode_state(state);
return 0;
}

BIN
test_cpu/tic.o Normal file

Binary file not shown.

54
tester/feed_tic.py Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env python3
import argparse
import typing as t
import time
from pathlib import Path
import serial
def iter_frames(raw_data: bytes) -> t.Iterator[bytes]:
FRAME_START = 0x02
next_frame = b""
# Discard first bytes
raw_data = raw_data[raw_data.find(FRAME_START) :]
for ch in raw_data:
# Discard first bytes
if ch == FRAME_START and next_frame:
yield next_frame
next_frame = b""
next_frame += bytes([ch])
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", default="/dev/ttyUSB1")
parser.add_argument("-b", "--baud", type=int, default=1200)
parser.add_argument(
"--frame-gap", type=int, default=1500, help="Time between two frames (in ms)"
)
parser.add_argument(
"recorded_data",
help="File containing real recorded data to replay",
type=Path,
)
args = parser.parse_args()
data: bytes = b""
with args.recorded_data.open("rb") as h:
data = h.read()
with serial.Serial(port=args.port, baudrate=args.baud) as ser:
for frame in iter_frames(data):
print(f"Feeding {len(frame)}b frame")
start_time = time.monotonic()
ser.write(frame)
elapsed_time = time.monotonic() - start_time
sleep_time = args.frame_gap / 1000 - elapsed_time
if sleep_time > 0:
time.sleep(sleep_time)
if __name__ == "__main__":
main()