]> mj.ucw.cz Git - home-hw.git/commitdiff
Aircon daemon: First bits
authorMartin Mares <mj@ucw.cz>
Tue, 16 Jul 2019 19:36:12 +0000 (21:36 +0200)
committerMartin Mares <mj@ucw.cz>
Tue, 16 Jul 2019 19:36:12 +0000 (21:36 +0200)
aircon/daemon/Makefile [new file with mode: 0644]
aircon/daemon/burrow-aircond.c [new file with mode: 0644]

diff --git a/aircon/daemon/Makefile b/aircon/daemon/Makefile
new file mode 100644 (file)
index 0000000..b7651ad
--- /dev/null
@@ -0,0 +1,23 @@
+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)
+MB_CFLAGS := $(shell $(PC) --cflags libmodbus)
+MB_LDFLAGS := $(shell $(PC) --libs libmodbus)
+UCW_CFLAGS := $(shell $(PC) --cflags libucw)
+UCW_LDFLAGS := $(shell $(PC) --libs libucw)
+
+export PATH=$(TARGET_PATH_PKG)
+CC=$(TARGET_CC_NOCACHE)
+LD=$(TARGET_LD_NOCACHE)
+CFLAGS=$(TARGET_CFLAGS) $(MB_CFLAGS) $(UCW_CFLAGS) -std=gnu99
+LDFLAGS=$(TARGET_LDFLAGS) $(MB_LDFLAGS) $(UCW_LDFLAGS) -lmosquitto
+
+all: burrow-aircond upload
+
+burrow-aircond: burrow-aircond.c
+
+upload:
+       rsync -av burrow-aircond micac:/root/burrow/
diff --git a/aircon/daemon/burrow-aircond.c b/aircon/daemon/burrow-aircond.c
new file mode 100644 (file)
index 0000000..629c202
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ *     A MQTT Gateway Daemon for the Air Conditioning Controller
+ *
+ *     (c) 2019 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/log.h>
+#include <ucw/opt.h>
+#include <ucw/strtonum.h>
+#include <ucw/stkstring.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <modbus.h>
+#include <mosquitto.h>
+
+#include "../firmware/registers.h"
+
+static struct mosquitto *mosq;
+static modbus_t *modbus;
+
+static void mqtt_publish(const char *topic, const char *fmt, ...)
+{
+       va_list args;
+       va_start(args, fmt);
+       char m[256];
+       int l = vsnprintf(m, sizeof(m), fmt, args);
+       if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS)
+               msg(L_ERROR, "Mosquitto: publish failed");
+       va_end(args);
+}
+
+static void mqtt_setup(void)
+{
+       if (mosquitto_subscribe(mosq, NULL, "burrow/air/#", 1) != MOSQ_ERR_SUCCESS)
+               die("Mosquitto: subscribe failed");
+
+       mqtt_publish("status/aircon", "ok");
+}
+
+static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
+{
+       // msg(L_INFO, "MQTT(%d): %s", level, message);
+}
+
+static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
+{
+       char val[256];
+       if (m->payloadlen >= sizeof(val) - 1) {
+               msg(L_ERROR, "Invalid value for topic %s", m->topic);
+               return;
+       }
+       memcpy(val, m->payload, m->payloadlen);
+       val[m->payloadlen] = 0;
+       msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
+
+       if (!strcmp(m->topic, "burrow/air/bypass")) {
+               uint x;
+               const char *err;
+               if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 2) {
+                       msg(L_ERROR, "Received invalid bypass setting %s: %s", val, err);
+                       return;
+               }
+               if (modbus_write_bit(modbus, AIRCON_COIL_EXCHANGER_BYPASS, x) < 0)
+                       msg(L_ERROR, "Modbus write failed: %s", modbus_strerror(errno));
+       } else if (!strcmp(m->topic, "burrow/air/exchanger-fan")) {
+               uint x;
+               const char *err;
+               if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 256) {
+                       msg(L_ERROR, "Received invalid exchanger fan setting %s: %s", val, err);
+                       return;
+               }
+               if (modbus_write_register(modbus, AIRCON_HREG_EXCHANGER_FAN, x) < 0)
+                       msg(L_ERROR, "Modbus write failed: %s", modbus_strerror(errno));
+       }
+}
+
+static void scan_temperatures(time_t now)
+{
+       static const char * const temp_names[] = {
+               "inside-intake",
+               "inside-exhaust",
+               "outside-intake",
+               "outside-exhaust",
+               "mixed",
+       };
+
+       u16 regs[5];
+       if (modbus_read_input_registers(modbus, AIRCON_IREG_TEMP_FROM_INSIDE, 5, regs) < 0) {
+               msg(L_ERROR, "Modbus read failed: %s", modbus_strerror(errno));
+               return;
+       }
+
+       for (uint i=0; i<5; i++) {
+               if (regs[i] != 0x8000) {
+                       int t = regs[i];
+                       if (t >= 0x8000)
+                               t -= 0x10000;
+                       mqtt_publish(stk_printf("burrow/air/%s", temp_names[i]), "%.3f %llu", t / 100., (unsigned long long) now);
+               }
+       }
+}
+
+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);
+
+       // FIXME: Find the right device. Reconnect if needed.
+       modbus = modbus_new_rtu("/dev/ttyUSB1", 19200, 'E', 8, 1);
+       if (!modbus)
+               die("Unable to establish Modbus context");
+       modbus_set_slave(modbus, 42);
+       if (modbus_connect(modbus) < 0)
+               die("Unable to connect to Modbus");
+
+       mosquitto_lib_init();
+       mosq = mosquitto_new("aircond", 1, NULL);
+       if (!mosq)
+               die("Mosquitto: initialization failed");
+
+       mosquitto_log_callback_set(mosq, mqtt_log_callback);
+       mosquitto_message_callback_set(mosq, mqtt_msg_callback);
+
+       if (mosquitto_will_set(mosq, "status/aircon", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
+               die("Mosquitto: unable to set will");
+
+       if (mosquitto_connect(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
+               die("Mosquitto: connect failed");
+
+       mqtt_setup();
+
+       time_t next_run = 0;
+       for (;;) {
+               time_t now = time(NULL);
+               if (now < next_run) {
+                       int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
+                       if (err == MOSQ_ERR_NO_CONN) {
+                               err = mosquitto_reconnect(mosq);
+                               if (err == MOSQ_ERR_SUCCESS)
+                                       mqtt_setup();
+                               else
+                                       msg(L_ERROR, "Mosquitto: cannot reconnect, error %d", err);
+                       } else if (err != MOSQ_ERR_SUCCESS)
+                               msg(L_ERROR, "Mosquitto: loop returned error %d", err);
+                       continue;
+               }
+
+               next_run = now + 10;
+               scan_temperatures(now);
+       }
+}