diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DopplerFrequencyCalculator.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DopplerFrequencyCalculator.kt index 277785db..51c59129 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DopplerFrequencyCalculator.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DopplerFrequencyCalculator.kt @@ -1,4 +1,4 @@ -/* +/* * Look4Sat. Amateur radio satellite tracker and pass predictor. * Copyright (C) 2019-2026 Arty Bishop and contributors. * @@ -45,6 +45,9 @@ object DopplerFrequencyCalculator { * Given a downlink frequency, compute the Doppler-corrected uplink frequency * with an offset applied to the downlink (in Hz). * Returns null if the transponder is not a linear passband type. + * + * The user-entered downlink frequency already includes the offset, so subtract + * it before mapping the downlink passband position back to the uplink. */ fun computeUplinkFromDownlinkWithOffset( downlinkHz: Long, @@ -53,7 +56,7 @@ object DopplerFrequencyCalculator { offsetHz: Long ): Long? { if (!isLinearTransponder(transponder)) return null - val baseUplink = TransponderMapper.mapDownlinkToUplink(downlinkHz + offsetHz, transponder) ?: return null + val baseUplink = TransponderMapper.mapDownlinkToUplink(downlinkHz - offsetHz, transponder) ?: return null return orbitalPos.getUplinkFreq(baseUplink) } diff --git a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DopplerFrequencyCalculatorTest.kt b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DopplerFrequencyCalculatorTest.kt index bdaca0f9..709e54d1 100644 --- a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DopplerFrequencyCalculatorTest.kt +++ b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DopplerFrequencyCalculatorTest.kt @@ -118,6 +118,53 @@ class DopplerFrequencyCalculatorTest { assertNull(result) } + @Test + fun computeDownlinkFromUplink_withPositiveOffset_addsOffsetToDownlink() { + val xpdr = linearTransponder() + val orbitalPos = pos(0.0) + val downlink = DopplerFrequencyCalculator.computeDownlinkFromUplinkWithOffset( + uplinkHz = 145_200_000L, + transponder = xpdr, + orbitalPos = orbitalPos, + offsetHz = 2_500L + ) + assertEquals(435_202_500L, downlink) + } + + @Test + fun computeUplinkFromDownlink_withPositiveOffset_subtractsOffsetBeforeMapping() { + val xpdr = linearTransponder() + val orbitalPos = pos(0.0) + val uplink = DopplerFrequencyCalculator.computeUplinkFromDownlinkWithOffset( + downlinkHz = 435_202_500L, + transponder = xpdr, + orbitalPos = orbitalPos, + offsetHz = 2_500L + ) + assertEquals(145_200_000L, uplink) + } + + @Test + fun computeOffsetRoundTrip_handlesNegativeOffset() { + val xpdr = linearTransponder() + val orbitalPos = pos(0.0) + val downlink = DopplerFrequencyCalculator.computeDownlinkFromUplinkWithOffset( + uplinkHz = 145_200_000L, + transponder = xpdr, + orbitalPos = orbitalPos, + offsetHz = -2_500L + ) + assertEquals(435_197_500L, downlink) + + val uplink = DopplerFrequencyCalculator.computeUplinkFromDownlinkWithOffset( + downlinkHz = downlink!!, + transponder = xpdr, + orbitalPos = orbitalPos, + offsetHz = -2_500L + ) + assertEquals(145_200_000L, uplink) + } + @Test fun computeUplinkFromDownlink_invertedTransponder() { val xpdr = linearTransponder(inverted = true, downHigh = 435_500_000L) diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt index 8c379637..f4af915b 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt @@ -618,7 +618,7 @@ private fun DopplerFrequencyCalculator( }, label = { Text(stringResource(R.string.radar_doppler_offset_hint)) }, singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), modifier = Modifier.fillMaxWidth() )