Browse Source

include LibTeleinfo + e2e tests

main
Nicolas Massé 4 years ago
parent
commit
0fcff22d67
  1. 4
      .gitmodules
  2. 2
      main/CMakeLists.txt
  3. 24
      main/Kconfig.projbuild
  4. 1
      main/libteleinfo
  5. 30
      main/libteleinfo.cpp
  6. 31
      main/libteleinfo.h
  7. 118
      main/tic.c
  8. 5
      main/tic.h
  9. 1
      test/e2e/.gitignore
  10. 92
      test/e2e/data.go
  11. 5
      test/e2e/go.mod
  12. 12
      test/e2e/go.sum
  13. 91
      test/e2e/main.go

4
.gitmodules

@ -0,0 +1,4 @@
[submodule "LibTeleinfo"]
path = main/libteleinfo
url = https://github.com/nmasse-itix/LibTeleinfo.git
branch = master

2
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 ".") INCLUDE_DIRS ".")

24
main/Kconfig.projbuild

@ -23,4 +23,28 @@ menu "TIC-to-MQTT Configuration"
help help
Defines stack size. Insufficient stack size can cause crash. 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 endmenu

1
main/libteleinfo

@ -0,0 +1 @@
Subproject commit d75082edfc961ade2513bb37e7187383e9caae17

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

31
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 <stdint.h>
// Needed for time_t
#include <time.h>
#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

118
main/tic.c

@ -6,97 +6,47 @@
#include "driver/uart.h" #include "driver/uart.h"
#include "esp_log.h" #include "esp_log.h"
#include "tic.h" #include "tic.h"
#include "libteleinfo.h"
static const char *TIC_LOGGER = "tic"; static const char *TIC_LOGGER = "tic";
static QueueHandle_t uart_queue;
uint8_t tic_checksum(uint8_t* buffer, size_t start, size_t end) { void tic_data_callback(time_t ts, uint8_t flags, char * name, char * value) {
uint16_t checksum = 0; char * prefix = "";
for (size_t i = start; i <= end; i++) {
checksum += buffer[i];
}
checksum = (checksum & 0x3F) + 0x20;
return (uint8_t)checksum;
}
static void tic_uart_events(void *pvParameters) { if (flags & LIBTELEINFO_FLAGS_ADDED)
uart_event_t event; prefix = "NEW ->";
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);
// '\n' + tag + sep + value + sep + checksum = at least 6 characters if (flags & LIBTELEINFO_FLAGS_UPDATED)
if (pos < 6) { prefix = "MAJ ->";
ESP_LOGI(TIC_LOGGER, "read string is too short: %d", pos);
break;
}
// during manual tests, there is no '\n' at the start of the string ESP_LOGI(TIC_LOGGER, "%s %s=%s", prefix, name, value);
// but according to Enedis, there is one in the actual data sent.
if (scratch[0] == '\n') {
scratch++;
pos--;
} }
if (methods[0] >= TIC_CHECKSUM_THRESHOLD || methods[1] >= TIC_CHECKSUM_THRESHOLD) { void tic_adps_callback(uint8_t phase) {
if (methods[0] >= TIC_CHECKSUM_THRESHOLD && tic_checksum(scratch, 0, pos - 3) == scratch[pos - 1]) { ESP_LOGI(TIC_LOGGER, "ALERTE phase=%d", phase);
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;
}
} }
// trim the checksum static void tic_uart_read(void *pvParameters) {
scratch[pos - 2] = '\0'; uint8_t* buffer = (uint8_t*) malloc(CONFIG_TIC_UART_READ_BUFFER_SIZE);
ESP_LOGD(TIC_LOGGER, "validated: '%s'", scratch); if (buffer == NULL) {
ESP_LOGE(TIC_LOGGER, "malloc(CONFIG_TIC_UART_READ_BUFFER_SIZE) failed!");
return;
} }
break;
//Others for (;;) {
default: 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; break;
} }
if (len == 0) {
continue;
} }
ESP_LOGD(TIC_LOGGER, "uart_read_bytes read %d bytes", len);
libteleinfo_process(buffer, len);
} }
free(buffer); free(buffer);
buffer = NULL; buffer = NULL;
vTaskDelete(NULL); vTaskDelete(NULL);
@ -128,22 +78,22 @@ void tic_uart_init() {
}; };
#endif #endif
// Setup the UART // 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_driver_install(CONFIG_TIC_UART_PORT_NUM, CONFIG_TIC_UART_BUFFER_SIZE, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(TIC_UART_NUM, &uart_config)); ESP_ERROR_CHECK(uart_param_config(CONFIG_TIC_UART_PORT_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_set_pin(CONFIG_TIC_UART_PORT_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)); libteleinfo_init(tic_data_callback, tic_adps_callback);
// Create a task to handler UART event from ISR // Create a task to handler UART event from ISR
BaseType_t xReturned; BaseType_t xReturned;
xReturned = xTaskCreate(tic_uart_events, xReturned = xTaskCreate(tic_uart_read,
"tic_uart_events", "tic_uart_read",
CONFIG_TIC_UART_STACK_SIZE, /* Stack size in words, not bytes. */ CONFIG_TIC_UART_STACK_SIZE, /* Stack size in words, not bytes. */
NULL, /* Parameter passed into the task. */ NULL, /* Parameter passed into the task. */
tskIDLE_PRIORITY + 12, tskIDLE_PRIORITY + 12,
NULL); NULL);
if (xReturned != pdPASS) { if (xReturned != pdPASS) {
ESP_LOGE(TIC_LOGGER, "xTaskCreate('tic_uart_events'): %d", xReturned); ESP_LOGE(TIC_LOGGER, "xTaskCreate('tic_uart_read'): %d", xReturned);
abort(); abort();
} }
} }

5
main/tic.h

@ -1,11 +1,6 @@
#ifndef __TIC_H__ #ifndef __TIC_H__
#define __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(); void tic_uart_init();
#endif #endif

1
test/e2e/.gitignore

@ -0,0 +1 @@
e2e

92
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 ,",
},
},
},
},
}

5
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

12
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=

91
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.")
}
Loading…
Cancel
Save