Compare commits
15 commits
7c96b87a11
...
51193a5d0b
| Author | SHA1 | Date | |
|---|---|---|---|
| 51193a5d0b | |||
| afb2d23173 | |||
| 7af4ec4957 | |||
| 25b9f87285 | |||
| 36171a221a | |||
| 93999abafa | |||
| a07b019a22 | |||
| f2b2e8f0a0 | |||
| 6fd04eca01 | |||
| cfe51b0fd3 | |||
| a96b27c741 | |||
| 221cb519a2 | |||
| 2fbfd1d458 | |||
| 09f7e55b4e | |||
| ff07551a59 |
42 changed files with 1617 additions and 138 deletions
|
|
@ -148,6 +148,8 @@ add_executable(smartvos
|
|||
src/ui/itemsettingsdialog.cpp
|
||||
src/ui/actorsettingsdialog.h
|
||||
src/ui/actorsettingsdialog.cpp
|
||||
src/ui/sensorsettingsdialog.h
|
||||
src/ui/sensorsettingsdialog.cpp
|
||||
|
||||
src/ui/actorwidgets/factoractorwidget.h
|
||||
src/ui/actorwidgets/factoractorwidget.cpp
|
||||
|
|
@ -181,6 +183,7 @@ target_sources(smartvos
|
|||
src/ui/itemcreationdialog.ui
|
||||
src/ui/itemsettingsdialog.ui
|
||||
src/ui/actorsettingsdialog.ui
|
||||
src/ui/sensorsettingsdialog.ui
|
||||
src/ui/actorwidgets/factoractorwidget.ui
|
||||
src/ui/actorwidgets/polynomalactorwidget.ui
|
||||
src/ui/actorwidgets/sensoractorwidget.ui
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ void PolynomalActor::getCoeffiancts( double& pow3, double& pow2, double& pow1, d
|
|||
pow0=pow0_;
|
||||
}
|
||||
|
||||
void PolynomalActor::sensorEvent(Sensor sensor)
|
||||
void PolynomalActor::sensorEvent(Sensor sensor, sensor_update_type_t type)
|
||||
{
|
||||
if(active && sensor == sensor_)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ private:
|
|||
|
||||
public slots:
|
||||
|
||||
void sensorEvent(Sensor sensor);
|
||||
void sensorEvent(Sensor sensor, sensor_update_type_t type);
|
||||
|
||||
public:
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ void Regulator::setSensor(const Sensor sensor)
|
|||
sensor_ = sensor;
|
||||
}
|
||||
|
||||
void Regulator::sensorEvent(Sensor sensor)
|
||||
void Regulator::sensorEvent(Sensor sensor, sensor_update_type_t type)
|
||||
{
|
||||
if(active && sensor == sensor_)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ private slots:
|
|||
|
||||
public slots:
|
||||
|
||||
void sensorEvent(Sensor sensor);
|
||||
void sensorEvent(Sensor sensor, sensor_update_type_t type);
|
||||
|
||||
void setSensor(const Sensor sensor);
|
||||
void setPoint(float setPoint );
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ void SensorActor::setSensor(const Sensor sensor)
|
|||
sensor_ = sensor;
|
||||
}
|
||||
|
||||
void SensorActor::sensorEvent(Sensor sensor)
|
||||
void SensorActor::sensorEvent(Sensor sensor, sensor_update_type_t type)
|
||||
{
|
||||
if(sensor == sensor_)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ private:
|
|||
|
||||
public slots:
|
||||
|
||||
void sensorEvent(Sensor sensor);
|
||||
void sensorEvent(Sensor sensor, sensor_update_type_t type);
|
||||
|
||||
void setSloap(uint8_t sloap);
|
||||
void setSensor(const Sensor sensor);
|
||||
|
|
|
|||
|
|
@ -58,9 +58,9 @@ void ItemData::storeWithChanges(QJsonObject& json, const ItemFieldChanges& chang
|
|||
json["Name"] = name_;
|
||||
if(changes.value)
|
||||
json["Value"] = static_cast<double>(value_);
|
||||
if(changes.groupName)
|
||||
if(changes.groupName && !groupName_.isEmpty() && groupName_ != "All")
|
||||
json["GroupName"] = groupName_;
|
||||
if(changes.valueNames)
|
||||
if(changes.valueNames && !valueNames_.empty())
|
||||
{
|
||||
QJsonArray valueNamesArray;
|
||||
for(const QString& name : valueNames_)
|
||||
|
|
@ -218,6 +218,8 @@ void Item::store(QJsonObject &json)
|
|||
{
|
||||
ItemData::store(json);
|
||||
json["override"] = override_;
|
||||
if(!actors_.empty())
|
||||
{
|
||||
QJsonArray actorsArray;
|
||||
for(size_t i = 0; i < actors_.size(); ++i)
|
||||
{
|
||||
|
|
@ -230,11 +232,14 @@ void Item::store(QJsonObject &json)
|
|||
}
|
||||
json["Actors"] = actorsArray;
|
||||
}
|
||||
}
|
||||
|
||||
void Item::load(const QJsonObject &json, const bool preserve)
|
||||
{
|
||||
ItemData::load(json, preserve);
|
||||
override_ = json["override"].toBool(false);
|
||||
if(json.contains("Actors"))
|
||||
{
|
||||
const QJsonArray actorsArray(json["Actors"].toArray(QJsonArray()));
|
||||
for(int i = 0; i < actorsArray.size(); ++i)
|
||||
{
|
||||
|
|
@ -246,6 +251,7 @@ void Item::load(const QJsonObject &json, const bool preserve)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item& Item::operator=(const ItemData& other)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ void ItemLoaderSource::refresh()
|
|||
request.type = ITEM_UPDATE_LOADED;
|
||||
request.payload = newItem;
|
||||
request.changes = ItemFieldChanges(true);
|
||||
if(newItem->hasActors())
|
||||
request.changes.actors = true;
|
||||
request.changes.value = false;
|
||||
itemAddRequests.push_back(request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ void MqttItem::onDevicesMessageReceived(const QMqttMessage& message)
|
|||
{
|
||||
loadExposeFromDevice(device);
|
||||
exposeLoaded_ = true;
|
||||
Q_EMIT exposeLoaded();
|
||||
|
||||
// Unsubscribe from devices topic since we found our device
|
||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
||||
|
|
@ -293,6 +294,30 @@ void MqttItem::setFromExpose(const QJsonObject& expose)
|
|||
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
|
||||
{
|
||||
return topic_;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ class QString;
|
|||
class MqttItem : public Item
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_SIGNALS:
|
||||
void exposeLoaded();
|
||||
|
||||
public:
|
||||
inline static std::weak_ptr<MqttClient> client;
|
||||
|
||||
|
|
@ -51,6 +54,9 @@ public:
|
|||
// Configure from Zigbee2MQTT expose info
|
||||
void setFromExpose(const QJsonObject& expose);
|
||||
|
||||
// Trigger expose lookup from bridge/devices
|
||||
void triggerExposeLookup();
|
||||
|
||||
QString getTopic() const;
|
||||
QString getValueKey() const;
|
||||
QString getValueOn() const;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
PowerItem::PowerItem(uint32_t itemIdIn, QString name, uint8_t value, QObject* parent):
|
||||
Item(itemIdIn, name, value, parent)
|
||||
{
|
||||
stateChanged(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 0, "Shutdown Imminent", true));
|
||||
globalSensors.sensorGotState(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 0, "Shutdown Imminent", true), SENSOR_UPDATE_BACKEND);
|
||||
value_ = true;
|
||||
hidden_ = true;
|
||||
type_ = ITEM_VALUE_NO_VALUE;
|
||||
|
|
@ -18,7 +18,7 @@ void PowerItem::enactValue(uint8_t value)
|
|||
{
|
||||
qDebug()<<"shutdown";
|
||||
QTimer::singleShot(5000, this, &PowerItem::timeout);
|
||||
stateChanged(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 1, "Shutdown Imminent", true));
|
||||
globalSensors.sensorGotState(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 1, "Shutdown Imminent", true), SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ class PowerItem: public Item
|
|||
private:
|
||||
|
||||
signals:
|
||||
|
||||
void stateChanged(Sensor sensor);
|
||||
void stateChanged(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
||||
|
||||
private slots:
|
||||
void timeout();
|
||||
|
|
|
|||
49
src/main.cpp
49
src/main.cpp
|
|
@ -14,17 +14,14 @@
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
//pw_init(&argc, &argv);
|
||||
|
||||
//set info
|
||||
QCoreApplication::setOrganizationName("UVOS");
|
||||
QCoreApplication::setOrganizationDomain("uvos.xyz");
|
||||
QCoreApplication::setApplicationName("SHinterface");
|
||||
QCoreApplication::setApplicationVersion("0.6");
|
||||
|
||||
QDir::setCurrent(a.applicationDirPath());
|
||||
QStringList args;
|
||||
for(int i = 0; i < argc; ++i)
|
||||
args<<argv[i];
|
||||
|
||||
//parse comand line
|
||||
QCommandLineParser parser;
|
||||
|
|
@ -42,9 +39,8 @@ int main(int argc, char *argv[])
|
|||
parser.addOption(settingsPathOption);
|
||||
QCommandLineOption headlessOption(QStringList()<<"e"<<"headless", QCoreApplication::translate("main", "Dont start the gui"));
|
||||
parser.addOption(headlessOption);
|
||||
parser.process(a);
|
||||
|
||||
int retVal;
|
||||
parser.process(args);
|
||||
|
||||
programMode = PROGRAM_MODE_UI_ONLY;
|
||||
if(parser.isSet(masterOption))
|
||||
|
|
@ -53,6 +49,17 @@ int main(int argc, char *argv[])
|
|||
if(parser.isSet(headlessOption))
|
||||
programMode = PROGRAM_MODE_HEADLESS_PRIMARY;
|
||||
}
|
||||
|
||||
QCoreApplication* a;
|
||||
if(programMode == PROGRAM_MODE_HEADLESS_PRIMARY)
|
||||
a = new QCoreApplication(argc, argv);
|
||||
else
|
||||
a = new QApplication(argc, argv);
|
||||
|
||||
int retVal;
|
||||
|
||||
QString uiSettingsPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/smartvos_ui.json";
|
||||
|
||||
if(programMode == PROGRAM_MODE_PRIMARY || programMode == PROGRAM_MODE_HEADLESS_PRIMARY)
|
||||
{
|
||||
QString settingsPath = parser.value(settingsPathOption);
|
||||
|
|
@ -106,11 +113,13 @@ int main(int argc, char *argv[])
|
|||
microDevice = microPort;
|
||||
}
|
||||
PrimaryMainObject mainObject(microDevice, settingsPath, parser.value(hostOption), parser.value(portOption).toInt());
|
||||
QObject::connect(mainObject.tcpServer, &TcpServer::sigRequestSave, &mainObject, [&mainObject, settingsPath](){mainObject.storeToDisk(settingsPath);});
|
||||
MainWindow* w = nullptr;
|
||||
if(programMode != PROGRAM_MODE_HEADLESS_PRIMARY)
|
||||
{
|
||||
w = new MainWindow(&mainObject);
|
||||
QJsonObject uiSettings = MainObject::getJsonObjectFromDisk(uiSettingsPath);
|
||||
w->load(uiSettings);
|
||||
|
||||
QObject::connect(&mainObject.micro, SIGNAL(textRecived(QString)), w, SLOT(changeHeaderLableText(QString)));
|
||||
QObject::connect(&mainObject.micro, SIGNAL(textRecived(QString)), w, SLOT(changeHeaderLableText(QString)));
|
||||
QObject::connect(w, &MainWindow::sigSetRgb, &mainObject.micro, &Microcontroller::changeRgbColor);
|
||||
|
|
@ -118,7 +127,15 @@ int main(int argc, char *argv[])
|
|||
QObject::connect(w, &MainWindow::createdItem, &globalItems, &ItemStore::addItem);
|
||||
w->show();
|
||||
}
|
||||
retVal = a.exec();
|
||||
|
||||
retVal = a->exec();
|
||||
|
||||
if(programMode != PROGRAM_MODE_HEADLESS_PRIMARY)
|
||||
{
|
||||
QJsonObject uiSettingsJson;
|
||||
w->store(uiSettingsJson);
|
||||
MainObject::storeJsonObjectToDisk(uiSettingsPath, uiSettingsJson);
|
||||
}
|
||||
|
||||
delete w;
|
||||
delete microDevice;
|
||||
|
|
@ -127,13 +144,23 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
SecondaryMainObject mainObject(parser.value(hostOption), parser.value(portOption).toInt());
|
||||
MainWindow w(&mainObject);
|
||||
QJsonObject uiSettings = MainObject::getJsonObjectFromDisk(uiSettingsPath);
|
||||
w.load(uiSettings);
|
||||
|
||||
QObject::connect(&w, &MainWindow::createdItem, &globalItems, &ItemStore::addItem);
|
||||
QObject::connect(&w, &MainWindow::sigSave, mainObject.tcpClient, &TcpClient::sendItems);
|
||||
|
||||
w.show();
|
||||
|
||||
retVal = a.exec();
|
||||
retVal = a->exec();
|
||||
|
||||
QJsonObject uiSettingsJson;
|
||||
w.store(uiSettingsJson);
|
||||
MainObject::storeJsonObjectToDisk(uiSettingsPath, uiSettingsJson);
|
||||
}
|
||||
|
||||
delete a;
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ void MainObject::refresh()
|
|||
globalItems.refresh();
|
||||
}
|
||||
|
||||
void MainObject::addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
|
||||
{
|
||||
// Default implementation does nothing - derived classes override
|
||||
}
|
||||
|
||||
QJsonObject MainObject::getJsonObjectFromDisk(const QString& filename, bool* error)
|
||||
{
|
||||
QFile file;
|
||||
|
|
@ -89,8 +94,8 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
|||
connect(&sunSensorSource, &SunSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
|
||||
connect(µ, &Microcontroller::gotSensorState, &globalSensors, &SensorStore::sensorGotState);
|
||||
connect(&mqttSensorSource, &MqttSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
|
||||
|
||||
sunSensorSource.run();
|
||||
connect(tcpServer, &TcpServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
|
||||
connect(webServer, &WebSocketServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
|
||||
|
||||
globalItems.registerItemSource(&fixedItems);
|
||||
globalItems.registerItemSource(tcpServer);
|
||||
|
|
@ -102,6 +107,8 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
|||
|
||||
loadFromDisk(settingsPath);
|
||||
|
||||
sunSensorSource.run();
|
||||
|
||||
QJsonObject mqttJson = settings["Mqtt"].toObject();
|
||||
mqttClient->start(mqttJson);
|
||||
mqttSensorSource.start(mqttClient, mqttJson);
|
||||
|
|
@ -120,6 +127,7 @@ PrimaryMainObject::~PrimaryMainObject()
|
|||
void PrimaryMainObject::store(QJsonObject &json)
|
||||
{
|
||||
globalItems.store(json);
|
||||
globalSensors.store(json);
|
||||
QJsonObject mqttJson = json["Mqtt"].toObject();
|
||||
mqttClient->store(mqttJson);
|
||||
mqttSensorSource.store(mqttJson);
|
||||
|
|
@ -130,6 +138,7 @@ void PrimaryMainObject::load(const QJsonObject& json)
|
|||
{
|
||||
settings = json;
|
||||
itemLoader.updateJson(json);
|
||||
globalSensors.load(json);
|
||||
globalItems.clear();
|
||||
globalItems.refresh();
|
||||
}
|
||||
|
|
@ -172,3 +181,8 @@ SecondaryMainObject::~SecondaryMainObject()
|
|||
{
|
||||
}
|
||||
|
||||
void SecondaryMainObject::addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
|
||||
{
|
||||
tcpClient->addSensor(sensor, backend, payload);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void refresh();
|
||||
virtual void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {});
|
||||
};
|
||||
|
||||
class PrimaryMainObject : public MainObject
|
||||
|
|
@ -74,6 +75,7 @@ public:
|
|||
public:
|
||||
explicit SecondaryMainObject(QString host, int port, QObject *parent = nullptr);
|
||||
~SecondaryMainObject();
|
||||
void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {}) override;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ void Microcontroller::processSensorState(const QString& buffer)
|
|||
{
|
||||
Sensor sensor = Sensor::sensorFromString(buffer);
|
||||
if(sensor.type != Sensor::TYPE_DUMMY)
|
||||
gotSensorState(sensor);
|
||||
gotSensorState(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ private slots:
|
|||
|
||||
signals:
|
||||
void textRecived(const QString string);
|
||||
void gotSensorState(Sensor sensor);
|
||||
void gotSensorState(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
||||
};
|
||||
|
||||
#endif // MICROCONTROLLER_H
|
||||
|
|
|
|||
|
|
@ -31,6 +31,25 @@ void MqttSensorSource::start(std::shared_ptr<MqttClient> client, const QJsonObje
|
|||
}
|
||||
}
|
||||
|
||||
void MqttSensorSource::addSensor(const QString& topic, const QString& name)
|
||||
{
|
||||
if(!client)
|
||||
return;
|
||||
|
||||
SensorSubscription sensor;
|
||||
sensor.topic = topic;
|
||||
sensor.name = name;
|
||||
sensor.id = qHash(client->getBaseTopic() + "/" + topic);
|
||||
sensors.push_back(sensor);
|
||||
|
||||
// Subscribe if already connected
|
||||
if(client->getClient()->state() == QMqttClient::ClientState::Connected)
|
||||
{
|
||||
sensor.subscription = client->subscribe(client->getBaseTopic() + "/" + topic);
|
||||
connect(sensor.subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttSensorSource::onMessageReceived);
|
||||
}
|
||||
}
|
||||
|
||||
MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const QString& topic)
|
||||
{
|
||||
for(SensorSubscription& sensor : sensors)
|
||||
|
|
@ -39,6 +58,7 @@ MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const Q
|
|||
return sensor;
|
||||
}
|
||||
assert(false);
|
||||
return sensors.front();
|
||||
}
|
||||
|
||||
void MqttSensorSource::onClientStateChanged(QMqttClient::ClientState state)
|
||||
|
|
@ -81,7 +101,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Temperature";
|
||||
sensor.type = Sensor::TYPE_TEMPERATURE;
|
||||
sensor.field = obj["temperature"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("local_temperature"))
|
||||
|
|
@ -89,7 +109,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Temperature";
|
||||
sensor.type = Sensor::TYPE_TEMPERATURE;
|
||||
sensor.field = obj["local_temperature"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("humidity"))
|
||||
|
|
@ -97,7 +117,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Humidity";
|
||||
sensor.type = Sensor::TYPE_HUMIDITY;
|
||||
sensor.field = obj["humidity"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("illuminance"))
|
||||
|
|
@ -105,7 +125,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Illuminance";
|
||||
sensor.type = Sensor::TYPE_BRIGHTNESS;
|
||||
sensor.field = obj["illuminance"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("presence"))
|
||||
|
|
@ -113,7 +133,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Presence";
|
||||
sensor.type = Sensor::TYPE_OCUPANCY;
|
||||
sensor.field = obj["presence"].toBool() ? 1 : 0;
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("co2"))
|
||||
|
|
@ -121,7 +141,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " co2";
|
||||
sensor.type = Sensor::TYPE_CO2;
|
||||
sensor.field = obj["co2"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("formaldehyd"))
|
||||
|
|
@ -129,7 +149,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Formaldehyd";
|
||||
sensor.type = Sensor::TYPE_FORMALDEHYD;
|
||||
sensor.field = obj["formaldehyd"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("pm25"))
|
||||
|
|
@ -137,7 +157,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " pm25";
|
||||
sensor.type = Sensor::TYPE_PM25;
|
||||
sensor.field = obj["pm25"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("voc"))
|
||||
|
|
@ -145,7 +165,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " VOC";
|
||||
sensor.type = Sensor::TYPE_TOTAL_VOC;
|
||||
sensor.field = obj["voc"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("power"))
|
||||
|
|
@ -153,7 +173,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Power";
|
||||
sensor.type = Sensor::TYPE_POWER;
|
||||
sensor.field = obj["power"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("energy"))
|
||||
|
|
@ -161,7 +181,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Energy";
|
||||
sensor.type = Sensor::TYPE_ENERGY_USE;
|
||||
sensor.field = obj["energy"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
||||
if(obj.contains("voltage"))
|
||||
|
|
@ -169,7 +189,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
|||
sensor.name = baseName + " Voltage";
|
||||
sensor.type = Sensor::TYPE_VOLTAGE;
|
||||
sensor.field = obj["voltage"].toDouble(0);
|
||||
stateChanged(sensor);
|
||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -193,3 +213,16 @@ MqttSensorSource::~MqttSensorSource()
|
|||
client->unsubscribe(client->getBaseTopic() + "/" + sub.topic);
|
||||
}
|
||||
|
||||
void MqttSensorSource::onSensorAdded(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
|
||||
{
|
||||
if(backend != Sensor::BACKEND_MQTT)
|
||||
return;
|
||||
|
||||
QString topic = payload["Topic"].toString();
|
||||
QString name = payload["Name"].toString();
|
||||
if(topic.isEmpty())
|
||||
return;
|
||||
|
||||
addSensor(topic, name);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,14 +31,18 @@ private slots:
|
|||
void onClientStateChanged(QMqttClient::ClientState state);
|
||||
void onMessageReceived(const QMqttMessage& message);
|
||||
|
||||
public slots:
|
||||
void onSensorAdded(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload);
|
||||
|
||||
public:
|
||||
explicit MqttSensorSource(QObject *parent = nullptr);
|
||||
~MqttSensorSource();
|
||||
void start(std::shared_ptr<MqttClient> client, const QJsonObject& settings);
|
||||
void addSensor(const QString& topic, const QString& name);
|
||||
void store(QJsonObject& json);
|
||||
|
||||
signals:
|
||||
void stateChanged(Sensor sensor);
|
||||
void stateChanged(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
||||
};
|
||||
|
||||
#endif // MQTTSENSORSOURCE_H
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "sensor.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
|
||||
SensorStore globalSensors;
|
||||
|
||||
|
|
@ -10,28 +11,115 @@ SensorStore::SensorStore(QObject *parent): QObject(parent)
|
|||
sensors_.push_back(Sensor(Sensor::TYPE_DOOR,0,0,"Bedroom door"));
|
||||
}
|
||||
|
||||
void SensorStore::sensorGotState(const Sensor& sensor)
|
||||
void SensorStore::store(QJsonObject& json)
|
||||
{
|
||||
bool exsisting = false;
|
||||
QJsonArray sensorsArray;
|
||||
for(const Sensor& sensor : sensors_)
|
||||
{
|
||||
QJsonObject sensorObject;
|
||||
sensor.store(sensorObject);
|
||||
sensorsArray.append(sensorObject);
|
||||
}
|
||||
json["Sensors"] = sensorsArray;
|
||||
}
|
||||
|
||||
void SensorStore::load(const QJsonObject& json)
|
||||
{
|
||||
knownSensors_.clear();
|
||||
QJsonArray sensorsArray = json["Sensors"].toArray();
|
||||
for(const QJsonValue& value : sensorsArray)
|
||||
{
|
||||
knownSensors_.push_back(Sensor(value.toObject()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<QString> SensorStore::allGroups() const
|
||||
{
|
||||
std::vector<QString> groups;
|
||||
for(const Sensor& sensor : sensors_)
|
||||
{
|
||||
if(!sensor.groupName.isEmpty())
|
||||
{
|
||||
bool found = false;
|
||||
for(const QString& group : groups)
|
||||
{
|
||||
if(group == sensor.groupName)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
groups.push_back(sensor.groupName);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
void SensorStore::sensorGotState(const Sensor& sensor, sensor_update_type_t type)
|
||||
{
|
||||
bool inSensors = false;
|
||||
|
||||
qDebug()<<"Sensor update for id"<<sensor.id<<"type"<<sensor.type<<"update type"<<type<<"for"<<sensor.name<<"with group"<<sensor.groupName;
|
||||
|
||||
for(unsigned i = 0; i < sensors_.size(); ++i)
|
||||
{
|
||||
if(sensor.type == sensors_[i].type && sensor.id == sensors_[i].id)
|
||||
{
|
||||
sensors_[i].updateSeen();
|
||||
if(sensors_[i].field != sensor.field)
|
||||
bool needsUpdate = false;
|
||||
|
||||
if(type == SENSOR_UPDATE_USER)
|
||||
{
|
||||
sensors_[i].field = sensor.field;
|
||||
sensorChangedState(sensor);
|
||||
stateChenged(sensors_);
|
||||
}
|
||||
exsisting = true;
|
||||
if(sensors_[i].name != sensor.name || sensors_[i].hidden != sensor.hidden || sensors_[i].groupName != sensor.groupName)
|
||||
{
|
||||
sensors_[i].name = sensor.name;
|
||||
sensors_[i].hidden = sensor.hidden;
|
||||
sensors_[i].groupName = sensor.groupName;
|
||||
for(Sensor& known : knownSensors_)
|
||||
{
|
||||
if(sensor.type == known.type && sensor.id == known.id)
|
||||
{
|
||||
known.name = sensor.name;
|
||||
known.hidden = sensor.hidden;
|
||||
known.groupName = sensor.groupName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!exsisting)
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
else if(sensors_[i].field != sensor.field)
|
||||
{
|
||||
sensors_.push_back(sensor);
|
||||
sensorChangedState(sensor);
|
||||
needsUpdate = true;
|
||||
sensors_[i].field = sensor.field;
|
||||
}
|
||||
|
||||
if(needsUpdate)
|
||||
{
|
||||
sensorChangedState(sensors_[i], type);
|
||||
stateChenged(sensors_);
|
||||
}
|
||||
|
||||
inSensors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!inSensors)
|
||||
{
|
||||
Sensor newSensor = sensor;
|
||||
for(const Sensor& known : knownSensors_)
|
||||
{
|
||||
if(sensor.type == known.type && sensor.id == known.id)
|
||||
{
|
||||
newSensor.name = known.name;
|
||||
newSensor.hidden = known.hidden;
|
||||
newSensor.groupName = known.groupName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sensors_.push_back(newSensor);
|
||||
sensorChangedState(newSensor, type);
|
||||
stateChenged(sensors_);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,21 +33,28 @@ public:
|
|||
TYPE_DUMMY,
|
||||
} sensor_type_t;
|
||||
|
||||
typedef enum {
|
||||
BACKEND_MICROCONTROLLER = 0,
|
||||
BACKEND_MQTT,
|
||||
BACKEND_SUN,
|
||||
} sensor_backend_type_t;
|
||||
|
||||
sensor_type_t type;
|
||||
uint64_t id;
|
||||
float field;
|
||||
QString name;
|
||||
QString groupName;
|
||||
QDateTime lastSeen;
|
||||
bool hidden;
|
||||
|
||||
Sensor(sensor_type_t typeIn, uint64_t idIn, float fieldIn = 0, QString nameIn = "", bool hiddenIn = false): type(typeIn),
|
||||
id(idIn), field(fieldIn), name(nameIn), hidden(hiddenIn)
|
||||
Sensor(sensor_type_t typeIn, uint64_t idIn, float fieldIn = 0, QString nameIn = "", bool hiddenIn = false, QString groupNameIn = ""): type(typeIn),
|
||||
id(idIn), field(fieldIn), name(nameIn), groupName(groupNameIn), hidden(hiddenIn)
|
||||
{
|
||||
lastSeen = QDateTime::currentDateTime();
|
||||
if(nameIn == "")
|
||||
generateName();
|
||||
}
|
||||
Sensor(QString nameIn = "dummy"): type(TYPE_DUMMY), id(0), field(0), name(nameIn), hidden(false)
|
||||
Sensor(QString nameIn = "dummy"): type(TYPE_DUMMY), id(0), field(0), name(nameIn), groupName(""), hidden(false)
|
||||
{
|
||||
lastSeen = QDateTime::currentDateTime();
|
||||
}
|
||||
|
|
@ -59,6 +66,7 @@ public:
|
|||
lastSeen = QDateTime::fromString(json["LastSeen"].toString(""));
|
||||
hidden = json["Hidden"].toBool(false);
|
||||
name = json["Name"].toString();
|
||||
groupName = json["GroupName"].toString();
|
||||
if(name == "")
|
||||
generateName();
|
||||
}
|
||||
|
|
@ -98,13 +106,14 @@ public:
|
|||
QString::number((type == Sensor::TYPE_HUMIDITY || type == Sensor::TYPE_TEMPERATURE) ? field*10 : field) +
|
||||
" TIME: " + QString::number(lastSeen.toSecsSinceEpoch());
|
||||
}
|
||||
inline void store(QJsonObject& json)
|
||||
inline void store(QJsonObject& json) const
|
||||
{
|
||||
json["Type"] = "Sensor";
|
||||
json["SensorType"] = static_cast<int>(type);
|
||||
json["Id"] = static_cast<int>(id);
|
||||
json["Field"] = field;
|
||||
json["Name"] = name;
|
||||
json["GroupName"] = groupName;
|
||||
json["LastSeen"] = lastSeen.toString();
|
||||
json["Hidden"] = hidden;
|
||||
json["Unit"] = getUnit();
|
||||
|
|
@ -127,7 +136,7 @@ public:
|
|||
name = "Shutdown Imminent";
|
||||
else name = "Sensor Type " + QString::number(type) + " Id " + QString::number(id);
|
||||
}
|
||||
QString getUnit()
|
||||
QString getUnit() const
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
|
|
@ -160,11 +169,19 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
SENSOR_UPDATE_USER = 0,
|
||||
SENSOR_UPDATE_BACKEND,
|
||||
SENSOR_UPDATE_REMOTE,
|
||||
SENSOR_UPDATE_INVALID
|
||||
} sensor_update_type_t;
|
||||
|
||||
class SensorStore: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
std::vector<Sensor> sensors_;
|
||||
std::vector<Sensor> knownSensors_;
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -176,15 +193,18 @@ public:
|
|||
return &sensors_;
|
||||
}
|
||||
|
||||
void store(QJsonObject& json);
|
||||
void load(const QJsonObject& json);
|
||||
std::vector<QString> allGroups() const;
|
||||
|
||||
public slots:
|
||||
|
||||
void sensorGotState(const Sensor& sensor);
|
||||
void sensorGotState(const Sensor& sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
||||
|
||||
signals:
|
||||
|
||||
void stateChenged(std::vector<Sensor> sensors);
|
||||
void sensorChangedState(Sensor sensor);
|
||||
void sensorChangedState(Sensor sensor, sensor_update_type_t type);
|
||||
void sensorDeleted(Sensor sensor);
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,5 +21,5 @@ void SunSensorSource::abort()
|
|||
|
||||
void SunSensorSource::doTick()
|
||||
{
|
||||
stateChanged(Sensor(Sensor::TYPE_SUN_ALTITUDE, 0, static_cast<float>(sun_.altitude())));
|
||||
stateChanged(Sensor(Sensor::TYPE_SUN_ALTITUDE, 0, static_cast<float>(sun_.altitude())), SENSOR_UPDATE_BACKEND);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public slots:
|
|||
void abort();
|
||||
|
||||
signals:
|
||||
void stateChanged(Sensor sensor);
|
||||
void stateChanged(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
||||
|
||||
private slots:
|
||||
void doTick();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ QJsonObject Service::createMessage(const QString& type, const QJsonArray& data)
|
|||
return json;
|
||||
}
|
||||
|
||||
void Service::sensorEvent(Sensor sensor)
|
||||
void Service::sensorEvent(Sensor sensor, sensor_update_type_t type)
|
||||
{
|
||||
QJsonArray sensors;
|
||||
QJsonObject sensorjson;
|
||||
|
|
@ -31,6 +31,17 @@ void Service::sensorEvent(Sensor sensor)
|
|||
|
||||
void Service::itemUpdated(ItemUpdateRequest update) {}
|
||||
|
||||
void Service::addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
|
||||
{
|
||||
QJsonObject sensorjson;
|
||||
sensor.store(sensorjson);
|
||||
QJsonObject json = createMessage("AddSensor", QJsonArray());
|
||||
json["Sensor"] = sensorjson;
|
||||
json["Backend"] = static_cast<int>(backend);
|
||||
json["Payload"] = payload;
|
||||
sendJson(json);
|
||||
}
|
||||
|
||||
void Service::refresh()
|
||||
{
|
||||
sendJson(createMessage("GetSensors", QJsonArray()));
|
||||
|
|
@ -85,7 +96,15 @@ void Service::processIncomeingJson(const QByteArray& jsonbytes)
|
|||
{
|
||||
QJsonObject jsonobject = sensorjson.toObject();
|
||||
Sensor sensor(jsonobject);
|
||||
gotSensor(sensor);
|
||||
gotSensor(sensor, SENSOR_UPDATE_REMOTE);
|
||||
}
|
||||
}
|
||||
else if(type == "AddSensor")
|
||||
{
|
||||
QJsonObject sensorjson = json["Sensor"].toObject();
|
||||
Sensor sensor(sensorjson);
|
||||
Sensor::sensor_backend_type_t backend = static_cast<Sensor::sensor_backend_type_t>(json["Backend"].toInt(0));
|
||||
QJsonObject payload = json["Payload"].toObject();
|
||||
emit sensorAdded(sensor, backend, payload);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ protected:
|
|||
} client_state_t;
|
||||
|
||||
signals:
|
||||
void gotSensor(Sensor sensor);
|
||||
void gotSensor(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
||||
void sensorAdded(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload);
|
||||
|
||||
public slots:
|
||||
void sensorEvent(Sensor sensor);
|
||||
void sensorEvent(Sensor sensor, sensor_update_type_t type);
|
||||
virtual void itemUpdated(ItemUpdateRequest update);
|
||||
virtual void refresh() override;
|
||||
virtual void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {});
|
||||
|
||||
public:
|
||||
Service(QObject* parent = nullptr);
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ void ItemScrollBox::ensureTabExists(const QString& groupName)
|
|||
|
||||
ui->tabWidget->addTab(tab.scroller, groupName);
|
||||
tabs_[groupName] = tab;
|
||||
|
||||
if(groupName == pendingSelectedGroup_)
|
||||
{
|
||||
ui->tabWidget->setCurrentWidget(tab.scroller);
|
||||
pendingSelectedGroup_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,3 +195,21 @@ void ItemScrollBox::cleanupEmptyTabs()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemScrollBox::store(QJsonObject& json) const
|
||||
{
|
||||
QJsonObject itemScrollBoxJson;
|
||||
int currentIndex = ui->tabWidget->currentIndex();
|
||||
if(currentIndex >= 0)
|
||||
{
|
||||
QString selectedGroup = ui->tabWidget->tabText(currentIndex);
|
||||
itemScrollBoxJson["SelectedGroup"] = selectedGroup;
|
||||
}
|
||||
json["ItemScrollBox"] = itemScrollBoxJson;
|
||||
}
|
||||
|
||||
void ItemScrollBox::load(const QJsonObject& json)
|
||||
{
|
||||
QJsonObject itemScrollBoxJson = json["ItemScrollBox"].toObject();
|
||||
pendingSelectedGroup_ = itemScrollBoxJson["SelectedGroup"].toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <memory>
|
||||
#include <QScrollArea>
|
||||
#include <QSpacerItem>
|
||||
#include <QJsonObject>
|
||||
#include "itemwidget.h"
|
||||
#include "../items/item.h"
|
||||
#include "../items/itemstore.h"
|
||||
|
|
@ -30,6 +31,7 @@ private:
|
|||
QMap<QString, Tab> tabs_;
|
||||
QMap<QString, std::vector<ItemWidget*>> widgets_;
|
||||
Ui::RelayScrollBox *ui;
|
||||
QString pendingSelectedGroup_;
|
||||
|
||||
signals:
|
||||
void deleteRequest(const ItemData& item);
|
||||
|
|
@ -41,6 +43,9 @@ public:
|
|||
|
||||
void setItemStore(ItemStore* itemStore);
|
||||
|
||||
void store(QJsonObject& json) const;
|
||||
void load(const QJsonObject& json);
|
||||
|
||||
public slots:
|
||||
|
||||
void addItem(std::weak_ptr<Item> item);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "mqttitemsettingswidget.h"
|
||||
#include "ui_mqttitemsettingswidget.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QDebug>
|
||||
|
||||
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())
|
||||
{
|
||||
suppressUpdates_ = true;
|
||||
ui->lineEdit_topic->setText(workingItem->getTopic());
|
||||
ui->lineEdit_valueKey->setText(workingItem->getValueKey());
|
||||
ui->lineEdit_valueOn->setText(workingItem->getValueOn());
|
||||
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_valueKey, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueKey);
|
||||
connect(ui->lineEdit_valueOn, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOn);
|
||||
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)
|
||||
{
|
||||
if(suppressUpdates_)
|
||||
return;
|
||||
if(auto workingItem = item_.lock())
|
||||
{
|
||||
workingItem->setTopic(topic);
|
||||
|
|
@ -34,6 +105,8 @@ void MqttItemSettingsWidget::setTopic(const QString& topic)
|
|||
|
||||
void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
|
||||
{
|
||||
if(suppressUpdates_)
|
||||
return;
|
||||
if(auto workingItem = item_.lock())
|
||||
{
|
||||
workingItem->setValueKey(valueKey);
|
||||
|
|
@ -42,6 +115,8 @@ void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
|
|||
|
||||
void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
|
||||
{
|
||||
if(suppressUpdates_)
|
||||
return;
|
||||
if(auto workingItem = item_.lock())
|
||||
{
|
||||
workingItem->setValueOn(valueOn);
|
||||
|
|
@ -50,12 +125,147 @@ void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
|
|||
|
||||
void MqttItemSettingsWidget::setValueOff(const QString& valueOff)
|
||||
{
|
||||
if(suppressUpdates_)
|
||||
return;
|
||||
if(auto workingItem = item_.lock())
|
||||
{
|
||||
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()
|
||||
{
|
||||
delete ui;
|
||||
|
|
|
|||
|
|
@ -14,12 +14,21 @@ class MqttItemSettingsWidget : public QWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
std::weak_ptr<MqttItem> item_;
|
||||
bool suppressUpdates_ = false;
|
||||
|
||||
private slots:
|
||||
void setTopic(const QString& topic);
|
||||
void setValueKey(const QString& valueKey);
|
||||
void setValueOn(const QString& valueOn);
|
||||
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:
|
||||
explicit MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent = nullptr);
|
||||
|
|
@ -27,6 +36,9 @@ public:
|
|||
|
||||
private:
|
||||
Ui::MqttItemSettingsWidget *ui;
|
||||
void updateVisibility();
|
||||
void updateValueNamesFromItem();
|
||||
void syncValueNamesToItem();
|
||||
};
|
||||
|
||||
#endif // MQTTITEMSETTINGSWIDGET_H
|
||||
|
|
@ -6,14 +6,15 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>216</height>
|
||||
<width>450</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<!-- Topic -->
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_topic">
|
||||
<property name="topMargin">
|
||||
|
|
@ -29,12 +30,13 @@
|
|||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_topic">
|
||||
<property name="placeholderText">
|
||||
<string>e.g., kitchen/light</string>
|
||||
<string>e.g., 0xa4c138ef510950e3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<!-- Value Key -->
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_valueKey">
|
||||
<property name="topMargin">
|
||||
|
|
@ -53,12 +55,66 @@
|
|||
<string>state</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g., state, brightness</string>
|
||||
<string>e.g., state, system_mode, brightness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOn">
|
||||
<property name="topMargin">
|
||||
|
|
@ -78,13 +134,6 @@
|
|||
</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">
|
||||
|
|
@ -101,6 +150,130 @@
|
|||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "mainwindow.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "ui_mainwindow.h"
|
||||
#include "itemscrollbox.h"
|
||||
|
|
@ -9,10 +10,12 @@
|
|||
#include "mainobject.h"
|
||||
#include "programmode.h"
|
||||
#include "items/poweritem.h"
|
||||
#include "sensors/mqttsensorsource.h"
|
||||
|
||||
MainWindow::MainWindow(MainObject * const mainObject, QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
mainObject(mainObject),
|
||||
colorChooser(this)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
|
@ -45,7 +48,9 @@ MainWindow::MainWindow(MainObject * const mainObject, QWidget *parent) :
|
|||
ui->button_color->hide();
|
||||
|
||||
connect(ui->pushButton_addItem, &QPushButton::clicked, this, &MainWindow::showItemCreationDialog);
|
||||
connect(ui->pushButton_addSensor, &QPushButton::clicked, this, &MainWindow::showSensorCreationDialog);
|
||||
connect(ui->relayList, &ItemScrollBox::deleteRequest, &globalItems, &ItemStore::removeItem);
|
||||
connect(ui->checkBox_sensorsShowHidden, &QCheckBox::clicked, ui->sensorListView, &SensorListWidget::setShowHidden);
|
||||
|
||||
ui->splitter->setStretchFactor(1, 1);
|
||||
}
|
||||
|
|
@ -89,6 +94,35 @@ void MainWindow::showItemCreationDialog()
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::showSensorCreationDialog()
|
||||
{
|
||||
bool ok;
|
||||
QString topic = QInputDialog::getText(this, "Add MQTT Sensor", "Topic:", QLineEdit::Normal, "", &ok);
|
||||
if(!ok || topic.isEmpty())
|
||||
return;
|
||||
|
||||
QString name = QInputDialog::getText(this, "Add MQTT Sensor", "Name:", QLineEdit::Normal, topic, &ok);
|
||||
if(!ok)
|
||||
return;
|
||||
|
||||
Sensor sensor(Sensor::TYPE_DUMMY, 0, 0, name);
|
||||
QJsonObject payload;
|
||||
payload["Topic"] = topic;
|
||||
payload["Name"] = name;
|
||||
|
||||
PrimaryMainObject* primaryMain = dynamic_cast<PrimaryMainObject*>(mainObject);
|
||||
if(primaryMain)
|
||||
{
|
||||
// Primary mode: add directly to mqttSensorSource
|
||||
primaryMain->mqttSensorSource.addSensor(topic, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Secondary mode: send via TCP to primary
|
||||
mainObject->addSensor(sensor, Sensor::BACKEND_MQTT, payload);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::changeHeaderLableText(QString string)
|
||||
{
|
||||
if(string.size() > 28)
|
||||
|
|
@ -98,3 +132,36 @@ void MainWindow::changeHeaderLableText(QString string)
|
|||
}
|
||||
ui->label_serialRecive->setText(string);
|
||||
}
|
||||
|
||||
void MainWindow::store(QJsonObject& json) const
|
||||
{
|
||||
QJsonObject mainWindowJson;
|
||||
|
||||
QList<int> splitterSizes = ui->splitter->sizes();
|
||||
QJsonArray splitterSizeArray;
|
||||
for(int size : splitterSizes)
|
||||
splitterSizeArray.append(size);
|
||||
mainWindowJson["SplitterSizes"] = splitterSizeArray;
|
||||
|
||||
ui->relayList->store(mainWindowJson);
|
||||
ui->sensorListView->store(mainWindowJson);
|
||||
|
||||
json["MainWindow"] = mainWindowJson;
|
||||
}
|
||||
|
||||
void MainWindow::load(const QJsonObject& json)
|
||||
{
|
||||
QJsonObject mainWindowJson = json["MainWindow"].toObject();
|
||||
|
||||
QJsonArray splitterSizes = mainWindowJson["SplitterSizes"].toArray();
|
||||
if(!splitterSizes.isEmpty())
|
||||
{
|
||||
QList<int> sizes;
|
||||
for(const QJsonValue& size : splitterSizes)
|
||||
sizes.append(size.toInt());
|
||||
ui->splitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
ui->relayList->load(mainWindowJson);
|
||||
ui->sensorListView->load(mainWindowJson);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <QListWidgetItem>
|
||||
#include <QTime>
|
||||
#include <memory>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include<items/item.h>
|
||||
|
||||
|
|
@ -24,8 +25,12 @@ public:
|
|||
explicit MainWindow(MainObject * const mainObject, QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
void store(QJsonObject& json) const;
|
||||
void load(const QJsonObject& json);
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
MainObject* const mainObject;
|
||||
|
||||
QColorDialog colorChooser;
|
||||
|
||||
|
|
@ -40,6 +45,7 @@ private slots:
|
|||
//RGB
|
||||
void showPowerItemDialog();
|
||||
void showItemCreationDialog();
|
||||
void showSensorCreationDialog();
|
||||
|
||||
public slots:
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_sensorsShowHidden">
|
||||
<property name="text">
|
||||
<string>Show Hidden</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -223,6 +230,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_addSensor">
|
||||
<property name="text">
|
||||
<string>Add Sensor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_quit">
|
||||
<property name="sizePolicy">
|
||||
|
|
|
|||
|
|
@ -3,83 +3,200 @@
|
|||
#include <QDebug>
|
||||
#include <QHeaderView>
|
||||
#include <QScroller>
|
||||
#include <QMap>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
SensorListWidget::SensorListWidget(const bool showHidden, QWidget *parent): QTableWidget(parent),
|
||||
#include "sensorsettingsdialog.h"
|
||||
|
||||
SensorListWidget::SensorListWidget(const bool showHidden, QWidget *parent): QTreeWidget(parent),
|
||||
showHidden_(showHidden)
|
||||
{
|
||||
setColumnCount(3);
|
||||
setHeaderLabels({"Sensor", "Value", "Time"});
|
||||
setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture);
|
||||
setAutoScroll(true);
|
||||
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
sensorsChanged(std::vector<Sensor>());
|
||||
verticalHeader()->hide();
|
||||
|
||||
connect(this, &QTreeWidget::itemDoubleClicked, this, &SensorListWidget::onDoubleClick);
|
||||
}
|
||||
|
||||
SensorListWidget::SensorListWidget(SensorStore& sensorStore, const bool showHidden,
|
||||
QWidget* parent): QTableWidget (parent), showHidden_(showHidden)
|
||||
QWidget* parent): QTreeWidget (parent), showHidden_(showHidden)
|
||||
{
|
||||
sensorsChanged(*(sensorStore.getSensors()));
|
||||
connect(this, &QTreeWidget::itemDoubleClicked, this, &SensorListWidget::onDoubleClick);
|
||||
}
|
||||
|
||||
void SensorListWidget::onDoubleClick(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
if(item && item->type() == 1001)
|
||||
{
|
||||
const Sensor& sensor = getSensorForIndex(currentIndex());
|
||||
SensorSettingsDialog diag(sensor, this);
|
||||
if(diag.exec())
|
||||
{
|
||||
Sensor updatedSensor = sensor;
|
||||
updatedSensor.name = diag.getName();
|
||||
updatedSensor.hidden = diag.getHidden();
|
||||
updatedSensor.groupName = diag.getGroupName();
|
||||
globalSensors.sensorGotState(updatedSensor, SENSOR_UPDATE_USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SensorListWidget::sensorsChanged(std::vector<Sensor> sensors)
|
||||
{
|
||||
clear();
|
||||
setHorizontalHeaderItem(0, new QTableWidgetItem("Sensor"));
|
||||
setHorizontalHeaderItem(1, new QTableWidgetItem("Value"));
|
||||
setHorizontalHeaderItem(2, new QTableWidgetItem("Time"));
|
||||
size_t listLen = 0;
|
||||
for(size_t i = 0; i < sensors.size(); ++i)
|
||||
if(showHidden_ || !sensors[i].hidden)
|
||||
++listLen;
|
||||
setRowCount(static_cast<int>(listLen));
|
||||
size_t row = 0;
|
||||
for(size_t i = 0; i < sensors.size(); ++i)
|
||||
{
|
||||
if(showHidden_ || !sensors[i].hidden)
|
||||
{
|
||||
QString itemString;
|
||||
itemString.append(QString::number(sensors[i].field));
|
||||
itemString.append(' ');
|
||||
QMap<QString, bool> expandedStates;
|
||||
QList<int> columnWidths;
|
||||
|
||||
if(sensors[i].type == Sensor::TYPE_DOOR)
|
||||
for(int i = 0; i < columnCount(); ++i)
|
||||
columnWidths.append(columnWidth(i));
|
||||
|
||||
for(int i = 0; i < topLevelItemCount(); ++i)
|
||||
{
|
||||
if(static_cast<bool>(sensors[i].field))
|
||||
itemString.append("\"Open\"");
|
||||
else itemString.append("\"Closed\"");
|
||||
}
|
||||
else if(sensors[i].type == Sensor::TYPE_AUDIO_OUTPUT)
|
||||
QTreeWidgetItem* item = topLevelItem(i);
|
||||
if(item->type() != 1001)
|
||||
{
|
||||
if(static_cast<bool>(sensors[i].field))
|
||||
itemString.append("\"Playing\"");
|
||||
else itemString.append("\"Silent\"");
|
||||
expandedStates[item->text(0)] = item->isExpanded();
|
||||
}
|
||||
else if(!sensors[i].getUnit().isEmpty())
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
QMap<QString, QTreeWidgetItem*> groupItems;
|
||||
|
||||
QStringList headerLabels = {"Sensor", "Value", "Time"};
|
||||
setHeaderLabels(headerLabels);
|
||||
|
||||
QList<SensorListItem*> ungroupedItems;
|
||||
|
||||
for(const Sensor& sensor : sensors)
|
||||
{
|
||||
if(!showHidden_ && sensor.hidden)
|
||||
continue;
|
||||
|
||||
QString itemString = QString::number(sensor.field);
|
||||
|
||||
if(sensor.type == Sensor::TYPE_DOOR)
|
||||
{
|
||||
if(static_cast<bool>(sensor.field))
|
||||
itemString = "\"Open\"";
|
||||
else
|
||||
itemString = "\"Closed\"";
|
||||
}
|
||||
else if(sensor.type == Sensor::TYPE_AUDIO_OUTPUT)
|
||||
{
|
||||
if(static_cast<bool>(sensor.field))
|
||||
itemString = "\"Playing\"";
|
||||
else
|
||||
itemString = "\"Silent\"";
|
||||
}
|
||||
else if(!sensor.getUnit().isEmpty())
|
||||
{
|
||||
itemString.append(" ");
|
||||
itemString.append(sensors[i].getUnit());
|
||||
itemString.append(sensor.getUnit());
|
||||
}
|
||||
|
||||
setItem(static_cast<int>(row), 0, new SensorListItem(sensors[i].name + (sensors[i].hidden ? " (H)" : ""), sensors[i]));
|
||||
setItem(static_cast<int>(row), 1, new QTableWidgetItem(itemString));
|
||||
if(sensors[i].type <= 128)
|
||||
setItem(static_cast<int>(row), 2, new QTableWidgetItem(sensors[i].lastSeen.time().toString("hh:mm")));
|
||||
++row;
|
||||
SensorListItem* sensorItem = new SensorListItem(
|
||||
sensor.name + (sensor.hidden ? " (H)" : ""), sensor);
|
||||
sensorItem->setText(0, sensor.name + (sensor.hidden ? " (H)" : ""));
|
||||
sensorItem->setText(1, itemString);
|
||||
if(sensor.type <= 128)
|
||||
sensorItem->setText(2, sensor.lastSeen.time().toString("hh:mm"));
|
||||
|
||||
if(sensor.groupName.isEmpty())
|
||||
{
|
||||
ungroupedItems.append(sensorItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
QTreeWidgetItem* groupItem;
|
||||
auto it = groupItems.find(sensor.groupName);
|
||||
if(it == groupItems.end())
|
||||
{
|
||||
groupItem = new QTreeWidgetItem(this);
|
||||
groupItem->setText(0, sensor.groupName);
|
||||
|
||||
bool wasExpanded = expandedStates.value(sensor.groupName, false);
|
||||
if(!wasExpanded && pendingGroupExpandedStates_.contains(sensor.groupName))
|
||||
{
|
||||
wasExpanded = pendingGroupExpandedStates_[sensor.groupName];
|
||||
}
|
||||
groupItem->setExpanded(wasExpanded);
|
||||
groupItems[sensor.groupName] = groupItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
groupItem = it.value();
|
||||
}
|
||||
groupItem->addChild(sensorItem);
|
||||
}
|
||||
}
|
||||
|
||||
for(SensorListItem* item : ungroupedItems)
|
||||
{
|
||||
addTopLevelItem(item);
|
||||
}
|
||||
sortItems(0, Qt::AscendingOrder);
|
||||
resizeColumnsToContents();
|
||||
|
||||
for(auto it = groupItems.begin(); it != groupItems.end(); ++it)
|
||||
{
|
||||
it.value()->sortChildren(0, Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
for(int i = 0; i < columnCount() && i < columnWidths.size(); ++i)
|
||||
setColumnWidth(i, columnWidths.at(i));
|
||||
}
|
||||
|
||||
const Sensor& SensorListWidget::getSensorForIndex(const QModelIndex &index)
|
||||
{
|
||||
return static_cast<SensorListItem*>(item(index.row(), 0))->getSensor();
|
||||
QTreeWidgetItem* item = itemFromIndex(index);
|
||||
if(item && item->type() == 1001)
|
||||
return static_cast<SensorListItem*>(item)->getSensor();
|
||||
static Sensor dummy;
|
||||
return dummy;
|
||||
}
|
||||
|
||||
void SensorListWidget::setShowHidden(const bool showHidden)
|
||||
{
|
||||
showHidden_ = showHidden;
|
||||
sensorsChanged(*globalSensors.getSensors());
|
||||
}
|
||||
|
||||
void SensorListWidget::store(QJsonObject& json) const
|
||||
{
|
||||
QJsonObject sensorListJson;
|
||||
|
||||
QJsonObject groupStates;
|
||||
for(int i = 0; i < topLevelItemCount(); ++i)
|
||||
{
|
||||
QTreeWidgetItem* item = topLevelItem(i);
|
||||
if(item->type() != 1001)
|
||||
{
|
||||
groupStates[item->text(0)] = item->isExpanded();
|
||||
}
|
||||
}
|
||||
sensorListJson["GroupStates"] = groupStates;
|
||||
|
||||
json["SensorList"] = sensorListJson;
|
||||
}
|
||||
|
||||
void SensorListWidget::load(const QJsonObject& json)
|
||||
{
|
||||
QJsonObject sensorListJson = json["SensorList"].toObject();
|
||||
|
||||
QJsonObject groupStates = sensorListJson["GroupStates"].toObject();
|
||||
pendingGroupExpandedStates_.clear();
|
||||
for(auto it = groupStates.begin(); it != groupStates.end(); ++it)
|
||||
{
|
||||
pendingGroupExpandedStates_[it.key()] = it.value().toBool();
|
||||
}
|
||||
}
|
||||
|
||||
const Sensor& SensorListItem::getSensor()
|
||||
|
|
@ -88,7 +205,8 @@ const Sensor& SensorListItem::getSensor()
|
|||
}
|
||||
|
||||
SensorListItem::SensorListItem(const QString& text, const Sensor& sensor):
|
||||
QTableWidgetItem(text, 1001), sensor(sensor)
|
||||
QTreeWidgetItem(1001), sensor(sensor)
|
||||
{
|
||||
setText(0, text);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
#include <QTableWidget>
|
||||
#include <QTreeWidget>
|
||||
#include <vector>
|
||||
#include "sensors/sensor.h"
|
||||
|
||||
class SensorListItem : public QTableWidgetItem
|
||||
class SensorListItem : public QTreeWidgetItem
|
||||
{
|
||||
Sensor sensor;
|
||||
|
||||
|
|
@ -12,22 +12,30 @@ public:
|
|||
SensorListItem(const QString& text, const Sensor& sensor);
|
||||
};
|
||||
|
||||
class SensorListWidget : public QTableWidget
|
||||
class SensorListWidget : public QTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
bool showHidden_;
|
||||
QString savedSelectedGroup_;
|
||||
QMap<QString, bool> pendingGroupExpandedStates_;
|
||||
|
||||
public:
|
||||
|
||||
SensorListWidget(const bool showHidden = true, QWidget* parent = nullptr);
|
||||
SensorListWidget(SensorStore& sensorStore, const bool showHidden = true, QWidget* parent = nullptr);
|
||||
virtual ~SensorListWidget() {}
|
||||
void setShowHidden(const bool showHidden);
|
||||
|
||||
const Sensor& getSensorForIndex(const QModelIndex &index);
|
||||
|
||||
void store(QJsonObject& json) const;
|
||||
void load(const QJsonObject& json);
|
||||
|
||||
public slots:
|
||||
|
||||
void setShowHidden(const bool showHidden);
|
||||
void sensorsChanged(std::vector<Sensor> sensors);
|
||||
|
||||
private slots:
|
||||
void onDoubleClick(QTreeWidgetItem *item, int column);
|
||||
};
|
||||
|
|
|
|||
41
src/ui/sensorsettingsdialog.cpp
Normal file
41
src/ui/sensorsettingsdialog.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include "sensorsettingsdialog.h"
|
||||
#include "ui_sensorsettingsdialog.h"
|
||||
|
||||
SensorSettingsDialog::SensorSettingsDialog(const Sensor& sensor, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::SensorSettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->label_typeValue->setText(QString::number(sensor.type));
|
||||
ui->label_idValue->setText(QString::number(sensor.id));
|
||||
ui->lineEdit_Name->setText(sensor.name);
|
||||
ui->checkBox_Hidden->setChecked(sensor.hidden);
|
||||
|
||||
// Populate group dropdown with existing groups
|
||||
std::vector<QString> groups = globalSensors.allGroups();
|
||||
for(const QString& group : groups)
|
||||
ui->comboBox_Group->addItem(group);
|
||||
// Set current group (will be empty string if no group)
|
||||
ui->comboBox_Group->setCurrentText(sensor.groupName);
|
||||
}
|
||||
|
||||
SensorSettingsDialog::~SensorSettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
QString SensorSettingsDialog::getName() const
|
||||
{
|
||||
return ui->lineEdit_Name->text();
|
||||
}
|
||||
|
||||
QString SensorSettingsDialog::getGroupName() const
|
||||
{
|
||||
return ui->comboBox_Group->currentText();
|
||||
}
|
||||
|
||||
bool SensorSettingsDialog::getHidden() const
|
||||
{
|
||||
return ui->checkBox_Hidden->isChecked();
|
||||
}
|
||||
28
src/ui/sensorsettingsdialog.h
Normal file
28
src/ui/sensorsettingsdialog.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef SENSORSETTINGSDIALOG_H
|
||||
#define SENSORSETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "sensors/sensor.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class SensorSettingsDialog;
|
||||
}
|
||||
|
||||
class SensorSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SensorSettingsDialog(const Sensor& sensor, QWidget* parent = nullptr);
|
||||
~SensorSettingsDialog();
|
||||
|
||||
QString getName() const;
|
||||
QString getGroupName() const;
|
||||
bool getHidden() const;
|
||||
|
||||
private:
|
||||
Ui::SensorSettingsDialog* ui;
|
||||
};
|
||||
|
||||
#endif // SENSORSETTINGSDIALOG_H
|
||||
156
src/ui/sensorsettingsdialog.ui
Normal file
156
src/ui/sensorsettingsdialog.ui
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SensorSettingsDialog</class>
|
||||
<widget class="QDialog" name="SensorSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>180</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Sensor Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_Type">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_typeValue">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_Id">
|
||||
<property name="text">
|
||||
<string>Id:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_idValue">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_Name">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_Name">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_Group">
|
||||
<property name="text">
|
||||
<string>Group:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_Group">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_Hidden">
|
||||
<property name="text">
|
||||
<string>Hidden:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_Hidden">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SensorSettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SensorSettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
|
@ -295,6 +295,128 @@ private slots:
|
|||
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()
|
||||
{
|
||||
// Cleanup after all tests
|
||||
|
|
|
|||
|
|
@ -219,6 +219,162 @@ private slots:
|
|||
QVERIFY(audio.type == Sensor::TYPE_AUDIO_OUTPUT);
|
||||
}
|
||||
|
||||
void testSensorStoreUserUpdateUpdatesNameAndHidden()
|
||||
{
|
||||
// Create a SensorStore
|
||||
SensorStore store;
|
||||
store.getSensors()->clear();
|
||||
|
||||
// Add initial sensor
|
||||
Sensor initialSensor(Sensor::TYPE_TEMPERATURE, 1, 20.0, "Initial Name", false);
|
||||
store.sensorGotState(initialSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Verify initial state
|
||||
std::vector<Sensor>* sensors = store.getSensors();
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "Initial Name");
|
||||
QVERIFY(sensors->at(0).hidden == false);
|
||||
|
||||
// Send USER update with new name and hidden state
|
||||
Sensor userUpdate(Sensor::TYPE_TEMPERATURE, 1, 25.0, "New Name", true);
|
||||
store.sensorGotState(userUpdate, SENSOR_UPDATE_USER);
|
||||
|
||||
// Verify name and hidden were updated, but field was NOT updated
|
||||
// (USER updates only update name/hidden/groupName, not field)
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "New Name");
|
||||
QVERIFY(sensors->at(0).hidden == true);
|
||||
QVERIFY(sensors->at(0).field == 20.0); // Field unchanged from initial
|
||||
}
|
||||
|
||||
void testSensorStoreNonUserUpdateIgnoresNameAndHidden()
|
||||
{
|
||||
// Create a SensorStore
|
||||
SensorStore store;
|
||||
store.getSensors()->clear();
|
||||
|
||||
// Add initial sensor
|
||||
Sensor initialSensor(Sensor::TYPE_TEMPERATURE, 1, 20.0, "Initial Name", false);
|
||||
store.sensorGotState(initialSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Verify initial state
|
||||
std::vector<Sensor>* sensors = store.getSensors();
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "Initial Name");
|
||||
QVERIFY(sensors->at(0).hidden == false);
|
||||
|
||||
// Send BACKEND update with new name and hidden state
|
||||
Sensor backendUpdate(Sensor::TYPE_TEMPERATURE, 1, 25.0, "Backend Name", true);
|
||||
store.sensorGotState(backendUpdate, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Verify name and hidden were NOT updated
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "Initial Name");
|
||||
QVERIFY(sensors->at(0).hidden == false);
|
||||
QVERIFY(sensors->at(0).field == 25.0);
|
||||
}
|
||||
|
||||
void testSensorStoreUserUpdateUpdatesKnownSensors()
|
||||
{
|
||||
// Create a SensorStore
|
||||
SensorStore store;
|
||||
store.getSensors()->clear();
|
||||
|
||||
// Add initial sensor
|
||||
Sensor initialSensor(Sensor::TYPE_TEMPERATURE, 1, 20.0, "Initial Name", false);
|
||||
store.sensorGotState(initialSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Send USER update with new name and hidden state
|
||||
Sensor userUpdate(Sensor::TYPE_TEMPERATURE, 1, 25.0, "New Name", true);
|
||||
store.sensorGotState(userUpdate, SENSOR_UPDATE_USER);
|
||||
|
||||
// Store to JSON and reload
|
||||
QJsonObject json;
|
||||
store.store(json);
|
||||
|
||||
SensorStore store2;
|
||||
store2.getSensors()->clear();
|
||||
store2.load(json);
|
||||
|
||||
// Add the sensor again - should use the updated name from knownSensors_
|
||||
Sensor newSensor(Sensor::TYPE_TEMPERATURE, 1, 30.0, "Original Name", false);
|
||||
store2.sensorGotState(newSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Verify the name was taken from knownSensors_
|
||||
std::vector<Sensor>* sensors = store2.getSensors();
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "New Name");
|
||||
QVERIFY(sensors->at(0).hidden == true);
|
||||
}
|
||||
|
||||
void testSensorStoreNewSensorNotInKnownSensors()
|
||||
{
|
||||
// Create a SensorStore
|
||||
SensorStore store;
|
||||
store.getSensors()->clear();
|
||||
|
||||
// Add a new sensor (not in knownSensors_)
|
||||
Sensor newSensor(Sensor::TYPE_TEMPERATURE, 99, 25.0, "New Sensor Name", true);
|
||||
store.sensorGotState(newSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Verify sensor was added with its original name
|
||||
std::vector<Sensor>* sensors = store.getSensors();
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "New Sensor Name");
|
||||
QVERIFY(sensors->at(0).hidden == true);
|
||||
}
|
||||
|
||||
void testSensorStoreNewSensorInKnownSensors()
|
||||
{
|
||||
// Create a SensorStore
|
||||
SensorStore store;
|
||||
store.getSensors()->clear();
|
||||
|
||||
// Load known sensors
|
||||
QJsonObject json;
|
||||
QJsonArray sensorsArray;
|
||||
QJsonObject knownSensor;
|
||||
knownSensor["SensorType"] = Sensor::TYPE_TEMPERATURE;
|
||||
knownSensor["Id"] = 99;
|
||||
knownSensor["Name"] = "Known Sensor Name";
|
||||
knownSensor["Hidden"] = true;
|
||||
sensorsArray.append(knownSensor);
|
||||
json["Sensors"] = sensorsArray;
|
||||
store.load(json);
|
||||
|
||||
// Add a new sensor that matches knownSensors_
|
||||
Sensor newSensor(Sensor::TYPE_TEMPERATURE, 99, 25.0, "Original Name", false);
|
||||
store.sensorGotState(newSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Verify name was overridden from knownSensors_
|
||||
std::vector<Sensor>* sensors = store.getSensors();
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "Known Sensor Name");
|
||||
QVERIFY(sensors->at(0).hidden == true);
|
||||
}
|
||||
|
||||
void testSensorStoreRemoteUpdateIgnored()
|
||||
{
|
||||
// Create a SensorStore
|
||||
SensorStore store;
|
||||
store.getSensors()->clear();
|
||||
|
||||
// Add initial sensor
|
||||
Sensor initialSensor(Sensor::TYPE_TEMPERATURE, 1, 20.0, "Initial Name", false);
|
||||
store.sensorGotState(initialSensor, SENSOR_UPDATE_BACKEND);
|
||||
|
||||
// Send REMOTE update with new name and hidden state
|
||||
Sensor remoteUpdate(Sensor::TYPE_TEMPERATURE, 1, 25.0, "Remote Name", true);
|
||||
store.sensorGotState(remoteUpdate, SENSOR_UPDATE_REMOTE);
|
||||
|
||||
// Verify name and hidden were NOT updated
|
||||
std::vector<Sensor>* sensors = store.getSensors();
|
||||
QVERIFY(sensors->size() == 1);
|
||||
QVERIFY(sensors->at(0).name == "Initial Name");
|
||||
QVERIFY(sensors->at(0).hidden == false);
|
||||
QVERIFY(sensors->at(0).field == 25.0);
|
||||
}
|
||||
|
||||
void cleanupTestCase()
|
||||
{
|
||||
// Cleanup after all tests
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "service/server.h"
|
||||
#include "service/service.h"
|
||||
#include "items/item.h"
|
||||
#include "sensors/sensor.h"
|
||||
|
||||
class TestTcp : public QObject
|
||||
{
|
||||
|
|
@ -332,6 +333,94 @@ private slots:
|
|||
QVERIFY(size == jsonData.size());
|
||||
}
|
||||
|
||||
void testAddSensorMessageFormat()
|
||||
{
|
||||
// Test creating an AddSensor message format
|
||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
|
||||
|
||||
QJsonObject sensorJson;
|
||||
sensor.store(sensorJson);
|
||||
|
||||
QJsonObject payload;
|
||||
payload["Topic"] = "home/temperature";
|
||||
payload["Name"] = "Living Room";
|
||||
|
||||
// Manually create the message (since createMessage is protected)
|
||||
QJsonObject message;
|
||||
message["MessageType"] = "AddSensor";
|
||||
message["Data"] = QJsonArray();
|
||||
message["Sensor"] = sensorJson;
|
||||
message["Backend"] = static_cast<int>(Sensor::BACKEND_MQTT);
|
||||
message["Payload"] = payload;
|
||||
|
||||
QVERIFY(message["MessageType"].toString() == "AddSensor");
|
||||
QVERIFY(message["Sensor"].toObject()["Name"].toString() == "temp_sensor");
|
||||
QVERIFY(message["Backend"].toInt() == Sensor::BACKEND_MQTT);
|
||||
QVERIFY(message["Payload"].toObject()["Topic"].toString() == "home/temperature");
|
||||
}
|
||||
|
||||
void testSensorBackendTypeEnum()
|
||||
{
|
||||
// Test that the backend type enum values are correct
|
||||
QVERIFY(Sensor::BACKEND_MICROCONTROLLER == 0);
|
||||
QVERIFY(Sensor::BACKEND_MQTT == 1);
|
||||
QVERIFY(Sensor::BACKEND_SUN == 2);
|
||||
}
|
||||
|
||||
void testServiceAddSensor()
|
||||
{
|
||||
// Test that Service::addSensor can be called without crashing
|
||||
// and creates proper JSON message format
|
||||
TcpServer server;
|
||||
bool result = server.launch(QHostAddress::LocalHost, 0);
|
||||
QVERIFY(result);
|
||||
|
||||
// Create a sensor and payload
|
||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
|
||||
QJsonObject payload;
|
||||
payload["Topic"] = "home/temperature";
|
||||
payload["Name"] = "Living Room";
|
||||
|
||||
// Call addSensor - this should not crash
|
||||
// The actual sending to clients depends on network timing
|
||||
server.addSensor(sensor, Sensor::BACKEND_MQTT, payload);
|
||||
|
||||
// Test passed if no crash occurred
|
||||
QVERIFY(true);
|
||||
}
|
||||
|
||||
void testServiceProcessAddSensorMessage()
|
||||
{
|
||||
// Test processing an incoming AddSensor message
|
||||
// We test the JSON parsing directly without network
|
||||
QJsonObject sensorJson;
|
||||
sensorJson["SensorType"] = static_cast<int>(Sensor::TYPE_TEMPERATURE);
|
||||
sensorJson["Id"] = 1;
|
||||
sensorJson["Field"] = 25.0;
|
||||
sensorJson["Name"] = "temp_sensor";
|
||||
|
||||
QJsonObject payload;
|
||||
payload["Topic"] = "home/temperature";
|
||||
payload["Name"] = "Living Room";
|
||||
|
||||
QJsonObject message;
|
||||
message["MessageType"] = "AddSensor";
|
||||
message["Data"] = QJsonArray();
|
||||
message["Sensor"] = sensorJson;
|
||||
message["Backend"] = static_cast<int>(Sensor::BACKEND_MQTT);
|
||||
message["Payload"] = payload;
|
||||
|
||||
// Test that the message can be parsed correctly
|
||||
QJsonDocument doc(message);
|
||||
QVERIFY(doc.isObject());
|
||||
|
||||
QJsonObject parsed = doc.object();
|
||||
QVERIFY(parsed["MessageType"].toString() == "AddSensor");
|
||||
QVERIFY(parsed["Backend"].toInt() == Sensor::BACKEND_MQTT);
|
||||
QVERIFY(parsed["Payload"].toObject()["Topic"].toString() == "home/temperature");
|
||||
QVERIFY(parsed["Sensor"].toObject()["Name"].toString() == "temp_sensor");
|
||||
}
|
||||
|
||||
void cleanupTestCase()
|
||||
{
|
||||
// Cleanup after all tests
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue