From 8ab25c1389bcdc169d3290e7c5e430e42c5f79e2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 19:58:40 +0100 Subject: [PATCH] Request high BLE priority for incoming Android links on the shared ACL --- lib/ble/tests/ble_peer_probe.c | 11 +++++- .../main/java/com/utun/chat/data/BleRadio.kt | 39 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/lib/ble/tests/ble_peer_probe.c b/lib/ble/tests/ble_peer_probe.c index c276276e..3647f5ed 100644 --- a/lib/ble/tests/ble_peer_probe.c +++ b/lib/ble/tests/ble_peer_probe.c @@ -1,5 +1,5 @@ /* Реальное Android↔desktop радио. Android instrumentation включает только тестовый echo. - * ble_peer_probe [connect|listen] [seconds]; 96 пакетов, TX saturation, побайтовая сверка. */ + * ble_peer_probe [connect|listen] [seconds] [peer_hex]; 96 пакетов, TX saturation, побайтовая сверка. */ #include "ble_transport.h" #include "ble_identity.h" #include "u_async.h" @@ -7,11 +7,12 @@ #include #include #include +#include static struct ble_transport* transport; static ble_link_id session; static unsigned sent, received_count, blocked; static int failed, dial, seen, radio_ready; -static uint64_t peer, started, verified_bytes; +static uint64_t peer, expected_peer, started, verified_bytes; static const unsigned lengths[] = {1,13,14,20,244,512,1499,1500}; static unsigned length(unsigned n) { return lengths[n % 8]; } static uint8_t probe_byte(unsigned n, unsigned i) { return (uint8_t)(n + i * 37); } @@ -58,6 +59,7 @@ static void status(void* arg, enum ble_state state, const char* reason) { static void broadcast(void* arg, const struct ble_endpoint* ep, const uint8_t* data, size_t len, int rssi) { (void)arg; struct ble_identity identity; if (ble_identity_decode(&identity, data, len) < 0) return; + if (expected_peer && identity.node_id != expected_peer) return; if (!seen) printf("DISCOVER node=%016llx name=%s rssi=%d endpoint=%s\n", (unsigned long long)identity.node_id, identity.name, rssi, ep->id); seen = 1; @@ -66,7 +68,12 @@ static void broadcast(void* arg, const struct ble_endpoint* ep, const uint8_t* d int main(int argc, char** argv) { dial = argc < 2 || !strcmp(argv[1], "connect"); int seconds = argc > 2 ? atoi(argv[2]) : 90; + if (argc > 3) { + char* end; errno = 0; expected_peer = strtoull(argv[3], &end, 16); + if (errno || !expected_peer || !argv[3][0] || *end) { fprintf(stderr, "Invalid peer_hex\n"); return 2; } + } debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_DISCOVERY, DEBUG_LEVEL_DEBUG); + if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_TRACE); struct UASYNC* ua = uasync_create(); struct ble_config cfg = {0}; cfg.node_id = 0x5643424c45544553ULL; cfg.company_id = 0xffff; cfg.callbacks.status = status; cfg.callbacks.broadcast = broadcast; cfg.callbacks.connection = connected; diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/BleRadio.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/BleRadio.kt index 9672725a..3dad5679 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/BleRadio.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/BleRadio.kt @@ -52,7 +52,8 @@ object BleRadio { var gatt: BluetoothGatt? = null, var rx: BluetoothGattCharacteristic? = null, var invite: 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 busySince: Long = 0, + var priorityGatt: BluetoothGatt? = null) // Клиентский handle того же ACL: server API не имеет requestConnectionPriority. @JvmStatic external fun nativeInstall() @JvmStatic external fun nativeConfigure(flags: Int): Boolean @JvmStatic private external fun nativeEvent(gen: Long, token: Long, type: Int, arg: Int, text: String, bytes: ByteArray) @@ -169,6 +170,7 @@ object BleRadio { links[link.token] = link emit(9, link.token, text = device.address) // Core ограничивает время ожидания обеих CCC. preferFastPhy(device, null) + tuneIncomingPriority(link) } else if (!accepted) { log("WARN", "Subscription rejected address=${device.address} characteristic=${descriptor.characteristic.uuid} " + "descriptor=${descriptor.uuid} incoming=$incoming prepared=$preparedWrite response=$responseNeeded " + @@ -301,6 +303,34 @@ object BleRadio { if (gatt != null) gatt.readPhy() else checkNotNull(server).readPhy(device) } catch (e: RuntimeException) { log("WARN", "PHY tuning unavailable address=${device.address}: ${e.message}") } } + private fun requestHighPriority(link: Link, gatt: BluetoothGatt) { + val accepted = gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH) + log(if (accepted) "INFO" else "WARN", "Connection priority HIGH requested link=${link.token} outgoing=${link.outgoing} accepted=$accepted") + } + // GATT client/server — роли API. Клиентский handle использует уже открытый физический ACL, без discovery/DATA. + private fun tuneIncomingPriority(link: Link) { + val gen = generation + try { + val callback = object : BluetoothGattCallback() { + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { + if (generation != gen || links[link.token] !== link || link.priorityGatt !== gatt) return + guarded { + log(if (status == 0) "DEBUG" else "WARN", "Priority GATT link=${link.token} status=$status state=$newState") + if (status == 0 && newState == BluetoothProfile.STATE_CONNECTED) { + try { requestHighPriority(link, gatt) } + catch (e: RuntimeException) { log("WARN", "Priority tuning failed link=${link.token}: $e") } + } else if (status != 0 || newState == BluetoothProfile.STATE_DISCONNECTED) { + link.priorityGatt = null + cleanup.retain(gatt, "priority GATT link=${link.token}") { gatt.close() }; drainCleanup() + } + } + } + } + link.priorityGatt = link.device.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE, + BluetoothDevice.PHY_LE_1M_MASK, worker) + if (link.priorityGatt == null) log("WARN", "Priority GATT unavailable link=${link.token}") + } catch (e: RuntimeException) { log("WARN", "Incoming priority tuning unavailable link=${link.token}: $e") } + } private fun connect(token: Long, address: String) { check(cleanup.isEmpty) { "Previous BLE cleanup pending" } check(links.size < 16) { "link limit" } @@ -314,8 +344,7 @@ object BleRadio { log(if (status == 0) "INFO" else "WARN", "Client link=$token status=$status state=$newState") if (status != 0 || newState != BluetoothProfile.STATE_CONNECTED) close(link, "client status=$status state=$newState") else { - val priority = gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH) - log(if (priority) "INFO" else "WARN", "Connection priority HIGH requested link=$token accepted=$priority") + requestHighPriority(link, gatt) preferFastPhy(link.device, gatt) if (!gatt.requestMtu(517)) close(link, "requestMtu refused") } @@ -454,6 +483,10 @@ object BleRadio { links.remove(link.token) log(if (report) "WARN" else "INFO", "Closed link=${link.token} address=${link.device.address} outgoing=${link.outgoing} " + "opened=${link.opened} channel=${link.channel} pending=${link.pending?.size} writing=${link.writing} reason=$reason") + link.priorityGatt?.let { gatt -> + link.priorityGatt = null + cleanup.retain(gatt, "priority GATT link=${link.token}") { gatt.close() } + } if (link.outgoing) link.gatt?.let { gatt -> try { gatt.disconnect() } catch (e: RuntimeException) { log("WARN", "Disconnect failed link=${link.token}: $e") } cleanup.retain(gatt, "client GATT link=${link.token}") { gatt.close() }