]> vcs.slashdirt.org Git - sw/tic2json.git/commitdiff

vcs.slashdirt.org Git - sw/tic2json.git/commitdiff

git git / sw / tic2json.git / commitdiff
? search:
summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 0906732)
Add lexer/parser for v01
2021年8月21日 11:53:42 +0000 (13:53 +0200)
2021年8月21日 14:20:24 +0000 (16:20 +0200)
ticv01.l [new file with mode: 0644] patch | blob
ticv01.y [new file with mode: 0644] patch | blob

diff --git a/tic2json.c b/tic2json.c
index 1a8a1da289babd27ce9b69cd1bdc46e70880991f..779048f4795b23d16c1c9e562a85a6a0974a41e2 100644 (file)
--- a/tic2json.c
+++ b/tic2json.c
@@ -59,6 +59,7 @@ static const char * tic_units[] = {
[U_KVA] = "kVA",
[U_VA] = "VA",
[U_W] = "W",
+ [U_MIN] = "mn",
};
int ticv02yylex_destroy();
diff --git a/tic2json.h b/tic2json.h
index a45ff15e04c4e2dac98e7c12f8ed621d3cd3bc4e..6f37e3755935a4bd85bda515a607632eab5003de 100644 (file)
--- a/tic2json.h
+++ b/tic2json.h
@@ -30,6 +30,7 @@ enum tic_unit {
U_KVA,
U_VA,
U_W,
+ U_MIN,
};
// this is to be packed in the upper 4 bits of a byte: must increment by 0x10
diff --git a/ticv01.l b/ticv01.l
new file mode 100644 (file)
index 0000000..d27235c
--- /dev/null
+++ b/ticv01.l
@@ -0,0 +1,142 @@
+/*
+// ticv01.l
+// A lexer for ENEDIS TIC version 01 protocol
+//
+// (C) 2021 Thibaut VARENE
+// License: GPLv2 - http://www.gnu.org/licenses/gpl-2.0.html
+*/
+
+/**
+ * @file
+ * Complete lexer for ENEDIS' TIC protocol.
+ * Supports version 01, a.k.a "historique" as found on ENEDIS' LINKY smart meter.
+ * Ref doc: https://www.enedis.fr/media/2027/download
+ *
+ * "le champ « étiquette » ne contient aucun caractère ayant une valeur égale à celle
+ * du caractère-séparateur utilisé pour la trame (caractère ASCII « espace » ou caractère
+ * ASCII « tabulation horizontale » suivant le cas). Par contre, le champ « donnée »
+ * contenant l’information fournie par le groupe peut, lui, contenir des caractères ayant
+ * une valeur égale à celle du caractère-séparateur utilisé pour la trame."
+ *
+ * Mais NOI-CPT_54E indique toutefois:
+ * "Note : le caractère séparateur des champs "Horizontal Tab" HT (0x09), en mode standard
+ * est different du caractère séparateur "Space" SP (0x20) en mode historique.
+ * Cette disposition permet d’utiliser le caractère "Space" pour les données."
+ *
+ * On serait donc fondé à penser qu'en mode historique, ni les étiquettes ni les données
+ * ne contiennent 0x20. Du moins en sortie Linky "historique". Cette hypothèse est donc
+ * retenue jusqu'à preuve du contraire.
+ */
+
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+%option noyywrap
+
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+%option nounput
+
+/* we never directly read input */
+%option noinput
+
+/* we don't need the default rule */
+%option nodefault
+
+/* batch means that we'll never use the generated lexer interactively. */
+%option batch
+
+/* we only process 7-bit input */
+%option 7bit
+
+/* Enables debug mode. To see the debug messages, one needs to also set
+ yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+%option nodebug
+
+%s FILTER
+
+DATAC [\x21-\x7e]
+CHKSUM [\x20-\x5f]
+SEP \x20
+
+%{
+ #include "tic2json.h"
+ #include "ticv01.tab.h"
+
+ extern int filter_mode;
+ static uint8_t checksum;
+
+ static void crc_calc(void)
+ {
+ for (size_t i = 0; i<yyleng; i++)
+ checksum += (uint8_t)yytext[i];
+ }
+%}
+
+%%
+
+ if (filter_mode)
+ BEGIN(FILTER);
+
+ /* only for filter */
+<FILTER>"#"ticfilter\n { return TICFILTER; }
+<FILTER>[ \t\n]+ /* ignore whitespace */
+
+ /* balises */
+<INITIAL>\x02 { return TOK_STX; }
+<INITIAL>\x03 { return TOK_ETX; }
+<INITIAL>\x04 { return TOK_EOT; }
+<INITIAL>\x0a { checksum=0; return FIELD_START; }
+<INITIAL>{SEP} { checksum += (uint8_t)*yytext; return TOK_SEP; }
+<INITIAL>{CHKSUM}\x0d {
+ checksum -= 0x20; // we have one space too many in the checksum: last SEP
+ checksum = (checksum & 0x3f) + 0x20;
+ if (checksum == (uint8_t)yytext[0]) return FIELD_OK;
+ else return FIELD_KO;
+ }
+
+
+ /* etiquettes - mode historique */
+ADCO { crc_calc(); ticv01yylval.label = "ADCO"; return ET_ADCO; }
+OPTARIF { crc_calc(); ticv01yylval.label = "OPTARIF"; return ET_OPTARIF; }
+ISOUSC { crc_calc(); ticv01yylval.label = "ISOUSC"; return ET_ISOUSC; }
+BASE { crc_calc(); ticv01yylval.label = "BASE"; return ET_BASE; }
+HCHC { crc_calc(); ticv01yylval.label = "HCHC"; return ET_HCHC; }
+HCHP { crc_calc(); ticv01yylval.label = "HCHP"; return ET_HCHP; }
+EJPHN { crc_calc(); ticv01yylval.label = "EJPHN"; return ET_EJPHN; }
+EJPHPM { crc_calc(); ticv01yylval.label = "EJPHPM"; return ET_EJPHPM; }
+BBRHCJB { crc_calc(); ticv01yylval.label = "BBRHCJB"; return ET_BBRHCJB; }
+BBRHPJB { crc_calc(); ticv01yylval.label = "BBRHPJB"; return ET_BBRHPJB; }
+BBRHCJW { crc_calc(); ticv01yylval.label = "BBRHCJW"; return ET_BBRHCJW; }
+BBRHPJW { crc_calc(); ticv01yylval.label = "BBRHPJW"; return ET_BBRHPJW; }
+BBRHCJR { crc_calc(); ticv01yylval.label = "BBRHCJR"; return ET_BBRHCJR; }
+BBRHPJR { crc_calc(); ticv01yylval.label = "BBRHPJR"; return ET_BBRHPJR; }
+PEJP { crc_calc(); ticv01yylval.label = "PEJP"; return ET_PEJP; }
+PTEC { crc_calc(); ticv01yylval.label = "PTEC"; return ET_PTEC; }
+DEMAIN { crc_calc(); ticv01yylval.label = "DEMAIN"; return ET_DEMAIN; }
+IINST { crc_calc(); ticv01yylval.label = "IINST"; return ET_IINST; }
+IINST1 { crc_calc(); ticv01yylval.label = "IINST1"; return ET_IINST1; }
+IINST2 { crc_calc(); ticv01yylval.label = "IINST2"; return ET_IINST2; }
+IINST3 { crc_calc(); ticv01yylval.label = "IINST3"; return ET_IINST3; }
+ADPS { crc_calc(); ticv01yylval.label = "ADPS"; return ET_ADPS; }
+IMAX { crc_calc(); ticv01yylval.label = "IMAX"; return ET_IMAX; }
+IMAX1 { crc_calc(); ticv01yylval.label = "IMAX1"; return ET_IMAX1; }
+IMAX2 { crc_calc(); ticv01yylval.label = "IMAX2"; return ET_IMAX2; }
+IMAX3 { crc_calc(); ticv01yylval.label = "IMAX3"; return ET_IMAX3; }
+PMAX { crc_calc(); ticv01yylval.label = "PMAX"; return ET_PMAX; }
+PAPP { crc_calc(); ticv01yylval.label = "PAPP"; return ET_PAPP; }
+HHPHC { crc_calc(); ticv01yylval.label = "HHPHC"; return ET_HHPHC; }
+MOTDETAT { crc_calc(); ticv01yylval.label = "MOTDETAT"; return ET_MOTDETAT; }
+PPOT { crc_calc(); ticv01yylval.label = "PPOT"; return ET_PPOT; }
+ADIR1 { crc_calc(); ticv01yylval.label = "ADIR1"; return ET_ADIR1; }
+ADIR2 { crc_calc(); ticv01yylval.label = "ADIR2"; return ET_ADIR2; }
+ADIR3 { crc_calc(); ticv01yylval.label = "ADIR3"; return ET_ADIR3; }
+
+
+<INITIAL>{DATAC}+ { crc_calc(); ticv01yylval.text = strdup(yytext); return TOK_DATA; }
+
+. { pr_err("spurious character 0x%02hhx\n", *yytext); return *yytext; }
+
+<<EOF>> { yyterminate(); }
+%%
diff --git a/ticv01.y b/ticv01.y
new file mode 100644 (file)
index 0000000..a4dcd9e
--- /dev/null
+++ b/ticv01.y
@@ -0,0 +1,171 @@
+//
+// ticv01.y
+// A parser for ENEDIS TIC version 01 protocol
+//
+// (C) 2021 Thibaut VARENE
+// License: GPLv2 - http://www.gnu.org/licenses/gpl-2.0.html
+//
+
+/**
+ * @file
+ * This parser implements a complete grammar that supports TIC version 01
+ * as specified in Enedis-NOI-CPT_02E.pdf version 6.
+ *
+ * This parser does not allocate memory, except if a filter configuration is used in
+ * which case the etiq_en array will be allocated (it's a few hundred bytes).
+ * A left-recursion grammar has been implemented to keep the memory usage to the bare
+ * minimum as well. As a tradeoff, valid datasets are always emitted regardless of the
+ * overall status of the containing frame.
+ */
+
+%{
+#include <stdlib.h>
+#include "tic2json.h"
+
+int ticv01yylex();
+int ticv01yylex_destroy();
+extern FILE *ticv01yyin;
+static void yyerror(const char *);
+
+extern int filter_mode;
+extern uint8_t *etiq_en;
+
+%}
+
+%union {
+ char *text;
+ const char *label;
+ struct tic_etiquette etiq;
+ struct tic_field field;
+}
+
+%verbose
+
+%token TOK_STX TOK_ETX TOK_EOT TOK_SEP
+%token FIELD_START FIELD_OK FIELD_KO
+%token TICFILTER
+
+%token <text> TOK_DATA
+
+%token <label> ET_ADCO ET_OPTARIF ET_ISOUSC ET_BASE ET_HCHC ET_HCHP ET_EJPHN ET_EJPHPM
+%token <label> ET_BBRHCJB ET_BBRHPJB ET_BBRHCJW ET_BBRHPJW ET_BBRHCJR ET_BBRHPJR
+%token <label> ET_PEJP ET_PTEC ET_DEMAIN ET_IINST ET_IINST1 ET_IINST2 ET_IINST3 ET_ADPS ET_IMAX ET_IMAX1 ET_IMAX2 ET_IMAX3
+%token <label> ET_PMAX ET_PAPP ET_HHPHC ET_MOTDETAT ET_PPOT ET_ADIR1 ET_ADIR2 ET_ADIR3
+
+%type <etiq> etiquette
+%type <field> field
+
+%destructor { free($$); } <text>
+%destructor { free_field(&$$); } <field>
+%destructor { } <> <*>
+
+%%
+
+start: filter | frames
+
+/* filter config only */
+filter:
+ TICFILTER etiquettes
+;
+
+etiquettes:
+ etiquette { etiq_en[1ドル.tok]=1; }
+ | etiquettes etiquette { etiq_en[2ドル.tok]=1; }
+ | error { YYABORT; }
+;
+
+/* stream processing */
+frames:
+ frame
+ | frames frame
+;
+
+frame:
+ TOK_STX datasets TOK_ETX { frame_sep(); }
+ | TOK_STX datasets TOK_EOT { /*mark error but don't print*/ frame_err(); frame_sep(); }
+ | error TOK_ETX { frame_err(); frame_sep(); pr_err("frame error\n"); yyerrok; }
+;
+
+datasets:
+ error { frame_err(); pr_err("dataset error\n"); }
+ | dataset
+ | datasets dataset
+;
+
+dataset:
+ FIELD_START field FIELD_OK { print_field(&2ドル); free_field(&2ドル); }
+ | FIELD_START field FIELD_KO { frame_err(); pr_err("dataset invalid checksum\n"); free_field(&2ドル); }
+ | FIELD_START error FIELD_OK { /*not a frame error*/ pr_err("unrecognized dataset\n"); yyerrok; }
+;
+
+field:
+ etiquette TOK_SEP TOK_DATA TOK_SEP { make_field(&$,ドル &1,ドル NULL, 3ドル); }
+;
+
+etiquette:
+ ET_ADCO { $$.tok=yytranslate[ET_ADCO]; $$.unittype=U_SANS|T_STRING; $$.label=1ドル; $$.desc="Adresse du compteur"; }
+ | ET_OPTARIF { $$.tok=yytranslate[ET_OPTARIF]; $$.unittype=U_SANS|T_STRING; $$.label=1ドル; $$.desc="Option tarifaire choisie"; }
+ | ET_ISOUSC { $$.tok=yytranslate[ET_ISOUSC]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité souscrite"; }
+ | ET_BASE { $$.tok=yytranslate[ET_BASE]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Base"; }
+ | ET_HCHC { $$.tok=yytranslate[ET_HCHC]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Heures Creuses: Heures Creuses"; }
+ | ET_HCHP { $$.tok=yytranslate[ET_HCHP]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Heures Creuses: Heures Pleines"; }
+ | ET_EJPHN { $$.tok=yytranslate[ET_EJPHN]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option EJP: Heures Normales"; }
+ | ET_EJPHPM { $$.tok=yytranslate[ET_EJPHPM]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option EJP: Heures de Pointe Mobile"; }
+ | ET_BBRHCJB { $$.tok=yytranslate[ET_BBRHCJB]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Tempo: Heures Creuses Jours Bleus"; }
+ | ET_BBRHPJB { $$.tok=yytranslate[ET_BBRHPJB]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Tempo: Heures Pleines Jours Bleus"; }
+ | ET_BBRHCJW { $$.tok=yytranslate[ET_BBRHCJW]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Tempo: Heures Creuses Jours Blancs"; }
+ | ET_BBRHPJW { $$.tok=yytranslate[ET_BBRHPJW]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Tempo: Heures Pleines Jours Blancs"; }
+ | ET_BBRHCJR { $$.tok=yytranslate[ET_BBRHCJR]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Tempo: Heures Creuses Jours Rouges"; }
+ | ET_BBRHPJR { $$.tok=yytranslate[ET_BBRHPJR]; $$.unittype=U_WH; $$.label=1ドル; $$.desc="Index option Tempo: Heures Pleines Jours Rouges"; }
+ | ET_PEJP { $$.tok=yytranslate[ET_PEJP]; $$.unittype=U_MIN; $$.label=1ドル; $$.desc="Préavis Début EJP (30 min)"; }
+ | ET_PTEC { $$.tok=yytranslate[ET_PTEC]; $$.unittype=U_SANS|T_STRING; $$.label=1ドル; $$.desc="Période Tarifaire en cours"; }
+ | ET_DEMAIN { $$.tok=yytranslate[ET_DEMAIN]; $$.unittype=U_SANS|T_STRING; $$.label=1ドル; $$.desc="Couleur du lendemain"; }
+ | ET_IINST { $$.tok=yytranslate[ET_IINST]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité Instantanée"; }
+ | ET_IINST1 { $$.tok=yytranslate[ET_IINST1]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité Instantanée phase 1"; }
+ | ET_IINST2 { $$.tok=yytranslate[ET_IINST2]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité Instantanée phase 2"; }
+ | ET_IINST3 { $$.tok=yytranslate[ET_IINST3]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité Instantanée phase 3"; }
+ | ET_ADPS { $$.tok=yytranslate[ET_ADPS]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Avertissement de Dépassement De Puissance Souscrite"; }
+ | ET_IMAX { $$.tok=yytranslate[ET_IMAX]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité maximale"; }
+ | ET_IMAX1 { $$.tok=yytranslate[ET_IMAX1]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité maximale phase 1"; }
+ | ET_IMAX2 { $$.tok=yytranslate[ET_IMAX2]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité maximale phase 2"; }
+ | ET_IMAX3 { $$.tok=yytranslate[ET_IMAX3]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Intensité maximale phase 3"; }
+ | ET_PMAX { $$.tok=yytranslate[ET_PMAX]; $$.unittype=U_W; $$.label=1ドル; $$.desc="Puissance maximale atteinte"; }
+ | ET_PAPP { $$.tok=yytranslate[ET_PAPP]; $$.unittype=U_VA; $$.label=1ドル; $$.desc="Puissance apparente soutirée"; }
+ | ET_HHPHC { $$.tok=yytranslate[ET_HHPHC]; $$.unittype=U_SANS|T_STRING; $$.label=1ドル; $$.desc="Horaire Heures Pleines Heures Creuses"; }
+ | ET_MOTDETAT { $$.tok=yytranslate[ET_MOTDETAT]; $$.unittype=U_SANS|T_STRING; $$.label=1ドル; $$.desc="Mot d'état du compteur"; }
+ | ET_PPOT { $$.tok=yytranslate[ET_PPOT]; $$.unittype=U_SANS; $$.label=1ドル; $$.desc="Présence des potentiels"; }
+ | ET_ADIR1 { $$.tok=yytranslate[ET_ADIR1]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Avertissemetn de Dépassement d'intensité de réglage phase 1"; }
+ | ET_ADIR2 { $$.tok=yytranslate[ET_ADIR2]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Avertissemetn de Dépassement d'intensité de réglage phase 2"; }
+ | ET_ADIR3 { $$.tok=yytranslate[ET_ADIR3]; $$.unittype=U_A; $$.label=1ドル; $$.desc="Avertissemetn de Dépassement d'intensité de réglage phase 3"; }
+;
+
+%%
+
+#ifndef BAREBUILD
+void parse_config_v01(const char *filename)
+{
+ if (!(ticv01yyin = fopen(filename, "r"))) {
+ perror(filename);
+ exit(-1);
+ }
+
+ etiq_en = calloc(YYNTOKENS, sizeof(*etiq_en));
+ if (!etiq_en)
+ abort(); // OOM
+
+ filter_mode = 1;
+ if (ticv01yyparse()) {
+ pr_err("%s: filter config error!\n", filename);
+ exit(-1);
+ }
+
+ fclose(ticv01yyin);
+ ticv01yylex_destroy();
+ ticv01yyin = stdin;
+ filter_mode = 0;
+}
+#endif /* !BAREBUILD */
+
+static void yyerror(const char * s)
+{
+}
tic2json TIC parser/converter
RSS Atom

AltStyle によって変換されたページ (->オリジナル) /