Compare commits

..

No commits in common. "51193a5d0b5cea3ecbc487e4cbf281f0754be1cb" and "7c96b87a11193b7737f8e2ebb7acba8da5d326be" have entirely different histories.

42 changed files with 133 additions and 1612 deletions

View file

@ -148,8 +148,6 @@ 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
@ -183,7 +181,6 @@ 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

View file

@ -31,7 +31,7 @@ void PolynomalActor::getCoeffiancts( double& pow3, double& pow2, double& pow1, d
pow0=pow0_;
}
void PolynomalActor::sensorEvent(Sensor sensor, sensor_update_type_t type)
void PolynomalActor::sensorEvent(Sensor sensor)
{
if(active && sensor == sensor_)
{

View file

@ -18,7 +18,7 @@ private:
public slots:
void sensorEvent(Sensor sensor, sensor_update_type_t type);
void sensorEvent(Sensor sensor);
public:

View file

@ -21,7 +21,7 @@ void Regulator::setSensor(const Sensor sensor)
sensor_ = sensor;
}
void Regulator::sensorEvent(Sensor sensor, sensor_update_type_t type)
void Regulator::sensorEvent(Sensor sensor)
{
if(active && sensor == sensor_)
{

View file

@ -26,7 +26,7 @@ private slots:
public slots:
void sensorEvent(Sensor sensor, sensor_update_type_t type);
void sensorEvent(Sensor sensor);
void setSensor(const Sensor sensor);
void setPoint(float setPoint );

View file

@ -17,7 +17,7 @@ void SensorActor::setSensor(const Sensor sensor)
sensor_ = sensor;
}
void SensorActor::sensorEvent(Sensor sensor, sensor_update_type_t type)
void SensorActor::sensorEvent(Sensor sensor)
{
if(sensor == sensor_)
{

View file

@ -18,7 +18,7 @@ private:
public slots:
void sensorEvent(Sensor sensor, sensor_update_type_t type);
void sensorEvent(Sensor sensor);
void setSloap(uint8_t sloap);
void setSensor(const Sensor sensor);

View file

@ -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 && !groupName_.isEmpty() && groupName_ != "All")
if(changes.groupName)
json["GroupName"] = groupName_;
if(changes.valueNames && !valueNames_.empty())
if(changes.valueNames)
{
QJsonArray valueNamesArray;
for(const QString& name : valueNames_)
@ -218,37 +218,31 @@ 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)
{
QJsonArray actorsArray;
for(size_t i = 0; i < actors_.size(); ++i)
if(!actors_[i]->isExausted())
{
if(!actors_[i]->isExausted())
{
QJsonObject actorObject;
actors_[i]->store(actorObject);
actorsArray.append(actorObject);
}
QJsonObject actorObject;
actors_[i]->store(actorObject);
actorsArray.append(actorObject);
}
json["Actors"] = actorsArray;
}
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)
{
const QJsonArray actorsArray(json["Actors"].toArray(QJsonArray()));
for(int i = 0; i < actorsArray.size(); ++i)
if(actorsArray[i].isObject())
{
if(actorsArray[i].isObject())
{
std::shared_ptr<Actor> actor = Actor::loadActor(actorsArray[i].toObject());
if(actor != nullptr)
addActor(actor);
}
std::shared_ptr<Actor> actor = Actor::loadActor(actorsArray[i].toObject());
if(actor != nullptr)
addActor(actor);
}
}
}

View file

@ -26,8 +26,6 @@ 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);
}

View file

@ -101,7 +101,6 @@ 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();
@ -294,30 +293,6 @@ 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_;

View file

