Skip to content

Commit 7a66f05

Browse files
committed
Merge remote-tracking branch 'origin/dev3' into 3422-master/GNTM-dev
# Conflicts: # buildSrc/src/main/kotlin/Versions.kt
2 parents 2ea08a2 + c28448d commit 7a66f05

4 files changed

Lines changed: 71 additions & 8 deletions

File tree

.circleci/config.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ jobs:
4242
emulator -avd citest -delay-adb -verbose -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
4343
background: true
4444

45+
- run:
46+
name: Wait for emulator boot
47+
command: |
48+
export ANDROID_SDK_ROOT=/usr/lib/android-sdk
49+
export ANDROID_HOME=/usr/lib/android-sdk
50+
# Boot gate: the emulator above is launched in the background, so without this the test task
51+
# can start before the device is online and fail with the misleading
52+
# "com.android.builder.testing.api.DeviceException: No online devices found".
53+
# Block until adb sees the device AND the system has fully booted, with a hard timeout so a
54+
# stuck emulator fails loudly HERE (clear message) instead of later.
55+
adb wait-for-device
56+
timeout 420 bash -c 'until [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d "\r")" = "1" ]; do sleep 2; done'
57+
adb shell getprop sys.boot_completed
58+
# Settle, dismiss keyguard and disable animations so ComposeMainActivity launches cleanly:
59+
# a heavy UI launch on an unsettled emulator can ANR system_server and drop the device offline
60+
# mid-run (the failure mode this hardening targets).
61+
adb shell input keyevent 82 || true
62+
adb shell settings put global window_animation_scale 0 || true
63+
adb shell settings put global transition_animation_scale 0 || true
64+
adb shell settings put global animator_duration_scale 0 || true
65+
adb devices
66+
4567
- run:
4668
name: Run connectedFullDebugAndroidTest
4769
command: |

.github/workflows/aaps-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ on:
66
tagName:
77
description: 'Select AAPS Version'
88
required: true
9-
default: '3.4.2.4'
9+
default: '3.4.2.5-dev'
1010
type: choice
1111
options:
1212
# ── 3.4.x (JDK 21) ──────────────────────────────
13+
- 3.4.2.5
1314
- 3.4.2.4
1415
- 3.4.2.3
1516
- 3.4.2.2

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
55
object Versions {
66

77
// On change edit aaps-ci.yml
8-
const val appVersion = "3.4.2.4dev+aisf3.2.1"
8+
const val appVersion = "3.4.2.5dev+aisf3.2.1"
99
const val versionCode = 1500
1010

1111
const val compileSdk = 36

pump/equil/src/main/kotlin/app/aaps/pump/equil/ble/EquilBLE.kt

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ class EquilBLE @Inject constructor(
123123
isConnected = true
124124
equilManager.equilState?.bluetoothConnectionState = BluetoothConnectionState.CONNECTED
125125
handler.removeMessages(TIME_OUT_CONNECT_WHAT)
126+
synchronized(notifyLock) {
127+
// New link: notifications not yet enabled. Block command dispatch until onDescriptorWrite.
128+
notificationEnabled = false
129+
pendingCmd = null
130+
}
126131
bluetoothGatt?.discoverServices()
127132
updateCmdStatus(ResolvedResult.FAILURE)
128133
// rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED));
@@ -175,7 +180,14 @@ class EquilBLE @Inject constructor(
175180
aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorWrite received: $status")
176181
if (status == BluetoothGatt.GATT_SUCCESS) {
177182
aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorWrite: Wrote GATT Descriptor successfully.")
178-
ready()
183+
synchronized(notifyLock) {
184+
notificationEnabled = true
185+
// Flush a command that writeCmd deferred while notifications were coming up.
186+
if (pendingCmd != null) {
187+
pendingCmd = null
188+
ready()
189+
}
190+
}
179191
}
180192
}
181193
}
@@ -236,6 +248,10 @@ class EquilBLE @Inject constructor(
236248
bluetoothGatt = null
237249
baseCmd = null
238250
preCmd = null
251+
synchronized(notifyLock) {
252+
notificationEnabled = false
253+
pendingCmd = null
254+
}
239255
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
240256
}
241257

@@ -267,6 +283,21 @@ class EquilBLE @Inject constructor(
267283

268284
private var baseCmd: BaseCmd? = null
269285
private var preCmd: BaseCmd? = null
286+
287+
// Notification-readiness gate for the current GATT connection. Android allows only ONE outstanding
288+
// GATT operation at a time. When the queue's connect() phase opens the link, `isConnected` flips
289+
// true at onConnectionStateChange(CONNECTED) - BEFORE onServicesDiscovered runs openNotification()
290+
// (the notify-descriptor write). If a command's writeCmd then writes its first characteristic packet
291+
// in that window, it collides with the pending descriptor write: writeDescriptor() returns false
292+
// (log: "openNotification: false"), notifications never enable, the pump's replies never arrive, and
293+
// the command idle-times-out after ~9 s -> "Pump connection failure / manually check delivered
294+
// insulin" (bolus, tempBasal, and profile/CmdSettingSet all hit this via different writeCmd branches).
295+
// Fix: never send on a connected link until onDescriptorWrite confirms notifications are enabled;
296+
// hold the command in `pendingCmd` and let onDescriptorWrite flush it. See #4910 (and its follow-up).
297+
private val notifyLock = Any()
298+
@Volatile private var notificationEnabled = false
299+
private var pendingCmd: BaseCmd? = null
300+
270301
fun writeCmd(baseCmd: BaseCmd) {
271302
aapsLogger.debug(LTag.PUMPCOMM, "writeCmd {}", baseCmd)
272303
this.baseCmd = baseCmd
@@ -276,6 +307,18 @@ class EquilBLE @Inject constructor(
276307
else -> equilManager?.equilState?.address ?: error("Unknown MAC address")
277308
}
278309
autoScan = baseCmd is CmdRunningModeGet || baseCmd is CmdInsulinGet
310+
if (isConnected) {
311+
synchronized(notifyLock) {
312+
if (!notificationEnabled) {
313+
// Fresh link, notifications not enabled yet: defer ALL send paths (pair step,
314+
// continuation, or first command) so the characteristic write does not collide with
315+
// the openNotification() descriptor write. onDescriptorWrite flushes pendingCmd.
316+
pendingCmd = baseCmd
317+
preCmd = baseCmd
318+
return
319+
}
320+
}
321+
}
279322
if (isConnected && baseCmd.isPairStep()) {
280323
ready()
281324
} else if (isConnected) {
@@ -285,11 +328,8 @@ class EquilBLE @Inject constructor(
285328
baseCmd.runPwd = prevCmd.runPwd
286329
nextCmd2()
287330
} else {
288-
// The GATT link was opened by the queue's connect() phase, which leaves no prior
289-
// command context (baseCmd/preCmd are null, so the connect-time ready() was a no-op).
290-
// Send this command as the first one on the open connection instead of silently
291-
// dropping it - otherwise the pump receives nothing and idle-disconnects (status 19),
292-
// surfacing as a bolus/command timeout. See issue #4910.
331+
// GATT link opened by the queue's connect() phase, notifications already up: send this
332+
// command as the first one on the open link (else the pump idle-disconnects, status 19).
293333
ready()
294334
}
295335
} else {

0 commit comments

Comments
 (0)