2 * A MQTT Gateway Daemon for the Air Conditioning Controller
4 * (c) 2019 Martin Mares <mj@ucw.cz>
10 #include <ucw/strtonum.h>
11 #include <ucw/stkstring.h>
21 #include <mosquitto.h>
23 #include "../firmware/registers.h"
25 static struct mosquitto *mosq;
26 static modbus_t *modbus;
28 static void mqtt_publish(const char *topic, const char *fmt, ...)
33 int l = vsnprintf(m, sizeof(m), fmt, args);
34 if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS)
35 msg(L_ERROR, "Mosquitto: publish failed");
39 static void mqtt_setup(void)
41 if (mosquitto_subscribe(mosq, NULL, "burrow/air/#", 1) != MOSQ_ERR_SUCCESS)
42 die("Mosquitto: subscribe failed");
44 mqtt_publish("status/aircon", "ok");
47 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
49 // msg(L_INFO, "MQTT(%d): %s", level, message);
52 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
55 if (m->payloadlen >= sizeof(val) - 1) {
56 msg(L_ERROR, "Invalid value for topic %s", m->topic);
59 memcpy(val, m->payload, m->payloadlen);
60 val[m->payloadlen] = 0;
61 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
63 if (!strcmp(m->topic, "burrow/air/bypass")) {
66 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 2) {
67 msg(L_ERROR, "Received invalid bypass setting %s: %s", val, err);
70 if (modbus_write_bit(modbus, AIRCON_COIL_EXCHANGER_BYPASS, x) < 0)
71 msg(L_ERROR, "Modbus write failed: %s", modbus_strerror(errno));
72 } else if (!strcmp(m->topic, "burrow/air/exchanger-fan")) {
75 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 256) {
76 msg(L_ERROR, "Received invalid exchanger fan setting %s: %s", val, err);
79 if (modbus_write_register(modbus, AIRCON_HREG_EXCHANGER_FAN, x) < 0)
80 msg(L_ERROR, "Modbus write failed: %s", modbus_strerror(errno));
84 static void scan_temperatures(time_t now)
86 static const char * const temp_names[] = {
95 if (modbus_read_input_registers(modbus, AIRCON_IREG_TEMP_FROM_INSIDE, 5, regs) < 0) {
96 msg(L_ERROR, "Modbus read failed: %s", modbus_strerror(errno));
100 for (uint i=0; i<5; i++) {
101 if (regs[i] != 0x8000) {
105 mqtt_publish(stk_printf("burrow/air/%s", temp_names[i]), "%.3f %llu", t / 100., (unsigned long long) now);
110 static int use_daemon;
111 static int use_debug;
113 static struct opt_section options = {
115 OPT_HELP("A daemon for controlling the air conditioning controller via MQTT"),
117 OPT_HELP("Options:"),
118 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
119 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
126 int main(int argc UNUSED, char **argv)
129 opt_parse(&options, argv+1);
132 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
133 log_set_default_stream(ls);
136 log_default_stream()->levels &= ~(1U << L_DEBUG);
138 // FIXME: Find the right device. Reconnect if needed.
139 modbus = modbus_new_rtu("/dev/ttyUSB1", 19200, 'E', 8, 1);
141 die("Unable to establish Modbus context");
142 modbus_set_slave(modbus, 42);
143 if (modbus_connect(modbus) < 0)
144 die("Unable to connect to Modbus");
146 mosquitto_lib_init();
147 mosq = mosquitto_new("aircond", 1, NULL);
149 die("Mosquitto: initialization failed");
151 mosquitto_log_callback_set(mosq, mqtt_log_callback);
152 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
154 if (mosquitto_will_set(mosq, "status/aircon", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
155 die("Mosquitto: unable to set will");
157 if (mosquitto_connect(mosq, "127.0.0.1", 1883, 60) != MOSQ_ERR_SUCCESS)
158 die("Mosquitto: connect failed");
164 time_t now = time(NULL);
165 if (now < next_run) {
166 int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
167 if (err == MOSQ_ERR_NO_CONN) {
168 err = mosquitto_reconnect(mosq);
169 if (err == MOSQ_ERR_SUCCESS)
172 msg(L_ERROR, "Mosquitto: cannot reconnect, error %d", err);
173 } else if (err != MOSQ_ERR_SUCCESS)
174 msg(L_ERROR, "Mosquitto: loop returned error %d", err);
179 scan_temperatures(now);