diff --git a/debian/control b/debian/control
index 770c31f1..4fc83b76 100644
--- a/debian/control
+++ b/debian/control
@@ -1061,6 +1061,21 @@ Description: nymea.io zigbee plugin for different generic recognizable lights
.
This package will install the nymea.io plugin for generic recognizable lights
+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
@@ -1133,7 +1148,8 @@ Section: libs
Architecture: all
Depends: nymea-plugin-zigbee-lumi,
nymea-plugin-zigbee-generic,
- nymea-plugin-zigbee-generic-lights
+ nymea-plugin-zigbee-generic-lights,
+ 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
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 13b45998..bb7c08e1 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -67,6 +67,7 @@ PLUGIN_DIRS = \
zigbee-generic \
zigbee-generic-lights \
zigbee-lumi \
+ zigbee-tradfri \
message(============================================)
diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json
new file mode 100644
index 00000000..05d430e1
--- /dev/null
+++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json
@@ -0,0 +1,128 @@
+{
+ "name": "ZigbeeTradfri",
+ "displayName": "Zigbee TRÅDFRI",
+ "id": "6a4343be-9fd6-4015-9ff5-38542651c534",
+ "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"]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp
new file mode 100644
index 00000000..5f9caa63
--- /dev/null
+++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp
@@ -0,0 +1,226 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+*
+* 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 "hardware/zigbee/zigbeehardwareresource.h"
+
+
+IntegrationPluginZigbeeTradfri::IntegrationPluginZigbeeTradfri()
+{
+ m_ieeeAddressParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingIeeeAddressParamTypeId;
+
+ m_networkUuidParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingNetworkUuidParamTypeId;
+
+ m_endpointIdParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingEndpointIdParamTypeId;
+
+ m_connectedStateTypeIds[onOffSwitchThingClassId] = onOffSwitchConnectedStateTypeId;
+
+ m_signalStrengthStateTypeIds[onOffSwitchThingClassId] = onOffSwitchSignalStrengthStateTypeId;
+
+ m_versionStateTypeIds[onOffSwitchThingClassId] = onOffSwitchVersionStateTypeId;
+}
+
+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->profile() == Zigbee::ZigbeeProfileHomeAutomation &&
+ endpoint->deviceId() == Zigbee::HomeAutomationDeviceNonColourController) {
+ qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI on/off switch" << node << endpoint;
+ ThingDescriptor descriptor(onOffSwitchThingClassId);
+ QString deviceClassName = supportedThings().findById(onOffSwitchThingClassId).displayName();
+ descriptor.setTitle(deviceClassName);
+
+ ParamList params;
+ params.append(Param(onOffSwitchThingNetworkUuidParamTypeId, networkUuid.toString()));
+ params.append(Param(onOffSwitchThingIeeeAddressParamTypeId, node->extendedAddress().toString()));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ handled = true;
+ }
+ }
+
+ return handled;
+}
+
+void IntegrationPluginZigbeeTradfri::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid)
+{
+ Q_UNUSED(networkUuid)
+ Thing *thing = m_thingNodes.key(node);
+ if (thing) {
+ qCDebug(dcZigbeeTradfri()) << 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 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());
+
+ if (thing->thingClassId() == onOffSwitchThingClassId) {
+
+ // 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 cntrol 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;
+ }
+ });
+ }
+
+ 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));
+ });
+ }
+ }
+
+ 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);
+}
+
diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.h b/zigbee-tradfri/integrationpluginzigbeetradfri.h
new file mode 100644
index 00000000..e40afb1d
--- /dev/null
+++ b/zigbee-tradfri/integrationpluginzigbeetradfri.h
@@ -0,0 +1,73 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "integrationpluginzigbee-tradfri.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:
+ 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);
+};
+
+#endif // INTEGRATIONPLUGINZIGBEETRADFRI_H
diff --git a/zigbee-tradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbee-tradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts
new file mode 100644
index 00000000..dcd02922
--- /dev/null
+++ b/zigbee-tradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/zigbee-tradfri/zigbee-tradfri.pro b/zigbee-tradfri/zigbee-tradfri.pro
new file mode 100644
index 00000000..f3b747ac
--- /dev/null
+++ b/zigbee-tradfri/zigbee-tradfri.pro
@@ -0,0 +1,12 @@
+include(../plugins.pri)
+
+PKGCONFIG += nymea-zigbee
+
+SOURCES += \
+ integrationpluginzigbeetradfri.cpp
+
+HEADERS += \
+ integrationpluginzigbeetradfri.h
+
+
+