Add a zigbee network topology map
This commit is contained in:
parent
8876a8635a
commit
a7a3c15abe
@ -85,7 +85,7 @@ NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
|
|||||||
|
|
||||||
updateActiveBearers();
|
updateActiveBearers();
|
||||||
|
|
||||||
m_reconnectTimer.setInterval(100);
|
m_reconnectTimer.setInterval(500);
|
||||||
m_reconnectTimer.setSingleShot(true);
|
m_reconnectTimer.setSingleShot(true);
|
||||||
connect(&m_reconnectTimer, &QTimer::timeout, this, [this](){
|
connect(&m_reconnectTimer, &QTimer::timeout, this, [this](){
|
||||||
if (m_currentHost && !m_currentTransport) {
|
if (m_currentHost && !m_currentTransport) {
|
||||||
|
|||||||
@ -49,9 +49,9 @@ TunnelProxyTransport::TunnelProxyTransport(QObject *parent) :
|
|||||||
QObject::connect(m_remoteConnection, &TunnelProxyRemoteConnection::dataReady, this, &TunnelProxyTransport::dataReady);
|
QObject::connect(m_remoteConnection, &TunnelProxyRemoteConnection::dataReady, this, &TunnelProxyTransport::dataReady);
|
||||||
QObject::connect(m_remoteConnection, &TunnelProxyRemoteConnection::errorOccurred, this, &TunnelProxyTransport::onRemoteConnectionErrorOccurred);
|
QObject::connect(m_remoteConnection, &TunnelProxyRemoteConnection::errorOccurred, this, &TunnelProxyTransport::onRemoteConnectionErrorOccurred);
|
||||||
QObject::connect(m_remoteConnection, &TunnelProxyRemoteConnection::sslErrors, this, [=](const QList<QSslError> &errors){
|
QObject::connect(m_remoteConnection, &TunnelProxyRemoteConnection::sslErrors, this, [=](const QList<QSslError> &errors){
|
||||||
qWarning() << "Remote tunnel proxy server SSL errors occurred:";
|
qCWarning(dcTunnelProxyRemoteConnectionDummy) << "Remote tunnel proxy server SSL errors occurred:";
|
||||||
foreach (const QSslError &sslError, errors) {
|
foreach (const QSslError &sslError, errors) {
|
||||||
qWarning() << " --> " << sslError.errorString();
|
qCWarning(dcTunnelProxyRemoteConnectionDummy) << " --> " << sslError.errorString();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -67,8 +67,6 @@ bool TunnelProxyTransport::connect(const QUrl &url)
|
|||||||
serverUrl.setPort(url.port());
|
serverUrl.setPort(url.port());
|
||||||
QUuid serverUuid = QUrlQuery(url).queryItemValue("uuid");
|
QUuid serverUuid = QUrlQuery(url).queryItemValue("uuid");
|
||||||
|
|
||||||
qCritical() << "Calling connect on" << serverUrl << serverUuid;
|
|
||||||
|
|
||||||
return m_remoteConnection->connectServer(serverUrl, serverUuid);
|
return m_remoteConnection->connectServer(serverUrl, serverUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +135,7 @@ void TunnelProxyTransport::onRemoteConnectionStateChanged(remoteproxyclient::Tun
|
|||||||
|
|
||||||
void TunnelProxyTransport::onRemoteConnectionErrorOccurred(QAbstractSocket::SocketError error)
|
void TunnelProxyTransport::onRemoteConnectionErrorOccurred(QAbstractSocket::SocketError error)
|
||||||
{
|
{
|
||||||
qWarning() << "Tunnel proxy socket error occurred" << error;
|
qCWarning(dcTunnelProxyRemoteConnectionDummy) << "Tunnel proxy socket error occurred" << error;
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaTransportInterface *TunnelProxyTransportFactory::createTransport(QObject *parent) const
|
NymeaTransportInterface *TunnelProxyTransportFactory::createTransport(QObject *parent) const
|
||||||
|
|||||||
@ -329,6 +329,7 @@ void registerQmlTypes() {
|
|||||||
qmlRegisterUncreatableType<ZigbeeNetwork>(uri, 1, 0, "ZigbeeNetwork", "Get it from the ZigbeeManager");
|
qmlRegisterUncreatableType<ZigbeeNetwork>(uri, 1, 0, "ZigbeeNetwork", "Get it from the ZigbeeManager");
|
||||||
qmlRegisterUncreatableType<ZigbeeNetworks>(uri, 1, 0, "ZigbeeNetworks", "Get it from the ZigbeeManager");
|
qmlRegisterUncreatableType<ZigbeeNetworks>(uri, 1, 0, "ZigbeeNetworks", "Get it from the ZigbeeManager");
|
||||||
qmlRegisterUncreatableType<ZigbeeNode>(uri, 1, 0, "ZigbeeNode", "Get it from the ZigbeeNodes");
|
qmlRegisterUncreatableType<ZigbeeNode>(uri, 1, 0, "ZigbeeNode", "Get it from the ZigbeeNodes");
|
||||||
|
qmlRegisterUncreatableType<ZigbeeNodeNeighbor>(uri, 1, 0, "ZigbeeNodeNeighbor", "Get it from the ZigbeeNode");
|
||||||
qmlRegisterUncreatableType<ZigbeeNodes>(uri, 1, 0, "ZigbeeNodes", "Get it from the ZigbeeNetwork");
|
qmlRegisterUncreatableType<ZigbeeNodes>(uri, 1, 0, "ZigbeeNodes", "Get it from the ZigbeeNetwork");
|
||||||
qmlRegisterType<ZigbeeNodesProxy>(uri, 1, 0, "ZigbeeNodesProxy");
|
qmlRegisterType<ZigbeeNodesProxy>(uri, 1, 0, "ZigbeeNodesProxy");
|
||||||
|
|
||||||
|
|||||||
@ -231,7 +231,7 @@ void ZigbeeManager::factoryResetNetworkResponse(int commandId, const QVariantMap
|
|||||||
|
|
||||||
void ZigbeeManager::getNodesResponse(int commandId, const QVariantMap ¶ms)
|
void ZigbeeManager::getNodesResponse(int commandId, const QVariantMap ¶ms)
|
||||||
{
|
{
|
||||||
qCDebug(dcZigbee()) << "Zigbee get nodes response" << commandId << params;
|
qCDebug(dcZigbee()) << "Zigbee get nodes response" << commandId << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||||
|
|
||||||
foreach (const QVariant &nodeVariant, params.value("zigbeeNodes").toList()) {
|
foreach (const QVariant &nodeVariant, params.value("zigbeeNodes").toList()) {
|
||||||
QVariantMap nodeMap = nodeVariant.toMap();
|
QVariantMap nodeMap = nodeVariant.toMap();
|
||||||
@ -254,7 +254,7 @@ void ZigbeeManager::removeNodeResponse(int commandId, const QVariantMap ¶ms)
|
|||||||
|
|
||||||
void ZigbeeManager::notificationReceived(const QVariantMap ¬ification)
|
void ZigbeeManager::notificationReceived(const QVariantMap ¬ification)
|
||||||
{
|
{
|
||||||
qCDebug(dcZigbee()) << "Zigbee notification" << qUtf8Printable(QJsonDocument::fromVariant(notification).toJson());
|
// qCDebug(dcZigbee()) << "Zigbee notification" << qUtf8Printable(QJsonDocument::fromVariant(notification).toJson());
|
||||||
QString notificationString = notification.value("notification").toString();
|
QString notificationString = notification.value("notification").toString();
|
||||||
if (notificationString == "Zigbee.AdapterAdded") {
|
if (notificationString == "Zigbee.AdapterAdded") {
|
||||||
QVariantMap adapterMap = notification.value("params").toMap().value("adapter").toMap();
|
QVariantMap adapterMap = notification.value("params").toMap().value("adapter").toMap();
|
||||||
@ -360,7 +360,7 @@ ZigbeeNode *ZigbeeManager::unpackNode(const QVariantMap &nodeMap)
|
|||||||
QUuid networkUuid = nodeMap.value("networkUuid").toUuid();
|
QUuid networkUuid = nodeMap.value("networkUuid").toUuid();
|
||||||
QString ieeeAddress = nodeMap.value("ieeeAddress").toString();
|
QString ieeeAddress = nodeMap.value("ieeeAddress").toString();
|
||||||
ZigbeeNode *node = new ZigbeeNode(networkUuid, ieeeAddress, this);
|
ZigbeeNode *node = new ZigbeeNode(networkUuid, ieeeAddress, this);
|
||||||
node->updateNodeProperties(nodeMap);
|
updateNodeProperties(node, nodeMap);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,9 +386,35 @@ void ZigbeeManager::addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &n
|
|||||||
QString ieeeAddress = nodeMap.value("ieeeAddress").toString();
|
QString ieeeAddress = nodeMap.value("ieeeAddress").toString();
|
||||||
ZigbeeNode *node = network->nodes()->getNode(ieeeAddress);
|
ZigbeeNode *node = network->nodes()->getNode(ieeeAddress);
|
||||||
if (node) {
|
if (node) {
|
||||||
node->updateNodeProperties(nodeMap);
|
updateNodeProperties(node, nodeMap);
|
||||||
} else {
|
} else {
|
||||||
network->nodes()->addNode(unpackNode(nodeMap));
|
network->nodes()->addNode(unpackNode(nodeMap));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZigbeeManager::updateNodeProperties(ZigbeeNode *node, const QVariantMap &nodeMap)
|
||||||
|
{
|
||||||
|
node->setNetworkAddress(nodeMap.value("networkAddress").toUInt());
|
||||||
|
node->setType(ZigbeeNode::stringToNodeType(nodeMap.value("type").toString()));
|
||||||
|
node->setState(ZigbeeNode::stringToNodeState(nodeMap.value("state").toString()));
|
||||||
|
node->setManufacturer(nodeMap.value("manufacturer").toString());
|
||||||
|
node->setModel(nodeMap.value("model").toString());
|
||||||
|
node->setVersion(nodeMap.value("version").toString());
|
||||||
|
node->setRxOnWhenIdle(nodeMap.value("receiverOnWhileIdle").toBool());
|
||||||
|
node->setReachable(nodeMap.value("reachable").toBool());
|
||||||
|
node->setLqi(nodeMap.value("lqi").toUInt());
|
||||||
|
node->setLastSeen(QDateTime::fromMSecsSinceEpoch(nodeMap.value("lastSeen").toUInt() * 1000));
|
||||||
|
QList<quint16> neighbors;
|
||||||
|
foreach (const QVariant &neighbor, nodeMap.value("neighborTableRecords").toList()) {
|
||||||
|
QVariantMap neighborMap = neighbor.toMap();
|
||||||
|
quint16 networkAddress = neighborMap.value("networkAddress").toUInt();
|
||||||
|
// qWarning() << "*********** adding neighbor" << networkAddress;
|
||||||
|
QMetaEnum relationshipEnum = QMetaEnum::fromType<ZigbeeNode::ZigbeeNodeRelationship>();
|
||||||
|
ZigbeeNode::ZigbeeNodeRelationship relationship = static_cast<ZigbeeNode::ZigbeeNodeRelationship>(relationshipEnum.keyToValue(neighborMap.value("relationship").toByteArray().data()));
|
||||||
|
quint8 lqi = neighborMap.value("lqi").toUInt();
|
||||||
|
quint8 depth = neighborMap.value("depth").toUInt();
|
||||||
|
node->addOrUpdateNeighbor(networkAddress, relationship, lqi, depth);
|
||||||
|
neighbors.append(networkAddress);
|
||||||
|
}
|
||||||
|
node->commitNeighbors(neighbors);
|
||||||
|
}
|
||||||
|
|||||||
@ -133,6 +133,7 @@ private:
|
|||||||
|
|
||||||
void fillNetworkData(ZigbeeNetwork *network, const QVariantMap &networkMap);
|
void fillNetworkData(ZigbeeNetwork *network, const QVariantMap &networkMap);
|
||||||
void addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &nodeMap);
|
void addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &nodeMap);
|
||||||
|
void updateNodeProperties(ZigbeeNode *node, const QVariantMap &nodeMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZIGBEEMANAGER_H
|
#endif // ZIGBEEMANAGER_H
|
||||||
|
|||||||
@ -189,6 +189,51 @@ void ZigbeeNode::setLastSeen(const QDateTime &lastSeen)
|
|||||||
emit lastSeenChanged(m_lastSeen);
|
emit lastSeenChanged(m_lastSeen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<ZigbeeNodeNeighbor *> ZigbeeNode::neighbors() const
|
||||||
|
{
|
||||||
|
return m_neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth)
|
||||||
|
{
|
||||||
|
foreach (ZigbeeNodeNeighbor *neighbor, m_neighbors) {
|
||||||
|
if (neighbor->networkAddress() == networkAddress) {
|
||||||
|
if (neighbor->relationship() != relationship) {
|
||||||
|
neighbor->setRelationship(relationship);
|
||||||
|
m_neighborsDirty = true;
|
||||||
|
}
|
||||||
|
if (neighbor->lqi() != lqi) {
|
||||||
|
neighbor->setLqi(lqi);
|
||||||
|
m_neighborsDirty = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ZigbeeNodeNeighbor *neighbor = new ZigbeeNodeNeighbor(networkAddress, this);
|
||||||
|
neighbor->setRelationship(relationship);
|
||||||
|
neighbor->setLqi(lqi);
|
||||||
|
neighbor->setDepth(depth);
|
||||||
|
m_neighbors.append(neighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZigbeeNode::commitNeighbors(QList<quint16> toBeKept)
|
||||||
|
{
|
||||||
|
QMutableListIterator<ZigbeeNodeNeighbor*> iter(m_neighbors);
|
||||||
|
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
ZigbeeNodeNeighbor *neighbor = iter.next();
|
||||||
|
if (!toBeKept.contains(neighbor->networkAddress())) {
|
||||||
|
iter.remove();
|
||||||
|
neighbor->deleteLater();
|
||||||
|
m_neighborsDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_neighborsDirty) {
|
||||||
|
emit neighborsChanged();
|
||||||
|
m_neighborsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ZigbeeNode::ZigbeeNodeState ZigbeeNode::stringToNodeState(const QString &nodeState)
|
ZigbeeNode::ZigbeeNodeState ZigbeeNode::stringToNodeState(const QString &nodeState)
|
||||||
{
|
{
|
||||||
if (nodeState == "ZigbeeNodeStateUninitialized") {
|
if (nodeState == "ZigbeeNodeStateUninitialized") {
|
||||||
@ -213,16 +258,53 @@ ZigbeeNode::ZigbeeNodeType ZigbeeNode::stringToNodeType(const QString &nodeType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZigbeeNode::updateNodeProperties(const QVariantMap &nodeMap)
|
ZigbeeNodeNeighbor::ZigbeeNodeNeighbor(quint16 networkAddress, QObject *parent):
|
||||||
|
QObject(parent),
|
||||||
|
m_networkAddress(networkAddress)
|
||||||
{
|
{
|
||||||
setNetworkAddress(nodeMap.value("networkAddress").toUInt());
|
|
||||||
setType(ZigbeeNode::stringToNodeType(nodeMap.value("type").toString()));
|
}
|
||||||
setState(ZigbeeNode::stringToNodeState(nodeMap.value("state").toString()));
|
|
||||||
setManufacturer(nodeMap.value("manufacturer").toString());
|
quint16 ZigbeeNodeNeighbor::networkAddress() const
|
||||||
setModel(nodeMap.value("model").toString());
|
{
|
||||||
setVersion(nodeMap.value("version").toString());
|
return m_networkAddress;
|
||||||
setRxOnWhenIdle(nodeMap.value("receiverOnWhileIdle").toBool());
|
}
|
||||||
setReachable(nodeMap.value("reachable").toBool());
|
|
||||||
setLqi(nodeMap.value("lqi").toUInt());
|
ZigbeeNode::ZigbeeNodeRelationship ZigbeeNodeNeighbor::relationship() const
|
||||||
setLastSeen(QDateTime::fromMSecsSinceEpoch(nodeMap.value("lastSeen").toUInt() * 1000));
|
{
|
||||||
|
return m_relationship;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZigbeeNodeNeighbor::setRelationship(ZigbeeNode::ZigbeeNodeRelationship relationship)
|
||||||
|
{
|
||||||
|
if (m_relationship != relationship) {
|
||||||
|
m_relationship = relationship;
|
||||||
|
emit relationshipChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quint8 ZigbeeNodeNeighbor::lqi() const
|
||||||
|
{
|
||||||
|
return m_lqi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZigbeeNodeNeighbor::setLqi(quint8 lqi)
|
||||||
|
{
|
||||||
|
if (m_lqi != lqi) {
|
||||||
|
m_lqi = lqi;
|
||||||
|
emit lqiChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quint8 ZigbeeNodeNeighbor::depth() const
|
||||||
|
{
|
||||||
|
return m_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZigbeeNodeNeighbor::setDepth(quint8 depth)
|
||||||
|
{
|
||||||
|
if (m_depth != depth) {
|
||||||
|
m_depth = depth;
|
||||||
|
emit depthChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,8 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
class ZigbeeNodeNeighbor;
|
||||||
|
|
||||||
class ZigbeeNode : public QObject
|
class ZigbeeNode : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -51,6 +53,7 @@ class ZigbeeNode : public QObject
|
|||||||
Q_PROPERTY(bool reachable READ reachable WRITE setReachable NOTIFY reachableChanged)
|
Q_PROPERTY(bool reachable READ reachable WRITE setReachable NOTIFY reachableChanged)
|
||||||
Q_PROPERTY(uint lqi READ lqi WRITE setLqi NOTIFY lqiChanged)
|
Q_PROPERTY(uint lqi READ lqi WRITE setLqi NOTIFY lqiChanged)
|
||||||
Q_PROPERTY(QDateTime lastSeen READ lastSeen WRITE setLastSeen NOTIFY lastSeenChanged)
|
Q_PROPERTY(QDateTime lastSeen READ lastSeen WRITE setLastSeen NOTIFY lastSeenChanged)
|
||||||
|
Q_PROPERTY(QList<ZigbeeNodeNeighbor*> neighbors READ neighbors NOTIFY neighborsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ZigbeeNodeType {
|
enum ZigbeeNodeType {
|
||||||
@ -68,6 +71,15 @@ public:
|
|||||||
};
|
};
|
||||||
Q_ENUM(ZigbeeNodeState)
|
Q_ENUM(ZigbeeNodeState)
|
||||||
|
|
||||||
|
enum ZigbeeNodeRelationship {
|
||||||
|
ZigbeeNodeRelationshipParent,
|
||||||
|
ZigbeeNodeRelationshipChild,
|
||||||
|
ZigbeeNodeRelationshipSibling,
|
||||||
|
ZigbeeNodeRelationshipNone,
|
||||||
|
ZigbeeNodeRelationshipPreviousChild
|
||||||
|
};
|
||||||
|
Q_ENUM(ZigbeeNodeRelationship)
|
||||||
|
|
||||||
explicit ZigbeeNode(const QUuid &networkUuid, const QString &ieeeAddress, QObject *parent = nullptr);
|
explicit ZigbeeNode(const QUuid &networkUuid, const QString &ieeeAddress, QObject *parent = nullptr);
|
||||||
|
|
||||||
QUuid networkUuid() const;
|
QUuid networkUuid() const;
|
||||||
@ -103,11 +115,13 @@ public:
|
|||||||
QDateTime lastSeen() const;
|
QDateTime lastSeen() const;
|
||||||
void setLastSeen(const QDateTime &lastSeen);
|
void setLastSeen(const QDateTime &lastSeen);
|
||||||
|
|
||||||
|
QList<ZigbeeNodeNeighbor*> neighbors() const;
|
||||||
|
void addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth);
|
||||||
|
void commitNeighbors(QList<quint16> toBeKept);
|
||||||
|
|
||||||
static ZigbeeNodeState stringToNodeState(const QString &nodeState);
|
static ZigbeeNodeState stringToNodeState(const QString &nodeState);
|
||||||
static ZigbeeNodeType stringToNodeType(const QString &nodeType);
|
static ZigbeeNodeType stringToNodeType(const QString &nodeType);
|
||||||
|
|
||||||
void updateNodeProperties(const QVariantMap &nodeMap);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void networkAddressChanged(quint16 networkAddress);
|
void networkAddressChanged(quint16 networkAddress);
|
||||||
void typeChanged(ZigbeeNodeType type);
|
void typeChanged(ZigbeeNodeType type);
|
||||||
@ -119,6 +133,7 @@ signals:
|
|||||||
void reachableChanged(bool reachable);
|
void reachableChanged(bool reachable);
|
||||||
void lqiChanged(uint lqi);
|
void lqiChanged(uint lqi);
|
||||||
void lastSeenChanged(const QDateTime &lastSeen);
|
void lastSeenChanged(const QDateTime &lastSeen);
|
||||||
|
void neighborsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUuid m_networkUuid;
|
QUuid m_networkUuid;
|
||||||
@ -133,6 +148,42 @@ private:
|
|||||||
bool m_reachable = false;
|
bool m_reachable = false;
|
||||||
uint m_lqi = 0;
|
uint m_lqi = 0;
|
||||||
QDateTime m_lastSeen;
|
QDateTime m_lastSeen;
|
||||||
|
QList<ZigbeeNodeNeighbor*> m_neighbors;
|
||||||
|
bool m_neighborsDirty = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ZigbeeNodeNeighbor: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(quint16 networkAddress READ networkAddress CONSTANT)
|
||||||
|
Q_PROPERTY(ZigbeeNode::ZigbeeNodeRelationship relationship READ relationship NOTIFY relationshipChanged)
|
||||||
|
Q_PROPERTY(quint8 lqi READ lqi NOTIFY lqiChanged)
|
||||||
|
Q_PROPERTY(quint8 depth READ depth NOTIFY depthChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
ZigbeeNodeNeighbor(quint16 networkAddress, QObject *parent);
|
||||||
|
|
||||||
|
quint16 networkAddress() const;
|
||||||
|
|
||||||
|
ZigbeeNode::ZigbeeNodeRelationship relationship() const;
|
||||||
|
void setRelationship(ZigbeeNode::ZigbeeNodeRelationship relationship);
|
||||||
|
|
||||||
|
quint8 lqi() const;
|
||||||
|
void setLqi(quint8 lqi);
|
||||||
|
|
||||||
|
quint8 depth() const;
|
||||||
|
void setDepth(quint8 depth);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void relationshipChanged();
|
||||||
|
void lqiChanged();
|
||||||
|
void depthChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
quint16 m_networkAddress;
|
||||||
|
ZigbeeNode::ZigbeeNodeRelationship m_relationship;
|
||||||
|
quint8 m_lqi = 0;
|
||||||
|
quint8 m_depth = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZIGBEENODE_H
|
#endif // ZIGBEENODE_H
|
||||||
|
|||||||
@ -191,3 +191,13 @@ ZigbeeNode *ZigbeeNodes::getNode(const QString &ieeeAddress) const
|
|||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ZigbeeNode *ZigbeeNodes::getNodeByNetworkAddress(quint16 networkAddress) const
|
||||||
|
{
|
||||||
|
foreach (ZigbeeNode *node, m_nodes) {
|
||||||
|
if (node->networkAddress() == networkAddress) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|||||||
@ -72,6 +72,7 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE virtual ZigbeeNode *get(int index) const;
|
Q_INVOKABLE virtual ZigbeeNode *get(int index) const;
|
||||||
Q_INVOKABLE ZigbeeNode *getNode(const QString &ieeeAddress) const;
|
Q_INVOKABLE ZigbeeNode *getNode(const QString &ieeeAddress) const;
|
||||||
|
Q_INVOKABLE ZigbeeNode *getNodeByNetworkAddress(quint16 networkAddress) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void countChanged();
|
void countChanged();
|
||||||
|
|||||||
@ -46,6 +46,7 @@ class ZigbeeNodesProxy : public QSortFilterProxyModel
|
|||||||
Q_PROPERTY(bool showCoordinator READ showCoordinator WRITE setShowCoordinator NOTIFY showCoordinatorChanged)
|
Q_PROPERTY(bool showCoordinator READ showCoordinator WRITE setShowCoordinator NOTIFY showCoordinatorChanged)
|
||||||
Q_PROPERTY(bool showOnline READ showOnline WRITE setShowOnline NOTIFY showOnlineChanged)
|
Q_PROPERTY(bool showOnline READ showOnline WRITE setShowOnline NOTIFY showOnlineChanged)
|
||||||
Q_PROPERTY(bool showOffline READ showOffline WRITE setShowOffline NOTIFY showOfflineChanged)
|
Q_PROPERTY(bool showOffline READ showOffline WRITE setShowOffline NOTIFY showOfflineChanged)
|
||||||
|
// Q_PROPERTY(quint16 filterByParentNeighbor READ filterByParentNeighbor WRITE setFilterByParentNeighbor NOTIFY filterByParentNeighborChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool newOnTop READ newOnTop WRITE setNewOnTop NOTIFY newOnTopChanged)
|
Q_PROPERTY(bool newOnTop READ newOnTop WRITE setNewOnTop NOTIFY newOnTopChanged)
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ private:
|
|||||||
bool m_showOffline = true;
|
bool m_showOffline = true;
|
||||||
|
|
||||||
bool m_newOnTop = false;
|
bool m_newOnTop = false;
|
||||||
|
bool m_sortByRelationship = false;
|
||||||
|
|
||||||
QHash<ZigbeeNode*, QDateTime> m_newNodes;
|
QHash<ZigbeeNode*, QDateTime> m_newNodes;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,6 @@ TEMPLATE=subdirs
|
|||||||
include(shared.pri)
|
include(shared.pri)
|
||||||
message("APP_VERSION: $${APP_VERSION} ($${APP_REVISION})")
|
message("APP_VERSION: $${APP_VERSION} ($${APP_REVISION})")
|
||||||
|
|
||||||
|
|
||||||
SUBDIRS = libnymea-app nymea-app
|
SUBDIRS = libnymea-app nymea-app
|
||||||
nymea-app.depends = libnymea-app
|
nymea-app.depends = libnymea-app
|
||||||
|
|
||||||
|
|||||||
@ -210,9 +210,9 @@
|
|||||||
<file>ui/components/ThingStatusIcons.qml</file>
|
<file>ui/components/ThingStatusIcons.qml</file>
|
||||||
<file>ui/components/InfoPaneBase.qml</file>
|
<file>ui/components/InfoPaneBase.qml</file>
|
||||||
<file>ui/components/ThingInfoPane.qml</file>
|
<file>ui/components/ThingInfoPane.qml</file>
|
||||||
<file>ui/system/ZigbeeSettingsPage.qml</file>
|
<file>ui/system/zigbee/ZigbeeSettingsPage.qml</file>
|
||||||
<file>ui/system/ZigbeeAddNetworkPage.qml</file>
|
<file>ui/system/zigbee/ZigbeeAddNetworkPage.qml</file>
|
||||||
<file>ui/system/ZigbeeNetworkSettingsPage.qml</file>
|
<file>ui/system/zigbee/ZigbeeNetworkSettingsPage.qml</file>
|
||||||
<file>ui/MainMenu.qml</file>
|
<file>ui/MainMenu.qml</file>
|
||||||
<file>ui/components/NymeaItemDelegate.qml</file>
|
<file>ui/components/NymeaItemDelegate.qml</file>
|
||||||
<file>ui/components/NymeaSwipeDelegate.qml</file>
|
<file>ui/components/NymeaSwipeDelegate.qml</file>
|
||||||
@ -244,7 +244,8 @@
|
|||||||
<file>ui/system/ModbusRtuReconfigureMasterPage.qml</file>
|
<file>ui/system/ModbusRtuReconfigureMasterPage.qml</file>
|
||||||
<file>ui/connection/ConnectionWizard.qml</file>
|
<file>ui/connection/ConnectionWizard.qml</file>
|
||||||
<file>ui/components/WizardPageBase.qml</file>
|
<file>ui/components/WizardPageBase.qml</file>
|
||||||
<file>ui/system/ZigbeeNetworkPage.qml</file>
|
<file>ui/system/zigbee/ZigbeeNetworkPage.qml</file>
|
||||||
|
<file>ui/system/zigbee/ZigbeeTopologyPage.qml</file>
|
||||||
<file>ui/components/ConnectionInfoDialog.qml</file>
|
<file>ui/components/ConnectionInfoDialog.qml</file>
|
||||||
<file>ui/components/ButtonControls.qml</file>
|
<file>ui/components/ButtonControls.qml</file>
|
||||||
<file>ui/components/CircleBackground.qml</file>
|
<file>ui/components/CircleBackground.qml</file>
|
||||||
|
|||||||
@ -118,7 +118,7 @@ Page {
|
|||||||
text: qsTr("ZigBee")
|
text: qsTr("ZigBee")
|
||||||
subText: qsTr("Configure ZigBee networks")
|
subText: qsTr("Configure ZigBee networks")
|
||||||
visible: engine.jsonRpcClient.ensureServerVersion("5.3") && NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.zigbeeSettingsEnabled
|
visible: engine.jsonRpcClient.ensureServerVersion("5.3") && NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.zigbeeSettingsEnabled
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("system/ZigbeeSettingsPage.qml"))
|
onClicked: pageStack.push(Qt.resolvedUrl("system/zigbee/ZigbeeSettingsPage.qml"))
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsTile {
|
SettingsTile {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2022, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -33,7 +33,7 @@ import QtQuick.Controls 2.2
|
|||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import Nymea 1.0
|
import Nymea 1.0
|
||||||
|
|
||||||
import "../components"
|
import "qrc:/ui/components"
|
||||||
|
|
||||||
SettingsPageBase {
|
SettingsPageBase {
|
||||||
id: root
|
id: root
|
||||||
@ -56,7 +56,7 @@ SettingsPageBase {
|
|||||||
ColorIcon {
|
ColorIcon {
|
||||||
Layout.preferredHeight: Style.iconSize
|
Layout.preferredHeight: Style.iconSize
|
||||||
Layout.preferredWidth: Style.iconSize
|
Layout.preferredWidth: Style.iconSize
|
||||||
name: "../images/connections/network-wifi-offline.svg"
|
name: "/ui/images/connections/network-wifi-offline.svg"
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -91,7 +91,7 @@ SettingsPageBase {
|
|||||||
|
|
||||||
delegate: NymeaSwipeDelegate {
|
delegate: NymeaSwipeDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
iconName: "../images/zigbee.svg"
|
iconName: "/ui/images/zigbee.svg"
|
||||||
text: model.backend + " - " + model.description + " - " + model.serialPort
|
text: model.backend + " - " + model.description + " - " + model.serialPort
|
||||||
onClicked: {
|
onClicked: {
|
||||||
pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: false})
|
pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: false})
|
||||||
@ -124,7 +124,7 @@ SettingsPageBase {
|
|||||||
delegate: NymeaSwipeDelegate {
|
delegate: NymeaSwipeDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
property ZigbeeAdapter adapter: root.zigbeeManager.adapters.get(index)
|
property ZigbeeAdapter adapter: root.zigbeeManager.adapters.get(index)
|
||||||
iconName: "../images/stock_usb.svg"
|
iconName: "/ui/images/stock_usb.svg"
|
||||||
text: model.description + " - " + model.serialPort
|
text: model.description + " - " + model.serialPort
|
||||||
onClicked: {
|
onClicked: {
|
||||||
pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: true})
|
pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: true})
|
||||||
@ -169,7 +169,7 @@ SettingsPageBase {
|
|||||||
default:
|
default:
|
||||||
props.errorCode = error;
|
props.errorCode = error;
|
||||||
}
|
}
|
||||||
var comp = Qt.createComponent("../components/ErrorDialog.qml")
|
var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
|
||||||
var popup = comp.createObject(app, props)
|
var popup = comp.createObject(app, props)
|
||||||
popup.open();
|
popup.open();
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2021, nymea GmbH
|
* Copyright 2013 - 2022, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Controls.Material 2.1
|
import QtQuick.Controls.Material 2.1
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import "../components"
|
import "qrc:/ui/components"
|
||||||
import Nymea 1.0
|
import Nymea 1.0
|
||||||
|
|
||||||
SettingsPageBase {
|
SettingsPageBase {
|
||||||
@ -96,7 +96,7 @@ SettingsPageBase {
|
|||||||
default:
|
default:
|
||||||
props.errorCode = error;
|
props.errorCode = error;
|
||||||
}
|
}
|
||||||
var comp = Qt.createComponent("../components/ErrorDialog.qml")
|
var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
|
||||||
var popup = comp.createObject(app, props)
|
var popup = comp.createObject(app, props)
|
||||||
popup.open();
|
popup.open();
|
||||||
}
|
}
|
||||||
@ -326,9 +326,11 @@ SettingsPageBase {
|
|||||||
additionalItem: ColorIcon {
|
additionalItem: ColorIcon {
|
||||||
size: Style.smallIconSize
|
size: Style.smallIconSize
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
name: node.type === ZigbeeNode.ZigbeeNodeTypeRouter
|
name: node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator
|
||||||
? "/ui/images/zigbee-router.svg"
|
? "/ui/images/zigbee-coordinator.svg"
|
||||||
: "/ui/images/zigbee-enddevice.svg"
|
: node.type === ZigbeeNode.ZigbeeNodeTypeRouter
|
||||||
|
? "/ui/images/zigbee-router.svg"
|
||||||
|
: "/ui/images/zigbee-enddevice.svg"
|
||||||
color: communicationIndicatorLedTimer.running ? Style.accentColor : Style.iconColor
|
color: communicationIndicatorLedTimer.running ? Style.accentColor : Style.iconColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2022, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Controls.Material 2.1
|
import QtQuick.Controls.Material 2.1
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import "../components"
|
import "qrc:/ui/components"
|
||||||
import Nymea 1.0
|
import Nymea 1.0
|
||||||
|
|
||||||
SettingsPageBase {
|
SettingsPageBase {
|
||||||
@ -89,6 +89,12 @@ SettingsPageBase {
|
|||||||
progressive: false
|
progressive: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NymeaItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Network topology")
|
||||||
|
onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeTopologyPage.qml"), {network: root.network})
|
||||||
|
}
|
||||||
|
|
||||||
SettingsPageSectionHeader {
|
SettingsPageSectionHeader {
|
||||||
text: qsTr("Hardware information")
|
text: qsTr("Hardware information")
|
||||||
}
|
}
|
||||||
@ -145,11 +151,11 @@ SettingsPageBase {
|
|||||||
Layout.rightMargin: app.margins
|
Layout.rightMargin: app.margins
|
||||||
text: qsTr("Remove network")
|
text: qsTr("Remove network")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
var dialog = Qt.createComponent(Qt.resolvedUrl("/ui/components/MeaDialog.qml"));
|
||||||
var text = qsTr("Are you sure you want to remove the network and all associated devices from the system?")
|
var text = qsTr("Are you sure you want to remove the network and all associated devices from the system?")
|
||||||
var popup = dialog.createObject(app,
|
var popup = dialog.createObject(app,
|
||||||
{
|
{
|
||||||
headerIcon: "../images/dialog-warning-symbolic.svg",
|
headerIcon: "/ui/images/dialog-warning-symbolic.svg",
|
||||||
title: qsTr("Remove network"),
|
title: qsTr("Remove network"),
|
||||||
text: text,
|
text: text,
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
@ -168,11 +174,11 @@ SettingsPageBase {
|
|||||||
Layout.rightMargin: app.margins
|
Layout.rightMargin: app.margins
|
||||||
text: qsTr("Factory reset controller")
|
text: qsTr("Factory reset controller")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
var dialog = Qt.createComponent(Qt.resolvedUrl("/ui/components/MeaDialog.qml"));
|
||||||
var text = qsTr("Are you sure you want to factory reset the controller? This will recreate the network and remove all associated devices from the system.")
|
var text = qsTr("Are you sure you want to factory reset the controller? This will recreate the network and remove all associated devices from the system.")
|
||||||
var popup = dialog.createObject(app,
|
var popup = dialog.createObject(app,
|
||||||
{
|
{
|
||||||
headerIcon: "../images/dialog-warning-symbolic.svg",
|
headerIcon: "/ui/images/dialog-warning-symbolic.svg",
|
||||||
title: qsTr("Reset controller"),
|
title: qsTr("Reset controller"),
|
||||||
text: text,
|
text: text,
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
83
nymea-app/ui/system/zigbee/ZigbeeNodeDelegate.qml
Normal file
83
nymea-app/ui/system/zigbee/ZigbeeNodeDelegate.qml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright 2013 - 2022, nymea GmbH
|
||||||
|
* Contact: contact@nymea.io
|
||||||
|
*
|
||||||
|
* This file is part of nymea.
|
||||||
|
* This project including source code and documentation is protected by
|
||||||
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||||
|
* reproduction, publication, editing and translation, are reserved. The use of
|
||||||
|
* this project is subject to the terms of a license agreement to be concluded
|
||||||
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||||
|
* under https://nymea.io/license
|
||||||
|
*
|
||||||
|
* GNU General Public License Usage
|
||||||
|
* Alternatively, this project may be redistributed and/or modified under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||||
|
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* For any further details and any questions please contact us under
|
||||||
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||||
|
* https://nymea.io/license/faq
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
import QtQuick 2.0
|
||||||
|
import Nymea 1.0
|
||||||
|
import QtQuick.Layouts 1.0
|
||||||
|
import "qrc:/ui/components"
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property ZigbeeManager zigbeeManager: null
|
||||||
|
property ZigbeeNetwork zigbeeNetwork: null
|
||||||
|
property ZigbeeNode node: null
|
||||||
|
|
||||||
|
NymeaItemDelegate {
|
||||||
|
id: thisNode
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.node.model + " - " + root.node.neighbors.length
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.node.neighbors.length
|
||||||
|
delegate: Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "fdsfdfasa, index" + index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.node.neighbors.length
|
||||||
|
delegate: Loader {
|
||||||
|
id: loader
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: item ? item.implicitHeight : 0
|
||||||
|
source: Qt.resolvedUrl("ZigbeeNodeDelegate.qml")
|
||||||
|
// ZigbeeNodeDelegate {
|
||||||
|
Binding {
|
||||||
|
target: loader.item
|
||||||
|
property: "zigbeeManager"
|
||||||
|
value: root.zigbeeManager
|
||||||
|
}
|
||||||
|
Binding {
|
||||||
|
target: loader.item
|
||||||
|
property: "zigbeeNetwork"
|
||||||
|
value: root.zigbeeNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: loader.item
|
||||||
|
property: "node"
|
||||||
|
value: root.zigbeeNetwork.nodes.getNodeByNetworkAddress(root.node.neighbors[index].networkAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2022, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Controls.Material 2.1
|
import QtQuick.Controls.Material 2.1
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import "../components"
|
import "qrc:/ui/components"
|
||||||
import Nymea 1.0
|
import Nymea 1.0
|
||||||
|
|
||||||
SettingsPageBase {
|
SettingsPageBase {
|
||||||
@ -43,7 +43,7 @@ SettingsPageBase {
|
|||||||
onBackPressed: pageStack.pop()
|
onBackPressed: pageStack.pop()
|
||||||
|
|
||||||
HeaderButton {
|
HeaderButton {
|
||||||
imageSource: "../images/add.svg"
|
imageSource: "/ui/images/add.svg"
|
||||||
text: qsTr("Add ZigBee network")
|
text: qsTr("Add ZigBee network")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
addNetwork()
|
addNetwork()
|
||||||
409
nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml
Normal file
409
nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import "qrc:/ui/components"
|
||||||
|
import Nymea 1.0
|
||||||
|
|
||||||
|
Page {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
header: NymeaHeader {
|
||||||
|
text: qsTr("ZigBee network topology")
|
||||||
|
backButtonVisible: true
|
||||||
|
onBackPressed: pageStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
property ZigbeeNetwork network: null
|
||||||
|
|
||||||
|
readonly property int nodeDistance: 150
|
||||||
|
readonly property int nodeSize: Style.iconSize + Style.margins
|
||||||
|
readonly property double scale: 1
|
||||||
|
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
generateNodeList()
|
||||||
|
canvas.requestPaint()
|
||||||
|
flickable.contentX = (flickable.contentWidth - flickable.width) / 2
|
||||||
|
flickable.contentY = (flickable.contentHeight - flickable.height) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNodeList() {
|
||||||
|
var coordinator = {}
|
||||||
|
var routers = []
|
||||||
|
var endDevices = []
|
||||||
|
for (var i = 0; i < root.network.nodes.count; i++) {
|
||||||
|
var node = root.network.nodes.get(i);
|
||||||
|
switch (node.type) {
|
||||||
|
case ZigbeeNode.ZigbeeNodeTypeRouter:
|
||||||
|
routers.push(node)
|
||||||
|
break;
|
||||||
|
case ZigbeeNode.ZigbeeNodeTypeEndDevice:
|
||||||
|
endDevices.push(node);
|
||||||
|
break;
|
||||||
|
case ZigbeeNode.ZigbeeNodeTypeCoordinator:
|
||||||
|
coordinator = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startAngle = -90
|
||||||
|
|
||||||
|
var x = root.nodeDistance * Math.cos(startAngle * Math.PI / 180)
|
||||||
|
var y = root.nodeDistance * Math.sin(startAngle * Math.PI / 180)
|
||||||
|
d.nodeItems.push(createNodeItem(coordinator, x, y, startAngle))
|
||||||
|
|
||||||
|
var handledEndDevices = []
|
||||||
|
|
||||||
|
var angle = 360 / (routers.length + 1);
|
||||||
|
for (var i = 0; i < routers.length; i++) {
|
||||||
|
var router = routers[i]
|
||||||
|
var nodeAngle = startAngle + angle * (i + 1);
|
||||||
|
var x = root.nodeDistance * Math.cos(nodeAngle * Math.PI / 180)
|
||||||
|
var y = root.nodeDistance * Math.sin(nodeAngle * Math.PI / 180)
|
||||||
|
d.nodeItems.push(createNodeItem(routers[i], x, y, nodeAngle));
|
||||||
|
|
||||||
|
|
||||||
|
var neighborCounter = 0;
|
||||||
|
for (var j = 0; j < router.neighbors.length; j++) {
|
||||||
|
var neighborNode = root.network.nodes.getNodeByNetworkAddress(router.neighbors[j].networkAddress)
|
||||||
|
if (!neighborNode) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neighborNode.type == ZigbeeNode.ZigbeeNodeTypeEndDevice) {
|
||||||
|
if (handledEndDevices.indexOf(neighborNode.networkAddress) >= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handledEndDevices.push(neighborNode.networkAddress)
|
||||||
|
|
||||||
|
|
||||||
|
var neighborAngle = nodeAngle + neighborCounter * 8
|
||||||
|
var neighborDistance = root.nodeDistance * 1.5 * root.scale + neighborCounter * root.nodeSize * 0.75 * root.scale
|
||||||
|
|
||||||
|
x = neighborDistance * Math.cos(neighborAngle * Math.PI / 180)
|
||||||
|
y = neighborDistance * Math.sin(neighborAngle * Math.PI / 180)
|
||||||
|
d.nodeItems.push(createNodeItem(neighborNode, x, y, angle))
|
||||||
|
|
||||||
|
neighborCounter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createNodeItem(node, x, y, angle) {
|
||||||
|
var icon = "/ui/images/zigbee.svg"
|
||||||
|
var thing = null
|
||||||
|
if (node.networkAddress == 0) {
|
||||||
|
icon = "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < engine.thingManager.things.count; i++) {
|
||||||
|
var t = engine.thingManager.things.get(i)
|
||||||
|
// print("checking thing", t.name)
|
||||||
|
var param = t.paramByName("ieeeAddress")
|
||||||
|
if (param && param.value == node.ieeeAddress) {
|
||||||
|
thing = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thing) {
|
||||||
|
icon = app.interfacesToIcon(thing.thingClass.interfaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeItem = {
|
||||||
|
node: node,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
edges: [],
|
||||||
|
image: imageComponent.createObject(canvas, {
|
||||||
|
x: Qt.binding(function() { return x + (canvas.width - Style.iconSize) / 2}),
|
||||||
|
y: Qt.binding(function() { return y + (canvas.height - Style.iconSize) / 2}),
|
||||||
|
name: icon,
|
||||||
|
color: Style.accentColor
|
||||||
|
}),
|
||||||
|
thing: thing
|
||||||
|
|
||||||
|
}
|
||||||
|
print("creared node", thing ? thing.name : "", " at", x, y)
|
||||||
|
d.adjustSize(x, y)
|
||||||
|
return nodeItem
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
property var nodeTree: ({})
|
||||||
|
property var handledNodes: []
|
||||||
|
|
||||||
|
property var nodeItems: []
|
||||||
|
|
||||||
|
property var selectedNodeItem: null
|
||||||
|
|
||||||
|
property int minX: 0
|
||||||
|
property int minY: 0
|
||||||
|
property int maxX: 0
|
||||||
|
property int maxY: 0
|
||||||
|
property int size: 0
|
||||||
|
|
||||||
|
function adjustSize(x, y) {
|
||||||
|
minX = Math.min(minX, x)
|
||||||
|
minY = Math.min(minY, y)
|
||||||
|
maxX = Math.max(maxX, x)
|
||||||
|
maxY = Math.max(maxY, y)
|
||||||
|
var minWidth = Math.max(-minX, maxX) * 2
|
||||||
|
var minHeight = Math.max(-minY, maxY) * 2
|
||||||
|
size = Math.max(minWidth, minHeight) + root.nodeSize * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: imageComponent
|
||||||
|
ColorIcon {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: flickable
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
contentWidth: canvas.width
|
||||||
|
contentHeight: canvas.height
|
||||||
|
// interactive: true
|
||||||
|
// flickableDirection: Flickable.HorizontalAndVerticalFlick
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: canvas
|
||||||
|
width: Math.max(d.size, flickable.width)
|
||||||
|
height: Math.max(d.size, flickable.height)
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
print("**** height:", canvas.height, "width", canvas.width)
|
||||||
|
var ctx = getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
|
||||||
|
var center = { x: canvas.width / 2, y: canvas.height / 2 };
|
||||||
|
ctx.translate(center.x, center.y)
|
||||||
|
|
||||||
|
paintNodeList(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintNodeList(ctx) {
|
||||||
|
for (var i = 0; i < d.nodeItems.length; i++) {
|
||||||
|
paintEdges(ctx, d.nodeItems[i], false)
|
||||||
|
}
|
||||||
|
for (var i = 0; i < d.nodeItems.length; i++) {
|
||||||
|
paintEdges(ctx, d.nodeItems[i], true)
|
||||||
|
}
|
||||||
|
for (var i = 0; i < d.nodeItems.length; i++) {
|
||||||
|
paintNode(ctx, d.nodeItems[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintEdges(ctx, nodeItem, selected) {
|
||||||
|
for (var i = 0; i < nodeItem.node.neighbors.length; i++) {
|
||||||
|
var neighbor = nodeItem.node.neighbors[i]
|
||||||
|
// print("ege from", nodeItem.node.networkAddress, "to", neighbor, "LQI", neighbor.lqi, "depth:", neighbor.depth)
|
||||||
|
for (var k = 0; k < d.nodeItems.length; k++) {
|
||||||
|
if (d.nodeItems[k].node.networkAddress == neighbor.networkAddress) {
|
||||||
|
var toNodeItem = d.nodeItems[k]
|
||||||
|
if (nodeItem === d.selectedNodeItem || toNodeItem === d.selectedNodeItem) {
|
||||||
|
if (selected) {
|
||||||
|
paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!selected) {
|
||||||
|
paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintNode(ctx, nodeItem) {
|
||||||
|
ctx.save()
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = Style.tileBackgroundColor
|
||||||
|
ctx.strokeStyle = nodeItem === d.selectedNodeItem ? Style.accentColor : Style.foregroundColor
|
||||||
|
ctx.arc(root.scale * nodeItem.x, root.scale * nodeItem.y, root.scale * root.nodeSize / 2, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
// ctx.stroke();
|
||||||
|
ctx.fillStyle = Style.foregroundColor
|
||||||
|
ctx.font = "" + Style.extraSmallFont.pixelSize + "px Ubuntu";
|
||||||
|
var text = ""
|
||||||
|
if (nodeItem.thing) {
|
||||||
|
text = nodeItem.thing.name
|
||||||
|
} else {
|
||||||
|
text = nodeItem.node.model
|
||||||
|
}
|
||||||
|
if (text.length > 10) {
|
||||||
|
text = text.substring(0, 9) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
var textSize = ctx.measureText(text)
|
||||||
|
// ctx.fillText(text, scale * (nodeItem.x ), scale * (nodeItem.y ))
|
||||||
|
ctx.fillText(text, scale * (nodeItem.x - textSize.width / 2), scale * (nodeItem.y + root.nodeSize / 2 + Style.extraSmallFont.pixelSize))
|
||||||
|
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintEdge(ctx, fromNodeItem, toNodeItem, lqi, selected) {
|
||||||
|
ctx.save()
|
||||||
|
var percent = lqi / 255;
|
||||||
|
var goodColor = Style.green
|
||||||
|
var badColor = Style.red
|
||||||
|
var resultRed = goodColor.r + percent * (badColor.r - goodColor.r);
|
||||||
|
var resultGreen = goodColor.g + percent * (badColor.g - goodColor.g);
|
||||||
|
var resultBlue = goodColor.b + percent * (badColor.b - goodColor.b);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, 1)
|
||||||
|
} else {
|
||||||
|
ctx.lineWidth = 1
|
||||||
|
var alpha = d.selectedNodeItem ? .2 : 1
|
||||||
|
ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, alpha)
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(scale * fromNodeItem.x, scale * fromNodeItem.y)
|
||||||
|
ctx.lineTo(scale * toNodeItem.x, scale * toNodeItem.y)
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
print("clicked:", mouseX, mouseY)
|
||||||
|
var translatedMouseX = mouseX - canvas.width / 2
|
||||||
|
var translatedMouseY = mouseY - canvas.height / 2
|
||||||
|
d.selectedNodeItem = null
|
||||||
|
for (var i = 0; i < d.nodeItems.length; i++) {
|
||||||
|
var nodeItem = d.nodeItems[i]
|
||||||
|
// print("nodeItem at:", root.scale * nodeItem.x, root.scale * nodeItem.y)
|
||||||
|
if (Math.abs(root.scale * nodeItem.x - translatedMouseX) < (root.scale * root.nodeSize / 2)
|
||||||
|
&& Math.abs(root.scale * nodeItem.y - translatedMouseY) < (root.scale * root.nodeSize / 2)) {
|
||||||
|
d.selectedNodeItem = nodeItem;
|
||||||
|
print("sleecting", nodeItem.node.networkAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.requestPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BigTile {
|
||||||
|
visible: d.selectedNodeItem
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
right: parent.right
|
||||||
|
margins: Style.margins
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 200
|
||||||
|
header: RowLayout {
|
||||||
|
width: parent.width - Style.smallMargins
|
||||||
|
spacing: Style.smallMargins
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: !d.selectedNodeItem
|
||||||
|
? ""
|
||||||
|
: d.selectedNodeItem.node.networkAddress === 0
|
||||||
|
? Configuration.systemName
|
||||||
|
: d.selectedNodeItem.thing
|
||||||
|
? d.selectedNodeItem.thing.name
|
||||||
|
: d.selectedNodeItem.node.model
|
||||||
|
}
|
||||||
|
ColorIcon {
|
||||||
|
size: Style.smallIconSize
|
||||||
|
name: {
|
||||||
|
if (!d.selectedNodeItem) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var signalStrength = d.selectedNodeItem.node.lqi * 100 / 255
|
||||||
|
if (signalStrength === 0)
|
||||||
|
return "/ui/images/connections/nm-signal-00.svg"
|
||||||
|
if (signalStrength <= 25)
|
||||||
|
return "/ui/images/connections/nm-signal-25.svg"
|
||||||
|
if (signalStrength <= 50)
|
||||||
|
return "/ui/images/connections/nm-signal-50.svg"
|
||||||
|
if (signalStrength <= 75)
|
||||||
|
return "/ui/images/connections/nm-signal-75.svg"
|
||||||
|
if (signalStrength <= 100)
|
||||||
|
return "/ui/images/connections/nm-signal-100.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColorIcon {
|
||||||
|
size: Style.smallIconSize
|
||||||
|
name: "/ui/images/things.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ListView {
|
||||||
|
spacing: app.margins
|
||||||
|
implicitHeight: Math.min(root.height / 4, count * Style.smallIconSize)
|
||||||
|
clip: true
|
||||||
|
model: d.selectedNodeItem ? d.selectedNodeItem.node.neighbors.length : 0
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
id: neighborTableDelegate
|
||||||
|
width: parent.width
|
||||||
|
property ZigbeeNodeNeighbor neighbor: d.selectedNodeItem.node.neighbors[index]
|
||||||
|
property ZigbeeNode neighborNode: root.network.nodes.getNodeByNetworkAddress(neighbor.networkAddress)
|
||||||
|
property Thing neighborNodeThing: {
|
||||||
|
for (var i = 0; i < engine.thingManager.things.count; i++) {
|
||||||
|
var thing = engine.thingManager.things.get(i)
|
||||||
|
var param = thing.paramByName("ieeeAddress")
|
||||||
|
if (param && param.value == neighborNode.ieeeAddress) {
|
||||||
|
return thing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: Style.smallFont
|
||||||
|
text: neighborTableDelegate.neighbor.networkAddress === 0
|
||||||
|
? Configuration.systemName
|
||||||
|
: neighborTableDelegate.neighborNodeThing
|
||||||
|
? neighborTableDelegate.neighborNodeThing.name
|
||||||
|
: neighborTableDelegate.neighborNode
|
||||||
|
? neighborTableDelegate.neighborNode.model
|
||||||
|
: "0x" + neighborTableDelegate.neighbor.networkAddress.toString(16)
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: (neighborTableDelegate.neighbor.lqi * 100 / 255).toFixed(0) + "%"
|
||||||
|
font: Style.smallFont
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.preferredWidth: Style.smallIconSize + Style.smallMargins
|
||||||
|
font: Style.smallFont
|
||||||
|
text: neighborTableDelegate.neighbor.depth
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user