@ -9,9 +9,6 @@ class QString;
class MqttItem : public Item
{
Q_OBJECT
Q_SIGNALS:
void exposeLoaded();
public:
inline static std::weak_ptr<MqttClient> client;
@ -54,9 +51,6 @@ 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;

View file

@ -6,7 +6,7 @@
PowerItem::PowerItem(uint32_t itemIdIn, QString name, uint8_t value, QObject* parent):
Item(itemIdIn, name, value, parent)
{
globalSensors.sensorGotState(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 0, "Shutdown Imminent", true), SENSOR_UPDATE_BACKEND);
stateChanged(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 0, "Shutdown Imminent", true));
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);
globalSensors.sensorGotState(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 1, "Shutdown Imminent", true), SENSOR_UPDATE_BACKEND);
stateChanged(Sensor(Sensor::TYPE_SHUTDOWN_IMMINENT, 0, 1, "Shutdown Imminent", true));
}
}

View file

@ -12,7 +12,8 @@ class PowerItem: public Item
private:
signals:
void stateChanged(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
void stateChanged(Sensor sensor);
private slots:
void timeout();

View file

@ -14,14 +14,17 @@
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");
QStringList args;
for(int i = 0; i < argc; ++i)
args<<argv[i];
QDir::setCurrent(a.applicationDirPath());
//parse comand line
QCommandLineParser parser;
@ -39,8 +42,9 @@ 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);
parser.process(args);
int retVal;
programMode = PROGRAM_MODE_UI_ONLY;
if(parser.isSet(masterOption))
@ -49,17 +53,6 @@ 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);
@ -113,13 +106,11 @@ 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);
@ -127,15 +118,7 @@ int main(int argc, char *argv[])
QObject::connect(w, &MainWindow::createdItem, &globalItems, &ItemStore::addItem);
w->show();
}
retVal = a->exec();
if(programMode != PROGRAM_MODE_HEADLESS_PRIMARY)
{
QJsonObject uiSettingsJson;
w->store(uiSettingsJson);
MainObject::storeJsonObjectToDisk(uiSettingsPath, uiSettingsJson);
}
retVal = a.exec();
delete w;
delete microDevice;
@ -144,23 +127,13 @@ 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();
QJsonObject uiSettingsJson;
w.store(uiSettingsJson);
MainObject::storeJsonObjectToDisk(uiSettingsPath, uiSettingsJson);
retVal = a.exec();
}
delete a;
return retVal;
}

View file

