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