Skip to content

Commit 941c8ad

Browse files
authored
Make services direct references and match properties for I/O (#238)
Previously, all I/O operations would lazily search for the first match when looking up characteristics or descriptors. This was problematic if duplicate UUIDs existed. Discovered GATT profile items (services, characteristics and descriptors) as provided via `Peripheral.services` are now direct references to underlying platform types. This means they no longer trigger lookups for I/O operations and specific GATT profile items can be sought after and used. When using `characteristicOf`, the properties of the characteristic are now taken into account when searching for the underlying platform object. For example, when performing a read, only a characteristic with a matching UUID **and** `read` property will be considered a match.
1 parent dc522f3 commit 941c8ad

26 files changed

Lines changed: 653 additions & 632 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
build/
44
.DS_Store
55
local.properties
6+
kotlin-js-store/

README.md

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,15 @@ For example, a peripheral might have the following structure:
299299
- Service S2
300300
- Characteristic C3
301301

302-
To access a characteristic or descriptor, use the [`charactisticOf`] or [`descriptorOf`] functions, respectively.
302+
To access a characteristic or descriptor, use the [`charactisticOf`] or [`descriptorOf`] functions, respectively. These
303+
functions lazily search for the first match (based on UUID) in the GATT profile when performing I/O.
303304

304-
In the above example, to access "Descriptor D3":
305+
_When performing I/O operations on a characteristic ([`read`], [`write`], [`observe`]), the properties of the
306+
characteristic are taken into account when finding the first match. For example, when performing a [`write`] with a
307+
[`WriteType`] of [`WithResponse`], the first characteristic matching the expected UUID **and** having the
308+
[`writeWithResponse`] property will be used._
309+
310+
In the above example, to lazily access "Descriptor D3":
305311

306312
```kotlin
307313
val descriptor = descriptorOf(
@@ -311,7 +317,29 @@ val descriptor = descriptorOf(
311317
)
312318
```
313319

314-
Once connected, data can be read from, or written to, characteristics and/or descriptors via [`read`] and [`write`]
320+
Alternatively, a characteristic or descriptor may be obtained by traversing the [`Peripheral.services`]. This is useful
321+
when multiple characteristics or descriptors have the same UUID. Objects obtained from the [`Peripheral.services`] hold
322+
strong references to the underlying platform types, so special care must be taken to properly remove references to
323+
objects retrieved from [`Peripheral.services`] when no longer needed.
324+
325+
To access "Descriptor D3" using a discovered descriptor:
326+
327+
```kotlin
328+
val services = peripheral.services ?: error("Services have not been discovered")
329+
val descriptor = services
330+
.first { it.serviceUuid == uuidFrom("00001815-0000-1000-8000-00805f9b34fb") }
331+
.characteristics
332+
.first { it.characteristicUuid == uuidFrom("00002a56-0000-1000-8000-00805f9b34fb") }
333+
.descriptors
334+
.first { it.descriptorUuid == uuidFrom("00002902-0000-1000-8000-00805f9b34fb") }
335+
```
336+
337+
_This example uses a similar search algorithm as `descriptorOf`, but other search methods may be utilized. For example,
338+
properties of the characteristic could be queried to find a specific characteristic that is expected to be the parent of
339+
the sought after descriptor. When searching for a specific characteristic, descriptors can be read that may identity the
340+
sought after characteristic._
341+
342+
When connected, data can be read from, or written to, characteristics and/or descriptors via [`read`] and [`write`]
315343
functions.
316344

317345
_The [`read`] and [`write`] functions throw [`NotReadyException`] until a connection is established._
@@ -338,10 +366,14 @@ observation.collect { data ->
338366
}
339367
```
340368

341-
The [`observe`] function can be called (and its returned [`Flow`] can be collected) prior to a connection being
342-
established. Once a connection is established then characteristic changes will stream from the [`Flow`]. If the
343-
connection drops, the [`Flow`] will remain active, and upon reconnecting it will resume streaming characteristic
344-
changes.
369+
When used with [`characteristicOf`], the [`observe`] function can be called (and its returned [`Flow`] can be collected)
370+
prior to a connection being established. Once a connection is established then characteristic changes will stream from
371+
the [`Flow`]. If the connection drops, the [`Flow`] will remain active, and upon reconnecting it will resume streaming
372+
characteristic changes.
373+
374+
A [`Characteristic`] may also be obtained via the [`Peripheral.services`] property and used with the [`observe`]
375+
function. As before, if the connection drops, the [`Flow`] will remain active, upon reconnecting the same underlying
376+
platform characteristic will be used to to resume streaming characteristic changes.
345377

346378
Failures related to notifications/indications are propagated via the [`observe`] [`Flow`], for example, if the
347379
associated characteristic is invalid or cannot be found, then a `NoSuchElementException` is propagated via the
@@ -515,30 +547,35 @@ limitations under the License.
515547
[`ScanSettings`]: https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings
516548

517549
[`Advertisement`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-advertisement/index.html
518-
[`advertisements`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-scanner/index.html#%5Bcom.juul.kable%2FScanner%2Fadvertisements%2F%23%2FPointingToDeclaration%2F%5D%2FProperties%2F-328684452
519-
[`charactisticOf`]: https://juullabs.github.io/kable/core/core/com.juul.kable/characteristic-of.html
520-
[`connect`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fconnect%2F%23%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
550+
[`Characteristic`]: https://juullabs.github.io/kable/core/com.juul.kable/-characteristic/index.html
521551
[`Connected`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-state/index.html#%5Bcom.juul.kable%2FState.Connected%2F%2F%2FPointingToDeclaration%2F%5D%2FClasslikes%2F-328684452
522-
[`CoroutineScope`]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
523552
[`CoroutineScope.peripheral`]: https://juullabs.github.io/kable/core/core/com.juul.kable/peripheral.html
524553
[`CoroutineScope.requestPeripheral`]: https://juullabs.github.io/kable/core/core/com.juul.kable/request-peripheral.html
525-
[`descriptorOf`]: https://juullabs.github.io/kable/core/core/com.juul.kable/descriptor-of.html
526-
[`disconnect`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fdisconnect%2F%23%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
554+
[`CoroutineScope`]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
527555
[`Disconnected`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-state/index.html#%5Bcom.juul.kable%2FState.Disconnected%2F%2F%2FPointingToDeclaration%2F%5D%2FClasslikes%2F-328684452
528556
[`Disconnecting`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-state/index.html#%5Bcom.juul.kable%2FState.Disconnecting%2F%2F%2FPointingToDeclaration%2F%5D%2FClasslikes%2F-328684452
529-
[`first`]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/first.html
530557
[`Flow`]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
531558
[`NotReadyException`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-not-ready-exception/index.html
532-
[`observe`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fobserve%2F%23com.juul.kable.Characteristic%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
533559
[`Options`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-options/index.html
534-
[`Peripheral`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html
535560
[`Peripheral.disconnect`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fdisconnect%2F%23%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
561+
[`Peripheral.services`]: https://juullabs.github.io/kable/core/com.juul.kable/-peripheral/index.html#-1607712299%2FProperties%2F-2011752812
562+
[`Peripheral`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html
563+
[`Scanner`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-scanner/index.html
564+
[`WithResponse`]: https://juullabs.github.io/kable/core/com.juul.kable/-write-type/index.html#-1405019860%2FClasslikes%2F-2011752812
565+
[`WriteType`]: https://juullabs.github.io/kable/core/com.juul.kable/-write-type/index.html
566+
[`advertisements`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-scanner/index.html#%5Bcom.juul.kable%2FScanner%2Fadvertisements%2F%23%2FPointingToDeclaration%2F%5D%2FProperties%2F-328684452
567+
[`charactisticOf`]: https://juullabs.github.io/kable/core/core/com.juul.kable/characteristic-of.html
568+
[`connect`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fconnect%2F%23%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
569+
[`descriptorOf`]: https://juullabs.github.io/kable/core/core/com.juul.kable/descriptor-of.html
570+
[`disconnect`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fdisconnect%2F%23%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
571+
[`first`]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/first.html
572+
[`observe`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fobserve%2F%23com.juul.kable.Characteristic%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
536573
[`read`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fread%2F%23com.juul.kable.Characteristic%2FPointingToDeclaration%2F%2C+com.juul.kable%2FPeripheral%2Fread%2F%23com.juul.kable.Descriptor%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
537574
[`requestPeripheral`]: https://juullabs.github.io/kable/core/core/com.juul.kable/request-peripheral.html
538-
[`Scanner`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-scanner/index.html
539575
[`state`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fstate%2F%23%2FPointingToDeclaration%2F%5D%2FProperties%2F-328684452
540-
[connection-state]: https://juullabs.github.io/kable/core/core/com.juul.kable/-state/index.html
576+
[`writeWithResponse`]: https://juullabs.github.io/kable/core/com.juul.kable/-characteristic/-properties/index.html#491699083%2FExtensions%2F-2011752812
541577
[`write`]: https://juullabs.github.io/kable/core/core/com.juul.kable/-peripheral/index.html#%5Bcom.juul.kable%2FPeripheral%2Fwrite%2F%23com.juul.kable.Descriptor%23kotlin.ByteArray%2FPointingToDeclaration%2F%2C+com.juul.kable%2FPeripheral%2Fwrite%2F%23com.juul.kable.Characteristic%23kotlin.ByteArray%23com.juul.kable.WriteType%2FPointingToDeclaration%2F%5D%2FFunctions%2F-328684452
578+
[connection-state]: https://juullabs.github.io/kable/core/core/com.juul.kable/-state/index.html
542579

543580
[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat
544581
[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat

core/api/core.api

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,17 @@ public abstract interface class com/juul/kable/Characteristic {
5454
public abstract fun getServiceUuid ()Ljava/util/UUID;
5555
}
5656

57-
public final class com/juul/kable/CharacteristicKt {
58-
public static final fun characteristicOf (Ljava/lang/String;Ljava/lang/String;)Lcom/juul/kable/Characteristic;
57+
public final class com/juul/kable/Characteristic$Properties {
58+
public static final synthetic fun box-impl (I)Lcom/juul/kable/Characteristic$Properties;
59+
public fun equals (Ljava/lang/Object;)Z
60+
public static fun equals-impl (ILjava/lang/Object;)Z
61+
public static final fun equals-impl0 (II)Z
62+
public final fun getValue ()I
63+
public fun hashCode ()I
64+
public static fun hashCode-impl (I)I
65+
public fun toString ()Ljava/lang/String;
66+
public static fun toString-impl (I)Ljava/lang/String;
67+
public final synthetic fun unbox-impl ()I
5968
}
6069

6170
public final class com/juul/kable/ConnectionLostException : com/juul/kable/NotConnectedException {
@@ -72,31 +81,36 @@ public abstract interface class com/juul/kable/Descriptor {
7281
public abstract fun getServiceUuid ()Ljava/util/UUID;
7382
}
7483

75-
public final class com/juul/kable/DescriptorKt {
76-
public static final fun descriptorOf (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/juul/kable/Descriptor;
77-
}
78-
7984
public final class com/juul/kable/DiscoveredCharacteristic : com/juul/kable/Characteristic {
80-
public final fun component1 ()Ljava/util/UUID;
81-
public final fun component2 ()Ljava/util/UUID;
82-
public final fun component3 ()Ljava/util/List;
83-
public final fun copy (Ljava/util/UUID;Ljava/util/UUID;Ljava/util/List;)Lcom/juul/kable/DiscoveredCharacteristic;
84-
public static synthetic fun copy$default (Lcom/juul/kable/DiscoveredCharacteristic;Ljava/util/UUID;Ljava/util/UUID;Ljava/util/List;ILjava/lang/Object;)Lcom/juul/kable/DiscoveredCharacteristic;
85+
public final fun copy (Landroid/bluetooth/BluetoothGattCharacteristic;)Lcom/juul/kable/DiscoveredCharacteristic;
86+
public static synthetic fun copy$default (Lcom/juul/kable/DiscoveredCharacteristic;Landroid/bluetooth/BluetoothGattCharacteristic;ILjava/lang/Object;)Lcom/juul/kable/DiscoveredCharacteristic;
8587
public fun equals (Ljava/lang/Object;)Z
8688
public fun getCharacteristicUuid ()Ljava/util/UUID;
8789
public final fun getDescriptors ()Ljava/util/List;
90+
public final fun getInstanceId ()I
91+
public final fun getProperties-bty6q6U ()I
92+
public fun getServiceUuid ()Ljava/util/UUID;
93+
public fun hashCode ()I
94+
public fun toString ()Ljava/lang/String;
95+
}
96+
97+
public final class com/juul/kable/DiscoveredDescriptor : com/juul/kable/Descriptor {
98+
public final fun copy (Landroid/bluetooth/BluetoothGattDescriptor;)Lcom/juul/kable/DiscoveredDescriptor;
99+
public static synthetic fun copy$default (Lcom/juul/kable/DiscoveredDescriptor;Landroid/bluetooth/BluetoothGattDescriptor;ILjava/lang/Object;)Lcom/juul/kable/DiscoveredDescriptor;
100+
public fun equals (Ljava/lang/Object;)Z
101+
public fun getCharacteristicUuid ()Ljava/util/UUID;
102+
public fun getDescriptorUuid ()Ljava/util/UUID;
88103
public fun getServiceUuid ()Ljava/util/UUID;
89104
public fun hashCode ()I
90105
public fun toString ()Ljava/lang/String;
91106
}
92107

93108
public final class com/juul/kable/DiscoveredService : com/juul/kable/Service {
94-
public final fun component1 ()Ljava/util/UUID;
95-
public final fun component2 ()Ljava/util/List;
96-
public final fun copy (Ljava/util/UUID;Ljava/util/List;)Lcom/juul/kable/DiscoveredService;
97-
public static synthetic fun copy$default (Lcom/juul/kable/DiscoveredService;Ljava/util/UUID;Ljava/util/List;ILjava/lang/Object;)Lcom/juul/kable/DiscoveredService;
109+
public final fun copy (Landroid/bluetooth/BluetoothGattService;)Lcom/juul/kable/DiscoveredService;
110+
public static synthetic fun copy$default (Lcom/juul/kable/DiscoveredService;Landroid/bluetooth/BluetoothGattService;ILjava/lang/Object;)Lcom/juul/kable/DiscoveredService;
98111
public fun equals (Ljava/lang/Object;)Z
99112
public final fun getCharacteristics ()Ljava/util/List;
113+
public final fun getInstanceId ()I
100114
public fun getServiceUuid ()Ljava/util/UUID;
101115
public fun hashCode ()I
102116
public fun toString ()Ljava/lang/String;
@@ -239,6 +253,19 @@ public final class com/juul/kable/Priority : java/lang/Enum {
239253
public static fun values ()[Lcom/juul/kable/Priority;
240254
}
241255

256+
public final class com/juul/kable/ProfileKt {
257+
public static final fun characteristicOf (Ljava/lang/String;Ljava/lang/String;)Lcom/juul/kable/Characteristic;
258+
public static final fun descriptorOf (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/juul/kable/Descriptor;
259+
public static final fun getBroadcast-G25LNqA (I)Z
260+
public static final fun getExtendedProperties-G25LNqA (I)Z
261+
public static final fun getIndicate-G25LNqA (I)Z
262+
public static final fun getNotify-G25LNqA (I)Z
263+
public static final fun getRead-G25LNqA (I)Z
264+
public static final fun getSignedWrite-G25LNqA (I)Z
265+
public static final fun getWrite-G25LNqA (I)Z
266+
public static final fun getWriteWithoutResponse-G25LNqA (I)Z
267+
}
268+
242269
public final class com/juul/kable/ScanFailedException : java/lang/IllegalStateException {
243270
public final fun getErrorCode ()I
244271
}

0 commit comments

Comments
 (0)