diff --git a/include/obis.h b/include/obis.h index df3d8ad..9526d5f 100644 --- a/include/obis.h +++ b/include/obis.h @@ -89,11 +89,7 @@ typedef union { typedef union { unsigned char raw[6]; struct { - unsigned char media; - unsigned char channel; - unsigned char indicator; - unsigned char mode; - unsigned char quantities; + unsigned char media, channel, indicator, mode, quantities; unsigned char storage; /* not used in Germany */ } groups; } obis_id_t; @@ -105,10 +101,10 @@ typedef struct { } obis_alias_t; /* Prototypes */ -obis_id_t obis_init(const unsigned char *raw); -obis_id_t obis_parse(const char *str); -obis_id_t obis_lookup_alias(const char *alias); -int obis_unparse(obis_id_t id, char *buffer); +obis_id_t * obis_init(obis_id_t *id, const unsigned char *raw); +obis_id_t * obis_lookup_alias(const char *alias); +int obis_parse(obis_id_t *id, const char *str, size_t n); +int obis_unparse(obis_id_t id, char *buffer, size_t n); int obis_compare(obis_id_t a, obis_id_t b); int obis_is_manufacturer_specific(obis_id_t id); int obis_is_null(obis_id_t id); diff --git a/src/obis.c b/src/obis.c index 2ba3892..26ec8d7 100644 --- a/src/obis.c +++ b/src/obis.c @@ -26,100 +26,147 @@ #include #include #include -#include +#include #include "obis.h" +#define DC 0xff // wildcard, dont care + obis_alias_t obis_aliases[] = { /** * 255 is considered as wildcard! * - * A B C D E F alias description + * A B C D E F alias description * ===================================================================================*/ /* General */ -{{{ 1, 0, 12, 7, 0, 255}}, "voltage", ""}, -{{{ 1, 0, 11, 7, 0, 255}}, "current", ""}, -{{{ 1, 0, 14, 7, 0, 255}}, "frequency", ""}, -{{{ 1, 0, 12, 7, 0, 255}}, "powerfactor",""}, +{{{ 1, 0, 12, 7, DC, DC}}, "voltage", ""}, +{{{ 1, 0, 32, 7, DC, DC}}, "voltage-l1", ""}, +{{{ 1, 0, 52, 7, DC, DC}}, "voltage-l2", ""}, +{{{ 1, 0, 72, 7, DC, DC}}, "voltage-l3", ""}, -{{{ 1, 0, 1, 7, 255, 255}}, "power", "Active Power Instantaneous value Total"}, -{{{ 1, 0, 21, 7, 255, 255}}, "power-l1", "L1 Active Power Instantaneous value Total"}, -{{{ 1, 0, 41, 7, 255, 255}}, "power-l2", "L1 Active Power Instantaneous value Total"}, -{{{ 1, 0, 61, 7, 255, 255}}, "power-l3", "L3 Active Power Instantaneous value Total"}, +{{{ 1, 0, 11, 7, DC, DC}}, "current", ""}, +{{{ 1, 0, 31, 7, DC, DC}}, "current-l1", ""}, +{{{ 1, 0, 51, 7, DC, DC}}, "current-l2", ""}, +{{{ 1, 0, 71, 7, DC, DC}}, "current-l3", ""}, -{{{ 1, 0, 1, 8, 0, 255}}, "counter", "Active Power Counter Total"}, +{{{ 1, 0, 14, 7, 0, DC}}, "frequency", ""}, +{{{ 1, 0, 12, 7, 0, DC}}, "powerfactor", ""}, + +{{{ 1, 0, 1, 7, DC, DC}}, "power", "Active Power Instantaneous value Total"}, +{{{ 1, 0, 21, 7, DC, DC}}, "power-l1", "L1 Active Power Instantaneous value Total"}, +{{{ 1, 0, 41, 7, DC, DC}}, "power-l2", "L1 Active Power Instantaneous value Total"}, +{{{ 1, 0, 61, 7, DC, DC}}, "power-l3", "L3 Active Power Instantaneous value Total"}, + +{{{ 0, 0, 96, 1, DC, DC}}, "device", "Complete device ID"}, +{{{ 1, 0, 96, 5, 5, DC}}, "status", "Meter status flag"}, + +{{{ 1, 0, 1, 8, DC, DC}}, "counter", "Active Power Counter Total"}, +{{{ 1, 0, 2, 8, DC, DC}}, "counter-out", "Zählerstand Lieferg."}, /* Easymeter */ -{{{ 1, 0, 96, 5, 5, 255}}, "status", "Meter status flag"}, /* ESYQ3B (Easymeter Q3B) */ -{{{129, 129, 199, 130, 3, 255}}, "", ""}, // ??? -{{{ 1, 0, 1, 8, 1, 255}}, "counter-t1", "Active Power Counter Tariff 1"}, -{{{ 1, 0, 1, 8, 2, 255}}, "counter-t2", "Active Power Counter Tariff 2"}, +{{{129, 129, 199, 130, 3, DC}}, "esy-?", ""}, // ??? +{{{ 1, 0, 1, 8, 1, DC}}, "esy-counter-t1", "Active Power Counter Tariff 1"}, +{{{ 1, 0, 1, 8, 2, DC}}, "esy-counter-t2", "Active Power Counter Tariff 2"}, /* ESYQ3D (Easymeter Q3D) */ -{{{ 0, 0, 96, 1, 255, 255}}, "device", "Complete device ID"}, -{{{ 0, 0, 0, 0, 0, 255}}, "", ""}, // ??? + +{{{ 0, 0, 0, 0, 0, DC}}, "esy-?", ""}, // ??? + +/* HAG eHZ010C_EHZ1WA02 (Hager eHz) */ +{{{ 1, 0, 0, 0, 0, DC}}, "hag-id", "Eigentumsnr."}, +{{{ 1, 0, 96, 50, 0, 0}}, "hag-status", "Netzstatus bitcodiert: Drehfeld, Anlaufschwelle, Energierichtung"}, +{{{ 1, 0, 96, 50, 0, 1}}, "hag-frequency", "Netz-Periode, hexadezimal (Einheit 1/100 ms)"}, +{{{ 1, 0, 96, 50, 0, 2}}, "hag-temp", "aktuelle Chiptemperatur, hexadezimal, Einheit °C"}, +{{{ 1, 0, 96, 50, 0, 3}}, "hag-temp-min", "minimale Chiptemperatur"}, +{{{ 1, 0, 96, 50, 0, 4}}, "hag-temp-avg", "gemittelte Chiptemperatur"}, +{{{ 1, 0, 96, 50, 0, 5}}, "hag-temp-max", "maximale Chiptemperatur"}, +{{{ 1, 0, 96, 50, 0, 6}}, "hag-check", "Kontrollnummer"}, +{{{ 1, 0, 96, 50, 0, 7}}, "hag-diag", "Diagnose"}, {} /* stop condition for iterator */ }; -obis_id_t obis_init(const unsigned char *raw) { - obis_id_t id; - +obis_id_t * obis_init(obis_id_t *id, const unsigned char *raw) { if (raw == NULL) { - memset(id.raw, 0, 6); /* initialize with zeros */ + memset(id->raw, 0, 6); /* initialize with zeros */ } else { - memcpy(id.raw, raw, 6); + memcpy(id->raw, raw, 6); } - + return id; } -obis_id_t obis_parse(const char *str) { - obis_id_t id; - regex_t re; - regmatch_t matches[7]; +int obis_parse(obis_id_t *id, const char *str, size_t n) { + enum { A = 0, B, C, D, E, F }; - regcomp(&re, "^([0-9])-([0-9]{,2}):([0-9]{,2})\\.([0-9]{,2})\\.([0-9]{,2})(\\[*&][0-9]{,2})?$", REG_EXTENDED | REG_ICASE); - // TODO make values A B C optional to allow notations like "1.8.0" + char b; + int num; + int field; - if (regexec(&re, str, 7, matches, 0) == 0) { /* found string in OBIS notation */ - for (int i=0; i<6; i++) { - if (matches[i+1].rm_so != -1) { - id.raw[i] = strtoul(str+matches[i+1].rm_so, NULL, 10); + num = b = 0; + field = -1; + memset(&id->raw, 0xff, 6); /* initialize as wildcard */ + + /* format: "A-B:C.D.E[*&]F" */ + /* fields A, B, E, F are optional */ + /* fields C & D are mandatory */ + for (int i = 0; i < n; i++) { + b = str[i]; + + if (isdigit(b)) { + num = (num * 10) + (b - '0'); /* parse digits */ + } + else { + if (b == '-' && field < A) { /* end of field A */ + field = A; } - else { - id.raw[i] = 0xff; /* default value */ + else if (b == ':' && field < B) { /* end of field B */ + field = B; } + else if (b == '.' && field < D) { /* end of field C & D*/ + field = (field < C) ? C : D; + } + else if ((b == '*' || b == '&') && field == D) { /* end of field E, start of field F */ + field = E; + } + else goto error; // TODO lookup aliases + + id->raw[field] = num; + num = 0; } } - else { /* looking for alias */ - id = obis_lookup_alias(str); - } - - regfree(&re); /* householding */ - return id; + /* set last field */ + id->raw[++field] = num; + + /* fields C & D are mandatory */ + if (field < D) goto error; + + return 0; + +error: + printf("something unexpected happened (field=%i, b=%c, num=%i): %s:%i!\n", field, b, num, __FUNCTION__, __LINE__); + return -1; } -obis_id_t obis_lookup_alias(const char *alias) { - obis_id_t nf = obis_init(NULL); /* not found */ +obis_id_t * obis_lookup_alias(const char *alias) { obis_alias_t *it = obis_aliases; do { /* linear search */ if (strcmp(it->name, alias) == 0) { - return it->id; + return &it->id; } } while ((++it)->name); - - return nf; + + return NULL; } -int obis_unparse(obis_id_t id, char *buffer) { - return sprintf(buffer, "%x-%x:%x.%x.%x*%x", +int obis_unparse(obis_id_t id, char *buffer, size_t n) { + return snprintf(buffer, n, "%i-%i:%i.%i.%i*%i", id.groups.media, id.groups.channel, id.groups.indicator, @@ -131,7 +178,7 @@ int obis_unparse(obis_id_t id, char *buffer) { int obis_compare(obis_id_t a, obis_id_t b) { for (int i = 0; i < 6; i++) { - if (a.raw[i] == b.raw[i] || a.raw[i] == 255 || b.raw[i] == 255 ) { + if (a.raw[i] == b.raw[i] || a.raw[i] == 0xff || b.raw[i] == 0xff ) { continue; /* skip on wildcard or equal */ } else if (a.raw[i] < b.raw[i]) { @@ -167,4 +214,3 @@ int obis_is_manufacturer_specific(obis_id_t id) { ); } -