From ca3a1686d321a882ec9335e741fb35f6472d3052 Mon Sep 17 00:00:00 2001 From: Bill Bierman Date: Tue, 9 Dec 2025 09:15:07 -1000 Subject: [PATCH 1/2] Added option to delay motion notification until snapshot available --- src/apps/unofficial-ring-connect.groovy | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/apps/unofficial-ring-connect.groovy b/src/apps/unofficial-ring-connect.groovy index 4c3bcc6..49dc1ae 100644 --- a/src/apps/unofficial-ring-connect.groovy +++ b/src/apps/unofficial-ring-connect.groovy @@ -193,6 +193,8 @@ def mainPage() { paragraph('Reset Compromised OAuth Access Token') paragraph('Do not toggle this button without understanding the following. Resetting this token will require you to manually update ALL of the URLs in any existing dashboard tile any IFTTT applet. There is no need to reset the token unless it was compromised.') input name: "tokenReset", type: "bool", title: "Toggle this to reset your app's OAuth token", defaultValue: false, submitOnChange: true + paragraph('Enabling this will DELAY motion event notifications until a snapshot is available from the triggering device.') + input name: "delayMotion", type: "bool", title: "Delay motion events until snapshot ready", defaultValue: false, submitOnChange: true } donationPageSection() @@ -483,7 +485,19 @@ void processIFTTT() { } if (kind == "motion") { - d.handleMotion(json) + if (delayMotion) { + logDebug "Delaying motion for ${json.id} until snapshot is ready json ${json} type ${json.class}" + + final Integer deviceId = getRingDeviceId(json.id).toInteger() + state.pendingMotion = state.pendingMotion ?: [:] + state.pendingMotion[deviceId] = json // store the event payload + + // Request snapshot immediately + apiRequestSnapshotTimestamps([deviceId]) + } else { + logDebug "Immediate motion trigger for ${json.id}" + d.handleMotion(json) + } } else if (kind == "ding") { d.handleDing(json) } else { @@ -1278,6 +1292,16 @@ void apiRequestSnapshotImages(final Map data) { byte[] retval = new byte[resp.data.available()] resp.data.read(retval) state.snapshots[getFormattedDNI(localDoorbotId)] = retval + final String idString = doorbotId.toString() + def pending = state.pendingMotion?.get(idString) + if (pending) { + logDebug "Releasing delayed motion event for ${doorbotId} pending ${pending} type ${pending.class}" + state.pendingMotion.remove(idString) + ChildDeviceWrapper d = getChildDeviceInternal(pending.id) + d.handleMotion(pending) + } else { + logDebug "No pending event for ${doorbotId}" + } } } } From 8f1cbd7fd3911b57722d3c1b70320bf38312ea7f Mon Sep 17 00:00:00 2001 From: Bill Bierman Date: Tue, 9 Dec 2025 09:15:40 -1000 Subject: [PATCH 2/2] Fixed logic in initializing dingable devices --- src/apps/unofficial-ring-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/unofficial-ring-connect.groovy b/src/apps/unofficial-ring-connect.groovy index 49dc1ae..f03342c 100644 --- a/src/apps/unofficial-ring-connect.groovy +++ b/src/apps/unofficial-ring-connect.groovy @@ -628,7 +628,7 @@ def uninstalled() { } void setupDingables() { - state.dingables = getChildDevices()?.findAll { RINGABLES.contains(it.getDataValue("kind")) }?.collect { getRingDeviceId(it.deviceNetworkId) } + state.dingables = getChildDevices()?.findAll { DINGABLES.contains(it.getDataValue("kind")) }?.collect { getRingDeviceId(it.deviceNetworkId) } } void configureDingPolling() {