#include "mqttcontroller.h" #include "logger.h" #include #include namespace { bool g_mqttInitialized = false; } MqttController::MqttController(const MqttConfig &configIn, QObject *parent) : QObject(parent), config(configIn) { if (!g_mqttInitialized) { mosquitto_lib_init(); g_mqttInitialized = true; } controlTopic = QString::fromStdString(config.topic); immichTopic = QString::fromStdString(config.immichTopic); } MqttController::~MqttController() { if (client) { mosquitto_disconnect(client); mosquitto_loop_stop(client, true); mosquitto_destroy(client); client = nullptr; } } void MqttController::start() { if (!config.enabled) { Log("MQTT disabled or missing host/topic."); return; } QString clientId = QString::fromStdString(config.clientId); if (clientId.isEmpty()) clientId = "slide"; client = mosquitto_new(clientId.toUtf8().constData(), true, this); if (!client) { Log("MQTT: failed to create client."); return; } mosquitto_connect_callback_set(client, &MqttController::HandleConnect); mosquitto_message_callback_set(client, &MqttController::HandleMessage); if (!config.username.empty()) { mosquitto_username_pw_set(client, config.username.c_str(), config.password.empty() ? nullptr : config.password.c_str()); } mosquitto_reconnect_delay_set(client, 2, 30, true); int rc = mosquitto_connect_async(client, config.host.c_str(), config.port, config.keepAlive); if (rc != MOSQ_ERR_SUCCESS) { Log("MQTT connect failed: ", mosquitto_strerror(rc)); return; } rc = mosquitto_loop_start(client); if (rc != MOSQ_ERR_SUCCESS) { Log("MQTT loop start failed: ", mosquitto_strerror(rc)); return; } } void MqttController::HandleConnect(struct mosquitto *mosq, void *userdata, int rc) { auto *self = static_cast(userdata); if (!self || !mosq) return; if (rc != 0) { Log("MQTT connect error: ", mosquitto_strerror(rc)); return; } self->connected = true; self->subscribe(); } void MqttController::subscribe() { if (!client || !connected) return; int rc = mosquitto_subscribe(client, nullptr, config.topic.c_str(), config.qos); if (rc != MOSQ_ERR_SUCCESS) { Log("MQTT subscribe failed: ", mosquitto_strerror(rc)); } else { Log("MQTT subscribed to ", config.topic); } if (!config.immichTopic.empty() && config.immichTopic != config.topic) { rc = mosquitto_subscribe(client, nullptr, config.immichTopic.c_str(), config.qos); if (rc != MOSQ_ERR_SUCCESS) { Log("MQTT subscribe (immich) failed: ", mosquitto_strerror(rc)); } else { Log("MQTT subscribed to ", config.immichTopic); } } } void MqttController::HandleMessage(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message) { Q_UNUSED(mosq); auto *self = static_cast(userdata); if (!self || !message || !message->payload) return; QString payload = QString::fromUtf8(static_cast(message->payload), message->payloadlen); QString topic = QString::fromUtf8(message->topic); QMetaObject::invokeMethod(self, "handleMessage", Qt::QueuedConnection, Q_ARG(QString, topic), Q_ARG(QString, payload)); } void MqttController::handleMessage(const QString &topic, const QString &payload) { QString cmd = payload.trimmed().toLower(); if (cmd.isEmpty()) return; Log("MQTT message on ", topic.toStdString(), ": ", cmd.toStdString()); if (!immichTopic.isEmpty() && topic == immichTopic) { emit immichControl(payload); return; } if (cmd == "play" || cmd == "resume") { emit play(); return; } if (cmd == "pause") { emit pause(); return; } if (cmd == "next" || cmd == "skip" || cmd == "next-image") { emit nextImage(); return; } if (cmd == "next-folder" || cmd == "folder-next" || cmd == "skip-folder") { emit nextFolder(); return; } if (cmd == "restart" || cmd == "reset") { emit restart(); return; } Log("MQTT unknown command: ", cmd.toStdString()); }