diff --git a/CMakeLists.txt b/CMakeLists.txt index 39c1e2e..ca60d65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,11 @@ add_subdirectory(tests) # Create executable add_executable(SHinterface - src/sensors/mqttsensorsource.h src/sensors/mqttsensorsource.cpp) + src/sensors/mqttsensorsource.h src/sensors/mqttsensorsource.cpp + src/items/mqttitem.h src/items/mqttitem.cpp + src/mqttclient.h src/mqttclient.cpp + +) # Add sources to executable target_sources(SHinterface @@ -139,6 +143,8 @@ target_sources(SHinterface src/ui/itemsettingswidgets/relayitemsettingswidget.cpp src/ui/itemsettingswidgets/systemitemsettingswidget.h src/ui/itemsettingswidgets/systemitemsettingswidget.cpp + src/ui/itemsettingswidgets/mqttitemsettingswidget.h + src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp ) # Add UI files @@ -159,6 +165,7 @@ target_sources(SHinterface src/ui/itemsettingswidgets/messageitemsettingswidget.ui src/ui/itemsettingswidgets/relayitemsettingswidget.ui src/ui/itemsettingswidgets/systemitemsettingswidget.ui + src/ui/itemsettingswidgets/mqttitemsettingswidget.ui ) # Add resource file diff --git a/src/items/item.cpp b/src/items/item.cpp index 6f391c5..b902c32 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -10,6 +10,7 @@ #include "auxitem.h" #include "poweritem.h" #include "rgbitem.h" +#include "mqttitem.h" #include @@ -338,6 +339,8 @@ std::shared_ptr Item::loadItem(const QJsonObject& json) newItem = std::shared_ptr(new PowerItem); else if(json["Type"].toString("") == "Rgb") newItem = std::shared_ptr(new RgbItem); + else if(json["Type"].toString("") == "Mqtt") + newItem = std::shared_ptr(new MqttItem); else qWarning()<<"Unable to load unkown item type: "< +#include +#include + +#include "mqttclient.h" + +MqttItem::MqttItem(QString name, uint8_t value, QObject *parent) + : Item(0, name, value, parent), + topic_(""), + valueKey_("state"), + valueOn_("ON"), + valueOff_("OFF") +{ + hashId(); + std::shared_ptr workClient = client.lock(); + assert(workClient); + + connect(workClient->getClient().get(), &QMqttClient::stateChanged, this, &MqttItem::onClientStateChanged); +} + +MqttItem::~MqttItem() +{ + qDebug()<<__func__; + std::shared_ptr workClient = client.lock(); + if(!workClient || topic_.isEmpty() || !subscription) + return; + + workClient->unsubscribe(subscription); +} + +void MqttItem::onClientStateChanged(QMqttClient::ClientState state) +{ + if(state == QMqttClient::Connected) + refreshSubscription(); +} + +void MqttItem::refreshSubscription() +{ + std::shared_ptr workClient = client.lock(); + if(!workClient || topic_.isEmpty()) + return; + + if(workClient->getClient()->state() != QMqttClient::Connected) + return; + + if(subscription) + { + disconnect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived); + workClient->unsubscribe(subscription); + } + + subscription = workClient->subscribe(workClient->getBaseTopic() + "/" + getTopic()); + connect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived); +} + +void MqttItem::onMessageReceived(const QMqttMessage& message) +{ + QJsonDocument doc = QJsonDocument::fromJson(message.payload()); + if(doc.isObject()) + { + QJsonObject obj = doc.object(); + if(obj.contains(getValueKey())) + { + QString value = obj[getValueKey()].toString(); + ItemUpdateRequest req = createValueUpdateRequest(ITEM_UPDATE_BACKEND); + req.changes.value = true; + if(value == getValueOn()) + req.payload.setValueData(true); + else + req.payload.setValueData(false); + requestUpdate(req); + } + } +} + +void MqttItem::hashId() +{ + QString hashString = topic_ + "/" + valueKey_; + itemId_ = qHash(hashString.toLatin1()); +} + +void MqttItem::setTopic(const QString& topic) +{ + topic_ = topic; + hashId(); + refreshSubscription(); +} + +void MqttItem::setValueKey(const QString& valueKey) +{ + valueKey_ = valueKey; + hashId(); +} + +void MqttItem::setValueOn(const QString& valueOn) +{ + valueOn_ = valueOn; +} + +void MqttItem::setValueOff(const QString& valueOff) +{ + valueOff_ = valueOff; +} + +QString MqttItem::getTopic() const +{ + return topic_; +} + +QString MqttItem::getValueKey() const +{ + return valueKey_; +} + +QString MqttItem::getValueOn() const +{ + return valueOn_; +} + +QString MqttItem::getValueOff() const +{ + return valueOff_; +} + +void MqttItem::store(QJsonObject& json) +{ + Item::store(json); + json["Type"] = "Mqtt"; + json["Topic"] = topic_; + json["ValueKey"] = valueKey_; + json["ValueOn"] = valueOn_; + json["ValueOff"] = valueOff_; +} + +void MqttItem::load(const QJsonObject& json, const bool preserve) +{ + Item::load(json, preserve); + topic_ = json["Topic"].toString(); + valueKey_ = json["ValueKey"].toString("state"); + valueOn_ = json["ValueOn"].toString("ON"); + valueOff_ = json["ValueOff"].toString("OFF"); + hashId(); + refreshSubscription(); +} + +void MqttItem::enactValue(uint8_t value) +{ + std::shared_ptr workClient = client.lock(); + if(!workClient || topic_.isEmpty()) + return; + + QString fullTopic = workClient->getBaseTopic() + "/" + topic_ + "/set"; + QJsonObject payload; + + payload[valueKey_] = value ? valueOn_ : valueOff_; + + QJsonDocument doc(payload); + QByteArray data = doc.toJson(QJsonDocument::Compact); + + qDebug() << "MqttItem publishing to" << fullTopic << ":" << data; + workClient->getClient()->publish(fullTopic, data); +} diff --git a/src/items/mqttitem.h b/src/items/mqttitem.h new file mode 100644 index 0000000..c092444 --- /dev/null +++ b/src/items/mqttitem.h @@ -0,0 +1,53 @@ +#ifndef MQTTITEM_H +#define MQTTITEM_H + +#include "item.h" +#include "mqttclient.h" + +class QString; + +class MqttItem : public Item +{ + Q_OBJECT +public: + inline static std::weak_ptr client; + +private: + QString topic_; + QString valueKey_; + QString valueOn_; + QString valueOff_; + + MqttClient::Subscription* subscription = nullptr; + + void hashId(); + void refreshSubscription(); + void onMessageReceived(const QMqttMessage& message); + void onClientStateChanged(QMqttClient::ClientState state); + +public: + explicit MqttItem(QString name = "MqttItem", + uint8_t value = 0, + QObject *parent = nullptr); + virtual ~MqttItem() override; + + void setTopic(const QString& topic); + void setValueKey(const QString& valueKey); + void setBaseTopic(const QString& baseTopic); + void setValueOn(const QString& valueOn); + void setValueOff(const QString& valueOff); + + QString getTopic() const; + QString getValueKey() const; + QString getBaseTopic() const; + QString getValueOn() const; + QString getValueOff() const; + + virtual void store(QJsonObject& json) override; + virtual void load(const QJsonObject& json, const bool preserve = false) override; + +protected: + virtual void enactValue(uint8_t value) override; +}; + +#endif // MQTTITEM_H \ No newline at end of file diff --git a/src/items/mqttitemsource.cpp b/src/items/mqttitemsource.cpp new file mode 100644 index 0000000..eb3c11c --- /dev/null +++ b/src/items/mqttitemsource.cpp @@ -0,0 +1,205 @@ +#include "mqttitemsource.h" + +#include +#include +#include + +MqttItemSource::MqttItemSource(QObject *parent) + : ItemSource(parent) +{ +} + +MqttItemSource::~MqttItemSource() +{ +} + +void MqttItemSource::start(const QJsonObject& settings) +{ + baseTopicName_ = settings["BaseTopic"].toString("zigbee2mqtt"); + + connect(&client_, &QMqttClient::stateChanged, this, &MqttItemSource::onClientStateChanged); + connect(&client_, &QMqttClient::errorChanged, this, &MqttItemSource::onClientError); + + client_.setHostname(settings["Host"].toString("127.0.0.1")); + client_.setPort(settings["Port"].toInt(1883)); + if(settings.contains("User")) + client_.setUsername(settings["User"].toString()); + if(settings.contains("Password")) + client_.setPassword(settings["Password"].toString()); + client_.setProtocolVersion(QMqttClient::MQTT_5_0); + + client_.connectToHost(); + + QJsonArray itemsArray = settings["Items"].toArray(); + + for(QJsonValueRef itemRef : itemsArray) + { + QJsonObject itemObject = itemRef.toObject(); + if(!itemObject.contains("Topic")) + continue; + + MqttItemConfig config; + config.topic = itemObject["Topic"].toString(); + config.name = itemObject.contains("Name") ? itemObject["Name"].toString() : config.topic; + config.valueKey = itemObject["ValueKey"].toString("state"); + config.valueOn = itemObject["ValueOn"].toString("ON"); + config.valueOff = itemObject["ValueOff"].toString("OFF"); + + // Determine value type + QString valueTypeStr = itemObject["ValueType"].toString("bool"); + if(valueTypeStr == "uint") + config.valueType = ITEM_VALUE_UINT; + else + config.valueType = ITEM_VALUE_BOOL; + + config.id = qHash(baseTopicName_ + "/" + config.topic); + + // Create the item + config.item = std::make_shared(config.id, config.name, 0); + config.item->setTopic(config.topic); + config.item->setValueKey(config.valueKey); + config.item->setBaseTopic(baseTopicName_); + config.item->setValueOn(config.valueOn); + config.item->setValueOff(config.valueOff); + config.item->setMqttClient(&client_); + + items_.push_back(config); + } +} + +MqttItemSource::MqttItemConfig* MqttItemSource::findConfig(const QString& topic) +{ + for(MqttItemConfig& config : items_) + { + if(baseTopicName_ + "/" + config.topic == topic) + return &config; + } + return nullptr; +} + +void MqttItemSource::onClientError(QMqttClient::ClientError error) +{ + qWarning() << "MqttItemSource Client error:" << error; +} + +void MqttItemSource::onClientStateChanged(QMqttClient::ClientState state) +{ + if(state == QMqttClient::ClientState::Connected) + { + qInfo() << "MqttItemSource connected to MQTT broker at " << client_.hostname() << client_.port(); + for(MqttItemConfig& config : items_) + { + // Subscribe to state topic to receive updates from devices + QString stateTopic = baseTopicName_ + "/" + config.topic; + qDebug() << "MqttItemSource subscribing to" << stateTopic; + QMqttSubscription* subscription = client_.subscribe(stateTopic); + if(subscription) + { + connect(subscription, &QMqttSubscription::messageReceived, this, &MqttItemSource::onMessageReceived); + } + } + } + else if(state == QMqttClient::ClientState::Disconnected) + { + qWarning() << "MqttItemSource lost connection to MQTT broker"; + } + else if(state == QMqttClient::ClientState::Connecting) + { + qInfo() << "MqttItemSource connecting to MQTT broker at " << client_.hostname() << client_.port(); + } +} + +void MqttItemSource::onMessageReceived(const QMqttMessage& message) +{ + MqttItemConfig* config = findConfig(message.topic().name()); + if(!config) + return; + + QJsonDocument doc = QJsonDocument::fromJson(message.payload()); + if(doc.isObject()) + { + QJsonObject obj = doc.object(); + QString valueKey = config->valueKey; + + if(obj.contains(valueKey)) + { + uint8_t newValue = 0; + + // Handle different value types + if(config->valueType == ITEM_VALUE_UINT) + { + // Numeric value (brightness, etc.) + newValue = obj[valueKey].toInt(0); + } + else + { + // Binary value (state on/off, etc.) + QString value = obj[valueKey].toString(); + if(value == config->valueOn || value == "ON" || value == "true") + newValue = 1; + else if(value == config->valueOff || value == "OFF" || value == "false") + newValue = 0; + } + + // Only update if value changed + if(config->item->getValue() != newValue) + { + ItemUpdateRequest update; + update.type = ITEM_UPDATE_BACKEND; + update.payload = *config->item; + update.payload.setValueData(newValue); + update.changes.value = true; + config->item->requestUpdate(update); + } + } + } +} + +void MqttItemSource::refresh() +{ + std::vector requests; + + for(MqttItemConfig& config : items_) + { + ItemAddRequest request; + request.type = ITEM_UPDATE_BACKEND; + request.payload = config.item; + request.changes = ItemFieldChanges(true); + requests.push_back(request); + } + + gotItems(requests); +} + +void MqttItemSource::store(QJsonObject& json) +{ + json["Host"] = client_.hostname(); + json["Port"] = client_.port(); + json["BaseTopic"] = baseTopicName_; + if(client_.username() != "") + json["User"] = client_.username(); + if(client_.password() != "") + json["Password"] = client_.password(); + + QJsonArray itemsArray; + for(const MqttItemConfig& config : items_) + { + QJsonObject itemObject; + itemObject["Name"] = config.name; + itemObject["Topic"] = config.topic; + itemObject["ValueKey"] = config.valueKey; + itemObject["ValueOn"] = config.valueOn; + itemObject["ValueOff"] = config.valueOff; + if(config.valueType == ITEM_VALUE_UINT) + itemObject["ValueType"] = "uint"; + else + itemObject["ValueType"] = "bool"; + itemsArray.append(itemObject); + } + json["Items"] = itemsArray; +} + +QMqttClient* MqttItemSource::getClient() +{ + return &client_; +} \ No newline at end of file diff --git a/src/items/mqttitemsource.h b/src/items/mqttitemsource.h new file mode 100644 index 0000000..080e8a8 --- /dev/null +++ b/src/items/mqttitemsource.h @@ -0,0 +1,34 @@ +#ifndef MQTTITEMSOURCE_H +#define MQTTITEMSOURCE_H + +#include +#include +#include +#include +#include + +#include "itemsource.h" +#include "mqttitem.h" + +class MqttItemSource : public ItemSource +{ + Q_OBJECT + + QString baseTopicName_; + QMqttClient client_; + +private slots: + void onClientStateChanged(QMqttClient::ClientState state); + void onMessageReceived(const QMqttMessage& message); + void onClientError(QMqttClient::ClientError error); + +public: + explicit MqttItemSource(QObject *parent = nullptr); + virtual ~MqttItemSource() override; + virtual void refresh() override; + void start(const QJsonObject& settings); + void store(QJsonObject& json); + QMqttClient* getClient(); +}; + +#endif // MQTTITEMSOURCE_H \ No newline at end of file diff --git a/src/mainobject.cpp b/src/mainobject.cpp index eed22d7..aeb90de 100644 --- a/src/mainobject.cpp +++ b/src/mainobject.cpp @@ -4,6 +4,8 @@ #include #include +#include "mqttclient.h" +#include "items/mqttitem.h" #include "items/itemstore.h" MainObject::MainObject(QObject *parent) : @@ -74,9 +76,12 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett micro(microDevice), tcpServer(new TcpServer(this)), webServer(new WebSocketServer("shinterface", this)), + mqttClient(new MqttClient), sunSensorSource(49.824972, 8.702194), fixedItems(µ) { + MqttItem::client = mqttClient; + //connect sensors subsystem connect(&globalSensors, &SensorStore::sensorChangedState, tcpServer, &TcpServer::sensorEvent); connect(tcpServer, &TcpServer::gotSensor, &globalSensors, &SensorStore::sensorGotState); @@ -98,7 +103,8 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett loadFromDisk(settingsPath); QJsonObject mqttJson = settings["Mqtt"].toObject(); - mqttSensorSource.start(mqttJson); + mqttClient->start(mqttJson); + mqttSensorSource.start(mqttClient, mqttJson); tcpServer->launch(QHostAddress(host), port); webServer->launch(QHostAddress(host), port+1); @@ -115,6 +121,7 @@ void PrimaryMainObject::store(QJsonObject &json) { globalItems.store(json); QJsonObject mqttJson = json["Mqtt"].toObject(); + mqttClient->store(mqttJson); mqttSensorSource.store(mqttJson); json["Mqtt"] = mqttJson; } diff --git a/src/mainobject.h b/src/mainobject.h index 48835dc..5552c6f 100644 --- a/src/mainobject.h +++ b/src/mainobject.h @@ -46,6 +46,7 @@ public: Microcontroller micro; TcpServer* tcpServer; WebSocketServer* webServer; + std::shared_ptr mqttClient; //sensors SunSensorSource sunSensorSource; diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp new file mode 100644 index 0000000..fcc4692 --- /dev/null +++ b/src/mqttclient.cpp @@ -0,0 +1,110 @@ +#include "mqttclient.h" + +MqttClient::MqttClient(): + client(new QMqttClient) +{ + +} + +void MqttClient::start(const QJsonObject& settings) +{ + baseTopicName = settings["BaseTopic"].toString("zigbee2mqtt"); + + QMqttClient* cl = client.get(); + connect(cl, &QMqttClient::stateChanged, this, &MqttClient::onClientStateChanged); + connect(cl, &QMqttClient::errorChanged, this, &MqttClient::onClientError); + + client->setHostname(settings["Host"].toString("127.0.0.1")); + client->setPort(settings["Port"].toInt(1883)); + if(settings.contains("User")) + client->setUsername(settings["User"].toString()); + if(settings.contains("Password")) + client->setPassword(settings["Password"].toString()); + client->setProtocolVersion(QMqttClient::MQTT_5_0); + + client->connectToHost(); +} + +void MqttClient::onClientError(QMqttClient::ClientError error) +{ + qWarning()<<"MQTT Client error:"<hostname()<port(); + else if (state == QMqttClient::ClientState::Disconnected) + qWarning()<<"Lost connection to MQTT broker"; + else if(state == QMqttClient::ClientState::Connecting) + qInfo()<<"Connecting to MQTT broker at "<hostname()<port(); +} + +void MqttClient::store(QJsonObject& json) +{ + json["Host"] = client->hostname(); + json["Port"] = client->port(); + json["BaseTopic"] = baseTopicName; + if(client->username() != "") + json["User"] = client->username(); + if(client->password() != "") + json["Password"] = client->password(); +} + +std::shared_ptr MqttClient::getClient() +{ + return client; +} + +MqttClient::Subscription* MqttClient::subscribe(QString topic) +{ + if(subscriptions.contains(topic)) + { + MqttClient::Subscription* sub = subscriptions[topic]; + ++sub->ref; + return sub; + } + else + { + qDebug()<<"MqttClient: subscibeing to"<subscription = client->subscribe(topic); + sub->ref = 1; + subscriptions.insert({topic, sub}); + return sub; + } +} + +void MqttClient::unsubscribe(MqttClient::Subscription* subscription) +{ + QString topic = subscription->subscription->topic().filter(); + unsubscribe(topic); +} + +void MqttClient::unsubscribe(QString topic) +{ + assert(!subscriptions.contains(topic)); + MqttClient::Subscription* sub = subscriptions[topic]; + + if(--sub->ref > 0) + return; + + qDebug()<<"MqttClient: unsubscibeing"<subscription->topic(); + client->unsubscribe(sub->subscription->topic()); + subscriptions.erase(topic); + delete sub; +} + +QString MqttClient::getBaseTopic() +{ + return baseTopicName; +} + +MqttClient::~MqttClient() +{ + for(const std::pair sub : subscriptions) + { + qWarning()<unsubscribe(sub.second->subscription->topic()); + } +} \ No newline at end of file diff --git a/src/mqttclient.h b/src/mqttclient.h new file mode 100644 index 0000000..1199855 --- /dev/null +++ b/src/mqttclient.h @@ -0,0 +1,40 @@ +#ifndef MQTTCLIENT_H +#define MQTTCLIENT_H + +#include +#include +#include +#include + +class MqttClient: public QObject +{ + Q_OBJECT +public: + struct Subscription + { + int ref; + QMqttSubscription* subscription; + }; + +private: + QString baseTopicName; + std::shared_ptr client; + std::map subscriptions; + +private slots: + void onClientStateChanged(QMqttClient::ClientState state); + void onClientError(QMqttClient::ClientError error); + +public: + explicit MqttClient(); + ~MqttClient(); + void start(const QJsonObject& settings); + void store(QJsonObject& json); + std::shared_ptr getClient(); + Subscription* subscribe(QString topic); + void unsubscribe(Subscription* subscription); + void unsubscribe(QString topic); + QString getBaseTopic(); +}; + +#endif // MQTTCLIENT_H diff --git a/src/sensors/mqttsensorsource.cpp b/src/sensors/mqttsensorsource.cpp index 4202ab8..63b5c02 100644 --- a/src/sensors/mqttsensorsource.cpp +++ b/src/sensors/mqttsensorsource.cpp @@ -7,22 +7,11 @@ MqttSensorSource::MqttSensorSource(QObject *parent) { } -void MqttSensorSource::start(const QJsonObject& settings) +void MqttSensorSource::start(std::shared_ptr client, const QJsonObject& settings) { - baseTopicName = settings["BaseTopic"].toString("zigbee2mqtt"); + this->client = client; - connect(&client, &QMqttClient::stateChanged, this, &MqttSensorSource::onClientStateChanged); - connect(&client, &QMqttClient::errorChanged, this, &MqttSensorSource::onClientError); - - client.setHostname(settings["Host"].toString("127.0.0.1")); - client.setPort(settings["Port"].toInt(1883)); - if(settings.contains("User")) - client.setUsername(settings["User"].toString()); - if(settings.contains("Password")) - client.setPassword(settings["Password"].toString()); - client.setProtocolVersion(QMqttClient::MQTT_5_0); - - client.connectToHost(); + connect(client->getClient().get(), &QMqttClient::stateChanged, this, &MqttSensorSource::onClientStateChanged); QJsonArray sensorsArray = settings["Sensors"].toArray(); @@ -37,21 +26,16 @@ void MqttSensorSource::start(const QJsonObject& settings) sensor.name = sensor.topic; else sensor.name = sensorObject["Name"].toString(); - sensor.id = qHash(baseTopicName + "/" + sensor.topic); + sensor.id = qHash(client->getBaseTopic() + "/" + sensor.topic); sensors.push_back(sensor); } } -void MqttSensorSource::onClientError(QMqttClient::ClientError error) -{ - qWarning()<<"MQTT Client error:"<getBaseTopic() + "/" + sensor.topic == topic) return sensor; } assert(false); @@ -61,30 +45,24 @@ void MqttSensorSource::onClientStateChanged(QMqttClient::ClientState state) { if(state == QMqttClient::ClientState::Connected) { - qInfo()<<"Connected to MQTT broker at "<getBaseTopic() + "/" + sensor.topic; + sensor.subscription = client->subscribe(client->getBaseTopic() + "/" + sensor.topic); + connect(sensor.subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttSensorSource::onMessageReceived); } } else if (state == QMqttClient::ClientState::Disconnected) { - qWarning()<<"Lost connection to MQTT broker"; for(SensorSubscription& sensor : sensors) { if(sensor.subscription) { - client.unsubscribe(sensor.topic); + client->unsubscribe(sensor.topic); sensor.subscription = nullptr; } } } - else if(state == QMqttClient::ClientState::Connecting) - { - qInfo()<<"Connecting to MQTT broker at "<unsubscribe(sub.topic); +} + diff --git a/src/sensors/mqttsensorsource.h b/src/sensors/mqttsensorsource.h index ca601c1..c726d70 100644 --- a/src/sensors/mqttsensorsource.h +++ b/src/sensors/mqttsensorsource.h @@ -7,6 +7,7 @@ #include #include "sensor.h" +#include "mqttclient.h" class MqttSensorSource : public QObject { @@ -17,12 +18,11 @@ class MqttSensorSource : public QObject uint64_t id; QString topic; QString name; - QMqttSubscription* subscription = nullptr; + MqttClient::Subscription* subscription = nullptr; }; - QString baseTopicName; std::vector sensors; - QMqttClient client; + std::shared_ptr client; private: SensorSubscription& findSubscription(const QString& topic); @@ -30,11 +30,11 @@ private: private slots: void onClientStateChanged(QMqttClient::ClientState state); void onMessageReceived(const QMqttMessage& message); - void onClientError(QMqttClient::ClientError error); public: explicit MqttSensorSource(QObject *parent = nullptr); - void start(const QJsonObject& settings); + ~MqttSensorSource(); + void start(std::shared_ptr client, const QJsonObject& settings); void store(QJsonObject& json); signals: diff --git a/src/ui/itemcreationdialog.cpp b/src/ui/itemcreationdialog.cpp index 57e804f..1133d08 100644 --- a/src/ui/itemcreationdialog.cpp +++ b/src/ui/itemcreationdialog.cpp @@ -3,6 +3,8 @@ #include "itemsettingswidgets/messageitemsettingswidget.h" #include "itemsettingswidgets/systemitemsettingswidget.h" +#include "itemsettingswidgets/mqttitemsettingswidget.h" +#include "items/mqttitem.h" ItemCreationDialog::ItemCreationDialog(QWidget *parent) : QDialog(parent), @@ -41,6 +43,13 @@ void ItemCreationDialog::itemTypeChanged(const QString& type) widget = new SystemItemSettingsWidget(systemItem, this); ui->verticalLayout->addWidget(widget); } + if(type == "Mqtt") + { + std::shared_ptr mqttItem(new MqttItem); + item = mqttItem; + widget = new MqttItemSettingsWidget(mqttItem, this); + ui->verticalLayout->addWidget(widget); + } } void ItemCreationDialog::itemNameChanged(const QString& name) diff --git a/src/ui/itemcreationdialog.ui b/src/ui/itemcreationdialog.ui index 4b55d6d..cc76289 100644 --- a/src/ui/itemcreationdialog.ui +++ b/src/ui/itemcreationdialog.ui @@ -7,7 +7,7 @@ 0 0 400 - 140 + 274 @@ -39,6 +39,11 @@ System + + + Mqtt + + @@ -64,13 +69,26 @@ + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/src/ui/itemsettingsdialog.cpp b/src/ui/itemsettingsdialog.cpp index aa60f01..d517312 100644 --- a/src/ui/itemsettingsdialog.cpp +++ b/src/ui/itemsettingsdialog.cpp @@ -12,6 +12,8 @@ #include "itemsettingswidgets/messageitemsettingswidget.h" #include "itemsettingswidgets/systemitemsettingswidget.h" #include "itemsettingswidgets/relayitemsettingswidget.h" +#include "itemsettingswidgets/mqttitemsettingswidget.h" +#include "../items/mqttitem.h" #include ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr item, bool noGroup, QWidget *parent) : @@ -47,6 +49,10 @@ ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr item, bool noGroup, { itemSpecificWidget_ = new SystemItemSettingsWidget(sysItem); } + else if(std::shared_ptr mqttItem = std::dynamic_pointer_cast(item_)) + { + itemSpecificWidget_ = new MqttItemSettingsWidget(mqttItem); + } if(itemSpecificWidget_) { diff --git a/src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp b/src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp new file mode 100644 index 0000000..43799c4 --- /dev/null +++ b/src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp @@ -0,0 +1,62 @@ +#include "mqttitemsettingswidget.h" +#include "ui_mqttitemsettingswidget.h" + +#include + +MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr item, QWidget *parent) : + QWidget(parent), + item_(item), + ui(new Ui::MqttItemSettingsWidget) +{ + ui->setupUi(this); + + if(auto workingItem = item_.lock()) + { + ui->lineEdit_topic->setText(workingItem->getTopic()); + ui->lineEdit_valueKey->setText(workingItem->getValueKey()); + ui->lineEdit_valueOn->setText(workingItem->getValueOn()); + ui->lineEdit_valueOff->setText(workingItem->getValueOff()); + } + + connect(ui->lineEdit_topic, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setTopic); + connect(ui->lineEdit_valueKey, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueKey); + connect(ui->lineEdit_valueOn, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOn); + connect(ui->lineEdit_valueOff, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOff); +} + +void MqttItemSettingsWidget::setTopic(const QString& topic) +{ + if(auto workingItem = item_.lock()) + { + workingItem->setTopic(topic); + } +} + +void MqttItemSettingsWidget::setValueKey(const QString& valueKey) +{ + if(auto workingItem = item_.lock()) + { + workingItem->setValueKey(valueKey); + } +} + +void MqttItemSettingsWidget::setValueOn(const QString& valueOn) +{ + if(auto workingItem = item_.lock()) + { + workingItem->setValueOn(valueOn); + } +} + +void MqttItemSettingsWidget::setValueOff(const QString& valueOff) +{ + if(auto workingItem = item_.lock()) + { + workingItem->setValueOff(valueOff); + } +} + +MqttItemSettingsWidget::~MqttItemSettingsWidget() +{ + delete ui; +} \ No newline at end of file diff --git a/src/ui/itemsettingswidgets/mqttitemsettingswidget.h b/src/ui/itemsettingswidgets/mqttitemsettingswidget.h new file mode 100644 index 0000000..b848b14 --- /dev/null +++ b/src/ui/itemsettingswidgets/mqttitemsettingswidget.h @@ -0,0 +1,32 @@ +#ifndef MQTTITEMSETTINGSWIDGET_H +#define MQTTITEMSETTINGSWIDGET_H + +#include +#include +#include "../../items/mqttitem.h" + +namespace Ui +{ +class MqttItemSettingsWidget; +} + +class MqttItemSettingsWidget : public QWidget +{ + Q_OBJECT + std::weak_ptr item_; + +private slots: + void setTopic(const QString& topic); + void setValueKey(const QString& valueKey); + void setValueOn(const QString& valueOn); + void setValueOff(const QString& valueOff); + +public: + explicit MqttItemSettingsWidget(std::weak_ptr item, QWidget *parent = nullptr); + ~MqttItemSettingsWidget(); + +private: + Ui::MqttItemSettingsWidget *ui; +}; + +#endif // MQTTITEMSETTINGSWIDGET_H \ No newline at end of file diff --git a/src/ui/itemsettingswidgets/mqttitemsettingswidget.ui b/src/ui/itemsettingswidgets/mqttitemsettingswidget.ui new file mode 100644 index 0000000..827b510 --- /dev/null +++ b/src/ui/itemsettingswidgets/mqttitemsettingswidget.ui @@ -0,0 +1,108 @@ + + + MqttItemSettingsWidget + + + + 0 + 0 + 400 + 216 + + + + Form + + + + + + 0 + + + + + Topic: + + + + + + + e.g., kitchen/light + + + + + + + + + 0 + + + + + Value Key: + + + + + + + state + + + e.g., state, brightness + + + + + + + + + 0 + + + + + Value On: + + + + + + + ON + + + + + + + + + 0 + + + + + Value Off: + + + + + + + OFF + + + + + + + + + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2b02715..28c8792 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,9 @@ set(COMMON_TEST_SOURCES ../src/items/itemstore.cpp ../src/items/itemloadersource.h ../src/items/itemloadersource.cpp + ../src/items/mqttitem.h + ../src/items/mqttitem.cpp + ../src/mqttclient.cpp ) # Add test executables - compile all needed sources into each test @@ -73,6 +76,7 @@ target_link_libraries(test_item Qt6::Gui Qt6::Widgets Qt6::Multimedia + Qt6::Mqtt Qt6::Test ) @@ -88,6 +92,7 @@ target_link_libraries(test_sensor Qt6::Gui Qt6::Widgets Qt6::Multimedia + Qt6::Mqtt Qt6::Test ) @@ -103,6 +108,7 @@ target_link_libraries(test_actor Qt6::Gui Qt6::Widgets Qt6::Multimedia + Qt6::Mqtt Qt6::Test ) @@ -118,6 +124,7 @@ target_link_libraries(test_itemstore Qt6::Gui Qt6::Widgets Qt6::Multimedia + Qt6::Mqtt Qt6::Test ) @@ -133,6 +140,7 @@ target_link_libraries(test_itemloadersource Qt6::Gui Qt6::Widgets Qt6::Multimedia + Qt6::Mqtt Qt6::Test ) @@ -150,6 +158,7 @@ target_link_libraries(test_tcp Qt6::Multimedia Qt6::Network Qt6::WebSockets + Qt6::Mqtt Qt6::Test )