Add support for mqtt sensors
This commit is contained in:
parent
d6c8d799e3
commit
e3b6d5c3a6
7 changed files with 251 additions and 5 deletions
|
|
@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
add_compile_options(-Wall)
|
add_compile_options(-Wall)
|
||||||
|
|
||||||
# Find Qt packages
|
# Find Qt packages
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia SerialPort REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia SerialPort Mqtt REQUIRED)
|
||||||
|
|
||||||
# Find dependencies using pkg-config
|
# Find dependencies using pkg-config
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
@ -25,7 +25,8 @@ set(CMAKE_AUTOUIC ON)
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
# Create executable
|
# Create executable
|
||||||
add_executable(SHinterface)
|
add_executable(SHinterface
|
||||||
|
src/sensors/mqttsensorsource.h src/sensors/mqttsensorsource.cpp)
|
||||||
|
|
||||||
# Add sources to executable
|
# Add sources to executable
|
||||||
target_sources(SHinterface
|
target_sources(SHinterface
|
||||||
|
|
@ -159,6 +160,7 @@ target_link_libraries(SHinterface
|
||||||
Qt6::Network
|
Qt6::Network
|
||||||
Qt6::Multimedia
|
Qt6::Multimedia
|
||||||
Qt6::SerialPort
|
Qt6::SerialPort
|
||||||
|
Qt6::Mqtt
|
||||||
${PIPEWIRE_LIBRARIES}
|
${PIPEWIRE_LIBRARIES}
|
||||||
${LIBNL3_LIBRARIES}
|
${LIBNL3_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
||||||
connect(tcpServer, &TcpServer::gotSensor, &globalSensors, &SensorStore::sensorGotState);
|
connect(tcpServer, &TcpServer::gotSensor, &globalSensors, &SensorStore::sensorGotState);
|
||||||
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);
|
||||||
|
|
||||||
sunSensorSource.run();
|
sunSensorSource.run();
|
||||||
|
|
||||||
|
|
@ -93,6 +94,9 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
||||||
|
|
||||||
loadFromDisk(settingsPath);
|
loadFromDisk(settingsPath);
|
||||||
|
|
||||||
|
QJsonObject mqttJson = settings["Mqtt"].toObject();
|
||||||
|
mqttSensorSource.start(mqttJson);
|
||||||
|
|
||||||
tcpServer->launch(QHostAddress(host), port);
|
tcpServer->launch(QHostAddress(host), port);
|
||||||
connect(&globalItems, &ItemStore::itemUpdated, tcpServer, &TcpServer::itemUpdated);
|
connect(&globalItems, &ItemStore::itemUpdated, tcpServer, &TcpServer::itemUpdated);
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +109,9 @@ PrimaryMainObject::~PrimaryMainObject()
|
||||||
void PrimaryMainObject::store(QJsonObject &json)
|
void PrimaryMainObject::store(QJsonObject &json)
|
||||||
{
|
{
|
||||||
globalItems.store(json);
|
globalItems.store(json);
|
||||||
|
QJsonObject mqttJson = json["Mqtt"].toObject();
|
||||||
|
mqttSensorSource.store(mqttJson);
|
||||||
|
json["Mqtt"] = mqttJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrimaryMainObject::load(const QJsonObject& json)
|
void PrimaryMainObject::load(const QJsonObject& json)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "microcontroller.h"
|
#include "microcontroller.h"
|
||||||
#include "ui/mainwindow.h"
|
#include "ui/mainwindow.h"
|
||||||
#include "sensors/sunsensor.h"
|
#include "sensors/sunsensor.h"
|
||||||
|
#include "sensors/mqttsensorsource.h"
|
||||||
#include "items/fixeditemsource.h"
|
#include "items/fixeditemsource.h"
|
||||||
#include "items/itemloadersource.h"
|
#include "items/itemloadersource.h"
|
||||||
#include "tcpserver.h"
|
#include "tcpserver.h"
|
||||||
|
|
@ -45,6 +46,7 @@ public:
|
||||||
|
|
||||||
//sensors
|
//sensors
|
||||||
SunSensorSource sunSensorSource;
|
SunSensorSource sunSensorSource;
|
||||||
|
MqttSensorSource mqttSensorSource;
|
||||||
|
|
||||||
//item sources
|
//item sources
|
||||||
FixedItemSource fixedItems;
|
FixedItemSource fixedItems;
|
||||||
|
|
|
||||||
187
src/sensors/mqttsensorsource.cpp
Normal file
187
src/sensors/mqttsensorsource.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
#include "mqttsensorsource.h"
|
||||||
|
|
||||||
|
#include<QJsonArray>
|
||||||
|
|
||||||
|
MqttSensorSource::MqttSensorSource(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttSensorSource::start(const QJsonObject& settings)
|
||||||
|
{
|
||||||
|
baseTopicName = settings["BaseTopic"].toString("zigbee2mqtt");
|
||||||
|
|
||||||
|
connect(&client, &QMqttClient::stateChanged, this, &MqttSensorSource::onClientStateChanged);
|
||||||
|
connect(&client, &QMqttClient::errorChanged, this, &MqttSensorSource::onClientError);
|
||||||
|
|
||||||
|
client.setHostname(settings["Host"].toString("127.0.0.1"));
|
||||||
|
client.setPort(settings["Port"].toInt(1883));
|
||||||
|
if(settings.contains("User"))
|
||||||
|
client.setUsername(settings["User"].toString());
|
||||||
|
if(settings.contains("Password"))
|
||||||
|
client.setPassword(settings["Password"].toString());
|
||||||
|
client.setProtocolVersion(QMqttClient::MQTT_5_0);
|
||||||
|
|
||||||
|
client.connectToHost();
|
||||||
|
|
||||||
|
QJsonArray sensorsArray = settings["Sensors"].toArray();
|
||||||
|
|
||||||
|
for(QJsonValueRef sensorRef : sensorsArray)
|
||||||
|
{
|
||||||
|
QJsonObject sensorObject = sensorRef.toObject();
|
||||||
|
if(!sensorObject.contains("Topic"))
|
||||||
|
continue;
|
||||||
|
SensorSubscription sensor;
|
||||||
|
sensor.topic = sensorObject["Topic"].toString();
|
||||||
|
if(!sensorObject.contains("Name"))
|
||||||
|
sensor.name = sensor.topic;
|
||||||
|
else
|
||||||
|
sensor.name = sensorObject["Name"].toString();
|
||||||
|
sensor.id = qHash(baseTopicName + "/" + sensor.topic);
|
||||||
|
sensors.push_back(sensor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttSensorSource::onClientError(QMqttClient::ClientError error)
|
||||||
|
{
|
||||||
|
qWarning()<<"MQTT Client error:"<<error;
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const QString& topic)
|
||||||
|
{
|
||||||
|
for(SensorSubscription& sensor : sensors)
|
||||||
|
{
|
||||||
|
if(baseTopicName + "/" + sensor.topic == topic)
|
||||||
|
return sensor;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttSensorSource::onClientStateChanged(QMqttClient::ClientState state)
|
||||||
|
{
|
||||||
|
if(state == QMqttClient::ClientState::Connected)
|
||||||
|
{
|
||||||
|
qInfo()<<"Connected to MQTT broker at "<<client.hostname()<<client.port();
|
||||||
|
for(SensorSubscription& sensor : sensors)
|
||||||
|
{
|
||||||
|
qDebug()<<"MQTT subscribeing to"<<baseTopicName + "/" + sensor.topic;
|
||||||
|
sensor.subscription = client.subscribe(baseTopicName + "/" + sensor.topic);
|
||||||
|
connect(sensor.subscription, &QMqttSubscription::messageReceived, this, &MqttSensorSource::onMessageReceived);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state == QMqttClient::ClientState::Disconnected)
|
||||||
|
{
|
||||||
|
qWarning()<<"Lost connection to MQTT broker";
|
||||||
|
for(SensorSubscription& sensor : sensors)
|
||||||
|
{
|
||||||
|
if(sensor.subscription)
|
||||||
|
{
|
||||||
|
client.unsubscribe(sensor.topic);
|
||||||
|
sensor.subscription = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(state == QMqttClient::ClientState::Connecting)
|
||||||
|
{
|
||||||
|
qInfo()<<"Connecting to MQTT broker at "<<client.hostname()<<client.port();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
||||||
|
{
|
||||||
|
Sensor sensor;
|
||||||
|
SensorSubscription& sensorSub = findSubscription(message.topic().name());
|
||||||
|
QString baseName = sensorSub.name;
|
||||||
|
sensor.id = sensorSub.id;
|
||||||
|
sensor.updateSeen();
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(message.payload());
|
||||||
|
if(doc.isObject())
|
||||||
|
{
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if(obj.contains("temperature"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " Temperature";
|
||||||
|
sensor.type = Sensor::TYPE_TEMPERATURE;
|
||||||
|
sensor.field = obj["temperature"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("humidity"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " Humidity";
|
||||||
|
sensor.type = Sensor::TYPE_HUMIDITY;
|
||||||
|
sensor.field = obj["humidity"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("illuminance"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " Illuminance";
|
||||||
|
sensor.type = Sensor::TYPE_BRIGHTNESS;
|
||||||
|
sensor.field = obj["illuminance"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("presence"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " Presence";
|
||||||
|
sensor.type = Sensor::TYPE_OCUPANCY;
|
||||||
|
sensor.field = obj["presence"].toBool() ? 1 : 0;
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("co2"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " co2";
|
||||||
|
sensor.type = Sensor::TYPE_CO2;
|
||||||
|
sensor.field = obj["co2"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("formaldehyd"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " Formaldehyd";
|
||||||
|
sensor.type = Sensor::TYPE_FORMALDEHYD;
|
||||||
|
sensor.field = obj["formaldehyd"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("pm25"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " pm25";
|
||||||
|
sensor.type = Sensor::TYPE_PM25;
|
||||||
|
sensor.field = obj["pm25"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.contains("voc"))
|
||||||
|
{
|
||||||
|
sensor.name = baseName + " VOC";
|
||||||
|
sensor.type = Sensor::TYPE_TOTAL_VOC;
|
||||||
|
sensor.field = obj["voc"].toDouble(0);
|
||||||
|
stateChanged(sensor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttSensorSource::store(QJsonObject& json)
|
||||||
|
{
|
||||||
|
json["Host"] = client.hostname();
|
||||||
|
json["Port"] = client.port();
|
||||||
|
json["BaseTopic"] = baseTopicName;
|
||||||
|
if(client.username() != "")
|
||||||
|
json["User"] = client.username();
|
||||||
|
if(client.password() != "")
|
||||||
|
json["Password"] = client.password();
|
||||||
|
QJsonArray sensorsArray;
|
||||||
|
for(const SensorSubscription& sensor : sensors)
|
||||||
|
{
|
||||||
|
QJsonObject sensorObject;
|
||||||
|
sensorObject["Name"] = sensor.name;
|
||||||
|
sensorObject["Topic"] = sensor.topic;
|
||||||
|
sensorsArray.append(sensorObject);
|
||||||
|
}
|
||||||
|
json["Sensors"] = sensorsArray;
|
||||||
|
}
|
||||||
|
|
||||||
44
src/sensors/mqttsensorsource.h
Normal file
44
src/sensors/mqttsensorsource.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef MQTTSENSORSOURCE_H
|
||||||
|
#define MQTTSENSORSOURCE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QtMqtt/QMqttClient>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "sensor.h"
|
||||||
|
|
||||||
|
class MqttSensorSource : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
struct SensorSubscription
|
||||||
|
{
|
||||||
|
uint64_t id;
|
||||||
|
QString topic;
|
||||||
|
QString name;
|
||||||
|
QMqttSubscription* subscription = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
QString baseTopicName;
|
||||||
|
std::vector<SensorSubscription> sensors;
|
||||||
|
QMqttClient client;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SensorSubscription& findSubscription(const QString& topic);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onClientStateChanged(QMqttClient::ClientState state);
|
||||||
|
void onMessageReceived(const QMqttMessage& message);
|
||||||
|
void onClientError(QMqttClient::ClientError error);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MqttSensorSource(QObject *parent = nullptr);
|
||||||
|
void start(const QJsonObject& settings);
|
||||||
|
void store(QJsonObject& json);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void stateChanged(Sensor sensor);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MQTTSENSORSOURCE_H
|
||||||
|
|
@ -17,6 +17,10 @@ public:
|
||||||
static constexpr uint8_t TYPE_BRIGHTNESS = 4;
|
static constexpr uint8_t TYPE_BRIGHTNESS = 4;
|
||||||
static constexpr uint8_t TYPE_BUTTON = 5;
|
static constexpr uint8_t TYPE_BUTTON = 5;
|
||||||
static constexpr uint8_t TYPE_ADC = 6;
|
static constexpr uint8_t TYPE_ADC = 6;
|
||||||
|
static constexpr uint8_t TYPE_CO2 = 7;
|
||||||
|
static constexpr uint8_t TYPE_FORMALDEHYD= 8;
|
||||||
|
static constexpr uint8_t TYPE_PM25 = 9;
|
||||||
|
static constexpr uint8_t TYPE_TOTAL_VOC = 10;
|
||||||
static constexpr uint8_t TYPE_LOWBATTERY = 128;
|
static constexpr uint8_t TYPE_LOWBATTERY = 128;
|
||||||
static constexpr uint8_t TYPE_SHUTDOWN_IMMINENT = 251;
|
static constexpr uint8_t TYPE_SHUTDOWN_IMMINENT = 251;
|
||||||
static constexpr uint8_t TYPE_OCUPANCY = 252;
|
static constexpr uint8_t TYPE_OCUPANCY = 252;
|
||||||
|
|
@ -25,13 +29,13 @@ public:
|
||||||
static constexpr uint8_t TYPE_DUMMY = 255;
|
static constexpr uint8_t TYPE_DUMMY = 255;
|
||||||
|
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
uint8_t id;
|
uint64_t id;
|
||||||
float field;
|
float field;
|
||||||
QString name;
|
QString name;
|
||||||
QDateTime lastSeen;
|
QDateTime lastSeen;
|
||||||
bool hidden;
|
bool hidden;
|
||||||
|
|
||||||
Sensor(uint8_t typeIn, uint8_t idIn, float fieldIn = 0, QString nameIn = "", bool hiddenIn = false): type(typeIn),
|
Sensor(uint64_t typeIn, uint8_t idIn, float fieldIn = 0, QString nameIn = "", bool hiddenIn = false): type(typeIn),
|
||||||
id(idIn), field(fieldIn), name(nameIn), hidden(hiddenIn)
|
id(idIn), field(fieldIn), name(nameIn), hidden(hiddenIn)
|
||||||
{
|
{
|
||||||
lastSeen = QDateTime::currentDateTime();
|
lastSeen = QDateTime::currentDateTime();
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<property name="autoFillBackground">
|
<property name="autoFillBackground">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0">
|
<layout class="QVBoxLayout" name="verticalLayout_5" stretch="0,0">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSplitter" name="splitter">
|
<widget class="QSplitter" name="splitter">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue