/*
* A gateway between MQTT and Prometheus
*
- * (c) 2018 Martin Mares <mj@ucw.cz>
+ * (c) 2018--2019 Martin Mares <mj@ucw.cz>
*/
#include <ucw/lib.h>
#include <mosquitto.h>
+#define MEASUREMENT_TIMEOUT 120
+
static struct mosquitto *mosq;
struct attr {
.metric = "temp_loft",
.help = "Temperature in the loft [degC]",
.type = "gauge",
- .topic = "burrow/loft/temperature"
+ .topic = "burrow/temp/loft",
},
{
.metric = "loft_fan",
.metric = "temp_ursarium",
.help = "Temperature in the Ursarium [degC]",
.type = "gauge",
- .topic = "burrow/arexxd/ursarium"
+ .topic = "burrow/temp/ursarium"
},
{
.metric = "temp_catarium",
.help = "Temperature in the Catarium [degC]",
.type = "gauge",
- .topic = "burrow/arexxd/catarium"
+ .topic = "burrow/temp/catarium"
},
{
.metric = "temp_garage",
.help = "Temperature in the garage [degC]",
.type = "gauge",
- .topic = "burrow/arexxd/garage"
+ .topic = "burrow/temp/garage"
+ },
+ {
+ .metric = "temp_kitchen",
+ .help = "Temperature in the kitchen [degC]",
+ .type = "gauge",
+ .topic = "burrow/temp/kitchen"
+ },
+ {
+ .metric = "rh_ursarium",
+ .help = "Relative humidity in the Ursarium [%]",
+ .type = "gauge",
+ .topic = "burrow/temp/ursarium-rh"
+ },
+ {
+ .metric = "temp_catarium_clock",
+ .help = "Temperature on Catarium clock [degC]",
+ .type = "gauge",
+ .topic = "burrow/temp/clock"
+ },
+ {
+ .metric = "press_catarium_clock",
+ .help = "Pressure on Catarium clock [Pa]",
+ .type = "gauge",
+ .topic = "burrow/pressure/clock"
+ },
+ {
+ .metric = "air_inside_intake",
+ .help = "Temperature of air intake from inside [degC]",
+ .type = "gauge",
+ .topic = "burrow/air/inside-intake",
+ },
+ {
+ .metric = "air_inside_exhaust",
+ .help = "Temperature of air exhaust to inside [degC]",
+ .type = "gauge",
+ .topic = "burrow/air/inside-exhaust",
+ },
+ {
+ .metric = "air_outside_intake",
+ .help = "Temperature of air intake from outside [degC]",
+ .type = "gauge",
+ .topic = "burrow/air/outside-intake",
+ },
+ {
+ .metric = "air_outside_exhaust",
+ .help = "Temperature of air exhaust to outside [degC]",
+ .type = "gauge",
+ .topic = "burrow/air/outside-exhaust",
+ },
+ {
+ .metric = "air_mixed",
+ .help = "Temperature of mixed air [degC]",
+ .type = "gauge",
+ .topic = "burrow/air/mixed",
+ },
+ {
+ .metric = "air_inside_intake_avg",
+ .help = "Average temperature of air intake from inside [degC]",
+ .type = "gauge",
+ .topic = "burrow/avg/air/inside-intake",
+ },
+ {
+ .metric = "air_outside_intake_avg",
+ .help = "Average temperature of air intake from outside [degC]",
+ .type = "gauge",
+ .topic = "burrow/avg/air/outside-intake",
+ },
+ {
+ .metric = "air_bypass",
+ .help = "Heat exchanger bypass (0-1)",
+ .type = "gauge",
+ .topic = "burrow/air/bypass"
+ },
+ {
+ .metric = "air_fan_pwm",
+ .help = "Heat exchanger fan PWM (0-255)",
+ .type = "gauge",
+ .topic = "burrow/air/exchanger-fan"
+ },
+ {
+ // Common heading for all voltages
+ .metric = "pm_voltage",
+ .help = "Voltage between phases and neutral [V]",
+ .type = "gauge",
+ },
+ {
+ .metric = "pm_voltage{phase=\"L1N\"}",
+ .topic = "burrow/power/voltage/l1n",
+ },
+ {
+ .metric = "pm_voltage{phase=\"L2N\"}",
+ .topic = "burrow/power/voltage/l2n",
+ },
+ {
+ .metric = "pm_voltage{phase=\"L3N\"}",
+ .topic = "burrow/power/voltage/l3n",
+ },
+ {
+ // Common heading for all currents
+ .metric = "pm_current",
+ .help = "Current through phases [A]",
+ .type = "gauge",
+ },
+ {
+ .metric = "pm_current{phase=\"L1\"}",
+ .topic = "burrow/power/current/l1",
+ },
+ {
+ .metric = "pm_current{phase=\"L2\"}",
+ .topic = "burrow/power/current/l2",
+ },
+ {
+ .metric = "pm_current{phase=\"L3\"}",
+ .topic = "burrow/power/current/l3",
+ },
+ {
+ .metric = "pm_power",
+ .help = "Total power [W]",
+ .type = "gauge",
+ .topic = "burrow/power/power",
+ },
+ {
+ .metric = "pm_energy",
+ .help = "Total energy [kWh]",
+ .type = "gauge",
+ .topic = "burrow/power/energy",
},
{
- .metric = "temp_machinarium",
- .help = "Temperature in the Machinarium [degC]",
+ .metric = "pm_reactive_power",
+ .help = "Total reactive power [VAr]",
.type = "gauge",
- .topic = "burrow/arexxd/machinarium"
+ .topic = "burrow/power/reactive/power",
},
{
- .metric = "rh_garage",
- .help = "Relative humidity in the garage [%]",
+ .metric = "pm_reactive_energy",
+ .help = "Total reactive energy [kVArh]",
.type = "gauge",
- .topic = "burrow/arexxd/garage-rh"
+ .topic = "burrow/power/reactive/energy",
},
};
if (mosquitto_subscribe(mosq, NULL, "burrow/#", 1) != MOSQ_ERR_SUCCESS)
die("Mosquitto: subscribe failed");
- // mqtt_publish("burrow/loft/status", "ok");
+ mqtt_publish("status/prometheus", "ok");
}
}
static void http_answer(struct fastbuf *fb)
{
- char val[256];
+ char val[256], *w[2];
+ time_t now = time(NULL);
for (uint i=0; i < ARRAY_SIZE(attr_table); i++) {
+ const struct attr *a = &attr_table[i];
+
pthread_mutex_lock(&attr_mutex);
snprintf(val, sizeof(val), "%s", attr_values[i] ? : "");
pthread_mutex_unlock(&attr_mutex);
- if (val[0]) {
- const struct attr *a = &attr_table[i];
- if (a->help)
- bprintf(fb, "# HELP %s %s\n", a->metric, a->help);
- if (a->type)
- bprintf(fb, "# TYPE %s %s\n", a->metric, a->type);
- // FIXME: Add timestamp if known
- bprintf(fb, "%s %s\n", a->metric, val);
+
+ if (a->help)
+ bprintf(fb, "# HELP %s %s\n", a->metric, a->help);
+ if (a->type)
+ bprintf(fb, "# TYPE %s %s\n", a->metric, a->type);
+
+ if (!val[0])
+ continue;
+ int fields = str_wordsplit(val, w, ARRAY_SIZE(w));
+ if (fields < 1)
+ continue;
+ if (fields >= 2) {
+ time_t t = atoll(w[1]);
+ if (t < now - MEASUREMENT_TIMEOUT)
+ continue;
+ }
+
+ if (a->topic) {
+ bprintf(fb, "%s %s", a->metric, val);
+#if 0
+ // Prometheus does not like our timestamps -- why?
+ if (fields >= 2)
+ bprintf(fb, " %s", w[1]);
+#endif
+ bputc(fb, '\n');
}
}
}
static struct opt_section options = {
OPT_ITEMS {
- OPT_HELP("A daemon for controlling the solid state relay module via MQTT"),
+ OPT_HELP("A daemon for transferring MQTT data to Prometheus"),
OPT_HELP(""),
OPT_HELP("Options:"),
OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
mosquitto_log_callback_set(mosq, mqtt_log_callback);
mosquitto_message_callback_set(mosq, mqtt_msg_callback);
-#if 0
- // FIXME: Publish online/offline status
- if (mosquitto_will_set(mosq, "burrow/loft/status", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
+ if (mosquitto_will_set(mosq, "status/prometheus", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
die("Mosquitto: unable to set will");
-#endif
if (mosquitto_connect_async(mosq, "10.32.184.5", 1883, 60) != MOSQ_ERR_SUCCESS)
die("Mosquitto: connect failed");