@@ -43,6 +43,7 @@ import kotlinx.coroutines.launch
4343import kotlinx.coroutines.sync.Mutex
4444import kotlinx.coroutines.withTimeout
4545import java.util.UUID
46+ import java.util.concurrent.ConcurrentHashMap
4647
4748// -------------------------------------------------------------------------------------------------
4849// GATT adapter (BLE)
@@ -66,7 +67,14 @@ class GattScaleAdapter(
6667
6768 private val opQueue = Channel < suspend () -> Unit > (Channel .UNLIMITED )
6869
69- private val deferredMap = mutableMapOf<UUID , CompletableDeferred <Unit >>()
70+ private data class PendingOp (
71+ val id : Long ,
72+ val deferred : CompletableDeferred <Unit >
73+ )
74+
75+ private val deferredMap = ConcurrentHashMap <UUID , PendingOp >()
76+ private var nextOpId = 0L
77+
7078 private val ioMutex = Mutex ()
7179
7280 private var connectAttempts = 0
@@ -75,6 +83,11 @@ class GattScaleAdapter(
7583 // Worker coroutine processes queued BLE operations sequentially
7684 scope.launch {
7785 for (op in opQueue) {
86+ // wait until BLE connection is established
87+ while (! _isConnected .value) {
88+ delay(10 )
89+ }
90+
7891 try {
7992 ioMutex.lock()
8093 op()
@@ -185,8 +198,8 @@ class GattScaleAdapter(
185198 ) {
186199 LogManager .d(TAG ," \u2190 write response chr=${characteristic.uuid} len=${value.size} status=${status} ${value.toHexPreview(24 )} " )
187200
188- deferredMap[characteristic.uuid]?.let {
189- it .complete(Unit )
201+ deferredMap[characteristic.uuid]?.let { op ->
202+ op.deferred .complete(Unit )
190203 deferredMap.remove(characteristic.uuid)
191204 }
192205 }
@@ -198,8 +211,8 @@ class GattScaleAdapter(
198211 ) {
199212 LogManager .d(TAG ," \u2190 notify state chr=${characteristic.uuid} status=${status} " )
200213
201- deferredMap[characteristic.uuid]?.let {
202- it .complete(Unit )
214+ deferredMap[characteristic.uuid]?.let { op ->
215+ op.deferred .complete(Unit )
203216 deferredMap.remove(characteristic.uuid)
204217 }
205218 }
@@ -214,8 +227,8 @@ class GattScaleAdapter(
214227
215228 handler.handleNotification(characteristic.uuid, value)
216229
217- deferredMap[characteristic.uuid]?.let {
218- it .complete(Unit )
230+ deferredMap[characteristic.uuid]?.let { op ->
231+ op.deferred .complete(Unit )
219232 deferredMap.remove(characteristic.uuid)
220233 }
221234 }
@@ -231,14 +244,16 @@ class GattScaleAdapter(
231244 val p = currentPeripheral ? : return @trySend
232245 LogManager .d(TAG , " → set notify on chr=$characteristic svc=$service " )
233246
247+ val opId = ++ nextOpId
234248 val deferred = CompletableDeferred <Unit >()
235- deferredMap[characteristic] = deferred
249+ deferredMap[characteristic] = PendingOp (opId, deferred)
236250
237251 val started = p.startNotify(service, characteristic)
238252 if (! started) {
239253 LogManager .w(TAG , " Failed to initiate notify for $characteristic " )
240254 // appCallbacks.onWarn(R.string.bt_warn_notify_failed, characteristic.toString()) // don't show message to the user
241255 deferred.complete(Unit )
256+ deferredMap.remove(characteristic)
242257 }
243258
244259 try {
@@ -248,6 +263,12 @@ class GattScaleAdapter(
248263 }
249264 } catch (e: Exception ) {
250265 LogManager .w(TAG , " Timeout waiting for notify on $characteristic " )
266+ } finally {
267+ val current = deferredMap[characteristic]
268+ if (current?.id == opId) {
269+ deferredMap.remove(characteristic)
270+ }
271+ deferred.cancel()
251272 }
252273
253274 ioGap(tuning.notifySetupDelayMs)
@@ -259,8 +280,9 @@ class GattScaleAdapter(
259280 val p = currentPeripheral ? : return @trySend
260281 val ch = p.getCharacteristic(service, characteristic) ? : return @trySend
261282
283+ val opId = ++ nextOpId
262284 val deferred = CompletableDeferred <Unit >()
263- deferredMap[characteristic] = deferred
285+ deferredMap[characteristic] = PendingOp (opId, deferred)
264286
265287 val supportsWriteNoResponse = ch.properties and BluetoothGattCharacteristic .PROPERTY_WRITE_NO_RESPONSE != 0
266288 val supportsWriteResponse = ch.properties and BluetoothGattCharacteristic .PROPERTY_WRITE != 0
@@ -293,6 +315,12 @@ class GattScaleAdapter(
293315 }
294316 } catch (t: Throwable ) {
295317 LogManager .w(TAG , " Timeout waiting for write on $characteristic " )
318+ } finally {
319+ val current = deferredMap[characteristic]
320+ if (current?.id == opId) {
321+ deferredMap.remove(characteristic)
322+ }
323+ deferred.cancel()
296324 }
297325
298326 ioGap(tuning.postWriteDelayMs)
@@ -304,8 +332,9 @@ class GattScaleAdapter(
304332 val p = currentPeripheral ? : return @trySend
305333 val ch = p.getCharacteristic(service, characteristic) ? : return @trySend
306334
335+ val opId = ++ nextOpId
307336 val deferred = CompletableDeferred <Unit >()
308- deferredMap[characteristic] = deferred
337+ deferredMap[characteristic] = PendingOp (opId, deferred)
309338
310339 p.readCharacteristic(service, characteristic)
311340
@@ -317,6 +346,12 @@ class GattScaleAdapter(
317346 }
318347 } catch (t: Throwable ) {
319348 LogManager .w(TAG , " Timeout waiting for read on $characteristic " )
349+ } finally {
350+ val current = deferredMap[characteristic]
351+ if (current?.id == opId) {
352+ deferredMap.remove(characteristic)
353+ }
354+ deferred.cancel()
320355 }
321356
322357 ioGap(tuning.postReadDelayMs)
0 commit comments