Add mre tradfri devices for testing
This commit is contained in:
parent
b48e1245ab
commit
0946e04ebb
1
debian/nymea-plugin-zigbee-tradfri.install.in
vendored
Normal file
1
debian/nymea-plugin-zigbee-tradfri.install.in
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-tradfri.so
|
||||
@ -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)
|
||||
|
||||
@ -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": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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<ZigbeeClusterBasic>(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<ZigbeeClusterOnOff>(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<ZigbeeClusterLevelControl>(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<ZigbeeClusterPowerConfiguration>(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<ZigbeeClusterOnOff>(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<ZigbeeClusterPowerConfiguration>(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<ZigbeeClusterOnOff>(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<ZigbeeClusterPowerConfiguration>(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<quint8>(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<quint8>(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<quint8>(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";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ public:
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
private:
|
||||
PluginTimer *m_presenceTimer = nullptr;
|
||||
QHash<Thing*, ZigbeeNode*> m_thingNodes;
|
||||
|
||||
QHash<ThingClassId, ParamTypeId> m_ieeeAddressParamTypeIds;
|
||||
@ -68,6 +69,17 @@ private:
|
||||
QHash<ThingClassId, StateTypeId> 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user