2 * Boiler System Bus Interface Daemon
4 * (c) 2020 Martin Mares <mj@ucw.cz>
10 #include <ucw/string.h>
11 #include <ucw/unaligned.h>
23 #include <mosquitto.h>
25 typedef unsigned char byte;
27 typedef unsigned int uint;
29 #include "../firmware/interface.h"
33 static struct mosquitto *mosq;
34 static bool mqtt_connected;
36 static void mqtt_publish(const char *topic, const char *fmt, ...)
43 int l = vsnprintf(m, sizeof(m), fmt, args);
44 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
45 if (err != MOSQ_ERR_SUCCESS)
46 msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
52 static void mqtt_publish_ephemeral(const char *topic, const char *fmt, ...)
59 int l = vsnprintf(m, sizeof(m), fmt, args);
60 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, false);
61 if (err != MOSQ_ERR_SUCCESS)
62 msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
68 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
71 msg(L_DEBUG, "MQTT: Connection established");
72 mqtt_connected = true;
73 mqtt_publish("status/bsb", "ok");
74 } else if (mqtt_connected) {
75 msg(L_DEBUG, "MQTT: Connection lost");
76 mqtt_connected = false;
80 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
82 msg(L_DEBUG, "MQTT(%d): %s", level, message);
85 static void mqtt_init(void)
89 mosq = mosquitto_new("bsbd", 1, NULL);
91 die("Mosquitto: Initialization failed");
93 mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
94 mosquitto_log_callback_set(mosq, mqtt_log_callback);
96 if (mosquitto_will_set(mosq, "status/bsb", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
97 die("Mosquitto: Unable to set will");
99 if (mosquitto_connect_async(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
100 die("Mosquitto: Unable to connect");
102 if (mosquitto_loop_start(mosq))
103 die("Mosquitto: Cannot start service thread");
108 static struct libusb_context *usb_ctxt;
109 static struct libusb_device_handle *devh;
111 static void usb_error(const char *msg, ...)
115 ucw_vmsg(L_ERROR, msg, args);
124 static void open_device(void)
127 libusb_device **devlist;
128 ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
130 die("Cannot enumerate USB devices: error %d", (int) devn);
132 for (ssize_t i=0; i<devn; i++) {
133 struct libusb_device_descriptor desc;
134 libusb_device *dev = devlist[i];
135 if (!libusb_get_device_descriptor(dev, &desc)) {
136 if (desc.idVendor == BSB_USB_VENDOR && desc.idProduct == BSB_USB_PRODUCT) {
137 msg(L_INFO, "Found device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
139 if (err = libusb_open(dev, &devh)) {
140 usb_error("Cannot open device: error %d", err);
143 libusb_reset_device(devh);
144 if (err = libusb_claim_interface(devh, 0)) {
145 usb_error("Cannot claim interface: error %d", err);
155 libusb_free_device_list(devlist, 1);
158 static void init_usb(void)
161 if (err = libusb_init(&usb_ctxt))
162 die("Cannot initialize libusb: error %d", err);
163 // libusb_set_debug(usb_ctxt, 3);
170 static const char * const stat_names[] = {
176 static int get_s16_be(const byte *x)
178 int val = get_u16_be(x);
180 return val - 0x10000;
185 static void process_stats(time_t t, byte *resp, uint len)
187 for (uint i=0; i < ARRAY_SIZE(stat_names) && 4*i + 3 < (uint) len; i++) {
189 snprintf(item, sizeof(item), "bsb/stats/%s", stat_names[i]);
190 mqtt_publish(item, "%u %lld", (uint) get_u32_le(resp+4*i), (long long) t);
194 static void process_info(time_t t, byte *p, uint len)
199 u32 addr = get_u32_be(p);
206 int err = get_u16_be(p);
207 mqtt_publish("burrow/heating/error", "%d %lld", err, (long long) t);
212 mqtt_publish("burrow/heating/clock", "%04d-%02d-%02dT%02d:%02d %lld",
213 get_u16_be(p) + 1900, p[2], p[3],
220 int temp = get_s16_be(p);
221 uint press = get_u16_be(p + 2);
222 mqtt_publish("burrow/heating/outside-temp", "%.2f %lld", temp / 64., (long long) t);
223 mqtt_publish("burrow/heating/water-pressure", "%.1f %lld", press / 10., (long long) t);
229 int temp = get_s16_be(p);
230 mqtt_publish("burrow/heating/circuit1/mix-temp", "%.2f %lld", temp / 64., (long long) t);
236 uint m = get_u16_be(p);
237 mqtt_publish("burrow/heating/circuit1/mix-valve", "%u %lld", m, (long long) t);
238 mqtt_publish("burrow/heating/circuit1/pump", "%u %lld", (p[3] == 3), (long long) t);
244 mqtt_publish("burrow/heating/circuit1/active", "%u %lld", (p[8] != 0), (long long) t);
250 mqtt_publish("burrow/heating/circuit2/active", "%u %lld", (p[8] != 0), (long long) t);
256 mqtt_publish("burrow/heating/water/active", "%u %lld", (p[1] != 0), (long long) t);
262 int temp = get_s16_be(p);
263 mqtt_publish("burrow/heating/circuit1/room-temp", "%.2f %lld", temp / 64., (long long) t);
269 int temp = get_s16_be(p);
270 mqtt_publish("burrow/heating/circuit2/room-temp", "%.2f %lld", temp / 64., (long long) t);
276 static void process_answer(time_t t, byte *p, uint len)
281 u32 addr = get_u32_be(p);
289 static void process_frame(time_t t, byte *pkt, uint len)
292 msg(L_ERROR, "Received empty frame");
296 byte status = *pkt++;
300 msg(L_ERROR, "Frame transmit status: %u", status);
305 msg(L_ERROR, "Received truncated frame");
310 mem_to_hex(hex, pkt, len, ' ');
311 mqtt_publish_ephemeral("bsb/frame", "%s", hex, t);
313 msg(L_DEBUG, "<< %s", hex);
315 byte *body = pkt + BF_BODY;
316 uint body_len = len - BF_BODY - 2; // 2 for CRC
318 switch (pkt[BF_OP]) {
320 process_info(t, body, body_len);
323 process_answer(t, body, body_len);
328 static int use_daemon;
329 static int use_debug;
331 static struct opt_section options = {
333 OPT_HELP("A daemon for controlling the air conditioning controller via MQTT"),
335 OPT_HELP("Options:"),
336 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
337 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
344 int main(int argc UNUSED, char **argv)
347 opt_parse(&options, argv+1);
350 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
351 log_set_default_stream(ls);
354 log_default_stream()->levels &= ~(1U << L_DEBUG);
359 time_t now = time(NULL);
360 time_t last_stats = 0;
361 // time_t last_query = now;
367 msg(L_INFO, "Waiting for device to appear...");
375 if (last_stats + 60 < now) {
376 if ((received = libusb_control_transfer(devh, 0xc0, 0x00, 0, 0, resp, sizeof(resp), 1000)) < 0) {
377 usb_error("Receive failed: error %d", received);
381 process_stats(now, resp, received);
386 if (last_query + 10 < now) {
387 byte pkt[] = { 0xdc, 0xc2, 0x00, 0x0b, 0x06, 0x3d, 0x2e, 0x11, 0x25, 0x00, 0x00 };
388 if (err = libusb_bulk_transfer(devh, 0x01, pkt, sizeof(pkt), &received, 2000)) {
389 usb_error("Send failed: error %d", err);
392 // msg(L_DEBUG"Send OK: %d bytes", received);
398 if (err = libusb_interrupt_transfer(devh, 0x82, resp, 64, &received, 1000)) {
399 if (err != LIBUSB_ERROR_TIMEOUT)
400 usb_error("Receive failed: error %d", err);
404 process_frame(now, resp, received);