From dc596b4348109fd004584c75efba22672953e3d6 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Fri, 24 Jul 2026 15:35:43 +0100 Subject: [PATCH] Fix bugs in setting Rx frequency --- .../core/data/framework/Ic705Controller.kt | 40 ++++++++++++++++--- .../core/data/framework/IcomCivProtocol.kt | 11 ++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/Ic705Controller.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/Ic705Controller.kt index 4641e11f..69d9cfe1 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/Ic705Controller.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/Ic705Controller.kt @@ -244,19 +244,25 @@ class Ic705Controller( /** * Drain whatever bytes the radio has buffered within a [timeoutMs] window. - * Uses non-blocking `available()` polling to avoid blocking indefinitely. + * Exits early as soon as a complete CI-V frame addressed to us is present + * in the buffer (i.e., FE FE E0 A4 … FD), so we don't waste the remaining + * timeout on responses that already arrived. */ private suspend fun drainWithTimeout(timeoutMs: Long): ByteArray { - val result = mutableListOf() - val deadline = System.currentTimeMillis() + timeoutMs - val stream = inputStream ?: return ByteArray(0) + val result = mutableListOf() + val deadline = System.currentTimeMillis() + timeoutMs + val stream = inputStream ?: return ByteArray(0) while (System.currentTimeMillis() < deadline) { try { val available = stream.available() if (available > 0) { val chunk = ByteArray(available) val read = stream.read(chunk) - if (read > 0) result.addAll(chunk.take(read)) + if (read > 0) { + result.addAll(chunk.take(read)) + // Exit early once we have a complete frame for us + if (hasCompleteFrameForUs(result)) break + } } else { delay(POLL_INTERVAL_MS) } @@ -269,6 +275,30 @@ class Ic705Controller( return result.toByteArray() } + /** + * Returns true if [buf] contains a complete CI-V frame addressed to the + * controller (FE FE [ADDR_CTRL] [ADDR_IC705] … FD). + * CI-V data bytes cannot be 0xFD, so the first 0xFD after the header is + * always the frame terminator. + */ + private fun hasCompleteFrameForUs(buf: List): Boolean { + var i = 0 + while (i < buf.size - 4) { + if (buf[i] == IcomCivProtocol.PREAMBLE && + buf[i + 1] == IcomCivProtocol.PREAMBLE && + buf[i + 2] == IcomCivProtocol.ADDR_CTRL && + buf[i + 3] == IcomCivProtocol.ADDR_IC705 + ) { + for (k in i + 4 until buf.size) { + if (buf[k] == IcomCivProtocol.END_OF_MSG) return true + } + return false // header found but no FD yet + } + i++ + } + return false + } + private fun write(bytes: ByteArray): Boolean { return try { outputStream?.write(bytes) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/IcomCivProtocol.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/IcomCivProtocol.kt index a851e3f5..73b25948 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/IcomCivProtocol.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/framework/IcomCivProtocol.kt @@ -283,11 +283,18 @@ object IcomCivProtocol { /** * Parse TX status from a CMD_TX_STATUS reply payload. + * + * The IC-705 responds to a `1C 00` query with: + * FE FE E0 A4 1C 00 FD + * After [parseResponse] strips the frame header the payload is: + * payload[0] = 0x00 (sub-command echo) + * payload[1] = status byte (0x00 = RX, 0x01 = TX) + * * Returns true if transmitting, false if receiving, null on error. */ fun parseTxStatus(payload: ByteArray): Boolean? { - if (payload.isEmpty()) return null - return when (payload[0]) { + if (payload.size < 2) return null + return when (payload[1]) { 0x00.toByte() -> false // RX 0x01.toByte() -> true // TX else -> null