2 * A MQTT Gateway Daemon for the Power Meter
4 * (c) 2019 Martin Mares <mj@ucw.cz>
10 #include <ucw/strtonum.h>
11 #include <ucw/stkstring.h>
22 #include <mosquitto.h>
26 static struct mosquitto *mosq;
27 static bool mqtt_connected;
29 static void mqtt_error(const char *operation, int err, bool teardown)
31 msg(L_ERROR, "Mosquitto: %s failed: error %d", operation, err);
34 mosquitto_destroy(mosq);
36 mqtt_connected = false;
37 } else if (err == MOSQ_ERR_NO_CONN || err == MOSQ_ERR_CONN_REFUSED || err == MOSQ_ERR_CONN_LOST) {
38 mqtt_connected = false;
42 static void mqtt_publish(const char *topic, const char *fmt, ...)
49 int l = vsnprintf(m, sizeof(m), fmt, args);
50 msg(L_DEBUG, "MQTT > %s %s", topic, m);
51 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
52 if (err != MOSQ_ERR_SUCCESS)
53 mqtt_error("publish", err, false);
59 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
61 // msg(L_INFO, "MQTT(%d): %s", level, message);
64 static bool mqtt_connect(void)
72 mosq = mosquitto_new("powerd", 1, NULL);
74 die("Mosquitto: initialization failed");
76 mosquitto_log_callback_set(mosq, mqtt_log_callback);
78 err = mosquitto_will_set(mosq, "status/power-meter", 4, "dead", 0, true);
79 if (err != MOSQ_ERR_SUCCESS) {
80 mqtt_error("will_set", err, true);
84 err = mosquitto_connect(mosq, "127.0.0.1", 1883, 60);
85 if (err != MOSQ_ERR_SUCCESS) {
86 mqtt_error("connect", err, true);
90 err = mosquitto_reconnect(mosq);
91 if (err != MOSQ_ERR_SUCCESS) {
92 mqtt_error("reconnect", err, false);
97 mqtt_connected = true;
99 mqtt_publish("status/power-meter", "ok");
101 return mqtt_connected;
106 static modbus_t *modbus;
107 static bool mb_is_open;
109 static void mb_error(const char *operation)
111 msg(L_ERROR, "MODBUS: %s failed: %s", operation, modbus_strerror(errno));
115 modbus_close(modbus);
123 static bool mb_connect(void)
128 // FIXME: Find the right device. Reconnect if needed.
129 modbus = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
135 modbus_set_slave(modbus, 42);
137 if (modbus_connect(modbus) < 0) {
147 static u16 mb_regs[22];
151 if (mb_regs[i+1] < 0x8000)
152 return (mb_regs[i+1] << 16) | mb_regs[i];
154 return ((mb_regs[i+1] - 65536) * 65536) + mb_regs[i];
157 static void scan_power_meter(time_t now)
161 if (modbus_read_registers(modbus, 0, 22, mb_regs) < 0) {
166 mqtt_publish("burrow/power/voltage/l1n", "%.1f %ld", get_s32(0) / 10., lnow);
167 mqtt_publish("burrow/power/voltage/l2n", "%.1f %ld", get_s32(2) / 10., lnow);
168 mqtt_publish("burrow/power/voltage/l3n", "%.1f %ld", get_s32(4) / 10., lnow);
170 mqtt_publish("burrow/power/current/l1", "%.3f %ld", get_s32(6) / 1000., lnow);
171 mqtt_publish("burrow/power/current/l1", "%.3f %ld", get_s32(8) / 1000., lnow);
172 mqtt_publish("burrow/power/current/l1", "%.3f %ld", get_s32(10) / 1000., lnow);
174 mqtt_publish("burrow/power/power", "%.1f %ld", get_s32(12) / 10., lnow);
175 mqtt_publish("burrow/power/energy", "%.1f %ld", get_s32(14) / 10., lnow);
177 mqtt_publish("burrow/power/reactive/power", "%.1f %ld", get_s32(18) / 10., lnow);
178 mqtt_publish("burrow/power/reactive/energy", "%.1f %ld", get_s32(20) / 10., lnow);
181 static int use_daemon;
182 static int use_debug;
184 static struct opt_section options = {
186 OPT_HELP("A daemon for sending power meter readings to MQTT"),
188 OPT_HELP("Options:"),
189 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
190 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
197 int main(int argc UNUSED, char **argv)
200 opt_parse(&options, argv+1);
203 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
204 log_set_default_stream(ls);
207 log_default_stream()->levels &= ~(1U << L_DEBUG);
209 mosquitto_lib_init();
213 if (!mqtt_connect() || !mb_connect()) {
218 time_t now = time(NULL);
219 if (now < next_run) {
220 int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
221 if (err != MOSQ_ERR_SUCCESS)
222 mqtt_error("loop", err, false);
227 scan_power_meter(now);