/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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) }