]> mj.ucw.cz Git - home-hw.git/commitdiff
BSB: Daemon for Turris communicating via MQTT
authorMartin Mares <mj@ucw.cz>
Fri, 28 Feb 2020 15:46:14 +0000 (16:46 +0100)
committerMartin Mares <mj@ucw.cz>
Fri, 28 Feb 2020 15:46:14 +0000 (16:46 +0100)
MQTT
bsb/daemon/Makefile
bsb/daemon/bsb-monitor.c [new file with mode: 0644]
bsb/daemon/burrow-bsb.c
bsb/firmware/interface.h

diff --git a/MQTT b/MQTT
index 85e4c83b529f18f605485e8d7d32850007c1eb83..54e48560d488a21a5c2c6e85f4c4f1d552b7c387 100644 (file)
--- a/MQTT
+++ b/MQTT
@@ -38,9 +38,21 @@ burrow/temp/ursarium-rh
 
 burrow/pressure/clock
 
+burrow/heating/water-pressure  [bar]
+burrow/heating/outside-temp
+burrow/heating/circuit1/room-temp
+burrow/heating/circuit1/mix-temp
+burrow/heating/circuit1/mix-valve
+burrow/heating/circuit2/room-temp
+
+bsb/stats/*
+bsb/frame                      hex dump of raw frames received
+
 status/aircon                  ok/dead
 status/auto                    ok/dead
+status/bsb                     ok/dead
 status/clock                   ok/dead
+status/influx                  ok/dead
 status/loft-ssr                        ok/dead
 status/power-meter             ok/dead
-status/prometheus              ok/dead
+status/prometheus              ok/dead         # Obsolete
index 1e97498cae88e04cb74d2ee99c62cf317c2c11eb..9f2ce2bcefa8816a44aca8951e8b84a60c520c58 100644 (file)
@@ -1,4 +1,24 @@
+BUILD_FOR_TURRIS=1
+
+ifdef BUILD_FOR_TURRIS
+
+TOPDIR=/root/turris
+
+include $(TOPDIR)/rules.mk
+include $(TOPDIR)/include/package.mk
+
+PC := PATH=$(STAGING_DIR_HOST)/bin:$(PATH) PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) PKG_CONFIG_LIBDIR=$(PKG_CONFIG_PATH) STAGING_PREFIX=$(STAGING_DIR)/usr $(PKG_CONFIG)
+
+export PATH=$(TARGET_PATH_PKG)
+CC=$(TARGET_CC_NOCACHE)
+LD=$(TARGET_LD_NOCACHE)
+
+else
+
 PC := PKG_CONFIG_PATH=/home/mj/tmp/bsb/root/lib/pkgconfig pkg-config
+
+endif
+
 USB_CFLAGS := $(shell $(PC) --cflags libusb-1.0)
 USB_LDFLAGS := $(shell $(PC) --libs libusb-1.0)
 UCW_CFLAGS := $(shell $(PC) --cflags libucw)
@@ -7,9 +27,7 @@ UCW_LDFLAGS := $(shell $(PC) --libs libucw)
 CFLAGS=$(USB_CFLAGS) $(UCW_CFLAGS) -std=gnu1x -O2 -Wall -Wextra -Wno-parentheses
 LDFLAGS=$(USB_LDFLAGS) $(UCW_LDFLAGS) -lmosquitto
 
-all: burrow-bsb
-
-burrow-usb: burrow-usb.c ../firmware/interface.h
+all: burrow-bsb bsb-monitor
 
-clean:
-       rm -f burrow-bsb
+# burrow-bsb: burrow-bsb.c ../firmware/interface.h
+# bsb-monitor: bsb-monitor.c ../firmware/interface.h
diff --git a/bsb/daemon/bsb-monitor.c b/bsb/daemon/bsb-monitor.c
new file mode 100644 (file)
index 0000000..e8bd4ca
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ *     Boiler System Bus Interface Daemon
+ *
+ *     (c) 2020 Martin Mares <mj@ucw.cz>
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libusb.h>
+
+static struct libusb_context *usb_ctxt;
+static struct libusb_device_handle *devh;
+
+typedef unsigned char byte;
+typedef uint32_t u32;
+typedef unsigned int uint;
+
+#include "../firmware/interface.h"
+
+static void die(const char *msg, ...)
+{
+       va_list args;
+       va_start(args, msg);
+
+       vfprintf(stderr, msg, args);
+       fputc('\n', stderr);
+
+       exit(1);
+}
+
+static void usb_error(const char *msg, ...)
+{
+       va_list args;
+       va_start(args, msg);
+       vfprintf(stderr, msg, args);
+       fputc('\n', stderr);
+       va_end(args);
+
+       if (devh) {
+               libusb_close(devh);
+               devh = NULL;
+       }
+}
+
+static void open_device(void)
+{
+       int err;
+       libusb_device **devlist;
+       ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
+       if (devn < 0)
+               die("Cannot enumerate USB devices: error %d", (int) devn);
+
+       for (ssize_t i=0; i<devn; i++) {
+               struct libusb_device_descriptor desc;
+               libusb_device *dev = devlist[i];
+               if (!libusb_get_device_descriptor(dev, &desc)) {
+                       if (desc.idVendor == BSB_USB_VENDOR && desc.idProduct == BSB_USB_PRODUCT) {
+                               fprintf(stderr, "Found device at usb%d.%d\n", libusb_get_bus_number(dev), libusb_get_device_address(dev));
+
+                               if (err = libusb_open(dev, &devh)) {
+                                       usb_error("Cannot open device: error %d", err);
+                                       goto out;
+                               }
+                               libusb_reset_device(devh);
+                               if (err = libusb_claim_interface(devh, 0)) {
+                                       usb_error("Cannot claim interface: error %d", err);
+                                       goto out;
+                               }
+
+                               goto out;
+                       }
+               }
+       }
+
+out:
+       libusb_free_device_list(devlist, 1);
+}
+
+static inline uint get_u32_le(byte *p)
+{
+       return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
+}
+
+static const char * const stat_names[] = {
+#define P(x) #x,
+       BSB_STATS
+#undef P
+};
+
+static void show_stats(byte *resp, uint len)
+{
+       printf("# Stats:");
+       for (uint i=0; 4*i + 3 < (uint) len; i++)
+               printf(" %s=%u", (i < sizeof(stat_names) / sizeof(stat_names[0]) ? stat_names[i] : "?"), get_u32_le(resp+4*i));
+       printf("\n");
+
+       fflush(stdout);
+}
+
+static void show_packet(byte *pkt, uint len)
+{
+       time_t now = time(NULL);
+       struct tm *tm = localtime(&now);
+       char timebuf[64];
+       strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm);
+       printf("%s ", timebuf);
+
+       if (!len) {
+               printf("ERROR: Received empty frame!\n");
+               return;
+       }
+
+       byte status = *pkt++;
+       len--;
+
+       if (!len) {
+               printf("ERROR: Transmit status: %u\n", status);
+               return;
+       }
+
+#if 0
+       printf(": [%d]", status);
+       for (uint i=0; i<len; i++)
+               printf(" %02x", pkt[i]);
+       putchar('\n');
+#endif
+
+       printf("%02x -> %02x: ", pkt[BF_SRC] ^ 0x80, pkt[BF_DEST]);
+       if (status)
+               printf("[REPLY] ");
+       switch (pkt[4]) {
+               case 2:
+                       printf("INFO %04x:%04x =", (pkt[5]<<8) | pkt[6], (pkt[7]<<8) | pkt[8]);
+                       for (uint i=9; i<len; i++)
+                               printf(" %02x", pkt[i]);
+                       putchar('\n');
+                       break;
+               case 6:
+                       printf("GET %04x:%04x\n", (pkt[6]<<8) | pkt[5], (pkt[7]<<8) | pkt[8]);
+                       break;
+               case 7:
+                       printf("RET %04x:%04x =", (pkt[5]<<8) | pkt[6], (pkt[7]<<8) | pkt[8]);
+                       for (uint i=9; i<len; i++)
+                               printf(" %02x", pkt[i]);
+                       putchar('\n');
+                       break;
+               default:
+                       printf("??? type=%02x\n", pkt[4]);
+       }
+
+       fflush(stdout);
+}
+
+int main(void)
+{
+       int err;
+       if (err = libusb_init(&usb_ctxt))
+               die("Cannot initialize libusb: error %d", err);
+       // libusb_set_debug(usb_ctxt, 3);
+       open_device();
+
+       time_t now = time(NULL);
+       time_t last_stats = 0;
+       time_t last_query = now;
+       int received;
+       byte resp[64];
+
+       for (;;) {
+               if (!devh) {
+                       fprintf(stderr, "Waiting for device to appear...\n");
+                       while (!devh) {
+                               sleep(5);
+                               open_device();
+                       }
+               }
+
+               now = time(NULL);
+               if (last_stats + 60 < now) {
+                       if ((received = libusb_control_transfer(devh, 0xc0, 0x00, 0, 0, resp, sizeof(resp), 1000)) < 0) {
+                               usb_error("Receive failed: error %d", received);
+                               continue;
+                       }
+
+                       show_stats(resp, received);
+                       last_stats = now;
+               }
+
+               if (last_query + 10 < now) {
+                       byte pkt[] = { 0xdc, 0xc2, 0x00, 0x0b, 0x06, 0x3d, 0x2e, 0x11, 0x25, 0x00, 0x00 };
+                       if (err = libusb_bulk_transfer(devh, 0x01, pkt, sizeof(pkt), &received, 2000)) {
+                               printf("Send failed: error %d\n", err);
+                               // usb_error("Receive failed: error %d", received);
+                               // continue;
+                       } else {
+                               // printf("Send OK: %d bytes\n", received);
+                       }
+                       last_query = now;
+               }
+
+               if (err = libusb_interrupt_transfer(devh, 0x82, resp, 64, &received, 1000)) {
+                       if (err != LIBUSB_ERROR_TIMEOUT)
+                               usb_error("Receive failed: error %d", err);
+                       continue;
+               }
+
+               show_packet(resp, received);
+       }
+}
index e8bd4ca0b5de47a9636b6e440f62e46d71a4e225..6b83a5fa03754341caef4d5ab4ec0b6cd0913539 100644 (file)
@@ -4,18 +4,23 @@
  *     (c) 2020 Martin Mares <mj@ucw.cz>
  */
 
