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();
|
||||
|
||||
m_reconnectTimer.setInterval(100);
|
||||
m_reconnectTimer.setInterval(500);
|
||||
m_reconnectTimer.setSingleShot(true);
|
||||
connect(&m_reconnectTimer, &QTimer::timeout, this, [this](){
|
||||
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::errorOccurred, this, &TunnelProxyTransport::onRemoteConnectionErrorOccurred);
|
||||
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) {
|
||||
qWarning() << " --> " << sslError.errorString();
|
||||
qCWarning(dcTunnelProxyRemoteConnectionDummy) << " --> " << sslError.errorString();
|
||||
}
|
||||
});
|
||||
|
||||
@ -67,8 +67,6 @@ bool TunnelProxyTransport::connect(const QUrl &url)
|
||||
serverUrl.setPort(url.port());
|
||||
QUuid serverUuid = QUrlQuery(url).queryItemValue("uuid");
|
||||
|
||||
qCritical() << "Calling connect on" << serverUrl << serverUuid;
|
||||
|
||||
return m_remoteConnection->connectServer(serverUrl, serverUuid);
|
||||
}
|
||||
|
||||
@ -137,7 +135,7 @@ void TunnelProxyTransport::onRemoteConnectionStateChanged(remoteproxyclient::Tun
|
||||
|
||||
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
|
||||
|
||||
@ -329,6 +329,7 @@ void registerQmlTypes() {
|
||||
qmlRegisterUncreatableType<ZigbeeNetwork>(uri, 1, 0, "ZigbeeNetwork", "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<ZigbeeNodeNeighbor>(uri, 1, 0, "ZigbeeNodeNeighbor", "Get it from the ZigbeeNode");
|
||||
qmlRegisterUncreatableType<ZigbeeNodes>(uri, 1, 0, "ZigbeeNodes", "Get it from the ZigbeeNetwork");
|
||||
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)
|
||||
{
|
||||
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()) {
|
||||
QVariantMap nodeMap = nodeVariant.toMap();
|
||||
@ -254,7 +254,7 @@ void ZigbeeManager::removeNodeResponse(int commandId, const QVariantMap ¶ms)
|
||||
|
||||
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();
|
||||
if (notificationString == "Zigbee.AdapterAdded") {
|
||||
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();
|
||||
QString ieeeAddress = nodeMap.value("ieeeAddress").toString();
|
||||
ZigbeeNode *node = new ZigbeeNode(networkUuid, ieeeAddress, this);
|
||||
node->updateNodeProperties(nodeMap);
|
||||
updateNodeProperties(node, nodeMap);
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -386,9 +386,35 @@ void ZigbeeManager::addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &n
|
||||
QString ieeeAddress = nodeMap.value("ieeeAddress").toString();
|
||||
ZigbeeNode *node = network->nodes()->getNode(ieeeAddress);
|
||||
if (node) {
|
||||
node->updateNodeProperties(nodeMap);
|
||||
updateNodeProperties(node, nodeMap);
|
||||
} else {
|
||||
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 addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &nodeMap);
|
||||
void updateNodeProperties(ZigbeeNode *node, const QVariantMap &nodeMap);
|
||||
};
|
||||
|
||||
#endif // ZIGBEEMANAGER_H
|
||||
|
||||
@ -189,6 +189,51 @@ void ZigbeeNode::setLastSeen(const QDateTime &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)
|
||||
{
|
||||
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());
|
||||
setModel(nodeMap.value("model").toString());
|
||||
setVersion(nodeMap.value("version").toString());
|
||||
setRxOnWhenIdle(nodeMap.value("receiverOnWhileIdle").toBool());
|
||||
setReachable(nodeMap.value("reachable").toBool());
|
||||
setLqi(nodeMap.value("lqi").toUInt());
|
||||
setLastSeen(QDateTime::fromMSecsSinceEpoch(nodeMap.value("lastSeen").toUInt() * 1000));
|
||||
|
||||
}
|
||||
|
||||
quint16 ZigbeeNodeNeighbor::networkAddress() const
|
||||
{
|
||||
return m_networkAddress;
|
||||
}
|
||||
|
||||
ZigbeeNode::ZigbeeNodeRelationship ZigbeeNodeNeighbor::relationship() const
|
||||
{
|
||||
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 <QVariantMap>
|
||||
|
||||
class ZigbeeNodeNeighbor;
|
||||
|
||||
class ZigbeeNode : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -51,6 +53,7 @@ class ZigbeeNode : public QObject
|
||||
Q_PROPERTY(bool reachable READ reachable WRITE setReachable NOTIFY reachableChanged)
|
||||
Q_PROPERTY(uint lqi READ lqi WRITE setLqi NOTIFY lqiChanged)
|
||||
Q_PROPERTY(QDateTime lastSeen READ lastSeen WRITE setLastSeen NOTIFY lastSeenChanged)
|
||||
Q_PROPERTY(QList<ZigbeeNodeNeighbor*> neighbors READ neighbors NOTIFY neighborsChanged)
|
||||
|
||||
public:
|
||||
enum ZigbeeNodeType {
|
||||
@ -68,6 +71,15 @@ public:
|
||||
};
|
||||
Q_ENUM(ZigbeeNodeState)
|
||||
|
||||
enum ZigbeeNodeRelationship {
|
||||
ZigbeeNodeRelationshipParent,
|
||||
ZigbeeNodeRelationshipChild,
|
||||
ZigbeeNodeRelationshipSibling,
|
||||
ZigbeeNodeRelationshipNone,
|
||||
ZigbeeNodeRelationshipPreviousChild
|
||||
};
|
||||
Q_ENUM(ZigbeeNodeRelationship)
|
||||
|
||||
explicit ZigbeeNode(const QUuid &networkUuid, const QString &ieeeAddress, QObject *parent = nullptr);
|
||||
|
||||
QUuid networkUuid() const;
|
||||
@ -103,11 +115,13 @@ public:
|
||||
QDateTime lastSeen() const;
|
||||
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 ZigbeeNodeType stringToNodeType(const QString &nodeType);
|
||||
|
||||
void updateNodeProperties(const QVariantMap &nodeMap);
|
||||
|
||||
signals:
|
||||
void networkAddressChanged(quint16 networkAddress);
|
||||
void typeChanged(ZigbeeNodeType type);
|
||||
@ -119,6 +133,7 @@ signals:
|
||||
void reachableChanged(bool reachable);
|
||||
void lqiChanged(uint lqi);
|
||||
void lastSeenChanged(const QDateTime &lastSeen);
|
||||
void neighborsChanged();
|
||||
|
||||
private:
|
||||
QUuid m_networkUuid;
|
||||
@ -133,6 +148,42 @@ private:
|
||||
bool m_reachable = false;
|
||||
uint m_lqi = 0;
|
||||
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
|
||||
|
||||
@ -191,3 +191,13 @@ ZigbeeNode *ZigbeeNodes::getNode(const QString &ieeeAddress) const
|
||||
|
||||
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 ZigbeeNode *getNode(const QString &ieeeAddress) const;
|
||||
Q_INVOKABLE ZigbeeNode *getNodeByNetworkAddress(quint16 networkAddress) const;
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
|
||||
@ -46,6 +46,7 @@ class ZigbeeNodesProxy : public QSortFilterProxyModel
|
||||
Q_PROPERTY(bool showCoordinator READ showCoordinator WRITE setShowCoordinator NOTIFY showCoordinatorChanged)
|
||||
Q_PROPERTY(bool showOnline READ showOnline WRITE setShowOnline NOTIFY showOnlineChanged)
|
||||
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)
|
||||
|
||||
@ -89,6 +90,7 @@ private:
|
||||
bool m_showOffline = true;
|
||||
|
||||
bool m_newOnTop = false;
|
||||
bool m_sortByRelationship = false;
|
||||
|
||||
QHash<ZigbeeNode*, QDateTime> m_newNodes;
|
||||
};
|
||||
|
||||
@ -3,7 +3,6 @@ TEMPLATE=subdirs
|
||||
include(shared.pri)
|
||||
message("APP_VERSION: $${APP_VERSION} ($${APP_REVISION})")
|
||||
|
||||
|
||||
SUBDIRS = libnymea-app nymea-app
|
||||
nymea-app.depends = libnymea-app
|
||||
|
||||
|
||||
@ -210,9 +210,9 @@
|
||||
<file>ui/components/ThingStatusIcons.qml</file>
|
||||
<file>ui/components/InfoPaneBase.qml</file>
|
||||
<file>ui/components/ThingInfoPane.qml</file>
|
||||
<file>ui/system/ZigbeeSettingsPage.qml</file>
|
||||
<file>ui/system/ZigbeeAddNetworkPage.qml</file>
|
||||
<file>ui/system/ZigbeeNetworkSettingsPage.qml</file>
|
||||
<file>ui/system/zigbee/ZigbeeSettingsPage.qml</file>
|
||||
<file>ui/system/zigbee/ZigbeeAddNetworkPage.qml</file>
|
||||
<file>ui/system/zigbee/ZigbeeNetworkSettingsPage.qml</file>
|
||||
<file>ui/MainMenu.qml</file>
|
||||
<file>ui/components/NymeaItemDelegate.qml</file>
|
||||
<file>ui/components/NymeaSwipeDelegate.qml</file>
|
||||
@ -244,7 +244,8 @@
|
||||
<file>ui/system/ModbusRtuReconfigureMasterPage.qml</file>
|
||||
<file>ui/connection/ConnectionWizard.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/ButtonControls.qml</file>
|
||||
<file>ui/components/CircleBackground.qml</file>
|
||||
|
||||
@ -118,7 +118,7 @@ Page {
|
||||
text: qsTr("ZigBee")
|
||||
subText: qsTr("Configure ZigBee networks")
|
||||
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 {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Copyright 2013 - 2022, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
@ -33,7 +33,7 @@ import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import Nymea 1.0
|
||||
|
||||
import "../components"
|
||||
import "qrc:/ui/components"
|
||||
|
||||
SettingsPageBase {
|
||||
id: root
|
||||
@ -56,7 +56,7 @@ SettingsPageBase {
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: Style.iconSize
|
||||
Layout.preferredWidth: Style.iconSize
|
||||
name: "../images/connections/network-wifi-offline.svg"
|
||||
name: "/ui/images/connections/network-wifi-offline.svg"
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
@ -91,7 +91,7 @@ SettingsPageBase {
|
||||
|
||||
delegate: NymeaSwipeDelegate {
|
||||
Layout.fillWidth: true
|
||||
iconName: "../images/zigbee.svg"
|
||||
iconName: "/ui/images/zigbee.svg"
|
||||
text: model.backend + " - " + model.description + " - " + model.serialPort
|
||||
onClicked: {
|
||||
pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: false})
|
||||
@ -124,7 +124,7 @@ SettingsPageBase {
|
||||
delegate: NymeaSwipeDelegate {
|
||||
Layout.fillWidth: true
|
||||
property ZigbeeAdapter adapter: root.zigbeeManager.adapters.get(index)
|
||||
iconName: "../images/stock_usb.svg"
|
||||
iconName: "/ui/images/stock_usb.svg"
|
||||
text: model.description + " - " + model.serialPort
|
||||
onClicked: {
|
||||
pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: true})
|
||||
@ -169,7 +169,7 @@ SettingsPageBase {
|
||||
default:
|
||||
props.errorCode = error;
|
||||
}
|
||||
var comp = Qt.createComponent("../components/ErrorDialog.qml")
|
||||
var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
|
||||
var popup = comp.createObject(app, props)
|
||||
popup.open();
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Copyright 2013 - 2022, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../components"
|
||||
import "qrc:/ui/components"
|
||||
import Nymea 1.0
|
||||
|
||||
SettingsPageBase {
|
||||
@ -96,7 +96,7 @@ SettingsPageBase {
|
||||
default:
|
||||
props.errorCode = error;
|
||||
}
|
||||
var comp = Qt.createComponent("../components/ErrorDialog.qml")
|
||||
var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
|
||||
var popup = comp.createObject(app, props)
|
||||
popup.open();
|
||||
}
|
||||
@ -326,9 +326,11 @@ SettingsPageBase {
|
||||
additionalItem: ColorIcon {
|
||||
size: Style.smallIconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: node.type === ZigbeeNode.ZigbeeNodeTypeRouter
|
||||
? "/ui/images/zigbee-router.svg"
|
||||
: "/ui/images/zigbee-enddevice.svg"
|
||||
name: node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator
|
||||
? "/ui/images/zigbee-coordinator.svg"
|
||||
: node.type === ZigbeeNode.ZigbeeNodeTypeRouter
|
||||
? "/ui/images/zigbee-router.svg"
|
||||
: "/ui/images/zigbee-enddevice.svg"
|
||||
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
|
||||
*
|
||||
* This file is part of nymea.
|
||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../components"
|
||||
import "qrc:/ui/components"
|
||||
import Nymea 1.0
|
||||
|
||||
SettingsPageBase {
|
||||
@ -89,6 +89,12 @@ SettingsPageBase {
|
||||
progressive: false
|
||||
}
|
||||
|
||||
NymeaItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Network topology")
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeTopologyPage.qml"), {network: root.network})
|
||||
}
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Hardware information")
|
||||
}
|
||||
@ -145,11 +151,11 @@ SettingsPageBase {
|
||||
Layout.rightMargin: app.margins
|
||||
text: qsTr("Remove network")
|
||||
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 popup = dialog.createObject(app,
|
||||
{
|
||||
headerIcon: "../images/dialog-warning-symbolic.svg",
|
||||
headerIcon: "/ui/images/dialog-warning-symbolic.svg",
|
||||
title: qsTr("Remove network"),
|
||||
text: text,
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
@ -168,11 +174,11 @@ SettingsPageBase {
|
||||
Layout.rightMargin: app.margins
|
||||
text: qsTr("Factory reset controller")
|
||||
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 popup = dialog.createObject(app,
|
||||
{
|
||||
headerIcon: "../images/dialog-warning-symbolic.svg",
|
||||
headerIcon: "/ui/images/dialog-warning-symbolic.svg",
|
||||
title: qsTr("Reset controller"),
|
||||
text: text,
|
||||
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
|
||||
*
|
||||
* This file is part of nymea.
|
||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../components"
|
||||
import "qrc:/ui/components"
|
||||
import Nymea 1.0
|
||||
|
||||
SettingsPageBase {
|
||||
@ -43,7 +43,7 @@ SettingsPageBase {
|
||||
onBackPressed: pageStack.pop()
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/add.svg"
|
||||
imageSource: "/ui/images/add.svg"
|
||||
text: qsTr("Add ZigBee network")
|
||||
onClicked: {
|
||||
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