diff --git a/debian/control b/debian/control
index 3bcefef0..a3c0e990 100644
--- a/debian/control
+++ b/debian/control
@@ -6,6 +6,7 @@ Build-depends: libboblight-dev,
debhelper (>= 0.0.0),
libnymea1-dev (>= 0.17),
libnymea-mqtt-dev,
+ libnymea-zigbee-dev,
libqt5serialport5-dev,
libqt5websockets5-dev,
nymea-dev-tools:native,
@@ -1043,6 +1044,86 @@ Description: nymea.io plugin to connect to your Tado account
This package will install the nymea.io plugin for Tado
+Package: nymea-plugin-zigbee-lumi
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libnymea-zigbee1,
+ nymea-plugins-translations,
+Description: nymea.io zigbee plugin for lumi/aquara/xiaomi things
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for Lumi
+
+
+Package: nymea-plugin-zigbee-generic
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libnymea-zigbee1,
+ nymea-plugins-translations,
+Description: nymea.io zigbee plugin for different generic recognizable devices
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for generic recognizable zigbee devices
+
+
+Package: nymea-plugin-zigbee-generic-lights
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libnymea-zigbee1,
+ nymea-plugins-translations,
+Description: nymea.io zigbee plugin for different generic recognizable lights
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for generic recognizable lights
+
+
+Package: nymea-plugin-zigbee-philipshue
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libnymea-zigbee1,
+ nymea-plugins-translations,
+Description: nymea.io zigbee plugin for Philips Hue devices
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for Philips Hue zigbee devices
+
+
+Package: nymea-plugin-zigbee-tradfri
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libnymea-zigbee1,
+ nymea-plugins-translations,
+Description: nymea.io zigbee plugin for Ikea TRADRFI devices
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for Ikea TRADFRI zigbee devices
+
+
Package: nymea-plugins-translations
Section: misc
Architecture: all
@@ -1101,6 +1182,7 @@ Depends: nymea-plugin-anel,
nymea-plugin-keba,
nymea-plugin-unifi,
nymea-plugin-usbrelay,
+ nymea-plugins-zigbee,
Replaces: guh-plugins
Description: Plugins for nymea IoT server - the default plugin collection
The nymea daemon is a plugin based IoT (Internet of Things) server. The
@@ -1111,6 +1193,23 @@ Description: Plugins for nymea IoT server - the default plugin collection
.
This package will install the default nymea plugins.
+Package: nymea-plugins-zigbee
+Section: libs
+Architecture: all
+Depends: nymea-plugin-zigbee-lumi,
+ nymea-plugin-zigbee-generic,
+ nymea-plugin-zigbee-generic-lights,
+ nymea-plugin-zigbee-philipshue,
+ nymea-plugin-zigbee-tradfri
+Description: Zigbee plugins for nymea IoT server - meta package for all zigbee replated plugins
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install all nymea zigbee related plugins.
+
Package: nymea-plugins-maker
Section: libs
diff --git a/debian/nymea-plugin-zigbee-generic-lights.install.in b/debian/nymea-plugin-zigbee-generic-lights.install.in
new file mode 100644
index 00000000..f46b810b
--- /dev/null
+++ b/debian/nymea-plugin-zigbee-generic-lights.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeegenericlights.so
diff --git a/debian/nymea-plugin-zigbee-generic.install.in b/debian/nymea-plugin-zigbee-generic.install.in
new file mode 100644
index 00000000..e36eada3
--- /dev/null
+++ b/debian/nymea-plugin-zigbee-generic.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeegeneric.so
diff --git a/debian/nymea-plugin-zigbee-lumi.install.in b/debian/nymea-plugin-zigbee-lumi.install.in
new file mode 100644
index 00000000..06ebdbf6
--- /dev/null
+++ b/debian/nymea-plugin-zigbee-lumi.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeelumi.so
diff --git a/debian/nymea-plugin-zigbee-philipshue.install.in b/debian/nymea-plugin-zigbee-philipshue.install.in
new file mode 100644
index 00000000..80a4b9ac
--- /dev/null
+++ b/debian/nymea-plugin-zigbee-philipshue.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeephilipshue.so
diff --git a/debian/nymea-plugin-zigbee-tradfri.install.in b/debian/nymea-plugin-zigbee-tradfri.install.in
new file mode 100644
index 00000000..9f7f026c
--- /dev/null
+++ b/debian/nymea-plugin-zigbee-tradfri.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeetradfri.so
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 64430fe5..30260ca7 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -66,6 +66,11 @@ PLUGIN_DIRS = \
wakeonlan \
wemo \
ws2812fx \
+ zigbeegeneric \
+ zigbeegenericlights \
+ zigbeelumi \
+ zigbeephilipshue \
+ zigbeetradfri \
message(============================================)
diff --git a/zigbeegeneric/README.md b/zigbeegeneric/README.md
new file mode 100644
index 00000000..0fb2067c
--- /dev/null
+++ b/zigbeegeneric/README.md
@@ -0,0 +1,25 @@
+# Generic ZigBee
+
+This plugin allows to interact with ZigBee from many different manufacturers and different types using a native ZigBee network controller in nymea.
+
+There is no specific list of supported manufacturers and models, because the plugin handles ZigBee certified devices in the most generic way possible. The manufacturer and model will be fetched from the ZigBee node and all requied information will be loaded dynamically from the ZigBee node according to the specifications.
+
+If no manufacturer specific plugin handles a new joined ZigBee node, this plugin will try to support the device. If the device can be handled in a generic way, this plugin will create an approriate thing in nymea.
+
+## Supported Things
+
+In order to bring a ZigBee device into the nymea ZigBee network, the network needs to be opened for joining before you perform the device pairing instructions. The joining process can take up to 30 seconds. If the device does not show up, please restart the pairing process.
+
+### On/off power sockets
+
+Simple on/off power sockets.
+
+**Pairing instructions**: Open the ZigBee network for joining. Follow the manufacturer specific reset/pairing instruction.
+
+> Most power sockets have a pairing button. Once the device is powered, it can be resetted / paired by clicking the button multiple times of keeping it pressed for several seconds.
+
+
+## Requirements
+
+* A compatible ZigBee controller and a running ZigBee network in nymea. You can find more information about supported controllers and ZigBee network configurations [here](https://nymea.io/documentation/users/usage/configuration#zigbee).
+
diff --git a/zigbeegeneric/integrationpluginzigbeegeneric.cpp b/zigbeegeneric/integrationpluginzigbeegeneric.cpp
new file mode 100644
index 00000000..c2fac542
--- /dev/null
+++ b/zigbeegeneric/integrationpluginzigbeegeneric.cpp
@@ -0,0 +1,533 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginzigbeegeneric.h"
+#include "plugininfo.h"
+#include "hardware/zigbee/zigbeehardwareresource.h"
+
+#include "zcl/hvac/zigbeeclusterthermostat.h"
+
+#include
+
+IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric()
+{
+ m_ieeeAddressParamTypeIds[thermostatThingClassId] = thermostatThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[powerSocketThingClassId] = powerSocketThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[doorLockThingClassId] = doorLockThingIeeeAddressParamTypeId;
+
+ m_networkUuidParamTypeIds[thermostatThingClassId] = thermostatThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[powerSocketThingClassId] = powerSocketThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[doorLockThingClassId] = doorLockThingNetworkUuidParamTypeId;
+
+ m_endpointIdParamTypeIds[thermostatThingClassId] = thermostatThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[powerSocketThingClassId] = powerSocketThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[doorLockThingClassId] = doorLockThingEndpointIdParamTypeId;
+
+ m_manufacturerIdParamTypeIds[thermostatThingClassId] = thermostatThingManufacturerParamTypeId;
+ m_manufacturerIdParamTypeIds[powerSocketThingClassId] = powerSocketThingManufacturerParamTypeId;
+ m_manufacturerIdParamTypeIds[doorLockThingClassId] = doorLockThingManufacturerParamTypeId;
+
+ m_modelIdParamTypeIds[thermostatThingClassId] = thermostatThingModelParamTypeId;
+ m_modelIdParamTypeIds[powerSocketThingClassId] = powerSocketThingModelParamTypeId;
+ m_modelIdParamTypeIds[doorLockThingClassId] = doorLockThingModelParamTypeId;
+
+ m_connectedStateTypeIds[thermostatThingClassId] = thermostatConnectedStateTypeId;
+ m_connectedStateTypeIds[powerSocketThingClassId] = powerSocketConnectedStateTypeId;
+ m_connectedStateTypeIds[doorLockThingClassId] = doorLockConnectedStateTypeId;
+
+ m_signalStrengthStateTypeIds[thermostatThingClassId] = thermostatSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[powerSocketThingClassId] = powerSocketSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[doorLockThingClassId] = doorLockSignalStrengthStateTypeId;
+
+ m_versionStateTypeIds[thermostatThingClassId] = thermostatVersionStateTypeId;
+ m_versionStateTypeIds[powerSocketThingClassId] = powerSocketVersionStateTypeId;
+ m_versionStateTypeIds[doorLockThingClassId] = doorLockVersionStateTypeId;
+}
+
+QString IntegrationPluginZigbeeGeneric::name() const
+{
+ return "Generic";
+}
+
+bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ bool handled = false;
+ foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
+ qCDebug(dcZigbeeGeneric()) << "Checking node endpoint:" << endpoint->endpointId() << endpoint->deviceId();
+
+ // Check thermostat
+ if (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation &&
+ endpoint->deviceId() == Zigbee::HomeAutomationDeviceThermostat) {
+ qCDebug(dcZigbeeGeneric()) << "Handeling thermostat endpoint for" << node << endpoint;
+ createThing(thermostatThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+
+ // Check on/off plug
+ if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink &&
+ endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOffPlugin) ||
+ (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation &&
+ endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffPlugin)) {
+
+ qCDebug(dcZigbeeGeneric()) << "Handeling power socket endpoint for" << node << endpoint;
+ createThing(powerSocketThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+
+ // Check door lock
+ if (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceDoorLock) {
+ if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration) ||
+ !endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)) {
+ qCWarning(dcZigbeeGeneric()) << "Endpoint claims to be a door lock, but the appropriate input clusters could not be found" << node << endpoint;
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Handeling door lock endpoint for" << node << endpoint;
+ createThing(doorLockThingClassId, networkUuid, node, endpoint);
+ // Initialize bindings and cluster attributes
+ initializeDoorLock(node, endpoint);
+ handled = true;
+ }
+ }
+ }
+
+ return handled;
+}
+
+void IntegrationPluginZigbeeGeneric::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ Q_UNUSED(networkUuid)
+ Thing *thing = m_thingNodes.key(node);
+ if (thing) {
+ qCDebug(dcZigbeeGeneric()) << node << "for" << thing << "has left the network.";
+ emit autoThingDisappeared(thing->id());
+
+ // Removing it from our map to prevent a loop that would ask the zigbee network to remove this node (see thingRemoved())
+ m_thingNodes.remove(thing);
+ }
+}
+
+void IntegrationPluginZigbeeGeneric::init()
+{
+ hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeCatchAll);
+}
+
+void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ qCDebug(dcZigbeeGeneric()) << "Nework uuid:" << networkUuid;
+ ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_ieeeAddressParamTypeIds.value(thing->thingClassId())).toString());
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node) {
+ node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress);
+ }
+
+ if (!node) {
+ qCWarning(dcZigbeeGeneric()) << "Zigbee node for" << info->thing()->name() << "not found.´";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+ m_thingNodes.insert(thing, node);
+
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find endpoint for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ // Update connected state
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), node->reachable());
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), reachable);
+ });
+
+ // Update signal strength
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), qRound(node->lqi() * 100.0 / 255.0));
+ connect(node, &ZigbeeNode::lqiChanged, thing, [this, thing](quint8 lqi){
+ uint signalStrength = qRound(lqi * 100.0 / 255.0);
+ qCDebug(dcZigbeeGeneric()) << thing << "signal strength changed" << signalStrength << "%";
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength);
+ });
+
+ // Set the version
+ thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId());
+
+ // Type specific setup
+ if (thing->thingClassId() == thermostatThingClassId) {
+ ZigbeeClusterThermostat *thermostatCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdThermostat);
+ if (!thermostatCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to read thermostat cluster";
+ return;
+ }
+// thermostatCluster->attribute(ZigbeeClusterLibrary::ClusterIdThermostat);
+
+ // We need to read them from the lamp
+ ZigbeeClusterReply *reply = thermostatCluster->readAttributes({ZigbeeClusterThermostat::AttributeLocalTemperature, ZigbeeClusterThermostat::AttributeOccupiedHeatingSetpoint});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Reading loacal temperature attribute finished with error" << reply->error();
+ return;
+ }
+
+ QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload);
+
+ foreach (const ZigbeeClusterLibrary::ReadAttributeStatusRecord &record, attributeStatusRecords) {
+ if (record.attributeId == ZigbeeClusterThermostat::AttributeLocalTemperature) {
+ bool valueOk = false;
+ quint16 localTemperature = record.dataType.toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to read local temperature" << attributeStatusRecords;
+ return;
+ }
+ thing->setStateValue(thermostatTemperatureStateTypeId, localTemperature * 0.01);
+ }
+
+ if (record.attributeId == ZigbeeClusterThermostat::AttributeOccupiedHeatingSetpoint) {
+ bool valueOk = false;
+ quint16 targetTemperature = record.dataType.toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to read local temperature" << attributeStatusRecords;
+ return;
+ }
+ thing->setStateValue(thermostatTargetTemperatureStateTypeId, targetTemperature * 0.01);
+ }
+ }
+
+
+
+ });
+ }
+
+ if (thing->thingClassId() == powerSocketThingClassId) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (onOffCluster) {
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(powerSocketPowerStateTypeId, onOffCluster->power());
+ }
+
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeGeneric()) << thing << "power changed" << power;
+ thing->setStateValue(powerSocketPowerStateTypeId, power);
+ });
+
+ connect(node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){
+ if (reachable) {
+ ZigbeeClusterReply *reply = onOffCluster->readAttributes({ZigbeeClusterOnOff::AttributeOnOff});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Reading attribute from" << thing << "finished with error" << reply->error();
+ }
+ // Note: the state will be updated using the power changed signal from the cluster
+ });
+ }
+ });
+ } else {
+ qCWarning(dcZigbeeGeneric()) << "Could not find the OnOff input cluster on" << thing << endpoint;
+ }
+ }
+
+ if (thing->thingClassId() == doorLockThingClassId) {
+
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find power configuration cluster on" << thing << endpoint;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(doorLockBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(doorLockBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeeGeneric()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(doorLockBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(doorLockBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+
+ // Get door state changes
+ ZigbeeClusterDoorLock *doorLockCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock);
+ if (!doorLockCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find door lock cluster on" << thing << endpoint;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (doorLockCluster->hasAttribute(ZigbeeClusterDoorLock::AttributeDoorState)) {
+ qCDebug(dcZigbeeGeneric()) << thing << doorLockCluster->doorState();
+ // TODO: check if we can use smart lock and set appropriate state
+ }
+
+ connect(doorLockCluster, &ZigbeeClusterDoorLock::lockStateChanged, thing, [=](ZigbeeClusterDoorLock::LockState lockState){
+ qCDebug(dcZigbeeGeneric()) << thing << "lock state changed" << lockState;
+ // TODO: check if we can use smart lock and set appropriate state
+ });
+ }
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginZigbeeGeneric::executeAction(ThingActionInfo *info)
+{
+ if (!hardwareManager()->zigbeeResource()->available()) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // Get the node
+ Thing *thing = info->thing();
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node->reachable()) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // Get the endpoint
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ if (thing->thingClassId() == powerSocketThingClassId) {
+ if (info->action().actionTypeId() == powerSocketAlertActionTypeId) {
+ ZigbeeClusterIdentify *identifyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIdentify);
+ if (!identifyCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find identify cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Send the command trough the network
+ ZigbeeClusterReply *reply = identifyCluster->identify(2);
+ connect(reply, &ZigbeeClusterReply::finished, this, [reply, info](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ }
+ });
+ return;
+ }
+
+ if (info->action().actionTypeId() == powerSocketPowerActionTypeId) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find on/off cluster for" << thing << "in" << endpoint;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Send the command trough the network
+ bool power = info->action().param(powerSocketPowerActionPowerParamTypeId).value().toBool();
+ qCDebug(dcZigbeeGeneric()) << "Set power for" << thing << "to" << power;
+ ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff());
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to set power on" << thing << reply->error();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ qCDebug(dcZigbeeGeneric()) << "Set power finished successfully for" << thing;
+ thing->setStateValue(powerSocketPowerStateTypeId, power);
+ }
+ });
+ return;
+ }
+ }
+
+ if (thing->thingClassId() == doorLockThingClassId) {
+ if (info->action().actionTypeId() == doorLockOpenActionTypeId) {
+ ZigbeeClusterDoorLock *doorLockCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock);
+ if (!doorLockCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find door lock cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Send the command trough the network
+ ZigbeeClusterReply *reply = doorLockCluster->unlockDoor();
+ connect(reply, &ZigbeeClusterReply::finished, this, [reply, info](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ }
+ });
+ return;
+ }
+
+ if (info->action().actionTypeId() == doorLockCloseActionTypeId) {
+ ZigbeeClusterDoorLock *doorLockCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock);
+ if (!doorLockCluster) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find door lock cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Send the command trough the network
+ ZigbeeClusterReply *reply = doorLockCluster->lockDoor();
+ connect(reply, &ZigbeeClusterReply::finished, this, [reply, info](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ }
+ });
+ return;
+ }
+ }
+
+ info->finish(Thing::ThingErrorUnsupportedFeature);
+}
+
+void IntegrationPluginZigbeeGeneric::thingRemoved(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.take(thing);
+ if (node) {
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node);
+ }
+}
+
+ZigbeeNodeEndpoint *IntegrationPluginZigbeeGeneric::findEndpoint(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node) {
+ qCWarning(dcZigbeeGeneric()) << "Could not find the node for" << thing;
+ return nullptr;
+ }
+
+ quint8 endpointId = thing->paramValue(m_endpointIdParamTypeIds.value(thing->thingClassId())).toUInt();
+ return node->getEndpoint(endpointId);
+}
+
+void IntegrationPluginZigbeeGeneric::createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+ ThingDescriptor descriptor(thingClassId);
+ QString deviceClassName = supportedThings().findById(thingClassId).displayName();
+ descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier()));
+
+ ParamList params;
+ params.append(Param(m_networkUuidParamTypeIds[thingClassId], networkUuid.toString()));
+ params.append(Param(m_ieeeAddressParamTypeIds[thingClassId], node->extendedAddress().toString()));
+ params.append(Param(m_endpointIdParamTypeIds[thingClassId], endpoint->endpointId()));
+ params.append(Param(m_modelIdParamTypeIds[thingClassId], endpoint->modelIdentifier()));
+ params.append(Param(m_manufacturerIdParamTypeIds[thingClassId], endpoint->manufacturerName()));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+}
+
+void IntegrationPluginZigbeeGeneric::initializeDoorLock(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+ qCDebug(dcZigbeeGeneric()) << "Read power configuration cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to read power configuration cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Read power configuration cluster attributes finished successfully";
+ }
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeeGeneric()) << "Bind power configuration cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Bind power configuration cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute rporting for battery remaining (0.5 % changes = 1)
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
+ reportingConfig.dataType = Zigbee::Uint8;
+ reportingConfig.minReportingInterval = 60; // for production use 300;
+ reportingConfig.maxReportingInterval = 120; // for production use 2700;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeeGeneric()) << "Configure attribute reporting for power configuration cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+ // Configure door lock attribute reporting and read initial values
+ qCDebug(dcZigbeeGeneric()) << "Read door lock cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)->readAttributes({ZigbeeClusterDoorLock::AttributeDoorState, ZigbeeClusterDoorLock::AttributeLockType});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to read door lock attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Read door lock cluster attributes finished successfully";
+ }
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeeGeneric()) << "Bind door lock cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdDoorLock, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to door lock cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Bind door lock cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute reporting for lock state
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterDoorLock::AttributeLockState;
+ reportingConfig.dataType = Zigbee::Enum8;
+ reportingConfig.minReportingInterval = 60;
+ reportingConfig.maxReportingInterval = 120;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeeGeneric()) << "Configure attribute reporting for door lock cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGeneric()) << "Failed to door lock cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeeGeneric()) << "Attribute reporting configuration finished for door lock cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/zigbeegeneric/integrationpluginzigbeegeneric.h b/zigbeegeneric/integrationpluginzigbeegeneric.h
new file mode 100644
index 00000000..788c7d26
--- /dev/null
+++ b/zigbeegeneric/integrationpluginzigbeegeneric.h
@@ -0,0 +1,80 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINZIGBEEGENERIC_H
+#define INTEGRATIONPLUGINZIGBEEGENERIC_H
+
+#include "integrations/integrationplugin.h"
+#include "hardware/zigbee/zigbeehandler.h"
+#include "plugintimer.h"
+
+#include
+
+class IntegrationPluginZigbeeGeneric: public IntegrationPlugin, public ZigbeeHandler
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeegeneric.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginZigbeeGeneric();
+
+ QString name() const override;
+ bool handleNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+ void handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+
+ void init() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ QHash m_thingNodes;
+
+ QHash m_ieeeAddressParamTypeIds;
+ QHash m_networkUuidParamTypeIds;
+ QHash m_endpointIdParamTypeIds;
+ QHash m_modelIdParamTypeIds;
+ QHash m_manufacturerIdParamTypeIds;
+
+ QHash m_connectedStateTypeIds;
+ QHash m_signalStrengthStateTypeIds;
+ QHash m_versionStateTypeIds;
+
+ // Get the endpoint for the given thing
+ ZigbeeNodeEndpoint *findEndpoint(Thing *thing);
+ void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+
+ void initializeDoorLock(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+
+};
+
+#endif // INTEGRATIONPLUGINZIGBEEGENERIC_H
diff --git a/zigbeegeneric/integrationpluginzigbeegeneric.json b/zigbeegeneric/integrationpluginzigbeegeneric.json
new file mode 100644
index 00000000..5c9a13b3
--- /dev/null
+++ b/zigbeegeneric/integrationpluginzigbeegeneric.json
@@ -0,0 +1,316 @@
+{
+ "name": "ZigbeeGeneric",
+ "displayName": "Zigbee Generic",
+ "id": "6a4343be-9fd6-4015-9ff5-38542651c534",
+ "vendors": [
+ {
+ "name": "nymea",
+ "displayName": "nymea",
+ "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
+ "thingClasses": [
+ {
+ "id": "ca9af6cf-2d15-4d54-ba07-3d2ce03445b8",
+ "name": "thermostat",
+ "displayName": "Zigbee Thermostat",
+ "createMethods": ["auto"],
+ "interfaces": ["thermostat", "temperaturesensor", "wirelessconnectable"],
+ "paramTypes": [
+ {
+ "id": "f38746d8-0084-43a3-b645-3ec743ea5fbc",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "4a92b536-de4c-4701-8117-9bb26dd51c3e",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "138a529d-1d85-410a-9d83-93ebc9a5f8ca",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "ae76acb0-40f4-440b-8ae7-16969fe6c91a",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "4262cfc6-4b04-49fd-af2b-765afca53b0b",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "88ad3957-2912-4de1-b53d-b360565dd361",
+ "name": "targetTemperature",
+ "displayName": "Target temperature",
+ "displayNameEvent": "Target temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "minValue": 0,
+ "maxValue": 50,
+ "defaultValue": 0
+ },
+ {
+ "id": "497af03a-a893-438c-aba2-1bf3ecfc66c5",
+ "name": "temperature",
+ "displayName": "Current temperature",
+ "displayNameEvent": "Current temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0
+ },
+ {
+ "id": "e9fb2b10-96da-4b70-afda-46e948399af8",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected/disconnected",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "8f0512ab-ced2-4dcb-90fe-aaa532efd0dd",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "c8bd12d2-b425-4422-9e0e-a65542a47b70",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ }
+ ]
+ },
+ {
+ "name": "powerSocket",
+ "displayName": "Power socket",
+ "id": "800a8df8-06cb-4d93-8334-944fcce9651a",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "powersocket", "alert", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "1db22159-08a3-43d0-b515-7b34211b1d04",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "26e92d1d-bc2f-4ccd-ad1f-d6d6189d2609",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "c7cbda61-2b84-4f2f-a40d-bcbe0efad08c",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "8daa4709-1dc1-4717-a529-b78e32002b59",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "ad03bc04-857a-43a9-8bea-a3c9db24461a",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "b5abd47e-95f1-4e35-94fa-be87c396073a",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "21889797-32c0-4f1b-903d-8af71981882b",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "41c74445-4d2d-46dc-8a63-c98f1df8e742",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "2c8268b9-d76b-415a-b2e9-6bfeabd98a76",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true,
+ "ioType": "digitalOutput"
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "4e3b1430-d98e-4f05-bae6-301dac34bd4b",
+ "name": "alert",
+ "displayName": "Identify"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "doorLock",
+ "displayName": "Door lock",
+ "id": "34cb3d09-dd9f-4b95-95d0-30a1cd94adac",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "simpleclosable", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "484bf80f-5d20-4029-b05e-6b4c9fbefd69",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "4d50cbd3-f297-421c-9938-65ec0bb4bd34",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "ebf17460-4a37-461f-aa24-e0a9a4238c63",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "1b177eb8-a13f-4975-92a7-a6bf54670c8f",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "b13f86a7-dc65-4ed6-860c-cdb099f90916",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "206b0508-b477-4aa2-b420-aba2259fb6e6",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "6c8f8db5-464c-408a-9c65-4e8096663019",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "9e27850b-99d8-40df-9bc7-4b3c7c01faf8",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "568e5bdd-47f3-4ccb-a1d8-ff3a5ea87ad8",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "89abea26-b772-4258-9b56-e026b80c2028",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "6e112e3b-080f-47e4-8e2b-453029f0eacb",
+ "name": "open",
+ "displayName": "Unlock door"
+ },
+ {
+ "id": "c26e1908-25d0-4475-8f82-5aaf034640f1",
+ "name": "close",
+ "displayName": "Lock door"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/zigbeegeneric/meta.json b/zigbeegeneric/meta.json
new file mode 100644
index 00000000..5b692488
--- /dev/null
+++ b/zigbeegeneric/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "ZigBee Generic",
+ "tagline": "Connect and control native ZigBee devices which can be handled in a generic way.",
+ "icon": "zigbee.svg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "zigbee"
+ ],
+ "categories": [
+ "socket"
+ ]
+}
diff --git a/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts
new file mode 100644
index 00000000..a3062199
--- /dev/null
+++ b/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts
@@ -0,0 +1,301 @@
+
+
+
+
+ ZigbeeGeneric
+
+
+
+ Battery
+ The name of the ParamType (ThingClass: doorLock, EventType: batteryLevel, ID: {568e5bdd-47f3-4ccb-a1d8-ff3a5ea87ad8})
+----------
+The name of the StateType ({568e5bdd-47f3-4ccb-a1d8-ff3a5ea87ad8}) of ThingClass doorLock
+
+
+
+
+ Battery changed
+ The name of the EventType ({568e5bdd-47f3-4ccb-a1d8-ff3a5ea87ad8}) of ThingClass doorLock
+
+
+
+
+
+ Battery critical
+ The name of the ParamType (ThingClass: doorLock, EventType: batteryCritical, ID: {89abea26-b772-4258-9b56-e026b80c2028})
+----------
+The name of the StateType ({89abea26-b772-4258-9b56-e026b80c2028}) of ThingClass doorLock
+
+
+
+
+ Battery critical changed
+ The name of the EventType ({89abea26-b772-4258-9b56-e026b80c2028}) of ThingClass doorLock
+
+
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: doorLock, EventType: connected, ID: {206b0508-b477-4aa2-b420-aba2259fb6e6})
+----------
+The name of the StateType ({206b0508-b477-4aa2-b420-aba2259fb6e6}) of ThingClass doorLock
+----------
+The name of the ParamType (ThingClass: powerSocket, EventType: connected, ID: {b5abd47e-95f1-4e35-94fa-be87c396073a})
+----------
+The name of the StateType ({b5abd47e-95f1-4e35-94fa-be87c396073a}) of ThingClass powerSocket
+----------
+The name of the ParamType (ThingClass: thermostat, EventType: connected, ID: {e9fb2b10-96da-4b70-afda-46e948399af8})
+----------
+The name of the StateType ({e9fb2b10-96da-4b70-afda-46e948399af8}) of ThingClass thermostat
+
+
+
+
+
+ Connected changed
+ The name of the EventType ({206b0508-b477-4aa2-b420-aba2259fb6e6}) of ThingClass doorLock
+----------
+The name of the EventType ({b5abd47e-95f1-4e35-94fa-be87c396073a}) of ThingClass powerSocket
+
+
+
+
+ Connected/disconnected
+ The name of the EventType ({e9fb2b10-96da-4b70-afda-46e948399af8}) of ThingClass thermostat
+
+
+
+
+
+ Current temperature
+ The name of the ParamType (ThingClass: thermostat, EventType: temperature, ID: {497af03a-a893-438c-aba2-1bf3ecfc66c5})
+----------
+The name of the StateType ({497af03a-a893-438c-aba2-1bf3ecfc66c5}) of ThingClass thermostat
+
+
+
+
+ Current temperature changed
+ The name of the EventType ({497af03a-a893-438c-aba2-1bf3ecfc66c5}) of ThingClass thermostat
+
+
+
+
+ Door lock
+ The name of the ThingClass ({34cb3d09-dd9f-4b95-95d0-30a1cd94adac})
+
+
+
+
+
+
+ Endpoint ID
+ The name of the ParamType (ThingClass: doorLock, Type: thing, ID: {ebf17460-4a37-461f-aa24-e0a9a4238c63})
+----------
+The name of the ParamType (ThingClass: powerSocket, Type: thing, ID: {c7cbda61-2b84-4f2f-a40d-bcbe0efad08c})
+----------
+The name of the ParamType (ThingClass: thermostat, Type: thing, ID: {138a529d-1d85-410a-9d83-93ebc9a5f8ca})
+
+
+
+
+
+
+ IEEE adress
+ The name of the ParamType (ThingClass: doorLock, Type: thing, ID: {484bf80f-5d20-4029-b05e-6b4c9fbefd69})
+----------
+The name of the ParamType (ThingClass: powerSocket, Type: thing, ID: {1db22159-08a3-43d0-b515-7b34211b1d04})
+----------
+The name of the ParamType (ThingClass: thermostat, Type: thing, ID: {f38746d8-0084-43a3-b645-3ec743ea5fbc})
+
+
+
+
+ Identify
+ The name of the ActionType ({4e3b1430-d98e-4f05-bae6-301dac34bd4b}) of ThingClass powerSocket
+
+
+
+
+ Lock door
+ The name of the ActionType ({c26e1908-25d0-4475-8f82-5aaf034640f1}) of ThingClass doorLock
+
+
+
+
+
+
+ Manufacturer
+ The name of the ParamType (ThingClass: doorLock, Type: thing, ID: {1b177eb8-a13f-4975-92a7-a6bf54670c8f})
+----------
+The name of the ParamType (ThingClass: powerSocket, Type: thing, ID: {8daa4709-1dc1-4717-a529-b78e32002b59})
+----------
+The name of the ParamType (ThingClass: thermostat, Type: thing, ID: {ae76acb0-40f4-440b-8ae7-16969fe6c91a})
+
+
+
+
+
+
+ Model
+ The name of the ParamType (ThingClass: doorLock, Type: thing, ID: {b13f86a7-dc65-4ed6-860c-cdb099f90916})
+----------
+The name of the ParamType (ThingClass: powerSocket, Type: thing, ID: {ad03bc04-857a-43a9-8bea-a3c9db24461a})
+----------
+The name of the ParamType (ThingClass: thermostat, Type: thing, ID: {4262cfc6-4b04-49fd-af2b-765afca53b0b})
+
+
+
+
+
+
+ Power
+ The name of the ParamType (ThingClass: powerSocket, ActionType: power, ID: {2c8268b9-d76b-415a-b2e9-6bfeabd98a76})
+----------
+The name of the ParamType (ThingClass: powerSocket, EventType: power, ID: {2c8268b9-d76b-415a-b2e9-6bfeabd98a76})
+----------
+The name of the StateType ({2c8268b9-d76b-415a-b2e9-6bfeabd98a76}) of ThingClass powerSocket
+
+
+
+
+ Power changed
+ The name of the EventType ({2c8268b9-d76b-415a-b2e9-6bfeabd98a76}) of ThingClass powerSocket
+
+
+
+
+ Power socket
+ The name of the ThingClass ({800a8df8-06cb-4d93-8334-944fcce9651a})
+
+
+
+
+ Set power
+ The name of the ActionType ({2c8268b9-d76b-415a-b2e9-6bfeabd98a76}) of ThingClass powerSocket
+
+
+
+
+
+
+
+
+
+ Signal strength
+ The name of the ParamType (ThingClass: doorLock, EventType: signalStrength, ID: {6c8f8db5-464c-408a-9c65-4e8096663019})
+----------
+The name of the StateType ({6c8f8db5-464c-408a-9c65-4e8096663019}) of ThingClass doorLock
+----------
+The name of the ParamType (ThingClass: powerSocket, EventType: signalStrength, ID: {21889797-32c0-4f1b-903d-8af71981882b})
+----------
+The name of the StateType ({21889797-32c0-4f1b-903d-8af71981882b}) of ThingClass powerSocket
+----------
+The name of the ParamType (ThingClass: thermostat, EventType: signalStrength, ID: {8f0512ab-ced2-4dcb-90fe-aaa532efd0dd})
+----------
+The name of the StateType ({8f0512ab-ced2-4dcb-90fe-aaa532efd0dd}) of ThingClass thermostat
+
+
+
+
+
+
+ Signal strength changed
+ The name of the EventType ({6c8f8db5-464c-408a-9c65-4e8096663019}) of ThingClass doorLock
+----------
+The name of the EventType ({21889797-32c0-4f1b-903d-8af71981882b}) of ThingClass powerSocket
+----------
+The name of the EventType ({8f0512ab-ced2-4dcb-90fe-aaa532efd0dd}) of ThingClass thermostat
+
+
+
+
+
+ Target temperature
+ The name of the ParamType (ThingClass: thermostat, EventType: targetTemperature, ID: {88ad3957-2912-4de1-b53d-b360565dd361})
+----------
+The name of the StateType ({88ad3957-2912-4de1-b53d-b360565dd361}) of ThingClass thermostat
+
+
+
+
+ Target temperature changed
+ The name of the EventType ({88ad3957-2912-4de1-b53d-b360565dd361}) of ThingClass thermostat
+
+
+
+
+ Unlock door
+ The name of the ActionType ({6e112e3b-080f-47e4-8e2b-453029f0eacb}) of ThingClass doorLock
+
+
+
+
+
+
+
+
+
+ Version
+ The name of the ParamType (ThingClass: doorLock, EventType: version, ID: {9e27850b-99d8-40df-9bc7-4b3c7c01faf8})
+----------
+The name of the StateType ({9e27850b-99d8-40df-9bc7-4b3c7c01faf8}) of ThingClass doorLock
+----------
+The name of the ParamType (ThingClass: powerSocket, EventType: version, ID: {41c74445-4d2d-46dc-8a63-c98f1df8e742})
+----------
+The name of the StateType ({41c74445-4d2d-46dc-8a63-c98f1df8e742}) of ThingClass powerSocket
+----------
+The name of the ParamType (ThingClass: thermostat, EventType: version, ID: {c8bd12d2-b425-4422-9e0e-a65542a47b70})
+----------
+The name of the StateType ({c8bd12d2-b425-4422-9e0e-a65542a47b70}) of ThingClass thermostat
+
+
+
+
+
+
+ Version changed
+ The name of the EventType ({9e27850b-99d8-40df-9bc7-4b3c7c01faf8}) of ThingClass doorLock
+----------
+The name of the EventType ({41c74445-4d2d-46dc-8a63-c98f1df8e742}) of ThingClass powerSocket
+----------
+The name of the EventType ({c8bd12d2-b425-4422-9e0e-a65542a47b70}) of ThingClass thermostat
+
+
+
+
+ Zigbee Generic
+ The name of the plugin ZigbeeGeneric ({6a4343be-9fd6-4015-9ff5-38542651c534})
+
+
+
+
+ Zigbee Thermostat
+ The name of the ThingClass ({ca9af6cf-2d15-4d54-ba07-3d2ce03445b8})
+
+
+
+
+
+
+ Zigbee network UUID
+ The name of the ParamType (ThingClass: doorLock, Type: thing, ID: {4d50cbd3-f297-421c-9938-65ec0bb4bd34})
+----------
+The name of the ParamType (ThingClass: powerSocket, Type: thing, ID: {26e92d1d-bc2f-4ccd-ad1f-d6d6189d2609})
+----------
+The name of the ParamType (ThingClass: thermostat, Type: thing, ID: {4a92b536-de4c-4701-8117-9bb26dd51c3e})
+
+
+
+
+ nymea
+ The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6})
+
+
+
+
diff --git a/zigbeegeneric/zigbee.svg b/zigbeegeneric/zigbee.svg
new file mode 100644
index 00000000..5366feb6
--- /dev/null
+++ b/zigbeegeneric/zigbee.svg
@@ -0,0 +1,179 @@
+
+
+
+
diff --git a/zigbeegeneric/zigbeegeneric.pro b/zigbeegeneric/zigbeegeneric.pro
new file mode 100644
index 00000000..23192648
--- /dev/null
+++ b/zigbeegeneric/zigbeegeneric.pro
@@ -0,0 +1,12 @@
+include(../plugins.pri)
+
+PKGCONFIG += nymea-zigbee
+
+SOURCES += \
+ integrationpluginzigbeegeneric.cpp
+
+HEADERS += \
+ integrationpluginzigbeegeneric.h
+
+
+
diff --git a/zigbeegenericlights/README.md b/zigbeegenericlights/README.md
new file mode 100644
index 00000000..e119dc0e
--- /dev/null
+++ b/zigbeegenericlights/README.md
@@ -0,0 +1,55 @@
+# Generic ZigBee lights
+
+This plugin allows to interact with ZigBee lights from many different manufacturers and different types using a native ZigBee network controller in nymea.
+
+There is no specific list of supported manufacturers and models, because the plugin handles ZigBee certified lights in the most generic way possible. The manufacturer and model will be fetched from the ZigBee node and all requied information will be loaded dynamically from the ZigBee node according to the specifications.
+
+* If a lamp supports native color temperature, the range of the color temperature will be fetched from the lamp.
+* If a color lamp does not support color temperature native (on a ZigBee cluster level), the color temperature will be emulated using a color scale.
+
+This plugin tries to catch all lightning devices based on the ZigBee profiles *Light Link* and *Home Automation*.
+
+## Supported Things
+
+In order to bring a ZigBee device into the nymea ZigBee network, the network needs to be opened for joining before you perform the device pairing instructions. The joining process can take up to 30 seconds. If the device does not show up, please restart the pairing process.
+
+### On/Off lights
+
+Lamps which support only beeing switched on and off.
+
+**Pairing instructions**: Open the ZigBee network for joining. Follow the manufacturer specific reset/pairing instruction.
+
+> Most lights can be resetted/paired by switching them off and on again 6 times. Some lights like Hue require a factory reset using a switch. Please check the manufacturer specific ZigBee plugins if available or read the user manual of your hardware how to bring the lamp into a network.
+
+### Dimmable lights
+
+Lamps which support only beeing switched on and off and dimmed.
+
+**Pairing instructions**: Open the ZigBee network for joining. Follow the manufacturer specific reset/pairing instruction.
+
+> Most lights can be resetted/paired by switching them off and on again 6 times. Some lights like Hue require a factory reset using a switch. Please check the manufacturer specific ZigBee plugins if available or read the user manual of your hardware how to bring the lamp into a network.
+
+### Color temperature lights
+
+Lamps which support beeing switched on/off, dimmed and allow setting the color temperature.
+
+**Pairing instructions**: Open the ZigBee network for joining. Follow the manufacturer specific reset/pairing instruction.
+
+> Most lights can be resetted/paired by switching them off and on again 6 times. Some lights like Hue require a factory reset using a switch. Please check the manufacturer specific ZigBee plugins if available or read the user manual of your hardware how to bring the lamp into a network.
+
+
+### Color lights
+
+Lamps which support beeing switched on/off, dimmed and allow setting the colors. The plugin tries to be as close as possible to the lamp capabilities and specifications like color temperature capabilities and ranges.
+
+> Note: the lamp specific color gamut will currently not be fetched, but it is a pland feature for the future.
+
+**Pairing instructions**: Open the ZigBee network for joining. Follow the manufacturer specific reset/pairing instruction.
+
+> Most lights can be resetted/paired by switching them off and on again 6 times. Some lights like Hue require a factory reset using a switch. Please check the manufacturer specific ZigBee plugins if available or read the user manual of your hardware how to bring the lamp into a network.
+
+
+## Requirements
+
+* A compatible ZigBee controller and a running ZigBee network in nymea. You can find more information about supported controllers and ZigBee network configurations [here](https://nymea.io/documentation/users/usage/configuration#zigbee).
+
diff --git a/zigbeegenericlights/integrationpluginzigbeegenericlights.cpp b/zigbeegenericlights/integrationpluginzigbeegenericlights.cpp
new file mode 100644
index 00000000..e87c2a4d
--- /dev/null
+++ b/zigbeegenericlights/integrationpluginzigbeegenericlights.cpp
@@ -0,0 +1,1124 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginzigbeegenericlights.h"
+#include "plugininfo.h"
+#include "hardware/zigbee/zigbeehardwareresource.h"
+
+#include
+
+#include
+
+IntegrationPluginZigbeeGenericLights::IntegrationPluginZigbeeGenericLights()
+{
+ // Common thing params map
+ m_ieeeAddressParamTypeIds[onOffLightThingClassId] = onOffLightThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[dimmableLightThingClassId] = dimmableLightThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[colorLightThingClassId] = colorLightThingIeeeAddressParamTypeId;
+
+ m_networkUuidParamTypeIds[onOffLightThingClassId] = onOffLightThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[dimmableLightThingClassId] = dimmableLightThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[colorLightThingClassId] = colorLightThingNetworkUuidParamTypeId;
+
+ m_endpointIdParamTypeIds[onOffLightThingClassId] = onOffLightThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[dimmableLightThingClassId] = dimmableLightThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[colorLightThingClassId] = colorLightThingEndpointIdParamTypeId;
+
+ m_manufacturerIdParamTypeIds[onOffLightThingClassId] = onOffLightThingManufacturerParamTypeId;
+ m_manufacturerIdParamTypeIds[dimmableLightThingClassId] = dimmableLightThingManufacturerParamTypeId;
+ m_manufacturerIdParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingManufacturerParamTypeId;
+ m_manufacturerIdParamTypeIds[colorLightThingClassId] = colorLightThingManufacturerParamTypeId;
+
+ m_modelIdParamTypeIds[onOffLightThingClassId] = onOffLightThingModelParamTypeId;
+ m_modelIdParamTypeIds[dimmableLightThingClassId] = dimmableLightThingModelParamTypeId;
+ m_modelIdParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingModelParamTypeId;
+ m_modelIdParamTypeIds[colorLightThingClassId] = colorLightThingModelParamTypeId;
+
+
+ // Common states map
+ m_connectedStateTypeIds[onOffLightThingClassId] = onOffLightConnectedStateTypeId;
+ m_connectedStateTypeIds[dimmableLightThingClassId] = dimmableLightConnectedStateTypeId;
+ m_connectedStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightConnectedStateTypeId;
+ m_connectedStateTypeIds[colorLightThingClassId] = colorLightConnectedStateTypeId;
+
+ m_signalStrengthStateTypeIds[onOffLightThingClassId] = onOffLightSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[dimmableLightThingClassId] = dimmableLightSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[colorLightThingClassId] = colorLightSignalStrengthStateTypeId;
+
+ m_versionStateTypeIds[onOffLightThingClassId] = onOffLightVersionStateTypeId;
+ m_versionStateTypeIds[dimmableLightThingClassId] = dimmableLightVersionStateTypeId;
+ m_versionStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightVersionStateTypeId;
+ m_versionStateTypeIds[colorLightThingClassId] = colorLightVersionStateTypeId;
+}
+
+QString IntegrationPluginZigbeeGenericLights::name() const
+{
+ return "Generic lights";
+}
+
+bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ bool handled = false;
+ foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
+ if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink &&
+ endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOffLight) ||
+ (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation &&
+ endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffLight)) {
+
+ qCDebug(dcZigbeeGenericLights()) << "Handeling on/off light for" << node << endpoint;
+ createLightThing(onOffLightThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+
+ if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink &&
+ endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceDimmableLight) ||
+ (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation &&
+ endpoint->deviceId() == Zigbee::HomeAutomationDeviceDimmableLight)) {
+
+ qCDebug(dcZigbeeGenericLights()) << "Handeling dimmable light for" << node << endpoint;
+ createLightThing(dimmableLightThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+
+
+ if ((endpoint->profile() == Zigbee::ZigbeeProfileLightLink &&
+ endpoint->deviceId() == Zigbee::LightLinkDeviceColourTemperatureLight) ||
+ (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation &&
+ endpoint->deviceId() == Zigbee::HomeAutomationDeviceColourTemperatureLight)) {
+
+ qCDebug(dcZigbeeGenericLights()) << "Handeling color temperature light for" << node << endpoint;
+ createLightThing(colorTemperatureLightThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+
+ if ((endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceColourLight) ||
+ (endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceExtendedColourLight) ||
+ (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceExtendedColourLight)) {
+
+ qCDebug(dcZigbeeGenericLights()) << "Handeling color light for" << node << endpoint;
+ createLightThing(colorLightThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+ }
+
+ return handled;
+}
+
+void IntegrationPluginZigbeeGenericLights::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ Q_UNUSED(networkUuid)
+
+ if (m_thingNodes.values().contains(node)) {
+ Thing *thing = m_thingNodes.key(node);
+ qCDebug(dcZigbeeGenericLights()) << node << "for" << thing << "has left the network.";
+ m_thingNodes.remove(thing);
+ emit autoThingDisappeared(thing->id());
+
+ if (m_colorTemperatureRanges.contains(thing)) {
+ m_colorTemperatureRanges.remove(thing);
+ }
+
+ if (m_colorCapabilities.contains(thing)) {
+ m_colorCapabilities.remove(thing);
+ }
+ }
+}
+
+void IntegrationPluginZigbeeGenericLights::init()
+{
+ hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeCatchAll);
+}
+
+void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ // Get the node for this thing
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_ieeeAddressParamTypeIds.value(thing->thingClassId())).toString());
+ ZigbeeNode *node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress);
+ if (!node) {
+ qCWarning(dcZigbeeGenericLights()) << "Zigbee node for" << info->thing()->name() << "not found.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // Store the node thing association for removing
+ m_thingNodes.insert(thing, node);
+
+ // Get the endpoint for this thing
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeGenericLights()) << "Unable to get the endpoint from node" << node << "for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ // General thing setup
+
+ // Update connected state
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), node->reachable());
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), reachable);
+ });
+
+ // Update signal strength
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), qRound(node->lqi() * 100.0 / 255.0));
+ connect(node, &ZigbeeNode::lqiChanged, thing, [this, thing](quint8 lqi){
+ uint signalStrength = qRound(lqi * 100.0 / 255.0);
+ qCDebug(dcZigbeeGenericLights()) << thing << "signal strength changed" << signalStrength << "%";
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength);
+ });
+
+ // Set the version
+ thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId());
+
+ // Thing specific setup
+
+ // On/Off light
+ if (thing->thingClassId() == onOffLightThingClassId) {
+
+ // Get the on/off cluster
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find on/off cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(onOffLightPowerStateTypeId, onOffCluster->power());
+ }
+
+ // Update the power state if the node power value changes
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeGenericLights()) << thing << "power state changed" << power;
+ thing->setStateValue(onOffLightPowerStateTypeId, power);
+ });
+
+ // Read the power state once the node gets reachable
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ if (reachable) {
+ readLightPowerState(thing);
+ }
+ });
+ }
+
+ // Dimmable light
+ if (thing->thingClassId() == dimmableLightThingClassId) {
+
+ // Get the on/off cluster
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find on/off cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(dimmableLightPowerStateTypeId, onOffCluster->power());
+ }
+
+ // Update the power state if the node power value changes
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeGenericLights()) << thing << "power state changed" << power;
+ thing->setStateValue(dimmableLightPowerStateTypeId, power);
+ });
+
+
+ // Get the level cluster
+ ZigbeeClusterLevelControl *levelCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find level cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (levelCluster->hasAttribute(ZigbeeClusterLevelControl::AttributeCurrentLevel)) {
+ int percentage = qRound(levelCluster->currentLevel() * 100.0 / 255.0);
+ thing->setStateValue(dimmableLightBrightnessStateTypeId, percentage);
+ }
+
+ connect(levelCluster, &ZigbeeClusterLevelControl::currentLevelChanged, thing, [thing](quint8 level){
+ int percentage = qRound(level * 100.0 / 255.0);
+ qCDebug(dcZigbeeGenericLights()) << thing << "level state changed" << level << percentage << "%";
+ thing->setStateValue(dimmableLightBrightnessStateTypeId, percentage);
+ });
+
+ // Read the states once the node gets reachable
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ if (reachable) {
+ readLightPowerState(thing);
+ readLightLevelState(thing);
+ }
+ });
+ }
+
+ // Color temperature light
+ if (thing->thingClassId() == colorTemperatureLightThingClassId) {
+
+ // Get the on/off cluster
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find on/off cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(colorTemperatureLightPowerStateTypeId, onOffCluster->power());
+ }
+
+ // Update the power state if the node power value changes
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeGenericLights()) << thing << "power state changed" << power;
+ thing->setStateValue(colorTemperatureLightPowerStateTypeId, power);
+ });
+
+
+ // Get the level cluster
+ ZigbeeClusterLevelControl *levelCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find level cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (levelCluster->hasAttribute(ZigbeeClusterLevelControl::AttributeCurrentLevel)) {
+ int percentage = qRound(levelCluster->currentLevel() * 100.0 / 255.0);
+ thing->setStateValue(colorTemperatureLightBrightnessStateTypeId, percentage);
+ }
+
+ connect(levelCluster, &ZigbeeClusterLevelControl::currentLevelChanged, thing, [thing](quint8 level){
+ int percentage = qRound(level * 100.0 / 255.0);
+ qCDebug(dcZigbeeGenericLights()) << thing << "level state changed" << level << percentage << "%";
+ thing->setStateValue(colorTemperatureLightBrightnessStateTypeId, percentage);
+ });
+
+
+ // Get color cluster
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find color cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (colorCluster->hasAttribute(ZigbeeClusterColorControl::AttributeColorTemperatureMireds)) {
+ int mappedValue = mapColorTemperatureToScaledValue(thing, colorCluster->colorTemperatureMireds());
+ thing->setStateValue(colorTemperatureLightColorTemperatureStateTypeId, mappedValue);
+ }
+
+ connect(colorCluster, &ZigbeeClusterColorControl::colorTemperatureMiredsChanged, thing, [this, thing](quint16 colorTemperatureMired){
+ qCDebug(dcZigbeeGenericLights()) << "Actual color temperature is" << colorTemperatureMired << "mireds";
+ int mappedValue = mapColorTemperatureToScaledValue(thing, colorTemperatureMired);
+ qCDebug(dcZigbeeGenericLights()) << "Mapped color temperature is" << mappedValue;
+ thing->setStateValue(colorTemperatureLightColorTemperatureStateTypeId, mappedValue);
+ });
+
+ // Read the states once the node gets reachable
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ if (reachable) {
+ readColorTemperatureRange(thing);
+ readLightPowerState(thing);
+ readLightLevelState(thing);
+ readLightColorTemperatureState(thing);
+ }
+ });
+ }
+
+ // Color temperature light
+ if (thing->thingClassId() == colorLightThingClassId) {
+
+ // Get the on/off cluster
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find on/off cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(colorLightPowerStateTypeId, onOffCluster->power());
+ }
+
+ // Update the power state if the node power value changes
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeGenericLights()) << thing << "power state changed" << power;
+ thing->setStateValue(colorLightPowerStateTypeId, power);
+ });
+
+
+ // Get the level cluster
+ ZigbeeClusterLevelControl *levelCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find level cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (levelCluster->hasAttribute(ZigbeeClusterLevelControl::AttributeCurrentLevel)) {
+ int percentage = qRound(levelCluster->currentLevel() * 100.0 / 255.0);
+ thing->setStateValue(colorLightBrightnessStateTypeId, percentage);
+ }
+
+ connect(levelCluster, &ZigbeeClusterLevelControl::currentLevelChanged, thing, [thing](quint8 level){
+ int percentage = qRound(level * 100.0 / 255.0);
+ qCDebug(dcZigbeeGenericLights()) << thing << "level state changed" << level << percentage << "%";
+ thing->setStateValue(colorLightBrightnessStateTypeId, percentage);
+ });
+
+
+ // Get color cluster
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find color cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Only set the state if the cluster actually has the attribute
+ if (colorCluster->hasAttribute(ZigbeeClusterColorControl::AttributeColorTemperatureMireds)) {
+ int mappedValue = mapColorTemperatureToScaledValue(thing, colorCluster->colorTemperatureMireds());
+ thing->setStateValue(colorLightColorTemperatureStateTypeId, mappedValue);
+ }
+
+ connect(colorCluster, &ZigbeeClusterColorControl::colorTemperatureMiredsChanged, thing, [this, thing](quint16 colorTemperatureMired){
+ qCDebug(dcZigbeeGenericLights()) << "Actual color temperature is" << colorTemperatureMired << "mireds";
+ int mappedValue = mapColorTemperatureToScaledValue(thing, colorTemperatureMired);
+ qCDebug(dcZigbeeGenericLights()) << "Mapped color temperature is" << mappedValue;
+ thing->setStateValue(colorLightColorTemperatureStateTypeId, mappedValue);
+ });
+
+ // Read the states once the node gets reachable
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ if (reachable) {
+ // Note: this will also read the color temperature range if available
+ readColorCapabilities(thing);
+ readLightPowerState(thing);
+ readLightLevelState(thing);
+ readLightColorXYState(thing);
+ }
+ });
+ }
+
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginZigbeeGenericLights::postSetupThing(Thing *thing)
+{
+ if (thing->thingClassId() == onOffLightThingClassId) {
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (node && node->reachable()) {
+ readLightPowerState(thing);
+ }
+ } else if (thing->thingClassId() == dimmableLightThingClassId) {
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (node && node->reachable()) {
+ readLightPowerState(thing);
+ readLightLevelState(thing);
+ }
+ } else if (thing->thingClassId() == colorTemperatureLightThingClassId) {
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (node && node->reachable()) {
+ readColorTemperatureRange(thing);
+ readLightPowerState(thing);
+ readLightLevelState(thing);
+ readLightColorTemperatureState(thing);
+ }
+ } else if (thing->thingClassId() == colorLightThingClassId) {
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (node && node->reachable()) {
+ readColorCapabilities(thing);
+ readLightPowerState(thing);
+ readLightLevelState(thing);
+ readLightColorXYState(thing);
+ }
+ }
+}
+
+void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info)
+{
+ if (!hardwareManager()->zigbeeResource()->available()) {
+ qCDebug(dcZigbeeGenericLights()) << "Failed to execute action" << info->thing() << info->action().actionTypeId().toString() << "because the hardware is not available.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // Get the node
+ Thing *thing = info->thing();
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node->reachable()) {
+ qCDebug(dcZigbeeGenericLights()) << "Failed to execute action" << info->thing() << info->action().actionTypeId().toString() << "because node seems not to be reachable.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // Get the endpoint
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCDebug(dcZigbeeGenericLights()) << "Failed to execute action" << info->thing() << info->action().actionTypeId().toString() << "because node endpoint could not be found.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // On/Off light
+ if (thing->thingClassId() == onOffLightThingClassId) {
+ if (info->action().actionTypeId() == onOffLightAlertActionTypeId) {
+ executeAlertAction(info, endpoint);
+ return;
+ }
+
+ if (info->action().actionTypeId() == onOffLightPowerActionTypeId) {
+ // Get the on/off server cluster from the endpoint
+ bool power = info->action().param(onOffLightPowerActionPowerParamTypeId).value().toBool();
+ executePowerAction(info, endpoint, onOffLightPowerStateTypeId, power);
+ return;
+ }
+ }
+
+ // Dimmable light
+ if (thing->thingClassId() == dimmableLightThingClassId) {
+ if (info->action().actionTypeId() == dimmableLightAlertActionTypeId) {
+ executeAlertAction(info, endpoint);
+ return;
+ }
+
+ if (info->action().actionTypeId() == dimmableLightPowerActionTypeId) {
+ bool power = info->action().param(dimmableLightPowerActionPowerParamTypeId).value().toBool();
+ executePowerAction(info, endpoint, dimmableLightPowerStateTypeId, power);
+ return;
+ }
+
+ if (info->action().actionTypeId() == dimmableLightBrightnessActionTypeId) {
+ int brightness = info->action().param(dimmableLightBrightnessActionBrightnessParamTypeId).value().toInt();
+ quint8 level = static_cast(qRound(255.0 * brightness / 100.0));
+ executeBrightnessAction(info, endpoint, dimmableLightPowerStateTypeId, dimmableLightBrightnessStateTypeId, brightness, level);
+ return;
+ }
+ }
+
+ // Color temperature light
+ if (thing->thingClassId() == colorTemperatureLightThingClassId) {
+ if (info->action().actionTypeId() == colorTemperatureLightAlertActionTypeId) {
+ executeAlertAction(info, endpoint);
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorTemperatureLightPowerActionTypeId) {
+ bool power = info->action().param(colorTemperatureLightPowerActionPowerParamTypeId).value().toBool();
+ executePowerAction(info, endpoint, colorTemperatureLightPowerStateTypeId, power);
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorTemperatureLightBrightnessActionTypeId) {
+ int brightness = info->action().param(colorTemperatureLightBrightnessActionBrightnessParamTypeId).value().toInt();
+ quint8 level = static_cast(qRound(255.0 * brightness / 100.0));
+ executeBrightnessAction(info, endpoint, colorTemperatureLightPowerStateTypeId, colorTemperatureLightBrightnessStateTypeId, brightness, level);
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorTemperatureLightColorTemperatureActionTypeId) {
+ int colorTemperatureScaled = info->action().param(colorTemperatureLightColorTemperatureActionColorTemperatureParamTypeId).value().toInt();
+ executeColorTemperatureAction(info, endpoint, colorTemperatureLightColorTemperatureStateTypeId, colorTemperatureScaled);
+ return;
+ }
+ }
+
+ // Color light
+ if (thing->thingClassId() == colorLightThingClassId) {
+ if (info->action().actionTypeId() == colorLightAlertActionTypeId) {
+ executeAlertAction(info, endpoint);
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorLightPowerActionTypeId) {
+ bool power = info->action().param(colorLightPowerActionPowerParamTypeId).value().toBool();
+ executePowerAction(info, endpoint, colorLightPowerStateTypeId, power);
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorLightBrightnessActionTypeId) {
+ int brightness = info->action().param(colorLightBrightnessActionBrightnessParamTypeId).value().toInt();
+ quint8 level = static_cast(qRound(255.0 * brightness / 100.0));
+ executeBrightnessAction(info, endpoint, colorLightPowerStateTypeId, colorLightBrightnessStateTypeId, brightness, level);
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorLightColorTemperatureActionTypeId) {
+ int colorTemperatureScaled = info->action().param(colorLightColorTemperatureActionColorTemperatureParamTypeId).value().toInt();
+ if (m_colorCapabilities[thing].testFlag(ZigbeeClusterColorControl::ColorCapabilityColorTemperature)) {
+ // Native color temperature available
+ executeColorTemperatureAction(info, endpoint, colorLightColorTemperatureStateTypeId, colorTemperatureScaled);
+ return;
+ }
+
+ // Note: there is no color temperature capability, we have to emulate the color using default min/max values
+ // Convert the scaled value into the min/max color temperature interval
+ quint16 colorTemperature = mapScaledValueToColorTemperature(thing, colorTemperatureScaled);
+ qCDebug(dcZigbeeGenericLights()) << "Mapping action value" << colorTemperatureScaled << "to the color temperature in the range of [" << m_colorTemperatureRanges[thing].minValue << "," << m_colorTemperatureRanges[thing].maxValue << "] -->" << colorTemperature << "mired";
+ // Note: the color temperature command/attribute is not supported. Using xy colors to interpolate the temperature colors
+ QColor temperatureColor = ZigbeeUtils::interpolateColorFromColorTemperature(colorTemperature, m_colorTemperatureRanges[thing].minValue, m_colorTemperatureRanges[thing].maxValue);
+ QPoint temperatureColorXyInt = ZigbeeUtils::convertColorToXYInt(temperatureColor);
+ qCDebug(dcZigbeeGenericLights()) << "Mapping interpolated value" << temperatureColor << "mired to the xy color" << temperatureColorXyInt;
+
+ // Set color
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find color control cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ ZigbeeClusterReply *reply = colorCluster->commandMoveToColor(temperatureColorXyInt.x(), temperatureColorXyInt.y(), 2);
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ qCDebug(dcZigbeeGenericLights()) << "Set color temperature" << colorTemperature << "mired finished successfully" << "(scalled" << colorTemperatureScaled << ")";
+ thing->setStateValue(colorLightColorTemperatureStateTypeId, colorTemperatureScaled);
+ info->finish(Thing::ThingErrorNoError);
+ }
+ });
+
+ return;
+ }
+
+ if (info->action().actionTypeId() == colorLightColorActionTypeId) {
+ QColor color = info->action().param(colorLightColorActionColorParamTypeId).value().value();
+ QPoint xyColorInt = ZigbeeUtils::convertColorToXYInt(color);
+ qCDebug(dcZigbeeGenericLights()) << "Set color" << color.toRgb() << xyColorInt;
+ executeColorAction(info, endpoint, colorLightColorStateTypeId, color);
+ return;
+ }
+ }
+
+
+ info->finish(Thing::ThingErrorUnsupportedFeature);
+}
+
+void IntegrationPluginZigbeeGenericLights::thingRemoved(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.take(thing);
+ if (node) {
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node);
+ }
+
+ if (m_colorTemperatureRanges.contains(thing)) {
+ m_colorTemperatureRanges.remove(thing);
+ }
+
+ if (m_colorCapabilities.contains(thing)) {
+ m_colorCapabilities.remove(thing);
+ }
+}
+
+ZigbeeNodeEndpoint *IntegrationPluginZigbeeGenericLights::findEndpoint(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find the node for" << thing;
+ return nullptr;
+ }
+
+ quint8 endpointId = thing->paramValue(m_endpointIdParamTypeIds.value(thing->thingClassId())).toUInt();
+ return node->getEndpoint(endpointId);
+}
+
+void IntegrationPluginZigbeeGenericLights::createLightThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+ ThingDescriptor descriptor(thingClassId);
+ QString deviceClassName = supportedThings().findById(thingClassId).displayName();
+ descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier()));
+
+ ParamList params;
+ params.append(Param(m_networkUuidParamTypeIds[thingClassId], networkUuid.toString()));
+ params.append(Param(m_ieeeAddressParamTypeIds[thingClassId], node->extendedAddress().toString()));
+ params.append(Param(m_endpointIdParamTypeIds[thingClassId], endpoint->endpointId()));
+ params.append(Param(m_modelIdParamTypeIds[thingClassId], endpoint->modelIdentifier()));
+ params.append(Param(m_manufacturerIdParamTypeIds[thingClassId], endpoint->manufacturerName()));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+}
+
+void IntegrationPluginZigbeeGenericLights::executeAlertAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint)
+{
+ Thing *thing = info->thing();
+ ZigbeeClusterIdentify *identifyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIdentify);
+ if (!identifyCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find identify cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Send the command trough the network
+ ZigbeeClusterReply *reply = identifyCluster->identify(2);
+ connect(reply, &ZigbeeClusterReply::finished, this, [reply, info](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::executePowerAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &powerStateTypeId, bool power)
+{
+ Thing *thing = info->thing();
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find on/off cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Send the command trough the network
+ qCDebug(dcZigbeeGenericLights()) << "Set power for" << info->thing() << "to" << power;
+ ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff());
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to set power on" << thing << reply->error();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ qCDebug(dcZigbeeGenericLights()) << "Set power finished successfully for" << thing;
+ thing->setStateValue(powerStateTypeId, power);
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::executeBrightnessAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &powerStateTypeId, const StateTypeId &brightnessStateTypeId, int brightness, quint8 level)
+{
+ Thing *thing = info->thing();
+ ZigbeeClusterLevelControl *levelCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find level control cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ ZigbeeClusterReply *reply = levelCluster->commandMoveToLevelWithOnOff(level, 5);
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ thing->setStateValue(powerStateTypeId, (brightness > 0 ? true : false));
+ thing->setStateValue(brightnessStateTypeId, brightness);
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::executeColorTemperatureAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &colorTemperatureStateTypeId, int colorTemperatureScaled)
+{
+ Thing *thing = info->thing();
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find color control cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Scale the value to the actual color temperature of this lamp
+ quint16 colorTemperature = mapScaledValueToColorTemperature(thing, colorTemperatureScaled);
+ // Note: time unit is 1/10 s
+ ZigbeeClusterReply *reply = colorCluster->commandMoveToColorTemperature(colorTemperature, 5);
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ thing->setStateValue(colorTemperatureStateTypeId, colorTemperatureScaled);
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::executeColorAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &colorStateTypeId, const QColor &color)
+{
+ Thing *thing = info->thing();
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Could not find color control cluster for" << thing << "in" << m_thingNodes.value(thing);
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ // Note: time unit is 1/10 s
+ QPoint colorXyInt = ZigbeeUtils::convertColorToXYInt(color);
+ ZigbeeClusterReply *reply = colorCluster->commandMoveToColor(colorXyInt.x(), colorXyInt.y(), 5);
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ thing->setStateValue(colorStateTypeId, color);
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::readLightPowerState(Thing *thing)
+{
+ // Get the node
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node->reachable())
+ return;
+
+ // Get the endpoint
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint)
+ return;
+
+ // Get the on/off server cluster from the endpoint
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster)
+ return;
+
+ qCDebug(dcZigbeeGenericLights()) << "Reading on/off power value for" << thing << "on" << node;
+ ZigbeeClusterReply *reply = onOffCluster->readAttributes({ZigbeeClusterOnOff::AttributeOnOff});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [thing, reply](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read on/off cluster attribute from" << thing << reply->error();
+ return;
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::readLightLevelState(Thing *thing)
+{
+ // Get the node
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node->reachable())
+ return;
+
+ // Get the endpoint
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint)
+ return;
+
+ // Get the level server cluster from the endpoint
+ ZigbeeClusterLevelControl *levelCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster)
+ return;
+
+ qCDebug(dcZigbeeGenericLights()) << "Reading level value for" << thing << "on" << node;
+ ZigbeeClusterReply *reply = levelCluster->readAttributes({ZigbeeClusterLevelControl::AttributeCurrentLevel});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [thing, reply](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read level cluster attribute from" << thing << reply->error();
+ return;
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::readLightColorTemperatureState(Thing *thing)
+{
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature for" << thing << "because the node could not be found";
+ return;
+ }
+
+ // Get the color server cluster from the endpoint
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature for" << thing << "because the color cluster could not be found on" << endpoint;
+ return;
+ }
+
+ ZigbeeClusterReply *reply = colorCluster->readAttributes({ZigbeeClusterColorControl::AttributeColorTemperatureMireds});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read ColorControl cluster attribute color temperature" << reply->error();
+ return;
+ }
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::readLightColorXYState(Thing *thing)
+{
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color xy for" << thing << "because the node could not be found";
+ return;
+ }
+
+ // Get the color server cluster from the endpoint
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color xy for" << thing << "because the color cluster could not be found on" << endpoint;
+ return;
+ }
+
+ ZigbeeClusterReply *reply = colorCluster->readAttributes({ZigbeeClusterColorControl::AttributeCurrentX, ZigbeeClusterColorControl::AttributeCurrentY});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read ColorControl cluster attribute" << reply->error();
+ return;
+ }
+
+ // Note: the attribute gets updated internally and the state gets updated with the currentLevelChanged signal
+ qCDebug(dcZigbeeGenericLights()) << "Reading ColorControl cluster attribute color x and y finished successfully";
+
+ QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload);
+ if (attributeStatusRecords.count() != 2) {
+ qCWarning(dcZigbeeGenericLights()) << "Did not receive color x and y attribute values from" << thing;
+ return;
+ }
+
+ // Parse the attribute status records and calculate the color
+ quint16 currentX = 0; quint16 currentY = 0;
+ foreach (const ZigbeeClusterLibrary::ReadAttributeStatusRecord &attributeStatusRecord, attributeStatusRecords) {
+ qCDebug(dcZigbeeGenericLights()) << "Received read attribute status record" << thing << attributeStatusRecord;
+ if (attributeStatusRecord.attributeId == ZigbeeClusterColorControl::AttributeCurrentX) {
+ bool valueOk = false;
+ currentX = attributeStatusRecord.dataType.toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to convert color x attribute values from" << thing << attributeStatusRecord;
+ return;
+ }
+ continue;
+ }
+
+ if (attributeStatusRecord.attributeId == ZigbeeClusterColorControl::AttributeCurrentY) {
+ bool valueOk = false;
+ currentY = attributeStatusRecord.dataType.toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to convert color y attribute values from" << thing << attributeStatusRecord;
+ return;
+ }
+ continue;
+ }
+ }
+
+ // Set the current color
+ QColor color = ZigbeeUtils::convertXYToColor(currentX, currentY);
+ qCDebug(dcZigbeeGenericLights()) << "Current color" << color.toRgb() << QPoint(currentX, currentY);
+ thing->setStateValue(colorLightColorStateTypeId, color);
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::readColorCapabilities(Thing *thing)
+{
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color capabilities for" << thing << "because the node could not be found";
+ return;
+ }
+
+ // Get the color server cluster from the endpoint
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color color capabilities for" << thing << "because the color cluster could not be found on" << endpoint;
+ return;
+ }
+
+ // Check if we know already the color capabilities for this lamp
+ if (colorCluster->hasAttribute(ZigbeeClusterColorControl::AttributeColorCapabilities)) {
+ m_colorCapabilities[thing] = colorCluster->colorCapabilities();
+ qCDebug(dcZigbeeGenericLights()) << "Found cached color capabilities for" << thing << colorCluster->colorCapabilities();
+ processColorCapabilities(thing);
+ return;
+ }
+
+ // We have to read the color capabilities
+ ZigbeeClusterReply *reply = colorCluster->readAttributes({ZigbeeClusterColorControl::AttributeColorCapabilities});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color capabilitie for" << thing << "because the node could not be found";
+ return;
+ }
+
+ m_colorCapabilities[thing] = colorCluster->colorCapabilities();
+ processColorCapabilities(thing);
+ });
+}
+
+void IntegrationPluginZigbeeGenericLights::processColorCapabilities(Thing *thing)
+{
+ // Fetch all information required depending on the capabilities
+ qCDebug(dcZigbeeGenericLights()) << "Loading information depending on the lamp capabilities" << thing << m_colorCapabilities[thing];
+ if (m_colorCapabilities[thing].testFlag(ZigbeeClusterColorControl::ColorCapabilityColorTemperature)) {
+ qCDebug(dcZigbeeGenericLights()) << "The lamp is capable of native controlling the color temperature";
+
+ // Read min/max value, otherwise emulate the color temperature using the color map
+ readColorTemperatureRange(thing);
+ } else {
+ qCDebug(dcZigbeeGenericLights()) << "The lamp has no native color temperature capability, emulating them using color map.";
+
+ // TODO: continue with color fetching (xy, hsv, gamut values)
+
+ qCDebug(dcZigbeeGenericLights()) << "Lamp capabilities information complete";
+ }
+}
+
+void IntegrationPluginZigbeeGenericLights::readColorTemperatureRange(Thing *thing)
+{
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature range for" << thing << "because the node could not be found";
+ return;
+ }
+
+ // Get the color server cluster from the endpoint
+ ZigbeeClusterColorControl *colorCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdColorControl);
+ if (!colorCluster) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature range for" << thing << "because the color cluster could not be found on" << endpoint;
+ return;
+ }
+
+ // Check if we can use the cached values from the database
+ if (readCachedColorTemperatureRange(thing, colorCluster)) {
+ qCDebug(dcZigbeeGenericLights()) << "Using cached color temperature mireds interval for mapping" << thing << "[" << m_colorTemperatureRanges[thing].minValue << "," << m_colorTemperatureRanges[thing].maxValue << "] mired";
+ return;
+ }
+
+ // Init with default values
+ m_colorTemperatureRanges[thing] = ColorTemperatureRange();
+
+ // We need to read them from the lamp
+ ZigbeeClusterReply *reply = colorCluster->readAttributes({ZigbeeClusterColorControl::AttributeColorTempPhysicalMinMireds, ZigbeeClusterColorControl::AttributeColorTempPhysicalMaxMireds});
+ connect(reply, &ZigbeeClusterReply::finished, thing, [=](){
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeGenericLights()) << "Reading color temperature range attributes finished with error" << reply->error();
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature min/max interval values. Using default values for" << thing << "[" << m_colorTemperatureRanges[thing].minValue << "," << m_colorTemperatureRanges[thing].maxValue << "] mired";
+ return;
+ }
+
+ QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload);
+ if (attributeStatusRecords.count() != 2) {
+ qCWarning(dcZigbeeGenericLights()) << "Did not receive temperature min/max interval values from" << thing;
+ qCWarning(dcZigbeeGenericLights()) << "Using default values for" << thing << "[" << m_colorTemperatureRanges[thing].minValue << "," << m_colorTemperatureRanges[thing].maxValue << "] mired" ;
+ return;
+ }
+
+ // Parse the attribute status records
+ foreach (const ZigbeeClusterLibrary::ReadAttributeStatusRecord &attributeStatusRecord, attributeStatusRecords) {
+ if (attributeStatusRecord.attributeId == ZigbeeClusterColorControl::AttributeColorTempPhysicalMinMireds) {
+ bool valueOk = false;
+ quint16 minMiredsValue = attributeStatusRecord.dataType.toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature min mireds attribute value and convert it" << attributeStatusRecord;
+ break;
+ }
+
+ m_colorTemperatureRanges[thing].minValue = minMiredsValue;
+ }
+
+ if (attributeStatusRecord.attributeId == ZigbeeClusterColorControl::AttributeColorTempPhysicalMaxMireds) {
+ bool valueOk = false;
+ quint16 maxMiredsValue = attributeStatusRecord.dataType.toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature max mireds attribute value and convert it" << attributeStatusRecord;
+ break;
+ }
+
+ m_colorTemperatureRanges[thing].maxValue = maxMiredsValue;
+ }
+ }
+
+ qCDebug(dcZigbeeGenericLights()) << "Using lamp specific color temperature mireds interval for mapping" << thing << "[" << m_colorTemperatureRanges[thing].minValue << "," << m_colorTemperatureRanges[thing].maxValue << "] mired";
+ });
+
+}
+
+bool IntegrationPluginZigbeeGenericLights::readCachedColorTemperatureRange(Thing *thing, ZigbeeClusterColorControl *colorCluster)
+{
+ if (colorCluster->hasAttribute(ZigbeeClusterColorControl::AttributeColorTempPhysicalMinMireds) && colorCluster->hasAttribute(ZigbeeClusterColorControl::AttributeColorTempPhysicalMaxMireds)) {
+ ZigbeeClusterAttribute minMiredsAttribute = colorCluster->attribute(ZigbeeClusterColorControl::AttributeColorTempPhysicalMinMireds);
+ bool valueOk = false;
+ quint16 minMiredsValue = minMiredsAttribute.dataType().toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature min mireds attribute value and convert it" << minMiredsAttribute;
+ return false;
+ }
+
+ ZigbeeClusterAttribute maxMiredsAttribute = colorCluster->attribute(ZigbeeClusterColorControl::AttributeColorTempPhysicalMaxMireds);
+ quint16 maxMiredsValue = maxMiredsAttribute.dataType().toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeGenericLights()) << "Failed to read color temperature max mireds attribute value and convert it" << maxMiredsAttribute;
+ return false;
+ }
+
+ ColorTemperatureRange range;
+ range.minValue = minMiredsValue;
+ range.maxValue = maxMiredsValue;
+ m_colorTemperatureRanges[thing] = range;
+ return true;
+ }
+
+ return false;
+}
+
+quint16 IntegrationPluginZigbeeGenericLights::mapScaledValueToColorTemperature(Thing *thing, int scaledColorTemperature)
+{
+ // Make sure we have values to scale
+ if (!m_colorTemperatureRanges.contains(thing)) {
+ m_colorTemperatureRanges[thing] = ColorTemperatureRange();
+ }
+
+ double percentage = static_cast((scaledColorTemperature - m_minScaleValue)) / (m_maxScaleValue - m_minScaleValue);
+ //qCDebug(dcZigbeeGenericLights()) << "Mapping color temperature value" << scaledColorTemperature << "between" << m_minScaleValue << m_maxScaleValue << "is" << percentage * 100 << "%";
+ double mappedValue = (m_colorTemperatureRanges[thing].maxValue - m_colorTemperatureRanges[thing].minValue) * percentage + m_colorTemperatureRanges[thing].minValue;
+ //qCDebug(dcZigbeeGenericLights()) << "Mapping color temperature value" << scaledColorTemperature << "is" << mappedValue << "mireds";
+ return static_cast(qRound(mappedValue));
+}
+
+int IntegrationPluginZigbeeGenericLights::mapColorTemperatureToScaledValue(Thing *thing, quint16 colorTemperature)
+{
+ // Make sure we have values to scale
+ if (!m_colorTemperatureRanges.contains(thing)) {
+ m_colorTemperatureRanges[thing] = ColorTemperatureRange();
+ }
+
+ double percentage = static_cast((colorTemperature - m_colorTemperatureRanges[thing].minValue)) / (m_colorTemperatureRanges[thing].maxValue - m_colorTemperatureRanges[thing].minValue);
+ //qCDebug(dcZigbee()) << "Mapping color temperature value" << colorTemperature << "mirred" << m_minColorTemperature << m_maxColorTemperature << "is" << percentage * 100 << "%";
+ double mappedValue = (m_maxScaleValue - m_minScaleValue) * percentage + m_minScaleValue;
+ //qCDebug(dcZigbee()) << "Mapping color temperature value" << colorTemperature << "results into the scaled value of" << mappedValue;
+ return static_cast(qRound(mappedValue));
+}
diff --git a/zigbeegenericlights/integrationpluginzigbeegenericlights.h b/zigbeegenericlights/integrationpluginzigbeegenericlights.h
new file mode 100644
index 00000000..6686ccc7
--- /dev/null
+++ b/zigbeegenericlights/integrationpluginzigbeegenericlights.h
@@ -0,0 +1,114 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINZIGBEEGENERICLIGHTS_H
+#define INTEGRATIONPLUGINZIGBEEGENERICLIGHTS_H
+
+#include "integrations/integrationplugin.h"
+#include "hardware/zigbee/zigbeehandler.h"
+
+class IntegrationPluginZigbeeGenericLights: public IntegrationPlugin, public ZigbeeHandler
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeegenericlights.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginZigbeeGenericLights();
+
+ QString name() const override;
+ bool handleNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+ void handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+
+ void init() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ // Common thing params
+ QHash m_ieeeAddressParamTypeIds;
+ QHash m_networkUuidParamTypeIds;
+ QHash m_endpointIdParamTypeIds;
+ QHash m_modelIdParamTypeIds;
+ QHash m_manufacturerIdParamTypeIds;
+
+ // Common states
+ QHash m_connectedStateTypeIds;
+ QHash m_signalStrengthStateTypeIds;
+ QHash m_versionStateTypeIds;
+
+ QHash m_thingNodes;
+
+ // Get the endpoint for the given thing
+ ZigbeeNodeEndpoint *findEndpoint(Thing *thing);
+
+ void createLightThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+
+ // Action help methods since they work all the same
+ void executeAlertAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint);
+ void executePowerAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &powerStateTypeId, bool power);
+ void executeBrightnessAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &powerStateTypeId, const StateTypeId &brightnessStateTypeId, int brightness, quint8 level);
+ void executeColorTemperatureAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &colorTemperatureStateTypeId, int colorTemperatureScaled);
+ void executeColorAction(ThingActionInfo *info, ZigbeeNodeEndpoint *endpoint, const StateTypeId &colorStateTypeId, const QColor &color);
+
+ // Read state values from the node
+ void readLightPowerState(Thing *thing);
+ void readLightLevelState(Thing *thing);
+ void readLightColorTemperatureState(Thing *thing);
+ void readLightColorXYState(Thing *thing);
+
+ // Color temperature information handling
+ typedef struct ColorTemperatureRange {
+ quint16 minValue = 250;
+ quint16 maxValue = 450;
+ } ColorTemperatureRange;
+
+ // Color temperature scaling range defined in
+ // all color temperature states/actions (the slider min/max)
+ int m_minScaleValue = 0;
+ int m_maxScaleValue = 200;
+
+ QHash m_colorTemperatureRanges;
+ QHash m_colorCapabilities;
+
+ void readColorCapabilities(Thing *thing);
+ void processColorCapabilities(Thing *thing);
+
+ void readColorTemperatureRange(Thing *thing);
+ bool readCachedColorTemperatureRange(Thing *thing, ZigbeeClusterColorControl *colorCluster);
+ quint16 mapScaledValueToColorTemperature(Thing *thing, int scaledColorTemperature);
+ int mapColorTemperatureToScaledValue(Thing *thing, quint16 colorTemperature);
+
+};
+
+#endif // INTEGRATIONPLUGINZIGBEEGENERICLIGHTS_H
diff --git a/zigbeegenericlights/integrationpluginzigbeegenericlights.json b/zigbeegenericlights/integrationpluginzigbeegenericlights.json
new file mode 100644
index 00000000..3c43370f
--- /dev/null
+++ b/zigbeegenericlights/integrationpluginzigbeegenericlights.json
@@ -0,0 +1,473 @@
+{
+ "name": "ZigbeeGenericLights",
+ "displayName": "Zigbee Generic Lights",
+ "id": "ee67f87d-d4cc-4223-a9f3-79e47c0267e0",
+ "vendors": [
+ {
+ "name": "nymea",
+ "displayName": "nymea",
+ "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
+ "thingClasses": [
+ {
+ "name": "onOffLight",
+ "displayName": "On/Off Light",
+ "id": "7fbc3165-e618-4dc0-8255-8dac2cc08dfa",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "light", "alert", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "1b434d5d-4fe4-439c-b842-3483e8e24820",
+ "name": "ieeeAddress",
+ "displayName": "IEEE address",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "b3e298de-140c-4d3d-be81-0a9792359275",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "fd8a6ae6-202b-4634-824f-8d4b187430fa",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "acb5e910-ed8b-4968-972e-5fc57f466b8a",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "3d942b2d-302a-4c42-8cf5-be2865736736",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "7548ac08-4ea1-4dcd-98b9-9d58bbc06ab7",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "5857943e-ed90-4321-966e-b56eddaf2441",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "f5b5e0b3-2c34-4ccc-baef-8e29b84e7a2b",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "ae03155c-f17f-4d9f-bb40-157a8cb1f83c",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "9388290d-5db2-484a-a702-53a0d842d412",
+ "name": "alert",
+ "displayName": "Identify"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "dimmableLight",
+ "displayName": "Dimmable Light",
+ "id": "b2eeb554-ca5c-46b5-a897-bc443fb186ea",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "dimmablelight", "alert", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "beab891f-c1b0-4b2c-88c4-216c8d19f9e2",
+ "name": "ieeeAddress",
+ "displayName": "IEEE address",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "81a82ed2-c465-4e53-a208-157d8473b16c",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "9472299b-b91b-456a-95fe-bf80d1a266a1",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "d9044272-2d7d-46d2-b7bf-37239014f2df",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "3e971afc-a6f1-429c-a902-69cba865c222",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "2dc93a53-c506-41a5-9242-b5d045f51c40",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "ab75980d-12f5-4ddc-be2d-dc54514a3b75",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "9390d2a6-7a17-4b42-8b35-cdb8b3d4d723",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "19d82a60-0009-4cff-966d-3b1c4fe358e1",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "6376376f-3221-4e6b-bce3-41872a6fc98a",
+ "name": "brightness",
+ "displayName": "Brightness",
+ "displayNameEvent": "Brightness changed",
+ "displayNameAction": "Set brightness",
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "int",
+ "defaultValue": 100,
+ "writable": true
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "a04d9e2b-e854-4361-bb34-2da74f8f028f",
+ "name": "alert",
+ "displayName": "Identify"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "colorTemperatureLight",
+ "displayName": "Color Temperature Light",
+ "id": "498815c1-a6a3-48e9-9c9b-bff974694b26",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "colortemperaturelight", "alert", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "60abc695-c945-4e08-8ea3-21c4f05ff2f4",
+ "name": "ieeeAddress",
+ "displayName": "IEEE address",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "2f1799f0-76da-4147-89a0-1db93bb74872",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "8511c361-79c2-420c-93c2-89e97da1baff",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "3b688ee1-dd08-4863-a221-70c4ccc062e9",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "c58bef19-87e3-484c-9a5c-c0daa23092a3",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "dd4eb1fe-4ddd-42fa-97bf-df690728c866",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "45e5b606-1e4d-42b8-9ee1-0442751247ef",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "477a1531-5f87-4f21-9b32-01161be93cd2",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "a8c56366-5b2d-4f4b-b910-38a87851c484",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "b72177f9-b673-49b5-864a-345936e5c821",
+ "name": "brightness",
+ "displayName": "Brightness",
+ "displayNameEvent": "Brightness changed",
+ "displayNameAction": "Set brightness",
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "int",
+ "defaultValue": 100,
+ "writable": true
+ },
+ {
+ "id": "69e94f88-a664-4bb9-ba04-2c3ea6cd7569",
+ "name": "colorTemperature",
+ "displayName": "Color temperature scaled",
+ "displayNameEvent": "Color temperature scaled changed",
+ "displayNameAction": "Set color temperature scaled",
+ "defaultValue": 100,
+ "minValue": 0,
+ "maxValue": 200,
+ "type": "int",
+ "writable": true
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "4f8a38f4-652f-40e1-86dd-028dcd64349c",
+ "name": "alert",
+ "displayName": "Identify"
+ },
+ {
+ "id": "a3e8d1c7-81c7-4255-b410-3bc4d5c7b4e9",
+ "name": "removeFromNetwork",
+ "displayName": "Remove from network"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "colorLight",
+ "displayName": "Color Light",
+ "id": "8d157281-73c0-4d2c-96a0-572d26e9dd4f",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "colorlight", "alert", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "03afaef5-0f68-494e-9153-745ca878f894",
+ "name": "ieeeAddress",
+ "displayName": "IEEE address",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "e409e8fe-9afa-407d-94fd-ec40f88f60f1",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "be14c4fe-5a19-4d0c-a546-2b68162ca707",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "787d0c2e-c475-4426-a0ba-fe8ec89b1a4d",
+ "name": "manufacturer",
+ "displayName": "Manufacturer",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "a1ef07bf-260a-48cc-b7cb-423cbbf860af",
+ "name": "model",
+ "displayName": "Model",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "bc56076a-cfa3-4f93-bbf5-2fe9b067cdc3",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "62789a2c-c6e6-47b4-b022-29a2d183f690",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "084b8268-4ae6-482d-b42b-67a57b5a7147",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "16cfb549-5347-4dc9-b5de-e4028cb4dddd",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "7329b2eb-fb7e-4098-a551-c3ff24fc2962",
+ "name": "brightness",
+ "displayName": "Brightness",
+ "displayNameEvent": "Brightness changed",
+ "displayNameAction": "Set brightness",
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "int",
+ "defaultValue": 100,
+ "writable": true
+ },
+ {
+ "id": "c2817667-afd0-434b-b0a3-e020d2c90082",
+ "name": "colorTemperature",
+ "displayName": "Color temperature scaled",
+ "displayNameEvent": "Color temperature scaled changed",
+ "displayNameAction": "Set color temperature scaled",
+ "defaultValue": 100,
+ "minValue": 0,
+ "maxValue": 200,
+ "type": "int",
+ "writable": true
+ },
+ {
+ "id": "51e8e6da-426c-4812-9c00-70f1356190dc",
+ "name": "color",
+ "displayName": "color",
+ "displayNameEvent": "color changed",
+ "displayNameAction": "Set color",
+ "type": "QColor",
+ "defaultValue": "#000000",
+ "writable": true
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "062fa97d-50b6-48ee-ab74-03701e4c7220",
+ "name": "alert",
+ "displayName": "Identify"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/zigbeegenericlights/meta.json b/zigbeegenericlights/meta.json
new file mode 100644
index 00000000..7b3f66ad
--- /dev/null
+++ b/zigbeegenericlights/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "ZigBee Generic lights",
+ "tagline": "Connect and control generic ZigBee lights native.",
+ "icon": "zigbee.svg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "zigbee"
+ ],
+ "categories": [
+ "light"
+ ]
+}
diff --git a/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts b/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts
new file mode 100644
index 00000000..41b8a875
--- /dev/null
+++ b/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts
@@ -0,0 +1,457 @@
+
+
+
+
+ ZigbeeGenericLights
+
+
+
+ Available
+ The name of the ParamType (ThingClass: colorLight, EventType: connected, ID: {bc56076a-cfa3-4f93-bbf5-2fe9b067cdc3})
+----------
+The name of the StateType ({bc56076a-cfa3-4f93-bbf5-2fe9b067cdc3}) of ThingClass colorLight
+
+
+
+
+ Available changed
+ The name of the EventType ({bc56076a-cfa3-4f93-bbf5-2fe9b067cdc3}) of ThingClass colorLight
+
+
+
+
+
+
+
+
+
+
+
+
+ Brightness
+ The name of the ParamType (ThingClass: colorLight, ActionType: brightness, ID: {7329b2eb-fb7e-4098-a551-c3ff24fc2962})
+----------
+The name of the ParamType (ThingClass: colorLight, EventType: brightness, ID: {7329b2eb-fb7e-4098-a551-c3ff24fc2962})
+----------
+The name of the StateType ({7329b2eb-fb7e-4098-a551-c3ff24fc2962}) of ThingClass colorLight
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, ActionType: brightness, ID: {b72177f9-b673-49b5-864a-345936e5c821})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, EventType: brightness, ID: {b72177f9-b673-49b5-864a-345936e5c821})
+----------
+The name of the StateType ({b72177f9-b673-49b5-864a-345936e5c821}) of ThingClass colorTemperatureLight
+----------
+The name of the ParamType (ThingClass: dimmableLight, ActionType: brightness, ID: {6376376f-3221-4e6b-bce3-41872a6fc98a})
+----------
+The name of the ParamType (ThingClass: dimmableLight, EventType: brightness, ID: {6376376f-3221-4e6b-bce3-41872a6fc98a})
+----------
+The name of the StateType ({6376376f-3221-4e6b-bce3-41872a6fc98a}) of ThingClass dimmableLight
+
+
+
+
+
+
+ Brightness changed
+ The name of the EventType ({7329b2eb-fb7e-4098-a551-c3ff24fc2962}) of ThingClass colorLight
+----------
+The name of the EventType ({b72177f9-b673-49b5-864a-345936e5c821}) of ThingClass colorTemperatureLight
+----------
+The name of the EventType ({6376376f-3221-4e6b-bce3-41872a6fc98a}) of ThingClass dimmableLight
+
+
+
+
+ Color Light
+ The name of the ThingClass ({8d157281-73c0-4d2c-96a0-572d26e9dd4f})
+
+
+
+
+ Color Temperature Light
+ The name of the ThingClass ({498815c1-a6a3-48e9-9c9b-bff974694b26})
+
+
+
+
+
+
+
+
+
+ Color temperature scaled
+ The name of the ParamType (ThingClass: colorLight, ActionType: colorTemperature, ID: {c2817667-afd0-434b-b0a3-e020d2c90082})
+----------
+The name of the ParamType (ThingClass: colorLight, EventType: colorTemperature, ID: {c2817667-afd0-434b-b0a3-e020d2c90082})
+----------
+The name of the StateType ({c2817667-afd0-434b-b0a3-e020d2c90082}) of ThingClass colorLight
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, ActionType: colorTemperature, ID: {69e94f88-a664-4bb9-ba04-2c3ea6cd7569})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, EventType: colorTemperature, ID: {69e94f88-a664-4bb9-ba04-2c3ea6cd7569})
+----------
+The name of the StateType ({69e94f88-a664-4bb9-ba04-2c3ea6cd7569}) of ThingClass colorTemperatureLight
+
+
+
+
+
+ Color temperature scaled changed
+ The name of the EventType ({c2817667-afd0-434b-b0a3-e020d2c90082}) of ThingClass colorLight
+----------
+The name of the EventType ({69e94f88-a664-4bb9-ba04-2c3ea6cd7569}) of ThingClass colorTemperatureLight
+
+
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: colorTemperatureLight, EventType: connected, ID: {dd4eb1fe-4ddd-42fa-97bf-df690728c866})
+----------
+The name of the StateType ({dd4eb1fe-4ddd-42fa-97bf-df690728c866}) of ThingClass colorTemperatureLight
+----------
+The name of the ParamType (ThingClass: dimmableLight, EventType: connected, ID: {2dc93a53-c506-41a5-9242-b5d045f51c40})
+----------
+The name of the StateType ({2dc93a53-c506-41a5-9242-b5d045f51c40}) of ThingClass dimmableLight
+----------
+The name of the ParamType (ThingClass: onOffLight, EventType: connected, ID: {7548ac08-4ea1-4dcd-98b9-9d58bbc06ab7})
+----------
+The name of the StateType ({7548ac08-4ea1-4dcd-98b9-9d58bbc06ab7}) of ThingClass onOffLight
+
+
+
+
+
+
+ Connected changed
+ The name of the EventType ({dd4eb1fe-4ddd-42fa-97bf-df690728c866}) of ThingClass colorTemperatureLight
+----------
+The name of the EventType ({2dc93a53-c506-41a5-9242-b5d045f51c40}) of ThingClass dimmableLight
+----------
+The name of the EventType ({7548ac08-4ea1-4dcd-98b9-9d58bbc06ab7}) of ThingClass onOffLight
+
+
+
+
+ Dimmable Light
+ The name of the ThingClass ({b2eeb554-ca5c-46b5-a897-bc443fb186ea})
+
+
+
+
+
+
+
+ Endpoint ID
+ The name of the ParamType (ThingClass: colorLight, Type: thing, ID: {be14c4fe-5a19-4d0c-a546-2b68162ca707})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, Type: thing, ID: {8511c361-79c2-420c-93c2-89e97da1baff})
+----------
+The name of the ParamType (ThingClass: dimmableLight, Type: thing, ID: {9472299b-b91b-456a-95fe-bf80d1a266a1})
+----------
+The name of the ParamType (ThingClass: onOffLight, Type: thing, ID: {fd8a6ae6-202b-4634-824f-8d4b187430fa})
+
+
+
+
+
+
+
+ IEEE address
+ The name of the ParamType (ThingClass: colorLight, Type: thing, ID: {03afaef5-0f68-494e-9153-745ca878f894})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, Type: thing, ID: {60abc695-c945-4e08-8ea3-21c4f05ff2f4})
+----------
+The name of the ParamType (ThingClass: dimmableLight, Type: thing, ID: {beab891f-c1b0-4b2c-88c4-216c8d19f9e2})
+----------
+The name of the ParamType (ThingClass: onOffLight, Type: thing, ID: {1b434d5d-4fe4-439c-b842-3483e8e24820})
+
+
+
+
+
+
+
+ Identify
+ The name of the ActionType ({062fa97d-50b6-48ee-ab74-03701e4c7220}) of ThingClass colorLight
+----------
+The name of the ActionType ({4f8a38f4-652f-40e1-86dd-028dcd64349c}) of ThingClass colorTemperatureLight
+----------
+The name of the ActionType ({a04d9e2b-e854-4361-bb34-2da74f8f028f}) of ThingClass dimmableLight
+----------
+The name of the ActionType ({9388290d-5db2-484a-a702-53a0d842d412}) of ThingClass onOffLight
+
+
+
+
+
+
+
+ Manufacturer
+ The name of the ParamType (ThingClass: colorLight, Type: thing, ID: {787d0c2e-c475-4426-a0ba-fe8ec89b1a4d})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, Type: thing, ID: {3b688ee1-dd08-4863-a221-70c4ccc062e9})
+----------
+The name of the ParamType (ThingClass: dimmableLight, Type: thing, ID: {d9044272-2d7d-46d2-b7bf-37239014f2df})
+----------
+The name of the ParamType (ThingClass: onOffLight, Type: thing, ID: {acb5e910-ed8b-4968-972e-5fc57f466b8a})
+
+
+
+
+
+
+
+ Model
+ The name of the ParamType (ThingClass: colorLight, Type: thing, ID: {a1ef07bf-260a-48cc-b7cb-423cbbf860af})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, Type: thing, ID: {c58bef19-87e3-484c-9a5c-c0daa23092a3})
+----------
+The name of the ParamType (ThingClass: dimmableLight, Type: thing, ID: {3e971afc-a6f1-429c-a902-69cba865c222})
+----------
+The name of the ParamType (ThingClass: onOffLight, Type: thing, ID: {3d942b2d-302a-4c42-8cf5-be2865736736})
+
+
+
+
+ On/Off Light
+ The name of the ThingClass ({7fbc3165-e618-4dc0-8255-8dac2cc08dfa})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Power
+ The name of the ParamType (ThingClass: colorLight, ActionType: power, ID: {16cfb549-5347-4dc9-b5de-e4028cb4dddd})
+----------
+The name of the ParamType (ThingClass: colorLight, EventType: power, ID: {16cfb549-5347-4dc9-b5de-e4028cb4dddd})
+----------
+The name of the StateType ({16cfb549-5347-4dc9-b5de-e4028cb4dddd}) of ThingClass colorLight
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, ActionType: power, ID: {a8c56366-5b2d-4f4b-b910-38a87851c484})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, EventType: power, ID: {a8c56366-5b2d-4f4b-b910-38a87851c484})
+----------
+The name of the StateType ({a8c56366-5b2d-4f4b-b910-38a87851c484}) of ThingClass colorTemperatureLight
+----------
+The name of the ParamType (ThingClass: dimmableLight, ActionType: power, ID: {19d82a60-0009-4cff-966d-3b1c4fe358e1})
+----------
+The name of the ParamType (ThingClass: dimmableLight, EventType: power, ID: {19d82a60-0009-4cff-966d-3b1c4fe358e1})
+----------
+The name of the StateType ({19d82a60-0009-4cff-966d-3b1c4fe358e1}) of ThingClass dimmableLight
+----------
+The name of the ParamType (ThingClass: onOffLight, ActionType: power, ID: {ae03155c-f17f-4d9f-bb40-157a8cb1f83c})
+----------
+The name of the ParamType (ThingClass: onOffLight, EventType: power, ID: {ae03155c-f17f-4d9f-bb40-157a8cb1f83c})
+----------
+The name of the StateType ({ae03155c-f17f-4d9f-bb40-157a8cb1f83c}) of ThingClass onOffLight
+
+
+
+
+
+
+
+ Power changed
+ The name of the EventType ({16cfb549-5347-4dc9-b5de-e4028cb4dddd}) of ThingClass colorLight
+----------
+The name of the EventType ({a8c56366-5b2d-4f4b-b910-38a87851c484}) of ThingClass colorTemperatureLight
+----------
+The name of the EventType ({19d82a60-0009-4cff-966d-3b1c4fe358e1}) of ThingClass dimmableLight
+----------
+The name of the EventType ({ae03155c-f17f-4d9f-bb40-157a8cb1f83c}) of ThingClass onOffLight
+
+
+
+
+ Remove from network
+ The name of the ActionType ({a3e8d1c7-81c7-4255-b410-3bc4d5c7b4e9}) of ThingClass colorTemperatureLight
+
+
+
+
+
+
+ Set brightness
+ The name of the ActionType ({7329b2eb-fb7e-4098-a551-c3ff24fc2962}) of ThingClass colorLight
+----------
+The name of the ActionType ({b72177f9-b673-49b5-864a-345936e5c821}) of ThingClass colorTemperatureLight
+----------
+The name of the ActionType ({6376376f-3221-4e6b-bce3-41872a6fc98a}) of ThingClass dimmableLight
+
+
+
+
+ Set color
+ The name of the ActionType ({51e8e6da-426c-4812-9c00-70f1356190dc}) of ThingClass colorLight
+
+
+
+
+
+ Set color temperature scaled
+ The name of the ActionType ({c2817667-afd0-434b-b0a3-e020d2c90082}) of ThingClass colorLight
+----------
+The name of the ActionType ({69e94f88-a664-4bb9-ba04-2c3ea6cd7569}) of ThingClass colorTemperatureLight
+
+
+
+
+
+
+
+ Set power
+ The name of the ActionType ({16cfb549-5347-4dc9-b5de-e4028cb4dddd}) of ThingClass colorLight
+----------
+The name of the ActionType ({a8c56366-5b2d-4f4b-b910-38a87851c484}) of ThingClass colorTemperatureLight
+----------
+The name of the ActionType ({19d82a60-0009-4cff-966d-3b1c4fe358e1}) of ThingClass dimmableLight
+----------
+The name of the ActionType ({ae03155c-f17f-4d9f-bb40-157a8cb1f83c}) of ThingClass onOffLight
+
+
+
+
+
+
+
+
+
+
+
+ Signal strength
+ The name of the ParamType (ThingClass: colorLight, EventType: signalStrength, ID: {62789a2c-c6e6-47b4-b022-29a2d183f690})
+----------
+The name of the StateType ({62789a2c-c6e6-47b4-b022-29a2d183f690}) of ThingClass colorLight
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, EventType: signalStrength, ID: {45e5b606-1e4d-42b8-9ee1-0442751247ef})
+----------
+The name of the StateType ({45e5b606-1e4d-42b8-9ee1-0442751247ef}) of ThingClass colorTemperatureLight
+----------
+The name of the ParamType (ThingClass: dimmableLight, EventType: signalStrength, ID: {ab75980d-12f5-4ddc-be2d-dc54514a3b75})
+----------
+The name of the StateType ({ab75980d-12f5-4ddc-be2d-dc54514a3b75}) of ThingClass dimmableLight
+----------
+The name of the ParamType (ThingClass: onOffLight, EventType: signalStrength, ID: {5857943e-ed90-4321-966e-b56eddaf2441})
+----------
+The name of the StateType ({5857943e-ed90-4321-966e-b56eddaf2441}) of ThingClass onOffLight
+
+
+
+
+
+
+
+ Signal strength changed
+ The name of the EventType ({62789a2c-c6e6-47b4-b022-29a2d183f690}) of ThingClass colorLight
+----------
+The name of the EventType ({45e5b606-1e4d-42b8-9ee1-0442751247ef}) of ThingClass colorTemperatureLight
+----------
+The name of the EventType ({ab75980d-12f5-4ddc-be2d-dc54514a3b75}) of ThingClass dimmableLight
+----------
+The name of the EventType ({5857943e-ed90-4321-966e-b56eddaf2441}) of ThingClass onOffLight
+
+
+
+
+
+
+
+
+
+
+
+ Version
+ The name of the ParamType (ThingClass: colorLight, EventType: version, ID: {084b8268-4ae6-482d-b42b-67a57b5a7147})
+----------
+The name of the StateType ({084b8268-4ae6-482d-b42b-67a57b5a7147}) of ThingClass colorLight
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, EventType: version, ID: {477a1531-5f87-4f21-9b32-01161be93cd2})
+----------
+The name of the StateType ({477a1531-5f87-4f21-9b32-01161be93cd2}) of ThingClass colorTemperatureLight
+----------
+The name of the ParamType (ThingClass: dimmableLight, EventType: version, ID: {9390d2a6-7a17-4b42-8b35-cdb8b3d4d723})
+----------
+The name of the StateType ({9390d2a6-7a17-4b42-8b35-cdb8b3d4d723}) of ThingClass dimmableLight
+----------
+The name of the ParamType (ThingClass: onOffLight, EventType: version, ID: {f5b5e0b3-2c34-4ccc-baef-8e29b84e7a2b})
+----------
+The name of the StateType ({f5b5e0b3-2c34-4ccc-baef-8e29b84e7a2b}) of ThingClass onOffLight
+
+
+
+
+
+
+
+ Version changed
+ The name of the EventType ({084b8268-4ae6-482d-b42b-67a57b5a7147}) of ThingClass colorLight
+----------
+The name of the EventType ({477a1531-5f87-4f21-9b32-01161be93cd2}) of ThingClass colorTemperatureLight
+----------
+The name of the EventType ({9390d2a6-7a17-4b42-8b35-cdb8b3d4d723}) of ThingClass dimmableLight
+----------
+The name of the EventType ({f5b5e0b3-2c34-4ccc-baef-8e29b84e7a2b}) of ThingClass onOffLight
+
+
+
+
+ Zigbee Generic Lights
+ The name of the plugin ZigbeeGenericLights ({ee67f87d-d4cc-4223-a9f3-79e47c0267e0})
+
+
+
+
+
+
+
+ Zigbee network UUID
+ The name of the ParamType (ThingClass: colorLight, Type: thing, ID: {e409e8fe-9afa-407d-94fd-ec40f88f60f1})
+----------
+The name of the ParamType (ThingClass: colorTemperatureLight, Type: thing, ID: {2f1799f0-76da-4147-89a0-1db93bb74872})
+----------
+The name of the ParamType (ThingClass: dimmableLight, Type: thing, ID: {81a82ed2-c465-4e53-a208-157d8473b16c})
+----------
+The name of the ParamType (ThingClass: onOffLight, Type: thing, ID: {b3e298de-140c-4d3d-be81-0a9792359275})
+
+
+
+
+
+
+ color
+ The name of the ParamType (ThingClass: colorLight, ActionType: color, ID: {51e8e6da-426c-4812-9c00-70f1356190dc})
+----------
+The name of the ParamType (ThingClass: colorLight, EventType: color, ID: {51e8e6da-426c-4812-9c00-70f1356190dc})
+----------
+The name of the StateType ({51e8e6da-426c-4812-9c00-70f1356190dc}) of ThingClass colorLight
+
+
+
+
+ color changed
+ The name of the EventType ({51e8e6da-426c-4812-9c00-70f1356190dc}) of ThingClass colorLight
+
+
+
+
+ nymea
+ The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6})
+
+
+
+
diff --git a/zigbeegenericlights/zigbee.svg b/zigbeegenericlights/zigbee.svg
new file mode 100644
index 00000000..5366feb6
--- /dev/null
+++ b/zigbeegenericlights/zigbee.svg
@@ -0,0 +1,179 @@
+
+
+
+
diff --git a/zigbeegenericlights/zigbeegenericlights.pro b/zigbeegenericlights/zigbeegenericlights.pro
new file mode 100644
index 00000000..5561cbf7
--- /dev/null
+++ b/zigbeegenericlights/zigbeegenericlights.pro
@@ -0,0 +1,9 @@
+include(../plugins.pri)
+
+PKGCONFIG += nymea-zigbee
+
+SOURCES += \
+ integrationpluginzigbeegenericlights.cpp
+
+HEADERS += \
+ integrationpluginzigbeegenericlights.h
diff --git a/zigbeelumi/README.md b/zigbeelumi/README.md
new file mode 100644
index 00000000..10e3eafc
--- /dev/null
+++ b/zigbeelumi/README.md
@@ -0,0 +1,112 @@
+# ZigBee Aqara / Xiaomi Mi / Lumi
+
+This plugin allows to interact with ZigBee from Aqara / Xiaomi Mi / Lumi using a native ZigBee network controller in nymea.
+
+## Supported Things
+
+In order to bring a ZigBee device into the nymea ZigBee network, the network needs to be opened for joining before you perform the device pairing instructions. The joining process can take up to 30 seconds. If the device does not show up, please restart the pairing process.
+
+> Note: if you you already added one of the following devices and you restart the pairing process while the network is closed, the device will leave the ZigBee network and the thing will be removed automatically.
+
+### Aqara smart plug
+
+The [Aqara smart plug](https://www.aqara.com/en/smart_plug.html) can be switched on and off. The power consumtion is not suppoted yet, but planed for the future.
+
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara weather sensor
+
+The [Aqara weather sensor](https://www.aqara.com/us/temperature_humidity_sensor.html) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara door and window sensor
+
+The [Aqara door and window sensor](https://www.aqara.com/us/door_and_window_sensor.html) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara motion sensor
+
+The [Aqara motion sensor](https://www.aqara.com/us/motion_sensor.html) is fully supported. The duration for the present state can be configured in the thing settings.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara water leak sensor
+
+The [Aqara water leak sensor](https://www.aqara.com/us/water_leak_sensor.html) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara vibration sensor
+
+The [Aqara vibration sensor](https://www.aqara.com/us/vibration_sensor.html) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara mini switch
+
+The [Aqara mini switch](https://www.aqara.com/us/wireless_mini_switch.html) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara wall switch (Double key)
+
+The [Aqara wall switch (Double key)](https://xiaomi-mi.com/sockets-and-sensors/remote-switch-for-aqara-smart-light-wall-switch-double-key/) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Aqara single switch Module T1
+
+The [Aqara single switch module T1](https://www.aqara.com/eu/single_switch_T1_no-neutral.html) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button until the LED starts blinking.
+
+
+### Xiaomi Mi temperature humidity sensor
+
+The [Aqara single switch module T1](https://xiaomi-mi.com/sockets-and-sensors/xiaomi-mi-temperature-humidity-sensor/) is fully supported.
+
+> Note: it takes about 30 seconds until the sensor shows up because the initilization takes longer than normal.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button with a needle until the LED starts blinking.
+
+
+### Xiaomi Mi wireless switch
+
+The [Xiaomi Mi wireless switch](https://xiaomi-mi.com/sockets-and-sensors/xiaomi-mi-wireless-switch/) supports currently only the single button press. The multi click events will be implemented in the future.
+
+> Note: it takes about 30 seconds until the sensor shows up because the initilization takes longer than normal.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button with a needle until the LED starts blinking.
+
+### Xiaomi Mi door and window sensor
+
+The [Xiaomi Mi door and window sensor](https://xiaomi-mi.com/sockets-and-sensors/xiaomi-mi-door-window-sensors/) is fully supported.
+
+> Note: it takes about 30 seconds until the sensor shows up because the initilization takes longer than normal.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button with a needle until the LED starts blinking.
+
+### Xiaomi Mi occupancy sensor
+
+The [Xiaomi Mi occupancy sensor](https://xiaomi-mi.com/sockets-and-sensors/xiaomi-mi-occupancy-sensor/) is fully supported. The duration for the present state can be configured in the thing settings.
+
+> Note: it takes about 30 seconds until the sensor shows up because the initilization takes longer than normal.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press and hold the pairing button with a needle until the LED starts blinking.
+
+
+## Requirements
+
+* A compatible ZigBee controller and a running ZigBee network in nymea. You can find more information about supported controllers and ZigBee network configurations [here](https://nymea.io/documentation/users/usage/configuration#zigbee).
+
diff --git a/zigbeelumi/integrationpluginzigbeelumi.cpp b/zigbeelumi/integrationpluginzigbeelumi.cpp
new file mode 100644
index 00000000..c7e17a50
--- /dev/null
+++ b/zigbeelumi/integrationpluginzigbeelumi.cpp
@@ -0,0 +1,703 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginzigbeelumi.h"
+#include "plugininfo.h"
+#include "hardware/zigbee/zigbeehardwareresource.h"
+
+#include
+
+#include
+
+IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi()
+{
+ m_networkUuidParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiRelayThingClassId] = lumiRelayThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[lumiRemoteThingClassId] = lumiRemoteThingNetworkUuidParamTypeId;
+
+ m_zigbeeAddressParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[xiaomiMotionSensorThingClassId] = xiaomiMotionSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiRelayThingClassId] = lumiRelayThingIeeeAddressParamTypeId;
+ m_zigbeeAddressParamTypeIds[lumiRemoteThingClassId] = lumiRemoteThingIeeeAddressParamTypeId;
+
+ m_connectedStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[xiaomiMotionSensorThingClassId] = xiaomiMotionSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiRelayThingClassId] = lumiRelayConnectedStateTypeId;
+ m_connectedStateTypeIds[lumiRemoteThingClassId] = lumiRemoteConnectedStateTypeId;
+
+ m_versionStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorVersionStateTypeId;
+ m_versionStateTypeIds[xiaomiMotionSensorThingClassId] = xiaomiMotionSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorVersionStateTypeId;
+ m_versionStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketVersionStateTypeId;
+ m_versionStateTypeIds[lumiRelayThingClassId] = lumiRelayVersionStateTypeId;
+ m_versionStateTypeIds[lumiRemoteThingClassId] = lumiRemoteVersionStateTypeId;
+
+ m_signalStrengthStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[xiaomiMotionSensorThingClassId] = xiaomiMotionSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiRelayThingClassId] = lumiRelaySignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[lumiRemoteThingClassId] = lumiRemoteSignalStrengthStateTypeId;
+
+ // Known model identifier
+ m_knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId);
+ m_knownLumiDevices.insert("lumi.sensor_magnet", lumiMagnetSensorThingClassId);
+ m_knownLumiDevices.insert("lumi.sensor_switch", lumiButtonSensorThingClassId);
+ // Check sensor_motion separate since the have the same name but different features
+ //m_knownLumiDevices.insert("lumi.sensor_motion", lumiMotionSensorThingClassId);
+ m_knownLumiDevices.insert("lumi.sensor_wleak", lumiWaterSensorThingClassId);
+ m_knownLumiDevices.insert("lumi.weather", lumiWeatherSensorThingClassId);
+ m_knownLumiDevices.insert("lumi.vibration", lumiVibrationSensorThingClassId);
+ m_knownLumiDevices.insert("lumi.plug", lumiPowerSocketThingClassId);
+ m_knownLumiDevices.insert("lumi.relay", lumiRelayThingClassId);
+ m_knownLumiDevices.insert("lumi.remote", lumiRemoteThingClassId);
+}
+
+QString IntegrationPluginZigbeeLumi::name() const
+{
+ return "Lumi";
+}
+
+bool IntegrationPluginZigbeeLumi::handleNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ // Check if this is Lumi
+ // Note: Lumi / Xiaomi / Aquara devices are not in the specs, some older models do not
+ // send the node descriptor or use a inconsistent manufacturer code. We use the model identifier
+ // for verification since they seem to start always with lumi.
+ foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
+ // Get the model identifier if present from the first endpoint. Also this is out of spec
+ if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdBasic)) {
+ qCDebug(dcZigbeeLumi()) << "This lumi endpoint does not have the basic input cluster, so we skipp it" << endpoint;
+ continue;
+ }
+
+ // Basic cluster exists, so we should have the model name
+ if (!endpoint->modelIdentifier().toLower().startsWith("lumi.")) {
+ return false;
+ }
+
+ ThingClassId thingClassId;
+ if (endpoint->modelIdentifier().startsWith("lumi.sensor_motion")) {
+ // Check if this is a xiaomi or aquara motion sensor
+ if (endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement)) {
+ thingClassId = lumiMotionSensorThingClassId;
+ } else {
+ thingClassId = xiaomiMotionSensorThingClassId;
+ }
+
+ } else {
+ foreach (const QString &knownLumi, m_knownLumiDevices.keys()) {
+ if (endpoint->modelIdentifier().startsWith(knownLumi)) {
+ thingClassId = m_knownLumiDevices.value(knownLumi);
+ break;
+ }
+ }
+ }
+ if (thingClassId.isNull()) {
+ qCWarning(dcZigbeeLumi()) << "Unhandled Lumi device:" << endpoint->modelIdentifier();
+ return false;
+ }
+
+ ThingDescriptor descriptor(thingClassId, supportedThings().findById(thingClassId).displayName());
+ ParamList params;
+ params << Param(m_networkUuidParamTypeIds.value(thingClassId), networkUuid.toString());
+ params << Param(m_zigbeeAddressParamTypeIds.value(thingClassId), node->extendedAddress().toString());
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ return true;
+ }
+
+ return false;
+}
+
+void IntegrationPluginZigbeeLumi::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ Q_UNUSED(networkUuid)
+
+ if (m_thingNodes.values().contains(node)) {
+ Thing *thing = m_thingNodes.key(node);
+ qCDebug(dcZigbeeLumi()) << node << "for" << thing << "has left the network.";
+ m_thingNodes.remove(thing);
+ emit autoThingDisappeared(thing->id());
+ }
+}
+
+void IntegrationPluginZigbeeLumi::init()
+{
+ hardwareManager()->zigbeeResource()->registerHandler(this);
+}
+
+void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ // Get the node for this thing
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_zigbeeAddressParamTypeIds.value(thing->thingClassId())).toString());
+ ZigbeeNode *node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress);
+ if (!node) {
+ qCWarning(dcZigbeeLumi()) << "Zigbee node for" << info->thing()->name() << "not found.´";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // Get the endpoint of interest (0x01) for this device
+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01);
+ if (!endpoint) {
+ qCWarning(dcZigbeeLumi()) << "Zigbee endpoint 1 not found on" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ // Store the node thing association for removing
+ m_thingNodes.insert(thing, node);
+
+ // Update connected state
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), node->reachable());
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), reachable);
+ });
+
+ // Update signal strength
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), qRound(node->lqi() * 100.0 / 255.0));
+ connect(node, &ZigbeeNode::lqiChanged, thing, [this, thing](quint8 lqi){
+ uint signalStrength = qRound(lqi * 100.0 / 255.0);
+ qCDebug(dcZigbeeLumi()) << thing << "signal strength changed" << signalStrength << "%";
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength);
+ });
+
+ // Set the version
+ thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId());
+
+ // Thing specific setup
+ if (thing->thingClassId() == lumiMagnetSensorThingClassId) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (onOffCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !onOffCluster->power());
+ }
+
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "closed" : "open");
+ thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !power);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the OnOff input cluster on" << thing << endpoint;
+ }
+ }
+
+
+ if (thing->thingClassId() == lumiMotionSensorThingClassId) {
+ ZigbeeClusterOccupancySensing *occupancyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing);
+ if (occupancyCluster) {
+ if (occupancyCluster->hasAttribute(ZigbeeClusterOccupancySensing::AttributeOccupancy)) {
+ thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancyCluster->occupied());
+ thing->setStateValue(lumiMotionSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000);
+ }
+
+ connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [this, thing](bool occupancy){
+ qCDebug(dcZigbeeLumi()) << "occupancy changed" << occupancy;
+ // Only change the state if the it changed to true, it will be disabled by the timer
+ if (occupancy) {
+ thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancy);
+ m_presenceTimer->start();
+ }
+
+ thing->setStateValue(lumiMotionSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000);
+ });
+
+ if (!m_presenceTimer) {
+ m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
+ }
+
+ connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){
+ if (thing->stateValue(lumiMotionSensorIsPresentStateTypeId).toBool()) {
+ int timeout = thing->setting(lumiMotionSensorSettingsTimeoutParamTypeId).toInt();
+ QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(lumiMotionSensorLastSeenTimeStateTypeId).toULongLong() * 1000);
+ if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) {
+ thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, false);
+ }
+ }
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Occupancy cluster not found on" << thing->name();
+ }
+
+ ZigbeeClusterIlluminanceMeasurment *illuminanceCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement);
+ if (illuminanceCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (illuminanceCluster->hasAttribute(ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue)) {
+ thing->setStateValue(lumiMotionSensorLightIntensityStateTypeId, illuminanceCluster->illuminance());
+ }
+
+ connect(illuminanceCluster, &ZigbeeClusterIlluminanceMeasurment::illuminanceChanged, thing, [thing](quint16 illuminance){
+ qCDebug(dcZigbeeLumi()) << thing << "light intensity changed" << illuminance << "lux";
+ thing->setStateValue(lumiMotionSensorLightIntensityStateTypeId, illuminance);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Illuminance cluster not found on" << thing->name();
+ }
+ }
+
+
+ if (thing->thingClassId() == xiaomiMotionSensorThingClassId) {
+ ZigbeeClusterOccupancySensing *occupancyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing);
+ if (occupancyCluster) {
+ if (occupancyCluster->hasAttribute(ZigbeeClusterOccupancySensing::AttributeOccupancy)) {
+ thing->setStateValue(xiaomiMotionSensorIsPresentStateTypeId, occupancyCluster->occupied());
+ thing->setStateValue(xiaomiMotionSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000);
+ }
+
+ connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [this, thing](bool occupancy){
+ qCDebug(dcZigbeeLumi()) << "occupancy changed" << occupancy;
+ // Only change the state if the it changed to true, it will be disabled by the timer
+ if (occupancy) {
+ thing->setStateValue(xiaomiMotionSensorIsPresentStateTypeId, occupancy);
+ m_presenceTimer->start();
+ }
+
+ thing->setStateValue(xiaomiMotionSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000);
+ });
+
+ if (!m_presenceTimer) {
+ m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
+ }
+
+ connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){
+ if (thing->stateValue(xiaomiMotionSensorIsPresentStateTypeId).toBool()) {
+ int timeout = thing->setting(xiaomiMotionSensorSettingsTimeoutParamTypeId).toInt();
+ QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(xiaomiMotionSensorLastSeenTimeStateTypeId).toULongLong() * 1000);
+ if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) {
+ thing->setStateValue(xiaomiMotionSensorIsPresentStateTypeId, false);
+ }
+ }
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Occupancy cluster not found on" << thing->name();
+ }
+ }
+
+ if (thing->thingClassId() == lumiHTSensorThingClassId) {
+ ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement);
+ if (temperatureCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (temperatureCluster->hasAttribute(ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue)) {
+ thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperatureCluster->temperature());
+ }
+
+ connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){
+ qCDebug(dcZigbeeLumi()) << thing << "temperature changed" << temperature << "°C";
+ thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperature);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the temperature measurement server cluster on" << thing << endpoint;
+ }
+
+ ZigbeeClusterRelativeHumidityMeasurement *humidityCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement);
+ if (humidityCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (humidityCluster->hasAttribute(ZigbeeClusterRelativeHumidityMeasurement::AttributeMeasuredValue)) {
+ thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidityCluster->humidity());
+ }
+
+ connect(humidityCluster, &ZigbeeClusterRelativeHumidityMeasurement::humidityChanged, thing, [thing](double humidity){
+ qCDebug(dcZigbeeLumi()) << thing << "humidity changed" << humidity << "%";
+ thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidity);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the relative humidity measurement server cluster on" << thing << endpoint;
+ }
+ }
+
+ if (thing->thingClassId() == lumiWeatherSensorThingClassId) {
+ ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement);
+ if (temperatureCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (temperatureCluster->hasAttribute(ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue)) {
+ thing->setStateValue(lumiWeatherSensorTemperatureStateTypeId, temperatureCluster->temperature());
+ }
+
+ connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){
+ qCDebug(dcZigbeeLumi()) << thing << "temperature changed" << temperature << "°C";
+ thing->setStateValue(lumiWeatherSensorTemperatureStateTypeId, temperature);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the temperature measurement server cluster on" << thing << endpoint;
+ }
+
+ ZigbeeClusterRelativeHumidityMeasurement *humidityCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement);
+ if (humidityCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (humidityCluster->hasAttribute(ZigbeeClusterRelativeHumidityMeasurement::AttributeMeasuredValue)) {
+ thing->setStateValue(lumiWeatherSensorHumidityStateTypeId, humidityCluster->humidity());
+ }
+
+ connect(humidityCluster, &ZigbeeClusterRelativeHumidityMeasurement::humidityChanged, thing, [thing](double humidity){
+ qCDebug(dcZigbeeLumi()) << thing << "humidity changed" << humidity << "%";
+ thing->setStateValue(lumiWeatherSensorHumidityStateTypeId, humidity);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the relative humidity measurement server cluster on" << thing << endpoint;
+ }
+
+ ZigbeeClusterPressureMeasurement *pressureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPressureMeasurement);
+ if (pressureCluster) {
+ // Only set the state if the cluster actually has the attribute
+ if (pressureCluster->hasAttribute(ZigbeeClusterPressureMeasurement::AttributeMeasuredValue)) {
+ thing->setStateValue(lumiWeatherSensorPressureStateTypeId, pressureCluster->pressure() * 101);
+ }
+
+ connect(pressureCluster, &ZigbeeClusterPressureMeasurement::pressureChanged, thing, [thing](double pressure){
+ thing->setStateValue(lumiWeatherSensorPressureStateTypeId, pressure * 101);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the pressure measurement server cluster on" << thing << endpoint;
+ }
+ }
+
+
+ if (thing->thingClassId() == lumiWaterSensorThingClassId) {
+ connect(endpoint, &ZigbeeNodeEndpoint::clusterAttributeChanged, this, [thing](ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute){
+ if (cluster->clusterId() == ZigbeeClusterLibrary::ClusterIdIasZone) {
+ if (attribute.id() == ZigbeeClusterIasZone::AttributeZoneState) {
+ bool valueOk = false;
+ ZigbeeClusterIasZone::ZoneStatusFlags zoneStatus = static_cast(attribute.dataType().toUInt16(&valueOk));
+ if (!valueOk) {
+ qCWarning(dcZigbeeLumi()) << thing << "failed to convert attribute data to uint16 flag. Not updating the states from" << attribute;
+ } else {
+ qCDebug(dcZigbeeLumi()) << thing << "zone status changed" << zoneStatus;
+
+ // Water detected gets indicated in the Alarm1 flag
+ if (zoneStatus.testFlag(ZigbeeClusterIasZone::ZoneStatusAlarm1)) {
+ thing->setStateValue(lumiWaterSensorWaterDetectedStateTypeId, true);
+ } else {
+ thing->setStateValue(lumiWaterSensorWaterDetectedStateTypeId, false);
+ }
+
+ // Battery alarm
+ if (zoneStatus.testFlag(ZigbeeClusterIasZone::ZoneStatusBattery)) {
+ thing->setStateValue(lumiWaterSensorBatteryCriticalStateTypeId, true);
+ } else {
+ thing->setStateValue(lumiWaterSensorBatteryCriticalStateTypeId, false);
+ }
+ }
+ }
+ }
+ });
+ }
+
+
+ if (thing->thingClassId() == lumiVibrationSensorThingClassId) {
+ connect(endpoint, &ZigbeeNodeEndpoint::clusterAttributeChanged, this, [this, thing](ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute){
+ if (cluster->clusterId() == ZigbeeClusterLibrary::ClusterIdDoorLock) {
+ // Note: shoehow the vibration sensor is using the door lock cluster, with undocumented attribitues.
+ // This device is completly out of spec, so we just recognize the vibration trough tests and it looks like
+ // attribute id 85 is the indicator for vibration. The payload contains an unsigned int, but not sure what it indicates yet
+
+ if (attribute.id() == 85) {
+ bool valueOk = false;
+ quint16 value = attribute.dataType().toUInt16(&valueOk);
+ if (!valueOk) {
+ qCWarning(dcZigbeeLumi()) << thing << "failed to convert attribute data to uint16." << attribute;
+ } else {
+ qCDebug(dcZigbeeLumi()) << thing << "vibration attribute changed" << value;
+ emitEvent(Event(lumiVibrationSensorVibrationDetectedEventTypeId, thing->id()));
+ }
+ }
+ }
+ });
+ }
+
+ if (thing->thingClassId() == lumiButtonSensorThingClassId) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (onOffCluster) {
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [this, thing](bool power){
+ qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "pressed" : "released");
+ if (!power) {
+ emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id()));
+ }
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the OnOff input cluster on" << thing << endpoint;
+ }
+ }
+
+
+ if (thing->thingClassId() == lumiPowerSocketThingClassId) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (onOffCluster) {
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(lumiPowerSocketPowerStateTypeId, onOffCluster->power());
+ }
+
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeLumi()) << thing << "power changed" << power;
+ thing->setStateValue(lumiPowerSocketPowerStateTypeId, power);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the OnOff input cluster on" << thing << endpoint;
+ }
+ }
+
+ if (thing->thingClassId() == lumiRemoteThingClassId) {
+ // Since we are here again out of spec, we just can react on cluster and endpoint signals
+ connect(node, &ZigbeeNode::endpointClusterAttributeChanged, thing, [this, thing](ZigbeeNodeEndpoint *endpoint, ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute){
+ switch (endpoint->endpointId()) {
+ case 0x01:
+ if (cluster->clusterId() == ZigbeeClusterLibrary::ClusterIdMultistateInput && attribute.id() == ZigbeeClusterMultistateInput::AttributePresentValue) {
+ quint16 value = attribute.dataType().toUInt16();
+ if (value == 1) {
+ emit emitEvent(Event(lumiRemotePressedEventTypeId, thing->id(), ParamList() << Param(lumiRemotePressedEventButtonNameParamTypeId, "1")));
+ } else {
+ emit emitEvent(Event(lumiRemoteLongPressedEventTypeId, thing->id(), ParamList() << Param(lumiRemoteLongPressedEventButtonNameParamTypeId, "1")));
+ }
+ }
+ break;
+ case 0x02:
+ if (cluster->clusterId() == ZigbeeClusterLibrary::ClusterIdMultistateInput && attribute.id() == ZigbeeClusterMultistateInput::AttributePresentValue) {
+ quint16 value = attribute.dataType().toUInt16();
+ if (value == 1) {
+ emit emitEvent(Event(lumiRemotePressedEventTypeId, thing->id(), ParamList() << Param(lumiRemotePressedEventButtonNameParamTypeId, "2")));
+ } else {
+ emit emitEvent(Event(lumiRemoteLongPressedEventTypeId, thing->id(), ParamList() << Param(lumiRemoteLongPressedEventButtonNameParamTypeId, "2")));
+ }
+ }
+ break;
+ case 0x03:
+ if (cluster->clusterId() == ZigbeeClusterLibrary::ClusterIdMultistateInput && attribute.id() == ZigbeeClusterMultistateInput::AttributePresentValue) {
+ quint16 value = attribute.dataType().toUInt16();
+ if (value == 1) {
+ emit emitEvent(Event(lumiRemotePressedEventTypeId, thing->id(), ParamList() << Param(lumiRemotePressedEventButtonNameParamTypeId, "1+2")));
+ } else {
+ emit emitEvent(Event(lumiRemoteLongPressedEventTypeId, thing->id(), ParamList() << Param(lumiRemoteLongPressedEventButtonNameParamTypeId, "1+2")));
+ }
+ }
+ break;
+ default:
+ qCWarning(dcZigbeeLumi()) << "Received attribute changed signal from unhandled endpoint" << thing << endpoint << cluster << attribute;
+ break;
+ }
+ });
+ }
+
+ if (thing->thingClassId() == lumiRelayThingClassId) {
+ // Get the 2 endpoints
+ ZigbeeNodeEndpoint *endpoint1 = node->getEndpoint(0x01);
+ if (endpoint1) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint1->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (onOffCluster) {
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(lumiRelayRelay1StateTypeId, onOffCluster->power());
+ }
+
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeLumi()) << thing << "power changed" << power;
+ thing->setStateValue(lumiRelayRelay1StateTypeId, power);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the OnOff input cluster on" << thing << endpoint1;
+ }
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find endpoint 1 on" << thing << node;
+ }
+
+ ZigbeeNodeEndpoint *endpoint2 = node->getEndpoint(0x02);
+ if (endpoint2) {
+ ZigbeeClusterOnOff *onOffCluster = endpoint2->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (onOffCluster) {
+ if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) {
+ thing->setStateValue(lumiRelayRelay2StateTypeId, onOffCluster->power());
+ }
+
+ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){
+ qCDebug(dcZigbeeLumi()) << thing << "power changed" << power;
+ thing->setStateValue(lumiRelayRelay2StateTypeId, power);
+ });
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find the OnOff input cluster on" << thing << endpoint1;
+ }
+ } else {
+ qCWarning(dcZigbeeLumi()) << "Could not find endpoint 2 on" << thing << node;
+ }
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info)
+{
+ Thing *thing = info->thing();
+
+ if (thing->thingClassId() == lumiPowerSocketThingClassId) {
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node) {
+ qCWarning(dcZigbeeLumi()) << "Zigbee node for" << thing << "not found.";
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01);
+ if (!endpoint) {
+ qCWarning(dcZigbeeLumi()) << "Unable to get the endpoint from node" << node << "for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeLumi()) << "Could not find on/off cluster for" << thing << "in" << node;
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ bool power = info->action().param(lumiPowerSocketPowerActionPowerParamTypeId).value().toBool();
+ qCDebug(dcZigbeeLumi()) << "Set power for" << info->thing() << "to" << power;
+ ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff());
+ connect(reply, &ZigbeeClusterReply::finished, info, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeLumi()) << "Failed to set power on" << thing << reply->error();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ qCDebug(dcZigbeeLumi()) << "Set power finished successfully for" << thing;
+ thing->setStateValue(lumiPowerSocketPowerStateTypeId, power);
+ }
+ });
+ return;
+ }
+
+ if (thing->thingClassId() == lumiRelayThingClassId) {
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node) {
+ qCWarning(dcZigbeeLumi()) << "Zigbee node for" << thing << "not found.";
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ if (info->action().actionTypeId() == lumiRelayRelay1ActionTypeId) {
+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01);
+ if (!endpoint) {
+ qCWarning(dcZigbeeLumi()) << "Unable to get the endpoint from node" << node << "for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeLumi()) << "Unable to get the OnOff cluster from endpoint" << endpoint << "on" << node << "for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+ bool power = info->action().param(lumiRelayRelay1ActionRelay1ParamTypeId).value().toBool();
+ ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff());
+ connect(reply, &ZigbeeClusterReply::finished, this, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ thing->setStateValue(lumiRelayRelay1StateTypeId, power);
+ }
+ });
+ }
+
+ if (info->action().actionTypeId() == lumiRelayRelay2ActionTypeId) {
+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x02);
+ if (!endpoint) {
+ qCWarning(dcZigbeeLumi()) << "Unable to get the endpoint from node" << node << "for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeLumi()) << "Unable to get the OnOff cluster from endpoint" << endpoint << "on" << node << "for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+ bool power = info->action().param(lumiRelayRelay2ActionRelay2ParamTypeId).value().toBool();
+ ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff());
+ connect(reply, &ZigbeeClusterReply::finished, this, [=](){
+ // Note: reply will be deleted automatically
+ if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ info->finish(Thing::ThingErrorNoError);
+ thing->setStateValue(lumiRelayRelay2StateTypeId, power);
+ }
+ });
+ }
+ }
+
+ info->finish(Thing::ThingErrorUnsupportedFeature);
+}
+
+void IntegrationPluginZigbeeLumi::thingRemoved(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.take(thing);
+ if (node) {
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node);
+ }
+}
diff --git a/zigbeelumi/integrationpluginzigbeelumi.h b/zigbeelumi/integrationpluginzigbeelumi.h
new file mode 100644
index 00000000..96369819
--- /dev/null
+++ b/zigbeelumi/integrationpluginzigbeelumi.h
@@ -0,0 +1,74 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINZIGBEELUMI_H
+#define INTEGRATIONPLUGINZIGBEELUMI_H
+
+#include "integrations/integrationplugin.h"
+#include "hardware/zigbee/zigbeehandler.h"
+#include "plugintimer.h"
+
+#include
+
+class IntegrationPluginZigbeeLumi: public IntegrationPlugin, public ZigbeeHandler
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeelumi.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginZigbeeLumi();
+
+ QString name() const override;
+ bool handleNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+ void handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+
+ void init() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ QHash m_networkUuidParamTypeIds;
+ QHash m_zigbeeAddressParamTypeIds;
+
+ QHash m_connectedStateTypeIds;
+ QHash m_signalStrengthStateTypeIds;
+ QHash m_versionStateTypeIds;
+
+ QHash m_knownLumiDevices;
+
+ QHash m_thingNodes;
+
+ PluginTimer *m_presenceTimer = nullptr;
+};
+
+#endif // INTEGRATIONPLUGINZIGBEELUMI_H
diff --git a/zigbeelumi/integrationpluginzigbeelumi.json b/zigbeelumi/integrationpluginzigbeelumi.json
new file mode 100644
index 00000000..5bd988cb
--- /dev/null
+++ b/zigbeelumi/integrationpluginzigbeelumi.json
@@ -0,0 +1,903 @@
+{
+ "name": "ZigbeeLumi",
+ "displayName": "Zigbee Lumi",
+ "id": "4a6e97da-0e4b-4731-8bfe-c93d806a9383",
+ "vendors": [
+ {
+ "name": "lumi",
+ "displayName": "Lumi",
+ "id": "f58dac98-0ba1-408f-946a-25d452080baa",
+ "thingClasses": [
+ {
+ "name": "lumiHTSensor",
+ "displayName": "Temperature humidity sensor",
+ "id": "dfabab0e-d483-43f8-82c6-720899e70c86",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "temperaturesensor", "humiditysensor", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "bd0b2bf2-2ec3-497f-9679-a63850101257",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "2daebedb-d272-4553-a410-91feff69ff7a",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "eb09e3f4-44ea-4ca9-9eac-9a693595fe37",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "0cb72e84-01c4-4041-9aee-1dabf26a062e",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "968ff8bb-b852-48d8-bde2-28cfa810b20e",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "152b475e-6bd8-4e6f-8765-0636c0bc8e9d",
+ "name": "temperature",
+ "displayName": "Temperature",
+ "displayNameEvent": "Temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.0
+ },
+ {
+ "id": "e6559d9e-2c64-485d-bb2b-8a9aec0ac656",
+ "name": "humidity",
+ "displayName": "Humidity",
+ "displayNameEvent": "Humidity changed",
+ "maxValue": 100,
+ "minValue": 0,
+ "unit": "Percentage",
+ "type": "double",
+ "defaultValue": 0.0
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiWeatherSensor",
+ "displayName": "Weather sensor",
+ "id": "0b582616-0b05-4ac9-8b59-51b66079b571",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "pressuresensor", "temperaturesensor", "humiditysensor", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "ef1129e2-bdf8-423e-9c11-fa8b406f960b",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "fe4cae8c-195c-4f38-ad88-cebd4adbb337",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "003f7e37-326b-455d-828b-5a9493b56a69",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "684f642e-08ed-4912-b7a9-597baef400c0",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "cc644362-19ca-489d-8122-4d1bc55f100f",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "b1641cec-3bf6-4654-b9c0-b81acb3b4481",
+ "name": "temperature",
+ "displayName": "Temperature",
+ "displayNameEvent": "Temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.0
+ },
+ {
+ "id": "27a1e85a-f654-48d8-905d-05b3e2bc499e",
+ "name": "humidity",
+ "displayName": "Humidity",
+ "displayNameEvent": "Humidity changed",
+ "maxValue": 100,
+ "minValue": 0,
+ "unit": "Percentage",
+ "type": "double",
+ "defaultValue": 0.0
+ },
+ {
+ "id": "7c3861f3-a9db-407e-9459-90a511d7f797",
+ "name": "pressure",
+ "displayName": "Pressure",
+ "displayNameEvent": "Pressure changed",
+ "unit": "MilliBar",
+ "type": "double",
+ "defaultValue": 0.0
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiMagnetSensor",
+ "displayName": "Magnet sensor",
+ "id": "b3e30f37-7467-4c66-8694-9fe624aebd10",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "closablesensor", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "36d8a40a-7f37-4d59-a0d9-6d4977ea63f3",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "6a238726-ccd5-484b-ac5d-92649a604f9a",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "76f6c739-73e0-4a36-97c9-043af5854af5",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "7fef2f25-91ac-4818-bc7a-e427bdeff438",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "ac0e4cd1-553b-4e43-b08a-6dd8d617e765",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "6fad76ec-9d36-408d-bbf1-00e5d18bf6b0",
+ "name": "closed",
+ "displayName": "Closed",
+ "displayNameEvent": "Closed changed",
+ "type": "bool",
+ "defaultValue": true,
+ "ioType": "digitalInput"
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiButtonSensor",
+ "displayName": "Button sensor",
+ "id": "42c1edba-cc5f-4eb9-84f8-1b0d47a6f95e",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "longpressbutton", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "929eb2be-6d8f-46b7-8cc9-896e7e2c494a",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "de153a5e-c7bd-4815-aa08-21d093b296d7",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "f0da4762-4778-4b3b-ba68-466ff504a3d3",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "6e9de8ee-cb28-4f7d-a0f6-fe9e64df0463",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "11d03ab4-7450-4caf-81ec-0b2b73e56b08",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ }
+ ],
+ "actionTypes": [
+ ],
+ "eventTypes": [
+ {
+ "id": "2515e40a-8146-4f11-8d79-2be9e20224e5",
+ "name": "pressed",
+ "displayName": "Pressed"
+ },
+ {
+ "id": "6e9dda9f-e51b-48c4-9839-01aa33085e2c",
+ "name": "longPressed",
+ "displayName": "Long pressed"
+ }
+ ]
+ },
+ {
+ "name": "lumiMotionSensor",
+ "displayName": "Motion sensor",
+ "id": "b5530192-0891-4934-88a2-7338b069be24",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "presencesensor", "lightsensor", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "3a44ed47-5a70-4052-9a14-78f9033eab85",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "bc0c78f6-ed88-4ae3-8e1e-2190374391af",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "settingsTypes": [
+ {
+ "id": "4aeb84fa-e3ef-495e-8754-a5f6c3aa934a",
+ "name": "timeout",
+ "displayName": "Time period",
+ "type": "uint",
+ "unit": "Seconds",
+ "defaultValue": 10,
+ "minValue": 10
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "007e2fe5-80d5-4f98-941d-b458b555965a",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "c5913170-e377-484b-a330-c5d5b590fee2",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "d1337b3b-0dfb-4505-90f1-e5b2b45be9bd",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "fed7582b-c057-42ab-b7c0-d562a2e7917c",
+ "name": "isPresent",
+ "displayName": "Present",
+ "displayNameEvent": "Present changed",
+ "type": "bool",
+ "defaultValue": true,
+ "ioType": "digitalInput"
+ },
+ {
+ "id": "021ce190-a775-40e0-b19b-c6307c73e343",
+ "name": "lastSeenTime",
+ "displayName": "Last seen time",
+ "displayNameEvent": "Last seen time changed",
+ "type": "int",
+ "unit": "UnixTime",
+ "defaultValue": 0
+ },
+ {
+ "id": "eb3fb43e-a9bb-4498-880b-464cbb752ff6",
+ "name": "lightIntensity",
+ "displayName": "Light intensity",
+ "displayNameEvent": "Light intensity changed",
+ "type": "double",
+ "unit": "Lux",
+ "defaultValue": 0
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "xiaomiMotionSensor",
+ "displayName": "Motion sensor",
+ "id": "73bfe746-4fc7-4a2a-a3c2-32dc44214170",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "presencesensor", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "e3a52900-320e-4ada-957a-6a10594f08c8",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "c1e7a76f-81ea-42f6-846b-86ef40087ba0",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "settingsTypes": [
+ {
+ "id": "96183481-42f2-40fc-8a19-5b59bd311720",
+ "name": "timeout",
+ "displayName": "Time period",
+ "type": "uint",
+ "unit": "Seconds",
+ "defaultValue": 10,
+ "minValue": 10
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "d0239a0a-9320-45b6-8652-5e6af4f544d8",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "84a2061d-da9c-4755-87b2-770dc92a2a53",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "ad45268e-54f0-44fe-9a8f-25ccdca92452",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "9a30ff91-64cb-432c-8db7-cf6a751e8601",
+ "name": "isPresent",
+ "displayName": "Present",
+ "displayNameEvent": "Present changed",
+ "type": "bool",
+ "defaultValue": true,
+ "ioType": "digitalInput"
+ },
+ {
+ "id": "55229e22-e42b-4d38-af4b-fb668439d38a",
+ "name": "lastSeenTime",
+ "displayName": "Last seen time",
+ "displayNameEvent": "Last seen time changed",
+ "type": "int",
+ "unit": "UnixTime",
+ "defaultValue": 0
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiWaterSensor",
+ "displayName": "Water sensor",
+ "id": "e8e18bbe-27bb-4fd7-98fc-f5cef3b5f213",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "sensor", "battery", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "e12453e7-5fc3-4549-b4fc-8b85f78607c6",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "4c67bab4-da84-407a-8d8d-84a5c373d585",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "03fc0e4d-dcd9-41dc-8aac-dbe735f11ef7",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "b3bba018-b9d0-47a8-a2cf-d8d2d6fcecce",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "bae1014f-67f5-4dcc-b36f-e15eb43afd6e",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "e43532a7-cbee-446e-9e4a-aff88026ad1a",
+ "name": "waterDetected",
+ "displayName": "Water detected",
+ "displayNameEvent": "Water detected changed",
+ "type": "bool",
+ "defaultValue": true,
+ "ioType": "digitalInput"
+ },
+ {
+ "id": "72aa617c-88cc-4992-a1a2-400b5e19106e",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiVibrationSensor",
+ "displayName": "Vibration sensor",
+ "id": "775be45e-08d8-4ae8-bc95-5721a5317856",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "324cd9a0-3381-490b-9537-88b65e0093bf",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "4e758a2e-32dc-44d8-872f-f656f30aca82",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "12970c44-5c59-4c6b-a244-6bc413d32c66",
+ "name": "connected",
+ "displayName": "Available",
+ "displayNameEvent": "Available changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "eb8bbd63-1f1c-4499-8385-06fbe8e301db",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "8a4aceef-c8ee-487a-946c-20f26fc2d5ed",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+ {
+ "id": "ce98b90b-f4fb-431d-a7c9-534fd2b76ba4",
+ "name": "vibrationDetected",
+ "displayName": "Vibration detected"
+ }
+ ]
+ },
+ {
+ "name": "lumiPowerSocket",
+ "displayName": "Power socket",
+ "id": "76afde12-e781-4723-94ca-5704e800e821",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "powersocket", "alert", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "0ebea7b9-488d-490e-9d70-e04ec92efd67",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "f92b9d8b-6dbd-45dd-91f7-cd5f4d496877",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "58e01e24-fa6e-4518-ad05-f3b118901f69",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "81926f63-4dc8-4701-a62d-07b20a849b37",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "e4a8e90c-4e38-4b3b-b5e6-fd336e3fa2a8",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "4c81aa6b-3a8a-4d2c-be87-29486ca242af",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true,
+ "ioType": "digitalOutput"
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "67052e4d-350a-406f-91a9-3ce3f8eaaa7f",
+ "name": "alert",
+ "displayName": "Identify"
+ }
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiRelay",
+ "displayName": "Relay",
+ "id": "8e381cb1-2caa-4261-be77-e76a538bf67a",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "f4a51b83-9cfa-402f-9d2a-e42933e33660",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "b5783bb8-ae1d-4e93-84f9-3f1e8c59b955",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "b12d1765-0b2e-4e95-b5ef-780d27fdda44",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "bc22b220-003f-4f19-9aef-21c4049c5a0e",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "ca1a8d08-4423-4401-bca7-d52b11e54ed9",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "486e0df0-cbda-4b67-b844-6f70e5af6891",
+ "name": "relay1",
+ "displayName": "Relay 1",
+ "displayNameEvent": "Relay 1 changed",
+ "displayNameAction": "Set relay 1",
+ "type": "bool",
+ "writable": true,
+ "defaultValue": false,
+ "ioType": "digitalOutput"
+ },
+ {
+ "id": "2caa4f0f-661e-4b59-83e5-8417058e48fd",
+ "name": "relay2",
+ "displayName": "Relay 2",
+ "displayNameEvent": "Relay 2 changed",
+ "displayNameAction": "Set relay 2",
+ "type": "bool",
+ "writable": true,
+ "defaultValue": false,
+ "ioType": "digitalOutput"
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "lumiRemote",
+ "displayName": "Switch",
+ "id": "f8aa2ba1-ce74-4691-b97d-85427f4cf8bd",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "longpressmultibutton", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "cce8b39c-ac8a-4a8f-b66e-b6100a209fb8",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "8eec3cae-8f3b-4c32-b26a-f7c49844841e",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "d1afa3ba-f3bd-47e5-90fa-9d80dcce91d8",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "a764dbfb-7b86-439b-94ea-aec742dcb13e",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "8164c1d0-657e-4347-99fb-f3fdc07af6de",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+ {
+ "id": "9a0c5f37-e346-41d9-93a4-4877c012b805",
+ "name": "pressed",
+ "displayName": "Button pressed",
+ "paramTypes": [
+ {
+ "id": "72df572a-77d2-42b4-8ffa-cde70ccca37c",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["1", "2", "1+2"]
+ }
+ ]
+ },
+ {
+ "id": "9a7a3d9e-faa3-471f-8c82-0bb98d9a788a",
+ "name": "longPressed",
+ "displayName": "Button longpressed",
+ "paramTypes": [
+ {
+ "id": "0a8d4da6-be6e-4b7d-92d7-5cf35d74042f",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["1", "2", "1+2"]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/zigbeelumi/meta.json b/zigbeelumi/meta.json
new file mode 100644
index 00000000..99ec7cfc
--- /dev/null
+++ b/zigbeelumi/meta.json
@@ -0,0 +1,16 @@
+{
+ "title": "ZigBee Lumi / Aqara / Xiaomi Mi",
+ "tagline": "Connect and control ZigBee devices from Lumi / Aqara / Xiaomi Mi native.",
+ "icon": "zigbee.svg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "zigbee"
+ ],
+ "categories": [
+ "sensor",
+ "switch",
+ "relay",
+ "socket"
+ ]
+}
diff --git a/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts b/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts
new file mode 100644
index 00000000..5278713f
--- /dev/null
+++ b/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts
@@ -0,0 +1,772 @@
+
+
+
+
+ ZigbeeLumi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Available
+ The name of the ParamType (ThingClass: lumiVibrationSensor, EventType: connected, ID: {12970c44-5c59-4c6b-a244-6bc413d32c66})
+----------
+The name of the StateType ({12970c44-5c59-4c6b-a244-6bc413d32c66}) of ThingClass lumiVibrationSensor
+----------
+The name of the ParamType (ThingClass: lumiWaterSensor, EventType: connected, ID: {03fc0e4d-dcd9-41dc-8aac-dbe735f11ef7})
+----------
+The name of the StateType ({03fc0e4d-dcd9-41dc-8aac-dbe735f11ef7}) of ThingClass lumiWaterSensor
+----------
+The name of the ParamType (ThingClass: xiaomiMotionSensor, EventType: connected, ID: {d0239a0a-9320-45b6-8652-5e6af4f544d8})
+----------
+The name of the StateType ({d0239a0a-9320-45b6-8652-5e6af4f544d8}) of ThingClass xiaomiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, EventType: connected, ID: {007e2fe5-80d5-4f98-941d-b458b555965a})
+----------
+The name of the StateType ({007e2fe5-80d5-4f98-941d-b458b555965a}) of ThingClass lumiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiButtonSensor, EventType: connected, ID: {f0da4762-4778-4b3b-ba68-466ff504a3d3})
+----------
+The name of the StateType ({f0da4762-4778-4b3b-ba68-466ff504a3d3}) of ThingClass lumiButtonSensor
+----------
+The name of the ParamType (ThingClass: lumiMagnetSensor, EventType: connected, ID: {76f6c739-73e0-4a36-97c9-043af5854af5})
+----------
+The name of the StateType ({76f6c739-73e0-4a36-97c9-043af5854af5}) of ThingClass lumiMagnetSensor
+----------
+The name of the ParamType (ThingClass: lumiWeatherSensor, EventType: connected, ID: {003f7e37-326b-455d-828b-5a9493b56a69})
+----------
+The name of the StateType ({003f7e37-326b-455d-828b-5a9493b56a69}) of ThingClass lumiWeatherSensor
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, EventType: connected, ID: {eb09e3f4-44ea-4ca9-9eac-9a693595fe37})
+----------
+The name of the StateType ({eb09e3f4-44ea-4ca9-9eac-9a693595fe37}) of ThingClass lumiHTSensor
+
+
+
+
+
+
+
+
+
+
+
+ Available changed
+ The name of the EventType ({12970c44-5c59-4c6b-a244-6bc413d32c66}) of ThingClass lumiVibrationSensor
+----------
+The name of the EventType ({03fc0e4d-dcd9-41dc-8aac-dbe735f11ef7}) of ThingClass lumiWaterSensor
+----------
+The name of the EventType ({d0239a0a-9320-45b6-8652-5e6af4f544d8}) of ThingClass xiaomiMotionSensor
+----------
+The name of the EventType ({007e2fe5-80d5-4f98-941d-b458b555965a}) of ThingClass lumiMotionSensor
+----------
+The name of the EventType ({f0da4762-4778-4b3b-ba68-466ff504a3d3}) of ThingClass lumiButtonSensor
+----------
+The name of the EventType ({76f6c739-73e0-4a36-97c9-043af5854af5}) of ThingClass lumiMagnetSensor
+----------
+The name of the EventType ({003f7e37-326b-455d-828b-5a9493b56a69}) of ThingClass lumiWeatherSensor
+----------
+The name of the EventType ({eb09e3f4-44ea-4ca9-9eac-9a693595fe37}) of ThingClass lumiHTSensor
+
+
+
+
+
+ Battery critical
+ The name of the ParamType (ThingClass: lumiWaterSensor, EventType: batteryCritical, ID: {72aa617c-88cc-4992-a1a2-400b5e19106e})
+----------
+The name of the StateType ({72aa617c-88cc-4992-a1a2-400b5e19106e}) of ThingClass lumiWaterSensor
+
+
+
+
+ Battery critical changed
+ The name of the EventType ({72aa617c-88cc-4992-a1a2-400b5e19106e}) of ThingClass lumiWaterSensor
+
+
+
+
+ Button longpressed
+ The name of the EventType ({9a7a3d9e-faa3-471f-8c82-0bb98d9a788a}) of ThingClass lumiRemote
+
+
+
+
+
+ Button name
+ The name of the ParamType (ThingClass: lumiRemote, EventType: longPressed, ID: {0a8d4da6-be6e-4b7d-92d7-5cf35d74042f})
+----------
+The name of the ParamType (ThingClass: lumiRemote, EventType: pressed, ID: {72df572a-77d2-42b4-8ffa-cde70ccca37c})
+
+
+
+
+ Button pressed
+ The name of the EventType ({9a0c5f37-e346-41d9-93a4-4877c012b805}) of ThingClass lumiRemote
+
+
+
+
+ Button sensor
+ The name of the ThingClass ({42c1edba-cc5f-4eb9-84f8-1b0d47a6f95e})
+
+
+
+
+
+ Closed
+ The name of the ParamType (ThingClass: lumiMagnetSensor, EventType: closed, ID: {6fad76ec-9d36-408d-bbf1-00e5d18bf6b0})
+----------
+The name of the StateType ({6fad76ec-9d36-408d-bbf1-00e5d18bf6b0}) of ThingClass lumiMagnetSensor
+
+
+
+
+ Closed changed
+ The name of the EventType ({6fad76ec-9d36-408d-bbf1-00e5d18bf6b0}) of ThingClass lumiMagnetSensor
+
+
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: lumiRemote, EventType: connected, ID: {d1afa3ba-f3bd-47e5-90fa-9d80dcce91d8})
+----------
+The name of the StateType ({d1afa3ba-f3bd-47e5-90fa-9d80dcce91d8}) of ThingClass lumiRemote
+----------
+The name of the ParamType (ThingClass: lumiRelay, EventType: connected, ID: {b12d1765-0b2e-4e95-b5ef-780d27fdda44})
+----------
+The name of the StateType ({b12d1765-0b2e-4e95-b5ef-780d27fdda44}) of ThingClass lumiRelay
+----------
+The name of the ParamType (ThingClass: lumiPowerSocket, EventType: connected, ID: {58e01e24-fa6e-4518-ad05-f3b118901f69})
+----------
+The name of the StateType ({58e01e24-fa6e-4518-ad05-f3b118901f69}) of ThingClass lumiPowerSocket
+
+
+
+
+
+
+ Connected changed
+ The name of the EventType ({d1afa3ba-f3bd-47e5-90fa-9d80dcce91d8}) of ThingClass lumiRemote
+----------
+The name of the EventType ({b12d1765-0b2e-4e95-b5ef-780d27fdda44}) of ThingClass lumiRelay
+----------
+The name of the EventType ({58e01e24-fa6e-4518-ad05-f3b118901f69}) of ThingClass lumiPowerSocket
+
+
+
+
+
+
+
+ Humidity
+ The name of the ParamType (ThingClass: lumiWeatherSensor, EventType: humidity, ID: {27a1e85a-f654-48d8-905d-05b3e2bc499e})
+----------
+The name of the StateType ({27a1e85a-f654-48d8-905d-05b3e2bc499e}) of ThingClass lumiWeatherSensor
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, EventType: humidity, ID: {e6559d9e-2c64-485d-bb2b-8a9aec0ac656})
+----------
+The name of the StateType ({e6559d9e-2c64-485d-bb2b-8a9aec0ac656}) of ThingClass lumiHTSensor
+
+
+
+
+
+ Humidity changed
+ The name of the EventType ({27a1e85a-f654-48d8-905d-05b3e2bc499e}) of ThingClass lumiWeatherSensor
+----------
+The name of the EventType ({e6559d9e-2c64-485d-bb2b-8a9aec0ac656}) of ThingClass lumiHTSensor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IEEE adress
+ The name of the ParamType (ThingClass: lumiRemote, Type: thing, ID: {cce8b39c-ac8a-4a8f-b66e-b6100a209fb8})
+----------
+The name of the ParamType (ThingClass: lumiRelay, Type: thing, ID: {f4a51b83-9cfa-402f-9d2a-e42933e33660})
+----------
+The name of the ParamType (ThingClass: lumiPowerSocket, Type: thing, ID: {0ebea7b9-488d-490e-9d70-e04ec92efd67})
+----------
+The name of the ParamType (ThingClass: lumiVibrationSensor, Type: thing, ID: {324cd9a0-3381-490b-9537-88b65e0093bf})
+----------
+The name of the ParamType (ThingClass: lumiWaterSensor, Type: thing, ID: {e12453e7-5fc3-4549-b4fc-8b85f78607c6})
+----------
+The name of the ParamType (ThingClass: xiaomiMotionSensor, Type: thing, ID: {e3a52900-320e-4ada-957a-6a10594f08c8})
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, Type: thing, ID: {3a44ed47-5a70-4052-9a14-78f9033eab85})
+----------
+The name of the ParamType (ThingClass: lumiButtonSensor, Type: thing, ID: {929eb2be-6d8f-46b7-8cc9-896e7e2c494a})
+----------
+The name of the ParamType (ThingClass: lumiMagnetSensor, Type: thing, ID: {36d8a40a-7f37-4d59-a0d9-6d4977ea63f3})
+----------
+The name of the ParamType (ThingClass: lumiWeatherSensor, Type: thing, ID: {ef1129e2-bdf8-423e-9c11-fa8b406f960b})
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, Type: thing, ID: {bd0b2bf2-2ec3-497f-9679-a63850101257})
+
+
+
+
+ Identify
+ The name of the ActionType ({67052e4d-350a-406f-91a9-3ce3f8eaaa7f}) of ThingClass lumiPowerSocket
+
+
+
+
+
+
+
+ Last seen time
+ The name of the ParamType (ThingClass: xiaomiMotionSensor, EventType: lastSeenTime, ID: {55229e22-e42b-4d38-af4b-fb668439d38a})
+----------
+The name of the StateType ({55229e22-e42b-4d38-af4b-fb668439d38a}) of ThingClass xiaomiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, EventType: lastSeenTime, ID: {021ce190-a775-40e0-b19b-c6307c73e343})
+----------
+The name of the StateType ({021ce190-a775-40e0-b19b-c6307c73e343}) of ThingClass lumiMotionSensor
+
+
+
+
+
+ Last seen time changed
+ The name of the EventType ({55229e22-e42b-4d38-af4b-fb668439d38a}) of ThingClass xiaomiMotionSensor
+----------
+The name of the EventType ({021ce190-a775-40e0-b19b-c6307c73e343}) of ThingClass lumiMotionSensor
+
+
+
+
+
+ Light intensity
+ The name of the ParamType (ThingClass: lumiMotionSensor, EventType: lightIntensity, ID: {eb3fb43e-a9bb-4498-880b-464cbb752ff6})
+----------
+The name of the StateType ({eb3fb43e-a9bb-4498-880b-464cbb752ff6}) of ThingClass lumiMotionSensor
+
+
+
+
+ Light intensity changed
+ The name of the EventType ({eb3fb43e-a9bb-4498-880b-464cbb752ff6}) of ThingClass lumiMotionSensor
+
+
+
+
+ Long pressed
+ The name of the EventType ({6e9dda9f-e51b-48c4-9839-01aa33085e2c}) of ThingClass lumiButtonSensor
+
+
+
+
+ Lumi
+ The name of the vendor ({f58dac98-0ba1-408f-946a-25d452080baa})
+
+
+
+
+ Magnet sensor
+ The name of the ThingClass ({b3e30f37-7467-4c66-8694-9fe624aebd10})
+
+
+
+
+
+ Motion sensor
+ The name of the ThingClass ({73bfe746-4fc7-4a2a-a3c2-32dc44214170})
+----------
+The name of the ThingClass ({b5530192-0891-4934-88a2-7338b069be24})
+
+
+
+
+
+
+ Power
+ The name of the ParamType (ThingClass: lumiPowerSocket, ActionType: power, ID: {4c81aa6b-3a8a-4d2c-be87-29486ca242af})
+----------
+The name of the ParamType (ThingClass: lumiPowerSocket, EventType: power, ID: {4c81aa6b-3a8a-4d2c-be87-29486ca242af})
+----------
+The name of the StateType ({4c81aa6b-3a8a-4d2c-be87-29486ca242af}) of ThingClass lumiPowerSocket
+
+
+
+
+ Power changed
+ The name of the EventType ({4c81aa6b-3a8a-4d2c-be87-29486ca242af}) of ThingClass lumiPowerSocket
+
+
+
+
+ Power socket
+ The name of the ThingClass ({76afde12-e781-4723-94ca-5704e800e821})
+
+
+
+
+
+
+
+ Present
+ The name of the ParamType (ThingClass: xiaomiMotionSensor, EventType: isPresent, ID: {9a30ff91-64cb-432c-8db7-cf6a751e8601})
+----------
+The name of the StateType ({9a30ff91-64cb-432c-8db7-cf6a751e8601}) of ThingClass xiaomiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, EventType: isPresent, ID: {fed7582b-c057-42ab-b7c0-d562a2e7917c})
+----------
+The name of the StateType ({fed7582b-c057-42ab-b7c0-d562a2e7917c}) of ThingClass lumiMotionSensor
+
+
+
+
+
+ Present changed
+ The name of the EventType ({9a30ff91-64cb-432c-8db7-cf6a751e8601}) of ThingClass xiaomiMotionSensor
+----------
+The name of the EventType ({fed7582b-c057-42ab-b7c0-d562a2e7917c}) of ThingClass lumiMotionSensor
+
+
+
+
+ Pressed
+ The name of the EventType ({2515e40a-8146-4f11-8d79-2be9e20224e5}) of ThingClass lumiButtonSensor
+
+
+
+
+
+ Pressure
+ The name of the ParamType (ThingClass: lumiWeatherSensor, EventType: pressure, ID: {7c3861f3-a9db-407e-9459-90a511d7f797})
+----------
+The name of the StateType ({7c3861f3-a9db-407e-9459-90a511d7f797}) of ThingClass lumiWeatherSensor
+
+
+
+
+ Pressure changed
+ The name of the EventType ({7c3861f3-a9db-407e-9459-90a511d7f797}) of ThingClass lumiWeatherSensor
+
+
+
+
+ Relay
+ The name of the ThingClass ({8e381cb1-2caa-4261-be77-e76a538bf67a})
+
+
+
+
+
+
+ Relay 1
+ The name of the ParamType (ThingClass: lumiRelay, ActionType: relay1, ID: {486e0df0-cbda-4b67-b844-6f70e5af6891})
+----------
+The name of the ParamType (ThingClass: lumiRelay, EventType: relay1, ID: {486e0df0-cbda-4b67-b844-6f70e5af6891})
+----------
+The name of the StateType ({486e0df0-cbda-4b67-b844-6f70e5af6891}) of ThingClass lumiRelay
+
+
+
+
+ Relay 1 changed
+ The name of the EventType ({486e0df0-cbda-4b67-b844-6f70e5af6891}) of ThingClass lumiRelay
+
+
+
+
+
+
+ Relay 2
+ The name of the ParamType (ThingClass: lumiRelay, ActionType: relay2, ID: {2caa4f0f-661e-4b59-83e5-8417058e48fd})
+----------
+The name of the ParamType (ThingClass: lumiRelay, EventType: relay2, ID: {2caa4f0f-661e-4b59-83e5-8417058e48fd})
+----------
+The name of the StateType ({2caa4f0f-661e-4b59-83e5-8417058e48fd}) of ThingClass lumiRelay
+
+
+
+
+ Relay 2 changed
+ The name of the EventType ({2caa4f0f-661e-4b59-83e5-8417058e48fd}) of ThingClass lumiRelay
+
+
+
+
+ Set power
+ The name of the ActionType ({4c81aa6b-3a8a-4d2c-be87-29486ca242af}) of ThingClass lumiPowerSocket
+
+
+
+
+ Set relay 1
+ The name of the ActionType ({486e0df0-cbda-4b67-b844-6f70e5af6891}) of ThingClass lumiRelay
+
+
+
+
+ Set relay 2
+ The name of the ActionType ({2caa4f0f-661e-4b59-83e5-8417058e48fd}) of ThingClass lumiRelay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Signal strength
+ The name of the ParamType (ThingClass: lumiRemote, EventType: signalStrength, ID: {a764dbfb-7b86-439b-94ea-aec742dcb13e})
+----------
+The name of the StateType ({a764dbfb-7b86-439b-94ea-aec742dcb13e}) of ThingClass lumiRemote
+----------
+The name of the ParamType (ThingClass: lumiRelay, EventType: signalStrength, ID: {bc22b220-003f-4f19-9aef-21c4049c5a0e})
+----------
+The name of the StateType ({bc22b220-003f-4f19-9aef-21c4049c5a0e}) of ThingClass lumiRelay
+----------
+The name of the ParamType (ThingClass: lumiPowerSocket, EventType: signalStrength, ID: {81926f63-4dc8-4701-a62d-07b20a849b37})
+----------
+The name of the StateType ({81926f63-4dc8-4701-a62d-07b20a849b37}) of ThingClass lumiPowerSocket
+----------
+The name of the ParamType (ThingClass: lumiVibrationSensor, EventType: signalStrength, ID: {eb8bbd63-1f1c-4499-8385-06fbe8e301db})
+----------
+The name of the StateType ({eb8bbd63-1f1c-4499-8385-06fbe8e301db}) of ThingClass lumiVibrationSensor
+----------
+The name of the ParamType (ThingClass: lumiWaterSensor, EventType: signalStrength, ID: {b3bba018-b9d0-47a8-a2cf-d8d2d6fcecce})
+----------
+The name of the StateType ({b3bba018-b9d0-47a8-a2cf-d8d2d6fcecce}) of ThingClass lumiWaterSensor
+----------
+The name of the ParamType (ThingClass: xiaomiMotionSensor, EventType: signalStrength, ID: {84a2061d-da9c-4755-87b2-770dc92a2a53})
+----------
+The name of the StateType ({84a2061d-da9c-4755-87b2-770dc92a2a53}) of ThingClass xiaomiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, EventType: signalStrength, ID: {c5913170-e377-484b-a330-c5d5b590fee2})
+----------
+The name of the StateType ({c5913170-e377-484b-a330-c5d5b590fee2}) of ThingClass lumiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiButtonSensor, EventType: signalStrength, ID: {6e9de8ee-cb28-4f7d-a0f6-fe9e64df0463})
+----------
+The name of the StateType ({6e9de8ee-cb28-4f7d-a0f6-fe9e64df0463}) of ThingClass lumiButtonSensor
+----------
+The name of the ParamType (ThingClass: lumiMagnetSensor, EventType: signalStrength, ID: {7fef2f25-91ac-4818-bc7a-e427bdeff438})
+----------
+The name of the StateType ({7fef2f25-91ac-4818-bc7a-e427bdeff438}) of ThingClass lumiMagnetSensor
+----------
+The name of the ParamType (ThingClass: lumiWeatherSensor, EventType: signalStrength, ID: {684f642e-08ed-4912-b7a9-597baef400c0})
+----------
+The name of the StateType ({684f642e-08ed-4912-b7a9-597baef400c0}) of ThingClass lumiWeatherSensor
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, EventType: signalStrength, ID: {0cb72e84-01c4-4041-9aee-1dabf26a062e})
+----------
+The name of the StateType ({0cb72e84-01c4-4041-9aee-1dabf26a062e}) of ThingClass lumiHTSensor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Signal strength changed
+ The name of the EventType ({a764dbfb-7b86-439b-94ea-aec742dcb13e}) of ThingClass lumiRemote
+----------
+The name of the EventType ({bc22b220-003f-4f19-9aef-21c4049c5a0e}) of ThingClass lumiRelay
+----------
+The name of the EventType ({81926f63-4dc8-4701-a62d-07b20a849b37}) of ThingClass lumiPowerSocket
+----------
+The name of the EventType ({eb8bbd63-1f1c-4499-8385-06fbe8e301db}) of ThingClass lumiVibrationSensor
+----------
+The name of the EventType ({b3bba018-b9d0-47a8-a2cf-d8d2d6fcecce}) of ThingClass lumiWaterSensor
+----------
+The name of the EventType ({84a2061d-da9c-4755-87b2-770dc92a2a53}) of ThingClass xiaomiMotionSensor
+----------
+The name of the EventType ({c5913170-e377-484b-a330-c5d5b590fee2}) of ThingClass lumiMotionSensor
+----------
+The name of the EventType ({6e9de8ee-cb28-4f7d-a0f6-fe9e64df0463}) of ThingClass lumiButtonSensor
+----------
+The name of the EventType ({7fef2f25-91ac-4818-bc7a-e427bdeff438}) of ThingClass lumiMagnetSensor
+----------
+The name of the EventType ({684f642e-08ed-4912-b7a9-597baef400c0}) of ThingClass lumiWeatherSensor
+----------
+The name of the EventType ({0cb72e84-01c4-4041-9aee-1dabf26a062e}) of ThingClass lumiHTSensor
+
+
+
+
+ Switch
+ The name of the ThingClass ({f8aa2ba1-ce74-4691-b97d-85427f4cf8bd})
+
+
+
+
+
+
+
+ Temperature
+ The name of the ParamType (ThingClass: lumiWeatherSensor, EventType: temperature, ID: {b1641cec-3bf6-4654-b9c0-b81acb3b4481})
+----------
+The name of the StateType ({b1641cec-3bf6-4654-b9c0-b81acb3b4481}) of ThingClass lumiWeatherSensor
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, EventType: temperature, ID: {152b475e-6bd8-4e6f-8765-0636c0bc8e9d})
+----------
+The name of the StateType ({152b475e-6bd8-4e6f-8765-0636c0bc8e9d}) of ThingClass lumiHTSensor
+
+
+
+
+
+ Temperature changed
+ The name of the EventType ({b1641cec-3bf6-4654-b9c0-b81acb3b4481}) of ThingClass lumiWeatherSensor
+----------
+The name of the EventType ({152b475e-6bd8-4e6f-8765-0636c0bc8e9d}) of ThingClass lumiHTSensor
+
+
+
+
+ Temperature humidity sensor
+ The name of the ThingClass ({dfabab0e-d483-43f8-82c6-720899e70c86})
+
+
+
+
+
+ Time period
+ The name of the ParamType (ThingClass: xiaomiMotionSensor, Type: settings, ID: {96183481-42f2-40fc-8a19-5b59bd311720})
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, Type: settings, ID: {4aeb84fa-e3ef-495e-8754-a5f6c3aa934a})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version
+ The name of the ParamType (ThingClass: lumiRemote, EventType: version, ID: {8164c1d0-657e-4347-99fb-f3fdc07af6de})
+----------
+The name of the StateType ({8164c1d0-657e-4347-99fb-f3fdc07af6de}) of ThingClass lumiRemote
+----------
+The name of the ParamType (ThingClass: lumiRelay, EventType: version, ID: {ca1a8d08-4423-4401-bca7-d52b11e54ed9})
+----------
+The name of the StateType ({ca1a8d08-4423-4401-bca7-d52b11e54ed9}) of ThingClass lumiRelay
+----------
+The name of the ParamType (ThingClass: lumiPowerSocket, EventType: version, ID: {e4a8e90c-4e38-4b3b-b5e6-fd336e3fa2a8})
+----------
+The name of the StateType ({e4a8e90c-4e38-4b3b-b5e6-fd336e3fa2a8}) of ThingClass lumiPowerSocket
+----------
+The name of the ParamType (ThingClass: lumiVibrationSensor, EventType: version, ID: {8a4aceef-c8ee-487a-946c-20f26fc2d5ed})
+----------
+The name of the StateType ({8a4aceef-c8ee-487a-946c-20f26fc2d5ed}) of ThingClass lumiVibrationSensor
+----------
+The name of the ParamType (ThingClass: lumiWaterSensor, EventType: version, ID: {bae1014f-67f5-4dcc-b36f-e15eb43afd6e})
+----------
+The name of the StateType ({bae1014f-67f5-4dcc-b36f-e15eb43afd6e}) of ThingClass lumiWaterSensor
+----------
+The name of the ParamType (ThingClass: xiaomiMotionSensor, EventType: version, ID: {ad45268e-54f0-44fe-9a8f-25ccdca92452})
+----------
+The name of the StateType ({ad45268e-54f0-44fe-9a8f-25ccdca92452}) of ThingClass xiaomiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, EventType: version, ID: {d1337b3b-0dfb-4505-90f1-e5b2b45be9bd})
+----------
+The name of the StateType ({d1337b3b-0dfb-4505-90f1-e5b2b45be9bd}) of ThingClass lumiMotionSensor
+----------
+The name of the ParamType (ThingClass: lumiButtonSensor, EventType: version, ID: {11d03ab4-7450-4caf-81ec-0b2b73e56b08})
+----------
+The name of the StateType ({11d03ab4-7450-4caf-81ec-0b2b73e56b08}) of ThingClass lumiButtonSensor
+----------
+The name of the ParamType (ThingClass: lumiMagnetSensor, EventType: version, ID: {ac0e4cd1-553b-4e43-b08a-6dd8d617e765})
+----------
+The name of the StateType ({ac0e4cd1-553b-4e43-b08a-6dd8d617e765}) of ThingClass lumiMagnetSensor
+----------
+The name of the ParamType (ThingClass: lumiWeatherSensor, EventType: version, ID: {cc644362-19ca-489d-8122-4d1bc55f100f})
+----------
+The name of the StateType ({cc644362-19ca-489d-8122-4d1bc55f100f}) of ThingClass lumiWeatherSensor
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, EventType: version, ID: {968ff8bb-b852-48d8-bde2-28cfa810b20e})
+----------
+The name of the StateType ({968ff8bb-b852-48d8-bde2-28cfa810b20e}) of ThingClass lumiHTSensor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version changed
+ The name of the EventType ({8164c1d0-657e-4347-99fb-f3fdc07af6de}) of ThingClass lumiRemote
+----------
+The name of the EventType ({ca1a8d08-4423-4401-bca7-d52b11e54ed9}) of ThingClass lumiRelay
+----------
+The name of the EventType ({e4a8e90c-4e38-4b3b-b5e6-fd336e3fa2a8}) of ThingClass lumiPowerSocket
+----------
+The name of the EventType ({8a4aceef-c8ee-487a-946c-20f26fc2d5ed}) of ThingClass lumiVibrationSensor
+----------
+The name of the EventType ({bae1014f-67f5-4dcc-b36f-e15eb43afd6e}) of ThingClass lumiWaterSensor
+----------
+The name of the EventType ({ad45268e-54f0-44fe-9a8f-25ccdca92452}) of ThingClass xiaomiMotionSensor
+----------
+The name of the EventType ({d1337b3b-0dfb-4505-90f1-e5b2b45be9bd}) of ThingClass lumiMotionSensor
+----------
+The name of the EventType ({11d03ab4-7450-4caf-81ec-0b2b73e56b08}) of ThingClass lumiButtonSensor
+----------
+The name of the EventType ({ac0e4cd1-553b-4e43-b08a-6dd8d617e765}) of ThingClass lumiMagnetSensor
+----------
+The name of the EventType ({cc644362-19ca-489d-8122-4d1bc55f100f}) of ThingClass lumiWeatherSensor
+----------
+The name of the EventType ({968ff8bb-b852-48d8-bde2-28cfa810b20e}) of ThingClass lumiHTSensor
+
+
+
+
+ Vibration detected
+ The name of the EventType ({ce98b90b-f4fb-431d-a7c9-534fd2b76ba4}) of ThingClass lumiVibrationSensor
+
+
+
+
+ Vibration sensor
+ The name of the ThingClass ({775be45e-08d8-4ae8-bc95-5721a5317856})
+
+
+
+
+
+ Water detected
+ The name of the ParamType (ThingClass: lumiWaterSensor, EventType: waterDetected, ID: {e43532a7-cbee-446e-9e4a-aff88026ad1a})
+----------
+The name of the StateType ({e43532a7-cbee-446e-9e4a-aff88026ad1a}) of ThingClass lumiWaterSensor
+
+
+
+
+ Water detected changed
+ The name of the EventType ({e43532a7-cbee-446e-9e4a-aff88026ad1a}) of ThingClass lumiWaterSensor
+
+
+
+
+ Water sensor
+ The name of the ThingClass ({e8e18bbe-27bb-4fd7-98fc-f5cef3b5f213})
+
+
+
+
+ Weather sensor
+ The name of the ThingClass ({0b582616-0b05-4ac9-8b59-51b66079b571})
+
+
+
+
+ Zigbee Lumi
+ The name of the plugin ZigbeeLumi ({4a6e97da-0e4b-4731-8bfe-c93d806a9383})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zigbee network UUID
+ The name of the ParamType (ThingClass: lumiRemote, Type: thing, ID: {8eec3cae-8f3b-4c32-b26a-f7c49844841e})
+----------
+The name of the ParamType (ThingClass: lumiRelay, Type: thing, ID: {b5783bb8-ae1d-4e93-84f9-3f1e8c59b955})
+----------
+The name of the ParamType (ThingClass: lumiPowerSocket, Type: thing, ID: {f92b9d8b-6dbd-45dd-91f7-cd5f4d496877})
+----------
+The name of the ParamType (ThingClass: lumiVibrationSensor, Type: thing, ID: {4e758a2e-32dc-44d8-872f-f656f30aca82})
+----------
+The name of the ParamType (ThingClass: lumiWaterSensor, Type: thing, ID: {4c67bab4-da84-407a-8d8d-84a5c373d585})
+----------
+The name of the ParamType (ThingClass: xiaomiMotionSensor, Type: thing, ID: {c1e7a76f-81ea-42f6-846b-86ef40087ba0})
+----------
+The name of the ParamType (ThingClass: lumiMotionSensor, Type: thing, ID: {bc0c78f6-ed88-4ae3-8e1e-2190374391af})
+----------
+The name of the ParamType (ThingClass: lumiButtonSensor, Type: thing, ID: {de153a5e-c7bd-4815-aa08-21d093b296d7})
+----------
+The name of the ParamType (ThingClass: lumiMagnetSensor, Type: thing, ID: {6a238726-ccd5-484b-ac5d-92649a604f9a})
+----------
+The name of the ParamType (ThingClass: lumiWeatherSensor, Type: thing, ID: {fe4cae8c-195c-4f38-ad88-cebd4adbb337})
+----------
+The name of the ParamType (ThingClass: lumiHTSensor, Type: thing, ID: {2daebedb-d272-4553-a410-91feff69ff7a})
+
+
+
+
diff --git a/zigbeelumi/zigbee.svg b/zigbeelumi/zigbee.svg
new file mode 100644
index 00000000..5366feb6
--- /dev/null
+++ b/zigbeelumi/zigbee.svg
@@ -0,0 +1,179 @@
+
+
+
+
diff --git a/zigbeelumi/zigbeelumi.pro b/zigbeelumi/zigbeelumi.pro
new file mode 100644
index 00000000..e4e53c79
--- /dev/null
+++ b/zigbeelumi/zigbeelumi.pro
@@ -0,0 +1,12 @@
+include(../plugins.pri)
+
+PKGCONFIG += nymea-zigbee
+
+SOURCES += \
+ integrationpluginzigbeelumi.cpp \
+
+HEADERS += \
+ integrationpluginzigbeelumi.h \
+
+
+
diff --git a/zigbeephilipshue/README.md b/zigbeephilipshue/README.md
new file mode 100644
index 00000000..bf1e7994
--- /dev/null
+++ b/zigbeephilipshue/README.md
@@ -0,0 +1,55 @@
+# ZigBee Philips Hue
+
+This plugin allows to interact with Philips Hue ZigBee devices using a native ZigBee network controller in nymea.
+
+## Supported Things
+
+In order to bring a ZigBee device into the nymea ZigBee network, the network needs to be opened for joining before you perform the device pairing instructions. The joining process can take up to 30 seconds. If the device does not show up, please restart the pairing process.
+
+
+### Hue motion sensor
+
+The [Hue motion sensor](https://www.philips-hue.com/en-us/p/hue-motion-sensor/046677473389) is fully supported. The time period for the present state can be specied in the thing setting.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press the setup button for 5 seconds until the LED start blinking in different colors.
+
+
+### Hue outdoor sensor
+The [Hue outdoor sensor](https://www.philips-hue.com/en-us/p/hue-outdoor-sensor/046677541736) is fully supported. The time period for the present state can be specied in the thing setting.
+
+
+### Hue dimmer switch
+
+The [Hue dimmer switch](https://www.philips-hue.com/en-us/p/hue-dimmer-switch/046677473372) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press the setup button for 5 seconds until the LED start blinking in different colors.
+
+### Hue lights
+
+Most of the lights and lamps from Philips Hue should be handled in a generic way by the `nymea-plugin-zigbee-generic-lights` plugin. There are 2 methods for bringing a Hue light / bulb into the nymea ZigBee network.
+
+1. Use a [Hue dimmer switch](https://www.philips-hue.com/en-us/p/hue-dimmer-switch/046677473372) (most reliable):
+
+ * Open the ZigBee network for for allowing new devices to join the network.
+ * Switch the lamp off and on again (take the power).
+ * Take the Hue dimmer switch and press the `On` and the `Off` key at the same time
+ * Hold the switch close to the lamp while keep pressing both buttons (less than 5 cm).
+ * Keep holding an pressing the buttons until the lamp starts blinking multiple times.
+ * Once you release the buttons on the switch, the lamp should be resetted and start joining the nymea ZigBee network.
+
+2. Remove the lamp from an existing Hue Bridge setup.
+
+ * Open the ZigBee network in nymea for allowing new devices to join the network.
+ * Remove the lamp from the Hue Bridge using the official Hue App or nymea.
+ * The lamp should now be resetted and start joining the nymea ZigBee network.
+
+
+## Requirements
+
+* A compatible ZigBee controller and a running ZigBee network in nymea. You can find more information about supported controllers and ZigBee network configurations [here](https://nymea.io/documentation/users/usage/configuration#zigbee).
+* *Optional*: [Hue dimmer switch](https://www.philips-hue.com/en-us/p/hue-dimmer-switch/046677473372) for resetting lights and bringing them into the nymea ZigBee network.
+
+
+## More
+
+ [Philips hue](http://www2.meethue.com/)
diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp
new file mode 100644
index 00000000..5b608c4e
--- /dev/null
+++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp
@@ -0,0 +1,622 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginzigbeephilipshue.h"
+#include "plugininfo.h"
+#include "hardware/zigbee/zigbeehardwareresource.h"
+
+#include
+
+IntegrationPluginZigbeePhilipsHue::IntegrationPluginZigbeePhilipsHue()
+{
+ m_ieeeAddressParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId;
+
+ m_networkUuidParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId;
+
+ m_connectedStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchConnectedStateTypeId;
+ m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId;
+
+ m_signalStrengthStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId;
+
+ m_versionStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchVersionStateTypeId;
+ m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId;
+}
+
+QString IntegrationPluginZigbeePhilipsHue::name() const
+{
+ return "Philips Hue";
+}
+
+bool IntegrationPluginZigbeePhilipsHue::handleNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ // Make sure this is from Philips 0x100b
+ if (node->nodeDescriptor().manufacturerCode != Zigbee::Philips)
+ return false;
+
+ bool handled = false;
+
+ if (node->endpoints().count() == 2 && node->hasEndpoint(0x01) && node->hasEndpoint(0x02)) {
+ ZigbeeNodeEndpoint *endpointOne = node->getEndpoint(0x01);
+ ZigbeeNodeEndpoint *endpoinTwo = node->getEndpoint(0x02);
+
+ // Dimmer switch
+ if (endpointOne->profile() == Zigbee::ZigbeeProfileLightLink &&
+ endpointOne->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController &&
+ endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation &&
+ endpoinTwo->deviceId() == Zigbee::HomeAutomationDeviceSimpleSensor) {
+
+ qCDebug(dcZigbeePhilipsHue()) << "Handeling Hue dimmer switch" << node << endpointOne << endpoinTwo;
+ createThing(dimmerSwitchThingClassId, networkUuid, node);
+ initDimmerSwitch(node);
+ return true;
+ }
+
+ // Outdoor sensor
+ if (endpointOne->profile() == Zigbee::ZigbeeProfileLightLink &&
+ endpointOne->deviceId() == Zigbee::LightLinkDeviceOnOffSensor &&
+ endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation &&
+ endpoinTwo->deviceId() == Zigbee::HomeAutomationDeviceOccupacySensor) {
+
+ qCDebug(dcZigbeePhilipsHue()) << "Handeling Hue outdoor sensor" << node << endpointOne << endpoinTwo;
+ createThing(motionSensorThingClassId, networkUuid, node);
+ initMotionSensor(node);
+ return true;
+ }
+ }
+
+ return handled;
+}
+
+void IntegrationPluginZigbeePhilipsHue::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ Q_UNUSED(networkUuid)
+
+ if (m_thingNodes.values().contains(node)) {
+ Thing *thing = m_thingNodes.key(node);
+ qCDebug(dcZigbeePhilipsHue()) << node << "for" << thing << "has left the network.";
+ m_thingNodes.remove(thing);
+ emit autoThingDisappeared(thing->id());
+ }
+}
+
+void IntegrationPluginZigbeePhilipsHue::init()
+{
+ hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeVendor);
+}
+
+void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_ieeeAddressParamTypeIds.value(thing->thingClassId())).toString());
+ ZigbeeNode *node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress);
+ if (!node) {
+ qCWarning(dcZigbeePhilipsHue()) << "Zigbee node for" << info->thing()->name() << "not found.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+ m_thingNodes.insert(thing, node);
+
+ // Update connected state
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), node->reachable());
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), reachable);
+ });
+
+ // Update signal strength
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), qRound(node->lqi() * 100.0 / 255.0));
+ connect(node, &ZigbeeNode::lqiChanged, thing, [this, thing](quint8 lqi){
+ uint signalStrength = qRound(lqi * 100.0 / 255.0);
+ qCDebug(dcZigbeePhilipsHue()) << thing << "signal strength changed" << signalStrength << "%";
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength);
+ });
+
+ // Thing specific setup
+ if (thing->thingClassId() == dimmerSwitchThingClassId) {
+ ZigbeeNodeEndpoint *endpointZll = node->getEndpoint(0x01);
+ ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02);
+
+ // Set the version
+ thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpointZll->softwareBuildId());
+
+ // Receive on/off commands
+ ZigbeeClusterOnOff *onOffCluster = endpointZll->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Could not find on/off client cluster on" << thing << endpointZll;
+ } else {
+ connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){
+ if (command == ZigbeeClusterOnOff::CommandOn) {
+ qCDebug(dcZigbeePhilipsHue()) << thing << "pressed ON";
+ emit emitEvent(Event(dimmerSwitchPressedEventTypeId, thing->id(), ParamList() << Param(dimmerSwitchPressedEventButtonNameParamTypeId, "ON")));
+ } else {
+ qCWarning(dcZigbeePhilipsHue()) << thing << "unhandled command received" << command;
+ }
+ });
+
+ connect(onOffCluster, &ZigbeeClusterOnOff::commandOffWithEffectSent, thing, [=](ZigbeeClusterOnOff::Effect effect, quint8 effectVariant){
+ qCDebug(dcZigbeePhilipsHue()) << thing << "OFF button pressed" << effect << effectVariant;
+ emit emitEvent(Event(dimmerSwitchPressedEventTypeId, thing->id(), ParamList() << Param(dimmerSwitchPressedEventButtonNameParamTypeId, "OFF")));
+ });
+ }
+
+ // Receive level control commands
+ ZigbeeClusterLevelControl *levelCluster = endpointZll->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Could not find level client cluster on" << thing << endpointZll;
+ } else {
+ connect(levelCluster, &ZigbeeClusterLevelControl::commandStepSent, thing, [=](ZigbeeClusterLevelControl::FadeMode fadeMode, quint8 stepSize, quint16 transitionTime){
+ qCDebug(dcZigbeePhilipsHue()) << thing << "level button pressed" << fadeMode << stepSize << transitionTime;
+ switch (fadeMode) {
+ case ZigbeeClusterLevelControl::FadeModeUp:
+ qCDebug(dcZigbeePhilipsHue()) << thing << "DIM UP pressed";
+ emit emitEvent(Event(dimmerSwitchPressedEventTypeId, thing->id(), ParamList() << Param(dimmerSwitchPressedEventButtonNameParamTypeId, "DIM UP")));
+ break;
+ case ZigbeeClusterLevelControl::FadeModeDown:
+ qCDebug(dcZigbeePhilipsHue()) << thing << "DIM DOWN pressed";
+ emit emitEvent(Event(dimmerSwitchPressedEventTypeId, thing->id(), ParamList() << Param(dimmerSwitchPressedEventButtonNameParamTypeId, "DIM DOWN")));
+ break;
+ }
+ });
+ }
+
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Could not find power configuration cluster on" << thing << endpointHa;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(dimmerSwitchBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(dimmerSwitchBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeePhilipsHue()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(dimmerSwitchBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(dimmerSwitchBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+ }
+
+ if (thing->thingClassId() == motionSensorThingClassId) {
+ ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02);
+
+ // Set the version
+ thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpointHa->softwareBuildId());
+
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Could not find power configuration cluster on" << thing << endpointHa;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(motionSensorBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeePhilipsHue()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(motionSensorBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+
+ ZigbeeClusterOccupancySensing *occupancyCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing);
+ if (!occupancyCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Occupancy cluster not found on" << thing;
+ } else {
+ if (!m_presenceTimer) {
+ m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
+ }
+
+ connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){
+ if (thing->stateValue(motionSensorIsPresentStateTypeId).toBool()) {
+ int timeout = thing->setting(motionSensorSettingsTimeoutParamTypeId).toInt();
+ QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(motionSensorLastSeenTimeStateTypeId).toULongLong() * 1000);
+ if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) {
+ qCDebug(dcZigbeePhilipsHue()) << thing << "Presence timeout";
+ thing->setStateValue(motionSensorIsPresentStateTypeId, false);
+ }
+ }
+ });
+
+ connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [=](bool occupancy){
+ qCDebug(dcZigbeePhilipsHue()) << thing << "occupancy cluster changed" << occupancy;
+ // Only change the state if the it changed to true, it will be disabled by the timer
+ if (occupancy) {
+ thing->setStateValue(motionSensorIsPresentStateTypeId, true);
+ thing->setStateValue(motionSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000);
+ m_presenceTimer->start();
+ }
+ });
+ }
+
+ ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement);
+ if (!temperatureCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Could not find the temperature measurement server cluster on" << thing << endpointHa;
+ } else {
+ // Only set the state if the cluster actually has the attribute
+ if (temperatureCluster->hasAttribute(ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue)) {
+ thing->setStateValue(motionSensorTemperatureStateTypeId, temperatureCluster->temperature());
+ }
+
+ connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){
+ qCDebug(dcZigbeePhilipsHue()) << thing << "temperature changed" << temperature << "°C";
+ thing->setStateValue(motionSensorTemperatureStateTypeId, temperature);
+ });
+ }
+
+ ZigbeeClusterIlluminanceMeasurment *illuminanceCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement);
+ if (!illuminanceCluster) {
+ qCWarning(dcZigbeePhilipsHue()) << "Could not find the illuminance measurement server cluster on" << thing << endpointHa;
+ } else {
+ // Only set the state if the cluster actually has the attribute
+ if (illuminanceCluster->hasAttribute(ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue)) {
+ int convertedValue = pow(10, illuminanceCluster->illuminance() / 10000) - 1;
+ qCDebug(dcZigbeePhilipsHue()) << thing << "illuminance" << illuminanceCluster->illuminance() << "-->" << convertedValue << "lux";
+ thing->setStateValue(motionSensorLightIntensityStateTypeId, convertedValue);
+ }
+
+ connect(illuminanceCluster, &ZigbeeClusterIlluminanceMeasurment::illuminanceChanged, thing, [thing](quint16 illuminance){
+ int convertedValue = pow(10, illuminance / 10000) - 1;
+ qCDebug(dcZigbeePhilipsHue()) << thing << "illuminance changed" << illuminance << "-->" << convertedValue << "lux";
+ thing->setStateValue(motionSensorLightIntensityStateTypeId, convertedValue);
+ });
+ }
+
+
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginZigbeePhilipsHue::executeAction(ThingActionInfo *info)
+{
+ info->finish(Thing::ThingErrorUnsupportedFeature);
+}
+
+void IntegrationPluginZigbeePhilipsHue::thingRemoved(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.take(thing);
+ if (node) {
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node);
+ }
+}
+
+void IntegrationPluginZigbeePhilipsHue::createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node)
+{
+ ThingDescriptor descriptor(thingClassId);
+ QString deviceClassName = supportedThings().findById(thingClassId).displayName();
+ descriptor.setTitle(deviceClassName);
+
+ ParamList params;
+ params.append(Param(m_networkUuidParamTypeIds[thingClassId], networkUuid.toString()));
+ params.append(Param(m_ieeeAddressParamTypeIds[thingClassId], node->extendedAddress().toString()));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+}
+
+void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node)
+{
+ ZigbeeNodeEndpoint *endpointZll = node->getEndpoint(0x01);
+ ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02);
+
+ // Get the current configured bindings for this node
+ ZigbeeReply *reply = node->removeAllBindings();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to remove all bindings for initialization of" << node;
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Removed all bindings successfully from" << node;
+ }
+
+ // Read battery, bind and configure attribute reporting for battery
+ if (!endpointHa->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpointHa;
+ return;
+ }
+
+ qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to read power cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes finished successfully";
+ }
+
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute rporting for battery remaining (0.5 % changes = 1)
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
+ reportingConfig.dataType = Zigbee::Uint8;
+ reportingConfig.minReportingInterval = 300;
+ reportingConfig.maxReportingInterval = 2700;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for power configuration cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+ qCDebug(dcZigbeePhilipsHue()) << "Bind on/off cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpointZll->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind on/off cluster to coordinator finished successfully";
+ }
+
+ qCDebug(dcZigbeePhilipsHue()) << "Bind power level cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpointZll->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind level cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind level cluster to coordinator finished successfully";
+ }
+
+ qCDebug(dcZigbeePhilipsHue()) << "Read binding table from node" << node;
+ ZigbeeReply *reply = node->readBindingTableEntries();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to read binding table from" << node;
+ } else {
+ foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) {
+ qCDebug(dcZigbeePhilipsHue()) << node << binding;
+ }
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+void IntegrationPluginZigbeePhilipsHue::initMotionSensor(ZigbeeNode *node)
+{
+ ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02);
+
+ // Get the current configured bindings for this node
+ ZigbeeReply *reply = node->removeAllBindings();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to remove all bindings for initialization of" << node;
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Removed all bindings successfully from" << node;
+ }
+
+ // Read battery, bind and configure attribute reporting for battery
+ if (!endpointHa->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpointHa;
+ return;
+ }
+
+ // Init power configuration cluster
+ qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to read power cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes finished successfully";
+ }
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute rporting for battery remaining (0.5 % changes = 1)
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
+ reportingConfig.dataType = Zigbee::Uint8;
+ reportingConfig.minReportingInterval = 300;
+ reportingConfig.maxReportingInterval = 2700;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for power configuration cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+
+ // Init temperature measurment cluster
+ qCDebug(dcZigbeePhilipsHue()) << "Bind temperature cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind temperature cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind temperature cluster to coordinator finished successfully";
+ }
+
+ // Read current temperature
+ if (!endpointHa->hasInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to initialize the temperature cluster because the cluster could not be found" << node << endpointHa;
+ return;
+ }
+
+ qCDebug(dcZigbeePhilipsHue()) << "Read temperature cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)->readAttributes({ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to read temperature cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Read temperature cluster attributes finished successfully";
+ }
+
+ // Configure attribute reporting for temperature
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue;
+ reportingConfig.dataType = Zigbee::Int16;
+ reportingConfig.minReportingInterval = 300;
+ reportingConfig.maxReportingInterval = 600;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(10)).data();
+
+ qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for temperature cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to configure temperature cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for temperature cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+ // Init illuminance measurment cluster
+ qCDebug(dcZigbeePhilipsHue()) << "Bind illuminance measurement cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind illuminance measurement cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind illuminance measurement cluster to coordinator finished successfully";
+ }
+
+ // Read current illuminance
+ if (!endpointHa->hasInputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement)) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to initialize the illuminance measurement cluster because the cluster could not be found" << node << endpointHa;
+ return;
+ }
+
+ qCDebug(dcZigbeePhilipsHue()) << "Read illuminance measurement cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement)->readAttributes({ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to read illuminance measurement cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Read illuminance measurement cluster attributes finished successfully";
+ }
+
+ // Configure attribute reporting for illuminance
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue;
+ reportingConfig.dataType = Zigbee::Uint16;
+ reportingConfig.minReportingInterval = 5;
+ reportingConfig.maxReportingInterval = 600;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(100)).data();
+
+ qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for illuminance measurement cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to configure illuminance measurement cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for illuminance measurement cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+
+ // Init occupancy sensing cluster
+ qCDebug(dcZigbeePhilipsHue()) << "Bind occupancy sensing cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdOccupancySensing,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to bind occupancy sensing cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Bind occupancy sensing cluster to coordinator finished successfully";
+ }
+
+
+ qCDebug(dcZigbeePhilipsHue()) << "Read occupancy sensing cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing)->readAttributes({ZigbeeClusterOccupancySensing::AttributeOccupancy, ZigbeeClusterOccupancySensing::AttributeOccupancySensorType});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to read occupancy sensing cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Read occupancy sensing cluster attributes finished successfully";
+ }
+
+ // Configure attribute reporting for temperature
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterOccupancySensing::AttributeOccupancy;
+ reportingConfig.dataType = Zigbee::BitMap8;
+ reportingConfig.minReportingInterval = 0;
+ reportingConfig.maxReportingInterval = 300;
+
+ qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for occupancy cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeePhilipsHue()) << "Failed to configure occupancy cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for occupancy cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.h b/zigbeephilipshue/integrationpluginzigbeephilipshue.h
new file mode 100644
index 00000000..47cd5ade
--- /dev/null
+++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.h
@@ -0,0 +1,77 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINZIGBEEPHILIPSHUE_H
+#define INTEGRATIONPLUGINZIGBEEPHILIPSHUE_H
+
+#include "integrations/integrationplugin.h"
+#include "hardware/zigbee/zigbeehandler.h"
+#include "plugintimer.h"
+
+#include
+
+class IntegrationPluginZigbeePhilipsHue: public IntegrationPlugin, public ZigbeeHandler
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeephilipshue.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginZigbeePhilipsHue();
+
+ QString name() const override;
+ bool handleNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+ void handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+
+ void init() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ PluginTimer *m_presenceTimer = nullptr;
+ QHash m_thingNodes;
+
+ QHash m_ieeeAddressParamTypeIds;
+ QHash m_networkUuidParamTypeIds;
+
+ QHash m_connectedStateTypeIds;
+ QHash m_signalStrengthStateTypeIds;
+ QHash m_versionStateTypeIds;
+
+ void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node);
+
+ void initDimmerSwitch(ZigbeeNode *node);
+ void initMotionSensor(ZigbeeNode *node);
+
+};
+
+#endif // INTEGRATIONPLUGINZIGBEEPHILIPSHUE_H
diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.json b/zigbeephilipshue/integrationpluginzigbeephilipshue.json
new file mode 100644
index 00000000..d45653ed
--- /dev/null
+++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.json
@@ -0,0 +1,234 @@
+{
+ "name": "ZigbeePhilipsHue",
+ "displayName": "Zigbee Philips Hue",
+ "id": "0ca340b7-061a-42e6-ab7d-d7b4fd235d02",
+ "vendors": [
+ {
+ "id": "0ae1e001-2aa6-47ed-b8c0-334c3728a68f",
+ "name": "philips",
+ "displayName": "Philips",
+ "thingClasses": [
+ {
+ "name": "dimmerSwitch",
+ "displayName": "Hue dimmer switch",
+ "id": "b2711164-a848-4715-8ddf-33ca86f9f4cf",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "multibutton", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "b221cad1-ef2e-4192-8168-11d0205a43da",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "d2bb97ee-caed-4776-8931-9fc0a04e4e8f",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "5ac101b2-4bb7-4b5c-8493-08b1ae7ca0c1",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "5a6e325e-a6ee-4a36-b429-f5d8c8adb80b",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "12139630-668a-4ad8-96fa-781028e9eced",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "3e28e0b3-fe23-4293-8876-8384def6c4fb",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "4223a3bc-9616-4ed2-ae50-704b9df62d0e",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+ {
+ "id": "33bb5816-8479-4995-99e2-cb0443886003",
+ "name": "pressed",
+ "displayName": "Button pressed",
+ "paramTypes": [
+ {
+ "id": "c086a247-838f-49c0-b1e4-2ae1ed181b55",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["ON", "OFF", "DIM UP", "DIM DOWN"]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "motionSensor",
+ "displayName": "Hue motion sensor",
+ "id": "031c45d1-e782-4f91-9f05-259d6b81c7ef",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "presencesensor", "lightsensor", "temperaturesensor", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "a866df99-7e2a-40e1-b82e-529c03846d5e",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "99f4ff28-6d61-4e96-bfc4-c32aa05bb256",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "settingsTypes": [
+ {
+ "id": "4576f68f-7856-48f8-a3fd-2c9e7f968537",
+ "name": "timeout",
+ "displayName": "Time period",
+ "type": "uint",
+ "unit": "Seconds",
+ "defaultValue": 60,
+ "minValue": 10
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "0d0a5ee1-41f2-4b5a-a3c0-7c8cca1c064d",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "d4f85c34-896a-4bab-af71-8b80d9889815",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "18cb67e1-220f-41d7-9145-1816fc5d38d0",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "00a4450b-d2fe-4e2b-92c8-1ab3a98d1628",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "9341f65a-b0aa-4648-82eb-e8400779f817",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "66e3b840-4f4c-4f4a-a71a-69fb751f2a3b",
+ "name": "isPresent",
+ "displayName": "Present",
+ "displayNameEvent": "Present changed",
+ "type": "bool",
+ "defaultValue": false,
+ "ioType": "digitalInput"
+ },
+ {
+ "id": "128777a9-75e7-4cc6-b196-691d2ffddbc9",
+ "name": "lastSeenTime",
+ "displayName": "Last seen time",
+ "displayNameEvent": "Last seen time changed",
+ "type": "int",
+ "unit": "UnixTime",
+ "defaultValue": 0
+ },
+ {
+ "id": "8077e335-1d18-4e3f-9b5e-6f71af4a33b1",
+ "name": "lightIntensity",
+ "displayName": "Light intensity",
+ "displayNameEvent": "Light intensity changed",
+ "type": "double",
+ "unit": "Lux",
+ "defaultValue": 0
+ },
+ {
+ "id": "978f069c-57c0-4e73-a81b-c593bc2e7624",
+ "name": "temperature",
+ "displayName": "Temperature",
+ "displayNameEvent": "Temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.0
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/zigbeephilipshue/meta.json b/zigbeephilipshue/meta.json
new file mode 100644
index 00000000..36ec9a97
--- /dev/null
+++ b/zigbeephilipshue/meta.json
@@ -0,0 +1,15 @@
+{
+ "title": "ZigBee Philips Hue",
+ "tagline": "Connect and control ZigBee Philips Hue devices native.",
+ "icon": "philipshue.png",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "zigbee"
+ ],
+ "categories": [
+ "light",
+ "sensor",
+ "switch"
+ ]
+}
diff --git a/zigbeephilipshue/philipshue.png b/zigbeephilipshue/philipshue.png
new file mode 100644
index 00000000..70dd93b4
Binary files /dev/null and b/zigbeephilipshue/philipshue.png differ
diff --git a/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts b/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts
new file mode 100644
index 00000000..eb467b05
--- /dev/null
+++ b/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts
@@ -0,0 +1,247 @@
+
+
+
+
+ ZigbeePhilipsHue
+
+
+
+
+
+ Battery
+ The name of the ParamType (ThingClass: motionSensor, EventType: batteryLevel, ID: {00a4450b-d2fe-4e2b-92c8-1ab3a98d1628})
+----------
+The name of the StateType ({00a4450b-d2fe-4e2b-92c8-1ab3a98d1628}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, EventType: batteryLevel, ID: {3e28e0b3-fe23-4293-8876-8384def6c4fb})
+----------
+The name of the StateType ({3e28e0b3-fe23-4293-8876-8384def6c4fb}) of ThingClass dimmerSwitch
+
+
+
+
+
+ Battery changed
+ The name of the EventType ({00a4450b-d2fe-4e2b-92c8-1ab3a98d1628}) of ThingClass motionSensor
+----------
+The name of the EventType ({3e28e0b3-fe23-4293-8876-8384def6c4fb}) of ThingClass dimmerSwitch
+
+
+
+
+
+
+
+ Battery critical
+ The name of the ParamType (ThingClass: motionSensor, EventType: batteryCritical, ID: {9341f65a-b0aa-4648-82eb-e8400779f817})
+----------
+The name of the StateType ({9341f65a-b0aa-4648-82eb-e8400779f817}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, EventType: batteryCritical, ID: {4223a3bc-9616-4ed2-ae50-704b9df62d0e})
+----------
+The name of the StateType ({4223a3bc-9616-4ed2-ae50-704b9df62d0e}) of ThingClass dimmerSwitch
+
+
+
+
+
+ Battery critical changed
+ The name of the EventType ({9341f65a-b0aa-4648-82eb-e8400779f817}) of ThingClass motionSensor
+----------
+The name of the EventType ({4223a3bc-9616-4ed2-ae50-704b9df62d0e}) of ThingClass dimmerSwitch
+
+
+
+
+ Button name
+ The name of the ParamType (ThingClass: dimmerSwitch, EventType: pressed, ID: {c086a247-838f-49c0-b1e4-2ae1ed181b55})
+
+
+
+
+ Button pressed
+ The name of the EventType ({33bb5816-8479-4995-99e2-cb0443886003}) of ThingClass dimmerSwitch
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: motionSensor, EventType: connected, ID: {0d0a5ee1-41f2-4b5a-a3c0-7c8cca1c064d})
+----------
+The name of the StateType ({0d0a5ee1-41f2-4b5a-a3c0-7c8cca1c064d}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, EventType: connected, ID: {5ac101b2-4bb7-4b5c-8493-08b1ae7ca0c1})
+----------
+The name of the StateType ({5ac101b2-4bb7-4b5c-8493-08b1ae7ca0c1}) of ThingClass dimmerSwitch
+
+
+
+
+
+ Connected changed
+ The name of the EventType ({0d0a5ee1-41f2-4b5a-a3c0-7c8cca1c064d}) of ThingClass motionSensor
+----------
+The name of the EventType ({5ac101b2-4bb7-4b5c-8493-08b1ae7ca0c1}) of ThingClass dimmerSwitch
+
+
+
+
+ Hue dimmer switch
+ The name of the ThingClass ({b2711164-a848-4715-8ddf-33ca86f9f4cf})
+
+
+
+
+ Hue motion sensor
+ The name of the ThingClass ({031c45d1-e782-4f91-9f05-259d6b81c7ef})
+
+
+
+
+
+ IEEE adress
+ The name of the ParamType (ThingClass: motionSensor, Type: thing, ID: {a866df99-7e2a-40e1-b82e-529c03846d5e})
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, Type: thing, ID: {b221cad1-ef2e-4192-8168-11d0205a43da})
+
+
+
+
+
+ Last seen time
+ The name of the ParamType (ThingClass: motionSensor, EventType: lastSeenTime, ID: {128777a9-75e7-4cc6-b196-691d2ffddbc9})
+----------
+The name of the StateType ({128777a9-75e7-4cc6-b196-691d2ffddbc9}) of ThingClass motionSensor
+
+
+
+
+ Last seen time changed
+ The name of the EventType ({128777a9-75e7-4cc6-b196-691d2ffddbc9}) of ThingClass motionSensor
+
+
+
+
+
+ Light intensity
+ The name of the ParamType (ThingClass: motionSensor, EventType: lightIntensity, ID: {8077e335-1d18-4e3f-9b5e-6f71af4a33b1})
+----------
+The name of the StateType ({8077e335-1d18-4e3f-9b5e-6f71af4a33b1}) of ThingClass motionSensor
+
+
+
+
+ Light intensity changed
+ The name of the EventType ({8077e335-1d18-4e3f-9b5e-6f71af4a33b1}) of ThingClass motionSensor
+
+
+
+
+ Philips
+ The name of the vendor ({0ae1e001-2aa6-47ed-b8c0-334c3728a68f})
+
+
+
+
+
+ Present
+ The name of the ParamType (ThingClass: motionSensor, EventType: isPresent, ID: {66e3b840-4f4c-4f4a-a71a-69fb751f2a3b})
+----------
+The name of the StateType ({66e3b840-4f4c-4f4a-a71a-69fb751f2a3b}) of ThingClass motionSensor
+
+
+
+
+ Present changed
+ The name of the EventType ({66e3b840-4f4c-4f4a-a71a-69fb751f2a3b}) of ThingClass motionSensor
+
+
+
+
+
+
+
+ Signal strength
+ The name of the ParamType (ThingClass: motionSensor, EventType: signalStrength, ID: {d4f85c34-896a-4bab-af71-8b80d9889815})
+----------
+The name of the StateType ({d4f85c34-896a-4bab-af71-8b80d9889815}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, EventType: signalStrength, ID: {5a6e325e-a6ee-4a36-b429-f5d8c8adb80b})
+----------
+The name of the StateType ({5a6e325e-a6ee-4a36-b429-f5d8c8adb80b}) of ThingClass dimmerSwitch
+
+
+
+
+
+ Signal strength changed
+ The name of the EventType ({d4f85c34-896a-4bab-af71-8b80d9889815}) of ThingClass motionSensor
+----------
+The name of the EventType ({5a6e325e-a6ee-4a36-b429-f5d8c8adb80b}) of ThingClass dimmerSwitch
+
+
+
+
+
+ Temperature
+ The name of the ParamType (ThingClass: motionSensor, EventType: temperature, ID: {978f069c-57c0-4e73-a81b-c593bc2e7624})
+----------
+The name of the StateType ({978f069c-57c0-4e73-a81b-c593bc2e7624}) of ThingClass motionSensor
+
+
+
+
+ Temperature changed
+ The name of the EventType ({978f069c-57c0-4e73-a81b-c593bc2e7624}) of ThingClass motionSensor
+
+
+
+
+ Time period
+ The name of the ParamType (ThingClass: motionSensor, Type: settings, ID: {4576f68f-7856-48f8-a3fd-2c9e7f968537})
+
+
+
+
+
+
+
+ Version
+ The name of the ParamType (ThingClass: motionSensor, EventType: version, ID: {18cb67e1-220f-41d7-9145-1816fc5d38d0})
+----------
+The name of the StateType ({18cb67e1-220f-41d7-9145-1816fc5d38d0}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, EventType: version, ID: {12139630-668a-4ad8-96fa-781028e9eced})
+----------
+The name of the StateType ({12139630-668a-4ad8-96fa-781028e9eced}) of ThingClass dimmerSwitch
+
+
+
+
+
+ Version changed
+ The name of the EventType ({18cb67e1-220f-41d7-9145-1816fc5d38d0}) of ThingClass motionSensor
+----------
+The name of the EventType ({12139630-668a-4ad8-96fa-781028e9eced}) of ThingClass dimmerSwitch
+
+
+
+
+ Zigbee Philips Hue
+ The name of the plugin ZigbeePhilipsHue ({0ca340b7-061a-42e6-ab7d-d7b4fd235d02})
+
+
+
+
+
+ Zigbee network UUID
+ The name of the ParamType (ThingClass: motionSensor, Type: thing, ID: {99f4ff28-6d61-4e96-bfc4-c32aa05bb256})
+----------
+The name of the ParamType (ThingClass: dimmerSwitch, Type: thing, ID: {d2bb97ee-caed-4776-8931-9fc0a04e4e8f})
+
+
+
+
diff --git a/zigbeephilipshue/zigbeephilipshue.pro b/zigbeephilipshue/zigbeephilipshue.pro
new file mode 100644
index 00000000..945859b8
--- /dev/null
+++ b/zigbeephilipshue/zigbeephilipshue.pro
@@ -0,0 +1,9 @@
+include(../plugins.pri)
+
+PKGCONFIG += nymea-zigbee
+
+SOURCES += \
+ integrationpluginzigbeephilipshue.cpp
+
+HEADERS += \
+ integrationpluginzigbeephilipshue.h
diff --git a/zigbeetradfri/README.md b/zigbeetradfri/README.md
new file mode 100644
index 00000000..510ecc73
--- /dev/null
+++ b/zigbeetradfri/README.md
@@ -0,0 +1,84 @@
+# ZigBee IKEA TRÅDFRI
+
+This plugin allows to interact with IKEA TRÅDFRI ZigBee devices using a native ZigBee network controller in nymea.
+
+## Supported Things
+
+In order to bring a ZigBee device into the nymea ZigBee network, the network needs to be opened for joining before you perform the device pairing instructions. The joining process can take up to 30 seconds. If the device does not show up, please restart the pairing process.
+
+
+
+### TRÅDFRI Wireless dimmer
+
+The [Wireless dimmer](https://www.ikea.com/us/en/p/tradfri-wireless-dimmer-white-10408598/) requires the latest firmware version in order to work properly with nymea. If the thing shows up in nymea once paired, but no button events will be recongized, please update the device to the latest firmware version and re-add it to nymea.
+
+> Known working device firmware version: `2.2.008`
+
+**Pairing instructions**: Open the ZigBee network for joining. Click the connect button 4 times within 5 seconds.
+
+
+
+### TRÅDFRI Remote control
+
+The [Remote control](https://www.ikea.com/us/en/p/tradfri-remote-control-00443130/) requires the latest firmware version in order to work properly with nymea. If the thing shows up in nymea once paired, but no button events will be recongized, please update the device to the latest firmware version and re-add it to nymea.
+
+> Known working device firmware version: `2.3.014`
+
+**Pairing instructions**: Open the ZigBee network for joining. Click the connect button 4 times within 5 seconds.
+
+
+
+### TRÅDFRI Symfonisk
+
+The [Remote control](https://www.ikea.com/us/en/p/symfonisk-sound-remote-white-20370482/) is *not* supported properly yet, even if it shows up when pairing. This implementation of this device is still under construction.
+
+**Pairing instructions**: Open the ZigBee network for joining. Click the connect button 4 times within 5 seconds.
+
+
+
+### TRÅDFRI Motion sensor
+
+The [Remote control](https://www.ikea.com/us/en/p/tradfri-wireless-motion-sensor-white-60377655/) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Click the connect button 4 times within 5 seconds.
+
+
+
+### TRÅDFRI Signal repeater
+
+The [Signal repeater](https://www.ikea.com/us/en/p/tradfri-signal-repeater-30400407/) is fully supported.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press the setup button with a needle for 5 seconds.
+
+
+
+#### Control outlet
+
+The [Control outlet](https://www.ikea.com/us/en/p/tradfri-wireless-control-outlet-30356169/) should be handeld in a generic way by the `nymea-plugin-zigbee-generic` plugin.
+
+**Pairing instructions**: Open the ZigBee network for joining. Press the setup button with a needle for 5 seconds. The power socket will switch quick on and off and start the pairing process.
+
+
+
+### TRÅDFRI lights
+
+Most of the lights and lamps from IKEA TRÅDFRI should be handled in a generic way by the `nymea-plugin-zigbee-generic-lights` plugin.
+
+Tested and verified lights:
+
+* TRADFRI bulb E27 CWS opal 600lm (color light)
+* TRADFRI bulb E27 WS clear 806lm (color temperature light)
+
+
+**Pairing instructions**: Open the ZigBee network for joining. Switch the light off and on 6 times in a 1 second rythm. Once the light start flashing/dimming, the pairing process has been started successfully and the lamp will join the nymea ZigBee network.
+
+
+
+## Requirements
+
+* A compatible ZigBee controller and a running ZigBee network in nymea. You can find more information about supported controllers and ZigBee network configurations [here](https://nymea.io/documentation/users/usage/configuration#zigbee).
+
+
+## More
+
+[IKEAD](https://www.ikea.com)
diff --git a/zigbeetradfri/ikea.svg b/zigbeetradfri/ikea.svg
new file mode 100644
index 00000000..16839893
--- /dev/null
+++ b/zigbeetradfri/ikea.svg
@@ -0,0 +1,76 @@
+
+
+
+
diff --git a/zigbeetradfri/integrationpluginzigbeetradfri.cpp b/zigbeetradfri/integrationpluginzigbeetradfri.cpp
new file mode 100644
index 00000000..3ecad31b
--- /dev/null
+++ b/zigbeetradfri/integrationpluginzigbeetradfri.cpp
@@ -0,0 +1,769 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginzigbeetradfri.h"
+#include "plugininfo.h"
+#include "zigbeeutils.h"
+#include "hardware/zigbee/zigbeehardwareresource.h"
+
+
+IntegrationPluginZigbeeTradfri::IntegrationPluginZigbeeTradfri()
+{
+ m_ieeeAddressParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[remoteThingClassId] = remoteThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[soundRemoteThingClassId] = soundRemoteThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId;
+ m_ieeeAddressParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingIeeeAddressParamTypeId;
+
+ m_networkUuidParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[remoteThingClassId] = remoteThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[soundRemoteThingClassId] = soundRemoteThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId;
+ m_networkUuidParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingNetworkUuidParamTypeId;
+
+ m_endpointIdParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[remoteThingClassId] = remoteThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[soundRemoteThingClassId] = soundRemoteThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[motionSensorThingClassId] = motionSensorThingEndpointIdParamTypeId;
+ m_endpointIdParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingEndpointIdParamTypeId;
+
+ m_connectedStateTypeIds[onOffSwitchThingClassId] = onOffSwitchConnectedStateTypeId;
+ m_connectedStateTypeIds[remoteThingClassId] = remoteConnectedStateTypeId;
+ m_connectedStateTypeIds[soundRemoteThingClassId] = soundRemoteConnectedStateTypeId;
+ m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId;
+ m_connectedStateTypeIds[signalRepeaterThingClassId] = signalRepeaterConnectedStateTypeId;
+
+ m_signalStrengthStateTypeIds[onOffSwitchThingClassId] = onOffSwitchSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[remoteThingClassId] = remoteSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[soundRemoteThingClassId] = soundRemoteSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId;
+ m_signalStrengthStateTypeIds[signalRepeaterThingClassId] = signalRepeaterSignalStrengthStateTypeId;
+
+ m_versionStateTypeIds[onOffSwitchThingClassId] = onOffSwitchVersionStateTypeId;
+ m_versionStateTypeIds[remoteThingClassId] = remoteVersionStateTypeId;
+ m_versionStateTypeIds[soundRemoteThingClassId] = soundRemoteVersionStateTypeId;
+ m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId;
+ m_versionStateTypeIds[signalRepeaterThingClassId] = signalRepeaterVersionStateTypeId;
+}
+
+QString IntegrationPluginZigbeeTradfri::name() const
+{
+ return "Ikea TRADFRI";
+}
+
+bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ // Make sure this is from ikea 0x117C
+ if (node->nodeDescriptor().manufacturerCode != Zigbee::Ikea)
+ return false;
+
+ bool handled = false;
+ foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
+ if (endpoint->modelIdentifier().contains("on/off switch")) {
+ // "TRADFRI on/off switch"
+ qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI on/off switch" << node << endpoint;
+ createThing(onOffSwitchThingClassId, networkUuid, node, endpoint);
+ initOnOffSwitch(node, endpoint);
+ handled = true;
+ }
+
+ if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffSensor) {
+ qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI motion sensor" << node << endpoint;
+ createThing(motionSensorThingClassId, networkUuid, node, endpoint);
+ initMotionSensor(node, endpoint);
+ handled = true;
+ }
+
+ if (endpoint->modelIdentifier().contains("remote control")) {
+ qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint;
+ createThing(remoteThingClassId, networkUuid, node, endpoint);
+ initRemote(node, endpoint);
+ handled = true;
+ }
+
+ if (endpoint->modelIdentifier().contains("SYMFONISK")) {
+ // "SYMFONISK Sound Controller"
+ qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI Symfonisk sound remote" << node << endpoint;
+ createThing(soundRemoteThingClassId, networkUuid, node, endpoint);
+ initOnOffSwitch(node, endpoint);
+ handled = true;
+ }
+
+ if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceRangeExtender) {
+ qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI signal repeater" << node << endpoint;
+ createThing(signalRepeaterThingClassId, networkUuid, node, endpoint);
+ handled = true;
+ }
+ }
+
+ return handled;
+}
+
+void IntegrationPluginZigbeeTradfri::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ Q_UNUSED(networkUuid)
+
+ if (m_thingNodes.values().contains(node)) {
+ Thing *thing = m_thingNodes.key(node);
+ qCDebug(dcZigbeeTradfri()) << node << "for" << thing << "has left the network.";
+ m_thingNodes.remove(thing);
+ emit autoThingDisappeared(thing->id());
+ }
+}
+
+void IntegrationPluginZigbeeTradfri::init()
+{
+ hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeVendor);
+}
+
+void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_ieeeAddressParamTypeIds.value(thing->thingClassId())).toString());
+ ZigbeeNode *node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress);
+ if (!node) {
+ qCWarning(dcZigbeeTradfri()) << "Zigbee node for" << info->thing()->name() << "not found.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+ m_thingNodes.insert(thing, node);
+
+ ZigbeeNodeEndpoint *endpoint = findEndpoint(thing);
+ if (!endpoint) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find endpoint for" << thing;
+ info->finish(Thing::ThingErrorSetupFailed);
+ return;
+ }
+
+ // Update connected state
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), node->reachable());
+ connect(node, &ZigbeeNode::reachableChanged, thing, [thing, this](bool reachable){
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), reachable);
+ });
+
+ // Update signal strength
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), qRound(node->lqi() * 100.0 / 255.0));
+ connect(node, &ZigbeeNode::lqiChanged, thing, [this, thing](quint8 lqi){
+ uint signalStrength = qRound(lqi * 100.0 / 255.0);
+ qCDebug(dcZigbeeTradfri()) << thing << "signal strength changed" << signalStrength << "%";
+ thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength);
+ });
+
+ // Set the version
+ thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId());
+
+ // Thing specific setup
+ if (thing->thingClassId() == onOffSwitchThingClassId) {
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find power configuration cluster on" << thing << endpoint;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(onOffSwitchBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(onOffSwitchBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+
+ // Receive on/off commands
+ ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint;
+ } else {
+ connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed" << command;
+ if (command == ZigbeeClusterOnOff::CommandOn) {
+ qCDebug(dcZigbeeTradfri()) << thing << "pressed ON";
+ emit emitEvent(Event(onOffSwitchPressedEventTypeId, thing->id(), ParamList() << Param(onOffSwitchLongPressedEventButtonNameParamTypeId, "ON")));
+ } else if (command == ZigbeeClusterOnOff::CommandOff) {
+ qCDebug(dcZigbeeTradfri()) << thing << "pressed OFF";
+ emit emitEvent(Event(onOffSwitchPressedEventTypeId, thing->id(), ParamList() << Param(onOffSwitchLongPressedEventButtonNameParamTypeId, "OFF")));
+ }
+ });
+ }
+
+ // Receive level control commands
+ ZigbeeClusterLevelControl *levelCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find level client cluster on" << thing << endpoint;
+ } else {
+ connect(levelCluster, &ZigbeeClusterLevelControl::commandSent, thing, [=](ZigbeeClusterLevelControl::Command command, const QByteArray &payload){
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed" << command << payload.toHex();
+ switch (command) {
+ case ZigbeeClusterLevelControl::CommandMoveWithOnOff:
+ qCDebug(dcZigbeeTradfri()) << thing << "long pressed ON";
+ emit emitEvent(Event(onOffSwitchLongPressedEventTypeId, thing->id(), ParamList() << Param(onOffSwitchLongPressedEventButtonNameParamTypeId, "ON")));
+ break;
+ case ZigbeeClusterLevelControl::CommandMove:
+ qCDebug(dcZigbeeTradfri()) << thing << "long pressed OFF";
+ emit emitEvent(Event(onOffSwitchLongPressedEventTypeId, thing->id(), ParamList() << Param(onOffSwitchLongPressedEventButtonNameParamTypeId, "OFF")));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+
+ if (thing->thingClassId() == motionSensorThingClassId) {
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find power configuration cluster on" << thing << endpoint;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(motionSensorBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(motionSensorBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+
+ // Create plugintimer if required
+ if (!m_presenceTimer) {
+ m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
+ }
+ connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){
+ if (thing->stateValue(motionSensorIsPresentStateTypeId).toBool()) {
+ int timeout = thing->setting(motionSensorSettingsTimeoutParamTypeId).toInt();
+ QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(motionSensorLastSeenTimeStateTypeId).toULongLong() * 1000);
+ if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) {
+ thing->setStateValue(motionSensorIsPresentStateTypeId, false);
+ }
+ }
+ });
+
+ // Receive on/off commands
+ ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint;
+ } else {
+ connect(onOffCluster, &ZigbeeClusterOnOff::commandOnWithTimedOffSent, thing, [=](bool acceptOnlyWhenOn, quint16 onTime, quint16 offTime){
+ qCDebug(dcZigbeeTradfri()) << thing << "command received: Accept only when on:" << acceptOnlyWhenOn << "On time:" << onTime / 10 << "s" << "Off time:" << offTime / 10 << "s";
+ thing->setStateValue(motionSensorLastSeenTimeStateTypeId, QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000);
+ thing->setStateValue(motionSensorIsPresentStateTypeId, true);
+ m_presenceTimer->start();
+ });
+ }
+ }
+
+ if (thing->thingClassId() == remoteThingClassId) {
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find power configuration cluster on" << thing << endpoint;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(remoteBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(remoteBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(remoteBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(remoteBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+
+ // Receive on/off commands
+ ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint;
+ } else {
+ connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){
+ qCDebug(dcZigbeeTradfri()) << thing << "power command received" << command;
+ if (command == ZigbeeClusterOnOff::CommandToggle) {
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Power";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power")));
+ }
+ });
+ }
+
+ // Receive level control commands
+ ZigbeeClusterLevelControl *levelCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find level client cluster on" << thing << endpoint;
+ } else {
+ connect(levelCluster, &ZigbeeClusterLevelControl::commandSent, thing, [=](ZigbeeClusterLevelControl::Command command, const QByteArray &payload){
+ qCDebug(dcZigbeeTradfri()) << thing << "level command received" << command << payload.toHex();
+ switch (command) {
+ case ZigbeeClusterLevelControl::CommandMoveWithOnOff:
+ case ZigbeeClusterLevelControl::CommandStepWithOnOff:
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up")));
+ break;
+ default:
+ break;
+ }
+ });
+
+ connect(levelCluster, &ZigbeeClusterLevelControl::commandMoveSent, thing, [=](ZigbeeClusterLevelControl::MoveMode moveMode, quint8 rate){
+ qCDebug(dcZigbeeTradfri()) << "level command move received" << moveMode << rate;
+ switch (moveMode) {
+ case ZigbeeClusterLevelControl::MoveModeUp:
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up")));
+ break;
+ case ZigbeeClusterLevelControl::MoveModeDown:
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Down";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Down")));
+ break;
+ }
+ });
+
+ connect(levelCluster, &ZigbeeClusterLevelControl::commandStepSent, thing, [=](ZigbeeClusterLevelControl::FadeMode fadeMode, quint8 stepSize, quint16 transitionTime){
+ qCDebug(dcZigbeeTradfri()) << "level command step received" << fadeMode << stepSize << transitionTime;
+ switch (fadeMode) {
+ case ZigbeeClusterLevelControl::FadeModeUp:
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up")));
+ break;
+ case ZigbeeClusterLevelControl::FadeModeDown:
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Down";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Down")));
+ break;
+ }
+ });
+ }
+
+ // Receive scene commands
+ ZigbeeClusterScenes *scenesCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdScenes);
+ if (!scenesCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find scenes client cluster on" << thing << endpoint;
+ } else {
+ connect(scenesCluster, &ZigbeeClusterScenes::commandSent, thing, [=](quint8 command, const QByteArray &payload){
+ qCDebug(dcZigbeeTradfri()) << thing << "scene command received" << command << payload.toHex();
+ if (payload.count() <= 0)
+ return;
+
+ switch (command) {
+ // Note: these comands are not in the specs
+ case 0x07:
+ if (payload.at(0) == 0x00) {
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Right";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Right")));
+ } else if (payload.at(0) == 0x01) {
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Left";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Left")));
+ }
+ break;
+ case 0x08:
+ if (payload.at(0) == 0x00) {
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Right";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Right")));
+ } else if (payload.at(0) == 0x01) {
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Left";
+ emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Left")));
+ }
+ break;
+ }
+ });
+ }
+ }
+
+ if (thing->thingClassId() == soundRemoteThingClassId) {
+ // Get battery level changes
+ ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
+ if (!powerCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find power configuration cluster on" << thing << endpoint;
+ } else {
+ // Only set the initial state if the attribute already exists
+ if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
+ thing->setStateValue(soundRemoteBatteryLevelStateTypeId, powerCluster->batteryPercentage());
+ thing->setStateValue(soundRemoteBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
+ }
+
+ connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
+ qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing;
+ thing->setStateValue(soundRemoteBatteryLevelStateTypeId, percentage);
+ thing->setStateValue(soundRemoteBatteryCriticalStateTypeId, (percentage < 10.0));
+ });
+ }
+
+ // Receive on/off commands
+ ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff);
+ if (!onOffCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint;
+ } else {
+ connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){
+ qCDebug(dcZigbeeTradfri()) << thing << "button pressed" << command;
+ if (command == ZigbeeClusterOnOff::CommandToggle) {
+ qCDebug(dcZigbeeTradfri()) << thing << "pressed power";
+ emit emitEvent(Event(soundRemotePressedEventTypeId, thing->id(), ParamList() << Param(soundRemotePressedEventButtonNameParamTypeId, "Power")));
+ }
+ });
+ }
+
+ // Receive level control commands
+ ZigbeeClusterLevelControl *levelCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl);
+ if (!levelCluster) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find level client cluster on" << thing << endpoint;
+ } else {
+ connect(levelCluster, &ZigbeeClusterLevelControl::commandSent, thing, [=](ZigbeeClusterLevelControl::Command command, const QByteArray &payload){
+ qCDebug(dcZigbeeTradfri()) << thing << "level cluster command sent" << command << payload.toHex();
+ // switch (command) {
+ // case ZigbeeClusterLevelControl::CommandMoveWithOnOff:
+ // qCDebug(dcZigbeeTradfri()) << thing << "long pressed ON";
+ // emit emitEvent(Event(onOffSwitchLongPressedEventTypeId, thing->id(), ParamList() << Param(onOffSwitchLongPressedEventButtonNameParamTypeId, "ON")));
+ // break;
+ // case ZigbeeClusterLevelControl::CommandMove:
+ // qCDebug(dcZigbeeTradfri()) << thing << "long pressed OFF";
+ // emit emitEvent(Event(onOffSwitchLongPressedEventTypeId, thing->id(), ParamList() << Param(onOffSwitchLongPressedEventButtonNameParamTypeId, "OFF")));
+ // break;
+ // default:
+ // break;
+ // }
+ });
+ }
+ }
+
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginZigbeeTradfri::executeAction(ThingActionInfo *info)
+{
+ info->finish(Thing::ThingErrorUnsupportedFeature);
+}
+
+void IntegrationPluginZigbeeTradfri::thingRemoved(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.take(thing);
+ if (node) {
+ QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid();
+ hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node);
+ }
+}
+
+ZigbeeNodeEndpoint *IntegrationPluginZigbeeTradfri::findEndpoint(Thing *thing)
+{
+ ZigbeeNode *node = m_thingNodes.value(thing);
+ if (!node) {
+ qCWarning(dcZigbeeTradfri()) << "Could not find the node for" << thing;
+ return nullptr;
+ }
+
+ quint8 endpointId = thing->paramValue(m_endpointIdParamTypeIds.value(thing->thingClassId())).toUInt();
+ return node->getEndpoint(endpointId);
+}
+
+void IntegrationPluginZigbeeTradfri::createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+ ThingDescriptor descriptor(thingClassId);
+ QString deviceClassName = supportedThings().findById(thingClassId).displayName();
+ descriptor.setTitle(deviceClassName);
+
+ ParamList params;
+ params.append(Param(m_networkUuidParamTypeIds[thingClassId], networkUuid.toString()));
+ params.append(Param(m_ieeeAddressParamTypeIds[thingClassId], node->extendedAddress().toString()));
+ params.append(Param(m_endpointIdParamTypeIds[thingClassId], endpoint->endpointId()));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+}
+
+void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+ // Get the current configured bindings for this node
+ ZigbeeReply *reply = node->removeAllBindings();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node;
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Removed all bindings successfully from" << node;
+ }
+
+ // Read battery, bind and configure attribute reporting for battery
+ if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint;
+ return;
+ }
+
+ qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully";
+ }
+
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute rporting for battery remaining (0.5 % changes = 1)
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
+ reportingConfig.dataType = Zigbee::Uint8;
+ reportingConfig.minReportingInterval = 300;
+ reportingConfig.maxReportingInterval = 2700;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+
+ qCDebug(dcZigbeeTradfri()) << "Bind on/off configuration cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully";
+ }
+
+ qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully";
+ }
+
+ qCDebug(dcZigbeeTradfri()) << "Read final binding table from node" << node;
+ ZigbeeReply *reply = node->readBindingTableEntries();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node;
+ } else {
+ foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) {
+ qCDebug(dcZigbeeTradfri()) << node << binding;
+ }
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+
+// if (endpoint->hasOutputCluster(ZigbeeClusterLibrary::ClusterIdGroups)) {
+// qCDebug(dcZigbeeTradfri()) << "Try to add group...";
+// ZigbeeClusterGroups *groupCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdGroups);
+// ZigbeeClusterReply *reply = groupCluster->addGroup(0x0000, QString());
+// connect(reply, &ZigbeeClusterReply::finished, node, [=](){
+// if (reply->error()) {
+// qCWarning(dcZigbeeTradfri()) << "Failed to add remote to group 0x0000";
+// return;
+// }
+
+
+// });
+// }
+
+ // Read battery, bind and configure attribute reporting for battery
+ if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint;
+ return;
+ }
+
+ qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully";
+ }
+
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute reporting for battery remaining (0.5 % changes = 1)
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
+ reportingConfig.dataType = Zigbee::Uint8;
+ reportingConfig.minReportingInterval = 300;
+ reportingConfig.maxReportingInterval = 2700;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+ // Init OnOff cluster
+ qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully";
+ }
+
+
+ // Init Level cluster
+ qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully";
+ }
+
+ // Read final bindings
+ qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node;
+ ZigbeeReply *reply = node->readBindingTableEntries();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node;
+ } else {
+ foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) {
+ qCDebug(dcZigbeeTradfri()) << node << binding;
+ }
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
+{
+ // Get the current configured bindings for this node
+ ZigbeeReply *reply = node->removeAllBindings();
+ connect(reply, &ZigbeeReply::finished, node, [=](){
+ if (reply->error() != ZigbeeReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node;
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Removed all bindings successfully from" << node;
+ }
+
+ // Read battery, bind and configure attribute reporting for battery
+
+ if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint;
+ return;
+ }
+
+ qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes" << node;
+ ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
+ connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
+ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully";
+ }
+
+ // Bind the cluster to the coordinator
+ qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator IEEE address";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
+ hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully";
+ }
+
+ // Configure attribute rporting for battery remaining (0.5 % changes = 1)
+ ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
+ reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
+ reportingConfig.dataType = Zigbee::Uint8;
+ reportingConfig.minReportingInterval = 300;
+ reportingConfig.maxReportingInterval = 2700;
+ reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data();
+
+ qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator";
+ ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
+ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
+ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
+ } else {
+ qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
+ }
+
+ qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator";
+ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000);
+ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
+ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
+ qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error();
+ return;
+ }
+ qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully";
+ });
+ });
+ });
+ });
+ });
+}
+
diff --git a/zigbeetradfri/integrationpluginzigbeetradfri.h b/zigbeetradfri/integrationpluginzigbeetradfri.h
new file mode 100644
index 00000000..746cdf15
--- /dev/null
+++ b/zigbeetradfri/integrationpluginzigbeetradfri.h
@@ -0,0 +1,80 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, 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 Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINZIGBEETRADFRI_H
+#define INTEGRATIONPLUGINZIGBEETRADFRI_H
+
+#include "integrations/integrationplugin.h"
+#include "hardware/zigbee/zigbeehandler.h"
+#include "plugintimer.h"
+
+#include
+
+class IntegrationPluginZigbeeTradfri: public IntegrationPlugin, public ZigbeeHandler
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeetradfri.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginZigbeeTradfri();
+
+ QString name() const override;
+ bool handleNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+ void handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) override;
+
+ void init() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ PluginTimer *m_presenceTimer = nullptr;
+ QHash m_thingNodes;
+
+ QHash m_ieeeAddressParamTypeIds;
+ QHash m_networkUuidParamTypeIds;
+ QHash m_endpointIdParamTypeIds;
+
+ QHash m_connectedStateTypeIds;
+ QHash m_signalStrengthStateTypeIds;
+ QHash m_versionStateTypeIds;
+
+ ZigbeeNodeEndpoint *findEndpoint(Thing *thing);
+ void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+
+ void initOnOffSwitch(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+ void initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+ void initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
+
+};
+
+#endif // INTEGRATIONPLUGINZIGBEETRADFRI_H
diff --git a/zigbeetradfri/integrationpluginzigbeetradfri.json b/zigbeetradfri/integrationpluginzigbeetradfri.json
new file mode 100644
index 00000000..fab02bda
--- /dev/null
+++ b/zigbeetradfri/integrationpluginzigbeetradfri.json
@@ -0,0 +1,526 @@
+{
+ "name": "ZigbeeTradfri",
+ "displayName": "Zigbee TRÅDFRI",
+ "id": "ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830",
+ "vendors": [
+ {
+ "name": "ikeaTradfri",
+ "displayName": "IKEA TRÅDFRI",
+ "id": "1a4fdeb3-2eab-47f2-9775-569552988a88",
+ "thingClasses": [
+ {
+ "name": "onOffSwitch",
+ "displayName": "TRÅDFRI On/Off switch",
+ "id": "2007e8ae-4849-47f6-98d6-ea6769c8ebd9",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "longpressmultibutton", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "8b33f5cb-d1a3-4ae6-837a-d88e77252185",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "37fae6ef-bbbe-4eb8-a199-9ed718224b70",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "4dafffae-af3b-40c0-8a61-8a15b9d24f64",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "b5abd47e-95f1-4e35-94fa-be87c396073a",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "21889797-32c0-4f1b-903d-8af71981882b",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "41c74445-4d2d-46dc-8a63-c98f1df8e742",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "103fba1d-79a6-4b48-b7a1-f91090f1c699",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "a47e27ce-f443-4c9a-96fc-d59ec4989b49",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+ {
+ "id": "20a75127-21ec-43b5-adb0-4f33e03303f7",
+ "name": "pressed",
+ "displayName": "Button pressed",
+ "paramTypes": [
+ {
+ "id": "1b2abb21-f60e-410b-9cfc-57ca991fb5af",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["ON", "OFF"]
+ }
+ ]
+ },
+ {
+ "id": "496c4d7f-ad90-427b-8363-56add5d64304",
+ "name": "longPressed",
+ "displayName": "Button longpressed",
+ "paramTypes": [
+ {
+ "id": "c6bde301-634e-404f-baed-80c5a50a3d46",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["ON", "OFF"]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "remote",
+ "displayName": "TRÅDFRI remote",
+ "id": "19ee3f56-f958-4b04-8ea3-747b72d7c7d6",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "multibutton", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "2ee18d3e-5fb8-4809-a52a-8e420b677f6e",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "7bb2e588-5e05-4cf8-bedc-a54d6cdae30e",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "09cbf843-0036-47b8-8c2b-a58939075203",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "9d5d116c-f742-4766-baf9-e7662f5003a9",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "b87d310a-28ba-4546-9f47-822e5182836c",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "340444fa-b158-4d03-9967-7fa45a96bd45",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "4a72d569-44be-4689-bda6-addc0c6351d4",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "87b60c83-d980-48f5-bc31-0daa241d727e",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+ {
+ "id": "4d671edd-d57c-4f72-a43a-d8e4eaa741cf",
+ "name": "pressed",
+ "displayName": "Button pressed",
+ "paramTypes": [
+ {
+ "id": "2e6bc343-41a3-4634-b514-4512ae54afbc",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["Power", "Up", "Down", "Left", "Right"]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "soundRemote",
+ "displayName": "TRÅDFRI Symfonisk",
+ "id": "a18e398d-c14b-4bcd-8e6d-b64737436814",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "longpressmultibutton", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "fa6544fd-9023-4c01-8600-7cc2f93aa4ca",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "268c557d-09e9-4cbb-9b0b-910c338c0dfb",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "194f1d90-fcdc-4489-b4d3-2448200b21d0",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "41ca0f43-998e-4a5e-ad6b-ae7deab38461",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "9485fea7-adfc-47aa-a7c4-76f38eff49ca",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "36c0a07a-7e20-4303-b6c7-7711c8ae36ad",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "1a0e2e7b-1b2a-461c-a24f-1323c2aea4a1",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "59312570-9cab-4e64-ac2f-2eda1c5ac314",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+ {
+ "id": "46a8c0c7-2bc7-4eb7-a7fe-c0d7263c2e39",
+ "name": "pressed",
+ "displayName": "Button pressed",
+ "paramTypes": [
+ {
+ "id": "e728e820-13e5-4ea8-ad93-a9a1d3f8742c",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["Power"]
+ }
+ ]
+ },
+ {
+ "id": "18ece32b-e0a0-4544-a00c-7e70b25a2398",
+ "name": "longPressed",
+ "displayName": "Button longpressed",
+ "paramTypes": [
+ {
+ "id": "f8a72cd6-1b78-4595-a941-377d7e9a2edf",
+ "name": "buttonName",
+ "displayName": "Button name",
+ "type": "QString",
+ "allowedValues": ["Power"]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "motionSensor",
+ "displayName": "TRÅDFRI motion sensor",
+ "id": "30a1090e-8058-4a5b-aee3-4ba112ba3ba7",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "presencesensor", "batterylevel", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "a03fabac-df59-4f4c-a691-237b1c37270f",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "263a92bc-a993-4527-915c-1eccb3c2a7c5",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "11e97521-0108-4157-8d85-26d64ecd7723",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "settingsTypes": [
+ {
+ "id": "31cd2810-453b-4d86-a3ea-5556bcde4822",
+ "name": "timeout",
+ "displayName": "Time period",
+ "type": "uint",
+ "unit": "Seconds",
+ "defaultValue": 180,
+ "minValue": 180
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "6bf470bb-4756-43fa-bddf-5e6ce002caa3",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "0a54036e-ca25-40fd-8f24-3516c5141c0b",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "709a215b-5664-4cea-b841-54dd01e387b8",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ },
+ {
+ "id": "e2e414c1-5f21-4244-942a-ee17cb25f1c0",
+ "name": "batteryLevel",
+ "displayName": "Battery",
+ "displayNameEvent": "Battery changed",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ {
+ "id": "21e7c58e-595e-4b7d-bd21-c0a424c876dc",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "16ee7fe2-1a86-40e9-aae5-610f712ca155",
+ "name": "isPresent",
+ "displayName": "Present",
+ "displayNameEvent": "Present changed",
+ "type": "bool",
+ "defaultValue": false,
+ "ioType": "digitalInput"
+ },
+ {
+ "id": "12041993-c731-4cc1-8010-904b5655d920",
+ "name": "lastSeenTime",
+ "displayName": "Last seen time",
+ "displayNameEvent": "Last seen time changed",
+ "type": "int",
+ "unit": "UnixTime",
+ "defaultValue": 0
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ },
+ {
+ "name": "signalRepeater",
+ "displayName": "TRÅDFRI signal repeater",
+ "id": "ac51f426-195e-472a-837f-4ba7e4666e6e",
+ "setupMethod": "JustAdd",
+ "createMethods": [ "Auto" ],
+ "interfaces": [ "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "fcaf0646-ef18-4e16-afde-39823437b7f4",
+ "name": "ieeeAddress",
+ "displayName": "IEEE adress",
+ "type": "QString",
+ "defaultValue": "00:00:00:00:00:00:00:00"
+ },
+ {
+ "id": "5ce9a369-545b-4848-a6d7-6d3bc40779a2",
+ "name": "networkUuid",
+ "displayName": "Zigbee network UUID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "3ff01f62-83dc-4856-a7f0-5605091845cc",
+ "name": "endpointId",
+ "displayName": "Endpoint ID",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "7415ea93-9b72-4740-b3b1-a2ebb73d1db5",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "05758f08-33a8-485b-8572-f87021e3af36",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "defaultValue": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "type": "uint",
+ "unit": "Percentage"
+ },
+ {
+ "id": "a7ad66f1-7b4c-4458-8a13-e1bb9467bc17",
+ "name": "version",
+ "displayName": "Version",
+ "displayNameEvent": "Version changed",
+ "type": "QString",
+ "cached": true,
+ "defaultValue": ""
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/zigbeetradfri/meta.json b/zigbeetradfri/meta.json
new file mode 100644
index 00000000..f5f3e15f
--- /dev/null
+++ b/zigbeetradfri/meta.json
@@ -0,0 +1,16 @@
+{
+ "title": "ZigBee TRÅDFRI",
+ "tagline": "Connect and control ZigBee IKEA TRÅDFRI devices native.",
+ "icon": "ikea.svg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "zigbee"
+ ],
+ "categories": [
+ "light",
+ "sensor",
+ "switch",
+ "socket"
+ ]
+}
diff --git a/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts b/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts
new file mode 100644
index 00000000..36abb795
--- /dev/null
+++ b/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts
@@ -0,0 +1,415 @@
+
+
+
+
+ ZigbeeTradfri
+
+
+
+
+
+
+
+
+
+ Battery
+ The name of the ParamType (ThingClass: motionSensor, EventType: batteryLevel, ID: {e2e414c1-5f21-4244-942a-ee17cb25f1c0})
+----------
+The name of the StateType ({e2e414c1-5f21-4244-942a-ee17cb25f1c0}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: soundRemote, EventType: batteryLevel, ID: {1a0e2e7b-1b2a-461c-a24f-1323c2aea4a1})
+----------
+The name of the StateType ({1a0e2e7b-1b2a-461c-a24f-1323c2aea4a1}) of ThingClass soundRemote
+----------
+The name of the ParamType (ThingClass: remote, EventType: batteryLevel, ID: {4a72d569-44be-4689-bda6-addc0c6351d4})
+----------
+The name of the StateType ({4a72d569-44be-4689-bda6-addc0c6351d4}) of ThingClass remote
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: batteryLevel, ID: {103fba1d-79a6-4b48-b7a1-f91090f1c699})
+----------
+The name of the StateType ({103fba1d-79a6-4b48-b7a1-f91090f1c699}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+ Battery changed
+ The name of the EventType ({e2e414c1-5f21-4244-942a-ee17cb25f1c0}) of ThingClass motionSensor
+----------
+The name of the EventType ({1a0e2e7b-1b2a-461c-a24f-1323c2aea4a1}) of ThingClass soundRemote
+----------
+The name of the EventType ({4a72d569-44be-4689-bda6-addc0c6351d4}) of ThingClass remote
+----------
+The name of the EventType ({103fba1d-79a6-4b48-b7a1-f91090f1c699}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+
+
+
+ Battery critical
+ The name of the ParamType (ThingClass: motionSensor, EventType: batteryCritical, ID: {21e7c58e-595e-4b7d-bd21-c0a424c876dc})
+----------
+The name of the StateType ({21e7c58e-595e-4b7d-bd21-c0a424c876dc}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: soundRemote, EventType: batteryCritical, ID: {59312570-9cab-4e64-ac2f-2eda1c5ac314})
+----------
+The name of the StateType ({59312570-9cab-4e64-ac2f-2eda1c5ac314}) of ThingClass soundRemote
+----------
+The name of the ParamType (ThingClass: remote, EventType: batteryCritical, ID: {87b60c83-d980-48f5-bc31-0daa241d727e})
+----------
+The name of the StateType ({87b60c83-d980-48f5-bc31-0daa241d727e}) of ThingClass remote
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: batteryCritical, ID: {a47e27ce-f443-4c9a-96fc-d59ec4989b49})
+----------
+The name of the StateType ({a47e27ce-f443-4c9a-96fc-d59ec4989b49}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+ Battery critical changed
+ The name of the EventType ({21e7c58e-595e-4b7d-bd21-c0a424c876dc}) of ThingClass motionSensor
+----------
+The name of the EventType ({59312570-9cab-4e64-ac2f-2eda1c5ac314}) of ThingClass soundRemote
+----------
+The name of the EventType ({87b60c83-d980-48f5-bc31-0daa241d727e}) of ThingClass remote
+----------
+The name of the EventType ({a47e27ce-f443-4c9a-96fc-d59ec4989b49}) of ThingClass onOffSwitch
+
+
+
+
+
+ Button longpressed
+ The name of the EventType ({18ece32b-e0a0-4544-a00c-7e70b25a2398}) of ThingClass soundRemote
+----------
+The name of the EventType ({496c4d7f-ad90-427b-8363-56add5d64304}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+ Button name
+ The name of the ParamType (ThingClass: soundRemote, EventType: longPressed, ID: {f8a72cd6-1b78-4595-a941-377d7e9a2edf})
+----------
+The name of the ParamType (ThingClass: soundRemote, EventType: pressed, ID: {e728e820-13e5-4ea8-ad93-a9a1d3f8742c})
+----------
+The name of the ParamType (ThingClass: remote, EventType: pressed, ID: {2e6bc343-41a3-4634-b514-4512ae54afbc})
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: longPressed, ID: {c6bde301-634e-404f-baed-80c5a50a3d46})
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: pressed, ID: {1b2abb21-f60e-410b-9cfc-57ca991fb5af})
+
+
+
+
+
+
+ Button pressed
+ The name of the EventType ({46a8c0c7-2bc7-4eb7-a7fe-c0d7263c2e39}) of ThingClass soundRemote
+----------
+The name of the EventType ({4d671edd-d57c-4f72-a43a-d8e4eaa741cf}) of ThingClass remote
+----------
+The name of the EventType ({20a75127-21ec-43b5-adb0-4f33e03303f7}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: signalRepeater, EventType: connected, ID: {7415ea93-9b72-4740-b3b1-a2ebb73d1db5})
+----------
+The name of the StateType ({7415ea93-9b72-4740-b3b1-a2ebb73d1db5}) of ThingClass signalRepeater
+----------
+The name of the ParamType (ThingClass: motionSensor, EventType: connected, ID: {6bf470bb-4756-43fa-bddf-5e6ce002caa3})
+----------
+The name of the StateType ({6bf470bb-4756-43fa-bddf-5e6ce002caa3}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: soundRemote, EventType: connected, ID: {41ca0f43-998e-4a5e-ad6b-ae7deab38461})
+----------
+The name of the StateType ({41ca0f43-998e-4a5e-ad6b-ae7deab38461}) of ThingClass soundRemote
+----------
+The name of the ParamType (ThingClass: remote, EventType: connected, ID: {9d5d116c-f742-4766-baf9-e7662f5003a9})
+----------
+The name of the StateType ({9d5d116c-f742-4766-baf9-e7662f5003a9}) of ThingClass remote
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: connected, ID: {b5abd47e-95f1-4e35-94fa-be87c396073a})
+----------
+The name of the StateType ({b5abd47e-95f1-4e35-94fa-be87c396073a}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+ Connected changed
+ The name of the EventType ({7415ea93-9b72-4740-b3b1-a2ebb73d1db5}) of ThingClass signalRepeater
+----------
+The name of the EventType ({6bf470bb-4756-43fa-bddf-5e6ce002caa3}) of ThingClass motionSensor
+----------
+The name of the EventType ({41ca0f43-998e-4a5e-ad6b-ae7deab38461}) of ThingClass soundRemote
+----------
+The name of the EventType ({9d5d116c-f742-4766-baf9-e7662f5003a9}) of ThingClass remote
+----------
+The name of the EventType ({b5abd47e-95f1-4e35-94fa-be87c396073a}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+ Endpoint ID
+ The name of the ParamType (ThingClass: signalRepeater, Type: thing, ID: {3ff01f62-83dc-4856-a7f0-5605091845cc})
+----------
+The name of the ParamType (ThingClass: motionSensor, Type: thing, ID: {11e97521-0108-4157-8d85-26d64ecd7723})
+----------
+The name of the ParamType (ThingClass: soundRemote, Type: thing, ID: {194f1d90-fcdc-4489-b4d3-2448200b21d0})
+----------
+The name of the ParamType (ThingClass: remote, Type: thing, ID: {09cbf843-0036-47b8-8c2b-a58939075203})
+----------
+The name of the ParamType (ThingClass: onOffSwitch, Type: thing, ID: {4dafffae-af3b-40c0-8a61-8a15b9d24f64})
+
+
+
+
+
+
+
+
+ IEEE adress
+ The name of the ParamType (ThingClass: signalRepeater, Type: thing, ID: {fcaf0646-ef18-4e16-afde-39823437b7f4})
+----------
+The name of the ParamType (ThingClass: motionSensor, Type: thing, ID: {a03fabac-df59-4f4c-a691-237b1c37270f})
+----------
+The name of the ParamType (ThingClass: soundRemote, Type: thing, ID: {fa6544fd-9023-4c01-8600-7cc2f93aa4ca})
+----------
+The name of the ParamType (ThingClass: remote, Type: thing, ID: {2ee18d3e-5fb8-4809-a52a-8e420b677f6e})
+----------
+The name of the ParamType (ThingClass: onOffSwitch, Type: thing, ID: {8b33f5cb-d1a3-4ae6-837a-d88e77252185})
+
+
+
+
+ IKEA TRÅDFRI
+ The name of the vendor ({1a4fdeb3-2eab-47f2-9775-569552988a88})
+
+
+
+
+
+ Last seen time
+ The name of the ParamType (ThingClass: motionSensor, EventType: lastSeenTime, ID: {12041993-c731-4cc1-8010-904b5655d920})
+----------
+The name of the StateType ({12041993-c731-4cc1-8010-904b5655d920}) of ThingClass motionSensor
+
+
+
+
+ Last seen time changed
+ The name of the EventType ({12041993-c731-4cc1-8010-904b5655d920}) of ThingClass motionSensor
+
+
+
+
+
+ Present
+ The name of the ParamType (ThingClass: motionSensor, EventType: isPresent, ID: {16ee7fe2-1a86-40e9-aae5-610f712ca155})
+----------
+The name of the StateType ({16ee7fe2-1a86-40e9-aae5-610f712ca155}) of ThingClass motionSensor
+
+
+
+
+ Present changed
+ The name of the EventType ({16ee7fe2-1a86-40e9-aae5-610f712ca155}) of ThingClass motionSensor
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Signal strength
+ The name of the ParamType (ThingClass: signalRepeater, EventType: signalStrength, ID: {05758f08-33a8-485b-8572-f87021e3af36})
+----------
+The name of the StateType ({05758f08-33a8-485b-8572-f87021e3af36}) of ThingClass signalRepeater
+----------
+The name of the ParamType (ThingClass: motionSensor, EventType: signalStrength, ID: {0a54036e-ca25-40fd-8f24-3516c5141c0b})
+----------
+The name of the StateType ({0a54036e-ca25-40fd-8f24-3516c5141c0b}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: soundRemote, EventType: signalStrength, ID: {9485fea7-adfc-47aa-a7c4-76f38eff49ca})
+----------
+The name of the StateType ({9485fea7-adfc-47aa-a7c4-76f38eff49ca}) of ThingClass soundRemote
+----------
+The name of the ParamType (ThingClass: remote, EventType: signalStrength, ID: {b87d310a-28ba-4546-9f47-822e5182836c})
+----------
+The name of the StateType ({b87d310a-28ba-4546-9f47-822e5182836c}) of ThingClass remote
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: signalStrength, ID: {21889797-32c0-4f1b-903d-8af71981882b})
+----------
+The name of the StateType ({21889797-32c0-4f1b-903d-8af71981882b}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+ Signal strength changed
+ The name of the EventType ({05758f08-33a8-485b-8572-f87021e3af36}) of ThingClass signalRepeater
+----------
+The name of the EventType ({0a54036e-ca25-40fd-8f24-3516c5141c0b}) of ThingClass motionSensor
+----------
+The name of the EventType ({9485fea7-adfc-47aa-a7c4-76f38eff49ca}) of ThingClass soundRemote
+----------
+The name of the EventType ({b87d310a-28ba-4546-9f47-822e5182836c}) of ThingClass remote
+----------
+The name of the EventType ({21889797-32c0-4f1b-903d-8af71981882b}) of ThingClass onOffSwitch
+
+
+
+
+ TRÅDFRI On/Off switch
+ The name of the ThingClass ({2007e8ae-4849-47f6-98d6-ea6769c8ebd9})
+
+
+
+
+ TRÅDFRI Symfonisk
+ The name of the ThingClass ({a18e398d-c14b-4bcd-8e6d-b64737436814})
+
+
+
+
+ TRÅDFRI motion sensor
+ The name of the ThingClass ({30a1090e-8058-4a5b-aee3-4ba112ba3ba7})
+
+
+
+
+ TRÅDFRI remote
+ The name of the ThingClass ({19ee3f56-f958-4b04-8ea3-747b72d7c7d6})
+
+
+
+
+ TRÅDFRI signal repeater
+ The name of the ThingClass ({ac51f426-195e-472a-837f-4ba7e4666e6e})
+
+
+
+
+ Time period
+ The name of the ParamType (ThingClass: motionSensor, Type: settings, ID: {31cd2810-453b-4d86-a3ea-5556bcde4822})
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version
+ The name of the ParamType (ThingClass: signalRepeater, EventType: version, ID: {a7ad66f1-7b4c-4458-8a13-e1bb9467bc17})
+----------
+The name of the StateType ({a7ad66f1-7b4c-4458-8a13-e1bb9467bc17}) of ThingClass signalRepeater
+----------
+The name of the ParamType (ThingClass: motionSensor, EventType: version, ID: {709a215b-5664-4cea-b841-54dd01e387b8})
+----------
+The name of the StateType ({709a215b-5664-4cea-b841-54dd01e387b8}) of ThingClass motionSensor
+----------
+The name of the ParamType (ThingClass: soundRemote, EventType: version, ID: {36c0a07a-7e20-4303-b6c7-7711c8ae36ad})
+----------
+The name of the StateType ({36c0a07a-7e20-4303-b6c7-7711c8ae36ad}) of ThingClass soundRemote
+----------
+The name of the ParamType (ThingClass: remote, EventType: version, ID: {340444fa-b158-4d03-9967-7fa45a96bd45})
+----------
+The name of the StateType ({340444fa-b158-4d03-9967-7fa45a96bd45}) of ThingClass remote
+----------
+The name of the ParamType (ThingClass: onOffSwitch, EventType: version, ID: {41c74445-4d2d-46dc-8a63-c98f1df8e742})
+----------
+The name of the StateType ({41c74445-4d2d-46dc-8a63-c98f1df8e742}) of ThingClass onOffSwitch
+
+
+
+
+
+
+
+
+ Version changed
+ The name of the EventType ({a7ad66f1-7b4c-4458-8a13-e1bb9467bc17}) of ThingClass signalRepeater
+----------
+The name of the EventType ({709a215b-5664-4cea-b841-54dd01e387b8}) of ThingClass motionSensor
+----------
+The name of the EventType ({36c0a07a-7e20-4303-b6c7-7711c8ae36ad}) of ThingClass soundRemote
+----------
+The name of the EventType ({340444fa-b158-4d03-9967-7fa45a96bd45}) of ThingClass remote
+----------
+The name of the EventType ({41c74445-4d2d-46dc-8a63-c98f1df8e742}) of ThingClass onOffSwitch
+
+
+
+
+ Zigbee TRÅDFRI
+ The name of the plugin ZigbeeTradfri ({ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830})
+
+
+
+
+
+
+
+
+ Zigbee network UUID
+ The name of the ParamType (ThingClass: signalRepeater, Type: thing, ID: {5ce9a369-545b-4848-a6d7-6d3bc40779a2})
+----------
+The name of the ParamType (ThingClass: motionSensor, Type: thing, ID: {263a92bc-a993-4527-915c-1eccb3c2a7c5})
+----------
+The name of the ParamType (ThingClass: soundRemote, Type: thing, ID: {268c557d-09e9-4cbb-9b0b-910c338c0dfb})
+----------
+The name of the ParamType (ThingClass: remote, Type: thing, ID: {7bb2e588-5e05-4cf8-bedc-a54d6cdae30e})
+----------
+The name of the ParamType (ThingClass: onOffSwitch, Type: thing, ID: {37fae6ef-bbbe-4eb8-a199-9ed718224b70})
+
+
+
+
diff --git a/zigbeetradfri/zigbeetradfri.pro b/zigbeetradfri/zigbeetradfri.pro
new file mode 100644
index 00000000..f3b747ac
--- /dev/null
+++ b/zigbeetradfri/zigbeetradfri.pro
@@ -0,0 +1,12 @@
+include(../plugins.pri)
+
+PKGCONFIG += nymea-zigbee
+
+SOURCES += \
+ integrationpluginzigbeetradfri.cpp
+
+HEADERS += \
+ integrationpluginzigbeetradfri.h
+
+
+