+#include <ucw/lib.h>
+#include <ucw/log.h>
+#include <ucw/opt.h>
+#include <ucw/string.h>
+#include <ucw/unaligned.h>
+
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include <syslog.h>
 #include <time.h>
 #include <unistd.h>
 
 #include <libusb.h>
-
-static struct libusb_context *usb_ctxt;
-static struct libusb_device_handle *devh;
+#include <mosquitto.h>
 
 typedef unsigned char byte;
 typedef uint32_t u32;
@@ -23,22 +28,75 @@ typedef unsigned int uint;
 
 #include "../firmware/interface.h"
 
-static void die(const char *msg, ...)
+/*** MQTT ***/
+
+static struct mosquitto *mosq;
+static bool mqtt_connected;
+
+static void mqtt_publish(const char *topic, const char *fmt, ...)
 {
        va_list args;
-       va_start(args, msg);
+       va_start(args, fmt);
+
+       if (mqtt_connected) {
+               char m[256];
+               int l = vsnprintf(m, sizeof(m), fmt, args);
+               int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
+               if (err != MOSQ_ERR_SUCCESS)
+                       msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
+       }
 
-       vfprintf(stderr, msg, args);
-       fputc('\n', stderr);
+       va_end(args);
+}
 
-       exit(1);
+static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
+{
+       if (!status) {
+               msg(L_DEBUG, "MQTT: Connection established");
+               mqtt_publish("status/bsb", "ok");
+               mqtt_connected = true;
+       } else if (mqtt_connected) {
+               msg(L_DEBUG, "MQTT: Connection lost");
+               mqtt_connected = false;
+       }
+}
+
+static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
+{
+       msg(L_DEBUG, "MQTT(%d): %s", level, message);
 }
 
