Fix setting the mode (e.g. FM, USB, LSB)

This commit is contained in:
Rui Oliveira committed 2026-07-24 20:10:10 +01:00
1 parent 77dfb7bb05
commit c7089ab314
4 files changed
+89 -12

No files matched your search

@@ -75,7 +75,12 @@ class Ic705Controller(
outputStream = btSocket.outputStream outputStream = btSocket.outputStream
inputStream = btSocket.inputStream inputStream = btSocket.inputStream
isConnected = true isConnected = true
Log.i(tag, "Connected to $deviceAddress") // Enter VFO mode — frequency/mode commands return FA if the radio
// is in memory-channel mode. Safe to send regardless of current state.
Log.i(tag, "Connected to $deviceAddress — entering VFO mode")
val vfoCmd = IcomCivProtocol.buildEnterVfoModeCommand()
Log.d(tag, "CMD enterVfoMode → ${IcomCivProtocol.toHex(vfoCmd)}")
ioMutex.withLock { sendAndWaitAck(vfoCmd) }
true true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "Connect error: ${e.message}") Log.e(tag, "Connect error: ${e.message}")
@@ -162,6 +167,16 @@ class Ic705Controller(
// ── IRadioController – IC-705 extended operations ─────────────────────── // ── IRadioController – IC-705 extended operations ───────────────────────
/** Select the band for [frequencyHz] via CMD 0x1A sub 0x00 (band stacking register). */
override suspend fun setBand(frequencyHz: Long): Boolean = withContext(Dispatchers.IO) {
val cmd = IcomCivProtocol.buildBandSelectCommand(frequencyHz) ?: run {
Log.w(tag, "setBand: no band code for ${frequencyHz}Hz — skipping")
return@withContext false
}
Log.d(tag, "CMD setBand (${frequencyHz}Hz) → ${IcomCivProtocol.toHex(cmd)}")
ioMutex.withLock { sendAndWaitAck(cmd) }
}
/** Select VFO-A (main/RX) or VFO-B (sub/TX). */ /** Select VFO-A (main/RX) or VFO-B (sub/TX). */
override suspend fun setVfo(vfoA: Boolean): Boolean = withContext(Dispatchers.IO) { override suspend fun setVfo(vfoA: Boolean): Boolean = withContext(Dispatchers.IO) {
val cmd = if (vfoA) IcomCivProtocol.buildSelectVfoACommand() val cmd = if (vfoA) IcomCivProtocol.buildSelectVfoACommand()
@@ -51,8 +51,16 @@ object IcomCivProtocol {
const val CMD_SET_MODE: Byte = 0x06 const val CMD_SET_MODE: Byte = 0x06
/** Select VFO / memory. */ /** Select VFO / memory. */
const val CMD_SELECT_VFO: Byte = 0x07 const val CMD_SELECT_VFO: Byte = 0x07
/**
* Select operating mode (VFO vs memory-channel).
* Sub 0x00 = VFO mode. Must be sent after connect if the radio is in
* memory-channel mode — frequency/mode commands return FA until it is.
*/
const val CMD_SELECT_OP_MODE: Byte = 0x08
/** Set repeater duplex / SPLIT. */ /** Set repeater duplex / SPLIT. */
const val CMD_DUPLEX_SPLIT: Byte = 0x0F const val CMD_DUPLEX_SPLIT: Byte = 0x0F
/** Band stacking register / band select (sub 0x00 = select, data = BCD band number). */
const val CMD_BAND_SELECT: Byte = 0x1A
/** Read/write CTCSS tone frequency. */ /** Read/write CTCSS tone frequency. */
const val CMD_CTCSS_TONE: Byte = 0x1B const val CMD_CTCSS_TONE: Byte = 0x1B
/** Read/write misc settings (used for enabling CTCSS encode). */ /** Read/write misc settings (used for enabling CTCSS encode). */
@@ -178,10 +186,41 @@ object IcomCivProtocol {
/** Read operating frequency (CMD 0x03). */ /** Read operating frequency (CMD 0x03). */
fun buildReadFreqCommand(): ByteArray = frame(CMD_READ_FREQ) fun buildReadFreqCommand(): ByteArray = frame(CMD_READ_FREQ)
/** Set operating mode (CMD 0x06). Filter byte 0x00 = keep current filter. */ /**
* Select band via CMD 0x1A sub 0x00.
* Band codes are BCD-numbered: 1=160m, 2=80m, …, 9=10m, 0x10=6m, 0x11=2m, 0x12=70cm, 0x13=23cm.
* Returns null if [frequencyHz] doesn't fall in a known amateur band.
*/
fun buildBandSelectCommand(frequencyHz: Long): ByteArray? {
val code = bandCodeForFrequency(frequencyHz) ?: return null
return frame(CMD_BAND_SELECT, 0x00, code)
}
/**
* Map a frequency in Hz to the IC-705 band stacking register code.
* Codes are BCD (band number in decimal expressed as hex nibbles).
*/
fun bandCodeForFrequency(frequencyHz: Long): Byte? = when {
frequencyHz in 1_800_000L ..1_999_999L -> 0x01 // 160 m
frequencyHz in 3_500_000L ..3_999_999L -> 0x02 // 80 m
frequencyHz in 7_000_000L ..7_299_999L -> 0x03 // 40 m
frequencyHz in 10_100_000L ..10_149_999L -> 0x04 // 30 m
frequencyHz in 14_000_000L ..14_349_999L -> 0x05 // 20 m
frequencyHz in 18_068_000L ..18_167_999L -> 0x06 // 17 m
frequencyHz in 21_000_000L ..21_449_999L -> 0x07 // 15 m
frequencyHz in 24_890_000L ..24_989_999L -> 0x08 // 12 m
frequencyHz in 28_000_000L ..29_699_999L -> 0x09 // 10 m
frequencyHz in 50_000_000L ..53_999_999L -> 0x10 // 6 m (BCD 10)
frequencyHz in 144_000_000L ..147_999_999L -> 0x11 // 2 m (BCD 11)
frequencyHz in 420_000_000L ..449_999_999L -> 0x12 // 70 cm (BCD 12)
frequencyHz in 1_240_000_000L ..1_299_999_999L -> 0x13 // 23 cm (BCD 13)
else -> null
}
/** Set operating mode (CMD 0x06). Filter byte is omitted — radio uses its default filter for the mode. */
fun buildSetModeCommand(mode: String): ByteArray? { fun buildSetModeCommand(mode: String): ByteArray? {
val modeByte = MODE_TO_BYTE[mode.uppercase(Locale.US)] ?: return null val modeByte = MODE_TO_BYTE[mode.uppercase(Locale.US)] ?: return null
return frame(CMD_SET_MODE, modeByte, 0x00) return frame(CMD_SET_MODE, modeByte)
} }
/** Select VFO-A (CMD 0x07 sub 0x00). */ /** Select VFO-A (CMD 0x07 sub 0x00). */
@@ -190,6 +229,13 @@ object IcomCivProtocol {
/** Select VFO-B (CMD 0x07 sub 0x01). */ /** Select VFO-B (CMD 0x07 sub 0x01). */
fun buildSelectVfoBCommand(): ByteArray = frame(CMD_SELECT_VFO, SUB_VFO_B) fun buildSelectVfoBCommand(): ByteArray = frame(CMD_SELECT_VFO, SUB_VFO_B)
/**
* Enter VFO operating mode (CMD 0x08 sub 0x00).
* Sent after connect — if the radio is in memory-channel mode frequency
* and mode commands return FA until this is issued.
*/
fun buildEnterVfoModeCommand(): ByteArray = frame(CMD_SELECT_OP_MODE, 0x00)
/** Enable or disable SPLIT mode (CMD 0x0F). */ /** Enable or disable SPLIT mode (CMD 0x0F). */
fun buildSplitModeCommand(enable: Boolean): ByteArray { fun buildSplitModeCommand(enable: Boolean): ByteArray {
val sub = if (enable) SUB_SPLIT_ON else SUB_SPLIT_OFF val sub = if (enable) SUB_SPLIT_ON else SUB_SPLIT_OFF
@@ -47,6 +47,8 @@ class RadioTrackingService(
) : IRadioTrackingService { ) : IRadioTrackingService {
private val tag = "RadioTracking" private val tag = "RadioTracking"
/** Delay between each step of the split-mode init sequence (ms). */
private val INIT_STEP_DELAY_MS = 200L
private val _state = MutableStateFlow(RadioTrackingState()) private val _state = MutableStateFlow(RadioTrackingState())
override val state: StateFlow<RadioTrackingState> = _state override val state: StateFlow<RadioTrackingState> = _state
@@ -316,10 +318,15 @@ class RadioTrackingService(
Log.i(tag, "IC-705 split setup: txBase=${txBase}Hz rxNominal=${rxNominal}Hz txMode=$txMode rxMode=$rxMode") Log.i(tag, "IC-705 split setup: txBase=${txBase}Hz rxNominal=${rxNominal}Hz txMode=$txMode rxMode=$rxMode")
// ── Initial setup sequence ────────────────────────────────────────── // ── Initial setup sequence ──────────────────────────────────────────
// 1. Select VFO-A (will be RX VFO), set downlink frequency and mode // Sequence per IC-705: explicitly select VFO, then band → freq → mode.
// ACK from each command gates the next — no fixed delays needed.
// VFO-A = RX (downlink)
Log.d(tag, "Split init: selecting VFO-A for RX (downlink)") Log.d(tag, "Split init: selecting VFO-A for RX (downlink)")
radio.setVfo(vfoA = true) radio.setVfo(vfoA = true)
if (rxNominal != null) { if (rxNominal != null) {
Log.d(tag, "Split init: VFO-A band for ${rxNominal}Hz")
radio.setBand(rxNominal)
Log.d(tag, "Split init: VFO-A freq=${rxNominal}Hz") Log.d(tag, "Split init: VFO-A freq=${rxNominal}Hz")
radio.setFrequency(rxNominal) radio.setFrequency(rxNominal)
} }
@@ -328,10 +335,12 @@ class RadioTrackingService(
radio.setMode(rxMode) radio.setMode(rxMode)
} }
// 2. Select VFO-B (will be TX VFO), set uplink frequency, mode, and CTCSS // VFO-B = TX (uplink)
Log.d(tag, "Split init: selecting VFO-B for TX (uplink)") Log.d(tag, "Split init: selecting VFO-B for TX (uplink)")
radio.setVfo(vfoA = false) radio.setVfo(vfoA = false)
if (txBase != null) { if (txBase != null) {
Log.d(tag, "Split init: VFO-B band for ${txBase}Hz")
radio.setBand(txBase)
Log.d(tag, "Split init: VFO-B freq=${txBase}Hz") Log.d(tag, "Split init: VFO-B freq=${txBase}Hz")
radio.setFrequency(txBase) radio.setFrequency(txBase)
} }
@@ -340,19 +349,20 @@ class RadioTrackingService(
radio.setMode(txMode) radio.setMode(txMode)
} }
if (txMode?.uppercase() == "FM") { if (txMode?.uppercase() == "FM") {
_state.value.ctcssTone?.let { tone -> val tone = _state.value.ctcssTone
if (tone != null) {
Log.d(tag, "Split init: CTCSS=${tone}Hz") Log.d(tag, "Split init: CTCSS=${tone}Hz")
radio.setCtcssTone(tone) radio.setCtcssTone(tone)
radio.setCtcssMode(true) radio.setCtcssMode(true)
} else {
radio.setCtcssMode(false)
} }
} }
// 3. Enable SPLIT — radio will now TX on VFO-B while listening on VFO-A // Enable SPLIT on VFO-A (return display to RX VFO first)
Log.d(tag, "Split init: enabling SPLIT mode") Log.d(tag, "Split init: returning to VFO-A, then enabling SPLIT mode")
radio.setSplitMode(enabled = true)
// Return to VFO-A so the display shows RX frequency
radio.setVfo(vfoA = true) radio.setVfo(vfoA = true)
radio.setSplitMode(enabled = true)
_state.update { it.copy(txMode = txMode, rxMode = rxMode, txBaseFrequencyHz = txBase) } _state.update { it.copy(txMode = txMode, rxMode = rxMode, txBaseFrequencyHz = txBase) }
Log.i(tag, "IC-705 split init done — entering tracking loop") Log.i(tag, "IC-705 split init done — entering tracking loop")
@@ -41,10 +41,16 @@ interface IRadioController {
// ── Extended operations (IC-705 / CI-V) ────────────────────────────── // ── Extended operations (IC-705 / CI-V) ──────────────────────────────
/**
* Select the band matching [frequencyHz] via the band stacking register.
* Must be called before [setFrequency] and [setMode] when first tracking.
* Default: no-op (Yaesu radios auto-switch band via frequency).
*/
suspend fun setBand(frequencyHz: Long): Boolean = false
/** /**
* Select the active VFO. * Select the active VFO.
* @param vfoA true → VFO-A (main/RX), false → VFO-B (sub/TX in split). * @param vfoA true → VFO-A (main/RX), false → VFO-B (sub/TX in split).
* Default: no-op.
*/ */
suspend fun setVfo(vfoA: Boolean): Boolean = false suspend fun setVfo(vfoA: Boolean): Boolean = false