forked from nightscout/AndroidAPS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
BleCommCallbacks.kt
173 lines (157 loc) · 8.02 KB
/
BleCommCallbacks.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmDescriptorWriteException
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmWriteException
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType.Companion.byValue
import info.nightscout.androidaps.utils.extensions.toHex
import java.util.concurrent.BlockingQueue
import java.util.concurrent.CountDownLatch
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class BleCommCallbacks(
private val aapsLogger: AAPSLogger,
private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>
) : BluetoothGattCallback() {
private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
private val connected: CountDownLatch = CountDownLatch(1)
private val writeQueue: BlockingQueue<CharacteristicWriteConfirmation> = LinkedBlockingQueue(1)
private val descriptorWriteQueue: BlockingQueue<DescriptorWriteConfirmation> = LinkedBlockingQueue(1)
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
aapsLogger.debug(LTag.PUMPBTCOMM, "OnConnectionStateChange with status/state: $status/$newState")
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
connected.countDown()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
super.onServicesDiscovered(gatt, status)
aapsLogger.debug(LTag.PUMPBTCOMM, "OnServicesDiscovered with status$status")
if (status == BluetoothGatt.GATT_SUCCESS) {
serviceDiscoveryComplete.countDown()
}
}
@Throws(InterruptedException::class)
fun waitForConnection(timeoutMs: Int) {
connected.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
}
@Throws(InterruptedException::class)
fun waitForServiceDiscovery(timeoutMs: Int) {
serviceDiscoveryComplete.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
}
@Throws(InterruptedException::class, TimeoutException::class, CouldNotConfirmWriteException::class)
fun confirmWrite(expectedPayload: ByteArray, timeoutMs: Int) {
val received: CharacteristicWriteConfirmation = writeQueue.poll(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
?: throw TimeoutException()
when (received) {
is CharacteristicWriteConfirmationPayload -> confirmWritePayload(expectedPayload, received)
is CharacteristicWriteConfirmationError -> throw CouldNotConfirmWriteException(received.status)
}
}
private fun confirmWritePayload(expectedPayload: ByteArray, received: CharacteristicWriteConfirmationPayload) {
if (!expectedPayload.contentEquals(received.payload)) {
aapsLogger.warn(
LTag.PUMPBTCOMM,
"Could not confirm write. Got " + received.payload.toHex() + ".Excepted: " + expectedPayload.toHex()
)
throw CouldNotConfirmWriteException(expectedPayload, received.payload)
}
aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed write with value: " + received.payload.toHex())
}
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
CharacteristicWriteConfirmationPayload(characteristic.value)
} else {
CharacteristicWriteConfirmationError(status)
}
aapsLogger.debug(
LTag.PUMPBTCOMM,
"OnCharacteristicWrite with status/char/value " +
status + "/" + byValue(characteristic.uuid.toString()) + "/" + characteristic.value.toHex()
)
try {
if (writeQueue.size > 0) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Write confirm queue should be empty. found: " + writeQueue.size)
writeQueue.clear()
}
val offered = writeQueue.offer(writeConfirmation, WRITE_CONFIRM_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
if (!offered) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed write confirmation")
}
} catch (e: InterruptedException) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending write confirmation")
}
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicChanged(gatt, characteristic)
val payload = characteristic.value
val characteristicType = byValue(characteristic.uuid.toString())
aapsLogger.debug(
LTag.PUMPBTCOMM,
"OnCharacteristicChanged with char/value " +
characteristicType + "/" +
payload.toHex()
)
incomingPackets[characteristicType]!!.add(payload)
}
@Throws(InterruptedException::class, CouldNotConfirmDescriptorWriteException::class)
fun confirmWriteDescriptor(descriptorUUID: String, timeoutMs: Int) {
val confirmed: DescriptorWriteConfirmation = descriptorWriteQueue.poll(
timeoutMs.toLong(),
TimeUnit.MILLISECONDS
)
?: throw TimeoutException()
when (confirmed) {
is DescriptorWriteConfirmationError -> throw CouldNotConfirmWriteException(confirmed.status)
is DescriptorWriteConfirmationUUID ->
if (confirmed.uuid != descriptorUUID) {
aapsLogger.warn(
LTag.PUMPBTCOMM,
"Could not confirm descriptor write. Got ${confirmed.uuid}. Expected: $descriptorUUID"
)
throw CouldNotConfirmDescriptorWriteException(descriptorUUID, confirmed.uuid)
} else {
aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed descriptor write : " + confirmed.uuid)
}
}
}
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
aapsLogger.debug(LTag.PUMPBTCOMM, "OnDescriptor value " + descriptor.value.toHex())
DescriptorWriteConfirmationUUID(descriptor.uuid.toString())
} else {
DescriptorWriteConfirmationError(status)
}
try {
if (descriptorWriteQueue.size > 0) {
aapsLogger.warn(
LTag.PUMPBTCOMM,
"Descriptor write queue should be empty, found: ${descriptorWriteQueue.size}"
)
descriptorWriteQueue.clear()
}
val offered = descriptorWriteQueue.offer(
writeConfirmation,
WRITE_CONFIRM_TIMEOUT_MS.toLong(),
TimeUnit.MILLISECONDS
)
if (!offered) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed descriptor write confirmation")
}
} catch (e: InterruptedException) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending descriptor write confirmation")
}
}
companion object {
private const val WRITE_CONFIRM_TIMEOUT_MS = 10 // the confirmation queue should be empty anyway
}
}