]> mj.ucw.cz Git - home-hw.git/blob - ssr/host/burrow-ssrd.c
Cleanup
[home-hw.git] / ssr / host / burrow-ssrd.c
1 /*
2  *      A MQTT Gateway Daemon for the Solid State Relay module
3  *
4  *      (c) 2018 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/log.h>
9 #include <ucw/mainloop.h>
10 #include <ucw/opt.h>
11 #include <ucw/strtonum.h>
12 #include <ucw/unaligned.h>
13
14 #include <stdarg.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <syslog.h>
19 #include <time.h>
20
21 #include <libusb-1.0/libusb.h>
22 #include <mosquitto.h>
23
24 struct libusb_context *usb_ctxt;
25 struct libusb_device_handle *devh;
26
27 struct mosquitto *mosq;
28
29 static u32 ssr_state;
30
31 void open_device(void)
32 {
33         int err;
34         libusb_device **devlist;
35         ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
36         if (devn < 0)
37                 die("Cannot enumerate USB devices: error %d", (int) devn);
38
39         for (ssize_t i=0; i<devn; i++) {
40                 struct libusb_device_descriptor desc;
41                 libusb_device *dev = devlist[i];
42                 if (!libusb_get_device_descriptor(dev, &desc)) {
43                         if (desc.idVendor == 0x4242 && desc.idProduct == 0x0002) {
44                                 msg(L_INFO, "Found SSR module at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
45
46                                 if (err = libusb_open(dev, &devh))
47                                         die("Cannot open device: error %d", err);
48                                 libusb_reset_device(devh);
49                                 if (err = libusb_claim_interface(devh, 0))
50                                         die("Cannot claim interface: error %d", err);
51
52                                 libusb_free_device_list(devlist, 1);
53                                 return;
54                         }
55                 }
56         }
57
58         libusb_free_device_list(devlist, 1);
59         die("Device not found");
60 }
61
62 static byte req[64], resp[64];
63
64 static int transaction(uint req_len, uint resp_len)
65 {
66         int err, transferred;
67         if (err = libusb_bulk_transfer(devh, 0x01, req, req_len, &transferred, 2000))
68                 die("Transfer failed: error %d\n", err);
69         // printf("Transferred %d bytes\n", transferred);
70
71         int received;
72         if (err = libusb_bulk_transfer(devh, 0x82, resp, 64, &received, 2000))
73                 die("Receive failed: error %d\n", err);
74         // printf("Received %d bytes\n", received);
75
76         if ((uint) received < resp_len)
77                 die("Received short packet (%u out of %u bytes)", received, resp_len);
78
79         if (received >= 4) {
80                 uint status = get_u32_be(resp);
81                 if (status)
82                         die("Received error status %08x", status);
83         }
84
85         return received;
86 }
87
88 static void set_relays(void)
89 {
90         msg(L_INFO, "Setting relays to %02x", ssr_state);
91         put_u32_be(req, 1);
92         put_u32_be(req+4, ssr_state);
93         transaction(8, 4);
94 }
95
96 static void mqtt_publish(const char *topic, const char *fmt, ...)
97 {
98         va_list args;
99         va_start(args, fmt);
100         char m[256];
101         int l = vsnprintf(m, sizeof(m), fmt, args);
102         if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS)
103                 msg(L_ERROR, "Mosquitto: publish failed");
104         va_end(args);
105 }
106
107 static void mqtt_setup(void)
108 {
109         if (mosquitto_subscribe(mosq, NULL, "burrow/loft/#", 1) != MOSQ_ERR_SUCCESS)
110                 die("Mosquitto: subscribe failed");
111
112         mqtt_publish("burrow/loft/status", "ok");
113 }
114
115 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
116 {
117         // msg(L_INFO, "MQTT(%d): %s", level, message);
118 }
119
120 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
121 {
122         char val[256];
123         if (m->payloadlen >= sizeof(val) - 1) {
124                 msg(L_ERROR, "Invalid value for topic %s", m->topic);
125                 return;
126         }
127         memcpy(val, m->payload, m->payloadlen);
128         val[m->payloadlen] = 0;
129         msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
130
131         if (!strcmp(m->topic, "burrow/loft/fan")) {
132                 uint x;
133                 const char *err;
134                 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 4) {
135                         msg(L_ERROR, "Received invalid fan setting %s: %s", val, err);
136                         x = 0;
137                 }
138                 msg(L_INFO, "Setting fan level to %u", x);
139                 ssr_state &= ~7U;
140                 switch (x) {
141                         case 1:
142                                 ssr_state |= 4;
143                                 break;
144                         case 2:
145                                 ssr_state |= 2;
146                                 break;
147                         case 3:
148                                 ssr_state |= 1;
149                                 break;
150                 }
151                 set_relays();
152         } else if (!strcmp(m->topic, "burrow/loft/circulation")) {
153                 uint x;
154                 const char *err;
155                 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 2) {
156                         msg(L_ERROR, "Received invalid circulation setting %s: %s", val, err);
157                         x = 0;
158                 }
159                 msg(L_INFO, "Setting circulation to %u", x);
160                 ssr_state &= ~8U;
161                 if (x)
162                         ssr_state |= 8;
163         }
164 }
165
166 static int use_daemon;
167 static int use_debug;
168
169 static struct opt_section options = {
170         OPT_ITEMS {
171                 OPT_HELP("A daemon for controlling the solid state relay module via MQTT"),
172                 OPT_HELP(""),
173                 OPT_HELP("Options:"),
174                 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
175                 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
176                 OPT_HELP_OPTION,
177                 OPT_CONF_OPTIONS,
178                 OPT_END
179         }
180 };
181
182 int main(int argc UNUSED, char **argv)
183 {
184         log_init(argv[0]);
185         opt_parse(&options, argv+1);
186
187         if (use_daemon) {
188                 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
189                 log_set_default_stream(ls);
190         }
191         if (!use_debug)
192                 log_default_stream()->levels &= ~(1U << L_DEBUG);
193
194         int err;
195         if (err = libusb_init(&usb_ctxt))
196                 die("Cannot initialize libusb: error %d", err);
197         if (use_debug)
198                 libusb_set_debug(usb_ctxt, 3);
199         open_device();
200         set_relays();
201
202         mosquitto_lib_init();
203         mosq = mosquitto_new("ssrd", 1, NULL);
204         if (!mosq)
205                 die("Mosquitto: initialization failed");
206
207         mosquitto_log_callback_set(mosq, mqtt_log_callback);
208         mosquitto_message_callback_set(mosq, mqtt_msg_callback);
209
210         if (mosquitto_will_set(mosq, "burrow/loft/status", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
211                 die("Mosquitto: unable to set will");
212
213         if (mosquitto_connect(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
214                 die("Mosquitto: connect failed");
215
216         mqtt_setup();
217
218         time_t next_run = 0;
219         for (;;) {
220                 time_t now = time(NULL);
221                 if (now < next_run) {
222                         int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
223                         if (err == MOSQ_ERR_NO_CONN) {
224                                 err = mosquitto_reconnect(mosq);
225                                 if (err == MOSQ_ERR_SUCCESS)
226                                         mqtt_setup();
227                                 else
228                                         msg(L_ERROR, "Mosquitto: cannot reconnect, error %d", err);
229                         } else if (err != MOSQ_ERR_SUCCESS)
230                                 msg(L_ERROR, "Mosquitto: loop returned error %d", err);
231                         continue;
232                 }
233
234                 next_run = now + 5;
235
236                 put_u32_be(req, 2);
237                 transaction(8, 8);
238                 int t = get_u32_be(resp+4);
239                 msg(L_DEBUG, "Measured raw temperature %d", t);
240
241                 mqtt_publish("burrow/loft/temperature", "%.3f", t / 1000.);
242                 mqtt_publish("burrow/loft/timestamp", "%u", (int) now);
243         }
244 }