CFLAGS=$(USB_CFLAGS) $(UCW_CFLAGS) -std=gnu1x -O2 -Wall -Wextra -Wno-parentheses
LDFLAGS=$(USB_LDFLAGS) $(UCW_LDFLAGS) -lmosquitto
-all: burrow-bsb bsb-monitor
+all: burrow-bsbd bsb-monitor
-# burrow-bsb: burrow-bsb.c ../firmware/interface.h
+# burrow-bsbd: burrow-bsbd.c ../firmware/interface.h
# bsb-monitor: bsb-monitor.c ../firmware/interface.h
+
+upload:
+ rsync -av burrow-bsbd bsb-monitor micac:/root/burrow/
+++ /dev/null
-/*
- * Boiler System Bus Interface Daemon
- *
- * (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>
-#include <mosquitto.h>
-
-typedef unsigned char byte;
-typedef uint32_t u32;
-typedef unsigned int uint;
-
-#include "../firmware/interface.h"
-
-/*** 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, 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);
- }
-
- va_end(args);
-}
-
-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);
- ucw_vmsg(L_ERROR, 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) {
- 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);
- 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 void init_usb(void)
-{
- 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 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)
-{
- if (len < 4)
- return;
-
- 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 process_answer(byte *p, uint len)
-{
- 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) {
- msg(L_ERROR, "Received empty frame");
- return;
- }
-
- byte status = *pkt++;
- len--;
-
- if (!len) {
- msg(L_ERROR, "Frame transmit status: %u", status);
- return;
- }
-
- if (len < 6) {
- msg(L_ERROR, "Received truncated frame");
- return;
- }
-
- 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 BSB_OP_ANSWER:
- process_answer(params, param_len);
- break;
- }
-}
-
-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)
-{
- 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 err, received;
- byte resp[64];
-
- for (;;) {
- if (!devh) {
- msg(L_INFO, "Waiting for device to appear...");
- 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;
- }
-
- 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)) {
- usb_error("Send failed: error %d", err);
- continue;
- } else {
- // 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)
- usb_error("Receive failed: error %d", err);
- continue;
- }
-
- process_frame(now, resp, received);
- }
-}
--- /dev/null
+/*
+ * Boiler System Bus Interface Daemon
+ *
+ * (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>
+#include <mosquitto.h>
+
+typedef unsigned char byte;
+typedef uint32_t u32;
+typedef unsigned int uint;
+
+#include "../firmware/interface.h"
+
+/*** 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, 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);
+ }
+
+ va_end(args);
+}
+
+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);
+ ucw_vmsg(L_ERROR, 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) {
+ 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);
+ 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 void init_usb(void)
+{
+ 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 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)
+{
+ if (len < 4)
+ return;
+
+ 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 process_answer(byte *p, uint len)
+{
+ 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) {
+ msg(L_ERROR, "Received empty frame");
+ return;
+ }
+
+ byte status = *pkt++;
+ len--;
+
+ if (!len) {
+ msg(L_ERROR, "Frame transmit status: %u", status);
+ return;
+ }
+
+ if (len < 6) {
+ msg(L_ERROR, "Received truncated frame");
+ return;
+ }
+
+ 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 BSB_OP_ANSWER:
+ process_answer(params, param_len);
+ break;
+ }
+}
+
+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)
+{
+ 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 err, received;
+ byte resp[64];
+
+ for (;;) {
+ if (!devh) {
+ msg(L_INFO, "Waiting for device to appear...");
+ 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;
+ }
+
+ 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)) {
+ usb_error("Send failed: error %d", err);
+ continue;
+ } else {
+ // 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)
+ usb_error("Receive failed: error %d", err);
+ continue;
+ }
+
+ process_frame(now, resp, received);
+ }
+}