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>
23 #include <mosquitto.h>
25 #include "../firmware/registers.h"
29 static struct mosquitto *mosq;
30 static bool mqtt_connected;
32 static void mqtt_error(const char *operation, int err, bool teardown)
34 msg(L_ERROR, "Mosquitto: %s failed: error %d", operation, err);
37 mosquitto_destroy(mosq);
39 mqtt_connected = false;
40 } else if (err == MOSQ_ERR_NO_CONN || err == MOSQ_ERR_CONN_REFUSED || err == MOSQ_ERR_CONN_LOST) {
41 mqtt_connected = false;
45 static void mqtt_publish(const char *topic, const char *fmt, ...)
52 int l = vsnprintf(m, sizeof(m), fmt, args);
53 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
54 if (err != MOSQ_ERR_SUCCESS)
55 mqtt_error("publish", err, false);
61 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level UNUSED, const char *message UNUSED)
63 // msg(L_INFO, "MQTT(%d): %s", level, message);
66 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m);
68 static bool mqtt_connect(void)
76 mosq = mosquitto_new("aircond", 1, NULL);
78 die("Mosquitto: initialization failed");
80 mosquitto_log_callback_set(mosq, mqtt_log_callback);
81 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
83 err = mosquitto_will_set(mosq, "status/aircon", 4, "dead", 0, true);
84 if (err != MOSQ_ERR_SUCCESS) {
85 mqtt_error("will_set", err, true);
89 err = mosquitto_connect(mosq, "127.0.0.1", 1883, 60);
90 if (err != MOSQ_ERR_SUCCESS) {
91 mqtt_error("connect", err, true);
95 err = mosquitto_reconnect(mosq);
96 if (err != MOSQ_ERR_SUCCESS) {
97 mqtt_error("reconnect", err, false);
102 err = mosquitto_subscribe(mosq, NULL, "burrow/air/#", 1);
103 if (err != MOSQ_ERR_SUCCESS) {
104 mqtt_error("subscribe", err, false);
108 mqtt_connected = true;
110 mqtt_publish("status/aircon", "ok");
112 return mqtt_connected;
117 static modbus_t *modbus;
118 static bool mb_is_open;
120 static void mb_error(const char *operation, bool need_close)
122 msg(L_ERROR, "MODBUS: %s failed: %s", operation, modbus_strerror(errno));
124 if (need_close && modbus) {
126 modbus_close(modbus);
134 static bool mb_connect(void)
139 modbus = modbus_new_tcp("127.0.0.1", 4301);
140 // modbus = modbus_new_rtu("/dev/modbus-aircon", 19200, 'E', 8, 1);
142 mb_error("open", true);
146 // modbus_set_debug(modbus, 1);
147 modbus_set_slave(modbus, 42);
148 modbus_set_response_timeout(modbus, 5, 0);
150 if (modbus_connect(modbus) < 0) {
151 mb_error("connect", true);
161 static void scan_temperatures(time_t now)
163 static const char * const temp_names[] = {
172 if (modbus_read_input_registers(modbus, AIRCON_IREG_TEMP_FROM_INSIDE, 5, regs) < 0) {
173 mb_error("read", true);
177 for (uint i=0; i<5; i++) {
178 if (regs[i] != 0x8000) {
182 mqtt_publish(stk_printf("burrow/air/%s", temp_names[i]), "%.3f %llu", t / 100., (unsigned long long) now);
187 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
190 if (m->payloadlen >= sizeof(val) - 1) {
191 msg(L_ERROR, "Invalid value for topic %s", m->topic);
194 memcpy(val, m->payload, m->payloadlen);
195 val[m->payloadlen] = 0;
196 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
198 if (!strcmp(m->topic, "burrow/air/bypass")) {
201 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 2) {
202 msg(L_ERROR, "Received invalid bypass setting %s: %s", val, err);
205 if (modbus_write_bit(modbus, AIRCON_COIL_EXCHANGER_BYPASS, x) < 0)
206 mb_error("coil write", false);
207 } else if (!strcmp(m->topic, "burrow/air/exchanger-fan")) {
210 if ((err = str_to_uint(&x, val, NULL, 10 | STN_WHOLE)) || x >= 256) {
211 msg(L_ERROR, "Received invalid exchanger fan setting %s: %s", val, err);
214 if (modbus_write_register(modbus, AIRCON_HREG_EXCHANGER_FAN, x) < 0)
215 mb_error("fan register write", false);
216 } else if (!strcmp(m->topic, "burrow/air/aircon-remote")) {
217 if (strlen(val) != 1) {
218 msg(L_ERROR, "Received invalid aircon remote command %s", val);
221 if (modbus_write_register(modbus, AIRCON_HREG_REMOTE_CONTROL, val[0]) < 0)
222 mb_error("remote control register write", false);
226 static int use_daemon;
227 static int use_debug;
229 static struct opt_section options = {
231 OPT_HELP("A daemon for controlling the air conditioning controller via MQTT"),
233 OPT_HELP("Options:"),
234 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
235 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
242 int main(int argc UNUSED, char **argv)
245 opt_parse(&options, argv+1);
248 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
249 log_set_default_stream(ls);
252 log_default_stream()->levels &= ~(1U << L_DEBUG);
254 mosquitto_lib_init();
258 if (!mqtt_connect() || !mb_connect()) {
263 time_t now = time(NULL);
264 if (now < next_run) {
265 int err = mosquitto_loop(mosq, (next_run - now) * 1000, 1);
266 if (err != MOSQ_ERR_SUCCESS)
267 mqtt_error("loop", err, false);
272 scan_temperatures(now);