#include <stdlib.h>
#include <string.h>
#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
#include <modbus.h>
#include <mosquitto.h>
#include "../firmware/registers.h"
+/*** MQTT ***/
+
static struct mosquitto *mosq;
-static modbus_t *modbus;
+static bool mqtt_connected;
+
+static void mqtt_error(const char *operation, int err, bool teardown)
+{
+ msg(L_ERROR, "Mosquitto: %s failed: error %d", operation, err);
+
+ if (teardown) {
+ mosquitto_destroy(mosq);
+ mosq = NULL;
+ mqtt_connected = false;
+ } else if (err == MOSQ_ERR_NO_CONN || err == MOSQ_ERR_CONN_REFUSED || err == MOSQ_ERR_CONN_LOST) {
+ mqtt_connected = false;
+ }
+}
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");
+
+ 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)
+ mqtt_error("publish", err, false);
+ }
+
va_end(args);
}
-static void mqtt_setup(void)
+static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level UNUSED, const char *message UNUSED)
{
- if (mosquitto_subscribe(mosq, NULL, "burrow/air/#", 1) != MOSQ_ERR_SUCCESS)
- die("Mosquitto: subscribe failed");
+ // 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);
+
+static bool mqtt_connect(void)
+{
+ int err;
+
+ if (mqtt_connected)
+ return true;
+
+ if (!mosq) {
+ 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);
+
+ err = mosquitto_will_set(mosq, "status/aircon", 4, "dead", 0, true);
+ if (err != MOSQ_ERR_SUCCESS) {
+ mqtt_error("will_set", err, true);
+ return false;
+ }
+
+ err = mosquitto_connect(mosq, "127.0.0.1", 1883, 60);
+ if (err != MOSQ_ERR_SUCCESS) {
+ mqtt_error("connect", err, true);
+ return false;
+ }
+ } else {
+ err = mosquitto_reconnect(mosq);
+ if (err != MOSQ_ERR_SUCCESS) {
+ mqtt_error("reconnect", err, false);
+ return false;
+ }
+ }
+
+ err = mosquitto_subscribe(mosq, NULL, "burrow/air/#", 1);
+ if (err != MOSQ_ERR_SUCCESS) {
+ mqtt_error("subscribe", err, false);
+ return false;
+ }
+
+ mqtt_connected = true;
mqtt_publish("status/aircon", "ok");
+
+ return mqtt_connected;
}
-static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
+/*** MODBUS ***/
+
+static modbus_t *modbus;
+static bool mb_is_open;
+
+static void mb_error(const char *operation, bool need_close)
{
- // msg(L_INFO, "MQTT(%d): %s", level, message);
+ msg(L_ERROR, "MODBUS: %s failed: %s", operation, modbus_strerror(errno));
+
+ if (need_close && modbus) {
+ if (mb_is_open) {
+ modbus_close(modbus);
+ mb_is_open = false;
+ }
+ modbus_free(modbus);
+ modbus = NULL;
+ }
}
-static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
+static bool mb_connect(void)
{
- char val[256];
- if (m->payloadlen >= sizeof(val) - 1) {
- msg(L_ERROR, "Invalid value for topic %s", m->topic);
- return;
+ if (modbus)
+ return true;
+
+ modbus = modbus_new_tcp("127.0.0.1", 4301);
+ // modbus = modbus_new_rtu("/dev/modbus-aircon", 19200, 'E', 8, 1);
+ if (!modbus) {
+ mb_error("open", true);
+ return false;
}
- 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));
+ // modbus_set_debug(modbus, 1);
+ modbus_set_slave(modbus, 42);
+ modbus_set_response_timeout(modbus, 5, 0);
+
+ if (modbus_connect(modbus) < 0) {
+ mb_error("connect", true);
+ return false;
}
+
+ mb_is_open = true;
+ return true;
}
+/*** Main loop ***/
+
static void scan_temperatures(time_t now)
{
static const char * const temp_names[] = {
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));
+ mb_error("read", true);
return;
}
}
}
+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)
+ mb_error("coil write", false);
+ } 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)
+ mb_error("fan register write", false);
+ } else if (!strcmp(m->topic, "burrow/air/aircon-remote")) {
+ if (strlen(val) != 1) {
+ msg(L_ERROR, "Received invalid aircon remote command %s", val);
+ return;
+ }
+ if (modbus_write_register(modbus, AIRCON_HREG_REMOTE_CONTROL, val[0]) < 0)
+ mb_error("remote control register write", false);
+ }
+}
+
static int use_daemon;
static int use_debug;
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 (;;) {
+ if (!mqtt_connect() || !mb_connect()) {
+ sleep(5);
+ continue;
+ }
+
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);
+ if (err != MOSQ_ERR_SUCCESS)
+ mqtt_error("loop", err, false);
continue;
}