@ -22,11 +22,6 @@ 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;
@ -94,8 +89,8 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
connect(&sunSensorSource, &SunSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
connect(&micro, &Microcontroller::gotSensorState, &globalSensors, &SensorStore::sensorGotState);
connect(&mqttSensorSource, &MqttSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
connect(tcpServer, &TcpServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
connect(webServer, &WebSocketServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
sunSensorSource.run();
globalItems.registerItemSource(&fixedItems);
globalItems.registerItemSource(tcpServer);
@ -107,8 +102,6 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
loadFromDisk(settingsPath);
sunSensorSource.run();
QJsonObject mqttJson = settings["Mqtt"].toObject();
mqttClient->start(mqttJson);
mqttSensorSource.start(mqttClient, mqttJson);
@ -127,7 +120,6 @@ PrimaryMainObject::~PrimaryMainObject()
void PrimaryMainObject::store(QJsonObject &json)
{
globalItems.store(json);
globalSensors.store(json);
QJsonObject mqttJson = json["Mqtt"].toObject();
mqttClient->store(mqttJson);
mqttSensorSource.store(mqttJson);
@ -138,7 +130,6 @@ void PrimaryMainObject::load(const QJsonObject& json)
{
settings = json;
itemLoader.updateJson(json);
globalSensors.load(json);
globalItems.clear();
globalItems.refresh();
}
@ -181,8 +172,3 @@ SecondaryMainObject::~SecondaryMainObject()
{
}
void SecondaryMainObject::addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
{
tcpClient->addSensor(sensor, backend, payload);
}

View file

@ -33,7 +33,6 @@ public:
public slots:
void refresh();
virtual void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {});
};
class PrimaryMainObject : public MainObject
@ -75,7 +74,6 @@ 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;
};

View file

@ -190,7 +190,7 @@ void Microcontroller::processSensorState(const QString& buffer)
{
Sensor sensor = Sensor::sensorFromString(buffer);
if(sensor.type != Sensor::TYPE_DUMMY)
gotSensorState(sensor, SENSOR_UPDATE_BACKEND);
gotSensorState(sensor);
}

View file

@ -78,7 +78,7 @@ private slots:
signals:
void textRecived(const QString string);
void gotSensorState(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
void gotSensorState(Sensor sensor);
};
#endif // MICROCONTROLLER_H

View file

@ -31,25 +31,6 @@ 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)
@ -58,7 +39,6 @@ MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const Q
return sensor;
}
assert(false);
return sensors.front();
}
void MqttSensorSource::onClientStateChanged(QMqttClient::ClientState state)
@ -101,7 +81,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " Temperature";
sensor.type = Sensor::TYPE_TEMPERATURE;
sensor.field = obj["temperature"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("local_temperature"))
@ -109,7 +89,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, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("humidity"))
@ -117,7 +97,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " Humidity";
sensor.type = Sensor::TYPE_HUMIDITY;
sensor.field = obj["humidity"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("illuminance"))
@ -125,7 +105,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " Illuminance";
sensor.type = Sensor::TYPE_BRIGHTNESS;
sensor.field = obj["illuminance"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("presence"))
@ -133,7 +113,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, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("co2"))
@ -141,7 +121,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " co2";
sensor.type = Sensor::TYPE_CO2;
sensor.field = obj["co2"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("formaldehyd"))
@ -149,7 +129,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " Formaldehyd";
sensor.type = Sensor::TYPE_FORMALDEHYD;
sensor.field = obj["formaldehyd"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("pm25"))
@ -157,7 +137,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " pm25";
sensor.type = Sensor::TYPE_PM25;
sensor.field = obj["pm25"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("voc"))
@ -165,7 +145,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, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("power"))
@ -173,7 +153,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " Power";
sensor.type = Sensor::TYPE_POWER;
sensor.field = obj["power"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("energy"))
@ -181,7 +161,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, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
if(obj.contains("voltage"))
@ -189,7 +169,7 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
sensor.name = baseName + " Voltage";
sensor.type = Sensor::TYPE_VOLTAGE;
sensor.field = obj["voltage"].toDouble(0);
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
stateChanged(sensor);
}
}
}
@ -213,16 +193,3 @@ 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);
}

View file

@ -31,18 +31,14 @@ 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, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
void stateChanged(Sensor sensor);
};
#endif // MQTTSENSORSOURCE_H

View file

@ -1,7 +1,6 @@
#include "sensor.h"
#include <QDebug>
#include <QJsonArray>
SensorStore globalSensors;
@ -11,115 +10,28 @@ SensorStore::SensorStore(QObject *parent): QObject(parent)
sensors_.push_back(Sensor(Sensor::TYPE_DOOR,0,0,"Bedroom door"));
}
void SensorStore::store(QJsonObject& json)
void SensorStore::sensorGotState(const Sensor& sensor)
{
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;
bool exsisting = false;
for(unsigned i = 0; i < sensors_.size(); ++i)
{
if(sensor.type == sensors_[i].type && sensor.id == sensors_[i].id)
{
sensors_[i].updateSeen();
bool needsUpdate = false;
if(type == SENSOR_UPDATE_USER)
if(sensors_[i].field != sensor.field)
{
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;
}
}
needsUpdate = true;
}
}
else if(sensors_[i].field != sensor.field)
{
needsUpdate = true;
sensors_[i].field = sensor.field;
}
if(needsUpdate)
{
sensorChangedState(sensors_[i], type);
sensorChangedState(sensor);
stateChenged(sensors_);
}
inSensors = true;
exsisting = true;
break;
}
}
if(!inSensors)
if(!exsisting)
{
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);
sensors_.push_back(sensor);
sensorChangedState(sensor);
stateChenged(sensors_);
}

View file

