Auto mqttitem attempt
This commit is contained in:
parent
25dd99d8b6
commit
05e4fb1cc1
3 changed files with 254 additions and 11 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
#include "mqttitem.h"
|
#include "mqttitem.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QtMqtt/QMqttClient>
|
#include <QtMqtt/QMqttClient>
|
||||||
|
|
@ -26,16 +27,35 @@ MqttItem::~MqttItem()
|
||||||
{
|
{
|
||||||
qDebug()<<__func__;
|
qDebug()<<__func__;
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
std::shared_ptr<MqttClient> workClient = client.lock();
|
||||||
if(!workClient || topic_.isEmpty() || !subscription)
|
if(!workClient)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(subscription)
|
||||||
|
{
|
||||||
|
disconnect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived);
|
||||||
workClient->unsubscribe(subscription);
|
workClient->unsubscribe(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(devicesSubscription)
|
||||||
|
{
|
||||||
|
disconnect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
||||||
|
workClient->unsubscribe(devicesSubscription);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttItem::onClientStateChanged(QMqttClient::ClientState state)
|
void MqttItem::onClientStateChanged(QMqttClient::ClientState state)
|
||||||
{
|
{
|
||||||
if(state == QMqttClient::Connected)
|
if(state == QMqttClient::Connected)
|
||||||
|
{
|
||||||
refreshSubscription();
|
refreshSubscription();
|
||||||
|
// Subscribe to bridge/devices to get exposes
|
||||||
|
std::shared_ptr<MqttClient> workClient = client.lock();
|
||||||
|
if(workClient && !exposeLoaded_)
|
||||||
|
{
|
||||||
|
devicesSubscription = workClient->subscribe(workClient->getBaseTopic() + "/bridge/devices");
|
||||||
|
connect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttItem::refreshSubscription()
|
void MqttItem::refreshSubscription()
|
||||||
|
|
@ -57,6 +77,101 @@ void MqttItem::refreshSubscription()
|
||||||
connect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived);
|
connect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttItem::onDevicesMessageReceived(const QMqttMessage& message)
|
||||||
|
{
|
||||||
|
if(exposeLoaded_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(message.payload());
|
||||||
|
if(!doc.isArray())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QJsonArray devices = doc.array();
|
||||||
|
for(const QJsonValue& deviceValue : devices)
|
||||||
|
{
|
||||||
|
if(!deviceValue.isObject())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QJsonObject device = deviceValue.toObject();
|
||||||
|
QString ieeeAddr = device["ieee_address"].toString();
|
||||||
|
|
||||||
|
// Check if this device matches our topic (friendly_name)
|
||||||
|
QString friendlyName = device["friendly_name"].toString();
|
||||||
|
if(friendlyName == topic_)
|
||||||
|
{
|
||||||
|
loadExposeFromDevice(device);
|
||||||
|
exposeLoaded_ = true;
|
||||||
|
|
||||||
|
// Unsubscribe from devices topic since we found our device
|
||||||
|
std::shared_ptr<MqttClient> workClient = client.lock();
|
||||||
|
if(workClient && devicesSubscription)
|
||||||
|
{
|
||||||
|
disconnect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
||||||
|
workClient->unsubscribe(devicesSubscription);
|
||||||
|
devicesSubscription = nullptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItem::loadExposeFromDevice(const QJsonObject& device)
|
||||||
|
{
|
||||||
|
// Get definition - may be null for unsupported devices
|
||||||
|
QJsonObject definition = device["definition"].toObject();
|
||||||
|
if(definition.isEmpty())
|
||||||
|
{
|
||||||
|
qWarning() << "MqttItem" << topic_ << "device has no definition (unsupported)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get exposes from definition
|
||||||
|
QJsonArray exposes = definition["exposes"].toArray();
|
||||||
|
if(exposes.isEmpty())
|
||||||
|
{
|
||||||
|
qWarning() << "MqttItem" << topic_ << "device has no exposes";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const QJsonValue& exposeValue : exposes)
|
||||||
|
{
|
||||||
|
if(!exposeValue.isObject())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QJsonObject expose = exposeValue.toObject();
|
||||||
|
QString property = expose["property"].toString();
|
||||||
|
|
||||||
|
// Check if this expose matches our valueKey
|
||||||
|
if(property == valueKey_)
|
||||||
|
{
|
||||||
|
setFromExpose(expose);
|
||||||
|
qDebug() << "MqttItem" << topic_ << "detected type" << expose["type"].toString() << "for property" << valueKey_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a composite type with features
|
||||||
|
if(expose["type"].toString() == "composite" || expose["type"].toString() == "light")
|
||||||
|
{
|
||||||
|
QJsonArray features = expose["features"].toArray();
|
||||||
|
for(const QJsonValue& featureValue : features)
|
||||||
|
{
|
||||||
|
if(!featureValue.isObject())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QJsonObject feature = featureValue.toObject();
|
||||||
|
if(feature["property"].toString() == valueKey_)
|
||||||
|
{
|
||||||
|
setFromExpose(feature);
|
||||||
|
qDebug() << "MqttItem" << topic_ << "detected type" << feature["type"].toString() << "for property" << valueKey_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "MqttItem" << topic_ << "could not find expose for property" << valueKey_;
|
||||||
|
}
|
||||||
|
|
||||||
void MqttItem::onMessageReceived(const QMqttMessage& message)
|
void MqttItem::onMessageReceived(const QMqttMessage& message)
|
||||||
{
|
{
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(message.payload());
|
QJsonDocument doc = QJsonDocument::fromJson(message.payload());
|
||||||
|
|
@ -65,13 +180,32 @@ void MqttItem::onMessageReceived(const QMqttMessage& message)
|
||||||
QJsonObject obj = doc.object();
|
QJsonObject obj = doc.object();
|
||||||
if(obj.contains(getValueKey()))
|
if(obj.contains(getValueKey()))
|
||||||
{
|
{
|
||||||
QString value = obj[getValueKey()].toString();
|
QJsonValue value = obj[getValueKey()];
|
||||||
ItemUpdateRequest req = createValueUpdateRequest(ITEM_UPDATE_BACKEND);
|
ItemUpdateRequest req = createValueUpdateRequest(ITEM_UPDATE_BACKEND);
|
||||||
req.changes.value = true;
|
req.changes.value = true;
|
||||||
if(value == getValueOn())
|
|
||||||
|
if(getValueType() == ITEM_VALUE_UINT)
|
||||||
|
{
|
||||||
|
// Numeric value
|
||||||
|
req.payload.setValueData(value.toInt(0));
|
||||||
|
}
|
||||||
|
else if(getValueType() == ITEM_VALUE_ENUM)
|
||||||
|
{
|
||||||
|
// Enum value - find index
|
||||||
|
QString strValue = value.toString();
|
||||||
|
int index = valueNameToIndex(strValue);
|
||||||
|
if(index >= 0)
|
||||||
|
req.payload.setValueData(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Binary value
|
||||||
|
QString strValue = value.toString();
|
||||||
|
if(strValue == getValueOn() || strValue == "ON" || strValue == "true")
|
||||||
req.payload.setValueData(true);
|
req.payload.setValueData(true);
|
||||||
else
|
else
|
||||||
req.payload.setValueData(false);
|
req.payload.setValueData(false);
|
||||||
|
}
|
||||||
requestUpdate(req);
|
requestUpdate(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +240,59 @@ void MqttItem::setValueOff(const QString& valueOff)
|
||||||
valueOff_ = valueOff;
|
valueOff_ = valueOff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttItem::setValueMin(int min)
|
||||||
|
{
|
||||||
|
valueMin_ = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItem::setValueMax(int max)
|
||||||
|
{
|
||||||
|
valueMax_ = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItem::setValueStep(int step)
|
||||||
|
{
|
||||||
|
valueStep_ = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItem::setValueType(item_value_type_t type)
|
||||||
|
{
|
||||||
|
type_ = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItem::setFromExpose(const QJsonObject& expose)
|
||||||
|
{
|
||||||
|
QString type = expose["type"].toString();
|
||||||
|
QString property = expose["property"].toString();
|
||||||
|
|
||||||
|
setValueKey(property);
|
||||||
|
|
||||||
|
if(type == "binary")
|
||||||
|
{
|
||||||
|
type_ = ITEM_VALUE_BOOL;
|
||||||
|
setValueOn(expose["value_on"].toString("ON"));
|
||||||
|
setValueOff(expose["value_off"].toString("OFF"));
|
||||||
|
}
|
||||||
|
else if(type == "numeric")
|
||||||
|
{
|
||||||
|
type_ = ITEM_VALUE_UINT;
|
||||||
|
setValueMin(expose["value_min"].toInt(0));
|
||||||
|
setValueMax(expose["value_max"].toInt(255));
|
||||||
|
setValueStep(expose["value_step"].toInt(1));
|
||||||
|
}
|
||||||
|
else if(type == "enum")
|
||||||
|
{
|
||||||
|
type_ = ITEM_VALUE_ENUM;
|
||||||
|
QJsonArray values = expose["values"].toArray();
|
||||||
|
std::vector<QString> valueNames;
|
||||||
|
for(const QJsonValue& v : values)
|
||||||
|
valueNames.push_back(v.toString());
|
||||||
|
setValueNames(valueNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashId();
|
||||||
|
}
|
||||||
|
|
||||||
QString MqttItem::getTopic() const
|
QString MqttItem::getTopic() const
|
||||||
{
|
{
|
||||||
return topic_;
|
return topic_;
|
||||||
|
|
@ -126,6 +313,26 @@ QString MqttItem::getValueOff() const
|
||||||
return valueOff_;
|
return valueOff_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MqttItem::getValueMin() const
|
||||||
|
{
|
||||||
|
return valueMin_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MqttItem::getValueMax() const
|
||||||
|
{
|
||||||
|
return valueMax_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MqttItem::getValueStep() const
|
||||||
|
{
|
||||||
|
return valueStep_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MqttItem::getExposeLoaded() const
|
||||||
|
{
|
||||||
|
return exposeLoaded_;
|
||||||
|
}
|
||||||
|
|
||||||
void MqttItem::store(QJsonObject& json)
|
void MqttItem::store(QJsonObject& json)
|
||||||
{
|
{
|
||||||
Item::store(json);
|
Item::store(json);
|
||||||
|
|
@ -134,6 +341,9 @@ void MqttItem::store(QJsonObject& json)
|
||||||
json["ValueKey"] = valueKey_;
|
json["ValueKey"] = valueKey_;
|
||||||
json["ValueOn"] = valueOn_;
|
json["ValueOn"] = valueOn_;
|
||||||
json["ValueOff"] = valueOff_;
|
json["ValueOff"] = valueOff_;
|
||||||
|
json["ValueMin"] = valueMin_;
|
||||||
|
json["ValueMax"] = valueMax_;
|
||||||
|
json["ValueStep"] = valueStep_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttItem::load(const QJsonObject& json, const bool preserve)
|
void MqttItem::load(const QJsonObject& json, const bool preserve)
|
||||||
|
|
@ -143,6 +353,10 @@ void MqttItem::load(const QJsonObject& json, const bool preserve)
|
||||||
valueKey_ = json["ValueKey"].toString("state");
|
valueKey_ = json["ValueKey"].toString("state");
|
||||||
valueOn_ = json["ValueOn"].toString("ON");
|
valueOn_ = json["ValueOn"].toString("ON");
|
||||||
valueOff_ = json["ValueOff"].toString("OFF");
|
valueOff_ = json["ValueOff"].toString("OFF");
|
||||||
|
valueMin_ = json["ValueMin"].toInt(0);
|
||||||
|
valueMax_ = json["ValueMax"].toInt(255);
|
||||||
|
valueStep_ = json["ValueStep"].toInt(1);
|
||||||
|
exposeLoaded_ = json["ExposeLoaded"].toBool(false);
|
||||||
hashId();
|
hashId();
|
||||||
refreshSubscription();
|
refreshSubscription();
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +370,18 @@ void MqttItem::enactValue(uint8_t value)
|
||||||
QString fullTopic = workClient->getBaseTopic() + "/" + topic_ + "/set";
|
QString fullTopic = workClient->getBaseTopic() + "/" + topic_ + "/set";
|
||||||
QJsonObject payload;
|
QJsonObject payload;
|
||||||
|
|
||||||
|
if(getValueType() == ITEM_VALUE_UINT)
|
||||||
|
{
|
||||||
|
payload[valueKey_] = static_cast<int>(value);
|
||||||
|
}
|
||||||
|
else if(getValueType() == ITEM_VALUE_ENUM)
|
||||||
|
{
|
||||||
|
payload[valueKey_] = indexToValueName(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
payload[valueKey_] = value ? valueOn_ : valueOff_;
|
payload[valueKey_] = value ? valueOn_ : valueOff_;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonDocument doc(payload);
|
QJsonDocument doc(payload);
|
||||||
QByteArray data = doc.toJson(QJsonDocument::Compact);
|
QByteArray data = doc.toJson(QJsonDocument::Compact);
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,22 @@ public:
|
||||||
private:
|
private:
|
||||||
QString topic_;
|
QString topic_;
|
||||||
QString valueKey_;
|
QString valueKey_;
|
||||||
QString valueOn_;
|
QString valueOn_ = "ON";
|
||||||
QString valueOff_;
|
QString valueOff_ = "OFF";
|
||||||
|
int valueMin_ = 0;
|
||||||
|
int valueMax_ = 255;
|
||||||
|
int valueStep_ = 1;
|
||||||
|
bool exposeLoaded_ = false;
|
||||||
|
|
||||||
MqttClient::Subscription* subscription = nullptr;
|
MqttClient::Subscription* subscription = nullptr;
|
||||||
|
MqttClient::Subscription* devicesSubscription = nullptr;
|
||||||
|
|
||||||
void hashId();
|
void hashId();
|
||||||
void refreshSubscription();
|
void refreshSubscription();
|
||||||
void onMessageReceived(const QMqttMessage& message);
|
void onMessageReceived(const QMqttMessage& message);
|
||||||
void onClientStateChanged(QMqttClient::ClientState state);
|
void onClientStateChanged(QMqttClient::ClientState state);
|
||||||
|
void onDevicesMessageReceived(const QMqttMessage& message);
|
||||||
|
void loadExposeFromDevice(const QJsonObject& device);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MqttItem(QString name = "MqttItem",
|
explicit MqttItem(QString name = "MqttItem",
|
||||||
|
|
@ -36,12 +43,22 @@ public:
|
||||||
void setBaseTopic(const QString& baseTopic);
|
void setBaseTopic(const QString& baseTopic);
|
||||||
void setValueOn(const QString& valueOn);
|
void setValueOn(const QString& valueOn);
|
||||||
void setValueOff(const QString& valueOff);
|
void setValueOff(const QString& valueOff);
|
||||||
|
void setValueMin(int min);
|
||||||
|
void setValueMax(int max);
|
||||||
|
void setValueStep(int step);
|
||||||
|
void setValueType(item_value_type_t type);
|
||||||
|
|
||||||
|
// Configure from Zigbee2MQTT expose info
|
||||||
|
void setFromExpose(const QJsonObject& expose);
|
||||||
|
|
||||||
QString getTopic() const;
|
QString getTopic() const;
|
||||||
QString getValueKey() const;
|
QString getValueKey() const;
|
||||||
QString getBaseTopic() const;
|
|
||||||
QString getValueOn() const;
|
QString getValueOn() const;
|
||||||
QString getValueOff() const;
|
QString getValueOff() const;
|
||||||
|
int getValueMin() const;
|
||||||
|
int getValueMax() const;
|
||||||
|
int getValueStep() const;
|
||||||
|
bool getExposeLoaded() const;
|
||||||
|
|
||||||
virtual void store(QJsonObject& json) override;
|
virtual void store(QJsonObject& json) override;
|
||||||
virtual void load(const QJsonObject& json, const bool preserve = false) override;
|
virtual void load(const QJsonObject& json, const bool preserve = false) override;
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,12 @@ void MqttClient::start(const QJsonObject& settings)
|
||||||
|
|
||||||
client->setHostname(settings["Host"].toString("127.0.0.1"));
|
client->setHostname(settings["Host"].toString("127.0.0.1"));
|
||||||
client->setPort(settings["Port"].toInt(1883));
|
client->setPort(settings["Port"].toInt(1883));
|
||||||
|
client->setClientId(settings["ClientId"].toString("smartvos"));
|
||||||
if(settings.contains("User"))
|
if(settings.contains("User"))
|
||||||
client->setUsername(settings["User"].toString());
|
client->setUsername(settings["User"].toString());
|
||||||
if(settings.contains("Password"))
|
if(settings.contains("Password"))
|
||||||
client->setPassword(settings["Password"].toString());
|
client->setPassword(settings["Password"].toString());
|
||||||
client->setProtocolVersion(QMqttClient::MQTT_5_0);
|
client->setProtocolVersion(QMqttClient::MQTT_3_1);
|
||||||
|
|
||||||
client->connectToHost();
|
client->connectToHost();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue