Connect the ACK characteristic to Android GATT and JNI
This commit is contained in:
1 parent
e7adcbe139
commit
d4182c22cd
2 files changed
+47
-29
No files matched your search
@@ -22,6 +22,7 @@ object BleRadio {
|
||||
private val rxUuid = UUID.fromString("f61a9021-4abc-45ac-98e1-761df4c13811")
|
||||
private val txUuid = UUID.fromString("f61a9022-4abc-45ac-98e1-761df4c13811")
|
||||
private val inviteUuid = UUID.fromString("f61a9023-4abc-45ac-98e1-761df4c13811")
|
||||
private val ackUuid = UUID.fromString("f61a9024-4abc-45ac-98e1-761df4c13811")
|
||||
private val cccUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
private val thread by lazy { HandlerThread("vibechat-ble").apply { start() } }
|
||||
private val worker by lazy { Handler(thread.looper) }
|
||||
@@ -39,6 +40,7 @@ object BleRadio {
|
||||
private var server: BluetoothGattServer? = null
|
||||
private var tx: BluetoothGattCharacteristic? = null
|
||||
private var invite: BluetoothGattCharacteristic? = null
|
||||
private var ack: BluetoothGattCharacteristic? = null
|
||||
private var scan: ScanCallback? = null
|
||||
private var scanner: BluetoothLeScanner? = null
|
||||
private var advertisement: AdvertiseCallback? = null
|
||||
@@ -51,7 +53,8 @@ object BleRadio {
|
||||
private val incomingMtu = mutableMapOf<String, Int>()
|
||||
private data class Link(val token: Long, val device: BluetoothDevice, val outgoing: Boolean,
|
||||
var gatt: BluetoothGatt? = null, var rx: BluetoothGattCharacteristic? = null,
|
||||
var invite: BluetoothGattCharacteristic? = null, var subscriptions: Int = 0, var channel: Int = 0,
|
||||
var invite: BluetoothGattCharacteristic? = null, var ack: BluetoothGattCharacteristic? = null,
|
||||
var subscriptions: Int = 0, var channel: Int = 0,
|
||||
var mtu: Int = 23, var opened: Boolean = false, var pending: ByteArray? = null, var writing: Boolean = false,
|
||||
var busySince: Long = 0,
|
||||
var priorityGatt: BluetoothGatt? = null) // Клиентский handle того же ACL: server API не имеет requestConnectionPriority.
|
||||
@@ -90,6 +93,7 @@ object BleRadio {
|
||||
}
|
||||
}
|
||||
private fun log(level: String, text: String) { LogManager.addLog(level, "discovery", "BLE $text") }
|
||||
private fun wireChannel(uuid: UUID): Int = when (uuid) { rxUuid, txUuid -> 0; inviteUuid -> 1; ackUuid -> 2; else -> -1 }
|
||||
private fun emit(type: Int, token: Long = 0, arg: Int = 0, text: String = "", data: ByteArray = byteArrayOf()) {
|
||||
nativeEvent(generation, token, type, arg, text, data)
|
||||
}
|
||||
@@ -124,7 +128,7 @@ object BleRadio {
|
||||
5 -> {
|
||||
val link = checkNotNull(links[token]) { "send unknown link=$token" }
|
||||
check(link.pending == null) { "send already pending link=$token" }
|
||||
check(arg in 0..1) { "Invalid BLE channel=$arg" }
|
||||
check(arg in 0..2) { "Invalid BLE channel=$arg" }
|
||||
link.channel = arg; link.pending = bytes
|
||||
pump()
|
||||
}
|
||||
@@ -174,7 +178,7 @@ object BleRadio {
|
||||
preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) = post {
|
||||
val expected = if (descriptor.characteristic.uuid == txUuid) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
else BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
||||
val valid = incoming && descriptor.uuid == cccUuid && descriptor.characteristic.uuid in listOf(txUuid, inviteUuid) &&
|
||||
val valid = incoming && descriptor.uuid == cccUuid && descriptor.characteristic.uuid in listOf(txUuid, inviteUuid, ackUuid) &&
|
||||
!preparedWrite && offset == 0 && value.contentEquals(expected)
|
||||
var link = links.values.find { !it.outgoing && it.device.address == device.address }
|
||||
val accepted = valid && (link != null || links.size < 16)
|
||||
@@ -182,7 +186,7 @@ object BleRadio {
|
||||
if (accepted && link == null) {
|
||||
link = Link(--sequence, device, false, mtu = incomingMtu[device.address] ?: 23)
|
||||
links[link.token] = link
|
||||
emit(9, link.token, text = device.address) // Core ограничивает время ожидания обеих CCC.
|
||||
emit(9, link.token, text = device.address) // Core ограничивает время ожидания всех трёх CCC.
|
||||
preferFastPhy(device, null)
|
||||
tuneIncomingPriority(link)
|
||||
} else if (!accepted) {
|
||||
@@ -192,9 +196,9 @@ object BleRadio {
|
||||
if (link != null) close(link, "subscription disabled")
|
||||
}
|
||||
if (accepted && link != null) {
|
||||
link.subscriptions = link.subscriptions or if (descriptor.characteristic.uuid == txUuid) 1 else 2
|
||||
link.subscriptions = link.subscriptions or (1 shl wireChannel(descriptor.characteristic.uuid))
|
||||
log("DEBUG", "Server subscribed link=${link.token} characteristic=${descriptor.characteristic.uuid} mode=${value[0]}")
|
||||
if (link.subscriptions == 3 && !link.opened) {
|
||||
if (link.subscriptions == 7 && !link.opened) {
|
||||
link.opened = true; emit(2, link.token, link.mtu, device.address)
|
||||
}
|
||||
}
|
||||
@@ -202,9 +206,9 @@ object BleRadio {
|
||||
override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic,
|
||||
preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) = post {
|
||||
val link = links.values.find { !it.outgoing && it.device.address == device.address }
|
||||
val valid = link != null && link.opened && characteristic.uuid in listOf(rxUuid, inviteUuid) &&
|
||||
!preparedWrite && responseNeeded == (characteristic.uuid == inviteUuid) && offset == 0
|
||||
if (valid) emit(3, link!!.token, arg = if (characteristic.uuid == inviteUuid) 1 else 0, data = value.copyOf())
|
||||
val valid = link != null && link.opened && characteristic.uuid in listOf(rxUuid, inviteUuid, ackUuid) &&
|
||||
!preparedWrite && responseNeeded == (characteristic.uuid != rxUuid) && offset == 0
|
||||
if (valid) emit(3, link!!.token, arg = wireChannel(characteristic.uuid), data = value.copyOf())
|
||||
if (responseNeeded) respond(device, requestId, if (valid) 0 else BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED)
|
||||
if (!valid) log("WARN", "Write rejected address=${device.address} characteristic=${characteristic.uuid} " +
|
||||
"bytes=${value.size} response=$responseNeeded prepared=$preparedWrite offset=$offset")
|
||||
@@ -248,6 +252,12 @@ object BleRadio {
|
||||
addDescriptor(BluetoothGattDescriptor(cccUuid, BluetoothGattDescriptor.PERMISSION_WRITE))
|
||||
}
|
||||
service.addCharacteristic(invite)
|
||||
ack = BluetoothGattCharacteristic(ackUuid,
|
||||
BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_INDICATE,
|
||||
BluetoothGattCharacteristic.PERMISSION_WRITE).apply {
|
||||
addDescriptor(BluetoothGattDescriptor(cccUuid, BluetoothGattDescriptor.PERMISSION_WRITE))
|
||||
}
|
||||
service.addCharacteristic(ack)
|
||||
check(server!!.addService(service)) { "addService refused" }
|
||||
}
|
||||
}
|
||||
@@ -381,11 +391,13 @@ object BleRadio {
|
||||
val ccc = characteristic?.getDescriptor(cccUuid)
|
||||
link.rx = service?.getCharacteristic(rxUuid)
|
||||
link.invite = service?.getCharacteristic(inviteUuid)
|
||||
link.ack = service?.getCharacteristic(ackUuid)
|
||||
log("DEBUG", "Client discovery link=$token status=$status service=${service != null} " +
|
||||
"rx_properties=${link.rx?.properties} tx_properties=${characteristic?.properties} invite_properties=${link.invite?.properties}")
|
||||
if (status != 0 || ccc == null || link.rx == null || link.invite == null) {
|
||||
"rx_properties=${link.rx?.properties} tx_properties=${characteristic?.properties} " +
|
||||
"invite_properties=${link.invite?.properties} ack_properties=${link.ack?.properties}")
|
||||
if (status != 0 || ccc == null || link.rx == null || link.invite == null || link.ack == null) {
|
||||
close(link, "service discovery failed status=$status service=${service != null} CCC=${ccc != null} " +
|
||||
"RX=${link.rx != null} INVITE=${link.invite != null}"); return@dispatch
|
||||
"RX=${link.rx != null} INVITE=${link.invite != null} ACK=${link.ack != null}"); return@dispatch
|
||||
}
|
||||
if (link.rx!!.properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE == 0) {
|
||||
close(link, "RX lacks Write Without Response properties=${link.rx!!.properties}"); return@dispatch
|
||||
@@ -393,6 +405,12 @@ object BleRadio {
|
||||
if (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY == 0) {
|
||||
close(link, "TX lacks Notification properties=${characteristic.properties}"); return@dispatch
|
||||
}
|
||||
val controlProperties = BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_INDICATE
|
||||
for (control in listOf(checkNotNull(link.invite), checkNotNull(link.ack))) {
|
||||
if (control.properties and controlProperties != controlProperties || control.getDescriptor(cccUuid) == null) {
|
||||
close(link, "Control characteristic invalid uuid=${control.uuid} properties=${control.properties}"); return@dispatch
|
||||
}
|
||||
}
|
||||
if (!gatt.setCharacteristicNotification(characteristic, true)) {
|
||||
close(link, "DATA local subscription refused characteristic=${characteristic.uuid}"); return@dispatch
|
||||
}
|
||||
@@ -403,29 +421,29 @@ object BleRadio {
|
||||
}
|
||||
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) = dispatch(gatt) {
|
||||
if (status != 0) close(link, "CCC status=$status")
|
||||
else if (descriptor.characteristic.uuid == txUuid) {
|
||||
val characteristic = link.invite
|
||||
else if (descriptor.characteristic.uuid in listOf(txUuid, inviteUuid)) {
|
||||
val characteristic = if (descriptor.characteristic.uuid == txUuid) link.invite else link.ack
|
||||
val ccc = characteristic?.getDescriptor(cccUuid)
|
||||
if (ccc == null || !gatt.setCharacteristicNotification(characteristic, true)) {
|
||||
close(link, "invitation subscription unavailable"); return@dispatch
|
||||
close(link, "control subscription unavailable after=${descriptor.characteristic.uuid}"); return@dispatch
|
||||
}
|
||||
val result = if (Build.VERSION.SDK_INT >= 33) gatt.writeDescriptor(ccc, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
|
||||
else { ccc.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; if (gatt.writeDescriptor(ccc)) 0 else -1 }
|
||||
if (result != 0) close(link, "INVITE CCC write refused status=$result")
|
||||
} else if (descriptor.characteristic.uuid == inviteUuid) { link.opened = true; emit(2, token, link.mtu, address) }
|
||||
if (result != 0) close(link, "Control CCC write refused uuid=${characteristic?.uuid} status=$result")
|
||||
} else if (descriptor.characteristic.uuid == ackUuid) { link.opened = true; emit(2, token, link.mtu, address) }
|
||||
else close(link, "unexpected CCC completion")
|
||||
}
|
||||
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) = dispatch(gatt) {
|
||||
complete(link, status)
|
||||
}
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) = dispatch(gatt) {
|
||||
if (characteristic.uuid in listOf(txUuid, inviteUuid))
|
||||
emit(3, token, arg = if (characteristic.uuid == inviteUuid) 1 else 0, data = value.copyOf())
|
||||
if (characteristic.uuid in listOf(txUuid, inviteUuid, ackUuid))
|
||||
emit(3, token, arg = wireChannel(characteristic.uuid), data = value.copyOf())
|
||||
}
|
||||
@Deprecated("API < 33")
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) = dispatch(gatt) {
|
||||
if (Build.VERSION.SDK_INT < 33 && characteristic.uuid in listOf(txUuid, inviteUuid))
|
||||
emit(3, token, arg = if (characteristic.uuid == inviteUuid) 1 else 0, data = characteristic.value.copyOf())
|
||||
if (Build.VERSION.SDK_INT < 33 && characteristic.uuid in listOf(txUuid, inviteUuid, ackUuid))
|
||||
emit(3, token, arg = wireChannel(characteristic.uuid), data = characteristic.value.copyOf())
|
||||
}
|
||||
}
|
||||
link.gatt = checkNotNull(link.device.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE,
|
||||
@@ -439,8 +457,8 @@ object BleRadio {
|
||||
if (!link.outgoing && writer != null) continue
|
||||
val bytes = checkNotNull(link.pending)
|
||||
val status = if (link.outgoing) {
|
||||
val characteristic = checkNotNull(if (link.channel == 1) link.invite else link.rx)
|
||||
val type = if (link.channel == 1) BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
val characteristic = checkNotNull(when (link.channel) { 0 -> link.rx; 1 -> link.invite; else -> link.ack })
|
||||
val type = if (link.channel != 0) BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
else BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
if (Build.VERSION.SDK_INT >= 33) link.gatt!!.writeCharacteristic(characteristic, bytes, type)
|
||||
else {
|
||||
@@ -448,11 +466,11 @@ object BleRadio {
|
||||
if (link.gatt!!.writeCharacteristic(characteristic)) 0 else -1
|
||||
}
|
||||
} else {
|
||||
val characteristic = checkNotNull(if (link.channel == 1) invite else tx)
|
||||
if (Build.VERSION.SDK_INT >= 33) server!!.notifyCharacteristicChanged(link.device, characteristic, link.channel == 1, bytes)
|
||||
val characteristic = checkNotNull(when (link.channel) { 0 -> tx; 1 -> invite; else -> ack })
|
||||
if (Build.VERSION.SDK_INT >= 33) server!!.notifyCharacteristicChanged(link.device, characteristic, link.channel != 0, bytes)
|
||||
else {
|
||||
characteristic.value = bytes
|
||||
if (server!!.notifyCharacteristicChanged(link.device, characteristic, link.channel == 1)) 0 else -1
|
||||
if (server!!.notifyCharacteristicChanged(link.device, characteristic, link.channel != 0)) 0 else -1
|
||||
}
|
||||
}
|
||||
if (status == 0) {
|
||||
@@ -520,7 +538,7 @@ object BleRadio {
|
||||
advertisement?.let { callback -> advertiser?.let { owner -> cleanup.retain(callback, "advertisement") { owner.stopAdvertising(callback) } } }
|
||||
server?.let { owner -> cleanup.retain(owner, "GATT server") { owner.close() } }
|
||||
scan = null; scanner = null; advertisement = null; advertiser = null; server = null
|
||||
tx = null; invite = null; writer = null; beacon = byteArrayOf(); incomingMtu.clear()
|
||||
tx = null; invite = null; ack = null; writer = null; beacon = byteArrayOf(); incomingMtu.clear()
|
||||
drainCleanup()
|
||||
}
|
||||
private val cleanupRetry = Runnable {
|
||||
|
||||
@@ -135,7 +135,7 @@ static void drain(int fd, void* arg) {
|
||||
if (l) ble_backend_opened(b->owner, l->id, e.arg);
|
||||
else command(4, b->generation, e.token, NULL, NULL, 0, 0);
|
||||
break;
|
||||
case 3: if (l) ble_backend_received(b->owner, l->id, e.arg, e.data, e.len); break;
|
||||
case 3: if (l) ble_backend_received(b->owner, l->id, (enum ble_wire_channel)e.arg, e.data, e.len); break;
|
||||
case 4: if (l) ble_backend_sent(b->owner, l->id, e.arg == 0); break;
|
||||
case 5: if (l) ble_backend_closed(b->owner, l->id, e.text); break;
|
||||
case 6: ble_backend_broadcast(b->owner, &ep, e.data, e.len, e.arg); break;
|
||||
@@ -213,7 +213,7 @@ void ble_backend_close(struct ble_backend* b, ble_link_id id) {
|
||||
struct android_link* l = by_id(b, id); if (!l) return;
|
||||
command(4, b->generation, l->token, NULL, NULL, 0, 0); memset(l, 0, sizeof(*l));
|
||||
}
|
||||
int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel channel, const uint8_t* data, size_t len) {
|
||||
int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_wire_channel channel, const uint8_t* data, size_t len) {
|
||||
struct android_link* l = by_id(b, id);
|
||||
if (!l) { DEBUG_ERROR(CAT, "BLE Android send unknown link"); return BLE_ERROR; }
|
||||
return command(5, b->generation, l->token, NULL, data, len, channel);
|
||||
|
||||
Reference in new issue
Block a user