Merge PR #506: New Plugin: Zigbee remotes

master
Jenkins nymea 2021-12-15 11:53:12 +01:00
commit 915fbb077d
13 changed files with 786 additions and 41 deletions

41
debian/control vendored
View File

@ -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},

View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeeremotes.so

View File

@ -80,6 +80,7 @@ PLUGIN_DIRS = \
zigbeegenericlights \
zigbeelumi \
zigbeephilipshue \
zigbeeremotes \
zigbeetradfri \

View File

@ -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;

17
zigbeeremotes/README.md Normal file
View File

@ -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.

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginzigbeeremotes.h"
#include "plugininfo.h"
#include "hardware/zigbee/zigbeehardwareresource.h"
#include <QDebug>
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<ZigbeeClusterOnOff>(ZigbeeClusterLibrary::ClusterIdOnOff);
ZigbeeClusterLevelControl *levelControlCluster = endpoint->outputCluster<ZigbeeClusterLevelControl>(ZigbeeClusterLibrary::ClusterIdLevelControl);
ZigbeeClusterScenes *scenesCluster = endpoint->outputCluster<ZigbeeClusterScenes>(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 &parameters){
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<quint8>(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<ZigbeeClusterPowerConfiguration>(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));
});
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINZIGBEEREMOTES_H
#define INTEGRATIONPLUGINZIGBEEREMOTES_H
#include "integrations/integrationplugin.h"
#include "hardware/zigbee/zigbeehandler.h"
#include "plugintimer.h"
#include "extern-plugininfo.h"
#include <QTimer>
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<Thing*, ZigbeeNode*> 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

View File

@ -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"]
}
]
}
]
}
]
}
]
}

13
zigbeeremotes/meta.json Normal file
View File

@ -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"
]
}

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>ZigbeeRemotes</name>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="38"/>
<source>Button</source>
<extracomment>The name of the ParamType (ThingClass: insta, EventType: pressed, ID: {135a1871-ea74-45fd-ac26-5d8ca21586a3})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="41"/>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="44"/>
<source>Connected</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="47"/>
<source>Connected or disconnected</source>
<extracomment>The name of the EventType ({bf6d6426-a967-4084-86bf-4edd2d074316}) of ThingClass insta</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="50"/>
<source>IEEE adress</source>
<extracomment>The name of the ParamType (ThingClass: insta, Type: thing, ID: {93da9fd8-9068-476c-8ff3-53fe51a11808})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="53"/>
<source>JUNG ZLL 5004</source>
<extracomment>The name of the ThingClass ({f81ad8c0-29e3-4284-ad3a-393e11c2cc37})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="56"/>
<source>Pressed</source>
<extracomment>The name of the EventType ({407d2e3e-4aca-4021-a79b-4b896e9c2440}) of ThingClass insta</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="59"/>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="62"/>
<source>Signal strength</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="65"/>
<source>Signal strength changed</source>
<extracomment>The name of the EventType ({3859cc17-b534-4e00-be3b-6fbdb67ed0fe}) of ThingClass insta</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="68"/>
<source>Zigbee network UUID</source>
<extracomment>The name of the ParamType (ThingClass: insta, Type: thing, ID: {4b425f78-0300-49bc-8c89-cdf651940da4})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="71"/>
<source>Zigbee remotes</source>
<extracomment>The name of the plugin ZigbeeRemotes ({d0184b49-6e6d-42fe-92e1-e2ede67ca7df})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/zigbeeremotes/plugininfo.h" line="74"/>
<source>nymea GmbH</source>
<extracomment>The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

179
zigbeeremotes/zigbee.svg Normal file
View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
viewBox="0 0 96 96.000001"
sodipodi:docname="zigbee_.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8.7812489"
inkscape:cx="10.528112"
inkscape:cy="41.064759"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="2880"
inkscape:window-height="1663"
inkscape:window-x="0"
inkscape:window-y="76"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080"
inkscape:locked="false" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170"
inkscape:locked="false" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<path
inkscape:connector-curvature="0"
d="m 418.41067,377.27822 c -2.47698,-9.19 -6.3325,-9.76 -10.1,-11.048 -5.65022,5.45 -41.7365,40.432 -41.7365,40.432 0,0 -2.35494,-21.802 2.0068,-46.974 -0.058,0.002 -0.096,0.004 -0.152,0.006 6.22646,-3.99 13.61138,-6.332 21.55452,-6.332 22.10074,0 40.01582,17.908 40.01582,40 0,11.268 -4.67784,21.43 -12.17882,28.7 1.92676,-11.678 3.36934,-34.48 0.59024,-44.784 z"
id="path2"
style="fill:#808080;fill-opacity:1;stroke-width:2.00039554" />
<path
inkscape:connector-curvature="0"
d="m 363.40291,411.91422 c 0.69428,3.964 4.2637,8.424 10.6082,9.414 2.47898,-2.874 41.03022,-39.988 41.03022,-39.988 0.84834,14.016 0.93038,29.138 -1.72868,44.474 -6.5726,4.732 -14.61378,7.548 -23.32922,7.548 -22.10074,0 -40.01582,-17.908 -40.01582,-40 0,-12.346 5.6022,-23.378 14.39568,-30.716 -1.05042,7.744 -3.92954,32.202 -0.96038,49.268 z"
id="path4"
style="fill:#808080;fill-opacity:1;stroke-width:2.00039554" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,12 @@
include(../plugins.pri)
PKGCONFIG += nymea-zigbee
SOURCES += \
integrationpluginzigbeeremotes.cpp
HEADERS += \
integrationpluginzigbeeremotes.h

View File

@ -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;
}
});
}