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("burrow/loft/status", "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);
165 static int use_daemon;
166 static int use_debug;
168 static struct opt_section options = {
170 OPT_HELP("A daemon for controlling the solid state relay module via MQTT"),
172 OPT_HELP("Options:"),
173 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
174 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
181 int main(int argc UNUSED, char **argv)
184 opt_parse(&options, argv+1);
187 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
188 log_set_default_stream(ls);
191 log_default_stream()->levels &= ~(1U << L_DEBUG);
194 if (err = libusb_init(&usb_ctxt))
195 die("Cannot initialize libusb: error %d", err);
197 libusb_set_debug(usb_ctxt, 3);
201 mosquitto_lib_init();
202 mosq = mosquitto_new("ssrd", 1, NULL);
204 die("Mosquitto: initialization failed");
206 mosquitto_log_callback_set(mosq, mqtt_log_callback);
207 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
209 if (mosquitto_will_set(mosq, "burrow/loft/status", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
210 die("Mosquitto: unable to set will");
212 if (mosquitto_connect(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
213 die("Mosquitto: connect failed");
219 time_t now = time(NULL);
220 if (now < next_run) {
221 int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
222 if (err == MOSQ_ERR_NO_CONN) {
223 err = mosquitto_reconnect(mosq);
224 if (err == MOSQ_ERR_SUCCESS)
227 msg(L_ERROR, "Mosquitto: cannot reconnect, error %d", err);
228 } else if (err != MOSQ_ERR_SUCCESS)
229 msg(L_ERROR, "Mosquitto: loop returned error %d", err);
237 int t = get_u32_be(resp+4);
238 msg(L_DEBUG, "Measured raw temperature %d", t);
240 mqtt_publish("burrow/loft/temperature", "%.3f", t / 1000.);
241 mqtt_publish("burrow/loft/timestamp", "%llu", (unsigned long long) now);