Auto mqttitem attempt

This commit is contained in:
Carl Philipp Klemm 2026-04-17 18:35:16 +02:00
parent 25dd99d8b6
commit 05e4fb1cc1
3 changed files with 254 additions and 11 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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();
} }