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;
28 static u32 ssr_state = ~0U;
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(u32 mask, u32 data)
89 u32 new = (ssr_state & mask) | data;
90 if (new != ssr_state) {
91 msg(L_INFO, "Setting relays to %02x", new);
93 put_u32_be(req+4, new);
99 static void mqtt_publish(const char *topic, const char *fmt, ...)
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");
110 static void mqtt_setup(void)
112 if (mosquitto_subscribe(mosq, NULL, "burrow/loft/#", 1) != MOSQ_ERR_SUCCESS)
113 die("Mosquitto: subscribe failed");
115 mqtt_publish("status/loft-ssr", "ok");
118 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
120 // msg(L_INFO, "MQTT(%d): %s", level, message);
123 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
126 if (m->payloadlen >= sizeof(val) - 1) {
127 msg(L_ERROR, "Invalid value for topic %s", m->topic);
130 memcpy(val, m->payload, m->payloadlen);
131 val[m->payloadlen] = 0;
132 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
134 if (!strcmp(m->topic, "burrow/loft/fan")) {
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);
141 msg(L_INFO, "Setting fan level to %u", x);
156 } else if (!strcmp(m->topic, "burrow/loft/circulation")) {
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);
163 msg(L_INFO, "Setting circulation to %u", x);
164 set_relays(~8U, (x ? 8 : 0));
168 static int use_daemon;
169 static int use_debug;
171 static struct opt_section options = {
173 OPT_HELP("A daemon for controlling the solid state relay module via MQTT"),
175 OPT_HELP("Options:"),
176 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
177 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
184 int main(int argc UNUSED, char **argv)
187 opt_parse(&options, argv+1);
190 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
191 log_set_default_stream(ls);
194 log_default_stream()->levels &= ~(1U << L_DEBUG);
197 if (err = libusb_init(&usb_ctxt))
198 die("Cannot initialize libusb: error %d", err);
200 libusb_set_debug(usb_ctxt, 3);
204 mosquitto_lib_init();
205 mosq = mosquitto_new("ssrd", 1, NULL);
207 die("Mosquitto: initialization failed");
209 mosquitto_log_callback_set(mosq, mqtt_log_callback);
210 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
212 if (mosquitto_will_set(mosq, "status/loft-ssr", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
213 die("Mosquitto: unable to set will");
215 if (mosquitto_connect(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
216 die("Mosquitto: connect failed");
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)
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);
240 int t = get_u32_be(resp+4);
241 msg(L_DEBUG, "Measured raw temperature %d", t);
243 mqtt_publish("burrow/temp/loft", "%.3f %llu", t / 1000., (unsigned long long) now);