]> mj.ucw.cz Git - home-hw.git/blob - power/daemon/burrow-powerd.c
README
[home-hw.git] / power / daemon / burrow-powerd.c
1 /*
2  *      A MQTT Gateway Daemon for the Power Meter
3  *
4  *      (c) 2019 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/log.h>
9 #include <ucw/opt.h>
10 #include <ucw/strtonum.h>
11 #include <ucw/stkstring.h>
12
13 #include <errno.h>
14 #include <stdio.h>
15 #include <stdarg.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <syslog.h>
19 #include <unistd.h>
20
21 #include <modbus.h>
22 #include <mosquitto.h>
23
24 /*** MQTT ***/
25
26 static struct mosquitto *mosq;
27 static bool mqtt_connected;
28
29 static void mqtt_error(const char *operation, int err, bool teardown)
30 {
31         msg(L_ERROR, "Mosquitto: %s failed: error %d", operation, err);
32
33         if (teardown) {
34                 mosquitto_destroy(mosq);
35                 mosq = NULL;
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;
39         }
40 }
41
42 static void mqtt_publish(const char *topic, const char *fmt, ...)
43 {
44         va_list args;
45         va_start(args, fmt);
46
47         if (mqtt_connected) {
48                 char m[256];
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);
54         }
55
56         va_end(args);
57 }
58
59 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
60 {
61         // msg(L_INFO, "MQTT(%d): %s", level, message);
62 }
63
64 static bool mqtt_connect(void)
65 {
66         int err;
67
68         if (mqtt_connected)
69                 return true;
70
71         if (!mosq) {
72                 mosq = mosquitto_new("powerd", 1, NULL);
73                 if (!mosq)
74                         die("Mosquitto: initialization failed");
75
76                 mosquitto_log_callback_set(mosq, mqtt_log_callback);
77
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);
81                         return false;
82                 }
83
84                 err = mosquitto_connect(mosq, "127.0.0.1", 1883, 60);
85                 if (err != MOSQ_ERR_SUCCESS) {
86                         mqtt_error("connect", err, true);
87                         return false;
88                 }
89         } else {
90                 err = mosquitto_reconnect(mosq);
91                 if (err != MOSQ_ERR_SUCCESS) {
92                         mqtt_error("reconnect", err, false);
93                         return false;
94                 }
95         }
96
97         mqtt_connected = true;
98
99         mqtt_publish("status/power-meter", "ok");
100
101         return mqtt_connected;
102 }
103
104 /*** MODBUS ***/
105
106 static modbus_t *modbus;
107 static bool mb_is_open;
108
109 static void mb_error(const char *operation)
110 {
111         msg(L_ERROR, "MODBUS: %s failed: %s", operation, modbus_strerror(errno));
112
113         if (modbus) {
114                 if (mb_is_open) {
115                         modbus_close(modbus);
116                         mb_is_open = false;
117                 }
118                 modbus_free(modbus);
119                 modbus = NULL;
120         }
121 }
122
123 static bool mb_connect(void)
124 {
125         if (modbus)
126                 return true;
127
128         modbus = modbus_new_rtu("/dev/modbus-power", 9600, 'N', 8, 1);
129         if (!modbus) {
130                 mb_error("open");
131                 return false;
132         }
133
134         modbus_set_slave(modbus, 42);
135
136         if (modbus_connect(modbus) < 0) {
137                 mb_error("connect");
138                 return false;
139         }
140
141         mb_is_open = true;
142 }
143
144 /*** Main loop ***/
145
146 static u16 mb_regs[22];
147
148 s32 get_s32(uint i)
149 {
150         if (mb_regs[i+1] < 0x8000)
151                 return (mb_regs[i+1] << 16) | mb_regs[i];
152         else
153                 return ((mb_regs[i+1] - 65536) * 65536) + mb_regs[i];
154 }
155
156 static void scan_power_meter(time_t now)
157 {
158         long int lnow = now;
159
160         if (modbus_read_registers(modbus, 0, 22, mb_regs) < 0) {
161                 mb_error("read");
162                 return;
163         }
164
165         mqtt_publish("burrow/power/voltage/l1n", "%.1f %ld", get_s32(0) / 10., lnow);
166         mqtt_publish("burrow/power/voltage/l2n", "%.1f %ld", get_s32(2) / 10., lnow);
167         mqtt_publish("burrow/power/voltage/l3n", "%.1f %ld", get_s32(4) / 10., lnow);
168
169         mqtt_publish("burrow/power/current/l1", "%.3f %ld", get_s32(6) / 1000., lnow);
170         mqtt_publish("burrow/power/current/l2", "%.3f %ld", get_s32(8) / 1000., lnow);
171         mqtt_publish("burrow/power/current/l3", "%.3f %ld", get_s32(10) / 1000., lnow);
172
173         mqtt_publish("burrow/power/power", "%.1f %ld", get_s32(12) / 10., lnow);
174         mqtt_publish("burrow/power/energy", "%.1f %ld", get_s32(14) / 10., lnow);
175
176         mqtt_publish("burrow/power/reactive/power", "%.1f %ld", get_s32(18) / 10., lnow);
177         mqtt_publish("burrow/power/reactive/energy", "%.1f %ld", get_s32(20) / 10., lnow);
178 }
179
180 static int use_daemon;
181 static int use_debug;
182
183 static struct opt_section options = {
184         OPT_ITEMS {
185                 OPT_HELP("A daemon for sending power meter readings to MQTT"),
186                 OPT_HELP(""),
187                 OPT_HELP("Options:"),
188                 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
189                 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
190                 OPT_HELP_OPTION,
191                 OPT_CONF_OPTIONS,
192                 OPT_END
193         }
194 };
195
196 int main(int argc UNUSED, char **argv)
197 {
198         log_init(argv[0]);
199         opt_parse(&options, argv+1);
200
201         if (use_daemon) {
202                 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
203                 log_set_default_stream(ls);
204         }
205         if (!use_debug)
206                 log_default_stream()->levels &= ~(1U << L_DEBUG);
207
208         mosquitto_lib_init();
209
210         time_t next_run = 0;
211         for (;;) {
212                 if (!mqtt_connect() || !mb_connect()) {
213                         sleep(5);
214                         continue;
215                 }
216
217                 time_t now = time(NULL);
218                 if (now < next_run) {
219                         int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
220                         if (err != MOSQ_ERR_SUCCESS)
221                                 mqtt_error("loop", err, false);
222                         continue;
223                 }
224
225                 next_run = now + 10;
226                 scan_power_meter(now);
227         }
228 }