@@ -13,34 +13,27 @@ import android.bluetooth.BluetoothGattService
13
13
import android.bluetooth.BluetoothProfile.STATE_CONNECTED
14
14
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
15
15
import android.os.RemoteException
16
- import com.juul.able.experimental.messenger.Message.DiscoverServices
17
- import com.juul.able.experimental.messenger.Message.ReadCharacteristic
18
- import com.juul.able.experimental.messenger.Message.RequestMtu
19
- import com.juul.able.experimental.messenger.Message.WriteCharacteristic
20
- import com.juul.able.experimental.messenger.Message.WriteDescriptor
21
- import com.juul.able.experimental.messenger.Messenger
22
- import com.juul.able.experimental.messenger.OnCharacteristicChanged
23
- import com.juul.able.experimental.messenger.OnCharacteristicRead
24
- import com.juul.able.experimental.messenger.OnCharacteristicWrite
25
- import com.juul.able.experimental.messenger.OnConnectionStateChange
26
- import com.juul.able.experimental.messenger.OnDescriptorWrite
27
- import com.juul.able.experimental.messenger.OnMtuChanged
28
- import kotlinx.coroutines.experimental.CompletableDeferred
16
+ import kotlinx.coroutines.experimental.CoroutineDispatcher
17
+ import kotlinx.coroutines.experimental.ThreadPoolDispatcher
29
18
import kotlinx.coroutines.experimental.channels.BroadcastChannel
19
+ import kotlinx.coroutines.experimental.channels.ReceiveChannel
20
+ import kotlinx.coroutines.experimental.newSingleThreadContext
21
+ import kotlinx.coroutines.experimental.withContext
30
22
import java.util.UUID
31
23
32
24
class CoroutinesGatt (
33
25
private val bluetoothGatt : BluetoothGatt ,
34
- private val messenger : Messenger
26
+ private val callback : GattCallback ,
27
+ private val dispatcher : CoroutineDispatcher = newSingleThreadContext("Gatt ")
35
28
) : Gatt {
36
29
37
30
private val connectionStateMonitor by lazy { ConnectionStateMonitor (this ) }
38
31
39
32
override val onConnectionStateChange: BroadcastChannel <OnConnectionStateChange >
40
- get() = messenger. callback.onConnectionStateChange
33
+ get() = callback.onConnectionStateChange
41
34
42
35
override val onCharacteristicChanged: BroadcastChannel <OnCharacteristicChanged >
43
- get() = messenger. callback.onCharacteristicChanged
36
+ get() = callback.onCharacteristicChanged
44
37
45
38
override fun requestConnect (): Boolean = bluetoothGatt.connect()
46
39
override fun requestDisconnect (): Unit = bluetoothGatt.disconnect()
@@ -62,7 +55,18 @@ class CoroutinesGatt(
62
55
override fun close () {
63
56
Able .verbose { " close → Begin" }
64
57
connectionStateMonitor.close()
65
- messenger.close()
58
+ callback.close()
59
+
60
+ if (dispatcher is ThreadPoolDispatcher ) {
61
+ /* *
62
+ * Explicitly close context (this is needed until #261 is fixed).
63
+ *
64
+ * [Kotlin Coroutines Issue #261](https://github.com/Kotlin/kotlinx.coroutines/issues/261)
65
+ * [Coroutines actor test Gist](https://gist.github.com/twyatt/c51f81d763a6ee39657233fa725f5435)
66
+ */
67
+ dispatcher.close()
68
+ }
69
+
66
70
bluetoothGatt.close()
67
71
Able .verbose { " close → End" }
68
72
}
@@ -73,52 +77,21 @@ class CoroutinesGatt(
73
77
/* *
74
78
* @throws [RemoteException] if underlying [BluetoothGatt.discoverServices] returns `false`.
75
79
*/
76
- override suspend fun discoverServices (): GattStatus {
77
- Able .debug { " discoverServices → send(DiscoverServices)" }
78
-
79
- val response = CompletableDeferred <Boolean >()
80
- messenger.send(DiscoverServices (response))
81
-
82
- val call = " BluetoothGatt.discoverServices()"
83
- Able .verbose { " discoverServices → Waiting for $call " }
84
- if (! response.await()) {
85
- throw RemoteException (" $call returned false." )
80
+ override suspend fun discoverServices (): GattStatus =
81
+ performBluetoothAction(" discoverServices" , callback.onServicesDiscovered) {
82
+ bluetoothGatt.discoverServices()
86
83
}
87
84
88
- Able .verbose { " discoverServices → Waiting for BluetoothGattCallback" }
89
- return messenger.callback.onServicesDiscovered.receive().also { status ->
90
- Able .info { " discoverServices, status=${status.asGattStatusString()} " }
91
- }
92
- }
93
-
94
85
/* *
95
86
* @throws [RemoteException] if underlying [BluetoothGatt.readCharacteristic] returns `false`.
96
87
*/
97
88
override suspend fun readCharacteristic (
98
89
characteristic : BluetoothGattCharacteristic
99
- ): OnCharacteristicRead {
100
- val uuid = characteristic.uuid
101
- Able .debug { " readCharacteristic → send(ReadCharacteristic[uuid=$uuid ])" }
102
-
103
- val response = CompletableDeferred <Boolean >()
104
- messenger.send(ReadCharacteristic (characteristic, response))
105
-
106
- val call = " BluetoothGatt.readCharacteristic(BluetoothGattCharacteristic[uuid=$uuid ])"
107
- Able .verbose { " readCharacteristic → Waiting for $call " }
108
- if (! response.await()) {
109
- throw RemoteException (" Failed to read characteristic with UUID $uuid ." )
90
+ ): OnCharacteristicRead =
91
+ performBluetoothAction(" readCharacteristic" , callback.onCharacteristicRead) {
92
+ bluetoothGatt.readCharacteristic(characteristic)
110
93
}
111
94
112
- Able .verbose { " readCharacteristic → Waiting for BluetoothGattCallback" }
113
- return messenger.callback.onCharacteristicRead.receive().also { (_, value, status) ->
114
- Able .info {
115
- val bytesString = value.size.bytesString
116
- val statusString = status.asGattStatusString()
117
- " ← readCharacteristic $uuid ($bytesString ), status=$statusString "
118
- }
119
- }
120
- }
121
-
122
95
/* *
123
96
* @param value applied to [characteristic] when characteristic is written.
124
97
* @param writeType applied to [characteristic] when characteristic is written.
@@ -128,87 +101,61 @@ class CoroutinesGatt(
128
101
characteristic : BluetoothGattCharacteristic ,
129
102
value : ByteArray ,
130
103
writeType : WriteType
131
- ): OnCharacteristicWrite {
132
- val uuid = characteristic.uuid
133
- Able .debug { " writeCharacteristic → send(WriteCharacteristic[uuid=$uuid ])" }
134
-
135
- val response = CompletableDeferred <Boolean >()
136
- messenger.send(WriteCharacteristic (characteristic, value, writeType, response))
137
-
138
- val call = " BluetoothGatt.writeCharacteristic(BluetoothGattCharacteristic[uuid=$uuid ])"
139
- Able .verbose { " writeCharacteristic → Waiting for $call " }
140
- if (! response.await()) {
141
- throw RemoteException (" $call returned false." )
104
+ ): OnCharacteristicWrite =
105
+ performBluetoothAction(" writeCharacteristic" , callback.onCharacteristicWrite) {
106
+ characteristic.value = value
107
+ characteristic.writeType = writeType
108
+ bluetoothGatt.writeCharacteristic(characteristic)
142
109
}
143
110
144
- Able .verbose { " writeCharacteristic → Waiting for BluetoothGattCallback" }
145
- return messenger.callback.onCharacteristicWrite.receive().also { (_, status) ->
146
- Able .info {
147
- val bytesString = value.size.bytesString
148
- val typeString = writeType.asWriteTypeString()
149
- val statusString = status.asGattStatusString()
150
- " → writeCharacteristic $uuid ($bytesString ), type=$typeString , status=$statusString "
151
- }
152
- }
153
- }
154
-
155
111
/* *
156
112
* @param value applied to [descriptor] when descriptor is written.
157
113
* @throws [RemoteException] if underlying [BluetoothGatt.writeDescriptor] returns `false`.
158
114
*/
159
115
override suspend fun writeDescriptor (
160
- descriptor : BluetoothGattDescriptor , value : ByteArray
161
- ): OnDescriptorWrite {
162
- val uuid = descriptor.uuid
163
- Able .debug { " writeDescriptor → send(WriteDescriptor[uuid=$uuid ])" }
164
-
165
- val response = CompletableDeferred <Boolean >()
166
- messenger.send(WriteDescriptor (descriptor, value, response))
167
-
168
- val call = " BluetoothGatt.writeDescriptor(BluetoothGattDescriptor[uuid=$uuid ])"
169
- Able .verbose { " writeDescriptor → Waiting for $call " }
170
- if (! response.await()) {
171
- throw RemoteException (" $call returned false." )
116
+ descriptor : BluetoothGattDescriptor ,
117
+ value : ByteArray
118
+ ): OnDescriptorWrite =
119
+ performBluetoothAction(" writeDescriptor" , callback.onDescriptorWrite) {
120
+ descriptor.value = value
121
+ bluetoothGatt.writeDescriptor(descriptor)
172
122
}
173
123
174
- Able .verbose { " writeDescriptor → Waiting for BluetoothGattCallback" }
175
- return messenger.callback.onDescriptorWrite.receive().also { (_, status) ->
176
- Able .info {
177
- val bytesString = value.size.bytesString
178
- val statusString = status.asGattStatusString()
179
- " → writeDescriptor $uuid ($bytesString ), status=$statusString "
180
- }
181
- }
182
- }
183
-
184
124
/* *
185
125
* @throws [RemoteException] if underlying [BluetoothGatt.requestMtu] returns `false`.
186
126
*/
187
- override suspend fun requestMtu (mtu : Int ): OnMtuChanged {
188
- Able .debug { " requestMtu → send(RequestMtu[mtu=$mtu ])" }
189
-
190
- val response = CompletableDeferred <Boolean >()
191
- messenger.send(RequestMtu (mtu, response))
192
-
193
- val call = " BluetoothGatt.requestMtu($mtu )"
194
- Able .verbose { " requestMtu → Waiting for $call " }
195
- if (! response.await()) {
196
- throw RemoteException (" $call returned false." )
127
+ override suspend fun requestMtu (mtu : Int ): OnMtuChanged =
128
+ performBluetoothAction(" requestMtu" , callback.onMtuChanged) {
129
+ bluetoothGatt.requestMtu(mtu)
197
130
}
198
131
199
- Able .verbose { " requestMtu → Waiting for BluetoothGattCallback" }
200
- return messenger.callback.onMtuChanged.receive().also { (mtu, status) ->
201
- Able .info { " requestMtu $mtu , status=${status.asGattStatusString()} " }
202
- }
203
- }
204
-
205
132
override fun setCharacteristicNotification (
206
133
characteristic : BluetoothGattCharacteristic ,
207
134
enable : Boolean
208
135
): Boolean {
209
- Able .info { " setCharacteristicNotification ${characteristic.uuid} enable=$enable " }
136
+ Able .info { " setCharacteristicNotification → uuid= ${characteristic.uuid} , enable=$enable " }
210
137
return bluetoothGatt.setCharacteristicNotification(characteristic, enable)
211
138
}
212
- }
213
139
214
- private val Int .bytesString get() = if (this == 1 ) " $this byte" else " $this bytes"
140
+ private suspend fun <T > performBluetoothAction (
141
+ methodName : String ,
142
+ responseChannel : ReceiveChannel <T >,
143
+ action : () -> Boolean
144
+ ): T {
145
+ Able .debug { " $methodName → Acquiring Gatt lock" }
146
+ callback.waitForGattReady()
147
+
148
+ Able .verbose { " $methodName → withContext" }
149
+ withContext(dispatcher) {
150
+ if (! action.invoke()) {
151
+ throw RemoteException (" BluetoothGatt.$methodName returned false." )
152
+ }
153
+ }
154
+
155
+ Able .verbose { " $methodName ← Waiting for BluetoothGattCallback" }
156
+ val response = responseChannel.receive()
157
+
158
+ Able .info { " $methodName ← $response " }
159
+ return response
160
+ }
161
+ }
0 commit comments