aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2019-05-25 02:33:11 +0200
committerBjørn Mork <bjorn@mork.no>2019-05-25 02:33:11 +0200
commite8fc6cab7d524f6a3c20432343f195098a788043 (patch)
tree141572a34b77371df01028940c6a4954dd1a7881
parent6bb089adb10ef2699a78dbc263303f69031c694f (diff)
wip: semi-working
Signed-off-by: Bjørn Mork <bjorn@mork.no>
-rw-r--r--aidon_v0001.json40
-rw-r--r--kaifa_v0001.json40
-rw-r--r--kamstrup_v0001.json40
-rw-r--r--obinsectd.c400
4 files changed, 418 insertions, 102 deletions
diff --git a/aidon_v0001.json b/aidon_v0001.json
index 287557c..abfff17 100644
--- a/aidon_v0001.json
+++ b/aidon_v0001.json
@@ -1,21 +1,21 @@
-[
- { "1-1:0.2.129.255" : { "name" : "ListId", "value" : "AIDON_V0001" } },
- { "0-0:96.1.0.255" : { "name" : "SerialNumber" } },
- { "0-0:96.1.7.255" : { "name" : "Model" } },
- { "1-0:1.7.0.255" : { "name" : "Power", "unit" : "kW", "scale" : 1000 } },
- { "1-0:2.7.0.255" : { "name" : "PowerExport", "unit" : "kW", "scale" : 1000 } },
- { "1-0:3.7.0.255" : { "name" : "ReactivePower", "unit" : "kW", "scale" : 1000 } },
- { "1-0:4.7.0.255" : { "name" : "ReactivePowerExport", "unit" : "kW", "scale" : 1000 } },
- { "1-0:31.7.0.255" : { "name" : "CurrentL1", "unit" : "A", "scale" : 10 } },
- { "1-0:51.7.0.255" : { "name" : "CurrentL2", "unit" : "A", "scale" : 10 } },
- { "1-0:71.7.0.255" : { "name" : "CurrentL3", "unit" : "A", "scale" : 10 } },
- { "1-0:32.7.0.255" : { "name" : "VoltageL1", "unit" : "V", "scale" : 10 } },
- { "1-0:52.7.0.255" : { "name" : "VoltageL2", "unit" : "V", "scale" : 10 } },
- { "1-0:72.7.0.255" : { "name" : "VoltageL3", "unit" : "V", "scale" : 10 } },
- { "0-0:1.0.0.255" : { "name" : "MeterTime" } },
- { "1-0:1.8.0.255" : { "name" : "CumulativeEnergy", "unit" : "kWh", "scale" : 100 } },
- { "1-0:2.8.0.255" : { "name" : "CumulativeEnergyExport", "unit" : "kWh", "scale" : 100 } },
- { "1-0:3.8.0.255" : { "name" : "CumulativeReactiveEnergy", "unit" : "kVArh", "scale" : 100 } },
- { "1-0:4.8.0.255" : { "name" : "CumulativeReactiveEnergyExport", "unit" : "kVArh", "scale" : 100 } }
-]
+{
+ "1-1:0.2.129.255" : { "name" : "ListId", "value" : "AIDON_V0001" },
+ "0-0:96.1.0.255" : { "name" : "SerialNumber" },
+ "0-0:96.1.7.255" : { "name" : "Model" },
+ "1-0:1.7.0.255" : { "name" : "Power", "unit" : "kW", "scale" : 1000 },
+ "1-0:2.7.0.255" : { "name" : "PowerExport", "unit" : "kW", "scale" : 1000 },
+ "1-0:3.7.0.255" : { "name" : "ReactivePower", "unit" : "kW", "scale" : 1000 },
+ "1-0:4.7.0.255" : { "name" : "ReactivePowerExport", "unit" : "kW", "scale" : 1000 },
+ "1-0:31.7.0.255" : { "name" : "CurrentL1", "unit" : "A", "scale" : 10 },
+ "1-0:51.7.0.255" : { "name" : "CurrentL2", "unit" : "A", "scale" : 10 },
+ "1-0:71.7.0.255" : { "name" : "CurrentL3", "unit" : "A", "scale" : 10 },
+ "1-0:32.7.0.255" : { "name" : "VoltageL1", "unit" : "V", "scale" : 10 },
+ "1-0:52.7.0.255" : { "name" : "VoltageL2", "unit" : "V", "scale" : 10 },
+ "1-0:72.7.0.255" : { "name" : "VoltageL3", "unit" : "V", "scale" : 10 },
+ "0-0:1.0.0.255" : { "name" : "MeterTime" },
+ "1-0:1.8.0.255" : { "name" : "CumulativeEnergy", "unit" : "kWh", "scale" : 100 },
+ "1-0:2.8.0.255" : { "name" : "CumulativeEnergyExport", "unit" : "kWh", "scale" : 100 },
+ "1-0:3.8.0.255" : { "name" : "CumulativeReactiveEnergy", "unit" : "kVArh", "scale" : 100 },
+ "1-0:4.8.0.255" : { "name" : "CumulativeReactiveEnergyExport", "unit" : "kVArh", "scale" : 100 }
+}
diff --git a/kaifa_v0001.json b/kaifa_v0001.json
index 470fc83..d6e0092 100644
--- a/kaifa_v0001.json
+++ b/kaifa_v0001.json
@@ -1,21 +1,21 @@
-[
- { "1-1:0.2.129.255" : { "name" : "ListId", "value" : "KFM_001" } },
- { "0-0:96.1.0.255" : { "name" : "SerialNumber" } },
- { "0-0:96.1.7.255" : { "name" : "Model" } },
- { "1-0:1.7.0.255" : { "name" : "Power", "unit" : "kW", "scale" : 1000 } },
- { "1-0:2.7.0.255" : { "name" : "PowerExport", "unit" : "kW", "scale" : 1000 } },
- { "1-0:3.7.0.255" : { "name" : "ReactivePower", "unit" : "kW", "scale" : 1000 } },
- { "1-0:4.7.0.255" : { "name" : "ReactivePowerExport", "unit" : "kW", "scale" : 1000 } },
- { "1-0:31.7.0.255" : { "name" : "CurrentL1", "unit" : "A", "scale" : 10 } },
- { "1-0:51.7.0.255" : { "name" : "CurrentL2", "unit" : "A", "scale" : 10 } },
- { "1-0:71.7.0.255" : { "name" : "CurrentL3", "unit" : "A", "scale" : 10 } },
- { "1-0:32.7.0.255" : { "name" : "VoltageL1", "unit" : "V", "scale" : 10 } },
- { "1-0:52.7.0.255" : { "name" : "VoltageL2", "unit" : "V", "scale" : 10 } },
- { "1-0:72.7.0.255" : { "name" : "VoltageL3", "unit" : "V", "scale" : 10 } },
- { "0-0:1.0.0.255" : { "name" : "MeterTime" } },
- { "1-0:1.8.0.255" : { "name" : "CumulativeEnergy", "unit" : "kWh", "scale" : 1000 } },
- { "1-0:2.8.0.255" : { "name" : "CumulativeEnergyExport", "unit" : "kWh", "scale" : 1000 } },
- { "1-0:3.8.0.255" : { "name" : "CumulativeReactiveEnergy", "unit" : "kVArh", "scale" : 1000 } },
- { "1-0:4.8.0.255" : { "name" : "CumulativeReactiveEnergyExport", "unit" : "kVArh", "scale" : 1000 } }
-]
+{
+ "1-1:0.2.129.255" : { "name" : "ListId", "value" : "KFM_001" },
+ "0-0:96.1.0.255" : { "name" : "SerialNumber" },
+ "0-0:96.1.7.255" : { "name" : "Model" },
+ "1-0:1.7.0.255" : { "name" : "Power", "unit" : "kW", "scale" : 1000 },
+ "1-0:2.7.0.255" : { "name" : "PowerExport", "unit" : "kW", "scale" : 1000 },
+ "1-0:3.7.0.255" : { "name" : "ReactivePower", "unit" : "kW", "scale" : 1000 },
+ "1-0:4.7.0.255" : { "name" : "ReactivePowerExport", "unit" : "kW", "scale" : 1000 },
+ "1-0:31.7.0.255" : { "name" : "CurrentL1", "unit" : "A", "scale" : 10 },
+ "1-0:51.7.0.255" : { "name" : "CurrentL2", "unit" : "A", "scale" : 10 },
+ "1-0:71.7.0.255" : { "name" : "CurrentL3", "unit" : "A", "scale" : 10 },
+ "1-0:32.7.0.255" : { "name" : "VoltageL1", "unit" : "V", "scale" : 10 },
+ "1-0:52.7.0.255" : { "name" : "VoltageL2", "unit" : "V", "scale" : 10 },
+ "1-0:72.7.0.255" : { "name" : "VoltageL3", "unit" : "V", "scale" : 10 },
+ "0-0:1.0.0.255" : { "name" : "MeterTime" },
+ "1-0:1.8.0.255" : { "name" : "CumulativeEnergy", "unit" : "kWh", "scale" : 1000 },
+ "1-0:2.8.0.255" : { "name" : "CumulativeEnergyExport", "unit" : "kWh", "scale" : 1000 },
+ "1-0:3.8.0.255" : { "name" : "CumulativeReactiveEnergy", "unit" : "kVArh", "scale" : 1000 },
+ "1-0:4.8.0.255" : { "name" : "CumulativeReactiveEnergyExport", "unit" : "kVArh", "scale" : 1000 }
+}
diff --git a/kamstrup_v0001.json b/kamstrup_v0001.json
index 9e5eaf8..2482895 100644
--- a/kamstrup_v0001.json
+++ b/kamstrup_v0001.json
@@ -1,20 +1,20 @@
-[
- { "1-1:0.2.129.255" : { "name" : "ListId", "value" : "Kamstrup_V0001" } },
- { "1-1:0.0.5.255" : { "name" : "SerialNumber" } },
- { "1-1:96.1.1.255" : { "name" : "Model" } },
- { "1-1:1.7.0.255" : { "name" : "Power", "unit" : "kW", "scale" : 1000 } },
- { "1-1:2.7.0.255" : { "name" : "PowerExport", "unit" : "kW", "scale" : 1000 } },
- { "1-1:3.7.0.255" : { "name" : "ReactivePower", "unit" : "kW", "scale" : 1000 } },
- { "1-1:4.7.0.255" : { "name" : "ReactivePowerExport", "unit" : "kW", "scale" : 1000 } },
- { "1-1:31.7.0.255" : { "name" : "CurrentL1", "unit" : "A", "scale" : 100 } },
- { "1-1:51.7.0.255" : { "name" : "CurrentL2", "unit" : "A", "scale" : 100 } },
- { "1-1:71.7.0.255" : { "name" : "CurrentL3", "unit" : "A", "scale" : 100 } },
- { "1-1:32.7.0.255" : { "name" : "VoltageL1", "unit" : "V", "scale" : 1 } },
- { "1-1:52.7.0.255" : { "name" : "VoltageL2", "unit" : "V", "scale" : 1 } },
- { "1-1:72.7.0.255" : { "name" : "VoltageL3", "unit" : "V", "scale" : 1 } },
- { "0-1:1.0.0.255" : { "name" : "MeterTime" } },
- { "1-1:1.8.0.255" : { "name" : "CumulativeEnergy", "unit" : "kWh", "scale" : 100 } },
- { "1-1:2.8.0.255" : { "name" : "CumulativeEnergyExport", "unit" : "kWh", "scale" : 100 } },
- { "1-1:3.8.0.255" : { "name" : "CumulativeReactiveEnergy", "unit" : "kVArh", "scale" : 100 } },
- { "1-1:4.8.0.255" : { "name" : "CumulativeReactiveEnergyExport", "unit" : "kVArh", "scale" : 100 } }
-]
+{
+ "1-1:0.2.129.255" : { "name" : "ListId", "value" : "Kamstrup_V0001" },
+ "1-1:0.0.5.255" : { "name" : "SerialNumber" },
+ "1-1:96.1.1.255" : { "name" : "Model" },
+ "1-1:1.7.0.255" : { "name" : "Power", "unit" : "kW", "scale" : 1000 },
+ "1-1:2.7.0.255" : { "name" : "PowerExport", "unit" : "kW", "scale" : 1000 },
+ "1-1:3.7.0.255" : { "name" : "ReactivePower", "unit" : "kW", "scale" : 1000 },
+ "1-1:4.7.0.255" : { "name" : "ReactivePowerExport", "unit" : "kW", "scale" : 1000 },
+ "1-1:31.7.0.255" : { "name" : "CurrentL1", "unit" : "A", "scale" : 100 },
+ "1-1:51.7.0.255" : { "name" : "CurrentL2", "unit" : "A", "scale" : 100 },
+ "1-1:71.7.0.255" : { "name" : "CurrentL3", "unit" : "A", "scale" : 100 },
+ "1-1:32.7.0.255" : { "name" : "VoltageL1", "unit" : "V", "scale" : 1 },
+ "1-1:52.7.0.255" : { "name" : "VoltageL2", "unit" : "V", "scale" : 1 },
+ "1-1:72.7.0.255" : { "name" : "VoltageL3", "unit" : "V", "scale" : 1 },
+ "0-1:1.0.0.255" : { "name" : "MeterTime" },
+ "1-1:1.8.0.255" : { "name" : "CumulativeEnergy", "unit" : "kWh", "scale" : 100 },
+ "1-1:2.8.0.255" : { "name" : "CumulativeEnergyExport", "unit" : "kWh", "scale" : 100 },
+ "1-1:3.8.0.255" : { "name" : "CumulativeReactiveEnergy", "unit" : "kVArh", "scale" : 100 },
+ "1-1:4.8.0.255" : { "name" : "CumulativeReactiveEnergyExport", "unit" : "kVArh", "scale" : 100 }
+}
diff --git a/obinsectd.c b/obinsectd.c
index 3784ec8..ebb52fe 100644
--- a/obinsectd.c
+++ b/obinsectd.c
@@ -49,12 +49,32 @@
/* parsed configuration */
static json_object *cfg = NULL;
-
#define BUFSIZE (1024 * 2)
+/* maximum number of OBIS codes in each list (-1) */
+#define MAX_OBISCODES 32
+
+/* maximum number of OBIS lists supported */
+#define MAX_LISTS 4
+
+struct obisfile {
+ size_t n;
+ const char *filename;
+ const char *listname;
+ char *code[MAX_OBISCODES];
+ const char *alias[MAX_OBISCODES];
+ const char *unit[MAX_OBISCODES];
+ int scale[MAX_OBISCODES];
+};
+
+static int current_list = 0; /* currently selected list */
+static struct obisfile *obisfiles[MAX_LISTS] = { }; /* parsed OBIS lists */
+
#ifdef DEBUG
static bool debug = true;
+
#define debug(arg...) fprintf(stderr, arg)
+
static void print_packet(const char *pfx, void *buf, int len)
{
int i;
@@ -65,10 +85,19 @@ static void print_packet(const char *pfx, void *buf, int len)
fprintf(stderr, "%02hhx%c", p[i], (i + 1) % 16 ? ' ' : '\n');
fprintf(stderr, "\n");
}
+
+static void libmosquitto_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
+{
+ debug("<%i> %s\n", level, str);
+}
+
#else
static bool debug = false;
+
#define debug(arg...)
+
#define print_packet(pfx, buf, len)
+
#endif /* DEBUG */
/*
@@ -909,6 +938,10 @@ f) Kamstrup list 2:
static const char *obis_lookup(const char *list, int idx)
{
+ struct obisfile *cur = obisfiles[current_list];
+ if (idx < 0 || idx > cur->n)
+ return "unknown";
+ return cur->code[idx];
switch (idx) {
case 1: return "1-1:0.2.129.255";
case 2: return "0-0:96.1.0.255";
@@ -942,7 +975,57 @@ static json_object *obis_get_val(const char *key, json_object *val)
return json_object_get(val);
}
-static json_object *normalize(json_object *json)
+
+static const char *get_alias(const char *key)
+{
+ return NULL;
+}
+
+static void add_keyval(json_object *pubcfg, json_object *pub, const char *key, json_object *val, bool createobj)
+{
+ json_object *tmp, *obj;
+ const char *arrayname;
+ int i;
+
+ if (!json_object_object_get_ex(pubcfg, key, &tmp))
+ return;
+
+ json_object_object_add(pub, key, val);
+ for (i = 0; i < json_object_array_length(tmp); i++) {
+ arrayname = json_object_get_string(json_object_array_get_idx(tmp, i));
+
+ /* get existing object */
+ if (!json_object_object_get_ex(pub, arrayname, &obj)) {
+ /* values like "timestamp" will not trigger publication alone */
+ if (!createobj) {
+ debug("Skipping field '%s' in array '%s' due to non-existing array\n", key, arrayname);
+ continue;
+ }
+
+ /* publish new array? */
+ obj = json_object_new_object();
+ json_object_object_add(pub, arrayname, obj);
+ }
+ json_object_object_add(obj, key, val);
+ }
+}
+
+static void add_obis(json_object *pubcfg, json_object *pub, const char *key, json_object *val)
+{
+ const char *alias = get_alias(key);
+ json_object *normal;
+
+ if (json_object_object_get_ex(pub, "normal", &normal))
+ json_object_object_add(normal, key, val);
+ add_keyval(pubcfg, pub, key, val, true);
+ if (alias)
+ add_keyval(pubcfg, pub, alias, val, true);
+}
+
+/*
+ * post process the parsed packet, converting the data to simple key => value pairs
+ */
+static json_object *normalize(json_object *pubcfg, json_object *json)
{
json_object *ret, *tmp, *notification, *body;
@@ -952,14 +1035,16 @@ static json_object *normalize(json_object *json)
if (!json_object_object_get_ex(notification, "notification-body", &body))
return NULL;
+ /* create a new object for the results */
ret = json_object_new_object();
- /* add some local metadata */
- json_object_object_add(ret, "timestamp", json_object_new_int(time(NULL)));
+ /* create a "normal" result list with all OBIS codes as keys? */
+ if (json_object_object_get_ex(pubcfg, "normal", NULL))
+ json_object_object_add(ret, "normal", json_object_new_object());
/* include the message time-stamp if available and not 0 */
if (json_object_object_get_ex(notification, "date-time", &tmp) && json_object_get_int(tmp))
- json_object_object_add(ret, "date-time", json_object_get(tmp));
+ add_keyval(pubcfg, ret, "date-time", json_object_get(tmp), true);
/* overall formatting differs between the 3:
@@ -1047,7 +1132,7 @@ static json_object *normalize(json_object *json)
myval = val;
}
if (mykey && myval)
- json_object_object_add(ret, mykey, obis_get_val(mykey, myval));
+ add_obis(pubcfg, ret, mykey, obis_get_val(mykey, myval));
}
} else {
const char *mykey = NULL;
@@ -1062,55 +1147,40 @@ static json_object *normalize(json_object *json)
* has only a single value:
* - Active power+ (Q1+Q4) in kW
*/
- json_object_object_add(ret, "1-0:1.7.0.255", json_object_get(val));
+ add_obis(pubcfg, ret, "1-0:1.7.0.255", json_object_get(val));
else if (!listname) {
/* the list name is always the first value of any multi-element list */
listname = json_object_get_string(val);
- json_object_object_add(ret, obis_lookup(listname, 1), json_object_get(val));
+ add_obis(pubcfg, ret, obis_lookup(listname, 1), json_object_get(val));
}
else if (!strncmp(key, "obis", 4))
mykey = json_object_get_string(val);
else
/* "mykey" is the obis code for Kamstrup lists. Look up by index for Kaifa lists */
- json_object_object_add(ret, mykey ? mykey : obis_lookup(listname, i), obis_get_val(mykey, val));
+ add_obis(pubcfg, ret, mykey ? mykey : obis_lookup(listname, i), obis_get_val(mykey, val));
}
}
return ret;
}
-static json_object *get_value(json_object *val, json_object *json, json_object *normal, unsigned char *raw, size_t rawlen)
+static json_object *get_value(json_object *val, json_object *values)
{
- char *type;
+ json_object *ret;
- if (json_object_is_type(val, json_type_array)) {
- return NULL; // FIXME!
- }
- type = json_object_get_string(val);
- if (!strncmp(key, "rawhexdump", 4)) {
- return NULL; // FIXME!
- } else if (!strncmp(key, "full", 4)) {
- return json_object_get(json);
- } else if (!strncmp(key, "normal", 4)) {
- return json_object_get(normal);
- } else if (json_object_object_get_ex(normal, key, &val)) // OBIS codes and timestamp
- return json_object_get(val);
- }
+ if (json_object_object_get_ex(values, json_object_get_string(val), &ret))
+ return ret;
return NULL;
}
// FIMXE: topic, qos and variable mapping should be configurable - json config file?
-static int publish(struct mosquitto *mosq, json_object *json, json_object *normal, unsigned char *raw, size_t rawlen)
+static int publish(struct mosquitto *mosq, json_object *pubdata)
{
static int mid = 1;
const char *pub;
- char *t;
size_t publen;
json_object *topics, *val;
- print_packet("*** raw packet:\n", raw, rawlen);
- debug("*** json:\n%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY));
- debug("*** normal:\n%s\n", json_object_to_json_string_ext(normal, JSON_C_TO_STRING_PRETTY));
// * requires libjson-c version 0.13+
// pub = json_object_to_json_string_length(normal, JSON_C_TO_STRING_PLAIN, &publen);
@@ -1119,26 +1189,47 @@ static int publish(struct mosquitto *mosq, json_object *json, json_object *norma
if (!cfg || !json_object_object_get_ex(cfg, "topicmap", &topics))
return 0;
- json_object_object_foreach(topics, t, val) {
- val = get_value(val, json, normal, raw, rawlen);
+ json_object_object_foreach(topics, t, tmp) {
+ val = get_value(tmp, pubdata);
if (!val)
continue;
- publen = strlen(pub);
pub = json_object_to_json_string_ext(val, JSON_C_TO_STRING_PLAIN);
publen = strlen(pub);
mosquitto_publish(mosq, &mid, t, publen, pub, 0, false);
- json_object_put(val);
}
return 0;
}
+static void add_raw_packet(json_object *pubcfg, json_object *pubdata, unsigned char *p, size_t plen)
+{
+ int i, pos = 0;
+ char printbuf[1024];
+
+ print_packet("*** raw packet:\n", p, plen);
+
+ /* shortcut to avoid printing unused data */
+ if (!json_object_object_get_ex(pubcfg, "rawhexdump", NULL))
+ return;
+
+ for (i=0; i<plen; i++) {
+ pos += snprintf(printbuf + pos, sizeof(printbuf) - pos, "%02hhx%c", p[i], (i + 1) % 16 ? ' ' : '\n');
+ if (pos >= sizeof(printbuf))
+ break;
+ }
+ add_keyval(pubcfg, pubdata, "rawhexdump", json_object_new_string_len(printbuf, pos), true);
+}
+
static int read_and_parse(int fd, struct mosquitto *mosq, unsigned char *rbuf, size_t rbuflen)
{
unsigned char *payload, *cur, *hdlc;
struct pollfd fds[1];
int ret, rlen, framelen = -1;
- json_object *json;
+ json_object *json, *pubcfg, *pubdata;
+
+ /* get the publish config */
+ if (!json_object_object_get_ex(cfg, "publish", &pubcfg))
+ pubcfg = NULL;
fds[0].fd = fd;
fds[0].events = POLLIN;
@@ -1208,8 +1299,22 @@ skipframe:
}
/* got a complete and verified frame - parse the payload */
- if (parse_payload(payload, framelen - (payload - hdlc + 1), json))
- publish(mosq, json, normalize(json), hdlc, framelen + 2);
+ if (parse_payload(payload, framelen - (payload - hdlc + 1), json)) {
+ pubdata = normalize(pubcfg, json);
+ add_raw_packet(pubcfg, pubdata, hdlc, framelen + 2);
+ add_keyval(pubcfg, pubdata, "full", json, true);
+
+ /* add some local metadata */
+ add_keyval(pubcfg, pubdata, "timestamp", json_object_new_int(time(NULL)), false);
+// add_keyval(pubcfg, ret, "current-list", , false);
+// add_keyval(pubcfg, ret, "runtime", , false);
+// add_keyval(pubcfg, ret, "packets", , false);
+
+ debug("*** json:\n%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY));
+ debug("*** pubdatal:\n%s\n", json_object_to_json_string_ext(pubdata, JSON_C_TO_STRING_PRETTY));
+
+ publish(mosq, pubdata);
+ }
/* and drop it */
json_object_put(json);
@@ -1221,18 +1326,116 @@ skipframe:
goto nextframe;
}
return 0;
+ }
+
+
+/* parse configuration and build some helper structures;
+
+
+"publish" is a set of keys which are be considered for publishing. The
+value is a possibly empyt list of arrays containing this key.
+
+ "publish": {
+ "key1" : [ "__array_2" ],
+ "key2" : [],
+ "key3" : [ "__array_1" ],
+ }
+
+"arrays" is a set of made up array names and their respective real key content
+
+ "arrays": {
+ "__array_1": [ "key3" ],
+ "__array_2": [ "key1" ],
+ }
+
+The original topic value is replaced by the made up array name,
+
+This allows us to efficiently create structures for publishing while parsing:
+
+ Considering key = "foo", value = "bar":
+
+ for k in "foo" and all aliases of foo:
+ if "publish"->{k} exists, then
+ 1. add k => "bar" to master publishset
+ 2. for all set bits:
+ add "__array_(bit)" => array(bit) object to master publishset
+ add k => "bar" to array(bit) object
+
+When publishing, we can simply loop through the list of topics and
+publish if there is a corresponding value in the master publishset,
+without having to consider aliases or arrays.
+
+
+ */
+
+static void set_publish(json_object *pub, const char *key, json_object *arrayname)
+{
+ json_object *tmp;
+
+ if (!json_object_object_get_ex(pub, key, &tmp)) {
+ tmp = json_object_new_array();
+ json_object_object_add(pub, key, tmp);
+ }
+ if (arrayname)
+ json_object_array_add(tmp, arrayname);
}
+static void process_cfg(json_object *cfg, char *buf, size_t bufsize)
+{
+ json_object *topics, *array, *publish, *arrayname;
+ int i, idx = 0;
+ const char *name;
+
+ /* nothing published unless configured */
+ if (!cfg || !json_object_object_get_ex(cfg, "topicmap", &topics))
+ return;
+
+ /* unconditionally add parent nodes */
+ publish = json_object_new_object();
+ json_object_object_add(cfg, "publish", publish);
+ array = json_object_new_object();
+ json_object_object_add(cfg, "arrays", array);
+
+ /* create a flat set of all keys to be published */
+ json_object_object_foreach(topics, t, tmp) {
+ /* tmp is one of three different types: OBIS code, symbolic alias, or array */
+
+ if (json_object_is_type(tmp, json_type_array)) {
+ /* 1. move to arrays */
+ idx++;
+ snprintf(buf, bufsize, "__array_%u", idx);
+ json_object_object_add(array, buf, json_object_get(tmp));
+ arrayname = json_object_new_string(buf);
+
+ /* 2. set bits on all keys in array */
+ for (i = 0; i < json_object_array_length(tmp); i++) {
+ name = json_object_get_string(json_object_array_get_idx(tmp, i));
+ set_publish(publish, name, arrayname);
+ }
+
+ /* 3. replace the original topic value with the array name */
+ name = strdup(t);
+ json_object_object_del(topics, t);
+ json_object_object_add(topics, name, arrayname);
+ free((char *)name);
+
+ } else {
+ name = json_object_get_string(tmp);
+ set_publish(publish, name, NULL);
+ }
+ }
+}
-static json_object *read_config(const char *fname, unsigned char *buf, size_t bufsize)
+static json_object *read_json_file(const char *fname, char *buf, size_t bufsize)
{
- int fd = open(fname, O_RDONLY);
+ int fd;
ssize_t len;
+ fd = open(fname, O_RDONLY);
if (fd < 0)
return NULL;
- len = read(fd, buf, buflen);
+ len = read(fd, buf, bufsize);
close(fd);
if (len <= 0)
return NULL;
@@ -1240,6 +1443,107 @@ static json_object *read_config(const char *fname, unsigned char *buf, size_t bu
return json_tokener_parse(buf);
}
+/* we create indexed lookup arrays for each list */
+
+static struct obisfile *parse_obisfile(json_object *cfg, const char *fname, char *buf, size_t bufsize)
+{
+ json_object *tmp;
+ struct obisfile *ret;
+ int i = 0;
+
+ tmp = read_json_file(fname, buf, bufsize);
+ if (!tmp)
+ return NULL;
+
+ /* save parsed file in config */
+ json_object_object_add(cfg, fname, tmp);
+
+ /* allocate a new obisfile struct */
+ ret = malloc(sizeof(struct obisfile));
+ if (!ret)
+ return NULL;
+
+ debug("parsed %s\n", fname);
+ memset(ret, 0, sizeof(struct obisfile));
+ memset(ret->scale, 1, sizeof(ret->scale));
+ ret->filename = fname;
+ json_object_object_foreach(tmp, code, obisobject) {
+ i++;
+ if (i >= MAX_OBISCODES)
+ continue;
+ ret->code[i] = code;
+ ret->n = i;
+ json_object_object_foreach(obisobject, key, val) {
+ if (!strncmp(key, "name", 4))
+ ret->alias[i] = json_object_get_string(val);
+ else if (!strncmp(key, "value", 5) && !strncmp(code, "1-1:0.2.129.255", 15))
+ ret->listname = json_object_get_string(val);
+ else if (!strncmp(key, "unit", 4))
+ ret->unit[i] = json_object_get_string(val);
+ else if (!strncmp(key, "scale", 5))
+ ret->scale[i] = json_object_get_int(val);
+ }
+ }
+ return ret;
+}
+
+/*
+ * free memory allocated outside libjson-c. Strings returned by
+ * libjson-c will be freed when the json object refcount goes to zero
+ */
+static void free_obisfiles()
+{
+ struct obisfile *tmp;
+ int i;
+
+ for (i = 0; i < MAX_LISTS; i++) {
+ tmp = obisfiles[i];
+ obisfiles[i] = NULL;
+ if (!tmp)
+ continue;
+ free(tmp);
+ }
+}
+
+static json_object *read_config(const char *fname, char *buf, size_t bufsize)
+{
+ json_object *tmp, *obis, *ret;
+ const char *name;
+ int i, count = 0;
+
+ ret = read_json_file(fname, buf, bufsize);
+ if (!ret)
+ return NULL;
+
+ /* support rereading config */
+ free_obisfiles();
+
+ /* read all the OBIS definitions so we can look up aliases */
+ if (json_object_object_get_ex(ret, "obisdefs", &tmp) && json_object_is_type(tmp, json_type_array)) {
+ /* create an object for the parsed files */
+ obis = json_object_new_object();
+ json_object_object_add(ret, "obisfiles", obis);
+
+ for (i = 0; i < json_object_array_length(tmp); i++) {
+ name = json_object_get_string(json_object_array_get_idx(tmp, i));
+ if (count >= MAX_LISTS) {
+ fprintf(stderr, "Too many 'obisdefs' - ignoring '%s'\n", name);
+ continue;
+ }
+ obisfiles[count] = parse_obisfile(obis, name, buf, bufsize);
+
+ /* increase counter only if the file could be parsed */
+ if (obisfiles[count])
+ count++;
+ }
+ }
+
+ /* post process config - adding helper structures */
+ process_cfg(ret, buf, bufsize);
+
+ return ret;
+}
+
static struct option main_options[] = {
{ "help", 0, 0, 'h' },
@@ -1384,14 +1688,20 @@ int main(int argc, char *argv[])
}
}
+ if (serfd < 0) {
+ usage(argv[0]);
+ return 0;
+ }
+
/* print banner */
fprintf(stderr, "%s version %s\n", argv[0], VERSION);
-
-
/* initialize mqtt client and read buffer */
mosquitto_lib_init();
mosq = mosquitto_new(mqttid, clean_session, NULL);
+#ifdef DEBUG
+ mosquitto_log_callback_set(mosq, libmosquitto_log_callback);
+#endif
buf = malloc(BUFSIZE);
if (!buf || !mosq) {
fprintf(stderr, "Error: Out of memory.\n");
@@ -1400,9 +1710,10 @@ int main(int argc, char *argv[])
}
/* read config file */
- cfg = read_config(cfgfile, buf, BUFSIZE);
+ cfg = read_config(cfgfile, (char *)buf, BUFSIZE);
if (!cfg)
fprintf(stderr, "Failed to parse '%s' - will not publish anything to '%s'\n", cfgfile, broker);
+ debug("%s\n", json_object_to_json_string_ext(cfg, JSON_C_TO_STRING_PRETTY));
/* configure broker connection */
if (mqttuser)
@@ -1437,7 +1748,12 @@ err:
if (serfd > 0 && serfd != STDIN_FILENO)
close(serfd);
+
+ sleep(1);
free(buf);
+ free_obisfiles();
+ if (cfg)
+ json_object_put(cfg);
mosquitto_disconnect(mosq);
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();