Add the ability to add mqtt sensors at runtime
This commit is contained in:
parent
36171a221a
commit
25b9f87285
11 changed files with 208 additions and 0 deletions
|
|
@ -22,6 +22,11 @@ void MainObject::refresh()
|
||||||
globalItems.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)
|
QJsonObject MainObject::getJsonObjectFromDisk(const QString& filename, bool* error)
|
||||||
{
|
{
|
||||||
QFile file;
|
QFile file;
|
||||||
|
|
@ -89,6 +94,8 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
||||||
connect(&sunSensorSource, &SunSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
|
connect(&sunSensorSource, &SunSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
|
||||||
connect(µ, &Microcontroller::gotSensorState, &globalSensors, &SensorStore::sensorGotState);
|
connect(µ, &Microcontroller::gotSensorState, &globalSensors, &SensorStore::sensorGotState);
|
||||||
connect(&mqttSensorSource, &MqttSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
|
connect(&mqttSensorSource, &MqttSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
|
||||||
|
connect(tcpServer, &TcpServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
|
||||||
|
connect(webServer, &WebSocketServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
|
||||||
|
|
||||||
globalItems.registerItemSource(&fixedItems);
|
globalItems.registerItemSource(&fixedItems);
|
||||||
globalItems.registerItemSource(tcpServer);
|
globalItems.registerItemSource(tcpServer);
|
||||||
|
|
@ -174,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:
|
public slots:
|
||||||
void refresh();
|
void refresh();
|
||||||
|
virtual void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {});
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrimaryMainObject : public MainObject
|
class PrimaryMainObject : public MainObject
|
||||||
|
|
@ -74,6 +75,7 @@ public:
|
||||||
public:
|
public:
|
||||||
explicit SecondaryMainObject(QString host, int port, QObject *parent = nullptr);
|
explicit SecondaryMainObject(QString host, int port, QObject *parent = nullptr);
|
||||||
~SecondaryMainObject();
|
~SecondaryMainObject();
|
||||||
|
void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {}) override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const QString& topic)
|
||||||
{
|
{
|
||||||
for(SensorSubscription& sensor : sensors)
|
for(SensorSubscription& sensor : sensors)
|
||||||
|
|
@ -194,3 +213,16 @@ MqttSensorSource::~MqttSensorSource()
|
||||||
client->unsubscribe(client->getBaseTopic() + "/" + sub.topic);
|
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,10 +31,14 @@ private slots:
|
||||||
void onClientStateChanged(QMqttClient::ClientState state);
|
void onClientStateChanged(QMqttClient::ClientState state);
|
||||||
void onMessageReceived(const QMqttMessage& message);
|
void onMessageReceived(const QMqttMessage& message);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onSensorAdded(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MqttSensorSource(QObject *parent = nullptr);
|
explicit MqttSensorSource(QObject *parent = nullptr);
|
||||||
~MqttSensorSource();
|
~MqttSensorSource();
|
||||||
void start(std::shared_ptr<MqttClient> client, const QJsonObject& settings);
|
void start(std::shared_ptr<MqttClient> client, const QJsonObject& settings);
|
||||||
|
void addSensor(const QString& topic, const QString& name);
|
||||||
void store(QJsonObject& json);
|
void store(QJsonObject& json);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,12 @@ public:
|
||||||
TYPE_DUMMY,
|
TYPE_DUMMY,
|
||||||
} sensor_type_t;
|
} sensor_type_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BACKEND_MICROCONTROLLER = 0,
|
||||||
|
BACKEND_MQTT,
|
||||||
|
BACKEND_SUN,
|
||||||
|
} sensor_backend_type_t;
|
||||||
|
|
||||||
sensor_type_t type;
|
sensor_type_t type;
|
||||||
uint64_t id;
|
uint64_t id;
|
||||||
float field;
|
float field;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,17 @@ void Service::sensorEvent(Sensor sensor, sensor_update_type_t type)
|
||||||
|
|
||||||
void Service::itemUpdated(ItemUpdateRequest update) {}
|
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()
|
void Service::refresh()
|
||||||
{
|
{
|
||||||
sendJson(createMessage("GetSensors", QJsonArray()));
|
sendJson(createMessage("GetSensors", QJsonArray()));
|
||||||
|
|
@ -88,4 +99,12 @@ void Service::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
gotSensor(sensor, SENSOR_UPDATE_REMOTE);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ protected:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void gotSensor(Sensor sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);
|
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:
|
public slots:
|
||||||
void sensorEvent(Sensor sensor, sensor_update_type_t type);
|
void sensorEvent(Sensor sensor, sensor_update_type_t type);
|
||||||
virtual void itemUpdated(ItemUpdateRequest update);
|
virtual void itemUpdated(ItemUpdateRequest update);
|
||||||
virtual void refresh() override;
|
virtual void refresh() override;
|
||||||
|
virtual void addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload = {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Service(QObject* parent = nullptr);
|
Service(QObject* parent = nullptr);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QInputDialog>
|
||||||
|
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "itemscrollbox.h"
|
#include "itemscrollbox.h"
|
||||||
|
|
@ -9,10 +10,12 @@
|
||||||
#include "mainobject.h"
|
#include "mainobject.h"
|
||||||
#include "programmode.h"
|
#include "programmode.h"
|
||||||
#include "items/poweritem.h"
|
#include "items/poweritem.h"
|
||||||
|
#include "sensors/mqttsensorsource.h"
|
||||||
|
|
||||||
MainWindow::MainWindow(MainObject * const mainObject, QWidget *parent) :
|
MainWindow::MainWindow(MainObject * const mainObject, QWidget *parent) :
|
||||||
QMainWindow(parent),
|
QMainWindow(parent),
|
||||||
ui(new Ui::MainWindow),
|
ui(new Ui::MainWindow),
|
||||||
|
mainObject(mainObject),
|
||||||
colorChooser(this)
|
colorChooser(this)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
@ -45,6 +48,7 @@ MainWindow::MainWindow(MainObject * const mainObject, QWidget *parent) :
|
||||||
ui->button_color->hide();
|
ui->button_color->hide();
|
||||||
|
|
||||||
connect(ui->pushButton_addItem, &QPushButton::clicked, this, &MainWindow::showItemCreationDialog);
|
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->relayList, &ItemScrollBox::deleteRequest, &globalItems, &ItemStore::removeItem);
|
||||||
connect(ui->checkBox_sensorsShowHidden, &QCheckBox::clicked, ui->sensorListView, &SensorListWidget::setShowHidden);
|
connect(ui->checkBox_sensorsShowHidden, &QCheckBox::clicked, ui->sensorListView, &SensorListWidget::setShowHidden);
|
||||||
|
|
||||||
|
|
@ -90,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)
|
void MainWindow::changeHeaderLableText(QString string)
|
||||||
{
|
{
|
||||||
if(string.size() > 28)
|
if(string.size() > 28)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
MainObject* const mainObject;
|
||||||
|
|
||||||
QColorDialog colorChooser;
|
QColorDialog colorChooser;
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ private slots:
|
||||||
//RGB
|
//RGB
|
||||||
void showPowerItemDialog();
|
void showPowerItemDialog();
|
||||||
void showItemCreationDialog();
|
void showItemCreationDialog();
|
||||||
|
void showSensorCreationDialog();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_addSensor">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add Sensor</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="button_quit">
|
<widget class="QPushButton" name="button_quit">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "service/server.h"
|
#include "service/server.h"
|
||||||
#include "service/service.h"
|
#include "service/service.h"
|
||||||
#include "items/item.h"
|
#include "items/item.h"
|
||||||
|
#include "sensors/sensor.h"
|
||||||
|
|
||||||
class TestTcp : public QObject
|
class TestTcp : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -332,6 +333,94 @@ private slots:
|
||||||
QVERIFY(size == jsonData.size());
|
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()
|
void cleanupTestCase()
|
||||||
{
|
{
|
||||||
// Cleanup after all tests
|
// Cleanup after all tests
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue