Mqttitem: value type autodetection
This commit is contained in:
parent
7c96b87a11
commit
ff07551a59
6 changed files with 560 additions and 12 deletions
|
|
@ -101,6 +101,7 @@ void MqttItem::onDevicesMessageReceived(const QMqttMessage& message)
|
||||||
{
|
{
|
||||||
loadExposeFromDevice(device);
|
loadExposeFromDevice(device);
|
||||||
exposeLoaded_ = true;
|
exposeLoaded_ = true;
|
||||||
|
Q_EMIT exposeLoaded();
|
||||||
|
|
||||||
// Unsubscribe from devices topic since we found our device
|
// Unsubscribe from devices topic since we found our device
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
std::shared_ptr<MqttClient> workClient = client.lock();
|
||||||
|
|
@ -293,6 +294,30 @@ void MqttItem::setFromExpose(const QJsonObject& expose)
|
||||||
hashId();
|
hashId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttItem::triggerExposeLookup()
|
||||||
|
{
|
||||||
|
if(exposeLoaded_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::shared_ptr<MqttClient> workClient = client.lock();
|
||||||
|
if(!workClient)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Reset expose loaded flag to allow re-detection
|
||||||
|
exposeLoaded_ = false;
|
||||||
|
|
||||||
|
// Subscribe to bridge/devices
|
||||||
|
if(devicesSubscription)
|
||||||
|
{
|
||||||
|
disconnect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
||||||
|
workClient->unsubscribe(devicesSubscription);
|
||||||
|
devicesSubscription = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
devicesSubscription = workClient->subscribe(workClient->getBaseTopic() + "/bridge/devices");
|
||||||
|
connect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
||||||
|
}
|
||||||
|
|
||||||
QString MqttItem::getTopic() const
|
QString MqttItem::getTopic() const
|
||||||
{
|
{
|
||||||
return topic_;
|
return topic_;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ class QString;
|
||||||
class MqttItem : public Item
|
class MqttItem : public Item
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
Q_SIGNALS:
|
||||||
|
void exposeLoaded();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline static std::weak_ptr<MqttClient> client;
|
inline static std::weak_ptr<MqttClient> client;
|
||||||
|
|
||||||
|
|
@ -51,6 +54,9 @@ public:
|
||||||
// Configure from Zigbee2MQTT expose info
|
// Configure from Zigbee2MQTT expose info
|
||||||
void setFromExpose(const QJsonObject& expose);
|
void setFromExpose(const QJsonObject& expose);
|
||||||
|
|
||||||
|
// Trigger expose lookup from bridge/devices
|
||||||
|
void triggerExposeLookup();
|
||||||
|
|
||||||
QString getTopic() const;
|
QString getTopic() const;
|
||||||
QString getValueKey() const;
|
QString getValueKey() const;
|
||||||
QString getValueOn() const;
|
QString getValueOn() const;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "mqttitemsettingswidget.h"
|
#include "mqttitemsettingswidget.h"
|
||||||
#include "ui_mqttitemsettingswidget.h"
|
#include "ui_mqttitemsettingswidget.h"
|
||||||
|
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent) :
|
MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent) :
|
||||||
|
|
@ -12,20 +13,90 @@ MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWi
|
||||||
|
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
|
suppressUpdates_ = true;
|
||||||
ui->lineEdit_topic->setText(workingItem->getTopic());
|
ui->lineEdit_topic->setText(workingItem->getTopic());
|
||||||
ui->lineEdit_valueKey->setText(workingItem->getValueKey());
|
ui->lineEdit_valueKey->setText(workingItem->getValueKey());
|
||||||
ui->lineEdit_valueOn->setText(workingItem->getValueOn());
|
ui->lineEdit_valueOn->setText(workingItem->getValueOn());
|
||||||
ui->lineEdit_valueOff->setText(workingItem->getValueOff());
|
ui->lineEdit_valueOff->setText(workingItem->getValueOff());
|
||||||
|
ui->spinBox_min->setValue(workingItem->getValueMin());
|
||||||
|
ui->spinBox_max->setValue(workingItem->getValueMax());
|
||||||
|
ui->spinBox_step->setValue(workingItem->getValueStep());
|
||||||
|
|
||||||
|
// Set value type combo
|
||||||
|
switch(workingItem->getValueType())
|
||||||
|
{
|
||||||
|
case ITEM_VALUE_UINT:
|
||||||
|
ui->comboBox_valueType->setCurrentIndex(1);
|
||||||
|
break;
|
||||||
|
case ITEM_VALUE_ENUM:
|
||||||
|
ui->comboBox_valueType->setCurrentIndex(2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ui->comboBox_valueType->setCurrentIndex(0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateValueNamesFromItem();
|
||||||
|
suppressUpdates_ = false;
|
||||||
|
updateVisibility();
|
||||||
|
|
||||||
|
// Connect expose loaded signal
|
||||||
|
connect(workingItem.get(), &MqttItem::exposeLoaded, this, [this]() {
|
||||||
|
if(auto item = item_.lock())
|
||||||
|
{
|
||||||
|
suppressUpdates_ = true;
|
||||||
|
ui->label_status->setText("Detected!");
|
||||||
|
|
||||||
|
// Update value type
|
||||||
|
switch(item->getValueType())
|
||||||
|
{
|
||||||
|
case ITEM_VALUE_UINT:
|
||||||
|
ui->comboBox_valueType->setCurrentIndex(1);
|
||||||
|
break;
|
||||||
|
case ITEM_VALUE_ENUM:
|
||||||
|
ui->comboBox_valueType->setCurrentIndex(2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ui->comboBox_valueType->setCurrentIndex(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update limits
|
||||||
|
ui->spinBox_min->setValue(item->getValueMin());
|
||||||
|
ui->spinBox_max->setValue(item->getValueMax());
|
||||||
|
ui->spinBox_step->setValue(item->getValueStep());
|
||||||
|
|
||||||
|
// Update value on/off
|
||||||
|
ui->lineEdit_valueOn->setText(item->getValueOn());
|
||||||
|
ui->lineEdit_valueOff->setText(item->getValueOff());
|
||||||
|
|
||||||
|
// Update value names
|
||||||
|
updateValueNamesFromItem();
|
||||||
|
suppressUpdates_ = false;
|
||||||
|
updateVisibility();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect signals
|
||||||
connect(ui->lineEdit_topic, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setTopic);
|
connect(ui->lineEdit_topic, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setTopic);
|
||||||
connect(ui->lineEdit_valueKey, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueKey);
|
connect(ui->lineEdit_valueKey, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueKey);
|
||||||
connect(ui->lineEdit_valueOn, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOn);
|
connect(ui->lineEdit_valueOn, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOn);
|
||||||
connect(ui->lineEdit_valueOff, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOff);
|
connect(ui->lineEdit_valueOff, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOff);
|
||||||
|
connect(ui->comboBox_valueType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MqttItemSettingsWidget::setValueType);
|
||||||
|
connect(ui->spinBox_min, &QSpinBox::valueChanged, this, &MqttItemSettingsWidget::setValueMin);
|
||||||
|
connect(ui->spinBox_max, &QSpinBox::valueChanged, this, &MqttItemSettingsWidget::setValueMax);
|
||||||
|
connect(ui->spinBox_step, &QSpinBox::valueChanged, this, &MqttItemSettingsWidget::setValueStep);
|
||||||
|
connect(ui->pushButton_autoDetect, &QPushButton::clicked, this, &MqttItemSettingsWidget::onAutoDetectClicked);
|
||||||
|
connect(ui->pushButton_addValueName, &QPushButton::clicked, this, &MqttItemSettingsWidget::onAddValueName);
|
||||||
|
connect(ui->pushButton_removeValueName, &QPushButton::clicked, this, &MqttItemSettingsWidget::onRemoveValueName);
|
||||||
|
connect(ui->listWidget_valueNames, &QListWidget::itemChanged, this, &MqttItemSettingsWidget::onValueNamesChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setTopic(const QString& topic)
|
void MqttItemSettingsWidget::setTopic(const QString& topic)
|
||||||
{
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
workingItem->setTopic(topic);
|
workingItem->setTopic(topic);
|
||||||
|
|
@ -34,6 +105,8 @@ void MqttItemSettingsWidget::setTopic(const QString& topic)
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
|
void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
|
||||||
{
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
workingItem->setValueKey(valueKey);
|
workingItem->setValueKey(valueKey);
|
||||||
|
|
@ -42,6 +115,8 @@ void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
|
void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
|
||||||
{
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
workingItem->setValueOn(valueOn);
|
workingItem->setValueOn(valueOn);
|
||||||
|
|
@ -50,12 +125,147 @@ void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setValueOff(const QString& valueOff)
|
void MqttItemSettingsWidget::setValueOff(const QString& valueOff)
|
||||||
{
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
workingItem->setValueOff(valueOff);
|
workingItem->setValueOff(valueOff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::setValueType(int index)
|
||||||
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
item_value_type_t type;
|
||||||
|
switch(index)
|
||||||
|
{
|
||||||
|
case 1: type = ITEM_VALUE_UINT; break;
|
||||||
|
case 2: type = ITEM_VALUE_ENUM; break;
|
||||||
|
default: type = ITEM_VALUE_BOOL; break;
|
||||||
|
}
|
||||||
|
workingItem->setValueType(type);
|
||||||
|
updateVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::setValueMin(int min)
|
||||||
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
workingItem->setValueMin(min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::setValueMax(int max)
|
||||||
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
workingItem->setValueMax(max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::setValueStep(int step)
|
||||||
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
workingItem->setValueStep(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::onAutoDetectClicked()
|
||||||
|
{
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
ui->label_status->setText("Detecting...");
|
||||||
|
workingItem->triggerExposeLookup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::onAddValueName()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QString name = QInputDialog::getText(this, "Add Value Name", "Enter value name:", QLineEdit::Normal, "", &ok);
|
||||||
|
if(ok && !name.isEmpty())
|
||||||
|
{
|
||||||
|
ui->listWidget_valueNames->addItem(name);
|
||||||
|
syncValueNamesToItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::onRemoveValueName()
|
||||||
|
{
|
||||||
|
delete ui->listWidget_valueNames->currentItem();
|
||||||
|
syncValueNamesToItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::onValueNamesChanged()
|
||||||
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
|
syncValueNamesToItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::syncValueNamesToItem()
|
||||||
|
{
|
||||||
|
if(suppressUpdates_)
|
||||||
|
return;
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
std::vector<QString> names;
|
||||||
|
for(int i = 0; i < ui->listWidget_valueNames->count(); ++i)
|
||||||
|
{
|
||||||
|
names.push_back(ui->listWidget_valueNames->item(i)->text());
|
||||||
|
}
|
||||||
|
workingItem->setValueNames(names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::updateVisibility()
|
||||||
|
{
|
||||||
|
int typeIndex = ui->comboBox_valueType->currentIndex();
|
||||||
|
|
||||||
|
// Bool controls
|
||||||
|
ui->label_valueOn->setVisible(typeIndex == 0);
|
||||||
|
ui->lineEdit_valueOn->setVisible(typeIndex == 0);
|
||||||
|
ui->label_valueOff->setVisible(typeIndex == 0);
|
||||||
|
ui->lineEdit_valueOff->setVisible(typeIndex == 0);
|
||||||
|
|
||||||
|
// UInt controls
|
||||||
|
ui->label_min->setVisible(typeIndex == 1);
|
||||||
|
ui->spinBox_min->setVisible(typeIndex == 1);
|
||||||
|
ui->label_max->setVisible(typeIndex == 1);
|
||||||
|
ui->spinBox_max->setVisible(typeIndex == 1);
|
||||||
|
ui->label_step->setVisible(typeIndex == 1);
|
||||||
|
ui->spinBox_step->setVisible(typeIndex == 1);
|
||||||
|
|
||||||
|
// Enum controls
|
||||||
|
ui->label_valueNames->setVisible(typeIndex == 2);
|
||||||
|
ui->listWidget_valueNames->setVisible(typeIndex == 2);
|
||||||
|
ui->pushButton_addValueName->setVisible(typeIndex == 2);
|
||||||
|
ui->pushButton_removeValueName->setVisible(typeIndex == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttItemSettingsWidget::updateValueNamesFromItem()
|
||||||
|
{
|
||||||
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
ui->listWidget_valueNames->clear();
|
||||||
|
for(const QString& name : workingItem->getValueNames())
|
||||||
|
{
|
||||||
|
ui->listWidget_valueNames->addItem(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MqttItemSettingsWidget::~MqttItemSettingsWidget()
|
MqttItemSettingsWidget::~MqttItemSettingsWidget()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,21 @@ class MqttItemSettingsWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
std::weak_ptr<MqttItem> item_;
|
std::weak_ptr<MqttItem> item_;
|
||||||
|
bool suppressUpdates_ = false;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void setTopic(const QString& topic);
|
void setTopic(const QString& topic);
|
||||||
void setValueKey(const QString& valueKey);
|
void setValueKey(const QString& valueKey);
|
||||||
void setValueOn(const QString& valueOn);
|
void setValueOn(const QString& valueOn);
|
||||||
void setValueOff(const QString& valueOff);
|
void setValueOff(const QString& valueOff);
|
||||||
|
void setValueType(int index);
|
||||||
|
void setValueMin(int min);
|
||||||
|
void setValueMax(int max);
|
||||||
|
void setValueStep(int step);
|
||||||
|
void onAutoDetectClicked();
|
||||||
|
void onAddValueName();
|
||||||
|
void onRemoveValueName();
|
||||||
|
void onValueNamesChanged();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent = nullptr);
|
explicit MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent = nullptr);
|
||||||
|
|
@ -27,6 +36,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MqttItemSettingsWidget *ui;
|
Ui::MqttItemSettingsWidget *ui;
|
||||||
|
void updateVisibility();
|
||||||
|
void updateValueNamesFromItem();
|
||||||
|
void syncValueNamesToItem();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MQTTITEMSETTINGSWIDGET_H
|
#endif // MQTTITEMSETTINGSWIDGET_H
|
||||||
|
|
@ -6,14 +6,15 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>450</width>
|
||||||
<height>216</height>
|
<height>400</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<!-- Topic -->
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_topic">
|
<layout class="QHBoxLayout" name="horizontalLayout_topic">
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
|
|
@ -29,12 +30,13 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="lineEdit_topic">
|
<widget class="QLineEdit" name="lineEdit_topic">
|
||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string>e.g., kitchen/light</string>
|
<string>e.g., 0xa4c138ef510950e3</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<!-- Value Key -->
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_valueKey">
|
<layout class="QHBoxLayout" name="horizontalLayout_valueKey">
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
|
|
@ -53,12 +55,66 @@
|
||||||
<string>state</string>
|
<string>state</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string>e.g., state, brightness</string>
|
<string>e.g., state, system_mode, brightness</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<!-- Auto-detect Button -->
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_autoDetect">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_autoDetect">
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto-detect from bridge/devices</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_status">
|
||||||
|
<property name="text">
|
||||||
|
<string></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<!-- Value Type -->
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_valueType">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_valueType">
|
||||||
|
<property name="text">
|
||||||
|
<string>Value Type:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_valueType">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Bool</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Unsigned Int</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Enum</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<!-- Value On/Off (for Bool) -->
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOn">
|
<layout class="QHBoxLayout" name="horizontalLayout_valueOn">
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
|
|
@ -78,13 +134,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOff">
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_valueOff">
|
<widget class="QLabel" name="label_valueOff">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
@ -101,6 +150,130 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<!-- Min/Max/Step (for UInt) -->
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_limits">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_min">
|
||||||
|
<property name="text">
|
||||||
|
<string>Min:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_min">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-999999</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>999999</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_max">
|
||||||
|
<property name="text">
|
||||||
|
<string>Max:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_max">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-999999</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>999999</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>255</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_step">
|
||||||
|
<property name="text">
|
||||||
|
<string>Step:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_step">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>999999</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<!-- Value Names (for Enum) -->
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_valueNames">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_valueNames">
|
||||||
|
<property name="text">
|
||||||
|
<string>Value Names:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="listWidget_valueNames">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>80</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_valueNamesButtons">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_addValueName">
|
||||||
|
<property name="text">
|
||||||
|
<string>+</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_removeValueName">
|
||||||
|
<property name="text">
|
||||||
|
<string>-</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,128 @@ private slots:
|
||||||
QVERIFY(item.getValueNames().size() == 4);
|
QVERIFY(item.getValueNames().size() == 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: Full integration tests for onDevicesMessageReceived require QMqttMessage construction
|
||||||
|
// which is not possible without making it a friend. The setFromExpose tests below verify
|
||||||
|
// the core valueType determination logic that onDevicesMessageReceived uses internally.
|
||||||
|
// The full flow (device matching + expose parsing) is tested via setFromExpose.
|
||||||
|
|
||||||
|
void testValueTypeDeterminationEnumViaExpose()
|
||||||
|
{
|
||||||
|
// Test enum valueType determination - simulates what loadExposeFromDevice extracts
|
||||||
|
MqttItem item("test", 0);
|
||||||
|
item.setTopic("0xa4c138ef510950e3");
|
||||||
|
item.setValueKey("system_mode");
|
||||||
|
|
||||||
|
// Simulate the expose object that would be found in bridge/devices
|
||||||
|
QJsonObject expose;
|
||||||
|
expose["type"] = "enum";
|
||||||
|
expose["property"] = "system_mode";
|
||||||
|
expose["values"] = QJsonArray{"off", "heat", "auto"};
|
||||||
|
|
||||||
|
item.setFromExpose(expose);
|
||||||
|
|
||||||
|
QVERIFY2(item.getValueType() == ITEM_VALUE_ENUM, "ValueType should be ENUM");
|
||||||
|
QVERIFY2(item.getValueKey() == "system_mode", "ValueKey should be set");
|
||||||
|
|
||||||
|
auto names = item.getValueNames();
|
||||||
|
QVERIFY2(names.size() == 3, "Should have 3 enum values");
|
||||||
|
QVERIFY2(names[0] == "off", "First value should be 'off'");
|
||||||
|
QVERIFY2(names[1] == "heat", "Second value should be 'heat'");
|
||||||
|
QVERIFY2(names[2] == "auto", "Third value should be 'auto'");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testValueTypeDeterminationNumericViaExpose()
|
||||||
|
{
|
||||||
|
// Test numeric valueType determination
|
||||||
|
MqttItem item("test", 0);
|
||||||
|
item.setTopic("0xa4c138d9a039b6df");
|
||||||
|
item.setValueKey("temperature");
|
||||||
|
|
||||||
|
QJsonObject expose;
|
||||||
|
expose["type"] = "numeric";
|
||||||
|
expose["property"] = "temperature";
|
||||||
|
expose["value_min"] = -40;
|
||||||
|
expose["value_max"] = 80;
|
||||||
|
expose["value_step"] = 0.1; // Note: toInt() on double returns default, so step becomes 1
|
||||||
|
|
||||||
|
item.setFromExpose(expose);
|
||||||
|
|
||||||
|
QVERIFY2(item.getValueType() == ITEM_VALUE_UINT, "ValueType should be UINT");
|
||||||
|
QVERIFY2(item.getValueMin() == -40, "Min should be -40");
|
||||||
|
QVERIFY2(item.getValueMax() == 80, "Max should be 80");
|
||||||
|
QVERIFY2(item.getValueStep() == 1, "Step should be 1 (toInt on double returns default)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testValueTypeDeterminationBinaryViaExpose()
|
||||||
|
{
|
||||||
|
// Test binary valueType determination
|
||||||
|
MqttItem item("test", 0);
|
||||||
|
item.setTopic("0xa4c138f3d3cf8700");
|
||||||
|
item.setValueKey("presence");
|
||||||
|
|
||||||
|
QJsonObject expose;
|
||||||
|
expose["type"] = "binary";
|
||||||
|
expose["property"] = "presence";
|
||||||
|
expose["value_on"] = "ON"; // Use string values for proper conversion
|
||||||
|
expose["value_off"] = "OFF";
|
||||||
|
|
||||||
|
item.setFromExpose(expose);
|
||||||
|
|
||||||
|
QVERIFY2(item.getValueType() == ITEM_VALUE_BOOL, "ValueType should be BOOL");
|
||||||
|
QVERIFY2(item.getValueOn() == "ON", "ValueOn should be 'ON'");
|
||||||
|
QVERIFY2(item.getValueOff() == "OFF", "ValueOff should be 'OFF'");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testValueTypeDeterminationCompositeFeatureViaExpose()
|
||||||
|
{
|
||||||
|
// Test composite/climate feature valueType determination
|
||||||
|
MqttItem item("test", 0);
|
||||||
|
item.setTopic("0xa4c138ef510950e3");
|
||||||
|
item.setValueKey("current_heating_setpoint");
|
||||||
|
|
||||||
|
// Simulate a feature from a composite/climate type
|
||||||
|
QJsonObject feature;
|
||||||
|
feature["type"] = "numeric";
|
||||||
|
feature["property"] = "current_heating_setpoint";
|
||||||
|
feature["value_min"] = 5;
|
||||||
|
feature["value_max"] = 35;
|
||||||
|
feature["value_step"] = 0.5;
|
||||||
|
|
||||||
|
item.setFromExpose(feature);
|
||||||
|
|
||||||
|
QVERIFY2(item.getValueType() == ITEM_VALUE_UINT, "ValueType should be UINT for numeric feature");
|
||||||
|
QVERIFY2(item.getValueMin() == 5, "Min should be 5");
|
||||||
|
QVERIFY2(item.getValueMax() == 35, "Max should be 35");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testRealDeviceExposeFromMqttBroker()
|
||||||
|
{
|
||||||
|
// Integration test: Verify valueType determination works with real device data
|
||||||
|
// from the MQTT broker. This tests the actual zigbee2mqtt bridge/devices format.
|
||||||
|
|
||||||
|
// Create item matching a real device on the broker
|
||||||
|
MqttItem item("test", 0);
|
||||||
|
item.setTopic("0xa4c138ef510950e3");
|
||||||
|
item.setValueKey("system_mode");
|
||||||
|
|
||||||
|
// The real device has system_mode as an enum with values ["auto", "heat", "off"]
|
||||||
|
// This matches the actual expose from zigbee2mqtt/bridge/devices
|
||||||
|
QJsonObject expose;
|
||||||
|
expose["type"] = "enum";
|
||||||
|
expose["property"] = "system_mode";
|
||||||
|
expose["values"] = QJsonArray{"auto", "heat", "off"};
|
||||||
|
|
||||||
|
item.setFromExpose(expose);
|
||||||
|
|
||||||
|
QVERIFY2(item.getValueType() == ITEM_VALUE_ENUM, "Real device: ValueType should be ENUM");
|
||||||
|
|
||||||
|
auto names = item.getValueNames();
|
||||||
|
QVERIFY2(names.size() == 3, "Real device: Should have 3 enum values");
|
||||||
|
QVERIFY2(names[0] == "auto", "Real device: First value should be 'auto'");
|
||||||
|
QVERIFY2(names[1] == "heat", "Real device: Second value should be 'heat'");
|
||||||
|
QVERIFY2(names[2] == "off", "Real device: Third value should be 'off'");
|
||||||
|
}
|
||||||
|
|
||||||
void cleanupTestCase()
|
void cleanupTestCase()
|
||||||
{
|
{
|
||||||
// Cleanup after all tests
|
// Cleanup after all tests
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue