From 5b533ab77f9ff0f72d19f99b2c5b289c9d602e4e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 4 Nov 2020 14:25:10 +0100 Subject: [PATCH 01/47] WIP: Add zigbee plugins --- nymea-plugins.pro | 2 + .../integrationpluginzigbee-generic.json | 14 + .../integrationpluginzigbeegeneric.cpp | 100 ++++ .../integrationpluginzigbeegeneric.h | 61 +++ zigbee-generic/zigbee-generic.pro | 12 + zigbee-lumi/integrationpluginzigbee-lumi.json | 434 ++++++++++++++++++ zigbee-lumi/integrationpluginzigbeelumi.cpp | 286 ++++++++++++ zigbee-lumi/integrationpluginzigbeelumi.h | 68 +++ zigbee-lumi/zigbee-lumi.pro | 12 + 9 files changed, 989 insertions(+) create mode 100644 zigbee-generic/integrationpluginzigbee-generic.json create mode 100644 zigbee-generic/integrationpluginzigbeegeneric.cpp create mode 100644 zigbee-generic/integrationpluginzigbeegeneric.h create mode 100644 zigbee-generic/zigbee-generic.pro create mode 100644 zigbee-lumi/integrationpluginzigbee-lumi.json create mode 100644 zigbee-lumi/integrationpluginzigbeelumi.cpp create mode 100644 zigbee-lumi/integrationpluginzigbeelumi.h create mode 100644 zigbee-lumi/zigbee-lumi.pro diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 1194d487..5c1f18c6 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -64,6 +64,8 @@ PLUGIN_DIRS = \ wakeonlan \ wemo \ ws2812fx \ + zigbee-generic \ + zigbee-lumi \ message(============================================) diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json new file mode 100644 index 00000000..b5a16145 --- /dev/null +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -0,0 +1,14 @@ +{ + "name": "ZigbeeGeneric", + "displayName": "Zigbee Generic", + "id": "6a4343be-9fd6-4015-9ff5-38542651c534", + "vendors": [ + { + "name": "nymea", + "displayName": "nymea", + "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6", + "thingClasses": [ + ] + } + ] +} diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp new file mode 100644 index 00000000..08921540 --- /dev/null +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -0,0 +1,100 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* +* 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 + +IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric() +{ + +} + +QString IntegrationPluginZigbeeGeneric::name() const +{ + return "Generic"; +} + +bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &networkUuid) +{ + foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { + qCDebug(dcZigbeeGeneric) << "Endpoint profile:" << endpoint->profile() << endpoint->deviceId() << networkUuid; +// if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOff +// (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOf> + +// // Create generic power socket +// qCDebug(dcZigbee()) << "This device is an power socket"; +// if (myThings().filterByThingClassId(genericPowerSocketThingClassId) +// .filterByParam(genericPowerSocketThingIeeeAddressParamTypeId, node->extendedAddress().toString()) +// .isEmpty()) { +// qCDebug(dcZigbee()) << "Adding new generic power socket"; +// ThingDescriptor descriptor(genericPowerSocketThingClassId); +// QString deviceClassName = supportedThings().findById(genericPowerSocketThingClassId).displayName(); +// descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier())); +// ParamList params; +// params.append(Param(genericPowerSocketThingIeeeAddressParamTypeId, node->extendedAddress().toString())); +// params.append(Param(genericPowerSocketThingManufacturerParamTypeId, endpoint->manufacturerName())); +// params.append(Param(genericPowerSocketThingModelParamTypeId, endpoint->modelIdentifier())); +// descriptor.setParams(params); +// descriptor.setParentId(networkManagerDevice->id()); +// emit autoThingsAppeared({descriptor}); +// } else { +// qCDebug(dcZigbee()) << "The device for this node has already been created."; +// } +// return true; +// } + } + + + return false; +} + +void IntegrationPluginZigbeeGeneric::init() +{ + hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeCatchAll); +} + +void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) +{ + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginZigbeeGeneric::executeAction(ThingActionInfo *info) +{ + info->finish(Thing::ThingErrorUnsupportedFeature); +} + +void IntegrationPluginZigbeeGeneric::thingRemoved(Thing *thing) +{ + Q_UNUSED(thing) +} diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h new file mode 100644 index 00000000..a678b50a --- /dev/null +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginzigbee-generic.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginZigbeeGeneric(); + + QString name() const override; + bool handleNode(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: +}; + +#endif // INTEGRATIONPLUGINZIGBEEGENERIC_H diff --git a/zigbee-generic/zigbee-generic.pro b/zigbee-generic/zigbee-generic.pro new file mode 100644 index 00000000..23192648 --- /dev/null +++ b/zigbee-generic/zigbee-generic.pro @@ -0,0 +1,12 @@ +include(../plugins.pri) + +PKGCONFIG += nymea-zigbee + +SOURCES += \ + integrationpluginzigbeegeneric.cpp + +HEADERS += \ + integrationpluginzigbeegeneric.h + + + diff --git a/zigbee-lumi/integrationpluginzigbee-lumi.json b/zigbee-lumi/integrationpluginzigbee-lumi.json new file mode 100644 index 00000000..927efef4 --- /dev/null +++ b/zigbee-lumi/integrationpluginzigbee-lumi.json @@ -0,0 +1,434 @@ +{ + "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": [ + { + "id": "ce853c00-d175-45bc-a3d9-3c8c93d88099", + "name": "removeFromNetwork", + "displayName": "Remove from network" + }, + { + "id": "4a9d9427-52cf-4c13-9b71-496145b18476", + "name": "test1", + "displayName": "Test 1" + } + ], + "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 + } + ], + "actionTypes": [ + { + "id": "2bae0e2d-0c20-475d-a6cd-2e685e6b0626", + "name": "removeFromNetwork", + "displayName": "Remove from network" + } + ], + "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": [ + { + "id": "65b43aad-1784-4e85-ad5a-ce01cc5757bd", + "name": "removeFromNetwork", + "displayName": "Remove from network" + } + ], + "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 + }, + { + "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": [ + { + "id": "2797c499-ca7f-4cf6-a732-c8e3cd580e56", + "name": "removeFromNetwork", + "displayName": "Remove from network" + } + ], + "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 + }, + { + "id": "72aa617c-88cc-4992-a1a2-400b5e19106e", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "ed085bda-5608-419d-88e2-81ebcb9f26e1", + "name": "removeFromNetwork", + "displayName": "Remove from network" + } + ], + "eventTypes": [ + + ] + } + ] + } + ] +} diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp new file mode 100644 index 00000000..4a019298 --- /dev/null +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -0,0 +1,286 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* +* 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_zigbeeAddressParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingIeeeAddressParamTypeId; + + m_connectedStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorConnectedStateTypeId; + + m_signalStrengthStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorSignalStrengthStateTypeId; + +} + +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, so no enum here + if (node->nodeDescriptor().manufacturerCode != 0x1037) { + return false; + } + + 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)) { + qCWarning(dcZigbeeLumi()) << "This lumi device does not have the basic input cluster yet."; + continue; + } + + QHash knownLumiDevices; + knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId); + knownLumiDevices.insert("lumi.sensor_magnet", lumiMagnetSensorThingClassId); + knownLumiDevices.insert("lumi.sensor_switch", lumiButtonSensorThingClassId); + knownLumiDevices.insert("lumi.sensor_motion", lumiMotionSensorThingClassId); + knownLumiDevices.insert("lumi.sensor_water", lumiWaterSensorThingClassId); + + ThingClassId thingClassId; + foreach (const QString &knownLumi, knownLumiDevices.keys()) { + if (endpoint->modelIdentifier().startsWith(knownLumi)) { + thingClassId = 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::init() +{ + hardwareManager()->zigbeeResource()->registerHandler(this); +} + +void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) +{ + if (!hardwareManager()->zigbeeResource()->available()) { + qCWarning(dcZigbeeLumi()) << "Zigbee is not available. Not setting up" << info->thing()->name(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + Thing *thing = info->thing(); + QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); + qCDebug(dcZigbeeLumi()) << "Nework uuid:" << networkUuid; + ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_zigbeeAddressParamTypeIds.value(thing->thingClassId())).toString()); + ZigbeeNode *node = hardwareManager()->zigbeeResource()->getNode(networkUuid, zigbeeAddress); + if (!node) { + qCWarning(dcZigbeeLumi()) << "Zigbee node for" << info->thing()->name() << "not found.´"; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + if (!endpoint) { + qCWarning(dcZigbeeLumi()) << "Zigbee endpoint 1 not found on" << thing->name(); + info->finish(Thing::ThingErrorSetupFailed); + return; + } + + // Update connected state + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), hardwareManager()->zigbeeResource()->networkState(networkUuid) == ZigbeeNetwork::StateRunning); + connect(hardwareManager()->zigbeeResource(), &ZigbeeHardwareResource::networkStateChanged, thing, [thing, this](const QUuid &networkUuid, ZigbeeNetwork::State state){ + if (thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid() == networkUuid) { + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), state == ZigbeeNetwork::StateRunning); + } + }); + + // 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); + }); + + + if (thing->thingClassId() == lumiMagnetSensorThingClassId) { + ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); + if (onOffCluster) { + thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !onOffCluster->powered()); + 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) { + thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancyCluster->occupancy()); + connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [thing](bool occupancy){ + qCDebug(dcZigbeeLumi()) << "occupancy changed" << occupancy; + thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancy); + 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) { + thing->setStateValue(lumiHTSensorTemperatureStateTypeId, illuminanceCluster->illuminance()); + connect(illuminanceCluster, &ZigbeeClusterIlluminanceMeasurment::illuminanceChanged, thing, [thing](quint16 illuminance){ + thing->setStateValue(lumiMotionSensorLightIntensityStateTypeId, illuminance); + }); + } else { + qCWarning(dcZigbeeLumi()) << "Illuminance cluster not found on" << thing->name(); + } + } + + if (thing->thingClassId() == lumiHTSensorThingClassId) { + ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); + if (temperatureCluster) { + thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperatureCluster->temperature()); + connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){ + 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) { + thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidityCluster->humidity()); + connect(humidityCluster, &ZigbeeClusterRelativeHumidityMeasurement::humidityChanged, thing, [thing](double humidity){ + thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidity); + }); + } else { + qCWarning(dcZigbeeLumi()) << "Could not find the relative humidity measurement server cluster on" << thing << endpoint; + } + } + + info->finish(Thing::ThingErrorNoError); + + + +// if (thing->thingClassId() == lumiButtonSensorThingClassId) { +// qCDebug(dcZigbee()) << "Lumi button sensor" << thing; +// ZigbeeAddress ieeeAddress(thing->paramValue(lumiButtonSensorThingIeeeAddressParamTypeId).toString()); +// ZigbeeNetwork *network = findParentNetwork(thing); +// LumiButtonSensor *sensor = new LumiButtonSensor(network, ieeeAddress, thing, this); +// connect(sensor, &LumiButtonSensor::buttonPressed, this, [this, thing](){ +// qCDebug(dcZigbee()) << thing << "clicked event"; +// emit emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); +// }); +// connect(sensor, &LumiButtonSensor::buttonLongPressed, this, [this, thing](){ +// qCDebug(dcZigbee()) << thing << "long pressed event"; +// emit emitEvent(Event(lumiButtonSensorLongPressedEventTypeId, thing->id())); +// }); + +// m_zigbeeDevices.insert(thing, sensor); +// info->finish(Thing::ThingErrorNoError); +// return; +// } + + +// if (thing->thingClassId() == lumiWaterSensorThingClassId) { +// qCDebug(dcZigbee()) << "Lumi water sensor" << thing; +// ZigbeeAddress ieeeAddress(thing->paramValue(lumiWaterSensorThingIeeeAddressParamTypeId).toString()); +// ZigbeeNetwork *network = findParentNetwork(thing); +// LumiWaterSensor *sensor = new LumiWaterSensor(network, ieeeAddress, thing, this); +// m_zigbeeDevices.insert(thing, sensor); +// info->finish(Thing::ThingErrorNoError); +// return; +// } + +// info->finish(Thing::ThingErrorThingClassNotFound); +} + +void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info) +{ + info->finish(Thing::ThingErrorUnsupportedFeature); +} + +void IntegrationPluginZigbeeLumi::thingRemoved(Thing *thing) +{ + Q_UNUSED(thing) +} diff --git a/zigbee-lumi/integrationpluginzigbeelumi.h b/zigbee-lumi/integrationpluginzigbeelumi.h new file mode 100644 index 00000000..e2fed597 --- /dev/null +++ b/zigbee-lumi/integrationpluginzigbeelumi.h @@ -0,0 +1,68 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginzigbee-lumi.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginZigbeeLumi(); + + QString name() const override; + bool handleNode(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; + + PluginTimer *m_presenceTimer = nullptr; +}; + +#endif // INTEGRATIONPLUGINZIGBEELUMI_H diff --git a/zigbee-lumi/zigbee-lumi.pro b/zigbee-lumi/zigbee-lumi.pro new file mode 100644 index 00000000..e4e53c79 --- /dev/null +++ b/zigbee-lumi/zigbee-lumi.pro @@ -0,0 +1,12 @@ +include(../plugins.pri) + +PKGCONFIG += nymea-zigbee + +SOURCES += \ + integrationpluginzigbeelumi.cpp \ + +HEADERS += \ + integrationpluginzigbeelumi.h \ + + + From 3cdc3414da1f2d73578022eb408d4c2fb7ba7845 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 7 Nov 2020 00:27:57 +0100 Subject: [PATCH 02/47] Try add a generic thermostat --- .../integrationpluginzigbee-generic.json | 56 ++++++++++ .../integrationpluginzigbeegeneric.cpp | 100 +++++++++++++----- .../integrationpluginzigbeegeneric.h | 6 ++ zigbee-lumi/integrationpluginzigbeelumi.cpp | 10 +- 4 files changed, 140 insertions(+), 32 deletions(-) diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json index b5a16145..b9fed263 100644 --- a/zigbee-generic/integrationpluginzigbee-generic.json +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -8,6 +8,62 @@ "displayName": "nymea", "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6", "thingClasses": [ + { + "id": "ca9af6cf-2d15-4d54-ba07-3d2ce03445b8", + "name": "thermostat", + "displayName": "Zigbee Thermostat", + "createMethods": ["auto"], + "interfaces": ["thermostat", "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": "" + } + ], + "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": "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" + } + ] + } ] } ] diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 08921540..50ad31cf 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -37,7 +37,13 @@ IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric() { + m_ieeeAddressParamTypeIds[thermostatThingClassId] = thermostatThingIeeeAddressParamTypeId; + m_networkUuidParamTypeIds[thermostatThingClassId] = thermostatThingNetworkUuidParamTypeId; + + m_connectedStateTypeIds[thermostatThingClassId] = thermostatConnectedStateTypeId; + + m_signalStrengthStateTypeIds[thermostatThingClassId] = thermostatSignalStrengthStateTypeId; } QString IntegrationPluginZigbeeGeneric::name() const @@ -47,36 +53,29 @@ QString IntegrationPluginZigbeeGeneric::name() const bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &networkUuid) { - foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { - qCDebug(dcZigbeeGeneric) << "Endpoint profile:" << endpoint->profile() << endpoint->deviceId() << networkUuid; -// if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOff -// (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOf> + qCDebug(dcZigbeeGeneric()) << "handleNode called for:" << node; -// // Create generic power socket -// qCDebug(dcZigbee()) << "This device is an power socket"; -// if (myThings().filterByThingClassId(genericPowerSocketThingClassId) -// .filterByParam(genericPowerSocketThingIeeeAddressParamTypeId, node->extendedAddress().toString()) -// .isEmpty()) { -// qCDebug(dcZigbee()) << "Adding new generic power socket"; -// ThingDescriptor descriptor(genericPowerSocketThingClassId); -// QString deviceClassName = supportedThings().findById(genericPowerSocketThingClassId).displayName(); -// descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier())); -// ParamList params; -// params.append(Param(genericPowerSocketThingIeeeAddressParamTypeId, node->extendedAddress().toString())); -// params.append(Param(genericPowerSocketThingManufacturerParamTypeId, endpoint->manufacturerName())); -// params.append(Param(genericPowerSocketThingModelParamTypeId, endpoint->modelIdentifier())); -// descriptor.setParams(params); -// descriptor.setParentId(networkManagerDevice->id()); -// emit autoThingsAppeared({descriptor}); -// } else { -// qCDebug(dcZigbee()) << "The device for this node has already been created."; -// } -// return true; -// } + QHash devicesThingClassIdsMap; + devicesThingClassIdsMap.insert(Zigbee::HomeAutomationDeviceThermostat, thermostatThingClassId); + + bool handled = false; + + foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { + + if (devicesThingClassIdsMap.contains(endpoint->deviceId())) { + ThingClassId thingClassId = devicesThingClassIdsMap.value(endpoint->deviceId()); + ThingDescriptor descriptor(thingClassId, endpoint->modelIdentifier(), endpoint->manufacturerName()); + ParamList params; + params << Param(m_ieeeAddressParamTypeIds.value(thingClassId), node->extendedAddress().toString()); + params << Param(m_networkUuidParamTypeIds.value(thingClassId), networkUuid.toString()); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + + handled = true; + } } - - return false; + return handled; } void IntegrationPluginZigbeeGeneric::init() @@ -86,6 +85,53 @@ void IntegrationPluginZigbeeGeneric::init() void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) { + if (!hardwareManager()->zigbeeResource()->available()) { + qCWarning(dcZigbeeGeneric()) << "Zigbee is not available. Not setting up" << info->thing()->name(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + 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 = hardwareManager()->zigbeeResource()->getNode(networkUuid, zigbeeAddress); + if (!node) { + qCWarning(dcZigbeeGeneric()) << "Zigbee node for" << info->thing()->name() << "not found.´"; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + if (!endpoint) { + qCWarning(dcZigbeeGeneric()) << "Zigbee endpoint 1 not found on" << thing->name(); + info->finish(Thing::ThingErrorSetupFailed); + return; + } + + // Update connected state + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), hardwareManager()->zigbeeResource()->networkState(networkUuid) == ZigbeeNetwork::StateRunning); + connect(hardwareManager()->zigbeeResource(), &ZigbeeHardwareResource::networkStateChanged, thing, [thing, this](const QUuid &networkUuid, ZigbeeNetwork::State state){ + if (thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid() == networkUuid) { + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), state == ZigbeeNetwork::StateRunning); + } + }); + + // 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); + }); + + // Type specific setup + if (thing->thingClassId() == thermostatThingClassId) { + // TODO: Thermostat cluster is missing + // ZigbeeClusterThermostat *thermostatCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdThermostat); + } + + + info->finish(Thing::ThingErrorNoError); } diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h index a678b50a..feb7f821 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -56,6 +56,12 @@ public: void thingRemoved(Thing *thing) override; private: + + QHash m_ieeeAddressParamTypeIds; + QHash m_networkUuidParamTypeIds; + + QHash m_connectedStateTypeIds; + QHash m_signalStrengthStateTypeIds; }; #endif // INTEGRATIONPLUGINZIGBEEGENERIC_H diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index 4a019298..62991ccb 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -167,7 +167,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == lumiMagnetSensorThingClassId) { ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (onOffCluster) { - thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !onOffCluster->powered()); +// thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !onOffCluster->powered()); connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){ qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "closed" : "open"); thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !power); @@ -180,7 +180,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == lumiMotionSensorThingClassId) { ZigbeeClusterOccupancySensing *occupancyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing); if (occupancyCluster) { - thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancyCluster->occupancy()); +// thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancyCluster->occupancy()); connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [thing](bool occupancy){ qCDebug(dcZigbeeLumi()) << "occupancy changed" << occupancy; thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancy); @@ -207,7 +207,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) ZigbeeClusterIlluminanceMeasurment *illuminanceCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement); if (illuminanceCluster) { - thing->setStateValue(lumiHTSensorTemperatureStateTypeId, illuminanceCluster->illuminance()); +// thing->setStateValue(lumiHTSensorTemperatureStateTypeId, illuminanceCluster->illuminance()); connect(illuminanceCluster, &ZigbeeClusterIlluminanceMeasurment::illuminanceChanged, thing, [thing](quint16 illuminance){ thing->setStateValue(lumiMotionSensorLightIntensityStateTypeId, illuminance); }); @@ -219,7 +219,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == lumiHTSensorThingClassId) { ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); if (temperatureCluster) { - thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperatureCluster->temperature()); +// thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperatureCluster->temperature()); connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){ thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperature); }); @@ -229,7 +229,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) ZigbeeClusterRelativeHumidityMeasurement *humidityCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement); if (humidityCluster) { - thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidityCluster->humidity()); +// thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidityCluster->humidity()); connect(humidityCluster, &ZigbeeClusterRelativeHumidityMeasurement::humidityChanged, thing, [thing](double humidity){ thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidity); }); From 1748cdaafe08fa5fa1bcc5cfe7e65b125f0d660f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 12 Nov 2020 13:26:11 +0100 Subject: [PATCH 03/47] Implement and improve lumi zigbee sensors according to library changes --- zigbee-lumi/integrationpluginzigbee-lumi.json | 181 +++++++++-- zigbee-lumi/integrationpluginzigbeelumi.cpp | 280 ++++++++++++++---- zigbee-lumi/integrationpluginzigbeelumi.h | 6 + 3 files changed, 385 insertions(+), 82 deletions(-) diff --git a/zigbee-lumi/integrationpluginzigbee-lumi.json b/zigbee-lumi/integrationpluginzigbee-lumi.json index 927efef4..3c8f0e55 100644 --- a/zigbee-lumi/integrationpluginzigbee-lumi.json +++ b/zigbee-lumi/integrationpluginzigbee-lumi.json @@ -83,16 +83,97 @@ } ], "actionTypes": [ + + ], + "eventTypes": [ + + ] + }, + { + "name": "lumiWeatherSensor", + "displayName": "Weather sensor", + "id": "0b582616-0b05-4ac9-8b59-51b66079b571", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "pressuresensor", "temperaturesensor", "humiditysensor", "wirelessconnectable" ], + "paramTypes": [ { - "id": "ce853c00-d175-45bc-a3d9-3c8c93d88099", - "name": "removeFromNetwork", - "displayName": "Remove from network" + "id": "ef1129e2-bdf8-423e-9c11-fa8b406f960b", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" }, { - "id": "4a9d9427-52cf-4c13-9b71-496145b18476", - "name": "test1", - "displayName": "Test 1" + "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": [ @@ -161,11 +242,7 @@ } ], "actionTypes": [ - { - "id": "2bae0e2d-0c20-475d-a6cd-2e685e6b0626", - "name": "removeFromNetwork", - "displayName": "Remove from network" - } + ], "eventTypes": [ @@ -226,11 +303,6 @@ } ], "actionTypes": [ - { - "id": "65b43aad-1784-4e85-ad5a-ce01cc5757bd", - "name": "removeFromNetwork", - "displayName": "Remove from network" - } ], "eventTypes": [ { @@ -337,11 +409,7 @@ } ], "actionTypes": [ - { - "id": "2797c499-ca7f-4cf6-a732-c8e3cd580e56", - "name": "removeFromNetwork", - "displayName": "Remove from network" - } + ], "eventTypes": [ @@ -418,15 +486,76 @@ } ], "actionTypes": [ - { - "id": "ed085bda-5608-419d-88e2-81ebcb9f26e1", - "name": "removeFromNetwork", - "displayName": "Remove from network" - } + ], "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" + } + ] } ] } diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index 62991ccb..af152394 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -44,25 +44,49 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_networkUuidParamTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingNetworkUuidParamTypeId; m_zigbeeAddressParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingIeeeAddressParamTypeId; m_connectedStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorConnectedStateTypeId; + + m_versionStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorVersionStateTypeId; + m_versionStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorVersionStateTypeId; + m_versionStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorVersionStateTypeId; + m_versionStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorVersionStateTypeId; + m_versionStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorVersionStateTypeId; + m_versionStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorVersionStateTypeId; + m_versionStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorVersionStateTypeId; m_signalStrengthStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiMagnetSensorThingClassId] = lumiMagnetSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiMotionSensorThingClassId] = lumiMotionSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorSignalStrengthStateTypeId; + // Known model identifier + m_knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId); + m_knownLumiDevices.insert("lumi.sensor_magnet", lumiMagnetSensorThingClassId); + m_knownLumiDevices.insert("lumi.sensor_switch", lumiButtonSensorThingClassId); + 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); } QString IntegrationPluginZigbeeLumi::name() const @@ -73,29 +97,25 @@ QString IntegrationPluginZigbeeLumi::name() const bool IntegrationPluginZigbeeLumi::handleNode(ZigbeeNode *node, const QUuid &networkUuid) { // Check if this is Lumi - // Note: Lumi / Xiaomi / Aquara devices are not in the specs, so no enum here - if (node->nodeDescriptor().manufacturerCode != 0x1037) { - return false; - } - + // 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 manufacturer + // name for matching since that has shown to be most constant 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)) { - qCWarning(dcZigbeeLumi()) << "This lumi device does not have the basic input cluster yet."; + qCDebug(dcZigbeeLumi()) << "This lumi endpoint does not have the basic input cluster, so we skipp it" << endpoint; continue; } - QHash knownLumiDevices; - knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId); - knownLumiDevices.insert("lumi.sensor_magnet", lumiMagnetSensorThingClassId); - knownLumiDevices.insert("lumi.sensor_switch", lumiButtonSensorThingClassId); - knownLumiDevices.insert("lumi.sensor_motion", lumiMotionSensorThingClassId); - knownLumiDevices.insert("lumi.sensor_water", lumiWaterSensorThingClassId); + // Basic cluster exists, so we should have the manufacturer name + if (!endpoint->manufacturerName().toLower().startsWith("lumi")) { + return false; + } ThingClassId thingClassId; - foreach (const QString &knownLumi, knownLumiDevices.keys()) { + foreach (const QString &knownLumi, m_knownLumiDevices.keys()) { if (endpoint->modelIdentifier().startsWith(knownLumi)) { - thingClassId = knownLumiDevices.value(knownLumi); + thingClassId = m_knownLumiDevices.value(knownLumi); break; } } @@ -117,6 +137,18 @@ bool IntegrationPluginZigbeeLumi::handleNode(ZigbeeNode *node, const QUuid &netw return false; } +void IntegrationPluginZigbeeLumi::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) +{ + Q_UNUSED(networkUuid) + + if (m_nodeThings.values().contains(node)) { + Thing *thing = m_nodeThings.key(node); + qCDebug(dcZigbeeLumi()) << "Node for" << thing << node << "has left the network. Removing thing"; + m_nodeThings.remove(thing); + emit autoThingDisappeared(thing->id()); + } +} + void IntegrationPluginZigbeeLumi::init() { hardwareManager()->zigbeeResource()->registerHandler(this); @@ -124,15 +156,11 @@ void IntegrationPluginZigbeeLumi::init() void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) { - if (!hardwareManager()->zigbeeResource()->available()) { - qCWarning(dcZigbeeLumi()) << "Zigbee is not available. Not setting up" << info->thing()->name(); - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } Thing *thing = info->thing(); QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); qCDebug(dcZigbeeLumi()) << "Nework uuid:" << networkUuid; ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_zigbeeAddressParamTypeIds.value(thing->thingClassId())).toString()); + ZigbeeNode *node = hardwareManager()->zigbeeResource()->getNode(networkUuid, zigbeeAddress); if (!node) { qCWarning(dcZigbeeLumi()) << "Zigbee node for" << info->thing()->name() << "not found.´"; @@ -140,6 +168,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) 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->name(); @@ -147,6 +176,9 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) return; } + m_nodeThings.insert(thing, node); + + // General signals and states // Update connected state thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), hardwareManager()->zigbeeResource()->networkState(networkUuid) == ZigbeeNetwork::StateRunning); connect(hardwareManager()->zigbeeResource(), &ZigbeeHardwareResource::networkStateChanged, thing, [thing, this](const QUuid &networkUuid, ZigbeeNetwork::State state){ @@ -163,11 +195,18 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength); }); + // Set the version + thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId()); + if (thing->thingClassId() == lumiMagnetSensorThingClassId) { ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (onOffCluster) { -// thing->setStateValue(lumiMagnetSensorClosedStateTypeId, !onOffCluster->powered()); + // 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); @@ -177,13 +216,23 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } } + if (thing->thingClassId() == lumiMotionSensorThingClassId) { ZigbeeClusterOccupancySensing *occupancyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing); if (occupancyCluster) { -// thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, occupancyCluster->occupancy()); - connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [thing](bool occupancy){ + 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; - thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, 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); }); @@ -200,15 +249,19 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } } }); - } else { qCWarning(dcZigbeeLumi()) << "Occupancy cluster not found on" << thing->name(); } ZigbeeClusterIlluminanceMeasurment *illuminanceCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIlluminanceMeasurement); if (illuminanceCluster) { -// thing->setStateValue(lumiHTSensorTemperatureStateTypeId, illuminanceCluster->illuminance()); + // 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 { @@ -216,11 +269,17 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } } + if (thing->thingClassId() == lumiHTSensorThingClassId) { ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); if (temperatureCluster) { -// thing->setStateValue(lumiHTSensorTemperatureStateTypeId, temperatureCluster->temperature()); + // 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 { @@ -229,8 +288,13 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) ZigbeeClusterRelativeHumidityMeasurement *humidityCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement); if (humidityCluster) { -// thing->setStateValue(lumiHTSensorHumidityStateTypeId, humidityCluster->humidity()); + // 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 { @@ -238,41 +302,141 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } } + + 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())); + } + } + } + }); + } + + info->finish(Thing::ThingErrorNoError); + // if (thing->thingClassId() == lumiButtonSensorThingClassId) { + // qCDebug(dcZigbee()) << "Lumi button sensor" << thing; + // ZigbeeAddress ieeeAddress(thing->paramValue(lumiButtonSensorThingIeeeAddressParamTypeId).toString()); + // ZigbeeNetwork *network = findParentNetwork(thing); + // LumiButtonSensor *sensor = new LumiButtonSensor(network, ieeeAddress, thing, this); + // connect(sensor, &LumiButtonSensor::buttonPressed, this, [this, thing](){ + // qCDebug(dcZigbee()) << thing << "clicked event"; + // emit emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); + // }); + // connect(sensor, &LumiButtonSensor::buttonLongPressed, this, [this, thing](){ + // qCDebug(dcZigbee()) << thing << "long pressed event"; + // emit emitEvent(Event(lumiButtonSensorLongPressedEventTypeId, thing->id())); + // }); -// if (thing->thingClassId() == lumiButtonSensorThingClassId) { -// qCDebug(dcZigbee()) << "Lumi button sensor" << thing; -// ZigbeeAddress ieeeAddress(thing->paramValue(lumiButtonSensorThingIeeeAddressParamTypeId).toString()); -// ZigbeeNetwork *network = findParentNetwork(thing); -// LumiButtonSensor *sensor = new LumiButtonSensor(network, ieeeAddress, thing, this); -// connect(sensor, &LumiButtonSensor::buttonPressed, this, [this, thing](){ -// qCDebug(dcZigbee()) << thing << "clicked event"; -// emit emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); -// }); -// connect(sensor, &LumiButtonSensor::buttonLongPressed, this, [this, thing](){ -// qCDebug(dcZigbee()) << thing << "long pressed event"; -// emit emitEvent(Event(lumiButtonSensorLongPressedEventTypeId, thing->id())); -// }); - -// m_zigbeeDevices.insert(thing, sensor); -// info->finish(Thing::ThingErrorNoError); -// return; -// } + // m_zigbeeDevices.insert(thing, sensor); + // info->finish(Thing::ThingErrorNoError); + // return; + // } -// if (thing->thingClassId() == lumiWaterSensorThingClassId) { -// qCDebug(dcZigbee()) << "Lumi water sensor" << thing; -// ZigbeeAddress ieeeAddress(thing->paramValue(lumiWaterSensorThingIeeeAddressParamTypeId).toString()); -// ZigbeeNetwork *network = findParentNetwork(thing); -// LumiWaterSensor *sensor = new LumiWaterSensor(network, ieeeAddress, thing, this); -// m_zigbeeDevices.insert(thing, sensor); -// info->finish(Thing::ThingErrorNoError); -// return; -// } + // if (thing->thingClassId() == lumiWaterSensorThingClassId) { + // qCDebug(dcZigbee()) << "Lumi water sensor" << thing; + // ZigbeeAddress ieeeAddress(thing->paramValue(lumiWaterSensorThingIeeeAddressParamTypeId).toString()); + // ZigbeeNetwork *network = findParentNetwork(thing); + // LumiWaterSensor *sensor = new LumiWaterSensor(network, ieeeAddress, thing, this); + // m_zigbeeDevices.insert(thing, sensor); + // info->finish(Thing::ThingErrorNoError); + // return; + // } -// info->finish(Thing::ThingErrorThingClassNotFound); + // info->finish(Thing::ThingErrorThingClassNotFound); } void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info) @@ -282,5 +446,9 @@ void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info) void IntegrationPluginZigbeeLumi::thingRemoved(Thing *thing) { - Q_UNUSED(thing) + ZigbeeNode *node = m_nodeThings.take(thing); + if (node) { + QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); + hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node); + } } diff --git a/zigbee-lumi/integrationpluginzigbeelumi.h b/zigbee-lumi/integrationpluginzigbeelumi.h index e2fed597..17670aac 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.h +++ b/zigbee-lumi/integrationpluginzigbeelumi.h @@ -49,6 +49,7 @@ public: 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; @@ -61,6 +62,11 @@ private: QHash m_connectedStateTypeIds; QHash m_signalStrengthStateTypeIds; + QHash m_versionStateTypeIds; + + QHash m_knownLumiDevices; + + QHash m_nodeThings; PluginTimer *m_presenceTimer = nullptr; }; From 07b27c227273a9d4aa91521d2e83c125fdc987f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 12 Nov 2020 14:07:41 +0100 Subject: [PATCH 04/47] Add lumi button sensor --- zigbee-lumi/integrationpluginzigbeelumi.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index af152394..90ae8a13 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -402,6 +402,21 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) }); } + 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 ? "closed" : "open"); + if (!power) { + emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); + } + }); + } else { + qCWarning(dcZigbeeLumi()) << "Could not find the OnOff input cluster on" << thing << endpoint; + } + } + + info->finish(Thing::ThingErrorNoError); From 694f7c8c99232da46470292185338d678b84cba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 12 Nov 2020 15:40:03 +0100 Subject: [PATCH 05/47] Add zigbee debian packages and introduce nymea-plugins-zigbee metapackage --- debian/control | 65 +++++ ...ea-plugin-zigbee-generic-lights.install.in | 1 + debian/nymea-plugin-zigbee-generic.install.in | 1 + debian/nymea-plugin-zigbee-lumi.install.in | 1 + nymea-plugins.pro | 1 + ...ntegrationpluginzigbee-generic-lights.json | 110 ++++++++ .../integrationpluginzigbeegenericlights.cpp | 261 ++++++++++++++++++ .../integrationpluginzigbeegenericlights.h | 71 +++++ .../zigbee-generic-lights.pro | 9 + zigbee-lumi/integrationpluginzigbeelumi.cpp | 32 +-- zigbee-lumi/integrationpluginzigbeelumi.h | 2 +- 11 files changed, 536 insertions(+), 18 deletions(-) create mode 100644 debian/nymea-plugin-zigbee-generic-lights.install.in create mode 100644 debian/nymea-plugin-zigbee-generic.install.in create mode 100644 debian/nymea-plugin-zigbee-lumi.install.in create mode 100644 zigbee-generic-lights/integrationpluginzigbee-generic-lights.json create mode 100644 zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp create mode 100644 zigbee-generic-lights/integrationpluginzigbeegenericlights.h create mode 100644 zigbee-generic-lights/zigbee-generic-lights.pro diff --git a/debian/control b/debian/control index e1975c7a..770c31f1 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, @@ -1013,6 +1014,54 @@ 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-plugins-translations Section: misc Architecture: all @@ -1079,6 +1128,21 @@ 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 +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 @@ -1125,6 +1189,7 @@ Package: nymea-plugins-all Section: libs Architecture: all Depends: nymea-plugins, + nymea-plugins-zigbee, nymea-plugin-simulation, nymea-plugin-snapd, nymea-plugins-maker, 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..c02aec29 --- /dev/null +++ b/debian/nymea-plugin-zigbee-generic-lights.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-generic-lights.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..c01e09e0 --- /dev/null +++ b/debian/nymea-plugin-zigbee-generic.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-generic.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..357171e5 --- /dev/null +++ b/debian/nymea-plugin-zigbee-lumi.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-lumi.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 5c1f18c6..13b45998 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -65,6 +65,7 @@ PLUGIN_DIRS = \ wemo \ ws2812fx \ zigbee-generic \ + zigbee-generic-lights \ zigbee-lumi \ diff --git a/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json b/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json new file mode 100644 index 00000000..b1185b55 --- /dev/null +++ b/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json @@ -0,0 +1,110 @@ +{ + "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": "Available", + "displayNameEvent": "Available 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": [ + + ] + } + ] + } + ] +} diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp new file mode 100644 index 00000000..7b7209ab --- /dev/null +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -0,0 +1,261 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* +* 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 + +IntegrationPluginZigbeeGenericLights::IntegrationPluginZigbeeGenericLights() +{ + m_ieeeAddressParamTypeIds[onOffLightThingClassId] = onOffLightThingIeeeAddressParamTypeId; + + m_networkUuidParamTypeIds[onOffLightThingClassId] = onOffLightThingNetworkUuidParamTypeId; + + m_endpointIdParamTypeIds[onOffLightThingClassId] = onOffLightThingEndpointIdParamTypeId; + + m_connectedStateTypeIds[onOffLightThingClassId] = onOffLightConnectedStateTypeId; + + m_signalStrengthStateTypeIds[onOffLightThingClassId] = onOffLightSignalStrengthStateTypeId; + + m_versionStateTypeIds[onOffLightThingClassId] = onOffLightVersionStateTypeId; +} + +QString IntegrationPluginZigbeeGenericLights::name() const +{ + return "Generic lights"; +} + +bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QUuid &networkUuid) +{ + 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)) { + + ThingDescriptor descriptor(onOffLightThingClassId); + QString deviceClassName = supportedThings().findById(onOffLightThingClassId).displayName(); + descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier())); + qCDebug(dcZigbeeGenericLights()) << "Handeling generic on/off light" << descriptor.title(); + + ParamList params; + params.append(Param(onOffLightThingNetworkUuidParamTypeId, networkUuid.toString())); + params.append(Param(onOffLightThingIeeeAddressParamTypeId, node->extendedAddress().toString())); + params.append(Param(onOffLightThingEndpointIdParamTypeId, endpoint->endpointId())); + params.append(Param(onOffLightThingModelParamTypeId, endpoint->modelIdentifier())); + params.append(Param(onOffLightThingManufacturerParamTypeId, endpoint->manufacturerName())); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + + return true; + } + } + + return false; +} + +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()); + } +} + +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()->getNode(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 + if (thing->thingClassId() == onOffLightThingClassId) { + + 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; + } + + connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){ + qCDebug(dcZigbeeGenericLights()) << thing << "power state changed" << power; + thing->setStateValue(onOffLightPowerStateTypeId, power); + }); + + } + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginZigbeeGenericLights::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() == onOffLightThingClassId) { + + if (info->action().actionTypeId() == onOffLightAlertActionTypeId) { + // Get the identify server cluster from the endpoint + ZigbeeClusterIdentify *identifyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIdentify); + if (!identifyCluster) { + qCWarning(dcZigbeeGenericLights()) << "Could not find identify cluster for" << thing << "in" << node; + 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() == onOffLightPowerActionTypeId) { + // Get the identify server cluster from the endpoint + 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; + } + + // Send the command trough the network + bool power = info->action().param(onOffLightPowerActionPowerParamTypeId).value().toBool(); + ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff()); + connect(reply, &ZigbeeClusterReply::finished, thing, [reply, info, power, thing](){ + // Note: reply will be deleted automatically + if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + info->finish(Thing::ThingErrorHardwareFailure); + } else { + info->finish(Thing::ThingErrorNoError); + thing->setStateValue(onOffLightPowerStateTypeId, power); + } + }); + 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); + } +} + +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); +} diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h new file mode 100644 index 00000000..6854b65f --- /dev/null +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h @@ -0,0 +1,71 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginzigbee-generic-lights.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 executeAction(ThingActionInfo *info) override; + void thingRemoved(Thing *thing) override; + +private: + QHash m_ieeeAddressParamTypeIds; + QHash m_networkUuidParamTypeIds; + QHash m_endpointIdParamTypeIds; + + QHash m_connectedStateTypeIds; + QHash m_signalStrengthStateTypeIds; + QHash m_versionStateTypeIds; + + QHash m_thingNodes; + + ZigbeeNodeEndpoint *findEndpoint(Thing *thing); + +}; + +#endif // INTEGRATIONPLUGINZIGBEEGENERICLIGHTS_H diff --git a/zigbee-generic-lights/zigbee-generic-lights.pro b/zigbee-generic-lights/zigbee-generic-lights.pro new file mode 100644 index 00000000..5561cbf7 --- /dev/null +++ b/zigbee-generic-lights/zigbee-generic-lights.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +PKGCONFIG += nymea-zigbee + +SOURCES += \ + integrationpluginzigbeegenericlights.cpp + +HEADERS += \ + integrationpluginzigbeegenericlights.h diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index 90ae8a13..ed1e33be 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -141,10 +141,10 @@ void IntegrationPluginZigbeeLumi::handleRemoveNode(ZigbeeNode *node, const QUuid { Q_UNUSED(networkUuid) - if (m_nodeThings.values().contains(node)) { - Thing *thing = m_nodeThings.key(node); - qCDebug(dcZigbeeLumi()) << "Node for" << thing << node << "has left the network. Removing thing"; - m_nodeThings.remove(thing); + 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()); } } @@ -157,10 +157,10 @@ void IntegrationPluginZigbeeLumi::init() void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); - qCDebug(dcZigbeeLumi()) << "Nework uuid:" << networkUuid; - ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_zigbeeAddressParamTypeIds.value(thing->thingClassId())).toString()); + // 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()->getNode(networkUuid, zigbeeAddress); if (!node) { qCWarning(dcZigbeeLumi()) << "Zigbee node for" << info->thing()->name() << "not found.´"; @@ -175,16 +175,14 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorSetupFailed); return; } - - m_nodeThings.insert(thing, node); - // General signals and states + // Store the node thing association for removing + m_thingNodes.insert(thing, node); + // Update connected state - thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), hardwareManager()->zigbeeResource()->networkState(networkUuid) == ZigbeeNetwork::StateRunning); - connect(hardwareManager()->zigbeeResource(), &ZigbeeHardwareResource::networkStateChanged, thing, [thing, this](const QUuid &networkUuid, ZigbeeNetwork::State state){ - if (thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid() == networkUuid) { - thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), state == ZigbeeNetwork::StateRunning); - } + 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 @@ -198,7 +196,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) // 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) { @@ -461,7 +459,7 @@ void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info) void IntegrationPluginZigbeeLumi::thingRemoved(Thing *thing) { - ZigbeeNode *node = m_nodeThings.take(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/zigbee-lumi/integrationpluginzigbeelumi.h b/zigbee-lumi/integrationpluginzigbeelumi.h index 17670aac..03ebce89 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.h +++ b/zigbee-lumi/integrationpluginzigbeelumi.h @@ -66,7 +66,7 @@ private: QHash m_knownLumiDevices; - QHash m_nodeThings; + QHash m_thingNodes; PluginTimer *m_presenceTimer = nullptr; }; From 13d3c5d4630f1e5ceed82f8d4e32f7bd6b5b4aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 12 Nov 2020 17:47:06 +0100 Subject: [PATCH 06/47] Handle reachable state for lights --- .../integrationpluginzigbeegenericlights.cpp | 48 +++++++++++++++++-- .../integrationpluginzigbeegenericlights.h | 1 + 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 7b7209ab..047e6de4 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -148,6 +148,7 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) // Thing specific setup 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; @@ -155,11 +156,18 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) return; } + // 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); + } + }); } info->finish(Thing::ThingErrorNoError); @@ -175,10 +183,10 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) // Get the node Thing *thing = info->thing(); ZigbeeNode *node = m_thingNodes.value(thing); - if (!node->reachable()) { - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } +// if (!node->reachable()) { +// info->finish(Thing::ThingErrorHardwareNotAvailable); +// return; +// } // Get the endpoint ZigbeeNodeEndpoint *endpoint = findEndpoint(thing); @@ -212,7 +220,7 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) } if (info->action().actionTypeId() == onOffLightPowerActionTypeId) { - // Get the identify server cluster from the endpoint + // Get the on/off server cluster from the endpoint ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (!onOffCluster) { qCWarning(dcZigbeeGenericLights()) << "Could not find on/off cluster for" << thing << "in" << node; @@ -222,13 +230,16 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) // Send the command trough the network bool power = info->action().param(onOffLightPowerActionPowerParamTypeId).value().toBool(); + qCDebug(dcZigbeeGenericLights()) << "Set power for" << thing << "to" << power; ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff()); connect(reply, &ZigbeeClusterReply::finished, thing, [reply, info, power, thing](){ // 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(onOffLightPowerStateTypeId, power); } }); @@ -259,3 +270,30 @@ ZigbeeNodeEndpoint *IntegrationPluginZigbeeGenericLights::findEndpoint(Thing *th quint8 endpointId = thing->paramValue(m_endpointIdParamTypeIds.value(thing->thingClassId())).toUInt(); return node->getEndpoint(endpointId); } + +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; + } + }); +} diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h index 6854b65f..9f67f6b9 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h @@ -66,6 +66,7 @@ private: ZigbeeNodeEndpoint *findEndpoint(Thing *thing); + void readLightPowerState(Thing *thing); }; #endif // INTEGRATIONPLUGINZIGBEEGENERICLIGHTS_H From 771929363b6e8c8293eeea9c411ae5054e08339c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 13 Nov 2020 10:36:09 +0100 Subject: [PATCH 07/47] Add dimmable and color temperature light to generic lights plugin --- ...ntegrationpluginzigbee-generic-lights.json | 237 ++++++- .../integrationpluginzigbeegenericlights.cpp | 588 ++++++++++++++++-- .../integrationpluginzigbeegenericlights.h | 35 ++ 3 files changed, 802 insertions(+), 58 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json b/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json index b1185b55..b7266a54 100644 --- a/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json +++ b/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json @@ -56,8 +56,8 @@ { "id": "7548ac08-4ea1-4dcd-98b9-9d58bbc06ab7", "name": "connected", - "displayName": "Available", - "displayNameEvent": "Available changed", + "displayName": "Connected", + "displayNameEvent": "Connected changed", "type": "bool", "cached": false, "defaultValue": false @@ -102,6 +102,239 @@ ], "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": [ + ] } ] diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 047e6de4..d3f5441f 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -37,17 +37,40 @@ IntegrationPluginZigbeeGenericLights::IntegrationPluginZigbeeGenericLights() { + // Common thing params map m_ieeeAddressParamTypeIds[onOffLightThingClassId] = onOffLightThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[dimmableLightThingClassId] = dimmableLightThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[onOffLightThingClassId] = onOffLightThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[dimmableLightThingClassId] = dimmableLightThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingNetworkUuidParamTypeId; m_endpointIdParamTypeIds[onOffLightThingClassId] = onOffLightThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[dimmableLightThingClassId] = dimmableLightThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingEndpointIdParamTypeId; + m_manufacturerIdParamTypeIds[onOffLightThingClassId] = onOffLightThingManufacturerParamTypeId; + m_manufacturerIdParamTypeIds[dimmableLightThingClassId] = dimmableLightThingManufacturerParamTypeId; + m_manufacturerIdParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingManufacturerParamTypeId; + + m_modelIdParamTypeIds[onOffLightThingClassId] = onOffLightThingModelParamTypeId; + m_modelIdParamTypeIds[dimmableLightThingClassId] = dimmableLightThingModelParamTypeId; + m_modelIdParamTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightThingModelParamTypeId; + + + // Common sates map m_connectedStateTypeIds[onOffLightThingClassId] = onOffLightConnectedStateTypeId; + m_connectedStateTypeIds[dimmableLightThingClassId] = dimmableLightConnectedStateTypeId; + m_connectedStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightConnectedStateTypeId; m_signalStrengthStateTypeIds[onOffLightThingClassId] = onOffLightSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[dimmableLightThingClassId] = dimmableLightSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightSignalStrengthStateTypeId; m_versionStateTypeIds[onOffLightThingClassId] = onOffLightVersionStateTypeId; + m_versionStateTypeIds[dimmableLightThingClassId] = dimmableLightVersionStateTypeId; + m_versionStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightVersionStateTypeId; } QString IntegrationPluginZigbeeGenericLights::name() const @@ -63,20 +86,29 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffLight)) { - ThingDescriptor descriptor(onOffLightThingClassId); - QString deviceClassName = supportedThings().findById(onOffLightThingClassId).displayName(); - descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier())); - qCDebug(dcZigbeeGenericLights()) << "Handeling generic on/off light" << descriptor.title(); + qCDebug(dcZigbeeGenericLights()) << "Handeling on/off light for" << node << endpoint; + createLightThing(onOffLightThingClassId, networkUuid, node, endpoint); + return true; + } - ParamList params; - params.append(Param(onOffLightThingNetworkUuidParamTypeId, networkUuid.toString())); - params.append(Param(onOffLightThingIeeeAddressParamTypeId, node->extendedAddress().toString())); - params.append(Param(onOffLightThingEndpointIdParamTypeId, endpoint->endpointId())); - params.append(Param(onOffLightThingModelParamTypeId, endpoint->modelIdentifier())); - params.append(Param(onOffLightThingManufacturerParamTypeId, endpoint->manufacturerName())); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); + 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); + return 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); return true; } } @@ -93,6 +125,10 @@ void IntegrationPluginZigbeeGenericLights::handleRemoveNode(ZigbeeNode *node, co 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); + } } } @@ -146,6 +182,8 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId()); // Thing specific setup + + // On/Off light if (thing->thingClassId() == onOffLightThingClassId) { // Get the on/off cluster @@ -156,6 +194,10 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) 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; @@ -170,6 +212,134 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) }); } + // 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); + } + }); + } + info->finish(Thing::ThingErrorNoError); } @@ -183,10 +353,10 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) // Get the node Thing *thing = info->thing(); ZigbeeNode *node = m_thingNodes.value(thing); -// if (!node->reachable()) { -// info->finish(Thing::ThingErrorHardwareNotAvailable); -// return; -// } + if (!node->reachable()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } // Get the endpoint ZigbeeNodeEndpoint *endpoint = findEndpoint(thing); @@ -195,58 +365,71 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) return; } + // On/Off light if (thing->thingClassId() == onOffLightThingClassId) { - if (info->action().actionTypeId() == onOffLightAlertActionTypeId) { - // Get the identify server cluster from the endpoint - ZigbeeClusterIdentify *identifyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIdentify); - if (!identifyCluster) { - qCWarning(dcZigbeeGenericLights()) << "Could not find identify cluster for" << thing << "in" << node; - 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); - } - }); + executeAlertAction(info, endpoint); return; } if (info->action().actionTypeId() == onOffLightPowerActionTypeId) { // Get the on/off server cluster from the endpoint - 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; - } - - // Send the command trough the network bool power = info->action().param(onOffLightPowerActionPowerParamTypeId).value().toBool(); - qCDebug(dcZigbeeGenericLights()) << "Set power for" << thing << "to" << power; - ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff()); - connect(reply, &ZigbeeClusterReply::finished, thing, [reply, info, power, thing](){ - // 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(onOffLightPowerStateTypeId, power); - } - }); + 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; + } + } + + + info->finish(Thing::ThingErrorUnsupportedFeature); } @@ -257,6 +440,10 @@ void IntegrationPluginZigbeeGenericLights::thingRemoved(Thing *thing) QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node); } + + if (m_colorTemperatureRanges.contains(thing)) { + m_colorTemperatureRanges.remove(thing); + } } ZigbeeNodeEndpoint *IntegrationPluginZigbeeGenericLights::findEndpoint(Thing *thing) @@ -271,6 +458,118 @@ ZigbeeNodeEndpoint *IntegrationPluginZigbeeGenericLights::findEndpoint(Thing *th 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); + 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::readLightPowerState(Thing *thing) { // Get the node @@ -297,3 +596,180 @@ void IntegrationPluginZigbeeGenericLights::readLightPowerState(Thing *thing) } }); } + +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::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/zigbee-generic-lights/integrationpluginzigbeegenericlights.h b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h index 9f67f6b9..b8be53fb 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h @@ -54,19 +54,54 @@ public: 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); + + // Read state values from the node void readLightPowerState(Thing *thing); + void readLightLevelState(Thing *thing); + void readLightColorTemperatureState(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; + + 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 From e9c4a744091933e1a6774eab73b4ff345a8acee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 16 Nov 2020 09:16:40 +0100 Subject: [PATCH 08/47] Add generic color light --- ...ntegrationpluginzigbee-generic-lights.json | 130 ++++++++ .../integrationpluginzigbeegenericlights.cpp | 305 ++++++++++++++++++ .../integrationpluginzigbeegenericlights.h | 6 + 3 files changed, 441 insertions(+) diff --git a/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json b/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json index b7266a54..3c43370f 100644 --- a/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json +++ b/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json @@ -335,6 +335,136 @@ ], "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/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index d3f5441f..1165a4ce 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -35,42 +35,52 @@ #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 sates 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 @@ -111,6 +121,15 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU createLightThing(colorTemperatureLightThingClassId, networkUuid, node, endpoint); return 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); + return true; + } } return false; @@ -340,6 +359,84 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) }); } + // 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); } @@ -428,6 +525,73 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) } } + // 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); @@ -570,6 +734,30 @@ void IntegrationPluginZigbeeGenericLights::executeColorTemperatureAction(ThingAc }); } +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(), 2); + 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 @@ -648,6 +836,123 @@ void IntegrationPluginZigbeeGenericLights::readLightColorTemperatureState(Thing }); } +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); diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h index b8be53fb..f14bb641 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h @@ -78,11 +78,13 @@ private: 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 { @@ -96,6 +98,10 @@ private: 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); From 20813ef401f43b7dcb3947d4b5e4646c804c5c4a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 16 Nov 2020 15:15:36 +0100 Subject: [PATCH 09/47] update to latest resource api changes --- .../integrationpluginzigbeegenericlights.cpp | 2 +- .../integrationpluginzigbeegeneric.cpp | 28 +++++++++++++++++-- .../integrationpluginzigbeegeneric.h | 2 ++ zigbee-lumi/integrationpluginzigbeelumi.cpp | 2 +- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 1165a4ce..d928a55d 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -163,7 +163,7 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) // 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()->getNode(networkUuid, zigbeeAddress); + ZigbeeNode *node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress); if (!node) { qCWarning(dcZigbeeGenericLights()) << "Zigbee node for" << info->thing()->name() << "not found.´"; info->finish(Thing::ThingErrorHardwareNotAvailable); diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 50ad31cf..76c5cc05 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -33,6 +33,8 @@ #include "plugininfo.h" #include "hardware/zigbee/zigbeehardwareresource.h" +#include "zcl/hvac/zigbeeclusterthermostat.h" + #include IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric() @@ -78,6 +80,19 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n return handled; } +void IntegrationPluginZigbeeGeneric::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) +{ + Q_UNUSED(networkUuid) + Thing *thing = m_zigbeeNodes.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_zigbeeNodes.remove(thing); + } +} + void IntegrationPluginZigbeeGeneric::init() { hardwareManager()->zigbeeResource()->registerHandler(this, ZigbeeHardwareResource::HandlerTypeCatchAll); @@ -94,13 +109,15 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) 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 = hardwareManager()->zigbeeResource()->getNode(networkUuid, zigbeeAddress); + ZigbeeNode *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_zigbeeNodes.insert(thing, node); + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); if (!endpoint) { qCWarning(dcZigbeeGeneric()) << "Zigbee endpoint 1 not found on" << thing->name(); @@ -127,7 +144,8 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) // Type specific setup if (thing->thingClassId() == thermostatThingClassId) { // TODO: Thermostat cluster is missing - // ZigbeeClusterThermostat *thermostatCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdThermostat); +// ZigbeeClusterThermostat *thermostatCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdThermostat); +// thermostatCluster->attribute(ZigbeeClusterLibrary::ClusterId); } @@ -142,5 +160,9 @@ void IntegrationPluginZigbeeGeneric::executeAction(ThingActionInfo *info) void IntegrationPluginZigbeeGeneric::thingRemoved(Thing *thing) { - Q_UNUSED(thing) + ZigbeeNode *node = m_zigbeeNodes.take(thing); + if (node) { + QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); + hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node); + } } diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h index feb7f821..39f755eb 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -49,6 +49,7 @@ public: 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; @@ -56,6 +57,7 @@ public: void thingRemoved(Thing *thing) override; private: + QHash m_zigbeeNodes; QHash m_ieeeAddressParamTypeIds; QHash m_networkUuidParamTypeIds; diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index ed1e33be..fb099283 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -161,7 +161,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) // 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()->getNode(networkUuid, zigbeeAddress); + ZigbeeNode *node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress); if (!node) { qCWarning(dcZigbeeLumi()) << "Zigbee node for" << info->thing()->name() << "not found.´"; info->finish(Thing::ThingErrorHardwareNotAvailable); From 4c367bbce6894c6331c7a7414249d8c66409a0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 16 Nov 2020 16:41:39 +0100 Subject: [PATCH 10/47] Add lumi smart power socket basics --- .../integrationpluginzigbeegenericlights.cpp | 2 +- zigbee-lumi/integrationpluginzigbee-lumi.json | 75 +++++++++++++++ zigbee-lumi/integrationpluginzigbeelumi.cpp | 95 ++++++++++++------- 3 files changed, 138 insertions(+), 34 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index d928a55d..6105b550 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -165,7 +165,7 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) 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.´"; + qCWarning(dcZigbeeGenericLights()) << "Zigbee node for" << info->thing()->name() << "not found."; info->finish(Thing::ThingErrorHardwareNotAvailable); return; } diff --git a/zigbee-lumi/integrationpluginzigbee-lumi.json b/zigbee-lumi/integrationpluginzigbee-lumi.json index 3c8f0e55..8e1fceca 100644 --- a/zigbee-lumi/integrationpluginzigbee-lumi.json +++ b/zigbee-lumi/integrationpluginzigbee-lumi.json @@ -556,6 +556,81 @@ "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 + } + ], + "actionTypes": [ + { + "id": "67052e4d-350a-406f-91a9-3ce3f8eaaa7f", + "name": "alert", + "displayName": "Identify" + } + ], + "eventTypes": [ + + ] } ] } diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index fb099283..28c436a1 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -46,6 +46,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_networkUuidParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingNetworkUuidParamTypeId; m_zigbeeAddressParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingIeeeAddressParamTypeId; @@ -54,6 +55,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_zigbeeAddressParamTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingIeeeAddressParamTypeId; m_connectedStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorConnectedStateTypeId; @@ -62,6 +64,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_connectedStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorConnectedStateTypeId; + m_connectedStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketConnectedStateTypeId; m_versionStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorVersionStateTypeId; m_versionStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorVersionStateTypeId; @@ -70,6 +73,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_versionStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorVersionStateTypeId; m_versionStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorVersionStateTypeId; m_versionStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorVersionStateTypeId; + m_versionStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketVersionStateTypeId; m_signalStrengthStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorSignalStrengthStateTypeId; @@ -78,6 +82,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_signalStrengthStateTypeIds[lumiWaterSensorThingClassId] = lumiWaterSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketSignalStrengthStateTypeId; // Known model identifier m_knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId); @@ -87,6 +92,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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); } QString IntegrationPluginZigbeeLumi::name() const @@ -415,45 +421,68 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } + 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; + } + } info->finish(Thing::ThingErrorNoError); - - - // if (thing->thingClassId() == lumiButtonSensorThingClassId) { - // qCDebug(dcZigbee()) << "Lumi button sensor" << thing; - // ZigbeeAddress ieeeAddress(thing->paramValue(lumiButtonSensorThingIeeeAddressParamTypeId).toString()); - // ZigbeeNetwork *network = findParentNetwork(thing); - // LumiButtonSensor *sensor = new LumiButtonSensor(network, ieeeAddress, thing, this); - // connect(sensor, &LumiButtonSensor::buttonPressed, this, [this, thing](){ - // qCDebug(dcZigbee()) << thing << "clicked event"; - // emit emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); - // }); - // connect(sensor, &LumiButtonSensor::buttonLongPressed, this, [this, thing](){ - // qCDebug(dcZigbee()) << thing << "long pressed event"; - // emit emitEvent(Event(lumiButtonSensorLongPressedEventTypeId, thing->id())); - // }); - - // m_zigbeeDevices.insert(thing, sensor); - // info->finish(Thing::ThingErrorNoError); - // return; - // } - - - // if (thing->thingClassId() == lumiWaterSensorThingClassId) { - // qCDebug(dcZigbee()) << "Lumi water sensor" << thing; - // ZigbeeAddress ieeeAddress(thing->paramValue(lumiWaterSensorThingIeeeAddressParamTypeId).toString()); - // ZigbeeNetwork *network = findParentNetwork(thing); - // LumiWaterSensor *sensor = new LumiWaterSensor(network, ieeeAddress, thing, this); - // m_zigbeeDevices.insert(thing, sensor); - // info->finish(Thing::ThingErrorNoError); - // return; - // } - - // info->finish(Thing::ThingErrorThingClassNotFound); } 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->name() << "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; + } + info->finish(Thing::ThingErrorUnsupportedFeature); } From acdb29907a17c8848f26599a4cf34dbcdc9c81ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 16 Nov 2020 21:00:00 +0100 Subject: [PATCH 11/47] Add translation files for zigbee plugins --- .../translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts | 1 + .../translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 1 + .../translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts | 1 + 3 files changed, 3 insertions(+) create mode 100644 zigbee-generic-lights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts create mode 100644 zigbee-generic/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts create mode 100644 zigbee-lumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts diff --git a/zigbee-generic-lights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts b/zigbee-generic-lights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts new file mode 100644 index 00000000..dcd02922 --- /dev/null +++ b/zigbee-generic-lights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/zigbee-generic/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbee-generic/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts new file mode 100644 index 00000000..dcd02922 --- /dev/null +++ b/zigbee-generic/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/zigbee-lumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts b/zigbee-lumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts new file mode 100644 index 00000000..dcd02922 --- /dev/null +++ b/zigbee-lumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts @@ -0,0 +1 @@ + \ No newline at end of file From ef57c2d0b15d25ab3b72a6c45bdcba783f64fa50 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 16 Nov 2020 23:41:37 +0100 Subject: [PATCH 12/47] Some more work on the thermostat --- .../integrationpluginzigbee-generic.json | 11 +++- .../integrationpluginzigbeegeneric.cpp | 66 ++++++++++++++----- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json index b9fed263..6e97d53b 100644 --- a/zigbee-generic/integrationpluginzigbee-generic.json +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -13,7 +13,7 @@ "name": "thermostat", "displayName": "Zigbee Thermostat", "createMethods": ["auto"], - "interfaces": ["thermostat", "wirelessconnectable"], + "interfaces": ["thermostat", "temperaturesensor", "wirelessconnectable"], "paramTypes": [ { "id": "f38746d8-0084-43a3-b645-3ec743ea5fbc", @@ -42,6 +42,15 @@ "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", diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 76c5cc05..4a80dcd6 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -63,6 +63,7 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n bool handled = false; foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { + qCDebug(dcZigbeeGeneric()) << "Checking ndoe endpoint:" << endpoint->endpointId() << endpoint->deviceId(); if (devicesThingClassIdsMap.contains(endpoint->deviceId())) { ThingClassId thingClassId = devicesThingClassIdsMap.value(endpoint->deviceId()); @@ -100,16 +101,14 @@ void IntegrationPluginZigbeeGeneric::init() void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) { - if (!hardwareManager()->zigbeeResource()->available()) { - qCWarning(dcZigbeeGeneric()) << "Zigbee is not available. Not setting up" << info->thing()->name(); - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } 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 = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress); + ZigbeeNode *node = m_zigbeeNodes.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); @@ -126,11 +125,9 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) } // Update connected state - thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), hardwareManager()->zigbeeResource()->networkState(networkUuid) == ZigbeeNetwork::StateRunning); - connect(hardwareManager()->zigbeeResource(), &ZigbeeHardwareResource::networkStateChanged, thing, [thing, this](const QUuid &networkUuid, ZigbeeNetwork::State state){ - if (thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid() == networkUuid) { - thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), state == ZigbeeNetwork::StateRunning); - } + 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 @@ -143,13 +140,50 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) // Type specific setup if (thing->thingClassId() == thermostatThingClassId) { - // TODO: Thermostat cluster is missing -// ZigbeeClusterThermostat *thermostatCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdThermostat); -// thermostatCluster->attribute(ZigbeeClusterLibrary::ClusterId); + 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); + } + } + + + + }); } - - info->finish(Thing::ThingErrorNoError); } From a58ee272e14807d1ef10f8ba6c1ca8cd68496acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 19 Nov 2020 08:46:26 +0100 Subject: [PATCH 13/47] Add lumi relay and add ioType to lumi things --- zigbee-lumi/integrationpluginzigbee-lumi.json | 95 ++++++++++++++- zigbee-lumi/integrationpluginzigbeelumi.cpp | 113 +++++++++++++++++- 2 files changed, 202 insertions(+), 6 deletions(-) diff --git a/zigbee-lumi/integrationpluginzigbee-lumi.json b/zigbee-lumi/integrationpluginzigbee-lumi.json index 8e1fceca..06775ce5 100644 --- a/zigbee-lumi/integrationpluginzigbee-lumi.json +++ b/zigbee-lumi/integrationpluginzigbee-lumi.json @@ -238,7 +238,8 @@ "displayName": "Closed", "displayNameEvent": "Closed changed", "type": "bool", - "defaultValue": true + "defaultValue": true, + "ioType": "digitalInput" } ], "actionTypes": [ @@ -387,7 +388,8 @@ "displayName": "Present", "displayNameEvent": "Present changed", "type": "bool", - "defaultValue": true + "defaultValue": true, + "ioType": "digitalInput" }, { "id": "021ce190-a775-40e0-b19b-c6307c73e343", @@ -474,7 +476,8 @@ "displayName": "Water detected", "displayNameEvent": "Water detected changed", "type": "bool", - "defaultValue": true + "defaultValue": true, + "ioType": "digitalInput" }, { "id": "72aa617c-88cc-4992-a1a2-400b5e19106e", @@ -618,7 +621,8 @@ "displayNameAction": "Set power", "type": "bool", "defaultValue": false, - "writable": true + "writable": true, + "ioType": "digitalOutput" } ], "actionTypes": [ @@ -630,6 +634,89 @@ ], "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": [ + ] } ] diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index 28c436a1..fc261f86 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -47,6 +47,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_networkUuidParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[lumiRelayThingClassId] = lumiRelayThingNetworkUuidParamTypeId; m_zigbeeAddressParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingIeeeAddressParamTypeId; @@ -56,6 +57,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_zigbeeAddressParamTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiRelayThingClassId] = lumiRelayThingIeeeAddressParamTypeId; m_connectedStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorConnectedStateTypeId; @@ -65,6 +67,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_connectedStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketConnectedStateTypeId; + m_connectedStateTypeIds[lumiRelayThingClassId] = lumiRelayConnectedStateTypeId; m_versionStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorVersionStateTypeId; m_versionStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorVersionStateTypeId; @@ -74,6 +77,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_versionStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorVersionStateTypeId; m_versionStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorVersionStateTypeId; m_versionStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketVersionStateTypeId; + m_versionStateTypeIds[lumiRelayThingClassId] = lumiRelayVersionStateTypeId; m_signalStrengthStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorSignalStrengthStateTypeId; @@ -83,6 +87,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_signalStrengthStateTypeIds[lumiWeatherSensorThingClassId] = lumiWeatherSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[lumiRelayThingClassId] = lumiRelaySignalStrengthStateTypeId; // Known model identifier m_knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId); @@ -93,6 +98,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_knownLumiDevices.insert("lumi.weather", lumiWeatherSensorThingClassId); m_knownLumiDevices.insert("lumi.vibration", lumiVibrationSensorThingClassId); m_knownLumiDevices.insert("lumi.plug", lumiPowerSocketThingClassId); + m_knownLumiDevices.insert("lumi.relay", lumiRelayThingClassId); } QString IntegrationPluginZigbeeLumi::name() const @@ -136,7 +142,6 @@ bool IntegrationPluginZigbeeLumi::handleNode(ZigbeeNode *node, const QUuid &netw params << Param(m_zigbeeAddressParamTypeIds.value(thingClassId), node->extendedAddress().toString()); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); - return true; } @@ -437,6 +442,47 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } } + 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); } @@ -447,7 +493,7 @@ void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info) if (thing->thingClassId() == lumiPowerSocketThingClassId) { ZigbeeNode *node = m_thingNodes.value(thing); if (!node) { - qCWarning(dcZigbeeLumi()) << "Zigbee node for" << thing->name() << "not found."; + qCWarning(dcZigbeeLumi()) << "Zigbee node for" << thing << "not found."; info->finish(Thing::ThingErrorHardwareFailure); return; } @@ -483,6 +529,69 @@ void IntegrationPluginZigbeeLumi::executeAction(ThingActionInfo *info) 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); } From 6c9276755ace7bbff51da5824a3d31ee2add0bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 19 Nov 2020 10:39:09 +0100 Subject: [PATCH 14/47] Add generic power socket for light link and home automation --- .../integrationpluginzigbeegenericlights.cpp | 11 +- .../integrationpluginzigbee-generic.json | 155 +++++++++++++-- .../integrationpluginzigbeegeneric.cpp | 188 +++++++++++++++--- .../integrationpluginzigbeegeneric.h | 11 +- 4 files changed, 322 insertions(+), 43 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 6105b550..42351b39 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -90,6 +90,7 @@ QString IntegrationPluginZigbeeGenericLights::name() const 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) || @@ -98,7 +99,7 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling on/off light for" << node << endpoint; createLightThing(onOffLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && @@ -108,7 +109,7 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling dimmable light for" << node << endpoint; createLightThing(dimmableLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } @@ -119,7 +120,7 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling color temperature light for" << node << endpoint; createLightThing(colorTemperatureLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } if ((endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceColourLight) || @@ -128,11 +129,11 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling color light for" << node << endpoint; createLightThing(colorLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } } - return false; + return handled; } void IntegrationPluginZigbeeGenericLights::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json index 6e97d53b..4793cfee 100644 --- a/zigbee-generic/integrationpluginzigbee-generic.json +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -15,20 +15,41 @@ "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": "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": [ { @@ -70,8 +91,114 @@ "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": [ + + ] } ] } diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 4a80dcd6..4c894c5d 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -40,12 +40,28 @@ IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric() { m_ieeeAddressParamTypeIds[thermostatThingClassId] = thermostatThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[powerSocketThingClassId] = powerSocketThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[thermostatThingClassId] = thermostatThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[powerSocketThingClassId] = powerSocketThingNetworkUuidParamTypeId; + + m_endpointIdParamTypeIds[thermostatThingClassId] = thermostatThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[powerSocketThingClassId] = powerSocketThingEndpointIdParamTypeId; + + m_manufacturerIdParamTypeIds[thermostatThingClassId] = thermostatThingManufacturerParamTypeId; + m_manufacturerIdParamTypeIds[powerSocketThingClassId] = powerSocketThingManufacturerParamTypeId; + + m_modelIdParamTypeIds[thermostatThingClassId] = thermostatThingModelParamTypeId; + m_modelIdParamTypeIds[powerSocketThingClassId] = powerSocketThingModelParamTypeId; m_connectedStateTypeIds[thermostatThingClassId] = thermostatConnectedStateTypeId; + m_connectedStateTypeIds[powerSocketThingClassId] = powerSocketConnectedStateTypeId; m_signalStrengthStateTypeIds[thermostatThingClassId] = thermostatSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[powerSocketThingClassId] = powerSocketSignalStrengthStateTypeId; + + m_versionStateTypeIds[thermostatThingClassId] = thermostatVersionStateTypeId; + m_versionStateTypeIds[powerSocketThingClassId] = powerSocketVersionStateTypeId; } QString IntegrationPluginZigbeeGeneric::name() const @@ -55,25 +71,23 @@ QString IntegrationPluginZigbeeGeneric::name() const bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &networkUuid) { - qCDebug(dcZigbeeGeneric()) << "handleNode called for:" << node; - - QHash devicesThingClassIdsMap; - devicesThingClassIdsMap.insert(Zigbee::HomeAutomationDeviceThermostat, thermostatThingClassId); - bool handled = false; - foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { - qCDebug(dcZigbeeGeneric()) << "Checking ndoe endpoint:" << endpoint->endpointId() << endpoint->deviceId(); + qCDebug(dcZigbeeGeneric()) << "Checking node endpoint:" << endpoint->endpointId() << endpoint->deviceId(); + 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; + } - if (devicesThingClassIdsMap.contains(endpoint->deviceId())) { - ThingClassId thingClassId = devicesThingClassIdsMap.value(endpoint->deviceId()); - ThingDescriptor descriptor(thingClassId, endpoint->modelIdentifier(), endpoint->manufacturerName()); - ParamList params; - params << Param(m_ieeeAddressParamTypeIds.value(thingClassId), node->extendedAddress().toString()); - params << Param(m_networkUuidParamTypeIds.value(thingClassId), networkUuid.toString()); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); + 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; } } @@ -84,13 +98,13 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n void IntegrationPluginZigbeeGeneric::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) { Q_UNUSED(networkUuid) - Thing *thing = m_zigbeeNodes.key(node); + 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_zigbeeNodes.remove(thing); + m_thingNodes.remove(thing); } } @@ -105,21 +119,21 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) 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_zigbeeNodes.value(thing); + 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); - m_zigbeeNodes.insert(thing, node); - - ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + ZigbeeNodeEndpoint *endpoint = findEndpoint(thing); if (!endpoint) { - qCWarning(dcZigbeeGeneric()) << "Zigbee endpoint 1 not found on" << thing->name(); + qCWarning(dcZigbeeGeneric()) << "Could not find endpoint for" << thing; info->finish(Thing::ThingErrorSetupFailed); return; } @@ -138,6 +152,9 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) 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); @@ -184,19 +201,144 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) }); } + 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; + } + } + 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; + } + } + info->finish(Thing::ThingErrorUnsupportedFeature); } void IntegrationPluginZigbeeGeneric::thingRemoved(Thing *thing) { - ZigbeeNode *node = m_zigbeeNodes.take(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}); +} diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h index 39f755eb..11d50d22 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -57,13 +57,22 @@ public: void thingRemoved(Thing *thing) override; private: - QHash m_zigbeeNodes; + 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); + }; #endif // INTEGRATIONPLUGINZIGBEEGENERIC_H From b48e1245ab98d64e995e99b4f480d3fb8acc4c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 19 Nov 2020 15:41:17 +0100 Subject: [PATCH 15/47] Add trafri plugin with on/off switch basic structure --- debian/control | 18 +- nymea-plugins.pro | 1 + .../integrationpluginzigbee-tradfri.json | 128 ++++++++++ .../integrationpluginzigbeetradfri.cpp | 226 ++++++++++++++++++ .../integrationpluginzigbeetradfri.h | 73 ++++++ ...343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 1 + zigbee-tradfri/zigbee-tradfri.pro | 12 + 7 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 zigbee-tradfri/integrationpluginzigbee-tradfri.json create mode 100644 zigbee-tradfri/integrationpluginzigbeetradfri.cpp create mode 100644 zigbee-tradfri/integrationpluginzigbeetradfri.h create mode 100644 zigbee-tradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts create mode 100644 zigbee-tradfri/zigbee-tradfri.pro 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 + + + From 0946e04ebb6a11914a2749083c563766ae1aea97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 25 Nov 2020 13:10:19 +0100 Subject: [PATCH 16/47] Add mre tradfri devices for testing --- debian/nymea-plugin-zigbee-tradfri.install.in | 1 + .../integrationpluginzigbeegenericlights.cpp | 8 + .../integrationpluginzigbee-tradfri.json | 232 +++++++++- .../integrationpluginzigbeetradfri.cpp | 438 +++++++++++++++++- .../integrationpluginzigbeetradfri.h | 12 + 5 files changed, 673 insertions(+), 18 deletions(-) create mode 100644 debian/nymea-plugin-zigbee-tradfri.install.in diff --git a/debian/nymea-plugin-zigbee-tradfri.install.in b/debian/nymea-plugin-zigbee-tradfri.install.in new file mode 100644 index 00000000..1d842d82 --- /dev/null +++ b/debian/nymea-plugin-zigbee-tradfri.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-tradfri.so diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 42351b39..a5f3ca25 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -149,6 +149,10 @@ void IntegrationPluginZigbeeGenericLights::handleRemoveNode(ZigbeeNode *node, co if (m_colorTemperatureRanges.contains(thing)) { m_colorTemperatureRanges.remove(thing); } + + if (m_colorCapabilities.contains(thing)) { + m_colorCapabilities.remove(thing); + } } } @@ -609,6 +613,10 @@ void IntegrationPluginZigbeeGenericLights::thingRemoved(Thing *thing) if (m_colorTemperatureRanges.contains(thing)) { m_colorTemperatureRanges.remove(thing); } + + if (m_colorCapabilities.contains(thing)) { + m_colorCapabilities.remove(thing); + } } ZigbeeNodeEndpoint *IntegrationPluginZigbeeGenericLights::findEndpoint(Thing *thing) diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index 05d430e1..dbf79d49 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -1,7 +1,7 @@ { "name": "ZigbeeTradfri", "displayName": "Zigbee TRÅDFRI", - "id": "6a4343be-9fd6-4015-9ff5-38542651c534", + "id": "ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830", "vendors": [ { "name": "ikeaTradfri", @@ -121,6 +121,236 @@ ] } ] + }, + { + "name": "remote", + "displayName": "TRÅDFRI remote", + "id": "19ee3f56-f958-4b04-8ea3-747b72d7c7d6", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "longpressmultibutton", "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"] + } + ] + }, + { + "id": "3fb1bc3b-50ea-4a9f-a5b1-10b1fae2592e", + "name": "longPressed", + "displayName": "Button longpressed", + "paramTypes": [ + { + "id": "4a151723-5798-4087-a4e5-5aa08ecbb9b8", + "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": true, + "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": [ + + ] } ] } diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index 5f9caa63..b06d91e2 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -37,16 +37,28 @@ IntegrationPluginZigbeeTradfri::IntegrationPluginZigbeeTradfri() { m_ieeeAddressParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[remoteThingClassId] = remoteThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[remoteThingClassId] = remoteThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId; m_endpointIdParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[remoteThingClassId] = remoteThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[motionSensorThingClassId] = motionSensorThingEndpointIdParamTypeId; m_connectedStateTypeIds[onOffSwitchThingClassId] = onOffSwitchConnectedStateTypeId; + m_connectedStateTypeIds[remoteThingClassId] = remoteConnectedStateTypeId; + m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId; m_signalStrengthStateTypeIds[onOffSwitchThingClassId] = onOffSwitchSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[remoteThingClassId] = remoteSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId; m_versionStateTypeIds[onOffSwitchThingClassId] = onOffSwitchVersionStateTypeId; + m_versionStateTypeIds[remoteThingClassId] = remoteVersionStateTypeId; + m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId; } QString IntegrationPluginZigbeeTradfri::name() const @@ -62,18 +74,38 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n bool handled = false; foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { - if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && - endpoint->deviceId() == Zigbee::HomeAutomationDeviceNonColourController) { + 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); + 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); + handled = true; + } + + if (endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController) { + qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint; + createThing(remoteThingClassId, networkUuid, node, endpoint); + + ZigbeeClusterBasic *basicCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdBasic); + if (!basicCluster) { + qCWarning(dcZigbeeTradfri()) << "Failed to find basic cluster for performing factory reset to defaults"; + initRemote(node, endpoint); + } else { + ZigbeeClusterReply *zclReply = basicCluster->resetToFactoryDefaults(); + connect(zclReply, &ZigbeeClusterReply::finished, node, [=](){ + if (zclReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to perform factory reset on basic cluster on" << endpoint; + } + + initRemote(node, endpoint); + }); + } - ParamList params; - params.append(Param(onOffSwitchThingNetworkUuidParamTypeId, networkUuid.toString())); - params.append(Param(onOffSwitchThingIeeeAddressParamTypeId, node->extendedAddress().toString())); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); handled = true; } } @@ -84,13 +116,12 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n 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()) + 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()); } } @@ -136,8 +167,8 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) // Set the version thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId()); + // Thing specific setup if (thing->thingClassId() == onOffSwitchThingClassId) { - // Receive on/off commands ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (!onOffCluster) { @@ -155,7 +186,7 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) }); } - // Receive level cntrol commands + // Receive level control commands ZigbeeClusterLevelControl *levelCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl); if (!levelCluster) { qCWarning(dcZigbeeTradfri()) << "Could not find level client cluster on" << thing << endpoint; @@ -177,6 +208,7 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) }); } + // Get battery level changes ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); if (!powerCluster) { qCWarning(dcZigbeeTradfri()) << "Could not find power configuration cluster on" << thing << endpoint; @@ -195,6 +227,87 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } } + if (thing->thingClassId() == motionSensorThingClassId) { + // 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(); + }); + } + + // 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)); + }); + } + } + + if (thing->thingClassId() == remoteThingClassId) { + // 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(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power"))); + } + }); + } + + // 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)); + }); + } + } + info->finish(Thing::ThingErrorNoError); } @@ -224,3 +337,294 @@ ZigbeeNodeEndpoint *IntegrationPluginZigbeeTradfri::findEndpoint(Thing *thing) 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; + } + + // 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(); + //emit nodeInitialized(node); + return; + } + + 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(); + return; + } + 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 = 60;//300; + reportingConfig.maxReportingInterval = 120;//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(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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"; + + qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + + 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::initRemote(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; + } + + // 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(); + //emit nodeInitialized(node); + return; + } + + 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(); + return; + } + 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 = 60;//300; + reportingConfig.maxReportingInterval = 120;//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(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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"; + + qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + + 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::readBindings(ZigbeeNode *node) +{ + 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::initPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + 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(); + //emit nodeInitialized(node); + return; + } + + 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(); + return; + } + 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 = 60;//300; + reportingConfig.maxReportingInterval = 120;//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, [reportingReply](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + }); + }); + }); +} + +void IntegrationPluginZigbeeTradfri::initOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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"; + }); +} + +void IntegrationPluginZigbeeTradfri::initLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + }); +} + diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.h b/zigbee-tradfri/integrationpluginzigbeetradfri.h index e40afb1d..8c28cef6 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.h +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.h @@ -57,6 +57,7 @@ public: void thingRemoved(Thing *thing) override; private: + PluginTimer *m_presenceTimer = nullptr; QHash m_thingNodes; QHash m_ieeeAddressParamTypeIds; @@ -68,6 +69,17 @@ private: 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 readBindings(ZigbeeNode *node); + void initPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void initOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void initLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + + }; #endif // INTEGRATIONPLUGINZIGBEETRADFRI_H From f906769f6010bdb8aec200d0655fd49b1505dbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 25 Nov 2020 13:11:17 +0100 Subject: [PATCH 17/47] Remove tradfri basic cluster factory reset temporary for testing --- .../integrationpluginzigbeetradfri.cpp | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index b06d91e2..107a64a9 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -91,20 +91,21 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint; createThing(remoteThingClassId, networkUuid, node, endpoint); - ZigbeeClusterBasic *basicCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdBasic); - if (!basicCluster) { - qCWarning(dcZigbeeTradfri()) << "Failed to find basic cluster for performing factory reset to defaults"; - initRemote(node, endpoint); - } else { - ZigbeeClusterReply *zclReply = basicCluster->resetToFactoryDefaults(); - connect(zclReply, &ZigbeeClusterReply::finished, node, [=](){ - if (zclReply->error() != ZigbeeClusterReply::ErrorNoError) { - qCWarning(dcZigbeeTradfri()) << "Failed to perform factory reset on basic cluster on" << endpoint; - } +// ZigbeeClusterBasic *basicCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdBasic); +// if (!basicCluster) { +// qCWarning(dcZigbeeTradfri()) << "Failed to find basic cluster for performing factory reset to defaults"; +// initRemote(node, endpoint); +// } else { +// ZigbeeClusterReply *zclReply = basicCluster->resetToFactoryDefaults(); +// connect(zclReply, &ZigbeeClusterReply::finished, node, [=](){ +// if (zclReply->error() != ZigbeeClusterReply::ErrorNoError) { +// qCWarning(dcZigbeeTradfri()) << "Failed to perform factory reset on basic cluster on" << endpoint; +// } - initRemote(node, endpoint); - }); - } +// initRemote(node, endpoint); +// }); +// } + initRemote(node, endpoint); handled = true; } From c7ffbfda1541b946fc84d65539089f816d1667d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 26 Nov 2020 10:33:06 +0100 Subject: [PATCH 18/47] Add door lock to generic zigbee things --- .../integrationpluginzigbee-generic.json | 110 ++++++++++ .../integrationpluginzigbeegeneric.cpp | 189 ++++++++++++++++++ .../integrationpluginzigbeegeneric.h | 2 + 3 files changed, 301 insertions(+) diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json index 4793cfee..5c9a13b3 100644 --- a/zigbee-generic/integrationpluginzigbee-generic.json +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -198,6 +198,116 @@ ], "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/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 4c894c5d..c2fac542 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -41,27 +41,35 @@ 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 @@ -74,6 +82,8 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n 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; @@ -81,6 +91,7 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n handled = true; } + // Check on/off plug if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOffPlugin) || (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && @@ -90,6 +101,20 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n 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; @@ -229,6 +254,44 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) } } + 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); } @@ -303,6 +366,50 @@ void IntegrationPluginZigbeeGeneric::executeAction(ThingActionInfo *info) } } + 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); } @@ -342,3 +449,85 @@ void IntegrationPluginZigbeeGeneric::createThing(const ThingClassId &thingClassI 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/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h index 11d50d22..102fbba8 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -73,6 +73,8 @@ private: 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 From 3d365d2c5798f2409e5ed0150c6c9d0868cf825a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 26 Nov 2020 11:59:29 +0100 Subject: [PATCH 19/47] Add lumi remote --- .../integrationpluginzigbeegenericlights.cpp | 2 +- zigbee-lumi/integrationpluginzigbee-lumi.json | 88 +++++++++++++++++++ zigbee-lumi/integrationpluginzigbeelumi.cpp | 47 ++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index a5f3ca25..47872aef 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -66,7 +66,7 @@ IntegrationPluginZigbeeGenericLights::IntegrationPluginZigbeeGenericLights() m_modelIdParamTypeIds[colorLightThingClassId] = colorLightThingModelParamTypeId; - // Common sates map + // Common states map m_connectedStateTypeIds[onOffLightThingClassId] = onOffLightConnectedStateTypeId; m_connectedStateTypeIds[dimmableLightThingClassId] = dimmableLightConnectedStateTypeId; m_connectedStateTypeIds[colorTemperatureLightThingClassId] = colorTemperatureLightConnectedStateTypeId; diff --git a/zigbee-lumi/integrationpluginzigbee-lumi.json b/zigbee-lumi/integrationpluginzigbee-lumi.json index 06775ce5..d18de87d 100644 --- a/zigbee-lumi/integrationpluginzigbee-lumi.json +++ b/zigbee-lumi/integrationpluginzigbee-lumi.json @@ -717,6 +717,94 @@ ], "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/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index fc261f86..eddaf8f2 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -48,6 +48,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_networkUuidParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[lumiRelayThingClassId] = lumiRelayThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[lumiRemoteThingClassId] = lumiRemoteThingNetworkUuidParamTypeId; m_zigbeeAddressParamTypeIds[lumiHTSensorThingClassId] = lumiHTSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorThingIeeeAddressParamTypeId; @@ -58,6 +59,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_zigbeeAddressParamTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketThingIeeeAddressParamTypeId; m_zigbeeAddressParamTypeIds[lumiRelayThingClassId] = lumiRelayThingIeeeAddressParamTypeId; + m_zigbeeAddressParamTypeIds[lumiRemoteThingClassId] = lumiRemoteThingIeeeAddressParamTypeId; m_connectedStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorConnectedStateTypeId; @@ -68,6 +70,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_connectedStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorConnectedStateTypeId; m_connectedStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketConnectedStateTypeId; m_connectedStateTypeIds[lumiRelayThingClassId] = lumiRelayConnectedStateTypeId; + m_connectedStateTypeIds[lumiRemoteThingClassId] = lumiRemoteConnectedStateTypeId; m_versionStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorVersionStateTypeId; m_versionStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorVersionStateTypeId; @@ -78,6 +81,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_versionStateTypeIds[lumiVibrationSensorThingClassId] = lumiVibrationSensorVersionStateTypeId; m_versionStateTypeIds[lumiPowerSocketThingClassId] = lumiPowerSocketVersionStateTypeId; m_versionStateTypeIds[lumiRelayThingClassId] = lumiRelayVersionStateTypeId; + m_versionStateTypeIds[lumiRemoteThingClassId] = lumiRemoteVersionStateTypeId; m_signalStrengthStateTypeIds[lumiHTSensorThingClassId] = lumiHTSensorSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[lumiButtonSensorThingClassId] = lumiButtonSensorSignalStrengthStateTypeId; @@ -88,6 +92,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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); @@ -99,6 +104,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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 @@ -442,6 +448,47 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } } + 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); From 10cc4ed8db231f9fa719886485ce2597920d1cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 26 Nov 2020 18:22:55 +0100 Subject: [PATCH 20/47] Add philips hue zigbee plugin with hue dimmer switch support --- debian/control | 18 + .../nymea-plugin-zigbee-philipshue.install.in | 1 + nymea-plugins.pro | 1 + .../integrationpluginzigbee-philipshue.json | 107 ++++++ .../integrationpluginzigbeephilipshue.cpp | 321 ++++++++++++++++++ .../integrationpluginzigbeephilipshue.h | 76 +++++ ...340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts | 1 + zigbee-philipshue/zigbee-philipshue.pro | 9 + .../integrationpluginzigbeetradfri.cpp | 12 +- 9 files changed, 540 insertions(+), 6 deletions(-) create mode 100644 debian/nymea-plugin-zigbee-philipshue.install.in create mode 100644 zigbee-philipshue/integrationpluginzigbee-philipshue.json create mode 100644 zigbee-philipshue/integrationpluginzigbeephilipshue.cpp create mode 100644 zigbee-philipshue/integrationpluginzigbeephilipshue.h create mode 100644 zigbee-philipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts create mode 100644 zigbee-philipshue/zigbee-philipshue.pro diff --git a/debian/control b/debian/control index 4fc83b76..f06ac686 100644 --- a/debian/control +++ b/debian/control @@ -1061,6 +1061,23 @@ 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-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}, @@ -1149,6 +1166,7 @@ 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 diff --git a/debian/nymea-plugin-zigbee-philipshue.install.in b/debian/nymea-plugin-zigbee-philipshue.install.in new file mode 100644 index 00000000..365ee14a --- /dev/null +++ b/debian/nymea-plugin-zigbee-philipshue.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-philipshue.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index bb7c08e1..a2782cdb 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -67,6 +67,7 @@ PLUGIN_DIRS = \ zigbee-generic \ zigbee-generic-lights \ zigbee-lumi \ + zigbee-philipshue \ zigbee-tradfri \ diff --git a/zigbee-philipshue/integrationpluginzigbee-philipshue.json b/zigbee-philipshue/integrationpluginzigbee-philipshue.json new file mode 100644 index 00000000..c64dbaad --- /dev/null +++ b/zigbee-philipshue/integrationpluginzigbee-philipshue.json @@ -0,0 +1,107 @@ +{ + "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"] + } + ] + } + ] + } + ] + } + ] +} diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp new file mode 100644 index 00000000..5364546a --- /dev/null +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp @@ -0,0 +1,321 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* +* 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" + +IntegrationPluginZigbeePhilipsHue::IntegrationPluginZigbeePhilipsHue() +{ + m_ieeeAddressParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingIeeeAddressParamTypeId; + + m_networkUuidParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingNetworkUuidParamTypeId; + + m_connectedStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchConnectedStateTypeId; + + m_signalStrengthStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchSignalStrengthStateTypeId; + + m_versionStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchVersionStateTypeId; + +} + +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; + } + } + + 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)); + }); + } + + } + + 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; + } + + // 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(); + //emit nodeInitialized(node); + return; + } + + 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(); + return; + } + 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(); + return; + } + + 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()->requestBindShortAddress(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(); + return; + } + qCDebug(dcZigbeePhilipsHue()) << "Bind on/off cluster to coordinator finished successfully"; + + qCDebug(dcZigbeePhilipsHue()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); + return; + } + 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; + } + } + }); + }); + }); + }); + }); + }); + }); +} + diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.h b/zigbee-philipshue/integrationpluginzigbeephilipshue.h new file mode 100644 index 00000000..974b6128 --- /dev/null +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.h @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginzigbee-philipshue.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); + +}; + +#endif // INTEGRATIONPLUGINZIGBEEPHILIPSHUE_H diff --git a/zigbee-philipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts b/zigbee-philipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts new file mode 100644 index 00000000..dcd02922 --- /dev/null +++ b/zigbee-philipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/zigbee-philipshue/zigbee-philipshue.pro b/zigbee-philipshue/zigbee-philipshue.pro new file mode 100644 index 00000000..945859b8 --- /dev/null +++ b/zigbee-philipshue/zigbee-philipshue.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +PKGCONFIG += nymea-zigbee + +SOURCES += \ + integrationpluginzigbeephilipshue.cpp + +HEADERS += \ + integrationpluginzigbeephilipshue.h diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index 107a64a9..d49633af 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -394,8 +394,8 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining; reportingConfig.dataType = Zigbee::Uint8; - reportingConfig.minReportingInterval = 60;//300; - reportingConfig.maxReportingInterval = 120;//2700; + reportingConfig.minReportingInterval = 300; + reportingConfig.maxReportingInterval = 2700; reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator"; @@ -487,8 +487,8 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining; reportingConfig.dataType = Zigbee::Uint8; - reportingConfig.minReportingInterval = 60;//300; - reportingConfig.maxReportingInterval = 120;//2700; + reportingConfig.minReportingInterval = 300; + reportingConfig.maxReportingInterval = 2700; reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator"; @@ -585,8 +585,8 @@ void IntegrationPluginZigbeeTradfri::initPowerConfigurationCluster(ZigbeeNode *n ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining; reportingConfig.dataType = Zigbee::Uint8; - reportingConfig.minReportingInterval = 60;//300; - reportingConfig.maxReportingInterval = 120;//2700; + reportingConfig.minReportingInterval = 300; + reportingConfig.maxReportingInterval = 2700; reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator"; From cb2a6f9f4c5e0ac7831370170dedf916e6276dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sat, 28 Nov 2020 14:27:31 +0100 Subject: [PATCH 21/47] Add tradfri range extender and fix motion sensor. Start implementing hue outdoor sensor --- .../integrationpluginzigbee-philipshue.json | 116 +++++++++ .../integrationpluginzigbeephilipshue.cpp | 245 +++++++++++++++++- .../integrationpluginzigbeephilipshue.h | 1 + .../integrationpluginzigbee-tradfri.json | 68 +++++ .../integrationpluginzigbeetradfri.cpp | 227 ++++++++-------- .../integrationpluginzigbeetradfri.h | 7 +- 6 files changed, 528 insertions(+), 136 deletions(-) diff --git a/zigbee-philipshue/integrationpluginzigbee-philipshue.json b/zigbee-philipshue/integrationpluginzigbee-philipshue.json index c64dbaad..fa21f314 100644 --- a/zigbee-philipshue/integrationpluginzigbee-philipshue.json +++ b/zigbee-philipshue/integrationpluginzigbee-philipshue.json @@ -100,6 +100,122 @@ ] } ] + }, + { + "name": "outdoorSensor", + "displayName": "Hue outdoor 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": "" + } + ], + "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": true, + "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/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp index 5364546a..fdf91ec6 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp @@ -36,15 +36,19 @@ IntegrationPluginZigbeePhilipsHue::IntegrationPluginZigbeePhilipsHue() { m_ieeeAddressParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[outdoorSensorThingClassId] = outdoorSensorThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[outdoorSensorThingClassId] = outdoorSensorThingNetworkUuidParamTypeId; m_connectedStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchConnectedStateTypeId; + m_connectedStateTypeIds[outdoorSensorThingClassId] = outdoorSensorConnectedStateTypeId; m_signalStrengthStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[outdoorSensorThingClassId] = outdoorSensorSignalStrengthStateTypeId; m_versionStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchVersionStateTypeId; - + m_versionStateTypeIds[outdoorSensorThingClassId] = outdoorSensorVersionStateTypeId; } QString IntegrationPluginZigbeePhilipsHue::name() const @@ -66,8 +70,8 @@ bool IntegrationPluginZigbeePhilipsHue::handleNode(ZigbeeNode *node, const QUuid // Dimmer switch if (endpointOne->profile() == Zigbee::ZigbeeProfileLightLink && - endpointOne->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController - && endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation && + endpointOne->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController && + endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoinTwo->deviceId() == Zigbee::HomeAutomationDeviceSimpleSensor) { qCDebug(dcZigbeePhilipsHue()) << "Handeling Hue dimmer switch" << node << endpointOne << endpoinTwo; @@ -75,6 +79,18 @@ bool IntegrationPluginZigbeePhilipsHue::handleNode(ZigbeeNode *node, const QUuid 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(outdoorSensorThingClassId, networkUuid, node); + initOutdoorSensor(node); + return true; + } } return handled; @@ -124,7 +140,6 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength); }); - // Thing specific setup if (thing->thingClassId() == dimmerSwitchThingClassId) { ZigbeeNodeEndpoint *endpointZll = node->getEndpoint(0x01); @@ -133,7 +148,6 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) // Set the version thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpointZll->softwareBuildId()); - // Receive on/off commands ZigbeeClusterOnOff *onOffCluster = endpointZll->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (!onOffCluster) { @@ -191,6 +205,81 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) thing->setStateValue(dimmerSwitchBatteryCriticalStateTypeId, (percentage < 10.0)); }); } + } + + if (thing->thingClassId() == outdoorSensorThingClassId) { + 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(outdoorSensorBatteryLevelStateTypeId, powerCluster->batteryPercentage()); + thing->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + } + + connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){ + qCDebug(dcZigbeePhilipsHue()) << "Battery percentage changed" << percentage << "%" << thing; + thing->setStateValue(outdoorSensorBatteryLevelStateTypeId, percentage); + thing->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (percentage < 10.0)); + }); + } + + ZigbeeClusterOccupancySensing *occupancyCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing); + if (!occupancyCluster) { + qCWarning(dcZigbeePhilipsHue()) << "Occupancy cluster not found on" << thing; + } else { + if (occupancyCluster->hasAttribute(ZigbeeClusterOccupancySensing::AttributeOccupancy)) { + thing->setStateValue(outdoorSensorIsPresentStateTypeId, occupancyCluster->occupied()); + thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + } + + connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [thing](bool occupancy){ + qCDebug(dcZigbeePhilipsHue()) << "occupancy changed" << occupancy; + // Only change the state if the it changed to true, it will be disabled by the timer + if (occupancy) { + thing->setStateValue(outdoorSensorIsPresentStateTypeId, occupancy); + //m_presenceTimer->start(); + } + thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + }); + + // if (!m_presenceTimer) { + // m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); + // } + + // connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){ + // if (thing->stateValue(outdoorSensorIsPresentStateTypeId).toBool()) { + // int timeout = thing->setting(outdoorSensorSettingsTimeoutParamTypeId).toInt(); + // QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(lumiMotionSensorLastSeenTimeStateTypeId).toULongLong() * 1000); + // if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) { + // thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, false); + // } + // } + // }); + } + + 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(outdoorSensorTemperatureStateTypeId, temperatureCluster->temperature()); + } + + connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){ + qCDebug(dcZigbeePhilipsHue()) << thing << "temperature changed" << temperature << "°C"; + thing->setStateValue(outdoorSensorTemperatureStateTypeId, temperature); + }); + } + } @@ -319,3 +408,149 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) }); } +void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(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; + } + + // 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); + } + + + // Init occupancy sensing cluster + qCDebug(dcZigbeePhilipsHue()) << "Bind occupancy 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 cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Bind occupency cluster to coordinator finished successfully"; + } + + + // Configure attribute reporting for temperature + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue; + reportingConfig.dataType = Zigbee::BitMap8; + reportingConfig.minReportingInterval = 1; + reportingConfig.maxReportingInterval = 10; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + 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); + } + + + // 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 + + // 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); + } + + + + + }); + }); + }); + }); + }); + }); + }); + }); + }); +} + diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.h b/zigbee-philipshue/integrationpluginzigbeephilipshue.h index 974b6128..07756382 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.h +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.h @@ -70,6 +70,7 @@ private: void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node); void initDimmerSwitch(ZigbeeNode *node); + void initOutdoorSensor(ZigbeeNode *node); }; diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index dbf79d49..c51ebedc 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -350,6 +350,74 @@ ], "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/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index d49633af..0c81be17 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -39,26 +39,32 @@ IntegrationPluginZigbeeTradfri::IntegrationPluginZigbeeTradfri() m_ieeeAddressParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingIeeeAddressParamTypeId; m_ieeeAddressParamTypeIds[remoteThingClassId] = remoteThingIeeeAddressParamTypeId; m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[remoteThingClassId] = remoteThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingNetworkUuidParamTypeId; m_endpointIdParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingEndpointIdParamTypeId; m_endpointIdParamTypeIds[remoteThingClassId] = remoteThingEndpointIdParamTypeId; m_endpointIdParamTypeIds[motionSensorThingClassId] = motionSensorThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingEndpointIdParamTypeId; m_connectedStateTypeIds[onOffSwitchThingClassId] = onOffSwitchConnectedStateTypeId; m_connectedStateTypeIds[remoteThingClassId] = remoteConnectedStateTypeId; m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId; + m_connectedStateTypeIds[signalRepeaterThingClassId] = signalRepeaterConnectedStateTypeId; m_signalStrengthStateTypeIds[onOffSwitchThingClassId] = onOffSwitchSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[remoteThingClassId] = remoteSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[signalRepeaterThingClassId] = signalRepeaterSignalStrengthStateTypeId; m_versionStateTypeIds[onOffSwitchThingClassId] = onOffSwitchVersionStateTypeId; m_versionStateTypeIds[remoteThingClassId] = remoteVersionStateTypeId; m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId; + m_versionStateTypeIds[signalRepeaterThingClassId] = signalRepeaterVersionStateTypeId; } QString IntegrationPluginZigbeeTradfri::name() const @@ -84,29 +90,20 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n 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->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController) { qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint; createThing(remoteThingClassId, networkUuid, node, endpoint); - -// ZigbeeClusterBasic *basicCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdBasic); -// if (!basicCluster) { -// qCWarning(dcZigbeeTradfri()) << "Failed to find basic cluster for performing factory reset to defaults"; -// initRemote(node, endpoint); -// } else { -// ZigbeeClusterReply *zclReply = basicCluster->resetToFactoryDefaults(); -// connect(zclReply, &ZigbeeClusterReply::finished, node, [=](){ -// if (zclReply->error() != ZigbeeClusterReply::ErrorNoError) { -// qCWarning(dcZigbeeTradfri()) << "Failed to perform factory reset on basic cluster on" << endpoint; -// } - -// initRemote(node, endpoint); -// }); -// } initRemote(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; } } @@ -170,6 +167,24 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) // 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) { @@ -208,7 +223,9 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } }); } + } + if (thing->thingClassId() == motionSensorThingClassId) { // Get battery level changes ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); if (!powerCluster) { @@ -216,19 +233,17 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } 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)); + 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(onOffSwitchBatteryLevelStateTypeId, percentage); - thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (percentage < 10.0)); + thing->setStateValue(motionSensorBatteryLevelStateTypeId, percentage); + thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (percentage < 10.0)); }); } - } - if (thing->thingClassId() == motionSensorThingClassId) { // Create plugintimer if required if (!m_presenceTimer) { m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); @@ -255,41 +270,9 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) m_presenceTimer->start(); }); } - - // 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)); - }); - } } if (thing->thingClassId() == remoteThingClassId) { - // 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(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power"))); - } - }); - } - // Get battery level changes ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); if (!powerCluster) { @@ -307,6 +290,20 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) 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 << "button pressed" << command; + if (command == ZigbeeClusterOnOff::CommandToggle) { + qCDebug(dcZigbeeTradfri()) << thing << "pressed power"; + emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power"))); + } + }); + } } info->finish(Thing::ThingErrorNoError); @@ -538,94 +535,74 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp }); } -void IntegrationPluginZigbeeTradfri::readBindings(ZigbeeNode *node) +void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) { - qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; - ZigbeeReply *reply = node->readBindingTableEntries(); + // 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 read binding table from" << node; - } else { - foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) { - qCDebug(dcZigbeeTradfri()) << node << binding; - } - } - }); -} + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node; + } -void IntegrationPluginZigbeeTradfri::initPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) -{ - if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) { - qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint; - return; - } + // Read battery, bind and configure attribute reporting for battery - 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(); - //emit nodeInitialized(node); + 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 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(); + 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(); + //emit nodeInitialized(node); return; } - 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()) << "Read power configuration cluster attributes finished successfully"; - qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator"; - ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig}); - connect(reportingReply, &ZigbeeClusterReply::finished, this, [reportingReply](){ - if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { - qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); + // 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(); return; } + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; - qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + // 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(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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"; + }); + }); }); }); }); } -void IntegrationPluginZigbeeTradfri::initOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) -{ - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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"; - }); -} - -void IntegrationPluginZigbeeTradfri::initLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) -{ - qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); - return; - } - qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; - }); -} - diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.h b/zigbee-tradfri/integrationpluginzigbeetradfri.h index 8c28cef6..706fe547 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.h +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.h @@ -73,12 +73,7 @@ private: void initOnOffSwitch(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); - - void readBindings(ZigbeeNode *node); - void initPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); - void initOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); - void initLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); - + void initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); }; From ddfaa7a6b88be01ba9ed7ba76c89aa707ad3a789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sat, 28 Nov 2020 17:04:14 +0100 Subject: [PATCH 22/47] Finish hue outdoor sensor presence detection with settings timout and refresh mechanism --- .../integrationpluginzigbee-philipshue.json | 13 +- .../integrationpluginzigbeephilipshue.cpp | 224 ++++++++++++------ .../integrationpluginzigbee-tradfri.json | 2 +- .../integrationpluginzigbeetradfri.cpp | 2 +- 4 files changed, 159 insertions(+), 82 deletions(-) diff --git a/zigbee-philipshue/integrationpluginzigbee-philipshue.json b/zigbee-philipshue/integrationpluginzigbee-philipshue.json index fa21f314..5ad69185 100644 --- a/zigbee-philipshue/integrationpluginzigbee-philipshue.json +++ b/zigbee-philipshue/integrationpluginzigbee-philipshue.json @@ -124,6 +124,17 @@ "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", @@ -179,7 +190,7 @@ "displayName": "Present", "displayNameEvent": "Present changed", "type": "bool", - "defaultValue": true, + "defaultValue": false, "ioType": "digitalInput" }, { diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp index fdf91ec6..68223354 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp @@ -33,6 +33,8 @@ #include "plugininfo.h" #include "hardware/zigbee/zigbeehardwareresource.h" +#include + IntegrationPluginZigbeePhilipsHue::IntegrationPluginZigbeePhilipsHue() { m_ieeeAddressParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingIeeeAddressParamTypeId; @@ -235,34 +237,30 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) if (!occupancyCluster) { qCWarning(dcZigbeePhilipsHue()) << "Occupancy cluster not found on" << thing; } else { - if (occupancyCluster->hasAttribute(ZigbeeClusterOccupancySensing::AttributeOccupancy)) { - thing->setStateValue(outdoorSensorIsPresentStateTypeId, occupancyCluster->occupied()); - thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + if (!m_presenceTimer) { + m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); } - connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [thing](bool occupancy){ - qCDebug(dcZigbeePhilipsHue()) << "occupancy changed" << occupancy; - // Only change the state if the it changed to true, it will be disabled by the timer - if (occupancy) { - thing->setStateValue(outdoorSensorIsPresentStateTypeId, occupancy); - //m_presenceTimer->start(); + connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){ + if (thing->stateValue(outdoorSensorIsPresentStateTypeId).toBool()) { + int timeout = thing->setting(outdoorSensorSettingsTimeoutParamTypeId).toInt(); + QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(outdoorSensorLastSeenTimeStateTypeId).toULongLong() * 1000); + if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) { + qCDebug(dcZigbeePhilipsHue()) << thing << "Presence timeout"; + thing->setStateValue(outdoorSensorIsPresentStateTypeId, false); + } } - thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); }); - // if (!m_presenceTimer) { - // m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); - // } - - // connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){ - // if (thing->stateValue(outdoorSensorIsPresentStateTypeId).toBool()) { - // int timeout = thing->setting(outdoorSensorSettingsTimeoutParamTypeId).toInt(); - // QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(lumiMotionSensorLastSeenTimeStateTypeId).toULongLong() * 1000); - // if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) { - // thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, 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(outdoorSensorIsPresentStateTypeId, true); + thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + m_presenceTimer->start(); + } + }); } ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); @@ -280,6 +278,24 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) }); } + 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(outdoorSensorLightIntensityStateTypeId, 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(outdoorSensorLightIntensityStateTypeId, convertedValue); + }); + } + } @@ -410,7 +426,6 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) { - //ZigbeeNodeEndpoint *endpointZll = node->getEndpoint(0x01); ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02); // Get the current configured bindings for this node @@ -426,6 +441,7 @@ void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) 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, [=](){ @@ -435,7 +451,6 @@ void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) 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, @@ -465,84 +480,135 @@ void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) } - // Init occupancy sensing cluster - qCDebug(dcZigbeePhilipsHue()) << "Bind occupancy cluster to coordinator IEEE address"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdOccupancySensing, + // 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 occupancy cluster to coordinator" << zdoReply->error(); + qCWarning(dcZigbeePhilipsHue()) << "Failed to bind temperature cluster to coordinator" << zdoReply->error(); } else { - qCDebug(dcZigbeePhilipsHue()) << "Bind occupency cluster to coordinator finished successfully"; + 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; + } - // Configure attribute reporting for temperature - ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; - reportingConfig.attributeId = ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue; - reportingConfig.dataType = Zigbee::BitMap8; - reportingConfig.minReportingInterval = 1; - reportingConfig.maxReportingInterval = 10; - reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); - - 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(); + 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()) << "Attribute reporting configuration finished for occupancy cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + 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(); - // 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(); + 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()) << "Bind temperature cluster to coordinator finished successfully"; + qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for temperature cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - // 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(); + // 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()) << "Read temperature cluster attributes finished successfully"; + qCDebug(dcZigbeePhilipsHue()) << "Bind illuminance measurement cluster to coordinator finished successfully"; } - // Configure attribute reporting + // 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; + } - // 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(); + 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()) << "Attribute reporting configuration finished for temperature cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + qCDebug(dcZigbeePhilipsHue()) << "Read illuminance measurement cluster attributes finished successfully"; } + // Configure attribute reporting + + // Configure attribute reporting for temperature + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue; + reportingConfig.dataType = Zigbee::Uint16; + reportingConfig.minReportingInterval = 300; + reportingConfig.maxReportingInterval = 600; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).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/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index c51ebedc..35ef6e06 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -332,7 +332,7 @@ "displayName": "Present", "displayNameEvent": "Present changed", "type": "bool", - "defaultValue": true, + "defaultValue": false, "ioType": "digitalInput" }, { diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index 0c81be17..16a1038b 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -591,7 +591,7 @@ void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNo qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { From 482a7d90e3eb66ea9249390a8aec0c6ff1ef46ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sat, 28 Nov 2020 17:08:02 +0100 Subject: [PATCH 23/47] Install zigbee plugins with default plugins --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index f06ac686..f31cd34b 100644 --- a/debian/control +++ b/debian/control @@ -1150,6 +1150,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 @@ -1223,7 +1224,6 @@ Package: nymea-plugins-all Section: libs Architecture: all Depends: nymea-plugins, - nymea-plugins-zigbee, nymea-plugin-simulation, nymea-plugin-snapd, nymea-plugins-maker, From e835bdb3262d60d41ecb97fc211d7024b8697e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 30 Nov 2020 17:45:29 +0100 Subject: [PATCH 24/47] Add smooth brightness change for dimmable zigbee lights --- .../integrationpluginzigbeegenericlights.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 47872aef..7721ee3c 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -705,7 +705,7 @@ void IntegrationPluginZigbeeGenericLights::executeBrightnessAction(ThingActionIn return; } - ZigbeeClusterReply *reply = levelCluster->commandMoveToLevelWithOnOff(level); + ZigbeeClusterReply *reply = levelCluster->commandMoveToLevelWithOnOff(level, 5); connect(reply, &ZigbeeClusterReply::finished, info, [=](){ // Note: reply will be deleted automatically if (reply->error() != ZigbeeClusterReply::ErrorNoError) { @@ -755,7 +755,7 @@ void IntegrationPluginZigbeeGenericLights::executeColorAction(ThingActionInfo *i // Note: time unit is 1/10 s QPoint colorXyInt = ZigbeeUtils::convertColorToXYInt(color); - ZigbeeClusterReply *reply = colorCluster->commandMoveToColor(colorXyInt.x(), colorXyInt.y(), 2); + 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) { From 638d31d959f7cb486345d0b87e405bb69ce7543a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 1 Dec 2020 09:44:49 +0100 Subject: [PATCH 25/47] Continue init procedure for zigbee tradfri if some steps fail --- .../integrationpluginzigbeetradfri.cpp | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index 16a1038b..b0680e4a 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -356,10 +356,11 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod 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; @@ -370,11 +371,10 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); - //emit nodeInitialized(node); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; } - 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"; @@ -383,9 +383,9 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; // Configure attribute rporting for battery remaining (0.5 % changes = 1) ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; @@ -400,30 +400,30 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; - qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; + qCDebug(dcZigbeeTradfri()) << "Read final binding table from node" << node; ZigbeeReply *reply = node->readBindingTableEntries(); connect(reply, &ZigbeeReply::finished, node, [=](){ if (reply->error() != ZigbeeReply::ErrorNoError) { @@ -449,6 +449,8 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp 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 @@ -463,11 +465,10 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); - //emit nodeInitialized(node); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; } - 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"; @@ -476,9 +477,9 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; // Configure attribute rporting for battery remaining (0.5 % changes = 1) ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; @@ -493,28 +494,28 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; ZigbeeReply *reply = node->readBindingTableEntries(); @@ -542,6 +543,8 @@ void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNo 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 @@ -556,12 +559,10 @@ void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNo connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); - //emit nodeInitialized(node); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; } - 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, @@ -569,9 +570,9 @@ void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNo connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; } - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; // Configure attribute rporting for battery remaining (0.5 % changes = 1) ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; @@ -586,11 +587,10 @@ void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNo connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); - return; + } else { + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - 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()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ From 762a32b57a807d02a9f82f1dde359421c35a538c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 1 Dec 2020 10:35:51 +0100 Subject: [PATCH 26/47] Improve hue zigbee init procedure and add tradfri symfonsik prototype for testing --- .../integrationpluginzigbeephilipshue.cpp | 26 ++-- .../integrationpluginzigbee-tradfri.json | 114 ++++++++++++++++++ .../integrationpluginzigbeetradfri.cpp | 80 +++++++++++- 3 files changed, 204 insertions(+), 16 deletions(-) diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp index 68223354..8c71c0c1 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp @@ -339,6 +339,8 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) 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 @@ -352,11 +354,10 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeePhilipsHue()) << "Failed to read power cluster attributes" << readAttributeReply->error(); - //emit nodeInitialized(node); - return; + } else { + qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes finished successfully"; } - 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"; @@ -365,9 +366,9 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeePhilipsHue()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); - return; + } else { + qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator finished successfully"; } - qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator finished successfully"; // Configure attribute rporting for battery remaining (0.5 % changes = 1) ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; @@ -382,28 +383,27 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeePhilipsHue()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); - return; + } else { + qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - 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()->requestBindShortAddress(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(); - return; + } else { + qCDebug(dcZigbeePhilipsHue()) << "Bind on/off cluster to coordinator finished successfully"; } - qCDebug(dcZigbeePhilipsHue()) << "Bind on/off cluster to coordinator finished successfully"; qCDebug(dcZigbeePhilipsHue()) << "Bind power level cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); - return; + } else { + qCDebug(dcZigbeePhilipsHue()) << "Bind level cluster to coordinator finished successfully"; } - qCDebug(dcZigbeePhilipsHue()) << "Bind level cluster to coordinator finished successfully"; qCDebug(dcZigbeePhilipsHue()) << "Read binding table from node" << node; ZigbeeReply *reply = node->readBindingTableEntries(); @@ -433,6 +433,8 @@ void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) 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 diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index 35ef6e06..cb113115 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -236,6 +236,120 @@ } ] }, + { + "name": "soundRemote", + "displayName": "TRÅDFRI Symfonsik", + "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", diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index b0680e4a..574c8744 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -38,31 +38,37 @@ 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; } @@ -101,6 +107,13 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n handled = true; } + if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceRemoteControl) { + qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI Symfonsik sound remote" << node << endpoint; + createThing(soundRemoteThingClassId, networkUuid, node, endpoint); + initRemote(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); @@ -306,6 +319,63 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } } + 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); } @@ -454,7 +524,6 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp } // 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; @@ -481,7 +550,7 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; } - // Configure attribute rporting for battery remaining (0.5 % changes = 1) + // Configure attribute reporting for battery remaining (0.5 % changes = 1) ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining; reportingConfig.dataType = Zigbee::Uint8; @@ -498,8 +567,8 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + // Init OnOff cluster + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { @@ -508,6 +577,8 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp 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()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ @@ -517,6 +588,7 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp 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, [=](){ From 34b5352c202f191e0581bc258dce6de72575330d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 2 Dec 2020 12:03:06 +0100 Subject: [PATCH 27/47] Update generic lights and add post setup reading for inital values and improve debugging --- .../integrationpluginzigbeegenericlights.cpp | 35 ++++ .../integrationpluginzigbeegenericlights.h | 1 + zigbee-lumi/integrationpluginzigbeelumi.cpp | 2 +- .../integrationpluginzigbeephilipshue.cpp | 4 +- .../integrationpluginzigbee-tradfri.json | 2 +- .../integrationpluginzigbeetradfri.cpp | 187 +++++++++--------- 6 files changed, 139 insertions(+), 92 deletions(-) diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 7721ee3c..e87c2a4d 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -445,9 +445,42 @@ void IntegrationPluginZigbeeGenericLights::setupThing(ThingSetupInfo *info) 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; } @@ -456,6 +489,7 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) 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; } @@ -463,6 +497,7 @@ void IntegrationPluginZigbeeGenericLights::executeAction(ThingActionInfo *info) // 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; } diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h index f14bb641..2e352f6c 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.h @@ -50,6 +50,7 @@ public: void init() override; void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; void executeAction(ThingActionInfo *info) override; void thingRemoved(Thing *thing) override; diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index eddaf8f2..a3019f1c 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -188,7 +188,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) // 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->name(); + qCWarning(dcZigbeeLumi()) << "Zigbee endpoint 1 not found on" << thing; info->finish(Thing::ThingErrorSetupFailed); return; } diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp index 8c71c0c1..f3577900 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp @@ -388,7 +388,7 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) } qCDebug(dcZigbeePhilipsHue()) << "Bind on/off cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpointZll->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); + 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(); @@ -397,7 +397,7 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) } qCDebug(dcZigbeePhilipsHue()) << "Bind power level cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpointZll->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); + 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(); diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index cb113115..3dbe747f 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -238,7 +238,7 @@ }, { "name": "soundRemote", - "displayName": "TRÅDFRI Symfonsik", + "displayName": "TRÅDFRI Symfonisk", "id": "a18e398d-c14b-4bcd-8e6d-b64737436814", "setupMethod": "JustAdd", "createMethods": [ "Auto" ], diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index 574c8744..9f019382 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -110,7 +110,7 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceRemoteControl) { qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI Symfonsik sound remote" << node << endpoint; createThing(soundRemoteThingClassId, networkUuid, node, endpoint); - initRemote(node, endpoint); + initOnOffSwitch(node, endpoint); handled = true; } @@ -359,18 +359,18 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } 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; -// } + // 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; + // } }); } } @@ -475,8 +475,8 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod } - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); + 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(); @@ -485,7 +485,7 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod } qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); + 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(); @@ -496,13 +496,13 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod 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; - } - } + 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; + } + } }); }); }); @@ -515,97 +515,108 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod void IntegrationPluginZigbeeTradfri::initRemote(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; - } +// 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(); + // Bind basic cluster + ZigbeeDeviceObjectReply *bindRegisterReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdBasic, 0x0000); + connect(bindRegisterReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (bindRegisterReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind basic cluster" << bindRegisterReply->error(); } else { - qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Bind basic cluster request finished successfully"; } + // 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; + } - // 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(); + 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()) << "Bind power configuration cluster to coordinator finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes 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(); + // 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()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; } - // Init OnOff cluster - qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(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(); + // 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()) << "Bind on/off cluster to coordinator finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - - // Init Level cluster - qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); + // 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 level cluster to coordinator" << zdoReply->error(); + qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error(); } else { - qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Bind on/off 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; - } - } + + // 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; + } + } + }); }); }); }); }); }); }); - }); +//433 }); } void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) @@ -664,7 +675,7 @@ void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNo } qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator"; - ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); + 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(); From 85bd3dd1400cb85798d9b205a3071f9c21118100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:00:06 +0100 Subject: [PATCH 28/47] Fix lumi relay endpoint verification --- zigbee-lumi/integrationpluginzigbeelumi.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbee-lumi/integrationpluginzigbeelumi.cpp index a3019f1c..34b3858a 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.cpp +++ b/zigbee-lumi/integrationpluginzigbeelumi.cpp @@ -316,7 +316,6 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) qCWarning(dcZigbeeLumi()) << "Could not find the relative humidity measurement server cluster on" << thing << endpoint; } } - if (thing->thingClassId() == lumiWeatherSensorThingClassId) { ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); @@ -492,7 +491,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == lumiRelayThingClassId) { // Get the 2 endpoints ZigbeeNodeEndpoint *endpoint1 = node->getEndpoint(0x01); - if (!endpoint1) { + if (endpoint1) { ZigbeeClusterOnOff *onOffCluster = endpoint1->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (onOffCluster) { if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) { @@ -511,7 +510,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } ZigbeeNodeEndpoint *endpoint2 = node->getEndpoint(0x02); - if (!endpoint2) { + if (endpoint2) { ZigbeeClusterOnOff *onOffCluster = endpoint2->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (onOffCluster) { if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) { From a8691fe998390b25b810aa8c01066d5cc779b7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:08:48 +0100 Subject: [PATCH 29/47] Rename zigbee-generic plugin to zigbeegeneric for consistancy --- debian/nymea-plugin-zigbee-generic.install.in | 2 +- nymea-plugins.pro | 2 +- .../integrationpluginzigbeegeneric.cpp | 0 .../integrationpluginzigbeegeneric.h | 2 +- .../integrationpluginzigbeegeneric.json | 0 .../translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 0 .../zigbee-generic.pro => zigbeegeneric/zigbeegeneric.pro | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {zigbee-generic => zigbeegeneric}/integrationpluginzigbeegeneric.cpp (100%) rename {zigbee-generic => zigbeegeneric}/integrationpluginzigbeegeneric.h (98%) rename zigbee-generic/integrationpluginzigbee-generic.json => zigbeegeneric/integrationpluginzigbeegeneric.json (100%) rename {zigbee-generic => zigbeegeneric}/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts (100%) rename zigbee-generic/zigbee-generic.pro => zigbeegeneric/zigbeegeneric.pro (100%) diff --git a/debian/nymea-plugin-zigbee-generic.install.in b/debian/nymea-plugin-zigbee-generic.install.in index c01e09e0..e36eada3 100644 --- a/debian/nymea-plugin-zigbee-generic.install.in +++ b/debian/nymea-plugin-zigbee-generic.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-generic.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeegeneric.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index a2782cdb..9690ed85 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -64,7 +64,7 @@ PLUGIN_DIRS = \ wakeonlan \ wemo \ ws2812fx \ - zigbee-generic \ + zigbeegeneric \ zigbee-generic-lights \ zigbee-lumi \ zigbee-philipshue \ diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbeegeneric/integrationpluginzigbeegeneric.cpp similarity index 100% rename from zigbee-generic/integrationpluginzigbeegeneric.cpp rename to zigbeegeneric/integrationpluginzigbeegeneric.cpp diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbeegeneric/integrationpluginzigbeegeneric.h similarity index 98% rename from zigbee-generic/integrationpluginzigbeegeneric.h rename to zigbeegeneric/integrationpluginzigbeegeneric.h index 102fbba8..788c7d26 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbeegeneric/integrationpluginzigbeegeneric.h @@ -41,7 +41,7 @@ class IntegrationPluginZigbeeGeneric: public IntegrationPlugin, public ZigbeeHan { Q_OBJECT - Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbee-generic.json") + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeegeneric.json") Q_INTERFACES(IntegrationPlugin) public: diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbeegeneric/integrationpluginzigbeegeneric.json similarity index 100% rename from zigbee-generic/integrationpluginzigbee-generic.json rename to zigbeegeneric/integrationpluginzigbeegeneric.json diff --git a/zigbee-generic/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts similarity index 100% rename from zigbee-generic/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts rename to zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts diff --git a/zigbee-generic/zigbee-generic.pro b/zigbeegeneric/zigbeegeneric.pro similarity index 100% rename from zigbee-generic/zigbee-generic.pro rename to zigbeegeneric/zigbeegeneric.pro From 4f4bc7e9f5ba2552072952b0984b349cbbc96a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:11:44 +0100 Subject: [PATCH 30/47] Rename zigbee-generic-lights plugin to zigbeegenericlights for consistancy --- debian/nymea-plugin-zigbee-generic-lights.install.in | 2 +- nymea-plugins.pro | 2 +- .../integrationpluginzigbeegenericlights.cpp | 0 .../integrationpluginzigbeegenericlights.h | 2 +- .../integrationpluginzigbeegenericlights.json | 0 .../translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts | 0 .../zigbeegenericlights.pro | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {zigbee-generic-lights => zigbeegenericlights}/integrationpluginzigbeegenericlights.cpp (100%) rename {zigbee-generic-lights => zigbeegenericlights}/integrationpluginzigbeegenericlights.h (99%) rename zigbee-generic-lights/integrationpluginzigbee-generic-lights.json => zigbeegenericlights/integrationpluginzigbeegenericlights.json (100%) rename {zigbee-generic-lights => zigbeegenericlights}/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts (100%) rename zigbee-generic-lights/zigbee-generic-lights.pro => zigbeegenericlights/zigbeegenericlights.pro (100%) diff --git a/debian/nymea-plugin-zigbee-generic-lights.install.in b/debian/nymea-plugin-zigbee-generic-lights.install.in index c02aec29..f46b810b 100644 --- a/debian/nymea-plugin-zigbee-generic-lights.install.in +++ b/debian/nymea-plugin-zigbee-generic-lights.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-generic-lights.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeegenericlights.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 9690ed85..5fe093f8 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -65,7 +65,7 @@ PLUGIN_DIRS = \ wemo \ ws2812fx \ zigbeegeneric \ - zigbee-generic-lights \ + zigbeegenericlights \ zigbee-lumi \ zigbee-philipshue \ zigbee-tradfri \ diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbeegenericlights/integrationpluginzigbeegenericlights.cpp similarity index 100% rename from zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp rename to zigbeegenericlights/integrationpluginzigbeegenericlights.cpp diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h b/zigbeegenericlights/integrationpluginzigbeegenericlights.h similarity index 99% rename from zigbee-generic-lights/integrationpluginzigbeegenericlights.h rename to zigbeegenericlights/integrationpluginzigbeegenericlights.h index 2e352f6c..6686ccc7 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.h +++ b/zigbeegenericlights/integrationpluginzigbeegenericlights.h @@ -38,7 +38,7 @@ class IntegrationPluginZigbeeGenericLights: public IntegrationPlugin, public Zig { Q_OBJECT - Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbee-generic-lights.json") + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeegenericlights.json") Q_INTERFACES(IntegrationPlugin) public: diff --git a/zigbee-generic-lights/integrationpluginzigbee-generic-lights.json b/zigbeegenericlights/integrationpluginzigbeegenericlights.json similarity index 100% rename from zigbee-generic-lights/integrationpluginzigbee-generic-lights.json rename to zigbeegenericlights/integrationpluginzigbeegenericlights.json diff --git a/zigbee-generic-lights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts b/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts similarity index 100% rename from zigbee-generic-lights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts rename to zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts diff --git a/zigbee-generic-lights/zigbee-generic-lights.pro b/zigbeegenericlights/zigbeegenericlights.pro similarity index 100% rename from zigbee-generic-lights/zigbee-generic-lights.pro rename to zigbeegenericlights/zigbeegenericlights.pro From 96f4acbfbe8e846489430c18a4d8c7ed85712497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:14:02 +0100 Subject: [PATCH 31/47] Rename zigbee-lumi plugin to zigbeelumi for consistancy --- debian/nymea-plugin-zigbee-lumi.install.in | 2 +- nymea-plugins.pro | 2 +- {zigbee-lumi => zigbeelumi}/integrationpluginzigbeelumi.cpp | 0 {zigbee-lumi => zigbeelumi}/integrationpluginzigbeelumi.h | 2 +- .../integrationpluginzigbeelumi.json | 0 .../translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts | 0 zigbee-lumi/zigbee-lumi.pro => zigbeelumi/zigbeelumi.pro | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {zigbee-lumi => zigbeelumi}/integrationpluginzigbeelumi.cpp (100%) rename {zigbee-lumi => zigbeelumi}/integrationpluginzigbeelumi.h (98%) rename zigbee-lumi/integrationpluginzigbee-lumi.json => zigbeelumi/integrationpluginzigbeelumi.json (100%) rename {zigbee-lumi => zigbeelumi}/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts (100%) rename zigbee-lumi/zigbee-lumi.pro => zigbeelumi/zigbeelumi.pro (100%) diff --git a/debian/nymea-plugin-zigbee-lumi.install.in b/debian/nymea-plugin-zigbee-lumi.install.in index 357171e5..06ebdbf6 100644 --- a/debian/nymea-plugin-zigbee-lumi.install.in +++ b/debian/nymea-plugin-zigbee-lumi.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-lumi.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeelumi.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 5fe093f8..bdb5ed34 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -66,7 +66,7 @@ PLUGIN_DIRS = \ ws2812fx \ zigbeegeneric \ zigbeegenericlights \ - zigbee-lumi \ + zigbeelumi \ zigbee-philipshue \ zigbee-tradfri \ diff --git a/zigbee-lumi/integrationpluginzigbeelumi.cpp b/zigbeelumi/integrationpluginzigbeelumi.cpp similarity index 100% rename from zigbee-lumi/integrationpluginzigbeelumi.cpp rename to zigbeelumi/integrationpluginzigbeelumi.cpp diff --git a/zigbee-lumi/integrationpluginzigbeelumi.h b/zigbeelumi/integrationpluginzigbeelumi.h similarity index 98% rename from zigbee-lumi/integrationpluginzigbeelumi.h rename to zigbeelumi/integrationpluginzigbeelumi.h index 03ebce89..96369819 100644 --- a/zigbee-lumi/integrationpluginzigbeelumi.h +++ b/zigbeelumi/integrationpluginzigbeelumi.h @@ -41,7 +41,7 @@ class IntegrationPluginZigbeeLumi: public IntegrationPlugin, public ZigbeeHandle { Q_OBJECT - Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbee-lumi.json") + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeelumi.json") Q_INTERFACES(IntegrationPlugin) public: diff --git a/zigbee-lumi/integrationpluginzigbee-lumi.json b/zigbeelumi/integrationpluginzigbeelumi.json similarity index 100% rename from zigbee-lumi/integrationpluginzigbee-lumi.json rename to zigbeelumi/integrationpluginzigbeelumi.json diff --git a/zigbee-lumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts b/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts similarity index 100% rename from zigbee-lumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts rename to zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts diff --git a/zigbee-lumi/zigbee-lumi.pro b/zigbeelumi/zigbeelumi.pro similarity index 100% rename from zigbee-lumi/zigbee-lumi.pro rename to zigbeelumi/zigbeelumi.pro From bb2f48ee91565e864ab5bffcdc8d1766b8cf668e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:16:01 +0100 Subject: [PATCH 32/47] Rename zigbee-philipshue plugin to zigbeephilipshue for consistancy --- debian/nymea-plugin-zigbee-philipshue.install.in | 2 +- nymea-plugins.pro | 2 +- .../integrationpluginzigbeephilipshue.cpp | 0 .../integrationpluginzigbeephilipshue.h | 2 +- .../integrationpluginzigbeephilipshue.json | 0 .../translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts | 0 .../zigbeephilipshue.pro | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {zigbee-philipshue => zigbeephilipshue}/integrationpluginzigbeephilipshue.cpp (100%) rename {zigbee-philipshue => zigbeephilipshue}/integrationpluginzigbeephilipshue.h (98%) rename zigbee-philipshue/integrationpluginzigbee-philipshue.json => zigbeephilipshue/integrationpluginzigbeephilipshue.json (100%) rename {zigbee-philipshue => zigbeephilipshue}/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts (100%) rename zigbee-philipshue/zigbee-philipshue.pro => zigbeephilipshue/zigbeephilipshue.pro (100%) diff --git a/debian/nymea-plugin-zigbee-philipshue.install.in b/debian/nymea-plugin-zigbee-philipshue.install.in index 365ee14a..80a4b9ac 100644 --- a/debian/nymea-plugin-zigbee-philipshue.install.in +++ b/debian/nymea-plugin-zigbee-philipshue.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-philipshue.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeephilipshue.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index bdb5ed34..06394a3d 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -67,7 +67,7 @@ PLUGIN_DIRS = \ zigbeegeneric \ zigbeegenericlights \ zigbeelumi \ - zigbee-philipshue \ + zigbeephilipshue \ zigbee-tradfri \ diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp similarity index 100% rename from zigbee-philipshue/integrationpluginzigbeephilipshue.cpp rename to zigbeephilipshue/integrationpluginzigbeephilipshue.cpp diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.h b/zigbeephilipshue/integrationpluginzigbeephilipshue.h similarity index 98% rename from zigbee-philipshue/integrationpluginzigbeephilipshue.h rename to zigbeephilipshue/integrationpluginzigbeephilipshue.h index 07756382..60321551 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.h +++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.h @@ -41,7 +41,7 @@ class IntegrationPluginZigbeePhilipsHue: public IntegrationPlugin, public Zigbee { Q_OBJECT - Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbee-philipshue.json") + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeephilipshue.json") Q_INTERFACES(IntegrationPlugin) public: diff --git a/zigbee-philipshue/integrationpluginzigbee-philipshue.json b/zigbeephilipshue/integrationpluginzigbeephilipshue.json similarity index 100% rename from zigbee-philipshue/integrationpluginzigbee-philipshue.json rename to zigbeephilipshue/integrationpluginzigbeephilipshue.json diff --git a/zigbee-philipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts b/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts similarity index 100% rename from zigbee-philipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts rename to zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts diff --git a/zigbee-philipshue/zigbee-philipshue.pro b/zigbeephilipshue/zigbeephilipshue.pro similarity index 100% rename from zigbee-philipshue/zigbee-philipshue.pro rename to zigbeephilipshue/zigbeephilipshue.pro From c99ce3236aa6266f00d84cb7b618c6235884af0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:18:21 +0100 Subject: [PATCH 33/47] Rename zigbee-tradfri plugin to zigbeetradfri for consistancy --- debian/nymea-plugin-zigbee-tradfri.install.in | 2 +- nymea-plugins.pro | 2 +- .../integrationpluginzigbeetradfri.cpp | 0 .../integrationpluginzigbeetradfri.h | 2 +- .../integrationpluginzigbeetradfri.json | 0 .../translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 0 .../zigbee-tradfri.pro => zigbeetradfri/zigbeetradfri.pro | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {zigbee-tradfri => zigbeetradfri}/integrationpluginzigbeetradfri.cpp (100%) rename {zigbee-tradfri => zigbeetradfri}/integrationpluginzigbeetradfri.h (98%) rename zigbee-tradfri/integrationpluginzigbee-tradfri.json => zigbeetradfri/integrationpluginzigbeetradfri.json (100%) rename {zigbee-tradfri => zigbeetradfri}/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts (100%) rename zigbee-tradfri/zigbee-tradfri.pro => zigbeetradfri/zigbeetradfri.pro (100%) diff --git a/debian/nymea-plugin-zigbee-tradfri.install.in b/debian/nymea-plugin-zigbee-tradfri.install.in index 1d842d82..9f7f026c 100644 --- a/debian/nymea-plugin-zigbee-tradfri.install.in +++ b/debian/nymea-plugin-zigbee-tradfri.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-tradfri.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbeetradfri.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 06394a3d..85f73d76 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -68,7 +68,7 @@ PLUGIN_DIRS = \ zigbeegenericlights \ zigbeelumi \ zigbeephilipshue \ - zigbee-tradfri \ + zigbeetradfri \ message(============================================) diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbeetradfri/integrationpluginzigbeetradfri.cpp similarity index 100% rename from zigbee-tradfri/integrationpluginzigbeetradfri.cpp rename to zigbeetradfri/integrationpluginzigbeetradfri.cpp diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.h b/zigbeetradfri/integrationpluginzigbeetradfri.h similarity index 98% rename from zigbee-tradfri/integrationpluginzigbeetradfri.h rename to zigbeetradfri/integrationpluginzigbeetradfri.h index 706fe547..746cdf15 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.h +++ b/zigbeetradfri/integrationpluginzigbeetradfri.h @@ -41,7 +41,7 @@ class IntegrationPluginZigbeeTradfri: public IntegrationPlugin, public ZigbeeHan { Q_OBJECT - Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbee-tradfri.json") + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginzigbeetradfri.json") Q_INTERFACES(IntegrationPlugin) public: diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbeetradfri/integrationpluginzigbeetradfri.json similarity index 100% rename from zigbee-tradfri/integrationpluginzigbee-tradfri.json rename to zigbeetradfri/integrationpluginzigbeetradfri.json diff --git a/zigbee-tradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts similarity index 100% rename from zigbee-tradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts rename to zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts diff --git a/zigbee-tradfri/zigbee-tradfri.pro b/zigbeetradfri/zigbeetradfri.pro similarity index 100% rename from zigbee-tradfri/zigbee-tradfri.pro rename to zigbeetradfri/zigbeetradfri.pro From 2d65aa59af0de1a294f1f27ccaf8bf45ae659a16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 14:31:36 +0100 Subject: [PATCH 34/47] Rename hue outdoor sensor to motion sensor since indoor and outdoor work the same way --- .../integrationpluginzigbeephilipshue.cpp | 54 +++++++++---------- .../integrationpluginzigbeephilipshue.h | 2 +- .../integrationpluginzigbeephilipshue.json | 4 +- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp index f3577900..5b608c4e 100644 --- a/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.cpp @@ -38,19 +38,19 @@ IntegrationPluginZigbeePhilipsHue::IntegrationPluginZigbeePhilipsHue() { m_ieeeAddressParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingIeeeAddressParamTypeId; - m_ieeeAddressParamTypeIds[outdoorSensorThingClassId] = outdoorSensorThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingNetworkUuidParamTypeId; - m_networkUuidParamTypeIds[outdoorSensorThingClassId] = outdoorSensorThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId; m_connectedStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchConnectedStateTypeId; - m_connectedStateTypeIds[outdoorSensorThingClassId] = outdoorSensorConnectedStateTypeId; + m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId; m_signalStrengthStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchSignalStrengthStateTypeId; - m_signalStrengthStateTypeIds[outdoorSensorThingClassId] = outdoorSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId; m_versionStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchVersionStateTypeId; - m_versionStateTypeIds[outdoorSensorThingClassId] = outdoorSensorVersionStateTypeId; + m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId; } QString IntegrationPluginZigbeePhilipsHue::name() const @@ -89,8 +89,8 @@ bool IntegrationPluginZigbeePhilipsHue::handleNode(ZigbeeNode *node, const QUuid endpoinTwo->deviceId() == Zigbee::HomeAutomationDeviceOccupacySensor) { qCDebug(dcZigbeePhilipsHue()) << "Handeling Hue outdoor sensor" << node << endpointOne << endpoinTwo; - createThing(outdoorSensorThingClassId, networkUuid, node); - initOutdoorSensor(node); + createThing(motionSensorThingClassId, networkUuid, node); + initMotionSensor(node); return true; } } @@ -209,7 +209,7 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) } } - if (thing->thingClassId() == outdoorSensorThingClassId) { + if (thing->thingClassId() == motionSensorThingClassId) { ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02); // Set the version @@ -222,14 +222,14 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) } else { // Only set the initial state if the attribute already exists if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) { - thing->setStateValue(outdoorSensorBatteryLevelStateTypeId, powerCluster->batteryPercentage()); - thing->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + 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(outdoorSensorBatteryLevelStateTypeId, percentage); - thing->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (percentage < 10.0)); + thing->setStateValue(motionSensorBatteryLevelStateTypeId, percentage); + thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (percentage < 10.0)); }); } @@ -242,12 +242,12 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) } connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){ - if (thing->stateValue(outdoorSensorIsPresentStateTypeId).toBool()) { - int timeout = thing->setting(outdoorSensorSettingsTimeoutParamTypeId).toInt(); - QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(outdoorSensorLastSeenTimeStateTypeId).toULongLong() * 1000); + 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(outdoorSensorIsPresentStateTypeId, false); + thing->setStateValue(motionSensorIsPresentStateTypeId, false); } } }); @@ -256,8 +256,8 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) 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(outdoorSensorIsPresentStateTypeId, true); - thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + thing->setStateValue(motionSensorIsPresentStateTypeId, true); + thing->setStateValue(motionSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); m_presenceTimer->start(); } }); @@ -269,12 +269,12 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) } else { // Only set the state if the cluster actually has the attribute if (temperatureCluster->hasAttribute(ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue)) { - thing->setStateValue(outdoorSensorTemperatureStateTypeId, temperatureCluster->temperature()); + thing->setStateValue(motionSensorTemperatureStateTypeId, temperatureCluster->temperature()); } connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){ qCDebug(dcZigbeePhilipsHue()) << thing << "temperature changed" << temperature << "°C"; - thing->setStateValue(outdoorSensorTemperatureStateTypeId, temperature); + thing->setStateValue(motionSensorTemperatureStateTypeId, temperature); }); } @@ -286,13 +286,13 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) if (illuminanceCluster->hasAttribute(ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue)) { int convertedValue = pow(10, illuminanceCluster->illuminance() / 10000) - 1; qCDebug(dcZigbeePhilipsHue()) << thing << "illuminance" << illuminanceCluster->illuminance() << "-->" << convertedValue << "lux"; - thing->setStateValue(outdoorSensorLightIntensityStateTypeId, convertedValue); + 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(outdoorSensorLightIntensityStateTypeId, convertedValue); + thing->setStateValue(motionSensorLightIntensityStateTypeId, convertedValue); }); } @@ -424,7 +424,7 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) }); } -void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) +void IntegrationPluginZigbeePhilipsHue::initMotionSensor(ZigbeeNode *node) { ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02); @@ -551,15 +551,13 @@ void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) qCDebug(dcZigbeePhilipsHue()) << "Read illuminance measurement cluster attributes finished successfully"; } - // Configure attribute reporting - - // Configure attribute reporting for temperature + // Configure attribute reporting for illuminance ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterIlluminanceMeasurment::AttributeMeasuredValue; reportingConfig.dataType = Zigbee::Uint16; - reportingConfig.minReportingInterval = 300; + reportingConfig.minReportingInterval = 5; reportingConfig.maxReportingInterval = 600; - reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + 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}); diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.h b/zigbeephilipshue/integrationpluginzigbeephilipshue.h index 60321551..47cd5ade 100644 --- a/zigbeephilipshue/integrationpluginzigbeephilipshue.h +++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.h @@ -70,7 +70,7 @@ private: void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node); void initDimmerSwitch(ZigbeeNode *node); - void initOutdoorSensor(ZigbeeNode *node); + void initMotionSensor(ZigbeeNode *node); }; diff --git a/zigbeephilipshue/integrationpluginzigbeephilipshue.json b/zigbeephilipshue/integrationpluginzigbeephilipshue.json index 5ad69185..d45653ed 100644 --- a/zigbeephilipshue/integrationpluginzigbeephilipshue.json +++ b/zigbeephilipshue/integrationpluginzigbeephilipshue.json @@ -102,8 +102,8 @@ ] }, { - "name": "outdoorSensor", - "displayName": "Hue outdoor sensor", + "name": "motionSensor", + "displayName": "Hue motion sensor", "id": "031c45d1-e782-4f91-9f05-259d6b81c7ef", "setupMethod": "JustAdd", "createMethods": [ "Auto" ], From 3b41efc12cc024c4a8fb5afff5d45f3ea99a4b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 9 Dec 2020 17:19:34 +0100 Subject: [PATCH 35/47] Add xiaomi motion sensor without illuminance measurement --- zigbeelumi/integrationpluginzigbeelumi.cpp | 72 ++++++++++++++--- zigbeelumi/integrationpluginzigbeelumi.json | 90 +++++++++++++++++++++ 2 files changed, 152 insertions(+), 10 deletions(-) diff --git a/zigbeelumi/integrationpluginzigbeelumi.cpp b/zigbeelumi/integrationpluginzigbeelumi.cpp index 34b3858a..d2c267b7 100644 --- a/zigbeelumi/integrationpluginzigbeelumi.cpp +++ b/zigbeelumi/integrationpluginzigbeelumi.cpp @@ -54,6 +54,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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; @@ -65,6 +66,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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; @@ -76,6 +78,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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; @@ -87,6 +90,7 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() 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; @@ -98,7 +102,8 @@ IntegrationPluginZigbeeLumi::IntegrationPluginZigbeeLumi() m_knownLumiDevices.insert("lumi.sensor_ht", lumiHTSensorThingClassId); m_knownLumiDevices.insert("lumi.sensor_magnet", lumiMagnetSensorThingClassId); m_knownLumiDevices.insert("lumi.sensor_switch", lumiButtonSensorThingClassId); - m_knownLumiDevices.insert("lumi.sensor_motion", lumiMotionSensorThingClassId); + // 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); @@ -116,8 +121,8 @@ bool IntegrationPluginZigbeeLumi::handleNode(ZigbeeNode *node, const QUuid &netw { // 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 manufacturer - // name for matching since that has shown to be most constant + // 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)) { @@ -125,16 +130,26 @@ bool IntegrationPluginZigbeeLumi::handleNode(ZigbeeNode *node, const QUuid &netw continue; } - // Basic cluster exists, so we should have the manufacturer name - if (!endpoint->manufacturerName().toLower().startsWith("lumi")) { + // Basic cluster exists, so we should have the model name + if (!endpoint->modelIdentifier().toLower().startsWith("lumi.")) { return false; } ThingClassId thingClassId; - foreach (const QString &knownLumi, m_knownLumiDevices.keys()) { - if (endpoint->modelIdentifier().startsWith(knownLumi)) { - thingClassId = m_knownLumiDevices.value(knownLumi); - break; + 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()) { @@ -285,6 +300,43 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } + 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) { @@ -364,7 +416,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) } - if (thing->thingClassId() == lumiWaterSensorThingClassId) { + 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) { diff --git a/zigbeelumi/integrationpluginzigbeelumi.json b/zigbeelumi/integrationpluginzigbeelumi.json index d18de87d..5bd988cb 100644 --- a/zigbeelumi/integrationpluginzigbeelumi.json +++ b/zigbeelumi/integrationpluginzigbeelumi.json @@ -417,6 +417,96 @@ ] }, + { + "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", From 616385b281d4cff13475290976f7a7b2b4a27009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 11 Dec 2020 18:07:11 +0100 Subject: [PATCH 36/47] Finish tradfri remote --- zigbeelumi/integrationpluginzigbeelumi.cpp | 2 +- .../integrationpluginzigbeetradfri.cpp | 246 ++++++++++++------ .../integrationpluginzigbeetradfri.json | 18 +- 3 files changed, 165 insertions(+), 101 deletions(-) diff --git a/zigbeelumi/integrationpluginzigbeelumi.cpp b/zigbeelumi/integrationpluginzigbeelumi.cpp index d2c267b7..c7e17a50 100644 --- a/zigbeelumi/integrationpluginzigbeelumi.cpp +++ b/zigbeelumi/integrationpluginzigbeelumi.cpp @@ -472,7 +472,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info) ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (onOffCluster) { connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [this, thing](bool power){ - qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "closed" : "open"); + qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "pressed" : "released"); if (!power) { emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); } diff --git a/zigbeetradfri/integrationpluginzigbeetradfri.cpp b/zigbeetradfri/integrationpluginzigbeetradfri.cpp index 9f019382..3ecad31b 100644 --- a/zigbeetradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbeetradfri/integrationpluginzigbeetradfri.cpp @@ -31,6 +31,7 @@ #include "integrationpluginzigbeetradfri.h" #include "plugininfo.h" +#include "zigbeeutils.h" #include "hardware/zigbee/zigbeehardwareresource.h" @@ -86,7 +87,8 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n bool handled = false; foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { - if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceNonColourController) { + 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); @@ -100,15 +102,16 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n handled = true; } - if (endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController) { + 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->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceRemoteControl) { - qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI Symfonsik sound remote" << node << endpoint; + 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; @@ -310,13 +313,94 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) 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; + qCDebug(dcZigbeeTradfri()) << thing << "power command received" << command; if (command == ZigbeeClusterOnOff::CommandToggle) { - qCDebug(dcZigbeeTradfri()) << thing << "pressed power"; + 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) { @@ -514,109 +598,103 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod void IntegrationPluginZigbeeTradfri::initRemote(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; -// } + +// 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; +// } - // Bind basic cluster - ZigbeeDeviceObjectReply *bindRegisterReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdBasic, 0x0000); - connect(bindRegisterReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ - if (bindRegisterReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { - qCWarning(dcZigbeeTradfri()) << "Failed to bind basic cluster" << bindRegisterReply->error(); +// }); +// } + + // 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 basic cluster request finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; } - // 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; - } + // 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()) << "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(); + 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()) << "Read power configuration cluster attributes finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); } - - // 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); + // 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 power cluster to coordinator" << zdoReply->error(); + qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error(); } else { - qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; + qCDebug(dcZigbeeTradfri()) << "Bind on/off 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(); + // 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()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; } - // 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(); + // 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 { - 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"; + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) { + qCDebug(dcZigbeeTradfri()) << node << binding; } - - // 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; - } - } - }); - }); + } }); }); }); }); }); -//433 }); + }); } void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) diff --git a/zigbeetradfri/integrationpluginzigbeetradfri.json b/zigbeetradfri/integrationpluginzigbeetradfri.json index 3dbe747f..fab02bda 100644 --- a/zigbeetradfri/integrationpluginzigbeetradfri.json +++ b/zigbeetradfri/integrationpluginzigbeetradfri.json @@ -128,7 +128,7 @@ "id": "19ee3f56-f958-4b04-8ea3-747b72d7c7d6", "setupMethod": "JustAdd", "createMethods": [ "Auto" ], - "interfaces": [ "longpressmultibutton", "batterylevel", "wirelessconnectable" ], + "interfaces": [ "multibutton", "batterylevel", "wirelessconnectable" ], "paramTypes": [ { "id": "2ee18d3e-5fb8-4809-a52a-8e420b677f6e", @@ -216,21 +216,7 @@ "name": "buttonName", "displayName": "Button name", "type": "QString", - "allowedValues": ["Power"] - } - ] - }, - { - "id": "3fb1bc3b-50ea-4a9f-a5b1-10b1fae2592e", - "name": "longPressed", - "displayName": "Button longpressed", - "paramTypes": [ - { - "id": "4a151723-5798-4087-a4e5-5aa08ecbb9b8", - "name": "buttonName", - "displayName": "Button name", - "type": "QString", - "allowedValues": ["Power"] + "allowedValues": ["Power", "Up", "Down", "Left", "Right"] } ] } From 27af1627bd370493a073f1c549de347a53aa0e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 13:23:27 +0100 Subject: [PATCH 37/47] Add missing zigbee tradfri translations file --- .../translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts 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..dcd02922 --- /dev/null +++ b/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts @@ -0,0 +1 @@ + \ No newline at end of file From f9afaae1bc2c02b5091f31fd972661f9171a080c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 13:39:46 +0100 Subject: [PATCH 38/47] Add initial translations for zigbee plugins --- ...343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 302 ++++++- ...7f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts | 458 ++++++++++- ...e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts | 773 +++++++++++++++++- ...340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts | 248 +++++- ...343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 416 +++++++++- ...480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts | 416 +++++++++- 6 files changed, 2607 insertions(+), 6 deletions(-) diff --git a/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts index dcd02922..a3062199 100644 --- a/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts +++ b/zigbeegeneric/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts @@ -1 +1,301 @@ - \ No newline at end of file + + + + + 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/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts b/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts index dcd02922..41b8a875 100644 --- a/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts +++ b/zigbeegenericlights/translations/ee67f87d-d4cc-4223-a9f3-79e47c0267e0-en_US.ts @@ -1 +1,457 @@ - \ No newline at end of file + + + + + 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/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts b/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts index dcd02922..5278713f 100644 --- a/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts +++ b/zigbeelumi/translations/4a6e97da-0e4b-4731-8bfe-c93d806a9383-en_US.ts @@ -1 +1,772 @@ - \ No newline at end of file + + + + + 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/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts b/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts index dcd02922..eb467b05 100644 --- a/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts +++ b/zigbeephilipshue/translations/0ca340b7-061a-42e6-ab7d-d7b4fd235d02-en_US.ts @@ -1 +1,247 @@ - \ No newline at end of file + + + + + 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/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts index dcd02922..36abb795 100644 --- a/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts +++ b/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts @@ -1 +1,415 @@ - \ No newline at end of file + + + + + 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/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts b/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts index dcd02922..36abb795 100644 --- a/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts +++ b/zigbeetradfri/translations/ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830-en_US.ts @@ -1 +1,415 @@ - \ No newline at end of file + + + + + 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}) + + + + From 782606f666c96e472f85a096898373e9d7062b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 13:41:50 +0100 Subject: [PATCH 39/47] Remove olde translation file from tradfri --- ...343be-9fd6-4015-9ff5-38542651c534-en_US.ts | 415 ------------------ 1 file changed, 415 deletions(-) delete mode 100644 zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts diff --git a/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts b/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts deleted file mode 100644 index 36abb795..00000000 --- a/zigbeetradfri/translations/6a4343be-9fd6-4015-9ff5-38542651c534-en_US.ts +++ /dev/null @@ -1,415 +0,0 @@ - - - - - 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}) - - - - From 881ff0ebd0a50a49003ad9383aed9aecb2605092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 15:08:07 +0100 Subject: [PATCH 40/47] Add zigbee philips hue plugin documentation --- zigbeephilipshue/README.md | 59 ++++++++++++++++++++++++++++++++ zigbeephilipshue/meta.json | 15 ++++++++ zigbeephilipshue/philipshue.png | Bin 0 -> 9143 bytes 3 files changed, 74 insertions(+) create mode 100644 zigbeephilipshue/README.md create mode 100644 zigbeephilipshue/meta.json create mode 100644 zigbeephilipshue/philipshue.png diff --git a/zigbeephilipshue/README.md b/zigbeephilipshue/README.md new file mode 100644 index 00000000..3de849ad --- /dev/null +++ b/zigbeephilipshue/README.md @@ -0,0 +1,59 @@ +# 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): + + * 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 (< 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. + +The most reliable way is method 1. + +## 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. + +## Request support for a ZigBee device. + +If your Hardware is not listed and you want to request support for your device, please let us know in the [nymea forum](https://forum.nymea.io/). + +## More + + [Philips hue](http://www2.meethue.com/) 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 0000000000000000000000000000000000000000..70dd93b46ba5bede7a6e9b873aa8a9700d071db1 GIT binary patch literal 9143 zcma)hWl$VZ(=L(_B#;D&u*f0-76K&5;vr~o_XUywfdzK4;2PY5JBtK&hh3cD7TkA{ z;O_31_x-BwpIi0SJyp}w=kzl@-A|u0b8047Md>p!AtfOe78bFrjHDVC7B=`l2R`n@ z$gGhP;^FkjOiWP>3#%f6=;qtwhd%ZXwa?;MrGr$PSXj7NDhe7>3g)(0&aGIk%@5K* zSN=b`^B)}77F@T+2Yr{CewT_K%cvF%b!#oKT>cPv=AHRc zKVZ%RsSZ@=PF!i;7A7XO36O?o%GEdtSNOjO%R1BsUZzsMFN(nZQTTwADE7m4s6Do# zJrAXNAm_1j{w^c^W+lyuGi_QM|7;DzsSh=McJhbPh(itFMGoZyv5bXDeC5HPtK8_a z5d*a>`{HzcwJbrMVwr!yHBIj&syx5eoBSuKVCthBsyz|<;uh|gu^AJ{w|K5ajScK) zfz*2AKyLiA;|y8iU%57cUkCn>F9Sk=+A!s%P8a81*6)9!#hPn zIz{R85tn6e9X%y zqd<8eYf0Qg)y|`+@LcnN*(q;}k{<|6XctUs7euu3pSsh~WTc*#QNE7<{Se8gN}s2` zIS;u=R=FQL&=Pv(iB)-Vltk@UP-&v9=4$B2P)ss4&VaPEJt6up^(N`9e21n$Y`cQh zD&WIBc^Q+TRAYZsK}8;waq2_UTL(H&2e#I~+n1uRZhHT|Ec~f=4zyAK@GJ085BSKw zprn~Aw}to661dAvzg0og(7>^;K!0Qf#Ih;Eu_-#X0WQB>%zRiFS@vp5>R7W%JJ{W? zNS~fjE#p0+UMXzQBKh!C;6;_OSi|i#Ddo|?J}YJF5m}=Up;33M5d6jwuXjIH6bA4m zJ`mYIXqgH9!Ee)_UO72+2+LF3W4D!VMO;pT)z|lh>c97%MCdNoG8NPwL~n;p9fWuU z7oCE3u+Gcb|NIMT4#ZGS4=f9744-G7!v5K6Sf`bGb%pF-K%ikyjSm$keEV)u`z^b- z8>aREEG*hwSxGSsXY@{ra?tD~bvJJo-<7fDTPY#kA^UlgoWi!}-kLrq?uaZ|+xZcZO!4pEO@Jr+e-Xy=#9U?0;+6GNg~v)@v1Y;}v!1SDeMf z$oViWrE_zJ+5Ge;1tH} z6ySgh>^{CAh96}Ai?2a%&ao?A&_eT<+Hl%#^oUwFQfpOk)8bfjph8;O+*Ne}ns1!< zG1BFVtF-qB#+?Fd)-e)+!R^|oA=_8R^9$AdlQUw!J#P>-WWY~z(&9ttcfS1tivwQ*U8P(iGBIt@l&OQfQ$lryMJO zO}U-QjZne%lHrn~XTi(0FQcW&+Plj!|A@$U`nB3RWOwxPS1oE=GuHl&x3@?0J~xsY zv)H){?$Aub$CU&7*Q!R(IABC#Z1i=RX=PGi9Zl+$FP>L^rx1<{vzO2T%Xan-BpKus z%>^wFC$r+Ubs2z*WAd!6mY?3Z@obSUf{*<2VTzT%B4ILQy-T0ny?t&W98r(`zHjGS zrJa?Ne3sh2LL#!0y~3z%0fWWRBN^&Z@(zLzRt!YIS!rrZauEMU_M;t2!UlGLa;@nA zqehhj-$3p(wR(h7s|)K9OmK}%;l(RVGCv4OVFR&)2?b$to%{fFb1D$0IgN>ORtiUq zMr{WBz&5y1#|m-v8@Zs|Kscz<3Xdev|A@LgEngLwleQoAZD1EcE;jDIIWxWVR_RA7 zrOP`Z5|&+h(_U+W6cgz}g?JxO}UvlhQ|QnR>kdx`}NhpL5}<2hoeTj#|Z}oLTp} ziEK8D!%$ib3qh_j<%hOpibFA=zkVk5RwAkIaJB}cjp>Z4yo9R!4@5s+rOPXLctO|x zYF|GYCV98Xm2Zpi7VMDAASkEYv`0jp|L~b-Qk!#RmcC!Nwz@JJa=%Drg zOy{ouz%Ys$_ZCWt=Gf&cpqNbhrRxXYewUbV=H$?TwO<+`XdNgu>=9H_B&%V!H};08 zLyDvPkDufL7^V{ze=71_wQ1h^{6q|NCch#C&=`=wgZ!X?=$q}b9pf#9RPv38%?5Ot zv^>jAeaVje;4&vY1@Dv;7Og0o)->On?gI8rD8dVj0a>cYE|VP^2<8YF3_m_2TGDBcB@ z>;?x0@8)nXzQP|YD3@R_d;y9 zi8Cg6f4$|zF|Lbuu-1413XaisHBzqmG&4qwK zn-OWtezf^DFxUw)sj+3$0UmEZl&{8*lP@5{C{Fy{2b=J1iivO8Z;aVss(!%@T$wNg z>@ixS+K==m8Y_s+HF&SbVgomkb;bzUF!ErcC}Ax%2g{zzS*G#L$Jb46I$hHN^%Slk zjX5A*G_I!d2yx(fA!8?!WS>I3g^V~!7dib>*ql+rt(rhT9zE3xd;0FJE}wzCEn=P{ zfp-AODwsP((ka;fFhi>F4BgO8QKaT1NN^?uqtFm%BH+ZxSoZd->Qv_<4_Oum#;L%J zQ2T^H`l4RkGuY_jfUXOyJuYP%WIQT6^?n&#OP7J+HWG^IcZ`{Pi7t>Fi4G1EE3H+& z*N^LH&B)5!rZI%ISIz11^Z~z_0mTu?lvBC}ZsD4ja&a5-e(y%!JWo&|rZt?8AE(L* zA9Y+w`yn^7@e)0o==2Of3Z>wG8z&bflj&4`i@OvAL*vq&p26K^xo z7{NltY$uO*C>LNO1UfHJ#B9i4Jr;ddI@PsGa`~+Fq2w63CX2z6Gm<6;78K?rI!B6D zQJ2xA*A79?AqNAn)_{5UgQ9t>3tri<*oM{)$lEt^KFiUl*r|Ni84G#&Yy_@bF+kB9 z%LR=%x0y&YCDiy42`hWc&Kyw?3$E6u<&#Ss7BRCrQftd%c@|oyXfE6`9T5CWtvYw$ zjeQXp=X;htx^fE<$2AO3KKYCibxfx0CQHe^W#{a}qj*N-h|@=E&%ADMrBUY~Od)Q7mxe(U#;*+GWn3Je=e4Hcv-EiJxGKn~}+q$o%Id5DpUg|FM+ZSW<8ME+cFPAj8;@i{XoUW=Y zZfPmlKmT~%GKtg+ng8|I;&nWqe%LMd6CCAeGkUqD%YR(ouEOY<|AvI60aYn-sTsto zwAAd|2448+`O>PDCh__SZXf-&ikOft;&K^`8##F%^34~FDw%cj* z)P8rOya;)-$p38{^Z(i|_I$J=s02KT?OB-6QT?>kBCcCIPk{O_kV$u#)S?mLpGRAbkfshd;LMvR97z`4? z2kODI@}0)BR2|5y2iMu zh`Fr9-3k0Gli2&q`9GJ8;1L!vB!D zaj(9(9{y1#A!xZhgfN#HHrj7a5F~Ikd!1T~@?9!@n<1SI&_WHS%()!jwFjQ>i#Fav zk4`T}{E39h3@(>@+xWbWK6~9A#A$n;p7tPfmoE-7l>T z2s!P|HvKr?oAdJeu(+zd;CwF`_t{_2ycnZ3FHlu=yxSgVt)zkYSFqN5w$aO{oTkt3 zwVGSq2qvK`ort;lsZ(jP{?bLn~j~M}AKdtuSinjS`D>n~4 z5Nw)e(F{9pu65d<_cHWc&Rh4xts^dhrOvq={A)#}S$!m%5vCFKyjkhxqq$$+gBd6H zjq)q~?ft4FM8M_O@hlRbL1~6Z(Sy^6Y?$|GsrL0Jv3VbU%2_?W5W3>$i~)as2Zfdv zseg_MV=sLgq*z6&^842>7Hmm58R=y6=ez)%oyeyIeR~4yY1e8i$n!L%hOTv=^p%%) zx2g9((tf0kL3Hxj66jLS8fTtZKj8A%+Q0u{Hvz+x3}1{BCc|dO`hiwAU)(_>!P%8Kh;3=Oo>ID2^o-;YQ{ z3aOw-XR@iMbJCNyc1%ndsu#tsPCm90k4zFS&o>pjdJ&>O>5bknl7?mMisGQ>3850I z`7z+7>h-Q}#9u|lAf#Cl19X=Cjx$04P3hEkQau}^9uMkC{)yZtfS;#A+bicn>(Vl! zw&AT46vzDd@Rp#lvs`%tp!x3r7=V@!f#}MBm2s5b`2-*}uia>WRF@FPA7@?ix`uuy z>;sQ(uE6)l&XS{u37rPM7&0}bycT-)t-VlS?CiKqX&z70xBiV~j!9SK0@-vVswB5B zr)cuGm>d*R6zi*3FhBZ@u25vDDJ%*OLgPJoQkFuTAc4r_cl0DJ>T-je4b99*@`i(@ zQGC|*^%gDBjDYchS-$z7cpzdpR$td}fZC?srg~&~?M65WQT9M(5OS_zME{kd%M|Ak zm~r;RrcW9;g9)-Iwwlo{W7;ImDjXXdBP$9aDt&pkwF0+(JvZw{!2poJaQBJzjZQj$ zCV_wBrKZ*a7o+hdFu@*q956W!@f?a9l7;$Bbj9iR<4Q3!US6Ix2QDdhL8hQ1>EkT) z#8Ss{IhDAwb-+T%zsMj912MY8hnx1oKegTY{&JFm+=>%M3YcgTPDnCp+$(p; zobFq`tBZra%F8?EVl)};{~Tm-Oc5NF8A0>}m{~X+3^qOvcz#};L;xRhmxg@QAnxxP zMG{4?9l0C=Z(vV9V{E*m~R5^Zu4m_PF!Q8<;K*kCudtSuet! z=b8MGrX<^OJLPleaa}vzUKCcGIu&A+e78 z6!qe1NYk+lyYuP~N#lIS$ldmG!pNadso_m#NsUu^0d1Y$k6p>4bNCRA9%61vVvK`xWI}> zw}K_z@oe0IY620+5_@FCvJB>O?EZ!sSv-^}u( ze=!x2{0~$v(OIUkBrk8>B=IHz;^LnJcLeWqAG!cC*^zpa4Ww%QqozKpfF(q-1`fQB zvY4_rdxSNF8nAjQ7LJ^e3xhIA8S}`lbwAyhX2l+yEe>ps{s$*+J^*8VLpWcMWx)UW z1gyd3n%hR!WDZ{y$%4KfAG2|4{Fq;}>*^ns{?gZewwFv~4T!mQ z5&87lL}K1_?)U8{uFbTOCn|j65c3zQ7M_cIc?9sP@a;SLB=WYihULodp=#Vo4k4j5mNdBwR(gByTVw6BB z=c^=C*j!Mo*LuI(<7(ZkY!kJzAm;ZA(&a=PunP!T~Sxy)f$0|2tlscfdA3!JiN zO~iv}esn7% zB(hvMR_+bDv`hz$VWl_Ud-}ay=)E-C36eM(dR&U~7Rk!eXQJ*qIhO?U*cvw}zPDY* z+Z45$OKuQjLr^AU*Ty?30@_Njm(~od8#?;Vp1)Pg$*X>0?Z&KQ7y7iZVi}L{!n*SX z`#|cLrTXP|V?GR>0*9qw9QW9gwL=zyjdllXSma*zX8eta(9V#jNH_1E?;6i$)*P(R ztZ`5^`7O_Q7u5Gi*c#%T(A|&cLw|{%DK`-k)DwyCoEo6*N3`fGQO)90(@IBY-|au2jflHnrXsmgWYwO zRVmqNX5L^Nb?tK^9AhenOtnkq@&3yJ!gN1>%grAN-B@aZL-V`4cr6Z7mm(geE-7Cw zQ~5Ay>cq@A)H&}W>A1B6!9H; zy`yhp@aH0g!Q27-mSg1oyrIf zhKtUr;9;ae&$$Tqb@zIB)9=Ku4icn3Lxa4tAuN7}0A*b)9ZfB5 zGc)yqKNuLhFx-_$aL_ihij1~hY8jU9I<}vzt z7xlxPe{AocvF}yZ6OTiA2<`j)jW*sn9G@qE3ci!=ysZVT?z>hh$ZCDE;+~7$){i}M z#D+7+8J4Sg!*+MmdZe{F=Sop7)ynnKHXN<~FgG3a0q`^)wu6gUYgB~91g?|baqP@F z&_S$lTINmJPM^@S!YwKj{zW6x-PFOJBhTM0_BdI1Z0RW_@6k)eRV3; z?_-*l=a+bkSvI^YZVdh2SI)I)=p(m0AhzWV3qqQX3opxoPhQ}fma<1mg9rGUqM^7U z3ax}P{n%xrrthoWcHm42l}UAD{bd|?VN7qSb}5FDgSTf~>W~TNDU6j%ENNnD(S41R z!w1FGZx{ORQPNWeVNo_COv6Erey=}QN&~^$(706ryaZR zUpFndc=pJ!@5Rnoc^x_n6obI@snO~XwF13OB-}_9 zCzowS*j&~1iV0XHx4TBFNE-4)IQh2=W59Gun72cK(lmQyHi1bt zJd3;2AKHGEx8VV3h4j2*U{^4X@HZA#W)*{PwOc0=idJehgSkZtrU~#X7>siR)2%SBSI=Lrrlqu<*x#edYl5i|P^gx_Z=O;dD5Wd@Z#QtU7NNI;2jmMPfRLLkWP#;lgxDUGq{Ow*;64L--wo zM?uqjsz_pvv9C}Mks-P^fXcH|=_wg>y`AFGINr$8qSJV?CrV{5Gx`|w(<1O&_mj<& z>?Ktk2Q66)SPj12-*37IBtp4Ou}ro(@@fv!8Nd(B_~I1QIJ`GsZrH`k?H1x{gi&Oj znr$ibE(S?%3`LDDVO}1zQbfNPC0oWo3~28fF|Cm=U(ya9w*_Th*vhq^yvX;b>C*tIs+MGoO0( z-oT=P8w_R&M&g?GKv+6B)O?VgokgiufnLs>rR{X@>O2D*oE(f9>R-HFeR+%yJ|C#r zi>HY>QtA<@QeLX3P_JunkY4{JZ{7@+&l%l5WsPH%w}ciqeH>NSKRcjH1t~F2i(aiO ze3a3S1@^{7zwlV#&jZKv1?3cJ3keC$9n{s=W8VXzH7>g=T{^+&9;KL9^3HP=m&c@O z6BBQQ1O-zDY4WPF>SlEGZ_e0fN-myg!G}=lkS!xMkVX5?QqDFMyg!uDTAo}hUQ0_$ zTT5FOEi;)J$=heyF@se8PA**7C;#xDqg-G$sRxsql;n~MbV^XgnBSgR_*Tzp{GsG$ z+AS5{rw~|cbg_uyDB~_>=TPk^c@A{J{9axAuG6kycZKQ%)Mkd3N?K$UeKkdb@76KM zD``6At9?2t_NU=Bx1CZ1>x@k+pS%nsRUnb-fOg6OfHJ&E;_pPq z_{fH|CdY9OHqF0+-O9i3310UV8JGmz8k-v8h>Wky^!tqomtc$B`r3%ctR57#9mbG{6AoH~YQ~Tod5+bqwQNbL-)D;=1%k za-B^VU4GHe5crnS1oE>dfiRW@y#LSP<9y`) z=2nK8LJ3;IXOqfnJc7p*u5OOqTzV6%hRH}x4GOb@&k)c?u;X3N30k3%DZfrb-PKJWJjC zm}>2^GfGMp`>kfAmMKFQU{$I{q3Vz_%!gvxgH%OL?4pUQ&F@s|;kN;!*m)JDsc#m$ zi+t!_n%n9gZ}gnH$LVFW$}}C{jHJqjF+H|O?ab`Q4&e~BIu!bAg(`^&!0et~vM1jL zhh}11aV7C=t%Q^;=2?X5;o+rFOEY|sdh#)Jv3+`Wj9X0=lm8ZJzSOkQo4L>UY%jJ2 z_p%KksIUFIAJhZ-O%kdk}NBwBv~r{)#v{J&2!!l literal 0 HcmV?d00001 From 54a710db00926911fc515dcc138fb2fedfac6485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 15:21:37 +0100 Subject: [PATCH 41/47] Remove support request since documented in the overall intergrations section --- zigbeephilipshue/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/zigbeephilipshue/README.md b/zigbeephilipshue/README.md index 3de849ad..50d064c9 100644 --- a/zigbeephilipshue/README.md +++ b/zigbeephilipshue/README.md @@ -13,7 +13,6 @@ The [Hue motion sensor](https://www.philips-hue.com/en-us/p/hue-motion-sensor/04 **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. @@ -50,10 +49,6 @@ The most reliable way is method 1. * 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. -## Request support for a ZigBee device. - -If your Hardware is not listed and you want to request support for your device, please let us know in the [nymea forum](https://forum.nymea.io/). - ## More [Philips hue](http://www2.meethue.com/) From 78ae7db224c0be98c4d7f0832aab6abdb89e5b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 16:01:55 +0100 Subject: [PATCH 42/47] Add zigbee tradfri plugin documentation --- zigbeephilipshue/README.md | 2 +- zigbeetradfri/README.md | 59 +++++++++++++++++++++++++++++ zigbeetradfri/ikea.svg | 76 ++++++++++++++++++++++++++++++++++++++ zigbeetradfri/meta.json | 15 ++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 zigbeetradfri/README.md create mode 100644 zigbeetradfri/ikea.svg create mode 100644 zigbeetradfri/meta.json diff --git a/zigbeephilipshue/README.md b/zigbeephilipshue/README.md index 50d064c9..b313e17a 100644 --- a/zigbeephilipshue/README.md +++ b/zigbeephilipshue/README.md @@ -1,4 +1,4 @@ -# Zigbee Philips Hue +# ZigBee Philips Hue This plugin allows to interact with Philips Hue ZigBee devices using a native ZigBee network controller in nymea. diff --git a/zigbeetradfri/README.md b/zigbeetradfri/README.md new file mode 100644 index 00000000..13d738e4 --- /dev/null +++ b/zigbeetradfri/README.md @@ -0,0 +1,59 @@ +# 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 Motion sensor + +The [Remote control](https://www.ikea.com/us/en/p/tradfri-wireless-motion-sensor-white-60377655/) 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. + +**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. + +**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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/zigbeetradfri/meta.json b/zigbeetradfri/meta.json new file mode 100644 index 00000000..14c5106a --- /dev/null +++ b/zigbeetradfri/meta.json @@ -0,0 +1,15 @@ +{ + "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" + ] +} From 6c14d5ec6977f0a3d60e0f062c15d55a0f9adb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 17:23:21 +0100 Subject: [PATCH 43/47] Add zigbee-generic-lights plugin documentation --- zigbeegenericlights/README.md | 55 ++++++++++ zigbeegenericlights/meta.json | 13 +++ zigbeegenericlights/zigbee.svg | 179 +++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 zigbeegenericlights/README.md create mode 100644 zigbeegenericlights/meta.json create mode 100644 zigbeegenericlights/zigbee.svg 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/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/zigbee.svg b/zigbeegenericlights/zigbee.svg new file mode 100644 index 00000000..5366feb6 --- /dev/null +++ b/zigbeegenericlights/zigbee.svg @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + From fb9f4338d436d540a3ae3005c6fcb6193aa839e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 16 Dec 2020 17:41:04 +0100 Subject: [PATCH 44/47] Add zigbee generic plugin documentation --- zigbeegeneric/README.md | 25 ++++++ zigbeegeneric/meta.json | 12 +++ zigbeegeneric/zigbee.svg | 179 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 zigbeegeneric/README.md create mode 100644 zigbeegeneric/meta.json create mode 100644 zigbeegeneric/zigbee.svg diff --git a/zigbeegeneric/README.md b/zigbeegeneric/README.md new file mode 100644 index 00000000..5f4dd873 --- /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 pairing/setup 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/meta.json b/zigbeegeneric/meta.json new file mode 100644 index 00000000..f58cc6e8 --- /dev/null +++ b/zigbeegeneric/meta.json @@ -0,0 +1,12 @@ +{ + "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": [ + ] +} 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + From 88ae369340c9397d15b5e19f2c46ef8482bff1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 17 Dec 2020 08:55:09 +0100 Subject: [PATCH 45/47] Add more zigbee plugin documentation --- zigbeegeneric/README.md | 2 +- zigbeegeneric/meta.json | 1 + zigbeelumi/README.md | 112 +++++++++++++++++++++++ zigbeelumi/meta.json | 16 ++++ zigbeelumi/zigbee.svg | 179 +++++++++++++++++++++++++++++++++++++ zigbeephilipshue/README.md | 7 +- zigbeetradfri/README.md | 29 +++++- zigbeetradfri/meta.json | 3 +- 8 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 zigbeelumi/README.md create mode 100644 zigbeelumi/meta.json create mode 100644 zigbeelumi/zigbee.svg diff --git a/zigbeegeneric/README.md b/zigbeegeneric/README.md index 5f4dd873..0fb2067c 100644 --- a/zigbeegeneric/README.md +++ b/zigbeegeneric/README.md @@ -16,7 +16,7 @@ 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 pairing/setup button multiple times of keeping it pressed for several seconds. +> 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 diff --git a/zigbeegeneric/meta.json b/zigbeegeneric/meta.json index f58cc6e8..5b692488 100644 --- a/zigbeegeneric/meta.json +++ b/zigbeegeneric/meta.json @@ -8,5 +8,6 @@ "zigbee" ], "categories": [ + "socket" ] } 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/meta.json b/zigbeelumi/meta.json new file mode 100644 index 00000000..21aaf349 --- /dev/null +++ b/zigbeelumi/meta.json @@ -0,0 +1,16 @@ +{ + "title": "ZigBee Lumi / Aqara / Xiaomi Mi", + "tagline": "Connect and control ZigBee devices from Lumi / Aquara / Xiaomi Mi native.", + "icon": "zigbee.svg", + "stability": "consumer", + "offline": true, + "technologies": [ + "zigbee" + ], + "categories": [ + "sensor", + "switch", + "relay", + "socket" + ] +} 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/zigbeephilipshue/README.md b/zigbeephilipshue/README.md index b313e17a..593822b8 100644 --- a/zigbeephilipshue/README.md +++ b/zigbeephilipshue/README.md @@ -6,6 +6,7 @@ This plugin allows to interact with Philips Hue ZigBee devices using a native Zi 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. @@ -27,7 +28,7 @@ The [Hue dimmer switch](https://www.philips-hue.com/en-us/p/hue-dimmer-switch/04 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): +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). @@ -42,12 +43,12 @@ Most of the lights and lamps from Philips Hue should be handled in a generic way * 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. -The most reliable way is method 1. ## 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. +* *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 diff --git a/zigbeetradfri/README.md b/zigbeetradfri/README.md index 13d738e4..510ecc73 100644 --- a/zigbeetradfri/README.md +++ b/zigbeetradfri/README.md @@ -6,6 +6,8 @@ This plugin allows to interact with IKEA TRÅDFRI ZigBee devices using a native 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. @@ -14,6 +16,8 @@ The [Wireless dimmer](https://www.ikea.com/us/en/p/tradfri-wireless-dimmer-white **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. @@ -22,12 +26,24 @@ The [Remote control](https://www.ikea.com/us/en/p/tradfri-remote-control-0044313 **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/) 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. + +### 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. @@ -35,6 +51,7 @@ The [Signal repeater](https://www.ikea.com/us/en/p/tradfri-signal-repeater-30400 **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. @@ -42,13 +59,21 @@ The [Control outlet](https://www.ikea.com/us/en/p/tradfri-wireless-control-outle **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). diff --git a/zigbeetradfri/meta.json b/zigbeetradfri/meta.json index 14c5106a..f5f3e15f 100644 --- a/zigbeetradfri/meta.json +++ b/zigbeetradfri/meta.json @@ -10,6 +10,7 @@ "categories": [ "light", "sensor", - "switch" + "switch", + "socket" ] } From 7fa9e7cdb6545db58fc128d7c3cdca1623e816d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 17 Dec 2020 11:48:31 +0100 Subject: [PATCH 46/47] Fix markdown parser error in zigbee philips hue documentation --- zigbeephilipshue/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigbeephilipshue/README.md b/zigbeephilipshue/README.md index 593822b8..bf1e7994 100644 --- a/zigbeephilipshue/README.md +++ b/zigbeephilipshue/README.md @@ -33,7 +33,7 @@ Most of the lights and lamps from Philips Hue should be handled in a generic way * 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 (< 5 cm). + * 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. From 076c2da5a869b50a3b2df3709cec5c690f0789d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 17 Dec 2020 15:46:40 +0100 Subject: [PATCH 47/47] Fix typo in lumi plugin meta.json --- zigbeelumi/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigbeelumi/meta.json b/zigbeelumi/meta.json index 21aaf349..99ec7cfc 100644 --- a/zigbeelumi/meta.json +++ b/zigbeelumi/meta.json @@ -1,6 +1,6 @@ { "title": "ZigBee Lumi / Aqara / Xiaomi Mi", - "tagline": "Connect and control ZigBee devices from Lumi / Aquara / Xiaomi Mi native.", + "tagline": "Connect and control ZigBee devices from Lumi / Aqara / Xiaomi Mi native.", "icon": "zigbee.svg", "stability": "consumer", "offline": true,