@ -33,28 +33,21 @@ 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, QString groupNameIn = ""): type(typeIn),
id(idIn), field(fieldIn), name(nameIn), groupName(groupNameIn), hidden(hiddenIn)
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)
{
lastSeen = QDateTime::currentDateTime();
if(nameIn == "")
generateName();
}
Sensor(QString nameIn = "dummy"): type(TYPE_DUMMY), id(0), field(0), name(nameIn), groupName(""), hidden(false)
Sensor(QString nameIn = "dummy"): type(TYPE_DUMMY), id(0), field(0), name(nameIn), hidden(false)
{
lastSeen = QDateTime::currentDateTime();
}
@ -66,7 +59,6 @@ public:
lastSeen = QDateTime::fromString(json["LastSeen"].toString(""));
hidden = json["Hidden"].toBool(false);
name = json["Name"].toString();
groupName = json["GroupName"].toString();
if(name == "")
generateName();
}
@ -106,14 +98,13 @@ public:
QString::number((type == Sensor::TYPE_HUMIDITY || type == Sensor::TYPE_TEMPERATURE) ? field*10 : field) +
" TIME: " + QString::number(lastSeen.toSecsSinceEpoch());
}
inline void store(QJsonObject& json) const
inline void store(QJsonObject& json)
{
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();
@ -136,7 +127,7 @@ public:
name = "Shutdown Imminent";
else name = "Sensor Type " + QString::number(type) + " Id " + QString::number(id);
}
QString getUnit() const
QString getUnit()
{
switch(type)
{
@ -169,19 +160,11 @@ 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:
@ -193,18 +176,15 @@ public:
return &sensors_;
}
void store(QJsonObject& json);
void load(const QJsonObject& json);
std::vector<QString> allGroups() const;
public slots:
void sensorGotState(const Sensor& sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
void sensorGotState(const Sensor& sensor);
signals:
void stateChenged(std::vector<Sensor> sensors);
void sensorChangedState(Sensor sensor, sensor_update_type_t type);
void sensorChangedState(Sensor sensor);
void sensorDeleted(Sensor sensor);
};

View file

@ -21,5 +21,5 @@ void SunSensorSource::abort()
void SunSensorSource::doTick()
{
stateChanged(Sensor(Sensor::TYPE_SUN_ALTITUDE, 0, static_cast<float>(sun_.altitude())), SENSOR_UPDATE_BACKEND);
stateChanged(Sensor(Sensor::TYPE_SUN_ALTITUDE, 0, static_cast<float>(sun_.altitude())));
}

View file

@ -22,7 +22,7 @@ public slots:
void abort();
signals:
void stateChanged(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
void stateChanged(Sensor sensor);
private slots:
void doTick();

View file

@ -19,7 +19,7 @@ QJsonObject Service::createMessage(const QString& type, const QJsonArray& data)
return json;
}
void Service::sensorEvent(Sensor sensor, sensor_update_type_t type)
void Service::sensorEvent(Sensor sensor)
{
QJsonArray sensors;
QJsonObject sensorjson;
@ -31,17 +31,6 @@ void Service::sensorEvent(Sensor sensor, sensor_update_type_t type)
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()));
@ -96,15 +85,7 @@ void Service::processIncomeingJson(const QByteArray& jsonbytes)
{
QJsonObject jsonobject = sensorjson.toObject();
Sensor sensor(jsonobject);
gotSensor(sensor, SENSOR_UPDATE_REMOTE);
gotSensor(sensor);
}
}
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);
}
}

View file

@ -20,14 +20,12 @@ protected:
} client_state_t;
signals:
void gotSensor(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
void sensorAdded(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload);
void gotSensor(Sensor sensor);
public slots:
void sensorEvent(Sensor sensor, sensor_update_type_t type);
void sensorEvent(Sensor sensor);
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);

View file

@ -163,12 +163,6 @@ 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();
}
}
}
@ -181,7 +175,7 @@ void ItemScrollBox::cleanupEmptyTabs()
continue;
qDebug()<<__func__<<it.value().content->layout()->count();
if(it.value().content->layout()->count() <= 1)
{
int index = ui->tabWidget->indexOf(tabs_[groupName].scroller);
@ -195,21 +189,3 @@ 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();
}

View file

