From 47f4b61364721b92257a345007ac35d56ab337ec Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Thu, 9 Jan 2020 11:36:48 +0800 Subject: [PATCH] add Association Management Commands (#14) * Add AddAssociation and RemoveAssociation Commands - This completes the Association Functionality --- docs/MQTT.md | 53 ++++++++++ .../qt-openzwave/qtozwassociationmodel.h | 4 + .../include/qt-openzwave/qtozwmanager.h | 2 + .../include/qt-openzwave/qtozwmanager.rep | 5 + qt-openzwave/include/qtozwmanager_p.h | 4 + qt-openzwave/source/qtozwassociationmodel.cpp | 34 +++++++ qt-openzwave/source/qtozwmanager.cpp | 7 ++ qt-openzwave/source/qtozwmanager_p.cpp | 99 +++++++++++++++++++ qt-ozwdaemon/mqttAssociations.cpp | 1 - qt-ozwdaemon/mqttcommands/addAssociation.cpp | 19 ++++ qt-ozwdaemon/mqttcommands/addAssociation.h | 17 ++++ qt-ozwdaemon/mqttcommands/mqttcommands.cpp | 4 + .../mqttcommands/removeAssociation.cpp | 19 ++++ qt-ozwdaemon/mqttcommands/removeAssociation.h | 17 ++++ qt-ozwdaemon/mqttpublisher.cpp | 2 +- qt-ozwdaemon/qt-ozwdaemon.pro | 9 +- 16 files changed, 292 insertions(+), 4 deletions(-) create mode 100644 qt-ozwdaemon/mqttcommands/addAssociation.cpp create mode 100644 qt-ozwdaemon/mqttcommands/addAssociation.h create mode 100644 qt-ozwdaemon/mqttcommands/removeAssociation.cpp create mode 100644 qt-ozwdaemon/mqttcommands/removeAssociation.h diff --git a/docs/MQTT.md b/docs/MQTT.md index fb42603..a683308 100755 --- a/docs/MQTT.md +++ b/docs/MQTT.md @@ -1177,3 +1177,56 @@ This allows a MQTT Client to set a value on a Device. As OZW supports many diffe ​ [testNetwork](#testNetwork) +## addAssociation + +**Params**: + +​ "node" - uint8: The NodeID to test + +​ "group" - uint8: The Association Group you wish to add a member to + + "target" - string: The Target Node you wish to send notifications to for this group. + +**Returns**: + +​"addAssociation" - if OZW accepted the command + +**Notification**: + +​topic "nodeGroupChanged" Indicates the Memberships was successfully refreshed from the device + +**Notes**: + + Note - The Target string can be specified as a single NodeID (eg, "1" or "1.0") or a NodeID​ and Instance as ".". This allows you to configure a device to send a notification to a different instance on Multi Channel Devices. You should ensure that the target device has a valid instance and supports the commands it will recieve. + If you specify a instance (other than 0) for a target node, but the group does not support sending MultiChannel messages, the instance will be removed (or set to 0, which indicates the root node). + +**See Also**: + +​ [removeAssociation](#removeAssociation) + +## removeAssociation + +**Params**: + +​ "node" - uint8: The NodeID to test + +​ "group" - uint8: The Association Group you wish to add a member to + + "target" - string: The Target Node you wish remove from the Group Membership List. + +**Returns**: + +​"removeAssociation" - if OZW accepted the command + +**Notification**: + +​topic "nodeGroupChanged" Indicates the Memberships was successfully refreshed from the device + +**Notes**: + + Note - The Target string can be specified as a single NodeID (eg, "1" or "1.0") or a NodeID​ and Instance as ".". You should first check the membership list via the Association Topics for the device and only attempt to remove existing members. Attempting to remove a member that does exist will result in a error. + +**See Also**: + +​ [addAssociation](#addAssociation) + diff --git a/qt-openzwave/include/qt-openzwave/qtozwassociationmodel.h b/qt-openzwave/include/qt-openzwave/qtozwassociationmodel.h index 5becd31..af13a59 100644 --- a/qt-openzwave/include/qt-openzwave/qtozwassociationmodel.h +++ b/qt-openzwave/include/qt-openzwave/qtozwassociationmodel.h @@ -63,6 +63,10 @@ public: int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; +#if 0 + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; +#endif protected: QVariant getassocationData(quint8 _node, quint8 _groupIDX, QTOZW_Associations::associationColumns _column); diff --git a/qt-openzwave/include/qt-openzwave/qtozwmanager.h b/qt-openzwave/include/qt-openzwave/qtozwmanager.h index 5ff6a0e..5577c0a 100644 --- a/qt-openzwave/include/qt-openzwave/qtozwmanager.h +++ b/qt-openzwave/include/qt-openzwave/qtozwmanager.h @@ -141,6 +141,8 @@ public: bool refreshValue(quint64); + bool AddAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target); + bool RemoveAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target); /* Property Methods */ QDir OZWDatabasePath() { return this->m_ozwdatabasepath; } diff --git a/qt-openzwave/include/qt-openzwave/qtozwmanager.rep b/qt-openzwave/include/qt-openzwave/qtozwmanager.rep index 4945a6b..aa063f0 100644 --- a/qt-openzwave/include/qt-openzwave/qtozwmanager.rep +++ b/qt-openzwave/include/qt-openzwave/qtozwmanager.rep @@ -149,6 +149,11 @@ class QTOZWManager { SLOT(QString getControllerPath()) SLOT(bool refreshValue(quint64)) + SLOT(bool AddAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target)) + SLOT(bool RemoveAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target)) + + + SLOT(qint32 getPollInterval()) SLOT(void setPollInterval(qint32 interval, bool intervalBetweenPolls)) diff --git a/qt-openzwave/include/qtozwmanager_p.h b/qt-openzwave/include/qtozwmanager_p.h index 74401f3..5df2819 100644 --- a/qt-openzwave/include/qtozwmanager_p.h +++ b/qt-openzwave/include/qtozwmanager_p.h @@ -135,6 +135,10 @@ public Q_SLOTS: bool refreshValue(quint64); + bool AddAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target); + bool RemoveAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target); + + /* these slots are called from our OZWNotification Class. Applications should not call them */ void pvt_valueAdded(quint64 vidKey); void pvt_valueRemoved(quint64 vidKey); diff --git a/qt-openzwave/source/qtozwassociationmodel.cpp b/qt-openzwave/source/qtozwassociationmodel.cpp index 9d9a0cd..c457740 100644 --- a/qt-openzwave/source/qtozwassociationmodel.cpp +++ b/qt-openzwave/source/qtozwassociationmodel.cpp @@ -99,7 +99,41 @@ QVariant QTOZW_Associations::headerData(int section, Qt::Orientation orientation } return QVariant(); } +#if 0 +bool QTOZW_Associations::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role != Qt::EditRole) { + return false; + } + switch (static_cast(index.column())) { + case Members: +// if (this->m_valueData.at(index.row())[static_cast(index.column())] != value) { +// qCDebug(valueModel) << "setData Called for Row" << index.row() << " With Value" << value; +// this->m_valueData[index.row()][static_cast(index.column())] = value; +// QVector roles; +// roles << Qt::DisplayRole << QTOZW_UserRoles::ModelDataChanged; +// this->dataChanged(index, index, roles); +// } + break; + default: + qCWarning(valueModel) << "got a setData for a Column other than Value. Ignoring" << index.column(); + return false; + } + return true; +} +Qt::ItemFlags QTOZW_Associations::flags(const QModelIndex &index) const { + if (!index.isValid()) + return Qt::NoItemFlags; + switch (static_cast(index.column())) { + case Members: { + return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; + } + break; + default: + return QAbstractTableModel::flags(index); + } +} +#endif QVariant QTOZW_Associations::getassocationData(quint8 _node, quint8 _groupIDX, associationColumns _column) { int32_t row = this->getassocationRow(_node, _groupIDX); if (row >= 0) diff --git a/qt-openzwave/source/qtozwmanager.cpp b/qt-openzwave/source/qtozwmanager.cpp index 73b2f1f..4fcb8ce 100644 --- a/qt-openzwave/source/qtozwmanager.cpp +++ b/qt-openzwave/source/qtozwmanager.cpp @@ -453,6 +453,13 @@ void QTOZWManager::syncroniseNodeNeighbors(quint8 node) { bool QTOZWManager::refreshValue(quint64 vidKey) { CALL_DPTR_RTN(refreshValue(vidKey), bool); } +bool QTOZWManager::AddAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target) { + CALL_DPTR_RTN(AddAssociation(_nodeId, _groupIdx, target), bool); +} +bool QTOZWManager::RemoveAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target) { + CALL_DPTR_RTN(RemoveAssociation(_nodeId, _groupIdx, target), bool); +} + void QTOZWManager::setOZWDatabasePath(QDir path) { if (path.exists()) diff --git a/qt-openzwave/source/qtozwmanager_p.cpp b/qt-openzwave/source/qtozwmanager_p.cpp index 8f093e2..df7b9a8 100644 --- a/qt-openzwave/source/qtozwmanager_p.cpp +++ b/qt-openzwave/source/qtozwmanager_p.cpp @@ -841,6 +841,105 @@ bool QTOZWManager_Internal::refreshValue(quint64 vidKey) { return false; } +bool QTOZWManager_Internal::AddAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target) { + if (!this->checkHomeId()) + return false; + if (!this->checkNodeId(_nodeId)) + return false; + quint8 targetnode = 0; + quint8 targetinstance = 0; + QRegExp rx("^(\\d+)\\.(\\d+)$"); + if (target.contains(rx)) { + targetnode = rx.cap(1).toInt(); + targetinstance = rx.cap(2).toInt(); + qCDebug(manager) << "Got a AddAssociation with a instance value" << target << " node: " << targetnode << " instance: " << targetinstance; + } + rx.setPattern("^(\\d+)$"); + if (target.contains(rx)) { + targetnode = rx.cap(1).toInt(); + targetinstance = 0; + qCDebug(manager) << "Got a AddAssociation with no instance value" << target << " node: " << targetnode; + } + if (targetnode == 0) { + qCWarning(manager) << "Couldn't find a valid Target in AddAssociation: " << target; + return false; + } + if (targetinstance > 0) { + try { + if (!this->m_manager->IsMultiInstance(this->homeId(), _nodeId, _groupIdx)) { + qCWarning(manager) << "AddAssociation: Association Group " << _groupIdx << "for node " << _nodeId << "does not support instances but a instance was specified:" << target << ". Removing"; + targetinstance = 0; + } + } catch (OpenZWave::OZWException &e) { + emit this->error(QTOZWManagerErrorCodes::OZWException); + this->setErrorString(e.GetMsg().c_str()); + return false; + } + } + if (this->m_associationsModel->findAssociation(_nodeId, _groupIdx, targetnode, targetinstance)) { + qCWarning(manager) << "AddAssociation: Association Group " << _groupIdx << "for node " << _nodeId << "already has a Assocation to " << target; + return false; + } + try { + this->m_manager->AddAssociation(this->homeId(), _nodeId, _groupIdx, targetnode, targetinstance); + return true; + } catch (OpenZWave::OZWException &e) { + emit this->error(QTOZWManagerErrorCodes::OZWException); + this->setErrorString(e.GetMsg().c_str()); + } + return false; +} +bool QTOZWManager_Internal::RemoveAssociation (quint8 const _nodeId, quint8 const _groupIdx, QString const target) { + if (!this->checkHomeId()) + return false; + if (!this->checkNodeId(_nodeId)) + return false; + quint8 targetnode = 0; + quint8 targetinstance = 0; + QRegExp rx("^(\\d+)\\.(\\d+)$"); + if (target.contains(rx)) { + targetnode = rx.cap(1).toInt(); + targetinstance = rx.cap(2).toInt(); + } + rx.setPattern("^(\\d+)$"); + if (target.contains(rx)) { + targetnode = rx.cap(1).toInt(); + targetinstance = 0; + } + if (targetnode == 0) { + qCWarning(manager) << "Couldn't find a valid Target in RemoveAssocation: " << target; + return false; + } + if (targetinstance > 0) { + try { + if (!this->m_manager->IsMultiInstance(this->homeId(), _nodeId, _groupIdx)) { + qCWarning(manager) << "RemoveAssoication: Association Group " << _groupIdx << "for node " << _nodeId << "does not support instances but a instance was specified:" << target << ". Removing"; + targetinstance = 0; + } + } catch (OpenZWave::OZWException &e) { + emit this->error(QTOZWManagerErrorCodes::OZWException); + this->setErrorString(e.GetMsg().c_str()); + return false; + } + } + + if (!this->m_associationsModel->findAssociation(_nodeId, _groupIdx, targetnode, targetinstance)) { + qCWarning(manager) << "RemoveAssociation: Association Group " << _groupIdx << "for node " << _nodeId << "does not have a Assocation to " << target; + return false; + } + + + try { + this->m_manager->RemoveAssociation(this->homeId(), _nodeId, _groupIdx, targetnode, targetinstance); + return true; + } catch (OpenZWave::OZWException &e) { + emit this->error(QTOZWManagerErrorCodes::OZWException); + this->setErrorString(e.GetMsg().c_str()); + } + return false; +} + + QTOZW_Nodes *QTOZWManager_Internal::getNodeModel() { diff --git a/qt-ozwdaemon/mqttAssociations.cpp b/qt-ozwdaemon/mqttAssociations.cpp index 634dc45..29a0bc4 100644 --- a/qt-ozwdaemon/mqttAssociations.cpp +++ b/qt-ozwdaemon/mqttAssociations.cpp @@ -1,4 +1,3 @@ - #include "qt-openzwave/qtozwmanager.h" #include "mqttAssociations.h" #include "qtrj.h" diff --git a/qt-ozwdaemon/mqttcommands/addAssociation.cpp b/qt-ozwdaemon/mqttcommands/addAssociation.cpp new file mode 100644 index 0000000..262709e --- /dev/null +++ b/qt-ozwdaemon/mqttcommands/addAssociation.cpp @@ -0,0 +1,19 @@ +#include "mqttcommands/addAssociation.h" + +MqttCommand_AddAssociation::MqttCommand_AddAssociation(QObject *parent) : + MqttCommand(parent) +{ + this->m_requiredIntFields << "node" << "group"; + this->m_requiredStringFields << "target"; +} +MqttCommand* MqttCommand_AddAssociation::Create(QObject *parent) { + return new MqttCommand_AddAssociation(parent); +} + +bool MqttCommand_AddAssociation::processMessage(rapidjson::Document &msg) { + if (!this->checkNode(msg, "node")) { + return this->sendSimpleStatus(false, "Invalid Node Number"); + } + QTOZWManager *mgr = getOZWManager(); + return this->sendSimpleStatus(mgr->AddAssociation(msg["node"].GetUint(), msg["group"].GetUint(), msg["target"].GetString())); +} \ No newline at end of file diff --git a/qt-ozwdaemon/mqttcommands/addAssociation.h b/qt-ozwdaemon/mqttcommands/addAssociation.h new file mode 100644 index 0000000..99ce1ec --- /dev/null +++ b/qt-ozwdaemon/mqttcommands/addAssociation.h @@ -0,0 +1,17 @@ +#ifndef ADDASSOCIATION_H +#define ADDASSOCIATION_H + +#include "mqttcommands/mqttcommands.h" + +class MqttCommand_AddAssociation : public MqttCommand { + Q_OBJECT +public: + static MqttCommand *Create(QObject *parent = nullptr); + static QString StaticGetCommand() { return "AddAssociation";}; + QString GetCommand() override { return StaticGetCommand(); }; + bool processMessage(rapidjson::Document &) override; +private: + MqttCommand_AddAssociation(QObject *parent = nullptr); +}; + +#endif // PING_H \ No newline at end of file diff --git a/qt-ozwdaemon/mqttcommands/mqttcommands.cpp b/qt-ozwdaemon/mqttcommands/mqttcommands.cpp index a572890..3fc4b53 100644 --- a/qt-ozwdaemon/mqttcommands/mqttcommands.cpp +++ b/qt-ozwdaemon/mqttcommands/mqttcommands.cpp @@ -38,6 +38,8 @@ #include "mqttcommands/syncroniseNodeNeighbors.h" #include "mqttcommands/enablePoll.h" #include "mqttcommands/refreshValue.h" +#include "mqttcommands/addAssociation.h" +#include "mqttcommands/removeAssociation.h" Q_LOGGING_CATEGORY(ozwmc, "ozw.mqtt.commands"); @@ -251,6 +253,8 @@ void MqttCommands::setupCommands() { this->Register(MqttCommand_SyncroniseNodeNeighbors::StaticGetCommand(), &MqttCommand_SyncroniseNodeNeighbors::Create); // this->Register(MqttCommand_EnablePoll::StaticGetCommand(), &MqttCommand_EnablePoll::Create); this->Register(MqttCommand_RefreshValue::StaticGetCommand(), &MqttCommand_RefreshValue::Create); + this->Register(MqttCommand_AddAssociation::StaticGetCommand(), &MqttCommand_AddAssociation::Create); + this->Register(MqttCommand_RemoveAssociation::StaticGetCommand(), &MqttCommand_RemoveAssociation::Create); } void MqttCommands::setupSubscriptions(QMqttClient *mqttclient, QString topTopic) { diff --git a/qt-ozwdaemon/mqttcommands/removeAssociation.cpp b/qt-ozwdaemon/mqttcommands/removeAssociation.cpp new file mode 100644 index 0000000..d93f19a --- /dev/null +++ b/qt-ozwdaemon/mqttcommands/removeAssociation.cpp @@ -0,0 +1,19 @@ +#include "mqttcommands/removeAssociation.h" + +MqttCommand_RemoveAssociation::MqttCommand_RemoveAssociation(QObject *parent) : + MqttCommand(parent) +{ + this->m_requiredIntFields << "node" << "group"; + this->m_requiredStringFields << "target"; +} +MqttCommand* MqttCommand_RemoveAssociation::Create(QObject *parent) { + return new MqttCommand_RemoveAssociation(parent); +} + +bool MqttCommand_RemoveAssociation::processMessage(rapidjson::Document &msg) { + if (!this->checkNode(msg, "node")) { + return this->sendSimpleStatus(false, "Invalid Node Number"); + } + QTOZWManager *mgr = getOZWManager(); + return this->sendSimpleStatus(mgr->RemoveAssociation(msg["node"].GetUint(), msg["group"].GetUint(), msg["target"].GetString())); +} \ No newline at end of file diff --git a/qt-ozwdaemon/mqttcommands/removeAssociation.h b/qt-ozwdaemon/mqttcommands/removeAssociation.h new file mode 100644 index 0000000..e1c087a --- /dev/null +++ b/qt-ozwdaemon/mqttcommands/removeAssociation.h @@ -0,0 +1,17 @@ +#ifndef REMOVEASSOCIATION_H +#define REMOVEASSOCIATION_H + +#include "mqttcommands/mqttcommands.h" + +class MqttCommand_RemoveAssociation : public MqttCommand { + Q_OBJECT +public: + static MqttCommand *Create(QObject *parent = nullptr); + static QString StaticGetCommand() { return "RemoveAssociation";}; + QString GetCommand() override { return StaticGetCommand(); }; + bool processMessage(rapidjson::Document &) override; +private: + MqttCommand_RemoveAssociation(QObject *parent = nullptr); +}; + +#endif // PING_H \ No newline at end of file diff --git a/qt-ozwdaemon/mqttpublisher.cpp b/qt-ozwdaemon/mqttpublisher.cpp index 7da2e37..6a71ad4 100644 --- a/qt-ozwdaemon/mqttpublisher.cpp +++ b/qt-ozwdaemon/mqttpublisher.cpp @@ -380,7 +380,7 @@ void mqttpublisher::sendCommandUpdate(QString command, rapidjson::Document &js) 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, false); + this->m_client->publish(QMqttTopicName(getAssociationTopic(node, group)), QT2JS::getJSON(js), 0, true); return; } diff --git a/qt-ozwdaemon/qt-ozwdaemon.pro b/qt-ozwdaemon/qt-ozwdaemon.pro index ae71cc4..622d718 100644 --- a/qt-ozwdaemon/qt-ozwdaemon.pro +++ b/qt-ozwdaemon/qt-ozwdaemon.pro @@ -59,7 +59,9 @@ qtHaveModule(mqtt) { mqttcommands/setPollInterval.cpp \ mqttcommands/getPollINterval.cpp \ mqttcommands/syncroniseNodeNeighbors.cpp \ - mqttcommands/refreshValue.cpp + mqttcommands/refreshValue.cpp \ + mqttcommands/addAssociation.cpp \ + mqttcommands/removeAssociation.cpp HEADERS += mqttpublisher.h \ qtrj.h \ @@ -101,7 +103,10 @@ qtHaveModule(mqtt) { mqttcommands/setPollInterval.h \ mqttcommands/getPollInterval.h\ mqttcommands/syncroniseNodeNeighbors.h \ - mqttcommands/refreshValue.h + mqttcommands/refreshValue.h \ + mqttcommands/addAssociation.h \ + mqttcommands/removeAssociation.h + } else { warning("MQTT Qt Module Not Found. Not Building MQTT Client Capabilities")