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();
|
||||
}
|
||||
|
||||
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,6 +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);
|
||||
connect(tcpServer, &TcpServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
|
||||
connect(webServer, &WebSocketServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
|
||||
|
||||
globalItems.registerItemSource(&fixedItems);
|
||||
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:
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -194,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,10 +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:
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ 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;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,17 @@ 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()));
|
||||
|
|
@ -88,4 +99,12 @@ void Service::processIncomeingJson(const QByteArray& jsonbytes)
|
|||
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:
|
||||
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, 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);
|
||||
|
|
|
|||
|
|
@ -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,6 +48,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);
|
||||
|
||||
|
|
@ -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)
|
||||
{
|
||||
if(string.size() > 28)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public:
|
|||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
MainObject* const mainObject;
|
||||
|
||||
QColorDialog colorChooser;
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ private slots:
|
|||
//RGB
|
||||
void showPowerItemDialog();
|
||||
void showItemCreationDialog();
|
||||
void showSensorCreationDialog();
|
||||
|
||||
public slots:
|
||||
|
||||
|
|
|
|||
|
|
@ -230,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">
|
||||
|
|
|
|||
|
|
@ -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