@ -6,7 +6,6 @@
#include <memory>
#include <QScrollArea>
#include <QSpacerItem>
#include <QJsonObject>
#include "itemwidget.h"
#include "../items/item.h"
#include "../items/itemstore.h"
@ -31,7 +30,6 @@ private:
QMap<QString, Tab> tabs_;
QMap<QString, std::vector<ItemWidget*>> widgets_;
Ui::RelayScrollBox *ui;
QString pendingSelectedGroup_;
signals:
void deleteRequest(const ItemData& item);
@ -43,9 +41,6 @@ public:
void setItemStore(ItemStore* itemStore);
void store(QJsonObject& json) const;
void load(const QJsonObject& json);
public slots:
void addItem(std::weak_ptr<Item> item);

View file

@ -1,7 +1,6 @@
#include "mqttitemsettingswidget.h"
#include "ui_mqttitemsettingswidget.h"
#include <QInputDialog>
#include <QDebug>
MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent) :
@ -13,90 +12,20 @@ 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);
@ -105,8 +34,6 @@ void MqttItemSettingsWidget::setTopic(const QString& topic)
void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
{
if(suppressUpdates_)
return;
if(auto workingItem = item_.lock())
{
workingItem->setValueKey(valueKey);
@ -115,8 +42,6 @@ void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
{
if(suppressUpdates_)
return;
if(auto workingItem = item_.lock())
{
workingItem->setValueOn(valueOn);
@ -125,147 +50,12 @@ 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;

View file

@ -14,21 +14,12 @@ 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);
@ -36,9 +27,6 @@ public:
private:
Ui::MqttItemSettingsWidget *ui;
void updateVisibility();
void updateValueNamesFromItem();
void syncValueNamesToItem();
};
#endif // MQTTITEMSETTINGSWIDGET_H

View file

@ -6,15 +6,14 @@
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>400</height>
<width>400</width>
<height>216</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">
@ -30,13 +29,12 @@
<item>
<widget class="QLineEdit" name="lineEdit_topic">
<property name="placeholderText">
<string>e.g., 0xa4c138ef510950e3</string>
<string>e.g., kitchen/light</string>
</property>
</widget>
</item>
</layout>
</item>
<!-- Value Key -->
<item>
<layout class="QHBoxLayout" name="horizontalLayout_valueKey">
<property name="topMargin">
@ -55,66 +53,12 @@
<string>state</string>
</property>
<property name="placeholderText">
<string>e.g., state, system_mode, brightness</string>
<string>e.g., state, 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">
@ -134,6 +78,13 @@
</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">
@ -150,132 +101,8 @@
</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/>
<connections/>
</ui>
</ui>

View file

@ -1,7 +1,6 @@
#include "mainwindow.h"
#include <QMessageBox>
#include <QInputDialog>
#include "ui_mainwindow.h"
#include "itemscrollbox.h"
@ -10,12 +9,10 @@
#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);
@ -48,9 +45,7 @@ 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);
}
@ -94,35 +89,6 @@ 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)
@ -132,36 +98,3 @@ 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);
}

View file

@ -6,7 +6,6 @@
#include <QListWidgetItem>
#include <QTime>
#include <memory>
#include <QJsonObject>
#include<items/item.h>
@ -25,12 +24,8 @@ 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;
@ -45,7 +40,6 @@ private slots:
//RGB
void showPowerItemDialog();
void showItemCreationDialog();
void showSensorCreationDialog();
public slots:

View file

@ -106,13 +106,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_sensorsShowHidden">
<property name="text">
<string>Show Hidden</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -230,13 +223,6 @@
</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">

View file

@ -3,200 +3,83 @@
#include <QDebug>
#include <QHeaderView>
#include <QScroller>
#include <QMap>
#include <QJsonObject>
#include <QJsonArray>
#include "sensorsettingsdialog.h"
SensorListWidget::SensorListWidget(const bool showHidden, QWidget *parent): QTreeWidget(parent),
SensorListWidget::SensorListWidget(const bool showHidden, QWidget *parent): QTableWidget(parent),
showHidden_(showHidden)
{
setColumnCount(3);
setHeaderLabels({"Sensor", "Value", "Time"});
setSelectionBehavior(QAbstractItemView::SelectRows);
header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture);
setAutoScroll(true);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
sensorsChanged(std::vector<Sensor>());
connect(this, &QTreeWidget::itemDoubleClicked, this, &SensorListWidget::onDoubleClick);
verticalHeader()->hide();
}
SensorListWidget::SensorListWidget(SensorStore& sensorStore, const bool showHidden,
QWidget* parent): QTreeWidget (parent), showHidden_(showHidden)
QWidget* parent): QTableWidget (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)
{
QMap<QString, bool> expandedStates;
QList<int> columnWidths;
for(int i = 0; i < columnCount(); ++i)
columnWidths.append(columnWidth(i));
for(int i = 0; i < topLevelItemCount(); ++i)
{
QTreeWidgetItem* item = topLevelItem(i);
if(item->type() != 1001)
{
expandedStates[item->text(0)] = item->isExpanded();
}
}
clear();
QMap<QString, QTreeWidgetItem*> groupItems;
QStringList headerLabels = {"Sensor", "Value", "Time"};
setHeaderLabels(headerLabels);
QList<SensorListItem*> ungroupedItems;
for(const Sensor& sensor : sensors)
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_ && sensor.hidden)
continue;
if(showHidden_ || !sensors[i].hidden)
{
QString itemString;
itemString.append(QString::number(sensors[i].field));
itemString.append(' ');
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(sensor.getUnit());
}
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())
if(sensors[i].type == Sensor::TYPE_DOOR)
{
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;
if(static_cast<bool>(sensors[i].field))
itemString.append("\"Open\"");
else itemString.append("\"Closed\"");
}
else
else if(sensors[i].type == Sensor::TYPE_AUDIO_OUTPUT)
{
groupItem = it.value();
if(static_cast<bool>(sensors[i].field))
itemString.append("\"Playing\"");
else itemString.append("\"Silent\"");
}
else if(!sensors[i].getUnit().isEmpty())
{
itemString.append(" ");
itemString.append(sensors[i].getUnit());
}
groupItem->addChild(sensorItem);
}
}
for(SensorListItem* item : ungroupedItems)
{
addTopLevelItem(item);
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;
}
}
sortItems(0, Qt::AscendingOrder);
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));
resizeColumnsToContents();
}
const Sensor& SensorListWidget::getSensorForIndex(const QModelIndex &index)
{
QTreeWidgetItem* item = itemFromIndex(index);
if(item && item->type() == 1001)
return static_cast<SensorListItem*>(item)->getSensor();
static Sensor dummy;
return dummy;
return static_cast<SensorListItem*>(item(index.row(), 0))->getSensor();
}
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();
}
showHidden_=showHidden;
}
const Sensor& SensorListItem::getSensor()
@ -205,8 +88,7 @@ const Sensor& SensorListItem::getSensor()
}
SensorListItem::SensorListItem(const QString& text, const Sensor& sensor):
QTreeWidgetItem(1001), sensor(sensor)
QTableWidgetItem(text, 1001), sensor(sensor)
{
setText(0, text);
}

View file

@ -1,9 +1,9 @@
#pragma once
#include <QTreeWidget>
#include <QTableWidget>
#include <vector>
#include "sensors/sensor.h"
class SensorListItem : public QTreeWidgetItem
class SensorListItem : public QTableWidgetItem
{
Sensor sensor;
@ -12,30 +12,22 @@ public:
SensorListItem(const QString& text, const Sensor& sensor);
};
class SensorListWidget : public QTreeWidget
class SensorListWidget : public QTableWidget
{
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);
};

View file

@ -1,41 +0,0 @@
#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();
}

View file

@ -1,28 +0,0 @@
#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

View file

@ -1,156 +0,0 @@
<?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>

View file

@ -295,128 +295,6 @@ 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

View file

@ -219,162 +219,6 @@ 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

View file

@ -10,7 +10,6 @@
#include "service/server.h"
#include "service/service.h"
#include "items/item.h"
#include "sensors/sensor.h"
class TestTcp : public QObject
{
@ -333,94 +332,6 @@ 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