diff --git a/debian/control b/debian/control index 525e5757..2100d8bf 100644 --- a/debian/control +++ b/debian/control @@ -1183,22 +1183,6 @@ Description: nymea.io zigbee plugin for develco things This package will install the nymea.io plugin for Develco -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}, @@ -1231,6 +1215,22 @@ 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-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-philipshue Architecture: any Depends: ${shlibs:Depends}, @@ -1247,6 +1247,15 @@ Description: nymea.io zigbee plugin for Philips Hue devices This package will install the nymea.io plugin for Philips Hue zigbee devices +Package: nymea-plugin-zigbee-remotes +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libnymea-zigbee1, + nymea-plugins-translations, +Description: nymea.io plugin for various zigbee remote controls. + + Package: nymea-plugin-zigbee-tradfri Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-zigbee-remotes.install.in b/debian/nymea-plugin-zigbee-remotes.install.in new file mode 100644 index 00000000..1fb765c7 --- /dev/null +++ b/debian/nymea-plugin-zigbee-remotes.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeeremotes.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 4b34049f..6a64c67e 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -80,6 +80,7 @@ PLUGIN_DIRS = \ zigbeegenericlights \ zigbeelumi \ zigbeephilipshue \ + zigbeeremotes \ zigbeetradfri \ diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp index f6c412b3..9afd3a90 100644 --- a/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp @@ -262,14 +262,14 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) if (!levelCluster) { qCWarning(dcZigbeePhilipsHue()) << "Could not find level client cluster on" << thing << endpointHa; } 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: + connect(levelCluster, &ZigbeeClusterLevelControl::commandStepSent, thing, [=](bool withOnOff, ZigbeeClusterLevelControl::StepMode stepMode, quint8 stepSize, quint16 transitionTime){ + qCDebug(dcZigbeePhilipsHue()) << thing << "level button pressed" << withOnOff << stepMode << stepSize << transitionTime; + switch (stepMode) { + case ZigbeeClusterLevelControl::StepModeUp: qCDebug(dcZigbeePhilipsHue()) << thing << "DIM UP pressed"; emit emitEvent(Event(smartButtonLongPressedEventTypeId, thing->id())); break; - case ZigbeeClusterLevelControl::FadeModeDown: + case ZigbeeClusterLevelControl::StepModeDown: qCDebug(dcZigbeePhilipsHue()) << thing << "DIM DOWN pressed"; emit emitEvent(Event(smartButtonLongPressedEventTypeId, thing->id())); break; diff --git a/zigbeeremotes/README.md b/zigbeeremotes/README.md new file mode 100644 index 00000000..f5ead779 --- /dev/null +++ b/zigbeeremotes/README.md @@ -0,0 +1,17 @@ +# ZigBee remotes + +This plugin contains a collection of various Zigbee remote controls. + +## Supported Things + +### JUNG ZLL5004m + +The JUNG ZLL5004m is a battery powered wall switch with 8 buttons. The top row acts as on/off on press as well as dimming up and down on longpress. +The other 6 buttons recall scenes. +To connect the device to nymea, start with factory resetting the remote by pressing and holding buttons 5 and 6 (3rd row, left and right) down simultaneously +until the remote starts blinking, then press the off button. Then permit new devices to join the ZigBee network and press and hold button 2 and 7 (top-right +and bottom-left) until the remote starts blinking. +After the remote has joined the nymea network, the remote can also be directly linked to light bulbs by holding it close to a light bulb and pressing and +holding buttons 1 and 8 (top-left and bottom-right) until the remote and the light start blinking. The remotes buttons will now control the light directly +as well as sending its button presses to nymea for further processing. + diff --git a/zigbeeremotes/integrationpluginzigbeeremotes.cpp b/zigbeeremotes/integrationpluginzigbeeremotes.cpp new file mode 100644 index 00000000..7aad49bf --- /dev/null +++ b/zigbeeremotes/integrationpluginzigbeeremotes.cpp @@ -0,0 +1,291 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* +* 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 "integrationpluginzigbeeremotes.h" +#include "plugininfo.h" +#include "hardware/zigbee/zigbeehardwareresource.h" + +#include + + +IntegrationPluginZigbeeRemotes::IntegrationPluginZigbeeRemotes() +{ +} + +QString IntegrationPluginZigbeeRemotes::name() const +{ + return "Remotes"; +} + +void IntegrationPluginZigbeeRemotes::init() +{ + hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeVendor); +} + +bool IntegrationPluginZigbeeRemotes::handleNode(ZigbeeNode *node, const QUuid &networkUuid) +{ + qCDebug(dcZigbeeRemotes) << "Evaluating node:" << node << node->nodeDescriptor().manufacturerCode << node->modelName(); + bool handled = false; + // "Insta" remote (JUNG ZLL 5004) + if (node->nodeDescriptor().manufacturerCode == 0x117A && node->modelName() == " Remote") { + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + if (!endpoint) { + qCWarning(dcZigbeeRemotes()) << "Device claims to be an Insta remote but does not provide endpoint 1"; + return false; + } + + createThing(instaThingClassId, networkUuid, node, endpoint); + + // Nothing to be done here... The device does not support battery level updates and will send all the commands + // to the coordinator unconditionally, no need to bind any clusters... + + handled = true; + } + + return handled; +} + +void IntegrationPluginZigbeeRemotes::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) +{ + Q_UNUSED(networkUuid) + Thing *thing = m_thingNodes.key(node); + if (thing) { + qCDebug(dcZigbeeRemotes()) << 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 IntegrationPluginZigbeeRemotes::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + QUuid networkUuid = thing->paramValue("networkUuid").toUuid(); + qCDebug(dcZigbeeRemotes()) << "Nework uuid:" << networkUuid; + ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue("ieeeAddress").toString()); + ZigbeeNode *node = m_thingNodes.value(thing); + if (!node) { + node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress); + } + + if (!node) { + qCWarning(dcZigbeeRemotes()) << "Zigbee node for" << info->thing()->name() << "not found."; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + m_thingNodes.insert(thing, node); + + // Update connected state + thing->setStateValue("connected", node->reachable()); + connect(node, &ZigbeeNode::reachableChanged, thing, [thing](bool reachable){ + thing->setStateValue("connected", reachable); + }); + + // Update signal strength + thing->setStateValue("signalStrength", qRound(node->lqi() * 100.0 / 255.0)); + connect(node, &ZigbeeNode::lqiChanged, thing, [thing](quint8 lqi){ + uint signalStrength = qRound(lqi * 100.0 / 255.0); + qCDebug(dcZigbeeRemotes()) << thing << "signal strength changed" << signalStrength << "%"; + thing->setStateValue("signalStrength", signalStrength); + }); + + // Type specific setup + if (thing->thingClassId() == instaThingClassId) { + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + + ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); + ZigbeeClusterLevelControl *levelControlCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl); + ZigbeeClusterScenes *scenesCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdScenes); + if (!onOffCluster || !levelControlCluster || !scenesCluster) { + qCWarning(dcZigbeeRemotes()) << "Could not find all of the needed clusters for" << thing->name() << "in" << m_thingNodes.value(thing) << "on endpoint" << endpoint->endpointId(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, this, [=](ZigbeeClusterOnOff::Command command, const QByteArray ¶meters){ + qCDebug(dcZigbeeRemotes()) << "OnOff command received:" << command << parameters; + switch (command) { + case ZigbeeClusterOnOff::CommandOn: + thing->emitEvent(instaPressedEventTypeId, {Param(instaPressedEventButtonNameParamTypeId, "ON")}); + break; + case ZigbeeClusterOnOff::CommandOffWithEffect: + thing->emitEvent(instaPressedEventTypeId, {Param(instaPressedEventButtonNameParamTypeId, "OFF")}); + break; + default: + qCWarning(dcZigbeeRemotes()) << "Unhandled command from Insta Remote:" << command << parameters.toHex(); + } + }); + connect(levelControlCluster, &ZigbeeClusterLevelControl::commandStepSent, this, [=](bool withOnOff, ZigbeeClusterLevelControl::StepMode stepMode, quint8 stepSize, quint16 transitionTime){ + qCDebug(dcZigbeeRemotes()) << "Level command received" << withOnOff << stepMode << stepSize << transitionTime; + thing->emitEvent(instaPressedEventTypeId, {Param(instaPressedEventButtonNameParamTypeId, stepMode == ZigbeeClusterLevelControl::StepModeUp ? "+" : "-")}); + }); + connect(scenesCluster, &ZigbeeClusterScenes::commandSent, this, [=](ZigbeeClusterScenes::Command command, quint16 groupId, quint8 sceneId){ + qCDebug(dcZigbeeRemotes()) << "Scenes command received:" << command << groupId << sceneId; + thing->emitEvent(instaPressedEventTypeId, {Param(instaPressedEventButtonNameParamTypeId, QString::number(sceneId))}); + }); + + + // The device also supports setting saturation, color and color temperature. However, it's quite funky to + // actually get there on the device and that mode seems to be only enabled if there are bindings to + // actual lamps. Once it's bound to lamps, pressing on and off simultaneously will start cycling through the bound + // lights and during that mode, the color/saturation/temperature will act on the currently selected lamp only. + // After some seconds without button press, it will revert back to the default mode where it sends all commands + // to the coordinator *and* all the bound lights simultaneously. + // So, in order to get that working we'd need to fake a like and somehow allow binding that via touch-link from a key-combo on the device. + + // Not supporting that here... A user may still additionally bind the device to a lamp and use that feature with the remote.... + + info->finish(Thing::ThingErrorNoError); + return; + } + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginZigbeeRemotes::thingRemoved(Thing *thing) +{ + ZigbeeNode *node = m_thingNodes.take(thing); + if (node) { + QUuid networkUuid = thing->paramValue("networkUuid").toUuid(); + hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node); + } +} + +void IntegrationPluginZigbeeRemotes::createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ThingDescriptor descriptor(thingClassId); + ThingClass thingClass = supportedThings().findById(thingClassId); + descriptor.setTitle(QString("%1 (%2 - %3)").arg(thingClass.displayName()).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier())); + + ParamList params; + params.append(Param(thingClass.paramTypes().findByName("networkUuid").id(), networkUuid.toString())); + params.append(Param(thingClass.paramTypes().findByName("ieeeAddress").id(), node->extendedAddress().toString())); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); +} + +void IntegrationPluginZigbeeRemotes::bindPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ZigbeeDeviceObjectReply *bindPowerReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(bindPowerReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (bindPowerReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeRemotes()) << "Failed to bind power configuration cluster" << bindPowerReply->error(); + } else { + qCDebug(dcZigbeeRemotes()) << "Binding power configuration cluster finished successfully"; + } + + ZigbeeClusterLibrary::AttributeReportingConfiguration batteryPercentageConfig; + batteryPercentageConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining; + batteryPercentageConfig.dataType = Zigbee::Uint8; + batteryPercentageConfig.minReportingInterval = 300; + batteryPercentageConfig.maxReportingInterval = 2700; + batteryPercentageConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeeRemotes()) << "Configuring attribute reporting for power configuration cluster"; + ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({batteryPercentageConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeRemotes()) << "Failed to configure power configuration cluster attribute reporting" << reportingReply->error(); + } else { + qCDebug(dcZigbeeRemotes()) << "Attribute reporting configuration finished for power configuration cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + }); + }); +} + +void IntegrationPluginZigbeeRemotes::bindOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ZigbeeDeviceObjectReply *bindOnOffClusterReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(bindOnOffClusterReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (bindOnOffClusterReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeRemotes()) << "Failed to bind on/off cluster" << bindOnOffClusterReply->error(); + } else { + qCDebug(dcZigbeeRemotes()) << "Bound on/off cluster successfully"; + } + }); +} + +void IntegrationPluginZigbeeRemotes::bindLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ZigbeeDeviceObjectReply *bindLevelControlClusterReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(bindLevelControlClusterReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (bindLevelControlClusterReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeRemotes()) << "Failed to bind level control cluster" << bindLevelControlClusterReply->error(); + } else { + qCDebug(dcZigbeeRemotes()) << "Bound level control cluster successfully"; + } + }); +} + +void IntegrationPluginZigbeeRemotes::bindScenesCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ZigbeeDeviceObjectReply *bindScenesClusterReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdScenes, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(bindScenesClusterReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (bindScenesClusterReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeRemotes()) << "Failed to bind on/off cluster" << bindScenesClusterReply->error(); + } else { + qCDebug(dcZigbeeRemotes()) << "Bound on/off cluster successfully"; + } + }); +} + +void IntegrationPluginZigbeeRemotes::connectToPowerConfigurationCluster(Thing *thing, ZigbeeNodeEndpoint *endpoint) +{ + // Get battery level changes + ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); + if (powerCluster) { + // If the power cluster attributes are already available, read values now + if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) { + thing->setStateValue("batteryLevel", powerCluster->batteryPercentage()); + thing->setStateValue("batteryCritical", (powerCluster->batteryPercentage() < 10.0)); + } + // Refresh power cluster attributes in any case + ZigbeeClusterReply *reply = powerCluster->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining}); + connect(reply, &ZigbeeClusterReply::finished, thing, [=](){ + if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeRemotes()) << "Reading power configuration cluster attributes finished with error" << reply->error(); + return; + } + thing->setStateValue("batteryLevel", powerCluster->batteryPercentage()); + thing->setStateValue("batteryCritical", (powerCluster->batteryPercentage() < 10.0)); + }); + + // Connect to battery level changes + connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){ + thing->setStateValue("batteryLevel", percentage); + thing->setStateValue("batteryCritical", (percentage < 10.0)); + }); + } +} diff --git a/zigbeeremotes/integrationpluginzigbeeremotes.h b/zigbeeremotes/integrationpluginzigbeeremotes.h new file mode 100644 index 00000000..0afc9fe3 --- /dev/null +++ b/zigbeeremotes/integrationpluginzigbeeremotes.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 INTEGRATIONPLUGINZIGBEEREMOTES_H +#define INTEGRATIONPLUGINZIGBEEREMOTES_H + +#include "integrations/integrationplugin.h" +#include "hardware/zigbee/zigbeehandler.h" +#include "plugintimer.h" + +#include "extern-plugininfo.h" + +#include + +class IntegrationPluginZigbeeRemotes: public IntegrationPlugin, public ZigbeeHandler +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeeremotes.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginZigbeeRemotes(); + + 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 thingRemoved(Thing *thing) override; + +private: + QHash m_thingNodes; + + void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + + void bindPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void bindOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void bindLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void bindScenesCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + + void connectToPowerConfigurationCluster(Thing *thing, ZigbeeNodeEndpoint *endpoint); +}; + +#endif // INTEGRATIONPLUGINZIGBEEREMOTES_H diff --git a/zigbeeremotes/integrationpluginzigbeeremotes.json b/zigbeeremotes/integrationpluginzigbeeremotes.json new file mode 100644 index 00000000..aa2255f6 --- /dev/null +++ b/zigbeeremotes/integrationpluginzigbeeremotes.json @@ -0,0 +1,75 @@ +{ + "name": "ZigbeeRemotes", + "displayName": "Zigbee remotes", + "id": "d0184b49-6e6d-42fe-92e1-e2ede67ca7df", + "vendors": [ + { + "name": "jung", + "displayName": "JUNG GmbH", + "id": "521ade0c-fad5-49a9-99f5-e2bc124c01a5", + "thingClasses": [ + { + "id": "f81ad8c0-29e3-4284-ad3a-393e11c2cc37", + "name": "insta", + "displayName": "JUNG ZLL 5004", + "createMethods": ["auto"], + "interfaces": ["multibutton", "wirelessconnectable"], + "paramTypes": [ + { + "id": "93da9fd8-9068-476c-8ff3-53fe51a11808", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "4b425f78-0300-49bc-8c89-cdf651940da4", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "bf6d6426-a967-4084-86bf-4edd2d074316", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected or disconnected", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "3859cc17-b534-4e00-be3b-6fbdb67ed0fe", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "type": "uint", + "minValue": 0, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": 0 + } + ], + "eventTypes": [ + { + "id": "407d2e3e-4aca-4021-a79b-4b896e9c2440", + "name": "pressed", + "displayName": "Pressed", + "paramTypes": [ + { + "id": "135a1871-ea74-45fd-ac26-5d8ca21586a3", + "name": "buttonName", + "displayName": "Button", + "type": "QString", + "allowedValues": ["OFF", "ON", "+", "-", "1", "2", "3", "4", "5", "6"] + } + ] + } + ] + } + ] + } + ] +} diff --git a/zigbeeremotes/meta.json b/zigbeeremotes/meta.json new file mode 100644 index 00000000..c3b0679f --- /dev/null +++ b/zigbeeremotes/meta.json @@ -0,0 +1,13 @@ +{ + "title": "ZigBee remotes", + "tagline": "A plugin for various zigbee remotes.", + "icon": "zigbee.svg", + "stability": "consumer", + "offline": true, + "technologies": [ + "zigbee" + ], + "categories": [ + "socket" + ] +} diff --git a/zigbeeremotes/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeeremotes/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts new file mode 100644 index 00000000..0e6677f2 --- /dev/null +++ b/zigbeeremotes/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts @@ -0,0 +1,79 @@ + + + + + ZigbeeRemotes + + + Button + The name of the ParamType (ThingClass: insta, EventType: pressed, ID: {135a1871-ea74-45fd-ac26-5d8ca21586a3}) + + + + + + Connected + The name of the ParamType (ThingClass: insta, EventType: connected, ID: {bf6d6426-a967-4084-86bf-4edd2d074316}) +---------- +The name of the StateType ({bf6d6426-a967-4084-86bf-4edd2d074316}) of ThingClass insta + + + + + Connected or disconnected + The name of the EventType ({bf6d6426-a967-4084-86bf-4edd2d074316}) of ThingClass insta + + + + + IEEE adress + The name of the ParamType (ThingClass: insta, Type: thing, ID: {93da9fd8-9068-476c-8ff3-53fe51a11808}) + + + + + JUNG ZLL 5004 + The name of the ThingClass ({f81ad8c0-29e3-4284-ad3a-393e11c2cc37}) + + + + + Pressed + The name of the EventType ({407d2e3e-4aca-4021-a79b-4b896e9c2440}) of ThingClass insta + + + + + + Signal strength + The name of the ParamType (ThingClass: insta, EventType: signalStrength, ID: {3859cc17-b534-4e00-be3b-6fbdb67ed0fe}) +---------- +The name of the StateType ({3859cc17-b534-4e00-be3b-6fbdb67ed0fe}) of ThingClass insta + + + + + Signal strength changed + The name of the EventType ({3859cc17-b534-4e00-be3b-6fbdb67ed0fe}) of ThingClass insta + + + + + Zigbee network UUID + The name of the ParamType (ThingClass: insta, Type: thing, ID: {4b425f78-0300-49bc-8c89-cdf651940da4}) + + + + + Zigbee remotes + The name of the plugin ZigbeeRemotes ({d0184b49-6e6d-42fe-92e1-e2ede67ca7df}) + + + + + nymea GmbH + The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6}) + + + + diff --git a/zigbeeremotes/zigbee.svg b/zigbeeremotes/zigbee.svg new file mode 100644 index 00000000..5366feb6 --- /dev/null +++ b/zigbeeremotes/zigbee.svg @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/zigbeeremotes/zigbeeremotes.pro b/zigbeeremotes/zigbeeremotes.pro new file mode 100644 index 00000000..008765b2 --- /dev/null +++ b/zigbeeremotes/zigbeeremotes.pro @@ -0,0 +1,12 @@ +include(../plugins.pri) + +PKGCONFIG += nymea-zigbee + +SOURCES += \ + integrationpluginzigbeeremotes.cpp + +HEADERS += \ + integrationpluginzigbeeremotes.h + + + diff --git a/zigbeetradfri/integrationpluginzigbeetradfri.cpp b/zigbeetradfri/integrationpluginzigbeetradfri.cpp index 3ecad31b..6ec62a74 100644 --- a/zigbeetradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbeetradfri/integrationpluginzigbeetradfri.cpp @@ -339,8 +339,8 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } }); - connect(levelCluster, &ZigbeeClusterLevelControl::commandMoveSent, thing, [=](ZigbeeClusterLevelControl::MoveMode moveMode, quint8 rate){ - qCDebug(dcZigbeeTradfri()) << "level command move received" << moveMode << rate; + connect(levelCluster, &ZigbeeClusterLevelControl::commandMoveSent, thing, [=](bool withOnOff, ZigbeeClusterLevelControl::MoveMode moveMode, quint8 rate){ + qCDebug(dcZigbeeTradfri()) << "level command move received" << withOnOff << moveMode << rate; switch (moveMode) { case ZigbeeClusterLevelControl::MoveModeUp: qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up"; @@ -353,14 +353,14 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } }); - 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: + connect(levelCluster, &ZigbeeClusterLevelControl::commandStepSent, thing, [=](bool withOnOff, ZigbeeClusterLevelControl::StepMode stepMode, quint8 stepSize, quint16 transitionTime){ + qCDebug(dcZigbeeTradfri()) << "level command step received" << withOnOff << stepMode << stepSize << transitionTime; + switch (stepMode) { + case ZigbeeClusterLevelControl::StepModeUp: qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up"; emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up"))); break; - case ZigbeeClusterLevelControl::FadeModeDown: + case ZigbeeClusterLevelControl::StepModeDown: qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Down"; emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Down"))); break; @@ -373,31 +373,26 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) 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; + connect(scenesCluster, &ZigbeeClusterScenes::commandSent, thing, [=](ZigbeeClusterScenes::Command command, quint16 groupId, quint8 sceneId){ + qCDebug(dcZigbeeTradfri()) << thing << "scene command received" << command << groupId << sceneId; - switch (command) { // Note: these comands are not in the specs - case 0x07: - if (payload.at(0) == 0x00) { + if (command == 0x07) { + if (groupId == 0x00) { qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Right"; emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Right"))); - } else if (payload.at(0) == 0x01) { + } else if (groupId == 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) { + } else if (command == 0x08) { + if (groupId == 0x00) { qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Right"; emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Right"))); - } else if (payload.at(0) == 0x01) { + } else if (groupId == 0x01) { qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Left"; emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Left"))); } - break; } }); }