diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index 240d45e0..9f10fac4 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -329,9 +329,12 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNetwork", "Get it from the ZigbeeManager"); qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNetworks", "Get it from the ZigbeeManager"); qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNode", "Get it from the ZigbeeNodes"); + qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodes", "Get it from the ZigbeeNetwork"); qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodeNeighbor", "Get it from the ZigbeeNode"); qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodeRoute", "Get it from the ZigbeeNode"); - qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodes", "Get it from the ZigbeeNetwork"); + qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodeBinding", "Get it from the ZigbeeNode"); + qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodeEndpoint", "Get it from the ZigbeeNode"); + qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeCluster", "Get it from the ZigbeeNode"); qmlRegisterType(uri, 1, 0, "ZigbeeNodesProxy"); qmlRegisterType(uri, 1, 0, "ZWaveManager"); diff --git a/libnymea-app/zigbee/zigbeemanager.cpp b/libnymea-app/zigbee/zigbeemanager.cpp index 94ccff65..58589fc8 100644 --- a/libnymea-app/zigbee/zigbeemanager.cpp +++ b/libnymea-app/zigbee/zigbeemanager.cpp @@ -156,6 +156,38 @@ void ZigbeeManager::refreshNeighborTables(const QUuid &networkUuid) m_engine->jsonRpcClient()->sendCommand("Zigbee.RefreshNeighborTables", {{"networkUuid", networkUuid}}); } +int ZigbeeManager::createBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpointId) +{ + QVariantMap params = { + {"networkUuid", networkUuid}, + {"sourceAddress", sourceAddress}, + {"sourceEndpointId", sourceEndpointId}, + {"clusterId", clusterId}, + {"destinationAddress", destinationAddress}, + {"destinationEndpointId", destinationEndpointId} + }; + qCDebug(dcZigbee()) << "Creating binding for:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + return m_engine->jsonRpcClient()->sendCommand("Zigbee.CreateBinding", params, this, "createBindingResponse"); +} + +int ZigbeeManager::removeBinding(const QUuid &networkUuid, ZigbeeNodeBinding *binding) +{ + QVariantMap params = { + {"networkUuid", networkUuid}, + {"sourceAddress", binding->sourceAddress()}, + {"sourceEndpointId", binding->sourceEndpointId()}, + {"clusterId", binding->clusterId()} + }; + if (!binding->destinationAddress().isEmpty()) { + params.insert("destinationAddress", binding->destinationAddress()); + params.insert("destinationEndpointId", binding->destinationEndpointId()); + } else { + params.insert("destinationGroupAddress", binding->groupAddress()); + } + qCDebug(dcZigbee()) << "Removing binding for:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + return m_engine->jsonRpcClient()->sendCommand("Zigbee.RemoveBinding", params, this, "removeBindingResponse"); +} + void ZigbeeManager::init() { m_fetchingData = true; @@ -216,7 +248,9 @@ void ZigbeeManager::getNetworksResponse(int commandId, const QVariantMap ¶ms void ZigbeeManager::addNetworkResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcZigbee()) << "Zigbee add network response" << commandId << params; - emit addNetworkReply(commandId, params.value("zigbeeError").toString(), params.value("networkUuid").toUuid()); + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZigbeeError error = static_cast(errorEnum.keyToValue(params.value("zigbeeError").toByteArray())); + emit addNetworkReply(commandId, error, params.value("networkUuid").toUuid()); } void ZigbeeManager::removeNetworkResponse(int commandId, const QVariantMap ¶ms) @@ -254,7 +288,25 @@ void ZigbeeManager::getNodesResponse(int commandId, const QVariantMap ¶ms) void ZigbeeManager::removeNodeResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcZigbee()) << "Zigbee remove node response" << commandId << params; - emit removeNodeReply(commandId, params.value("zigbeeError").toString()); + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZigbeeError error = static_cast(errorEnum.keyToValue(params.value("zigbeeError").toByteArray())); + emit removeNodeReply(commandId, error); +} + +void ZigbeeManager::createBindingResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZigbee()) << "Create binding response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZigbeeError error = static_cast(errorEnum.keyToValue(params.value("zigbeeError").toByteArray())); + emit createBindingReply(commandId, error); +} + +void ZigbeeManager::removeBindingResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZigbee()) << "Remove binding response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZigbeeError error = static_cast(errorEnum.keyToValue(params.value("zigbeeError").toByteArray())); + emit removeBindingReply(commandId, error); } void ZigbeeManager::notificationReceived(const QVariantMap ¬ification) @@ -438,4 +490,52 @@ void ZigbeeManager::updateNodeProperties(ZigbeeNode *node, const QVariantMap &no routes.append(destinationAddress); } node->commitRoutes(routes); + + foreach (const QVariant &binding, nodeMap.value("bindingTableRecords").toList()) { + QVariantMap bindingMap = binding.toMap(); + QString sourceAddress = bindingMap.value("sourceAddress").toString(); + quint8 sourceEndpointId = bindingMap.value("sourceEndpointId").toUInt(); + quint16 clusterId = bindingMap.value("clusterId").toUInt(); + if (bindingMap.contains("groupAddress")) { + quint16 groupAddress = bindingMap.value("groupAddress").toUInt(); + node->addBinding(sourceAddress, sourceEndpointId, clusterId, groupAddress); + } else { + QString destinationAddress = bindingMap.value("destinationAddress").toString(); + quint8 destinationEndpointId = bindingMap.value("destinationEndpointId").toUInt(); + node->addBinding(sourceAddress, sourceEndpointId, clusterId, destinationAddress, destinationEndpointId); + } + } + node->commitBindings(); + + foreach (const QVariant &e, nodeMap.value("endpoints").toList()) { + QVariantMap endpointMap = e.toMap(); + quint8 endpointId = endpointMap.value("endpointId").toUInt(); + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(endpointId); + if (!endpoint) { + endpoint = new ZigbeeNodeEndpoint(endpointId); + node->addEndpoint(endpoint); + } + foreach (const QVariant &c, endpointMap.value("inputClusters").toList()) { + QVariantMap clusterMap = c.toMap(); + quint16 clusterId = clusterMap.value("clusterId").toUInt(); + if (endpoint->getInputCluster(clusterId)) { + continue; + } + QMetaEnum clusterDirectionEnum = QMetaEnum::fromType(); + ZigbeeCluster::ZigbeeClusterDirection direction = static_cast(clusterDirectionEnum.keyToValue(clusterMap.value("direction").toByteArray().data())); + ZigbeeCluster *cluster = new ZigbeeCluster(clusterId, direction); + endpoint->addInputCluster(cluster); + } + foreach (const QVariant &c, endpointMap.value("outputClusters").toList()) { + QVariantMap clusterMap = c.toMap(); + quint16 clusterId = clusterMap.value("clusterId").toUInt(); + if (endpoint->getOutputCluster(clusterId)) { + continue; + } + QMetaEnum clusterDirectionEnum = QMetaEnum::fromType(); + ZigbeeCluster::ZigbeeClusterDirection direction = static_cast(clusterDirectionEnum.keyToValue(clusterMap.value("direction").toByteArray().data())); + ZigbeeCluster *cluster = new ZigbeeCluster(clusterId, direction); + endpoint->addOutputCluster(cluster); + } + } } diff --git a/libnymea-app/zigbee/zigbeemanager.h b/libnymea-app/zigbee/zigbeemanager.h index 38624695..4f0878e9 100644 --- a/libnymea-app/zigbee/zigbeemanager.h +++ b/libnymea-app/zigbee/zigbeemanager.h @@ -41,6 +41,7 @@ class ZigbeeNetwork; class ZigbeeNetworks; class ZigbeeNode; class ZigbeeNodes; +class ZigbeeNodeBinding; class ZigbeeManager : public QObject { @@ -53,6 +54,22 @@ class ZigbeeManager : public QObject Q_PROPERTY(ZigbeeNetworks *networks READ networks CONSTANT) public: + enum ZigbeeError { + ZigbeeErrorNoError, + ZigbeeErrorAdapterNotAvailable, + ZigbeeErrorAdapterAlreadyInUse, + ZigbeeErrorNetworkUuidNotFound, + ZigbeeErrorDurationOutOfRange, + ZigbeeErrorNetworkOffline, + ZigbeeErrorUnknownBackend, + ZigbeeErrorNodeNotFound, + ZigbeeErrorForbidden, + ZigbeeErrorInvalidChannel, + ZigbeeErrorNetworkError, + ZigbeeErrorTimeoutError + }; + Q_ENUM(ZigbeeError) + enum ZigbeeChannel { ZigbeeChannel11 = 0x00000800, ZigbeeChannel12 = 0x00001000, @@ -96,13 +113,17 @@ public: Q_INVOKABLE void getNodes(const QUuid &networkUuid); Q_INVOKABLE int removeNode(const QUuid &networkUuid, const QString &ieeeAddress); Q_INVOKABLE void refreshNeighborTables(const QUuid &networkUuid); + Q_INVOKABLE int createBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpointId); + Q_INVOKABLE int removeBinding(const QUuid &networkUuid, ZigbeeNodeBinding *binding); signals: void engineChanged(); void fetchingDataChanged(); void availableBackendsChanged(); - void addNetworkReply(int commandId, const QString &error, const QUuid &networkUuid); - void removeNodeReply(int commandId, const QString &error); + void addNetworkReply(int commandId, ZigbeeError error, const QUuid &networkUuid); + void removeNodeReply(int commandId, ZigbeeError error); + void createBindingReply(int commandId, ZigbeeError error); + void removeBindingReply(int commandId, ZigbeeError error); private: void init(); @@ -119,6 +140,9 @@ private: Q_INVOKABLE void getNodesResponse(int commandId, const QVariantMap ¶ms); Q_INVOKABLE void removeNodeResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void createBindingResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void removeBindingResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void notificationReceived(const QVariantMap ¬ification); private: diff --git a/libnymea-app/zigbee/zigbeenode.cpp b/libnymea-app/zigbee/zigbeenode.cpp index 471ad9fd..cc1bd640 100644 --- a/libnymea-app/zigbee/zigbeenode.cpp +++ b/libnymea-app/zigbee/zigbeenode.cpp @@ -30,6 +30,8 @@ #include "zigbeenode.h" +#include + ZigbeeNode::ZigbeeNode(const QUuid &networkUuid, const QString &ieeeAddress, QObject *parent) : QObject(parent), m_networkUuid(networkUuid), @@ -299,6 +301,81 @@ void ZigbeeNode::commitRoutes(QList toBeKept) } } +QList ZigbeeNode::bindings() const +{ + return m_bindings; +} + +void ZigbeeNode::addBinding(const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, quint16 groupAddress) +{ + ZigbeeNodeBinding *newBinding = new ZigbeeNodeBinding(sourceAddress, sourceEndpointId, clusterId, groupAddress, this); + foreach (ZigbeeNodeBinding *binding, m_bindings) { + if (binding == newBinding) { + binding->setProperty("validated", true); + delete newBinding; + return; + } + } + newBinding->setProperty("validated", true); + m_bindings.append(newBinding); + m_bindingsDirty = true; +} + +void ZigbeeNode::addBinding(const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpointId) +{ + ZigbeeNodeBinding *newBinding = new ZigbeeNodeBinding(sourceAddress, sourceEndpointId, clusterId, destinationAddress, destinationEndpointId, this); + foreach (ZigbeeNodeBinding *binding, m_bindings) { + if (binding == newBinding) { + binding->setProperty("validated", true); + delete newBinding; + return; + } + } + newBinding->setProperty("validated", true); + m_bindings.append(newBinding); + m_bindingsDirty = true; +} + +void ZigbeeNode::commitBindings() +{ + QMutableListIterator it(m_bindings); + while (it.hasNext()) { + ZigbeeNodeBinding *binding = it.next(); + if (!binding->property("validated").toBool()) { + it.remove(); + m_bindingsDirty = true; + } else { + binding->setProperty("validated", false); + } + } + if (m_bindingsDirty) { + m_bindingsDirty = false; + emit bindingsChanged(); + } +} + +QList ZigbeeNode::endpoints() const +{ + return m_endpoints; +} + +ZigbeeNodeEndpoint *ZigbeeNode::getEndpoint(quint8 endpointId) const +{ + foreach (ZigbeeNodeEndpoint *endpoint, m_endpoints) { + if (endpoint->endpointId() == endpointId) { + return endpoint; + } + } + return nullptr; +} + +void ZigbeeNode::addEndpoint(ZigbeeNodeEndpoint *endpoint) +{ + endpoint->setParent(this); + m_endpoints.append(endpoint); + emit endpointsChanged(); +} + ZigbeeNode::ZigbeeNodeState ZigbeeNode::stringToNodeState(const QString &nodeState) { if (nodeState == "ZigbeeNodeStateUninitialized") { @@ -450,3 +527,157 @@ void ZigbeeNodeRoute::setManyToOne(bool manyToOne) emit manyToOneChanged(); } } + +ZigbeeNodeBinding::ZigbeeNodeBinding(const QString &sourceAddress, quint8 sourceEndointId, quint16 clusterId, quint16 groupAddress, QObject *parent): + QObject(parent), + m_sourceAddress(sourceAddress), + m_sourceEndpointId(sourceEndointId), + m_clusterId(clusterId), + m_type(ZigbeeNode::ZigbeeNodeBindingTypeGroup), + m_groupAddress(groupAddress) +{ + +} + +ZigbeeNodeBinding::ZigbeeNodeBinding(const QString &sourceAddress, quint8 sourceEndointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpoint, QObject *parent): + QObject(parent), + m_sourceAddress(sourceAddress), + m_sourceEndpointId(sourceEndointId), + m_clusterId(clusterId), + m_type(ZigbeeNode::ZigbeeNodeBindingTypeDevice), + m_destinationAddress(destinationAddress), + m_destinationEndpointId(destinationEndpoint) +{ + +} + +QString ZigbeeNodeBinding::sourceAddress() const +{ + return m_sourceAddress; +} + +quint8 ZigbeeNodeBinding::sourceEndpointId() const +{ + return m_sourceEndpointId; +} + +quint16 ZigbeeNodeBinding::clusterId() const +{ + return m_clusterId; +} + +ZigbeeNode::ZigbeeNodeBindingType ZigbeeNodeBinding::type() const +{ + return m_type; +} + +quint16 ZigbeeNodeBinding::groupAddress() const +{ + return m_groupAddress; +} + +QString ZigbeeNodeBinding::destinationAddress() const +{ + return m_destinationAddress; +} + +quint8 ZigbeeNodeBinding::destinationEndpointId() const +{ + return m_destinationEndpointId; +} + +ZigbeeCluster::ZigbeeCluster(quint16 clusterId, ZigbeeClusterDirection direction, QObject *parent): + QObject(parent), + m_clusterId(clusterId), + m_direction(direction) +{ + +} + +quint16 ZigbeeCluster::clusterId() const +{ + return m_clusterId; +} + +ZigbeeCluster::ZigbeeClusterDirection ZigbeeCluster::direction() const +{ + return m_direction; +} + +ZigbeeNodeEndpoint::ZigbeeNodeEndpoint(quint8 endpointId, const QList &inputClusters, const QList &outputClusters, QObject *parent): + QObject(parent), + m_endpointId(endpointId), + m_inputClusters(inputClusters), + m_outputClusters(outputClusters) +{ + foreach (ZigbeeCluster *cluster, inputClusters) { + cluster->setParent(this); + } + foreach (ZigbeeCluster *cluster, outputClusters) { + cluster->setParent(this); + } +} + +quint8 ZigbeeNodeEndpoint::endpointId() const +{ + return m_endpointId; +} + +QList ZigbeeNodeEndpoint::inputClusters() const +{ + return m_inputClusters; +} + +ZigbeeCluster *ZigbeeNodeEndpoint::getInputCluster(quint16 clusterId) const +{ + foreach (ZigbeeCluster *cluster, m_inputClusters) { + if (cluster->clusterId() == clusterId) { + return cluster; + } + } + return nullptr; +} + +void ZigbeeNodeEndpoint::addInputCluster(ZigbeeCluster *cluster) +{ + cluster->setParent(this); + m_inputClusters.append(cluster); + emit inputClustersChanged(); +} + +QList ZigbeeNodeEndpoint::outputClusters() const +{ + return m_outputClusters; +} + +ZigbeeCluster *ZigbeeNodeEndpoint::getOutputCluster(quint16 clusterId) const +{ + foreach (ZigbeeCluster *cluster, m_outputClusters) { + if (cluster->clusterId() == clusterId) { + return cluster; + } + } + return nullptr; +} + +void ZigbeeNodeEndpoint::addOutputCluster(ZigbeeCluster *cluster) +{ + cluster->setParent(this); + m_outputClusters.append(cluster); + emit outputClustersChanged(); +} + +QString ZigbeeCluster::clusterName() const +{ + QMetaEnum clusterEnum = QMetaEnum::fromType(); + QString name = clusterEnum.valueToKey(m_clusterId); + name.remove("ZigbeeClusterId"); + QRegExp re1 = QRegExp("([A-Z])([a-z]*)"); + name.replace(re1, ";\\1\\2"); + QStringList parts = name.split(";"); + QString clusterName = parts.join(" ").trimmed(); + if (clusterName.isEmpty()) { + clusterName = "0x" + QString::number(m_clusterId, 16); + } + return clusterName; +} diff --git a/libnymea-app/zigbee/zigbeenode.h b/libnymea-app/zigbee/zigbeenode.h index a73f1386..3e824e53 100644 --- a/libnymea-app/zigbee/zigbeenode.h +++ b/libnymea-app/zigbee/zigbeenode.h @@ -38,6 +38,8 @@ class ZigbeeNodeNeighbor; class ZigbeeNodeRoute; +class ZigbeeNodeBinding; +class ZigbeeNodeEndpoint; class ZigbeeNode : public QObject { @@ -56,6 +58,8 @@ class ZigbeeNode : public QObject Q_PROPERTY(QDateTime lastSeen READ lastSeen WRITE setLastSeen NOTIFY lastSeenChanged) Q_PROPERTY(QList neighbors READ neighbors NOTIFY neighborsChanged) Q_PROPERTY(QList routes READ routes NOTIFY routesChanged) + Q_PROPERTY(QList bindings READ bindings NOTIFY bindingsChanged) + Q_PROPERTY(QList endpoints READ endpoints NOTIFY endpointsChanged) public: enum ZigbeeNodeType { @@ -91,6 +95,12 @@ public: }; Q_ENUM(ZigbeeNodeRouteStatus) + enum ZigbeeNodeBindingType { + ZigbeeNodeBindingTypeDevice, + ZigbeeNodeBindingTypeGroup + }; + Q_ENUM(ZigbeeNodeBindingType) + explicit ZigbeeNode(const QUuid &networkUuid, const QString &ieeeAddress, QObject *parent = nullptr); QUuid networkUuid() const; @@ -134,6 +144,15 @@ public: void addOrUpdateRoute(quint16 destinationAddress, quint16 nextHopAddress, ZigbeeNodeRouteStatus status, bool memoryConstrained, bool manyToOne); void commitRoutes(QList toBeKept); + QList bindings() const; + void addBinding(const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, quint16 groupAddress); + void addBinding(const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpointId); + void commitBindings(); + + QList endpoints() const; + Q_INVOKABLE ZigbeeNodeEndpoint *getEndpoint(quint8 endpointId) const; + void addEndpoint(ZigbeeNodeEndpoint *endpoint); + static ZigbeeNodeState stringToNodeState(const QString &nodeState); static ZigbeeNodeType stringToNodeType(const QString &nodeType); @@ -150,6 +169,8 @@ signals: void lastSeenChanged(const QDateTime &lastSeen); void neighborsChanged(); void routesChanged(); + void bindingsChanged(); + void endpointsChanged(); private: QUuid m_networkUuid; @@ -168,6 +189,9 @@ private: bool m_neighborsDirty = false; QList m_routes; bool m_routesDirty = false; + QList m_bindings; + bool m_bindingsDirty = false; + QList m_endpoints; }; class ZigbeeNodeNeighbor: public QObject @@ -250,4 +274,184 @@ private: bool m_manyToOne = false; }; +class ZigbeeNodeBinding: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString sourceAddress READ sourceAddress CONSTANT) + Q_PROPERTY(quint8 sourceEndpointId READ sourceEndpointId CONSTANT) + Q_PROPERTY(quint16 clusterId READ clusterId CONSTANT) + Q_PROPERTY(ZigbeeNode::ZigbeeNodeBindingType type READ type CONSTANT) + Q_PROPERTY(quint16 groupAddress READ groupAddress CONSTANT) + Q_PROPERTY(QString destinationAddress READ destinationAddress CONSTANT) + Q_PROPERTY(quint8 destinationEndpointId READ destinationEndpointId CONSTANT) + +public: + ZigbeeNodeBinding(const QString &sourceAddress, quint8 sourceEndointId, quint16 clusterId, quint16 groupAddress, QObject *parent); + ZigbeeNodeBinding(const QString &sourceAddress, quint8 sourceEndointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpoint, QObject *parent); + + QString sourceAddress() const; + quint8 sourceEndpointId() const; + quint16 clusterId() const; + ZigbeeNode::ZigbeeNodeBindingType type() const; + quint16 groupAddress() const; + QString destinationAddress() const; + quint8 destinationEndpointId() const; + +private: + QString m_sourceAddress; + quint8 m_sourceEndpointId = 0; + quint16 m_clusterId = 0; + ZigbeeNode::ZigbeeNodeBindingType m_type = ZigbeeNode::ZigbeeNodeBindingTypeDevice; + quint16 m_groupAddress = 0; + QString m_destinationAddress; + quint8 m_destinationEndpointId = 0; +}; + +class ZigbeeCluster: public QObject +{ + Q_OBJECT + Q_PROPERTY(quint16 clusterId READ clusterId CONSTANT) + Q_PROPERTY(ZigbeeClusterDirection direction READ direction CONSTANT) +public: + enum ZigbeeClusterDirection { + ZigbeeClusterDirectionServer, + ZigbeeClusterDirectionClient + }; + Q_ENUM(ZigbeeClusterDirection) + + enum ZigbeeClusterId { + // Basics + ZigbeeClusterIdUnknown = 0xffff, + ZigbeeClusterIdBasic = 0x0000, + ZigbeeClusterIdPowerConfiguration = 0x0001, + ZigbeeClusterIdDeviceTemperature = 0x0002, + ZigbeeClusterIdIdentify = 0x0003, + ZigbeeClusterIdGroups = 0x0004, + ZigbeeClusterIdScenes = 0x0005, + ZigbeeClusterIdOnOff = 0x0006, + ZigbeeClusterIdOnOffCOnfiguration = 0x0007, + ZigbeeClusterIdLevelControl = 0x0008, + ZigbeeClusterIdAlarms = 0x0009, + ZigbeeClusterIdTime = 0x000A, + ZigbeeClusterIdRssiLocation = 0x000B, + ZigbeeClusterIdAnalogInput = 0x000C, + ZigbeeClusterIdAnalogOutput = 0x000D, + ZigbeeClusterIdAnalogValue = 0x000E, + ZigbeeClusterIdBinaryInput = 0x000F, + ZigbeeClusterIdBinaryOutput = 0x0010, + ZigbeeClusterIdBinaryValue = 0x0011, + ZigbeeClusterIdMultistateInput = 0x0012, + ZigbeeClusterIdMultistateOutput = 0x0013, + ZigbeeClusterIdMultistateValue = 0x0014, + ZigbeeClusterIdCommissoning = 0x0015, + + // Over the air uppgrade (OTA) + ZigbeeClusterIdOtaUpgrade = 0x0019, + + // Poll controll + ZigbeeClusterIdPollControl = 0x0020, + + + // Closures + ZigbeeClusterIdShadeConfiguration = 0x0100, + + // Door Lock + ZigbeeClusterIdDoorLock = 0x0101, + + // Heating, Ventilation and Air-Conditioning (HVAC) + ZigbeeClusterIdPumpConfigurationControl = 0x0200, + ZigbeeClusterIdThermostat = 0x0201, + ZigbeeClusterIdFanControll = 0x0202, + ZigbeeClusterIdDehumiditationControl = 0x0203, + ZigbeeClusterIdThermostatUserControl = 0x0204, + + // Lighting + ZigbeeClusterIdColorControl = 0x0300, + ZigbeeClusterIdBallastConfiguration = 0x0301, + + // Sensing + ZigbeeClusterIdIlluminanceMeasurement = 0x0400, + ZigbeeClusterIdIlluminanceLevelSensing = 0x0401, + ZigbeeClusterIdTemperatureMeasurement = 0x0402, + ZigbeeClusterIdPressureMeasurement = 0x0403, + ZigbeeClusterIdFlowMeasurement = 0x0404, + ZigbeeClusterIdRelativeHumidityMeasurement = 0x0405, + ZigbeeClusterIdOccupancySensing = 0x0406, + + // Security and Safty + ZigbeeClusterIdIasZone = 0x0500, + ZigbeeClusterIdIasAce = 0x0501, + ZigbeeClusterIdIasWd = 0x0502, + + // Smart energy + ZigbeeClusterIdPrice = 0x0700, + ZigbeeClusterIdDemandResponseAndLoadControl = 0x0701, + ZigbeeClusterIdMetering = 0x0702, + ZigbeeClusterIdMessaging = 0x0703, + ZigbeeClusterIdTunneling = 0x0704, + ZigbeeClusterIdKeyEstablishment = 0x0800, + + // ZLL + ZigbeeClusterIdTouchlinkCommissioning = 0x1000, + + // NXP Appliances + ZigbeeClusterIdApplianceControl = 0x001B, + ZigbeeClusterIdApplianceIdentification = 0x0B00, + ZigbeeClusterIdApplianceEventsAlerts = 0x0B02, + ZigbeeClusterIdApplianceStatistics = 0x0B03, + + // Electrical Measurement + ZigbeeClusterIdElectricalMeasurement = 0x0B04, + ZigbeeClusterIdDiagnostics = 0x0B05, + + // Zigbee green power + ZigbeeClusterIdGreenPower = 0x0021, + + // Manufacturer specific + ZigbeeClusterIdManufacturerSpecificPhilips = 0xfc00, + + }; + Q_ENUM(ZigbeeClusterId) + + + ZigbeeCluster(quint16 clusterId, ZigbeeClusterDirection direction, QObject *parent = nullptr); + + quint16 clusterId() const; + ZigbeeClusterDirection direction() const; + + Q_INVOKABLE QString clusterName() const; + +private: + quint16 m_clusterId = 0; + ZigbeeClusterDirection m_direction; +}; + +class ZigbeeNodeEndpoint: public QObject +{ + Q_OBJECT + Q_PROPERTY(quint8 endpointId READ endpointId CONSTANT) + Q_PROPERTY(QList inputClusters READ inputClusters NOTIFY inputClustersChanged) + Q_PROPERTY(QList outputClusters READ outputClusters NOTIFY outputClustersChanged) +public: + ZigbeeNodeEndpoint(quint8 endpointId, const QList &inputClusters = QList(), const QList &outputClusters = QList(), QObject *parent = nullptr); + + quint8 endpointId() const; + QList inputClusters() const; + Q_INVOKABLE ZigbeeCluster *getInputCluster(quint16 clusterId) const; + void addInputCluster(ZigbeeCluster *cluster); + + QList outputClusters() const; + Q_INVOKABLE ZigbeeCluster *getOutputCluster(quint16 clusterId) const; + void addOutputCluster(ZigbeeCluster *cluster); + +signals: + void inputClustersChanged(); + void outputClustersChanged(); + +private: + quint8 m_endpointId = 0; + QList m_inputClusters; + QList m_outputClusters; +}; + #endif // ZIGBEENODE_H diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 89cad99a..1b08b19d 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -277,5 +277,6 @@ ui/system/zwave/ZWaveNetworkPage.qml ui/system/zwave/ZWaveNetworkSettingsPage.qml ui/components/ActivityIndicator.qml + ui/system/zigbee/ZigbeeNodePage.qml diff --git a/nymea-app/ui/system/zigbee/ZigbeeAddNetworkPage.qml b/nymea-app/ui/system/zigbee/ZigbeeAddNetworkPage.qml index 1b854fca..d0d7ca7a 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeAddNetworkPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeAddNetworkPage.qml @@ -157,17 +157,17 @@ SettingsPageBase { d.pendingCommandId = -1 var props = {}; switch (error) { - case "ZigbeeErrorNoError": + case ZigbeeManager.ZigbeeErrorNoError: root.done() return; - case "ZigbeeErrorAdapterNotAvailable": + case ZigbeeManager.ZigbeeErrorAdapterNotAvailable: props.text = qsTr("The selected adapter is not available or the selected serial port configration is incorrect."); break; - case "ZigbeeErrorAdapterAlreadyInUse": + case ZigbeeManager.ZigbeeErrorAdapterAlreadyInUse: props.text = qsTr("The selected adapter is already in use."); break; default: - props.errorCode = error; + props.error = error; } var comp = Qt.createComponent("/ui/components/ErrorDialog.qml") var popup = comp.createObject(app, props) diff --git a/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml b/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml index 69337e2c..8f60a8d1 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml @@ -85,16 +85,16 @@ SettingsPageBase { d.pendingCommandId = -1 var props = {}; switch (error) { - case "ZigbeeErrorNoError": + case ZigbeeManager.ZigbeeErrorNoError: return; - case "ZigbeeErrorAdapterNotAvailable": + case ZigbeeManager.ZigbeeErrorAdapterNotAvailable: props.text = qsTr("The selected adapter is not available or the selected serial port configration is incorrect."); break; - case "ZigbeeErrorAdapterAlreadyInUse": + case ZigbeeManager.ZigbeeErrorAdapterAlreadyInUse: props.text = qsTr("The selected adapter is already in use."); break; default: - props.errorCode = error; + props.error = error; } var comp = Qt.createComponent("/ui/components/ErrorDialog.qml") var popup = comp.createObject(app, props) @@ -355,8 +355,9 @@ SettingsPageBase { } onClicked: { - var popup = nodeInfoComponent.createObject(app, {node: node, nodeThings: nodeThings}) - popup.open() + pageStack.push("ZigbeeNodePage.qml", {zigbeeManager: zigbeeManager, network: network, node: node}) +// var popup = nodeInfoComponent.createObject(app, {node: node, nodeThings: nodeThings}) +// popup.open() } } } diff --git a/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml b/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml new file mode 100644 index 00000000..4252ca1f --- /dev/null +++ b/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml @@ -0,0 +1,502 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "qrc:/ui/components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + property ZigbeeManager zigbeeManager: null + property ZigbeeNetwork network: null + property ZigbeeNode node: null + + header: NymeaHeader { + text: qsTr("ZigBee node info") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "/ui/images/delete.svg" + text: qsTr("Remove node") + onClicked: { + var popup = removeZigbeeNodeDialogComponent.createObject(app) + popup.open() + } + } + } + + busy: d.pendingCommandId != -1 + + QtObject { + id: d + property int pendingCommandId: -1 + function removeNode(networkUuid, ieeeAddress) { + d.pendingCommandId = root.zigbeeManager.removeNode(networkUuid, ieeeAddress) + } + + onPendingCommandIdChanged: { + print("pendingCommandId changed", pendingCommandId, wakeupDialog) + if (pendingCommandId == -1 && wakeupDialog != null) { + wakeupDialog.close(); + wakeupDialog.destroy(); + wakeupDialog = null + } + } + + property var wakeupDialog: null + } + + Connections { + target: root.zigbeeManager + onCreateBindingReply: { + print("**** create binding reply", error) + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + var props = {}; + switch (error) { + case ZigbeeManager.ZigbeeErrorNoError: + return; + case ZigbeeManager.ZigbeeErrorNetworkError: + props.text = qsTr("An error happened in the ZigBee network when creating the binding."); + break; + case ZigbeeManager.ZigbeeErrorTimeoutError: + props.text = qsTr("The ZigBee device did not respond. Please try again."); + break; + default: + props.error = error; + } + var comp = Qt.createComponent("/ui/components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + } + } + onRemoveBindingReply: { + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + + var props = {}; + switch (error) { + case ZigbeeManager.ZigbeeErrorNoError: + return; + case ZigbeeManager.ZigbeeErrorNetworkError: + props.text = qsTr("An error happened in the ZigBee network when removing the binding."); + break; + case ZigbeeManager.ZigbeeErrorTimeoutError: + props.text = qsTr("The ZigBee device did not respond. Please try again."); + break; + default: + props.error = error; + } + var comp = Qt.createComponent("/ui/components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + } + } + } + + ThingsProxy { + id: nodeThings + engine: _engine + paramsFilter: {"ieeeAddress": root.node.ieeeAddress} + } + + property int signalStrength: node ? Math.round(node.lqi * 100.0 / 255.0) : 0 + + NymeaItemDelegate { + Layout.fillWidth: true + text: root.node.model + subText: qsTr("Model") + prominentSubText: false + progressive: false + } + NymeaItemDelegate { + prominentSubText: false + progressive: false + Layout.fillWidth: true + text: root.node.manufacturer + subText: qsTr("Manufacturer") + } + + NymeaItemDelegate { + prominentSubText: false + progressive: false + Layout.fillWidth: true + text: root.node.ieeeAddress + subText: qsTr("IEEE address") + font: Style.smallFont + } + NymeaItemDelegate { + prominentSubText: false + progressive: false + Layout.fillWidth: true + text: "0x" + root.node.networkAddress.toString(16) + subText: qsTr("Network address") + } + NymeaItemDelegate { + prominentSubText: false + progressive: false + Layout.fillWidth: true + text: (root.node.lqi * 100 / 255).toFixed(0) + " %" + subText: qsTr("Signal strength") + } + NymeaItemDelegate { + prominentSubText: false + progressive: false + Layout.fillWidth: true + text: root.node.version.length > 0 ? root.node.version : qsTr("Unknown") + subText: qsTr("Version") + } + +// NymeaItemDelegate { +// Layout.fillWidth: true +// text: qsTr("Device endpoints") +// subText: qsTr("Show detailed information about the node") +// onClicked: pageStack.push(endpointsPageComponent) +// } + + SettingsPageSectionHeader { + text: qsTr("Associated things") + } + + Repeater { + model: nodeThings + delegate: NymeaItemDelegate { + Layout.fillWidth: true + property Thing thing: nodeThings.get(index) + iconName: app.interfacesToIcon(thing.thingClass.interfaces) + text: thing.name + onClicked: pageStack.push("/ui/thingconfiguration/ConfigureThingPage.qml", {thing: thing}) + } + +// delegate: RowLayout { +// id: thingDelegate +// Layout.leftMargin: Style.margins +// Layout.rightMargin: Style.margins +// property Thing thing: nodeThings.get(index) +// Layout.fillWidth: true +// ColorIcon { +// size: Style.iconSize +// source: app.interfacesToIcon(thing.thingClass.interfaces) +// color: Style.accentColor +// } +// TextField { +// text: thingDelegate.thing.name +// Layout.fillWidth: true +// onEditingFinished: engine.thingManager.editThing(thingDelegate.thing.id, text) +// } +// } + } + + + SettingsPageSectionHeader { + text: qsTr("Bindings") + } + + property ZigbeeNode coordinatorNode: root.network.nodes.getNodeByNetworkAddress(0) + Repeater { + model: root.node.bindings + delegate: NymeaSwipeDelegate { + id: bindingDelegate + Layout.fillWidth: true + property ZigbeeNodeBinding binding: root.node.bindings[index] + ThingsProxy { + id: destinationThings + engine: _engine + paramsFilter: {"ieeeAddress": bindingDelegate.binding.destinationAddress} + } + + canDelete: true + progressive: false + text: { + if (destinationThings.count > 0) { + return destinationThings.get(0).name + } + if (binding.destinationAddress != "") { + if (binding.destinationAddress == coordinatorNode.ieeeAddress) { + return Configuration.systemName + } + return binding.destinationAddress + } + return qsTr("Group: 0x%1").arg(NymeaUtils.pad(binding.groupAddress.toString(16), 4)) + } + property ZigbeeNodeEndpoint endpoint: root.node.getEndpoint(binding.sourceEndpointId); + property ZigbeeCluster inputCluster: endpoint ? endpoint.getInputCluster(binding.clusterId) : null + property ZigbeeCluster outputCluster: endpoint ? endpoint.getOutputCluster(binding.clusterId) : null + property ZigbeeCluster usedCluster: inputCluster ? inputCluster : outputCluster + subText: { + var ret = usedCluster.clusterName(); + if (binding.destinationAddress != "") { + ret += " (" + binding.sourceEndpointId + " -> " + binding.destinationEndpointId + ")" + } + return ret; + } + onDeleteClicked: { + if (!node.rxOnWhenIdle) { + d.wakeupDialog = wakeupDialogComponent.createObject(root) + d.wakeupDialog.open() + } + d.pendingCommandId = zigbeeManager.removeBinding(network.networkUuid, binding) + } + } + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + text: qsTr("Add binding") + onClicked: { + var dialog = addBindingComponent.createObject(root) + dialog.open() + + } + } + + + + Component { + id: removeZigbeeNodeDialogComponent + + MeaDialog { + id: removeZigbeeNodeDialog + + property ZigbeeNode zigbeeNode + + headerIcon: "/ui/images/zigbee.svg" + title: qsTr("Remove ZigBee node") + " " + (zigbeeNode ? zigbeeNode.model : "") + text: qsTr("Are you sure you want to remove this node from the network?") + standardButtons: Dialog.Ok | Dialog.Cancel + + Label { + text: qsTr("Please note that if this node has been assigned to a thing, it will also be removed from the system.") + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + onAccepted: { + d.removeNode(zigbeeNode.networkUuid, zigbeeNode.ieeeAddress) + } + } + } + + Component { + id: endpointsPageComponent + SettingsPageBase { + title: qsTr("Device endpoints") + + Repeater { + model: root.node.endpoints + delegate: ColumnLayout { + id: endpointDelegate + property ZigbeeNodeEndpoint endpoint: root.node.endpoints[index] + Label { + Layout.fillWidth: true + text: "- " + qsTr("Endpoint %1").arg(endpointDelegate.endpoint.endpointId) + } + Label { + Layout.fillWidth: true + text: " " + qsTr("Input clusters") + } + + Repeater { + model: endpointDelegate.endpoint.inputClusters + delegate: Label { + Layout.fillWidth: true + property ZigbeeCluster cluster: endpointDelegate.endpoint.inputClusters[index] + text: " - " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")" + } + } + Label { + Layout.fillWidth: true + text: " " + qsTr("Output clusters") + } + + Repeater { + model: endpointDelegate.endpoint.outputClusters + delegate: Label { + Layout.fillWidth: true + property ZigbeeCluster cluster: endpointDelegate.endpoint.outputClusters[index] + text: " - " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")" + } + } + } + } + } + } + + + Component { + id: addBindingComponent + MeaDialog { + title: qsTr("Add binding") + + Label { + text: qsTr("Source endpoint") + Layout.fillWidth: true + } + ComboBox { + Layout.fillWidth: true + id: sourceEndpointComboBox + model: root.node.endpoints + textRole: "endpointId" + displayText: currentEndpoint.endpointId + property ZigbeeNodeEndpoint currentEndpoint: root.node.endpoints[currentIndex] + onCurrentEndpointChanged: print("source endpoint changed", currentEndpoint.endpointId) + } + Label { + text: qsTr("Target node") + Layout.fillWidth: true + } + ComboBox { + id: destinationNodeComboBox + Layout.fillWidth: true + model: network.nodes + displayText: currentDestinationNodeThings.count > 0 + ? currentDestinationNodeThings.get(0).name + : currentNode == root.coordinatorNode + ? Configuration.systemName + : currentNode.model + property ZigbeeNode currentNode: network.nodes.get(currentIndex) + ThingsProxy { + id: currentDestinationNodeThings + engine: _engine + paramsFilter: {"ieeeAddress": destinationNodeDelegate.node.ieeeAddress} + } + delegate: ItemDelegate { + id: destinationNodeDelegate + property ZigbeeNode node: network.nodes.get(index) + width: parent.width + text: destinationNodeThings.count > 0 + ? destinationNodeThings.get(0).name + : node == root.coordinatorNode + ? Configuration.systemName + : node.model + ThingsProxy { + id: destinationNodeThings + engine: _engine + paramsFilter: {"ieeeAddress": destinationNodeDelegate.node.ieeeAddress} + } + } + } + Label { + text: qsTr("Destination endpoint") + Layout.fillWidth: true + } + ComboBox { + id: destinationEndpointComboBox + Layout.fillWidth: true + model: destinationNodeComboBox.currentNode.endpoints + textRole: "endpointId" + displayText: currentEndpoint.endpointId + property ZigbeeNodeEndpoint currentEndpoint: destinationNodeComboBox.currentNode.endpoints[currentIndex] + } + + Label { + text: qsTr("Cluster") + Layout.fillWidth: true + } + + ComboBox { + id: clusterComboBox + Layout.fillWidth: true + delegate: ItemDelegate { + width: parent.width + text: modelData.clusterName() + } + model: { + var ret = [] + print("updating clusters", sourceEndpointComboBox.currentEndpoint, destinationNodeComboBox.currentNode, destinationEndpointComboBox.currentEndpoint) + if (!sourceEndpointComboBox.currentEndpoint || !destinationNodeComboBox.currentNode || !destinationEndpointComboBox.currentEndpoint) { + return ret; + } + + for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) { + var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i] + print("source has cluster", outputCluster.clusterId) + for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.inputClusters.length; j++) { + var inputCluster = destinationEndpointComboBox.currentEndpoint.inputClusters[j] + print("destination has cluster", inputCluster.clusterId) + if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) { + ret.push(outputCluster); + break; + } + } + } + for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) { + var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i] + print("source has cluster", inputCluster.clusterId) + for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.outputClusters.length; j++) { + var outputCluster = destinationEndpointComboBox.currentEndpoint.outputClusters[j] + print("destination has cluster", outputCluster.clusterId) + if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) { + ret.push(inputCluster); + break; + } + } + } + + return ret + } + property ZigbeeCluster currentCluster: currentValue + displayText: currentValue.clusterName() + } + + + onAccepted: { + d.pendingCommandId = root.zigbeeManager.createBinding( + root.network.networkUuid, + root.node.ieeeAddress, + sourceEndpointComboBox.currentEndpoint.endpointId, + clusterComboBox.currentCluster.clusterId, + destinationNodeComboBox.currentNode.ieeeAddress, + destinationEndpointComboBox.currentEndpoint.endpointId) + + if (!root.node.rxOnWhenIdle) { + d.wakeupDialog = wakeupDialogComponent.createObject(root) + d.wakeupDialog.open() + } + } + } + } + + Component { + id: wakeupDialogComponent + MeaDialog { + id: wakeupDialog + title: qsTr("Wake up %1").arg(root.node.model) + text: qsTr("The selected node is a sleepy device. Please wake up the device by pressing a button.") + } + } +} diff --git a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml index a1cdb140..9e18f15c 100644 --- a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml +++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml @@ -121,23 +121,26 @@ SettingsPageBase { text: qsTr("Information") } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true - text: qsTr("Vendor:") - subText: engine.thingManager.vendors.getVendor(root.thing.thingClass.vendorId).displayName + text: engine.thingManager.vendors.getVendor(root.thing.thingClass.vendorId).displayName + subText: qsTr("Vendor") + prominentSubText: false progressive: false } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true - text: qsTr("Type:") - subText: root.thing.thingClass.displayName + text: root.thing.thingClass.displayName + subText: qsTr("Type") + prominentSubText: false progressive: false } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true - text: qsTr("ID:") - subText: root.thing.id.toString().replace(/[{}]/g, "") + text: root.thing.id.toString().replace(/[{}]/g, "") + subText: qsTr("ID") + prominentSubText: false progressive: false onClicked: { PlatformHelper.toClipBoard(root.thing.id.toString().replace(/[{}]/g, "")); @@ -145,7 +148,7 @@ SettingsPageBase { } } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true text: qsTr("Thing class") subText: qsTr("View the type definition for this thing")