2 * A MQTT Gateway Daemon for the Solid State Relay module
4 * (c) 2018 Martin Mares <mj@ucw.cz>
10 #include <ucw/strtonum.h>
11 #include <ucw/unaligned.h>
20 #include <libusb-1.0/libusb.h>
21 #include <mosquitto.h>
23 static struct libusb_context *usb_ctxt;
24 static struct libusb_device_handle *devh;
26 static struct mosquitto *mosq;
30 void open_device(void)
33 libusb_device **devlist;
34 ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
36 die("Cannot enumerate USB devices: error %d", (int) devn);
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));
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);
51 libusb_free_device_list(devlist, 1);
57 libusb_free_device_list(devlist, 1);
58 die("Device not found");
61 static byte req[64], resp[64];
63 static int transaction(uint req_len, uint resp_len)
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);
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);
75 if ((uint) received < resp_len)
76 die("Received short packet (%u out of %u bytes)", received, resp_len);
79 uint status = get_u32_be(resp);
81 die("Received error status %08x", status);
87 static void set_relays(void)
89 msg(L_INFO, "Setting relays to %02x", ssr_state);
91 put_u32_be(req+4, ssr_state);
95 static void mqtt_publish(const char *topic, const char *fmt, ...)
100 int l = vsnprintf(m, sizeof(m), fmt, args);
101 if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS)
102 msg(L_ERROR, "Mosquitto: publish failed");
106 static void mqtt_setup(void)
108 if (mosquitto_subscribe(mosq, NULL, "burrow/loft/#", 1) != MOSQ_ERR_SUCCESS)
109 die("Mosquitto: subscribe failed");
111 mqtt_publish("status/loft-ssr", "ok");
114 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
116 // msg(L_INFO, "MQTT(%d): %s", level, message);
119 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
122 if (m->payloadlen >= sizeof(val) - 1) {
123 msg(L_ERROR, "Invalid value for topic %s", m->topic);
126 memcpy(val, m->payload, m->payloadlen);
127 val[m->payloadlen] = 0;
128 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
130 if (!strcmp(m->topic, "burrow/loft/fan")) {
133 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 4) {
134 msg(L_ERROR, "Received invalid fan setting %s: %s", val, err);
137 msg(L_INFO, "Setting fan level to %u", x);
151 } else if (!strcmp(m->topic, "burrow/loft/circulation")) {
154 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 2) {
155 msg(L_ERROR, "Received invalid circulation setting %s: %s", val, err);
158 msg(L_INFO, "Setting circulation to %u", x);
166 static int use_daemon;
167 static int use_debug;
169 static struct opt_section options = {
171 OPT_HELP("A daemon for controlling the solid state relay module via MQTT"),
173 OPT_HELP("Options:"),
174 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
175 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
182 int main(int argc UNUSED, char **argv)
185 opt_parse(&options, argv+1);
188 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
189 log_set_default_stream(ls);
192 log_default_stream()->levels &= ~(1U << L_DEBUG);
195 if (err = libusb_init(&usb_ctxt))
196 die("Cannot initialize libusb: error %d", err);
198 libusb_set_debug(usb_ctxt, 3);
202 mosquitto_lib_init();
203 mosq = mosquitto_new("ssrd", 1, NULL);
205 die("Mosquitto: initialization failed");
207 mosquitto_log_callback_set(mosq, mqtt_log_callback);
208 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
210 if (mosquitto_will_set(mosq, "status/loft-ssr", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
211 die("Mosquitto: unable to set will");
213 if (mosquitto_connect(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
214 die("Mosquitto: connect failed");
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)
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);
238 int t = get_u32_be(resp+4);
239 msg(L_DEBUG, "Measured raw temperature %d", t);
241 mqtt_publish("burrow/temp/loft", "%.3f %llu", t / 1000., (unsigned long long) now);