+static void mqtt_init(void)
+{
+       mosquitto_lib_init();
+
+       mosq = mosquitto_new("bsbd", 1, NULL);
+       if (!mosq)
+               die("Mosquitto: Initialization failed");
+
+       mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
+       mosquitto_log_callback_set(mosq, mqtt_log_callback);
+
+       if (mosquitto_will_set(mosq, "status/bsb", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
+               die("Mosquitto: Unable to set will");
+
+       if (mosquitto_connect_async(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
+               die("Mosquitto: Unable to connect");
+
+       if (mosquitto_loop_start(mosq))
+               die("Mosquitto: Cannot start service thread");
+}
+
+/*** USB ***/
+
+static struct libusb_context *usb_ctxt;
+static struct libusb_device_handle *devh;
+
 static void usb_error(const char *msg, ...)
 {
        va_list args;
        va_start(args, msg);
-       vfprintf(stderr, msg, args);
+       ucw_vmsg(L_ERROR, msg, args);
        fputc('\n', stderr);
        va_end(args);
 
@@ -61,7 +119,7 @@ static void open_device(void)
                libusb_device *dev = devlist[i];
                if (!libusb_get_device_descriptor(dev, &desc)) {
                        if (desc.idVendor == BSB_USB_VENDOR && desc.idProduct == BSB_USB_PRODUCT) {
-                               fprintf(stderr, "Found device at usb%d.%d\n", libusb_get_bus_number(dev), libusb_get_device_address(dev));
+                               msg(L_INFO, "Found device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
 
                                if (err = libusb_open(dev, &devh)) {
                                        usb_error("Cannot open device: error %d", err);
@@ -82,37 +140,99 @@ out:
        libusb_free_device_list(devlist, 1);
 }
 
-static inline uint get_u32_le(byte *p)
+static void init_usb(void)
 {
-       return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
+       int err;
+       if (err = libusb_init(&usb_ctxt))
+               die("Cannot initialize libusb: error %d", err);
+       // libusb_set_debug(usb_ctxt, 3);
+       open_device();
+
 }
 
+/*** Protocol ***/
+
 static const char * const stat_names[] = {
 #define P(x) #x,
        BSB_STATS
 #undef P
 };
 
-static void show_stats(byte *resp, uint len)
+static void process_stats(time_t t, byte *resp, uint len)
+{
+       for (uint i=0; i < ARRAY_SIZE(stat_names) && 4*i + 3 < (uint) len; i++) {
+               char item[64];
+               snprintf(item, sizeof(item), "burrow/bsb/stats/%s", stat_names[i]);
+               mqtt_publish(item, "%u", (uint) get_u32_le(resp+4*i), t);
+       }
+}
+
+static void process_info(byte *p, uint len)
 {
-       printf("# Stats:");
-       for (uint i=0; 4*i + 3 < (uint) len; i++)
-               printf(" %s=%u", (i < sizeof(stat_names) / sizeof(stat_names[0]) ? stat_names[i] : "?"), get_u32_le(resp+4*i));
-       printf("\n");
+       if (len < 4)
+               return;
 
-       fflush(stdout);
+       u32 addr = get_u32_be(p);
+       p += 4;
+       len -= 4;
+
+       switch (addr) {
+               case 0x05000219:
+                       if (len >= 4) {
+                               uint temp = get_u16_be(p);
+                               uint press = get_u16_be(p + 2);
+                               mqtt_publish("burrow/heating/outside-temp", "%.2f", temp / 64.);
+                               mqtt_publish("burrow/heating/water-pressure", "%.1f", press / 10.);
+                       }
+                       break;
+               case 0x05000229:
+                       // AGU.2 status
+                       if (len >= 2) {
+                               uint temp = get_u16_be(p);
+                               mqtt_publish("burrow/heating/circuit1/mix-temp", "%.2f", temp / 64.);
+                       }
+                       break;
+               case 0x05040227:
+                       // AGU.2 control
+                       if (len >= 2) {
+                               uint m = get_u16_be(p);
+                               mqtt_publish("burrow/heating/circuit1/mix-valve", "%u", m);
+                       }
+                       break;
+               case 0x3d2d0215:
+                       // Room 1 status
+                       if (len >= 2) {
+                               uint temp = get_u16_be(p);
+                               mqtt_publish("burrow/heating/circuit1/room-temp", "%.2f", temp / 64.);
+                       }
+                       break;
+               case 0x3e2e0215:
+                       // Room 2 status
+                       if (len >= 2) {
+                               uint temp = get_u16_be(p);
+                               mqtt_publish("burrow/heating/circuit2/room-temp", "%.2f", temp / 64.);
+                       }
+                       break;
+       }
 }
 
-static void show_packet(byte *pkt, uint len)
+static void process_answer(byte *p, uint len)
 {
-       time_t now = time(NULL);
-       struct tm *tm = localtime(&now);
-       char timebuf[64];
-       strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm);
-       printf("%s ", timebuf);
+       if (len < 4)
+               return;
+
+       u32 addr = get_u32_be(p);
+       p += 4;
+       len -= 4;
+
+       switch (addr) {
+       }
+}
 
+static void process_frame(time_t t, byte *pkt, uint len)
+{
        if (!len) {
-               printf("ERROR: Received empty frame!\n");
+               msg(L_ERROR, "Received empty frame");
                return;
        }
 
@@ -120,60 +240,74 @@ static void show_packet(byte *pkt, uint len)
        len--;
 
        if (!len) {
-               printf("ERROR: Transmit status: %u\n", status);
+               msg(L_ERROR, "Frame transmit status: %u", status);
                return;
        }
 
-#if 0
-       printf(": [%d]", status);
-       for (uint i=0; i<len; i++)
-               printf(" %02x", pkt[i]);
-       putchar('\n');
-#endif
+       if (len < 6) {
+               msg(L_ERROR, "Received truncated frame");
+               return;
+       }
 
-       printf("%02x -> %02x: ", pkt[BF_SRC] ^ 0x80, pkt[BF_DEST]);
-       if (status)
-               printf("[REPLY] ");
-       switch (pkt[4]) {
-               case 2:
-                       printf("INFO %04x:%04x =", (pkt[5]<<8) | pkt[6], (pkt[7]<<8) | pkt[8]);
-                       for (uint i=9; i<len; i++)
-                               printf(" %02x", pkt[i]);
-                       putchar('\n');
-                       break;
-               case 6:
-                       printf("GET %04x:%04x\n", (pkt[6]<<8) | pkt[5], (pkt[7]<<8) | pkt[8]);
+       char hex[3*len + 1];
+       mem_to_hex(hex, pkt, len, ' ');
+       mqtt_publish("bsb/frame", "%s", hex, t);
+
+       msg(L_DEBUG, "<< %s", hex);
+
+       byte *params = pkt + BF_PARAMS;
+       uint param_len = len - BF_PARAMS - 2;   // 2 for CRC
+
+       switch (pkt[BF_OP]) {
+               case BSB_OP_INFO:
+                       process_info(params, param_len);
                        break;
-               case 7:
-                       printf("RET %04x:%04x =", (pkt[5]<<8) | pkt[6], (pkt[7]<<8) | pkt[8]);
-                       for (uint i=9; i<len; i++)
-                               printf(" %02x", pkt[i]);
-                       putchar('\n');
+               case BSB_OP_ANSWER:
+                       process_answer(params, param_len);
                        break;
-               default:
-                       printf("??? type=%02x\n", pkt[4]);
        }
-
-       fflush(stdout);
 }
 
-int main(void)
+static int use_daemon;
+static int use_debug;
+
+static struct opt_section options = {
+       OPT_ITEMS {
+               OPT_HELP("A daemon for controlling the air conditioning controller via MQTT"),
+               OPT_HELP(""),
+               OPT_HELP("Options:"),
+               OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
+               OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
+               OPT_HELP_OPTION,
+               OPT_CONF_OPTIONS,
+               OPT_END
+       }
+};
+
+int main(int argc UNUSED, char **argv)
 {
-       int err;
-       if (err = libusb_init(&usb_ctxt))
-               die("Cannot initialize libusb: error %d", err);
-       // libusb_set_debug(usb_ctxt, 3);
-       open_device();
+       log_init(argv[0]);
+       opt_parse(&options, argv+1);
+
+       if (use_daemon) {
+               struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
+               log_set_default_stream(ls);
+       }
+       if (!use_debug)
+               log_default_stream()->levels &= ~(1U << L_DEBUG);
+
+       mqtt_init();
+       init_usb();
 
        time_t now = time(NULL);
        time_t last_stats = 0;
-       time_t last_query = now;
-       int received;
+       // time_t last_query = now;
+       int err, received;
        byte resp[64];
 
        for (;;) {
                if (!devh) {
-                       fprintf(stderr, "Waiting for device to appear...\n");
+                       msg(L_INFO, "Waiting for device to appear...");
                        while (!devh) {
                                sleep(5);
                                open_device();
@@ -187,21 +321,22 @@ int main(void)
                                continue;
                        }
 
-                       show_stats(resp, received);
+                       process_stats(now, resp, received);
                        last_stats = now;
                }
 
+#if 0
                if (last_query + 10 < now) {
                        byte pkt[] = { 0xdc, 0xc2, 0x00, 0x0b, 0x06, 0x3d, 0x2e, 0x11, 0x25, 0x00, 0x00 };
                        if (err = libusb_bulk_transfer(devh, 0x01, pkt, sizeof(pkt), &received, 2000)) {
-                               printf("Send failed: error %d\n", err);
-                               // usb_error("Receive failed: error %d", received);
-                               // continue;
+                               usb_error("Send failed: error %d", err);
+                               continue;
                        } else {
-                               // printf("Send OK: %d bytes\n", received);
+                               // msg(L_DEBUG"Send OK: %d bytes", received);
                        }
                        last_query = now;
                }
+#endif
 
                if (err = libusb_interrupt_transfer(devh, 0x82, resp, 64, &received, 1000)) {
                        if (err != LIBUSB_ERROR_TIMEOUT)
@@ -209,6 +344,6 @@ int main(void)
                        continue;
                }
 
-               show_packet(resp, received);
+               process_frame(now, resp, received);
        }
 }
index 6be9f8a1ff06c9da39c8b02dd8d18db133b5b253..8c032c499cc96261ff84a7c2d9c0eda105eba3eb 100644 (file)
@@ -84,6 +84,7 @@ enum bsb_frame {
        BF_DEST,
        BF_LEN,
        BF_OP,
+       BF_PARAMS,
 };
 
 enum bsb_address {