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",