Add Mqtt item
This commit is contained in:
parent
0fd50eb227
commit
eb60f85604
19 changed files with 901 additions and 48 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include "auxitem.h"
|
||||
#include "poweritem.h"
|
||||
#include "rgbitem.h"
|
||||
#include "mqttitem.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
|
|
@ -338,6 +339,8 @@ std::shared_ptr<Item> Item::loadItem(const QJsonObject& json)
|
|||
newItem = std::shared_ptr<PowerItem>(new PowerItem);
|
||||
else if(json["Type"].toString("") == "Rgb")
|
||||
newItem = std::shared_ptr<RgbItem>(new RgbItem);
|
||||
else if(json["Type"].toString("") == "Mqtt")
|
||||
newItem = std::shared_ptr<MqttItem>(new MqttItem);
|
||||
else
|
||||
qWarning()<<"Unable to load unkown item type: "<<json["Type"].toString();
|
||||
if(newItem)
|
||||
|
|
|
|||
164
src/items/mqttitem.cpp
Normal file
164
src/items/mqttitem.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "mqttitem.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QtMqtt/QMqttClient>
|
||||
|
||||
#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<MqttClient> workClient = client.lock();
|
||||
assert(workClient);
|
||||
|
||||
connect(workClient->getClient().get(), &QMqttClient::stateChanged, this, &MqttItem::onClientStateChanged);
|
||||
}
|
||||
|
||||
MqttItem::~MqttItem()
|
||||
{
|
||||
qDebug()<<__func__;
|
||||
std::shared_ptr<MqttClient> 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<MqttClient> 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<MqttClient> 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);
|
||||
}
|
||||
53
src/items/mqttitem.h
Normal file
53
src/items/mqttitem.h
Normal file
|
|
@ -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<MqttClient> 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
|
||||
205
src/items/mqttitemsource.cpp
Normal file
205
src/items/mqttitemsource.cpp
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#include "mqttitemsource.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
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<MqttItem>(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<ItemAddRequest> 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_;
|
||||
}
|
||||
34
src/items/mqttitemsource.h
Normal file
34
src/items/mqttitemsource.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef MQTTITEMSOURCE_H
|
||||
#define MQTTITEMSOURCE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QtMqtt/QMqttClient>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#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
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
#include<QJsonArray>
|
||||
#include<QMessageBox>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public:
|
|||
Microcontroller micro;
|
||||
TcpServer* tcpServer;
|
||||
WebSocketServer* webServer;
|
||||
std::shared_ptr<MqttClient> mqttClient;
|
||||
|
||||
//sensors
|
||||
SunSensorSource sunSensorSource;
|
||||
|
|
|
|||
110
src/mqttclient.cpp
Normal file
110
src/mqttclient.cpp
Normal file
|
|
@ -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:"<<error;
|
||||
}
|
||||
|
||||
void MqttClient::onClientStateChanged(QMqttClient::ClientState state)
|
||||
{
|
||||
if(state == QMqttClient::ClientState::Connected)
|
||||
qInfo()<<"Connected to MQTT broker at "<<client->hostname()<<client->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 "<<client->hostname()<<client->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<QMqttClient> 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"<<topic;
|
||||
MqttClient::Subscription* sub = new Subscription;
|
||||
sub->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"<<sub->subscription->topic();
|
||||
client->unsubscribe(sub->subscription->topic());
|
||||
subscriptions.erase(topic);
|
||||
delete sub;
|
||||
}
|
||||
|
||||
QString MqttClient::getBaseTopic()
|
||||
{
|
||||
return baseTopicName;
|
||||
}
|
||||
|
||||
MqttClient::~MqttClient()
|
||||
{
|
||||
for(const std::pair<QString, Subscription*> sub : subscriptions)
|
||||
{
|
||||
qWarning()<<sub.first<<"not unregistered at exit!";
|
||||
client->unsubscribe(sub.second->subscription->topic());
|
||||
}
|
||||
}
|
||||
40
src/mqttclient.h
Normal file
40
src/mqttclient.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef MQTTCLIENT_H
|
||||
#define MQTTCLIENT_H
|
||||
|
||||
#include <QMqttClient>
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <map>
|
||||
|
||||
class MqttClient: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Subscription
|
||||
{
|
||||
int ref;
|
||||
QMqttSubscription* subscription;
|
||||
};
|
||||
|
||||
private:
|
||||
QString baseTopicName;
|
||||
std::shared_ptr<QMqttClient> client;
|
||||
std::map<QString, Subscription*> 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<QMqttClient> getClient();
|
||||
Subscription* subscribe(QString topic);
|
||||
void unsubscribe(Subscription* subscription);
|
||||
void unsubscribe(QString topic);
|
||||
QString getBaseTopic();
|
||||
};
|
||||
|
||||
#endif // MQTTCLIENT_H
|
||||
|
|
@ -7,22 +7,11 @@ MqttSensorSource::MqttSensorSource(QObject *parent)
|
|||
{
|
||||
}
|
||||
|
||||
void MqttSensorSource::start(const QJsonObject& settings)
|
||||
void MqttSensorSource::start(std::shared_ptr<MqttClient> 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:"<<error;
|
||||
}
|
||||
|
||||
MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const QString& topic)
|
||||
{
|
||||
for(SensorSubscription& sensor : sensors)
|
||||
{
|
||||
if(baseTopicName + "/" + sensor.topic == topic)
|
||||
if(client->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 "<<client.hostname()<<client.port();
|
||||
for(SensorSubscription& sensor : sensors)
|
||||
{
|
||||
qDebug()<<"MQTT subscribeing to"<<baseTopicName + "/" + sensor.topic;
|
||||
sensor.subscription = client.subscribe(baseTopicName + "/" + sensor.topic);
|
||||
connect(sensor.subscription, &QMqttSubscription::messageReceived, this, &MqttSensorSource::onMessageReceived);
|
||||
qDebug()<<"MQTT subscribeing to"<<client->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 "<<client.hostname()<<client.port();
|
||||
}
|
||||
}
|
||||
|
||||
void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
||||
|
|
@ -107,6 +85,14 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
stateChanged(sensor);
|
||||
}
|
||||
|
||||
if(obj.contains("local_temperature"))
|
||||
{
|
||||
sensor.name = baseName + " Temperature";
|
||||
sensor.type = Sensor::TYPE_TEMPERATURE;
|
||||
sensor.field = obj["local_temperature"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
}
|
||||
|
||||
if(obj.contains("humidity"))
|
||||
{
|
||||
sensor.name = baseName + " Humidity";
|
||||
|
|
@ -167,13 +153,6 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
|
||||
void MqttSensorSource::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 sensorsArray;
|
||||
for(const SensorSubscription& sensor : sensors)
|
||||
{
|
||||
|
|
@ -185,3 +164,9 @@ void MqttSensorSource::store(QJsonObject& json)
|
|||
json["Sensors"] = sensorsArray;
|
||||
}
|
||||
|
||||
MqttSensorSource::~MqttSensorSource()
|
||||
{
|
||||
for(SensorSubscription& sub : sensors)
|
||||
client->unsubscribe(sub.topic);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <vector>
|
||||
|
||||
#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<SensorSubscription> sensors;
|
||||
QMqttClient client;
|
||||
std::shared_ptr<MqttClient> 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<MqttClient> client, const QJsonObject& settings);
|
||||
void store(QJsonObject& json);
|
||||
|
||||
signals:
|
||||
|
|
|
|||
|
|
@ -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> mqttItem(new MqttItem);
|
||||
item = mqttItem;
|
||||
widget = new MqttItemSettingsWidget(mqttItem, this);
|
||||
ui->verticalLayout->addWidget(widget);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreationDialog::itemNameChanged(const QString& name)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>140</height>
|
||||
<height>274</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
|
@ -39,6 +39,11 @@
|
|||
<string>System</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mqtt</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
@ -64,13 +69,26 @@
|
|||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -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<memory>
|
||||
|
||||
ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup, QWidget *parent) :
|
||||
|
|
@ -47,6 +49,10 @@ ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup,
|
|||
{
|
||||
itemSpecificWidget_ = new SystemItemSettingsWidget(sysItem);
|
||||
}
|
||||
else if(std::shared_ptr<MqttItem> mqttItem = std::dynamic_pointer_cast<MqttItem>(item_))
|
||||
{
|
||||
itemSpecificWidget_ = new MqttItemSettingsWidget(mqttItem);
|
||||
}
|
||||
|
||||
if(itemSpecificWidget_)
|
||||
{
|
||||
|
|
|
|||
62
src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp
Normal file
62
src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include "mqttitemsettingswidget.h"
|
||||
#include "ui_mqttitemsettingswidget.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr<MqttItem> 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;
|
||||
}
|
||||
32
src/ui/itemsettingswidgets/mqttitemsettingswidget.h
Normal file
32
src/ui/itemsettingswidgets/mqttitemsettingswidget.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef MQTTITEMSETTINGSWIDGET_H
|
||||
#define MQTTITEMSETTINGSWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
#include "../../items/mqttitem.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class MqttItemSettingsWidget;
|
||||
}
|
||||
|
||||
class MqttItemSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
std::weak_ptr<MqttItem> 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<MqttItem> item, QWidget *parent = nullptr);
|
||||
~MqttItemSettingsWidget();
|
||||
|
||||
private:
|
||||
Ui::MqttItemSettingsWidget *ui;
|
||||
};
|
||||
|
||||
#endif // MQTTITEMSETTINGSWIDGET_H
|
||||
108
src/ui/itemsettingswidgets/mqttitemsettingswidget.ui
Normal file
108
src/ui/itemsettingswidgets/mqttitemsettingswidget.ui
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MqttItemSettingsWidget</class>
|
||||
<widget class="QWidget" name="MqttItemSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>216</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_topic">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_topic">
|
||||
<property name="text">
|
||||
<string>Topic:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_topic">
|
||||
<property name="placeholderText">
|
||||
<string>e.g., kitchen/light</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_valueKey">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_valueKey">
|
||||
<property name="text">
|
||||
<string>Value Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_valueKey">
|
||||
<property name="text">
|
||||
<string>state</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g., state, brightness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOn">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_valueOn">
|
||||
<property name="text">
|
||||
<string>Value On:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_valueOn">
|
||||
<property name="text">
|
||||
<string>ON</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOff">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_valueOff">
|
||||
<property name="text">
|
||||
<string>Value Off:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_valueOff">
|
||||
<property name="text">
|
||||
<string>OFF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Loading…
Add table
Add a link
Reference in a new issue