qt-openzwave/qt-ozwdaemon/mqttpublisher.cpp
Justin Hammond 72a55e5b38
Fix Crash when values that have been deleted (#17)
* Allow Statistics Timeout to be adjustable

* Fix Crash with Values that have been deleted/removed when the Notification Arrives at the app
2020-01-17 16:16:08 +08:00

821 lines
41 KiB
C++

#include <QDateTime>
#include <QCoreApplication>
#include "mqttpublisher.h"
#include "qtrj.h"
#include "mqttcommands/mqttcommands.h"
#include <rapidjson/prettywriter.h> // for stringify JSON
Q_LOGGING_CATEGORY(ozwmp, "ozw.mqtt.publisher");
mqttpublisher::mqttpublisher(QSettings *settings, QObject *parent) : QObject(parent)
{
this->settings = settings;
this->m_client = new QMqttClient(this);
this->m_client->setHostname(settings->value("MQTTServer", "127.0.0.1").toString());
this->m_client->setPort(static_cast<quint16>(settings->value("MQTTPort", 1883).toInt()));
this->m_ozwstatus.SetObject();
/* setup the Commands */
this->m_commands = new MqttCommands(this);
this->m_commands->setupCommands();
connect(this->m_client, &QMqttClient::stateChanged, this, &mqttpublisher::updateLogStateChange);
connect(this->m_client, &QMqttClient::disconnected, this, &mqttpublisher::brokerDisconnected);
connect(this->m_client, &QMqttClient::errorChanged, this, &mqttpublisher::brokerError);
connect(m_client, &QMqttClient::pingResponseReceived, this, [this]() {
const QString content = QDateTime::currentDateTime().toString()
+ QLatin1String(" PingResponse")
+ QLatin1Char('\n');
qCDebug(ozwmp) << content;
});
this->m_client->setWillTopic(getTopic(MQTT_OZW_STATUS_TOPIC));
rapidjson::Document willMsg(rapidjson::kObjectType);
QT2JS::SetString(willMsg, "Status", "Offline");
this->m_client->setWillMessage(QT2JS::getJSON(willMsg));
this->m_client->setWillRetain(true);
connect(&this->m_statsTimer, &QTimer::timeout, this, &mqttpublisher::doStats);
}
void mqttpublisher::cleanTopics(QMqttMessage msg) {
if (msg.retain() == true) {
qCDebug(ozwmp) << "Topics: " << msg.topic().name();
rapidjson::Document jsmsg;
jsmsg.Parse(msg.payload());
if (msg.topic().name() == getTopic("status")) {
qCDebug(ozwmp) << msg.payload();
/* when our status message is anything other than Offline, drop the Subscription */
if (jsmsg.HasMember("Status") && (QString::fromStdString(jsmsg["Status"].GetString()) != "Offline")) {
qCDebug(ozwmp) << "Unsubscribing from Topic Cleanup";
this->m_cleanTopicSubscription->unsubscribe();
exit(-1);
}
return;
}
if (jsmsg.HasMember("TimeStamp")) {
QDateTime ts = QDateTime::fromSecsSinceEpoch(jsmsg["TimeStamp"].GetUint64());
if (ts < this->m_currentStartTime) {
qCDebug(ozwmp) << "Removing Stale Topic/Msg: " << msg.topic().name();
this->m_client->publish(msg.topic(), "", 0, true);
}
} else {
qCWarning(ozwmp) << "MQTT Message on Topic " << msg.topic().name() << "Does not have TimeStamp - Not Cleaning: " << msg.payload();
}
}
}
void mqttpublisher::brokerError(QMqttClient::ClientError error) {
qCWarning(ozwmp) << "Broker Error" << error;
if (settings->value("MQTTTLS").toBool() == true) {
qCWarning(ozwmp) << qobject_cast<QSslSocket *>(this->m_client->transport())->errorString();
}
}
void mqttpublisher::doStats() {
if (!this->m_qtozwdeamon) {
return;
}
QTOZWManager *manager = this->m_qtozwdeamon->getManager();
DriverStatistics ds = manager->GetDriverStatistics();
rapidjson::Document dsjson;
QT2JS::SetInt64(dsjson, "SOFCnt", ds.m_SOFCnt);
QT2JS::SetInt64(dsjson, "ACKWaiting", ds.m_ACKWaiting);
QT2JS::SetInt64(dsjson, "readAborts", ds.m_readAborts);
QT2JS::SetInt64(dsjson, "badChecksum", ds.m_badChecksum);
QT2JS::SetInt64(dsjson, "readCnt", ds.m_readCnt);
QT2JS::SetInt64(dsjson, "writeCnt", ds.m_writeCnt);
QT2JS::SetInt64(dsjson, "CANCnt", ds.m_CANCnt);
QT2JS::SetInt64(dsjson, "NAKCnt", ds.m_NAKCnt);
QT2JS::SetInt64(dsjson, "ACKCnt", ds.m_ACKCnt);
QT2JS::SetInt64(dsjson, "OOFCnt", ds.m_OOFCnt);
QT2JS::SetInt64(dsjson, "dropped", ds.m_dropped);
QT2JS::SetInt64(dsjson, "retries", ds.m_retries);
QT2JS::SetInt64(dsjson, "callbacks", ds.m_callbacks);
QT2JS::SetInt64(dsjson, "badroutes", ds.m_badroutes);
QT2JS::SetInt64(dsjson, "noack", ds.m_noack);
QT2JS::SetInt64(dsjson, "netbusy", ds.m_netbusy);
QT2JS::SetInt64(dsjson, "notidle", ds.m_notidle);
QT2JS::SetInt64(dsjson, "txverified", ds.m_txverified);
QT2JS::SetInt64(dsjson, "nondelivery", ds.m_nondelivery);
QT2JS::SetInt64(dsjson, "routedbusy", ds.m_routedbusy);
QT2JS::SetInt64(dsjson, "broadcastReadCnt", ds.m_broadcastReadCnt);
QT2JS::SetInt64(dsjson, "broadcastWriteCnt", ds.m_broadcastWriteCnt);
this->m_client->publish(QMqttTopicName(getTopic(MQTT_OZW_STATS_TOPIC)), QT2JS::getJSON(dsjson), 0, false);
for (int i = 0; i < this->m_nodeModel->rowCount(QModelIndex()); i++) {
rapidjson::Document nsjson;
int NodeID = this->m_nodeModel->data(this->m_nodeModel->index(i, mqttNodeModel::NodeColumns::NodeID), Qt::DisplayRole).toInt();
NodeStatistics ns = manager->GetNodeStatistics(NodeID);
QT2JS::SetInt64(nsjson, "sendCount", ns.sentCount); /**< Number of Packets Sent to the Node */
QT2JS::SetInt64(nsjson, "sentFailed", ns.sentFailed); /**< Number of Packets that Failed to be acknowledged by the Node or Controller */
QT2JS::SetInt64(nsjson, "retries", ns.retries); /**< Number of times we have had to Retry sending packets to the Node */
QT2JS::SetInt64(nsjson, "receivedPackets", ns.receivedPackets); /**< Number of received Packets from the Node */
QT2JS::SetInt64(nsjson, "receivedDupPackets", ns.receivedDupPackets); /**< Number of Duplicate Packets received from the Node */
QT2JS::SetInt64(nsjson, "receivedUnsolicited", ns.receivedUnsolicited); /**< Number of Unsolicited Packets received from the Node */
QT2JS::SetInt64(nsjson, "lastSentTimeStamp", ns.lastSentTimeStamp.toSecsSinceEpoch()); /**< TimeStamp of the Last time we sent a packet to the Node */
QT2JS::SetInt64(nsjson, "lastReceivedTimeStamp", ns.lastReceivedTimeStamp.toSecsSinceEpoch()); /**< Timestamp of the last time we received a packet from the Node */
QT2JS::SetInt64(nsjson, "lastRequestRTT", ns.lastRequestRTT); /**< Last Round-Trip Time when we made a request to the Node */
QT2JS::SetInt64(nsjson, "averageRequestRTT", ns.averageRequestRTT); /**< Average Round-Trip Time when we make requests to a Node */
QT2JS::SetInt64(nsjson, "lastResponseRTT", ns.lastResponseRTT); /**< Last Round-Trip Time when we got a Response from a Node */
QT2JS::SetInt64(nsjson, "averageResponseRTT", ns.averageResponseRTT); /**< Average Round-Trip Time when got a Response from a Node */
QT2JS::SetInt(nsjson, "quality", ns.quality); /**< The Quality of the Signal from the Node, as Reported by the Controller */
QT2JS::SetBool(nsjson, "extendedTXSupported", ns.extendedTXSupported); /**< If these statistics support Extended TX Reporting (Controller Dependent) */
QT2JS::SetInt(nsjson, "txTime", ns.txTime); /**< The Time it took to Transmit the last packet to the Node */
QT2JS::SetInt(nsjson, "hops", ns.hops); /**< The Number of hops the packet traversed to reach the node */
QT2JS::SetString(nsjson, "rssi_1", ns.rssi_1); /**< The RSSI Strength of the first hop */
QT2JS::SetString(nsjson, "rssi_2", ns.rssi_2); /**< The RSSI Strength of the second hop */
QT2JS::SetString(nsjson, "rssi_3", ns.rssi_3); /**< The RSSI Strength of the third hop */
QT2JS::SetString(nsjson, "rssi_4", ns.rssi_4); /**< The RSSI Strength of the fourth hop */
QT2JS::SetString(nsjson, "rssi_5", ns.rssi_5); /**< The RSSI Strength of the final hop */
QT2JS::SetInt(nsjson, "route_1", ns.route_1); /**< The NodeId of the First Hop */
QT2JS::SetInt(nsjson, "route_2", ns.route_2); /**< The NodeId of the Second Hop */
QT2JS::SetInt(nsjson, "route_3", ns.route_3); /**< The NodeId of the third Hop */
QT2JS::SetInt(nsjson, "route_4", ns.route_4); /**< The NodeId of the Fourth Hop */
QT2JS::SetInt(nsjson, "ackChannel", ns.ackChannel); /**< The Channel that recieved the ACK From the Node */
QT2JS::SetInt(nsjson, "lastTXChannel", ns.lastTXChannel); /**< The last Channel we used to communicate with the Node */
QT2JS::SetString(nsjson, "routeScheme", ns.routeScheme); /**< How the Route was calculated when we last communicated with the Node */
QT2JS::SetString(nsjson, "routeUsed", ns.routeUsed); /**< The Route last used to communicate with the Node */
QT2JS::SetString(nsjson, "routeSpeed", ns.routeSpeed); /**< The Speed that was used when we last communicated with the node */
QT2JS::SetInt(nsjson, "routeTries", ns.routeTries); /**< The Number of attempts the Controller made to route the packet to the Node */
QT2JS::SetInt(nsjson, "lastFailedLinkFrom", ns.lastFailedLinkFrom); /**< The Last Failed Link From */
QT2JS::SetInt(nsjson, "lastFailedLinkTo", ns.lastFailedLinkTo); /**< The Last Failed Link To */
this->m_client->publish(QMqttTopicName(getNodeTopic(MQTT_OZW_STATS_NODE_TOPIC, NodeID)), QT2JS::getJSON(nsjson), 0, false);
}
}
bool mqttpublisher::isValidNode(quint8 node) {
return this->m_nodeModel->isValidNode(node);
}
bool mqttpublisher::isValidValueID(quint64 vidKey) {
return this->m_valueModel->isValidValueID(vidKey);
}
QVariant mqttpublisher::getValueData(quint64 vidKey, mqttValueIDModel::ValueIdColumns col) {
return this->m_valueModel->getValueData(vidKey, col);
}
bool mqttpublisher::setValue(quint64 vidKey, QVariant data) {
return this->m_valueModel->setData(vidKey, data);
}
QString mqttpublisher::getTopic(QString topic) {
if (!topic.endsWith('#') && !topic.endsWith('/'))
topic = topic.append("/");
return QString(MQTT_OZW_TOP_TOPIC).arg(settings->value("Instance", 1).toInt()).append(topic);
}
QString mqttpublisher::getNodeTopic(QString topic, quint8 node) {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(topic.arg(static_cast<quint8>(node)));
return t;
}
QString mqttpublisher::getInstanceTopic(QString topic, quint8 node, quint8 instance) {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(topic.arg(static_cast<quint8>(node)).arg(static_cast<quint8>(instance)));
return t;
}
QString mqttpublisher::getCommandClassTopic(QString topic, quint8 node, quint8 instance, quint8 cc) {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(topic.arg(static_cast<quint8>(node)).arg(static_cast<quint8>(instance)).arg(static_cast<quint8>(cc)));
return t;
}
QString mqttpublisher::getValueTopic(QString topic, quint8 node, quint8 instance, quint8 cc, quint64 vid) {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(topic.arg(static_cast<quint8>(node)).arg(static_cast<quint8>(instance)).arg(static_cast<quint8>(cc)).arg(static_cast<quint64>(vid)));
return t;
}
QString mqttpublisher::getAssociationTopic(quint8 node, quint8 group) {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(QString(MQTT_OZW_ASSOCIATION_TOPIC).arg(static_cast<quint8>(node)).arg(static_cast<quint8>(group)));
return t;
}
QString mqttpublisher::getCommandTopic() {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(QString(MQTT_OZW_COMMAND_TOPIC));
return t;
}
QString mqttpublisher::getCommandResponseTopic(QString cmd) {
QString t(MQTT_OZW_TOP_TOPIC);
t = t.arg(settings->value("Instance", 1).toInt());
t.append(QString(MQTT_OZW_RESPONSE_TOPIC).arg(cmd));
return t;
}
void mqttpublisher::setOZWDaemon(qtozwdaemon *ozwdaemon) {
this->m_qtozwdeamon = ozwdaemon;
QTOZWManager *manager = this->m_qtozwdeamon->getManager();
this->m_nodeModel = static_cast<mqttNodeModel *>(manager->getNodeModel());
this->m_valueModel = static_cast<mqttValueIDModel *>(manager->getValueModel());
this->m_assocModel = static_cast<mqttAssociationModel *>(manager->getAssociationModel());
connect(manager, &QTOZWManager::ready, this, &mqttpublisher::ready);
connect(manager, &QTOZWManager::valueAdded, this, &mqttpublisher::valueAdded);
connect(manager, &QTOZWManager::valueChanged, this, &mqttpublisher::valueChanged);
connect(manager, &QTOZWManager::valueRemoved, this, &mqttpublisher::valueRemoved);
connect(manager, &QTOZWManager::valueRefreshed, this, &mqttpublisher::valueRefreshed);
connect(manager, &QTOZWManager::nodeNew, this, &mqttpublisher::nodeNew);
connect(manager, &QTOZWManager::nodeAdded, this, &mqttpublisher::nodeAdded);
connect(manager, &QTOZWManager::nodeEvent, this, &mqttpublisher::nodeEvent);
connect(manager, &QTOZWManager::nodeReset, this, &mqttpublisher::nodeReset);
connect(manager, &QTOZWManager::nodeNaming, this, &mqttpublisher::nodeNaming);
connect(manager, &QTOZWManager::nodeRemoved, this, &mqttpublisher::nodeRemoved);
connect(manager, &QTOZWManager::nodeProtocolInfo, this, &mqttpublisher::nodeProtocolInfo);
connect(manager, &QTOZWManager::nodeEssentialNodeQueriesComplete, this, &mqttpublisher::nodeEssentialNodeQueriesComplete);
connect(manager, &QTOZWManager::nodeQueriesComplete, this, &mqttpublisher::nodeQueriesComplete);
connect(manager, &QTOZWManager::nodeGroupChanged, this, &mqttpublisher::nodeGroupChanged);
connect(manager, &QTOZWManager::driverReady, this, &mqttpublisher::driverReady);
connect(manager, &QTOZWManager::driverReset, this, &mqttpublisher::driverReset);
connect(manager, &QTOZWManager::driverFailed, this, &mqttpublisher::driverFailed);
connect(manager, &QTOZWManager::driverRemoved, this, &mqttpublisher::driverRemoved);
connect(manager, &QTOZWManager::driverAllNodesQueried, this, &mqttpublisher::driverAllNodesQueried);
connect(manager, &QTOZWManager::driverAwakeNodesQueried, this, &mqttpublisher::driverAwakeNodesQueried);
connect(manager, &QTOZWManager::driverAllNodesQueriedSomeDead, this, &mqttpublisher::driverAllNodesQueriedSomeDead);
connect(manager, &QTOZWManager::controllerCommand, this, &mqttpublisher::controllerCommand);
connect(manager, &QTOZWManager::ozwNotification, this, &mqttpublisher::ozwNotification);
connect(manager, &QTOZWManager::ozwUserAlert, this, &mqttpublisher::ozwUserAlert);
connect(manager, &QTOZWManager::manufacturerSpecificDBReady, this, &mqttpublisher::manufacturerSpecificDBReady);
connect(manager, &QTOZWManager::starting, this, &mqttpublisher::starting);
connect(manager, &QTOZWManager::started, this, &mqttpublisher::started);
connect(manager, &QTOZWManager::stopped, this, &mqttpublisher::stopped);
QT2JS::SetString(this->m_ozwstatus, "OpenZWave_Version", this->getQTOZWManager()->getVersionAsString());
QT2JS::SetString(this->m_ozwstatus, "OZWDeamon_Version", QCoreApplication::applicationVersion());
QT2JS::SetString(this->m_ozwstatus, "QTOpenZWave_Version", this->m_qtozwdeamon->getQTOpenZWave()->getVersion());
QT2JS::SetString(this->m_ozwstatus, "QT_Version", qVersion());
this->m_currentStartTime = QDateTime::currentDateTime();
if (settings->value("MQTTTLS").toBool() == true) {
this->m_client->connectToHostEncrypted();
} else {
this->m_client->connectToHost();
}
}
void mqttpublisher::updateLogStateChange()
{
qCDebug(ozwmp) << "MQTT State Change" << m_client->state();
if (this->m_client->state() == QMqttClient::ClientState::Connecting) {
qCInfo(ozwmp) << "MQTT Client Connecting";
if (settings->value("MQTTTLS").toBool() == true) {
QSslSocket *socket = qobject_cast<QSslSocket *>(this->m_client->transport());
socket->setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone);
}
} else if (this->m_client->state() == QMqttClient::ClientState::Connected) {
qCInfo(ozwmp) << "MQTT Client Connected";
this->m_cleanTopicSubscription = this->m_client->subscribe(QMqttTopicFilter(getTopic("#")));
connect(this->m_cleanTopicSubscription, &QMqttSubscription::messageReceived, this, &mqttpublisher::cleanTopics);
this->m_commands->setupSubscriptions(this->m_client, this->getCommandTopic());
return;
} else if (this->m_client->state() == QMqttClient::ClientState::Disconnected) {
if (settings->value("StopOnFailure", false).toBool()) {
qCWarning(ozwmp) << "Exiting on Failure";
exit(-1);
}
return;
}
}
void mqttpublisher::brokerDisconnected()
{
qCDebug(ozwmp) << "Disconnnected";
}
bool mqttpublisher::sendStatusUpdate() {
QT2JS::SetUInt64(this->m_ozwstatus, "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getTopic(MQTT_OZW_STATUS_TOPIC)), QT2JS::getJSON(this->m_ozwstatus), 0, true);
return true;
}
bool mqttpublisher::clearStatusUpdate() {
QT2JS::removeField(this->m_ozwstatus, "getControllerNodeId");
QT2JS::removeField(this->m_ozwstatus, "getSUCNodeId");
QT2JS::removeField(this->m_ozwstatus, "isPrimaryController");
QT2JS::removeField(this->m_ozwstatus, "isBridgeController");
QT2JS::removeField(this->m_ozwstatus, "hasExtendedTXStatistics");
QT2JS::removeField(this->m_ozwstatus, "getControllerLibraryVersion");
QT2JS::removeField(this->m_ozwstatus, "getControllerLibraryType");
QT2JS::removeField(this->m_ozwstatus, "getControllerPath");
return true;
}
bool mqttpublisher::sendNodeUpdate(quint8 node) {
QT2JS::SetUInt64(*this->m_nodes[node], "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getNodeTopic(MQTT_OZW_NODE_TOPIC, node)), QT2JS::getJSON(*this->m_nodes[node]), 0, true);
return true;
}
bool mqttpublisher::sendValueUpdate(quint64 vidKey) {
quint8 node = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::Node).value<quint8>();
quint8 instance = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::Instance).value<quint8>();
quint8 cc = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::CommandClass).value<quint8>();
if (node == 0) {
qCWarning(ozwmp) << "sendValueUpdate: Can't find Node for Value: " << vidKey;
return false;
}
if (instance == 0) {
qCWarning(ozwmp) << "sendValueUpdate: Can't find instance for Value: " << vidKey;
return false;
}
if (cc == 0) {
qCWarning(ozwmp) << "sendValueUpdate: Can't find CC for Value: " << vidKey;
return false;
}
QT2JS::SetUInt64(*this->m_values[vidKey], "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getValueTopic(MQTT_OZW_VID_TOPIC, node, instance, cc, vidKey)), QT2JS::getJSON(*this->m_values[vidKey]), 0, true);
return true;
}
bool mqttpublisher::sendInstanceUpdate(quint8 node, quint8 instance) {
rapidjson::Document *jsinstance = nullptr;
if (!(jsinstance = this->getInstanceJSON(node, instance))) {
return false;
}
QT2JS::SetUInt64(*jsinstance, "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getInstanceTopic(MQTT_OZW_INSTANCE_TOPIC, node, instance)), QT2JS::getJSON(*jsinstance), 0, true);
return true;
}
bool mqttpublisher::sendCommandClassUpdate(quint8 node, quint8 instance, quint8 cc) {
rapidjson::Document *jsCommandClass = nullptr;
if (!(jsCommandClass = this->getCommandClassJSON(node, instance, cc))) {
return false;
}
QT2JS::SetUInt64(*jsCommandClass, "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getCommandClassTopic(MQTT_OZW_COMMANDCLASS_TOPIC, node, instance, cc)), QT2JS::getJSON(*jsCommandClass), 0, true);
return true;
}
void mqttpublisher::sendCommandUpdate(QString command, rapidjson::Document &js) {
QT2JS::SetUInt64(js, "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getCommandResponseTopic(command.toLower())), QT2JS::getJSON(js), 0, false);
return;
}
void mqttpublisher::sendAssociationUpdate(quint8 node, quint8 group, rapidjson::Document &js) {
QT2JS::SetUInt64(js, "TimeStamp", QDateTime::currentSecsSinceEpoch());
this->m_client->publish(QMqttTopicName(getAssociationTopic(node, group)), QT2JS::getJSON(js), 0, true);
return;
}
bool mqttpublisher::delNodeTopic(quint8 node) {
this->m_client->publish(QMqttTopicName(getNodeTopic(MQTT_OZW_NODE_TOPIC, node)), NULL, 0, true);
return true;
}
bool mqttpublisher::delValueTopic(quint64 vidKey) {
quint8 node = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::Node).value<quint8>();
quint8 instance = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::Instance).value<quint8>();
quint8 cc = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::CommandClass).value<quint8>();
if (node == 0) {
qCWarning(ozwmp) << "delValueTopic: Can't find Node for Value: " << vidKey;
return false;
}
if (instance == 0) {
qCWarning(ozwmp) << "sendValueUpdate: Can't find instance for Value: " << vidKey;
return false;
}
if (cc == 0) {
qCWarning(ozwmp) << "sendValueUpdate: Can't find CC for Value: " << vidKey;
return false;
}
this->m_client->publish(QMqttTopicName(getValueTopic(MQTT_OZW_VID_TOPIC, node, instance, cc, vidKey)), NULL, 0, true);
return true;
}
bool mqttpublisher::delInstanceTopic(quint8 node, quint8 instance) {
this->m_client->publish(QMqttTopicName(getInstanceTopic(MQTT_OZW_INSTANCE_TOPIC, node, instance)), NULL, 0, true);
return true;
}
bool mqttpublisher::delCommandClassTopic(quint8 node, quint8 instance, quint8 cc) {
this->m_client->publish(QMqttTopicName(getCommandClassTopic(MQTT_OZW_COMMANDCLASS_TOPIC, node, instance, cc)), NULL, 0, true);
return true;
}
void mqttpublisher::ready() {
qCDebug(ozwmp) << "Publishing Event ready:";
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "Ready");
this->sendStatusUpdate();
}
void mqttpublisher::valueAdded(quint64 vidKey) {
qCDebug(ozwmp) << "Publishing Event valueAdded:" << vidKey;
/* create instance and CC Topics if necessary */
rapidjson::Document *jsinstance = nullptr;
quint8 node = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::Node).value<quint8>();
quint8 instance = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::Instance).value<quint8>();
quint8 cc = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::CommandClass).value<quint8>();
if (!(jsinstance = this->getInstanceJSON(node, instance))) {
jsinstance = new rapidjson::Document(rapidjson::kObjectType);
QT2JS::SetInt(*jsinstance, "Instance", instance);
this->m_instances[node][instance] = jsinstance;
}
this->sendInstanceUpdate(node, instance);
rapidjson::Document *jsCommandClass = nullptr;
if (!(jsCommandClass = this->getCommandClassJSON(node, instance, cc))) {
jsCommandClass = new rapidjson::Document(rapidjson::kObjectType);
QT2JS::SetInt(*jsCommandClass, "Instance", instance);
QT2JS::SetInt(*jsCommandClass, "CommandClassId", cc);
QT2JS::SetString(*jsCommandClass, "CommandClass", this->getQTOZWManager()->getCommandClassString(cc));
this->m_CommandClasses[node][instance][cc] = jsCommandClass;
}
this->sendCommandClassUpdate(node, instance, cc);
if (this->m_values.find(vidKey) == this->m_values.end()) {
this->m_values.insert(vidKey, new rapidjson::Document());
}
if (this->m_valueModel->populateJsonObject(*this->m_values[vidKey], vidKey, this->m_qtozwdeamon->getManager())) {
/* something has changed */
QT2JS::SetString(*this->m_values[vidKey], "Event", "valueAdded");
this->sendValueUpdate(vidKey);
}
}
void mqttpublisher::valueRemoved(quint64 vidKey) {
qCDebug(ozwmp) << "Publishing Event valueRemoved:" << vidKey;
this->delValueTopic(vidKey);
quint8 vinstance = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::Instance).toInt();
quint8 vcc = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::CommandClass).toInt();
quint8 node = this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::Node).toInt();
bool removeInstance = true;
bool removeCC = true;
QMap<quint64, rapidjson::Document *>::iterator it;
for (it =this->m_values.begin(); it != this->m_values.end(); it++) {
if (this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::ValueIDKey).value<quint64>() == vidKey) {
continue;
}
if (this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::Node).toInt() != node) {
continue;
}
if (this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::Instance).toInt() != vinstance) {
continue;
};
quint8 cc = this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::CommandClass).toInt();
if (vcc == cc) {
removeCC = false;
}
}
if (removeCC) {
qCDebug(ozwmp) << "Removing CommandClass Topic for " << this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::Node).toInt() << vinstance << vcc;
this->delCommandClassTopic(this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::Node).toInt(), vinstance, vcc);
}
for (it =this->m_values.begin(); it != this->m_values.end(); it++) {
if (this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::ValueIDKey).value<quint64>() == vidKey) {
continue;
}
if (this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::Node).toInt() != node) {
continue;
}
quint8 instance = this->m_valueModel->getValueData(it.key(), QTOZW_ValueIds::ValueIdColumns::Instance).toInt();
if (vinstance == instance) {
removeInstance = false;
}
}
if (removeInstance) {
qCDebug(ozwmp) << "Removing Instance Topic for " << this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::Node).toInt() << vinstance;
this->delInstanceTopic(this->m_valueModel->getValueData(vidKey, QTOZW_ValueIds::ValueIdColumns::Node).toInt(), vinstance);
}
if (this->m_values.find(vidKey) != this->m_values.end()) {
this->m_values.remove(vidKey);
} else {
qCWarning(ozwmp) << "Can't Find Value Map for " << vidKey;
}
}
void mqttpublisher::valueChanged(quint64 vidKey) {
qCDebug(ozwmp) << "Publishing Event valueChanged:" << vidKey;
if (this->m_valueModel->encodeValue(*this->m_values[vidKey], vidKey)) {
/* something has changed */
QT2JS::SetString(*this->m_values[vidKey], "Event", "valueChanged");
this->sendValueUpdate(vidKey);
}
}
void mqttpublisher::valueRefreshed(quint64 vidKey) {
qCDebug(ozwmp) << "Publishing Event valueRefreshed:" << vidKey;
if (this->m_valueModel->encodeValue(*this->m_values[vidKey], vidKey)) {
/* something has changed */
QT2JS::SetString(*this->m_values[vidKey], "Event", "valueRefreshed");
this->sendValueUpdate(vidKey);
}
}
void mqttpublisher::nodeNew(quint8 node) {
qCDebug(ozwmp) << "Publishing Event NodeNew:" << node;
if (this->m_nodes.find(node) == this->m_nodes.end()) {
this->m_nodes.insert(node, new rapidjson::Document(rapidjson::kObjectType));
}
this->m_nodeModel->populateJsonObject(*this->m_nodes[node], node, this->m_qtozwdeamon->getManager());
QT2JS::SetString(*this->m_nodes[node], "Event", "nodeNew");
this->sendNodeUpdate(node);
}
void mqttpublisher::nodeAdded(quint8 node) {
qCDebug(ozwmp) << "Publishing Event NodeAdded:" << node;
if (this->m_nodes.find(node) == this->m_nodes.end()) {
this->m_nodes.insert(node, new rapidjson::Document(rapidjson::kObjectType));
}
this->m_nodeModel->populateJsonObject(*this->m_nodes[node], node, this->m_qtozwdeamon->getManager());
QT2JS::SetString(*this->m_nodes[node], "Event", "nodeAdded");
this->sendNodeUpdate(node);
}
void mqttpublisher::nodeRemoved(quint8 node) {
qCDebug(ozwmp) << "Publishing Event nodeRemoved:" << node;
this->delNodeTopic(node);
if (this->m_nodes.find(node) == this->m_nodes.end()) {
this->m_nodes.remove(node);
}
}
void mqttpublisher::nodeReset(quint8 node) {
qCDebug(ozwmp) << "Publishing Event nodeReset:" << node;
this->delNodeTopic(node);
if (this->m_nodes.find(node) == this->m_nodes.end()) {
this->m_nodes.remove(node);
}
}
void mqttpublisher::nodeNaming(quint8 node) {
qCDebug(ozwmp) << "Publishing Event nodeNaming:" << node;
QT2JS::SetString(*this->m_nodes[node], "Event", "nodeNaming");
this->sendNodeUpdate(node);
}
void mqttpublisher::nodeEvent(quint8 node, quint8 event) {
Q_UNUSED(node);
Q_UNUSED(event);
/* we dont do anything here, as NodeEvent is just a BASIC message
* which should be handled via the normal ValueID/ValueChanged Events
*/
}
void mqttpublisher::nodeProtocolInfo(quint8 node) {
qCDebug(ozwmp) << "Publishing Event nodeProtocolInfo:" << node;
this->m_nodeModel->populateJsonObject(*this->m_nodes[node], node, this->m_qtozwdeamon->getManager());
QT2JS::SetString(*this->m_nodes[node], "Event", "nodeProtocolInfo");
this->sendNodeUpdate(node);
}
void mqttpublisher::nodeEssentialNodeQueriesComplete(quint8 node) {
qCDebug(ozwmp) << "Publishing Event nodeEssentialNodeQueriesComplete:" << node;
this->m_nodeModel->populateJsonObject(*this->m_nodes[node], node, this->m_qtozwdeamon->getManager());
QT2JS::SetString(*this->m_nodes[node], "Event", "nodeEssentialNodeQueriesComplete");
this->sendNodeUpdate(node);
}
void mqttpublisher::nodeQueriesComplete(quint8 node) {
qCDebug(ozwmp) << "Publishing Event nodeQueriesComplete:" << node;
this->m_nodeModel->populateJsonObject(*this->m_nodes[node], node, this->m_qtozwdeamon->getManager());
QT2JS::SetString(*this->m_nodes[node], "Event", "nodeQueriesComplete");
this->sendNodeUpdate(node);
}
void mqttpublisher::nodeGroupChanged(quint8 node, quint8 group) {
qCDebug(ozwmp) << "Publishing Event nodeGroupChanged: " << node << " Group: " << group;
rapidjson::Document *jsinstance = new rapidjson::Document(rapidjson::kObjectType);
this->m_assocModel->populateJsonObject(*jsinstance, node, group, this->m_qtozwdeamon->getManager());
this->sendAssociationUpdate(node, group, *jsinstance);
delete jsinstance;
}
void mqttpublisher::driverReady(quint32 homeID) {
qCDebug(ozwmp) << "Publishing Event driverReady:" << homeID;
QT2JS::SetString(this->m_ozwstatus, "Status", "driverReady");
QT2JS::SetUint(this->m_ozwstatus, "getControllerNodeId", this->getQTOZWManager()->getControllerNodeId());
QT2JS::SetUint(this->m_ozwstatus, "getSUCNodeId", this->getQTOZWManager()->getSucNodeId());
QT2JS::SetBool(this->m_ozwstatus, "isPrimaryController", this->getQTOZWManager()->isStaticUpdateController());
QT2JS::SetBool(this->m_ozwstatus, "isBridgeController", this->getQTOZWManager()->isBridgeController());
QT2JS::SetBool(this->m_ozwstatus, "hasExtendedTXStatistics", this->getQTOZWManager()->hasExtendedTXStatus());
QT2JS::SetString(this->m_ozwstatus, "getControllerLibraryVersion", this->getQTOZWManager()->getLibraryVersion());
QT2JS::SetString(this->m_ozwstatus, "getControllerLibraryType", this->getQTOZWManager()->getLibraryTypeName());
QT2JS::SetString(this->m_ozwstatus, "getControllerPath", this->getQTOZWManager()->getControllerPath());
QT2JS::SetUint(this->m_ozwstatus, "homeID", homeID);
this->sendStatusUpdate();
}
void mqttpublisher::driverFailed(quint32 homeID) {
qCDebug(ozwmp) << "Publishing Event driverFailed:" << homeID;
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "driverFailed");
QT2JS::SetUint(this->m_ozwstatus, "homeID", homeID);
this->sendStatusUpdate();
if (settings->value("StopOnFailure", false).toBool()) {
qCWarning(ozwmp) << "Exiting on Failure";
exit(-1);
}
}
void mqttpublisher::driverReset(quint32 homeID) {
qCDebug(ozwmp) << "Publishing Event driverReset:" << homeID;
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "driverReset");
QT2JS::SetUint(this->m_ozwstatus, "homeID", homeID);
this->sendStatusUpdate();
/* XXX TODO: Scan Nodes, Instances, CC and Value Lists and delete them */
}
void mqttpublisher::driverRemoved(quint32 homeID) {
qCDebug(ozwmp) << "Publishing Event driverRemoved:" << homeID;
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "driverRemoved");
QT2JS::SetUint(this->m_ozwstatus, "homeID", homeID);
this->sendStatusUpdate();
/* XXX TODO: Scan Nodes, Instances, CC and Value Lists and delete them */
}
void mqttpublisher::driverAllNodesQueriedSomeDead() {
qCDebug(ozwmp) << "Publishing Event driverAllNodesQueriedSomeDead:" ;
QT2JS::SetString(this->m_ozwstatus, "Status", "driverAllNodesQueriedSomeDead");
this->sendStatusUpdate();
}
void mqttpublisher::driverAllNodesQueried() {
qCDebug(ozwmp) << "Publishing Event driverAllNodesQueried:" ;
QT2JS::SetString(this->m_ozwstatus, "Status", "driverAllNodesQueried");
this->sendStatusUpdate();
}
void mqttpublisher::driverAwakeNodesQueried() {
qCDebug(ozwmp) << "Publishing Event driverAwakeNodesQueried:" ;
QT2JS::SetString(this->m_ozwstatus, "Status", "driverAwakeNodesQueried");
this->sendStatusUpdate();
}
void mqttpublisher::controllerCommand(quint8 node, NotificationTypes::QTOZW_Notification_Controller_Cmd command, NotificationTypes::QTOZW_Notification_Controller_State state, NotificationTypes::QTOZW_Notification_Controller_Error error) {
qCDebug(ozwmp) << "Publishing Event controllerCommand" << node << command << state << error;
rapidjson::Document js;
if (node > 0)
QT2JS::SetUint(js, "Node", node);
QMetaEnum metaEnum = QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_State>();
QT2JS::SetString(js, "State", metaEnum.valueToKey(state));
if (error != NotificationTypes::QTOZW_Notification_Controller_Error::Ctrl_Error_None) {
metaEnum = QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Error>();
QT2JS::SetString(js, "Error", metaEnum.valueToKey(error));
}
switch(command) {
case NotificationTypes::Ctrl_Cmd_None: {
qCWarning(ozwmp) << "Got a controllerCommand Event with no Controller Command" << command << state << error;
break;
}
case NotificationTypes::Ctrl_Cmd_AddNode: {
this->sendCommandUpdate("AddNode", js);
break;
}
case NotificationTypes::Ctrl_Cmd_AssignReturnRoute: {
this->sendCommandUpdate("AssignReturnRoute", js);
break;
}
case NotificationTypes::Ctrl_Cmd_CreateButton: {
QT2JS::SetString(js, "Command", QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Cmd>().valueToKey(command));
this->sendCommandUpdate("ControllerCommand", js);
break;
}
case NotificationTypes::Ctrl_Cmd_CreateNewPrimary: {
QT2JS::SetString(js, "Command", QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Cmd>().valueToKey(command));
this->sendCommandUpdate("ControllerCommand", js);
break;
}
case NotificationTypes::Ctrl_Cmd_DeleteAllReturnRoute: {
this->sendCommandUpdate("DeleteAllReturnRoute", js);
break;
}
case NotificationTypes::Ctrl_Cmd_DeleteButton: {
QT2JS::SetString(js, "Command", QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Cmd>().valueToKey(command));
this->sendCommandUpdate("ControllerCommand", js);
break;
}
case NotificationTypes::Ctrl_Cmd_HasNodeFailed: {
this->sendCommandUpdate("HasNodeFailed", js);
break;
}
case NotificationTypes::Ctrl_Cmd_ReceiveConfiguration: {
QT2JS::SetString(js, "Command", QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Cmd>().valueToKey(command));
this->sendCommandUpdate("ControllerCommand", js);
break;
}
case NotificationTypes::Ctrl_Cmd_RemoveFailedNode: {
this->sendCommandUpdate("RemoveFailedNode", js);
break;
}
case NotificationTypes::Ctrl_Cmd_RemoveNode: {
this->sendCommandUpdate("RemoveNode", js);
break;
}
case NotificationTypes::Ctrl_Cmd_ReplaceFailedNode: {
this->sendCommandUpdate("ReplaceFailedNode", js);
break;
}
case NotificationTypes::Ctrl_Cmd_ReplicationSend: {
QT2JS::SetString(js, "Command", QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Cmd>().valueToKey(command));
this->sendCommandUpdate("ControllerCommand", js);
break;
}
case NotificationTypes::Ctrl_Cmd_RequestNetworkUpdate: {
this->sendCommandUpdate("RequestNetworkUpdate", js);
break;
}
case NotificationTypes::Ctrl_Cmd_RequestNodeNeighborUpdate: {
this->sendCommandUpdate("RequestNodeNeighborUpdate", js);
break;
}
case NotificationTypes::Ctrl_Cmd_SendNodeInformation: {
this->sendCommandUpdate("SendNodeInformation", js);
break;
}
case NotificationTypes::Ctrl_Cmd_TransferPrimaryRole: {
QT2JS::SetString(js, "Command", QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Controller_Cmd>().valueToKey(command));
this->sendCommandUpdate("ControllerCommand", js);
break;
}
case NotificationTypes::Ctrl_Cmd_count: {
qWarning() << "Recieved controllerCommand for a unknown Command:" << command;
return;
}
};
}
void mqttpublisher::ozwNotification(quint8 node, NotificationTypes::QTOZW_Notification_Code event) {
qCDebug(ozwmp) << "Publishing Event ozwNotification";
rapidjson::Document js;
QMetaEnum metaEnum = QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_Code>();
QT2JS::SetUint(js, "Node", node);
QT2JS::SetString(js, "Event", metaEnum.valueToKey(event));
this->sendCommandUpdate("Notification", js);
}
void mqttpublisher::ozwUserAlert(quint8 node, NotificationTypes::QTOZW_Notification_User event, quint8 retry) {
qCDebug(ozwmp) << "Publishing Event ozwUserAlert";
rapidjson::Document js;
QMetaEnum metaEnum = QMetaEnum::fromType<NotificationTypes::QTOZW_Notification_User>();
QT2JS::SetUint(js, "Node", node);
QT2JS::SetString(js, "Event", metaEnum.valueToKey(event));
if (event == NotificationTypes::QTOZW_Notification_User::Notification_User_ApplicationStatus_Retry) {
QT2JS::SetUint(js, "Retry", retry);
}
this->sendCommandUpdate("UserAlert", js);
}
void mqttpublisher::manufacturerSpecificDBReady() {
qCDebug(ozwmp) << "Publishing Event manufacturerSpecificDBReady";
QT2JS::SetBool(this->m_ozwstatus, "ManufacturerSpecificDBReady", true);
this->sendStatusUpdate();
}
void mqttpublisher::starting() {
qCDebug(ozwmp) << "Publishing Event starting";
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "starting");
this->sendStatusUpdate();
}
void mqttpublisher::started(quint32 homeID) {
qCDebug(ozwmp) << "Publishing Event started";
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "started");
QT2JS::SetUint(this->m_ozwstatus, "homeID", homeID);
this->sendStatusUpdate();
this->m_statsTimer.start(settings->value("StatisticsUpdateInterval", 30000).toInt());
}
void mqttpublisher::stopped(quint32 homeID) {
qCDebug(ozwmp) << "Publishing Event stopped";
this->clearStatusUpdate();
QT2JS::SetString(this->m_ozwstatus, "Status", "stopped");
QT2JS::SetUint(this->m_ozwstatus, "homeID", homeID);
this->sendStatusUpdate();
this->m_statsTimer.stop();
}
//void error(QTOZWErrorCodes errorcode);
QTOZWManager *mqttpublisher::getQTOZWManager() {
return this->m_qtozwdeamon->getManager();
}
rapidjson::Document *mqttpublisher::getInstanceJSON(quint8 node, quint8 instance) {
if (this->m_instances.find(node) != this->m_instances.end()) {
if (this->m_instances[node].find(instance) != this->m_instances[node].end()) {
return this->m_instances[node][instance];
}
}
return nullptr;
}
rapidjson::Document *mqttpublisher::getCommandClassJSON(quint8 node, quint8 instance, quint8 cc) {
if (this->m_CommandClasses.find(node) != this->m_CommandClasses.end()) {
if (this->m_CommandClasses[node].find(instance) != this->m_CommandClasses[node].end()) {
if (this->m_CommandClasses[node][instance].find(cc) != this->m_CommandClasses[node][instance].end()) {
return this->m_CommandClasses[node][instance][cc];
}
}
}
return nullptr;
}