From 0fcff22d6732aca7d5a7d1abb7c418a38eee6177 Mon Sep 17 00:00:00 2001 From: Nicolas MASSE Date: Thu, 3 Feb 2022 18:38:33 +0100 Subject: [PATCH] include LibTeleinfo + e2e tests --- .gitmodules | 4 ++ main/CMakeLists.txt | 2 +- main/Kconfig.projbuild | 24 ++++++++ main/libteleinfo | 1 + main/libteleinfo.cpp | 30 ++++++++++ main/libteleinfo.h | 31 ++++++++++ main/tic.c | 128 +++++++++++++---------------------------- main/tic.h | 5 -- test/e2e/.gitignore | 1 + test/e2e/data.go | 92 +++++++++++++++++++++++++++++ test/e2e/go.mod | 5 ++ test/e2e/go.sum | 12 ++++ test/e2e/main.go | 91 +++++++++++++++++++++++++++++ 13 files changed, 331 insertions(+), 95 deletions(-) create mode 100644 .gitmodules create mode 160000 main/libteleinfo create mode 100644 main/libteleinfo.cpp create mode 100644 main/libteleinfo.h create mode 100644 test/e2e/.gitignore create mode 100644 test/e2e/data.go create mode 100644 test/e2e/go.mod create mode 100644 test/e2e/go.sum create mode 100644 test/e2e/main.go diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3fe2110 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "LibTeleinfo"] + path = main/libteleinfo + url = https://github.com/nmasse-itix/LibTeleinfo.git + branch = master diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 9d242cc..36a3887 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,2 @@ -idf_component_register(SRCS "main.c" "tic.c" +idf_component_register(SRCS "main.c" "tic.c" "libteleinfo.cpp" "libteleinfo/src/LibTeleinfo.cpp" INCLUDE_DIRS ".") diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 6431b97..cdd74fc 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -23,4 +23,28 @@ menu "TIC-to-MQTT Configuration" help Defines stack size. Insufficient stack size can cause crash. + config TIC_UART_BUFFER_SIZE + int "UART internal buffer size" + range 128 16384 + default 256 + help + Defines buffer size. + + config TIC_UART_READ_BUFFER_SIZE + int "UART reading buffer size" + range 128 16384 + default 256 + help + Defines buffer size. + + config TIC_UART_PORT_NUM + int "UART port number" + range 0 2 if IDF_TARGET_ESP32 + range 0 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + default 2 if IDF_TARGET_ESP32 + default 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + help + UART communication port number for the example. + See UART documentation for available port numbers. + endmenu diff --git a/main/libteleinfo b/main/libteleinfo new file mode 160000 index 0000000..d75082e --- /dev/null +++ b/main/libteleinfo @@ -0,0 +1 @@ +Subproject commit d75082edfc961ade2513bb37e7187383e9caae17 diff --git a/main/libteleinfo.cpp b/main/libteleinfo.cpp new file mode 100644 index 0000000..b5858ce --- /dev/null +++ b/main/libteleinfo.cpp @@ -0,0 +1,30 @@ +#include "libteleinfo.h" +#include "libteleinfo/src/LibTeleinfo.h" + +static TInfo tinfo; +static libteleinfo_data_callback data_cb; +static libteleinfo_adps_callback adps_cb; + +void _libteleinfo_data_callback(ValueList * valueslist, uint8_t flags) { + data_cb(valueslist->ts, flags, valueslist->name, valueslist->value); +} + +void _libteleinfo_adps_callback(uint8_t phase) { + adps_cb(phase); +} + +EXTERNC void libteleinfo_init(libteleinfo_data_callback dcb, libteleinfo_adps_callback acb) { + data_cb = dcb; + adps_cb = acb; + + // Initialize the LibTeleinfo + tinfo.init(); + tinfo.attachData(_libteleinfo_data_callback); + tinfo.attachADPS(_libteleinfo_adps_callback); +} + +EXTERNC void libteleinfo_process(uint8_t* buffer, int len) { + for (int i = 0; i < len; i++) { + tinfo.process(buffer[i]); + } +} diff --git a/main/libteleinfo.h b/main/libteleinfo.h new file mode 100644 index 0000000..c43ca56 --- /dev/null +++ b/main/libteleinfo.h @@ -0,0 +1,31 @@ +// This is a C to C++ bridge. It is required to call the LibTeleinfo (C++) from FreeRTOS (C). + +#ifndef __LIBTELEINFO_H__ +#define __LIBTELEINFO_H__ + +// The magic lays here... +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +// Needed for uint8_t +#include +// Needed for time_t +#include + +#define LIBTELEINFO_FLAGS_NONE 0x00 +#define LIBTELEINFO_FLAGS_NOTHING 0x01 +#define LIBTELEINFO_FLAGS_ADDED 0x02 +#define LIBTELEINFO_FLAGS_EXIST 0x04 +#define LIBTELEINFO_FLAGS_UPDATED 0x08 +#define LIBTELEINFO_FLAGS_ALERT 0x80 + +typedef void(*libteleinfo_data_callback)(time_t,uint8_t,char*,char*); +typedef void(*libteleinfo_adps_callback)(uint8_t); + +EXTERNC void libteleinfo_init(libteleinfo_data_callback, libteleinfo_adps_callback); +EXTERNC void libteleinfo_process(uint8_t* buffer, int len); + +#endif diff --git a/main/tic.c b/main/tic.c index 7956e46..f4a22f8 100644 --- a/main/tic.c +++ b/main/tic.c @@ -6,97 +6,47 @@ #include "driver/uart.h" #include "esp_log.h" #include "tic.h" +#include "libteleinfo.h" static const char *TIC_LOGGER = "tic"; -static QueueHandle_t uart_queue; -uint8_t tic_checksum(uint8_t* buffer, size_t start, size_t end) { - uint16_t checksum = 0; - for (size_t i = start; i <= end; i++) { - checksum += buffer[i]; - } - checksum = (checksum & 0x3F) + 0x20; - return (uint8_t)checksum; -} +void tic_data_callback(time_t ts, uint8_t flags, char * name, char * value) { + char * prefix = ""; -static void tic_uart_events(void *pvParameters) { - uart_event_t event; - size_t buffered_size; - uint8_t* buffer = (uint8_t*) malloc(TIC_READ_BUFFER_SIZE); - uint8_t methods[2] = {0, 0}; - for(;;) { - // Waiting for UART event. - if(xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { - switch(event.type) { - case UART_FIFO_OVF: - case UART_BUFFER_FULL: - case UART_PARITY_ERR: - case UART_FRAME_ERR: - ESP_LOGI(TIC_LOGGER, "error: event type %d. Discarding existing data...", event.type); - uart_flush_input(TIC_UART_NUM); - xQueueReset(uart_queue); - break; - case UART_PATTERN_DET: - bzero(buffer, TIC_READ_BUFFER_SIZE); - uart_get_buffered_data_len(TIC_UART_NUM, &buffered_size); - int pos = uart_pattern_pop_pos(TIC_UART_NUM); - if (pos == -1) { - // There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not - // record the position. We should set a larger queue size. - // As an example, we directly flush the rx buffer here. - uart_flush_input(TIC_UART_NUM); - } else { - uint8_t* scratch = buffer; - uart_read_bytes(TIC_UART_NUM, scratch, pos + 1, 100 / portTICK_PERIOD_MS); - scratch[pos] = '\0'; - ESP_LOGD(TIC_LOGGER, "read data: '%s', separator at %d", scratch, pos); + if (flags & LIBTELEINFO_FLAGS_ADDED) + prefix = "NEW ->"; - // '\n' + tag + sep + value + sep + checksum = at least 6 characters - if (pos < 6) { - ESP_LOGI(TIC_LOGGER, "read string is too short: %d", pos); - break; - } + if (flags & LIBTELEINFO_FLAGS_UPDATED) + prefix = "MAJ ->"; - // during manual tests, there is no '\n' at the start of the string - // but according to Enedis, there is one in the actual data sent. - if (scratch[0] == '\n') { - scratch++; - pos--; - } + ESP_LOGI(TIC_LOGGER, "%s %s=%s", prefix, name, value); +} + +void tic_adps_callback(uint8_t phase) { + ESP_LOGI(TIC_LOGGER, "ALERTE phase=%d", phase); +} - if (methods[0] >= TIC_CHECKSUM_THRESHOLD || methods[1] >= TIC_CHECKSUM_THRESHOLD) { - if (methods[0] >= TIC_CHECKSUM_THRESHOLD && tic_checksum(scratch, 0, pos - 3) == scratch[pos - 1]) { - ESP_LOGD(TIC_LOGGER, "validated with method1: %s", scratch); - } else if (methods[1] >= TIC_CHECKSUM_THRESHOLD && tic_checksum(scratch, 0, pos - 2) == scratch[pos - 1]) { - ESP_LOGD(TIC_LOGGER, "validated with method2: %s", scratch); - } else { - ESP_LOGI(TIC_LOGGER, "wrong checksum: %s", scratch); - break; - } - } else { - if (tic_checksum(scratch, 0, pos - 3) == scratch[pos - 1]) { - methods[0]++; - ESP_LOGD(TIC_LOGGER, "validated with method 1 while learning: %s", scratch); - } else if (tic_checksum(scratch, 0, pos - 2) == scratch[pos - 1]) { - methods[1]++; - ESP_LOGD(TIC_LOGGER, "validated with method 2 while learning: %s", scratch); - } else { - ESP_LOGI(TIC_LOGGER, "wrong checksum: %s", scratch); - break; - } - } +static void tic_uart_read(void *pvParameters) { + uint8_t* buffer = (uint8_t*) malloc(CONFIG_TIC_UART_READ_BUFFER_SIZE); + if (buffer == NULL) { + ESP_LOGE(TIC_LOGGER, "malloc(CONFIG_TIC_UART_READ_BUFFER_SIZE) failed!"); + return; + } - // trim the checksum - scratch[pos - 2] = '\0'; - ESP_LOGD(TIC_LOGGER, "validated: '%s'", scratch); - } - break; - //Others - default: - break; - } + for (;;) { + int len = uart_read_bytes(CONFIG_TIC_UART_PORT_NUM, buffer, CONFIG_TIC_UART_READ_BUFFER_SIZE, 20 / portTICK_RATE_MS); + if (len == -1) { + ESP_LOGE(TIC_LOGGER, "uart_read_bytes failed!"); + break; + } + if (len == 0) { + continue; } + ESP_LOGD(TIC_LOGGER, "uart_read_bytes read %d bytes", len); + + libteleinfo_process(buffer, len); } + free(buffer); buffer = NULL; vTaskDelete(NULL); @@ -128,22 +78,22 @@ void tic_uart_init() { }; #endif // Setup the UART - ESP_ERROR_CHECK(uart_driver_install(TIC_UART_NUM, TIC_BUFFER_SIZE, TIC_BUFFER_SIZE, 20, &uart_queue, 0)); - ESP_ERROR_CHECK(uart_param_config(TIC_UART_NUM, &uart_config)); - ESP_ERROR_CHECK(uart_set_pin(TIC_UART_NUM, UART_PIN_NO_CHANGE, CONFIG_TIC_UART_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - ESP_ERROR_CHECK(uart_enable_pattern_det_baud_intr(TIC_UART_NUM, '\r', 1, 9, 0, 0)); - ESP_ERROR_CHECK(uart_pattern_queue_reset(TIC_UART_NUM, 20)); + ESP_ERROR_CHECK(uart_driver_install(CONFIG_TIC_UART_PORT_NUM, CONFIG_TIC_UART_BUFFER_SIZE, 0, 0, NULL, 0)); + ESP_ERROR_CHECK(uart_param_config(CONFIG_TIC_UART_PORT_NUM, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(CONFIG_TIC_UART_PORT_NUM, UART_PIN_NO_CHANGE, CONFIG_TIC_UART_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + libteleinfo_init(tic_data_callback, tic_adps_callback); // Create a task to handler UART event from ISR BaseType_t xReturned; - xReturned = xTaskCreate(tic_uart_events, - "tic_uart_events", + xReturned = xTaskCreate(tic_uart_read, + "tic_uart_read", CONFIG_TIC_UART_STACK_SIZE, /* Stack size in words, not bytes. */ NULL, /* Parameter passed into the task. */ tskIDLE_PRIORITY + 12, NULL); if (xReturned != pdPASS) { - ESP_LOGE(TIC_LOGGER, "xTaskCreate('tic_uart_events'): %d", xReturned); + ESP_LOGE(TIC_LOGGER, "xTaskCreate('tic_uart_read'): %d", xReturned); abort(); } } \ No newline at end of file diff --git a/main/tic.h b/main/tic.h index 581f7e6..984779d 100644 --- a/main/tic.h +++ b/main/tic.h @@ -1,11 +1,6 @@ #ifndef __TIC_H__ #define __TIC_H__ -#define TIC_BUFFER_SIZE (2048) -#define TIC_READ_BUFFER_SIZE (TIC_BUFFER_SIZE / 2) -#define TIC_CHECKSUM_THRESHOLD (10) -#define TIC_UART_NUM UART_NUM_2 - void tic_uart_init(); #endif diff --git a/test/e2e/.gitignore b/test/e2e/.gitignore new file mode 100644 index 0000000..c7ca8d0 --- /dev/null +++ b/test/e2e/.gitignore @@ -0,0 +1 @@ +e2e diff --git a/test/e2e/data.go b/test/e2e/data.go new file mode 100644 index 0000000..24d1eda --- /dev/null +++ b/test/e2e/data.go @@ -0,0 +1,92 @@ +package main + +type TicMode int64 + +const ( + TIC_MODE_HISTORIQUE TicMode = iota + TIC_MODE_STANDARD +) + +type MQTTResult struct { + // TODO +} + +type TestStep struct { + Sent []string + Expected []MQTTResult +} + +type TestCase struct { + Name string + Mode TicMode + Steps []TestStep +} + +var testCases []TestCase = []TestCase{ + { + Name: "historique_simple", + Mode: TIC_MODE_HISTORIQUE, + Steps: []TestStep{ + { + Sent: []string{ + "MOTDETAT 000000 B", + "PPOT 00 #", + "OPTARIF HC.. <", + "ISOUSC 25 =", + "HCHC 015558379 1", + "HCHP 011651340 (", + "PTEC HP.. ", + "IINST1 001 I", + "IINST2 001 J", + "IINST3 000 J", + "IMAX1 060 6", + "IMAX2 060 7", + "IMAX3 060 8", + "PMAX 08611 6", + "PAPP 00540 *", + "HHPHC A ,", + }, + }, + { + Sent: []string{ + "MOTDETAT 000000 B", + "PPOT 00 #", + "OPTARIF HC.. <", + "ISOUSC 25 =", + "HCHC 015558379 1", + "HCHP 011651341 )", + "PTEC HP.. ", + "IINST1 001 I", + "IINST2 009 R", + "IINST3 000 J", + "IMAX1 060 6", + "IMAX2 060 7", + "IMAX3 060 8", + "PMAX 08611 6", + "PAPP 02420 )", + "HHPHC A ,", + }, + }, + { + Sent: []string{ + "MOTDETAT 000000 B", + "PPOT 00 #", + "OPTARIF HC.. <", + "ISOUSC 25 =", + "HCHC 015558379 1", + "HCHP 011651343 +", + "PTEC HP.. ", + "IINST1 001 I", + "IINST2 006 O", + "IINST3 000 J", + "IMAX1 060 6", + "IMAX2 060 7", + "IMAX3 060 8", + "PMAX 08611 6", + "PAPP 01690 1", + "HHPHC A ,", + }, + }, + }, + }, +} diff --git a/test/e2e/go.mod b/test/e2e/go.mod new file mode 100644 index 0000000..f11f3fc --- /dev/null +++ b/test/e2e/go.mod @@ -0,0 +1,5 @@ +module github.com/nmasse-itix/tic-to-mqtt/test/e2e + +go 1.16 + +require go.bug.st/serial v1.3.4 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum new file mode 100644 index 0000000..0793003 --- /dev/null +++ b/test/e2e/go.sum @@ -0,0 +1,12 @@ +github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.bug.st/serial v1.3.4 h1:fMpfNEOsPQjYGZ3VHcs/xxsxoaPgbcjrm4YnMkcir3Y= +go.bug.st/serial v1.3.4/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/e2e/main.go b/test/e2e/main.go new file mode 100644 index 0000000..557e3c2 --- /dev/null +++ b/test/e2e/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "os" + "sort" + "time" + + "go.bug.st/serial" +) + +func main() { + var portName string + + flag.StringVar(&portName, "p", "/dev/ttyUSB1", "Serial port to use") + flag.Parse() + + wanted := flag.Args() + if len(wanted) == 0 { + fmt.Println("Usage: e2e [-t /dev/ttyUSBX ] test_case1 test_case2 ...") + fmt.Println() + fmt.Println("Available test cases:") + for _, testCase := range testCases { + fmt.Printf("- %s\n", testCase.Name) + } + os.Exit(1) + } + sort.Strings(wanted) + for _, testCase := range testCases { + i := sort.SearchStrings(wanted, testCase.Name) + if i >= len(wanted) || wanted[i] != testCase.Name { + continue + } + + fmt.Printf("Running test case %s...\n", testCase.Name) + + var mode serial.Mode + var modeName string + if testCase.Mode == TIC_MODE_HISTORIQUE { + mode = serial.Mode{ + BaudRate: 1200, + DataBits: 7, + Parity: serial.EvenParity, + StopBits: serial.OneStopBit, + } + modeName = "historique" + } else if testCase.Mode == TIC_MODE_STANDARD { + mode = serial.Mode{ + BaudRate: 9600, + DataBits: 7, + Parity: serial.EvenParity, + StopBits: serial.OneStopBit, + } + modeName = "standard" + } else { + panic("Unknown mode") + } + + fmt.Printf("Opening port %s with mode %s...\n", portName, modeName) + + port, err := serial.Open(portName, &mode) + if err != nil { + panic(err) + } + defer port.Close() + + for i, step := range testCase.Steps { + fmt.Printf("Sending trame %d...\n", i) + var b bytes.Buffer + b.WriteByte(0x02) + for _, info := range step.Sent { + b.WriteString(fmt.Sprintf("\n%s\r", info)) + } + b.WriteByte(0x03) + buffer := b.Bytes() + n, err := port.Write(buffer) + if err != nil { + panic(err) + } + if n != len(buffer) { + panic("Cannot send bytes to serial port!") + } + // Can be any value between 16.7 and 33.4 ms + time.Sleep(33 * time.Millisecond) + } + } + + fmt.Println("Done.") +}