kopia lustrzana https://github.com/rt-bishop/Look4Sat
Improvised solution to transmit frequencies to IC705
rodzic
20ba2be2ac
commit
e23ebb03ae
|
@ -13,8 +13,8 @@ android {
|
||||||
namespace = "com.rtbishop.look4sat"
|
namespace = "com.rtbishop.look4sat"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.rtbishop.look4sat"
|
applicationId = "com.rtbishop.look4satic705"
|
||||||
minSdk = 24
|
minSdk = 32
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 400
|
versionCode = 400
|
||||||
versionName = "4.0.0"
|
versionName = "4.0.0"
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
|
@ -28,7 +28,6 @@ import com.rtbishop.look4sat.presentation.MainTheme
|
||||||
import com.rtbishop.look4sat.presentation.MainScreen
|
import com.rtbishop.look4sat.presentation.MainScreen
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
@ -40,3 +39,5 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check and Request BT Permission?
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.rtbishop.look4sat.presentation.radar
|
package com.rtbishop.look4sat.presentation.radar
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
import androidx.compose.animation.core.RepeatMode
|
import androidx.compose.animation.core.RepeatMode
|
||||||
import androidx.compose.animation.core.animateFloat
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
@ -10,6 +11,7 @@ import androidx.compose.foundation.MarqueeSpacing
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.basicMarquee
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -31,11 +33,13 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
@ -51,6 +55,7 @@ import androidx.navigation.navArgument
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.domain.model.SatRadio
|
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||||
import com.rtbishop.look4sat.domain.utility.toDegrees
|
import com.rtbishop.look4sat.domain.utility.toDegrees
|
||||||
|
import com.rtbishop.look4sat.framework.BluetoothCIV
|
||||||
import com.rtbishop.look4sat.presentation.MainTheme
|
import com.rtbishop.look4sat.presentation.MainTheme
|
||||||
import com.rtbishop.look4sat.presentation.Screen
|
import com.rtbishop.look4sat.presentation.Screen
|
||||||
import com.rtbishop.look4sat.presentation.components.CardIcon
|
import com.rtbishop.look4sat.presentation.components.CardIcon
|
||||||
|
@ -74,6 +79,8 @@ fun NavGraphBuilder.radarDestination(navigateBack: () -> Unit) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
|
private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
|
||||||
|
BluetoothCIV.init(LocalContext.current)
|
||||||
|
|
||||||
val addToCalendar: () -> Unit = {
|
val addToCalendar: () -> Unit = {
|
||||||
uiState.currentPass?.let { pass ->
|
uiState.currentPass?.let { pass ->
|
||||||
uiState.sendAction(RadarAction.AddToCalendar(pass.name, pass.aosTime, pass.losTime))
|
uiState.sendAction(RadarAction.AddToCalendar(pass.name, pass.aosTime, pass.losTime))
|
||||||
|
@ -250,7 +257,20 @@ private fun EclipsedIndicator() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TransmitterItem(radio: SatRadio) {
|
private fun TransmitterItem(radio: SatRadio) {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
LaunchedEffect(radio) {
|
||||||
|
BluetoothCIV.updateOnce(radio)
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(color = MaterialTheme.colorScheme.background,
|
||||||
|
modifier = Modifier.clickable(onClick = {
|
||||||
|
Log.d("BluetoothCivManager", radio.toString())
|
||||||
|
if(radio.uuid == BluetoothCIV.selected) BluetoothCIV.selected = "NONE" else
|
||||||
|
{
|
||||||
|
BluetoothCIV.connect(radio)
|
||||||
|
BluetoothCIV.updateOnce(radio)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
) {
|
||||||
Surface(modifier = Modifier.padding(bottom = 2.dp)) {
|
Surface(modifier = Modifier.padding(bottom = 2.dp)) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
|
|
@ -105,7 +105,7 @@ class RadarViewModel(
|
||||||
sendPassData(satPass, pos, satPass.orbitalObject)
|
sendPassData(satPass, pos, satPass.orbitalObject)
|
||||||
sendPassDataBT(pos)
|
sendPassDataBT(pos)
|
||||||
processRadios(transmitters, satPass.orbitalObject, timeNow)
|
processRadios(transmitters, satPass.orbitalObject, timeNow)
|
||||||
delay(1000)
|
delay(1000) //TODO: Change me maybe if smaller freq steps are better?? maybe dynamically?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
package com.rtbishop.look4sat.framework
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothSocket
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
import java.io.IOException
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||||
|
import android.content.Context
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
enum class Mode(val value: Int) {
|
||||||
|
LSB(0),
|
||||||
|
USB(1),
|
||||||
|
AM(2),
|
||||||
|
CW(3),
|
||||||
|
FMN(5),
|
||||||
|
FM(5),
|
||||||
|
DSTAR(17);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(name: String): Int {
|
||||||
|
return values().find { it.name.equals(name, ignoreCase = true) }?.value ?: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBluetoothAdapter(context: Context): BluetoothAdapter? {
|
||||||
|
val bluetoothManager = context.getSystemService(BluetoothManager::class.java)
|
||||||
|
return bluetoothManager?.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
public object BluetoothCIV {
|
||||||
|
|
||||||
|
// UUID for Serial Port Service
|
||||||
|
private val SERIAL_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
|
||||||
|
// Bluetooth adapter
|
||||||
|
private var bluetoothAdapter: BluetoothAdapter? = null
|
||||||
|
private var bluetoothSocket: BluetoothSocket? = null
|
||||||
|
// CI-V Address of the ICOM IC-705 (verify for your radio)
|
||||||
|
private val CIV_ADDRESS = 0xA4
|
||||||
|
private var outputStream: OutputStream? = null
|
||||||
|
private var inputStream: InputStream? = null
|
||||||
|
|
||||||
|
public fun init(context: Context) {
|
||||||
|
bluetoothAdapter= getBluetoothAdapter(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureConnected():Boolean {
|
||||||
|
if( bluetoothSocket?.isConnected != true) {
|
||||||
|
if (bluetoothSocket != null) {
|
||||||
|
try {
|
||||||
|
bluetoothSocket?.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("BluetoothCivManager", "Fehler beim Schließen des alten Sockets", e)
|
||||||
|
}
|
||||||
|
bluetoothSocket = null
|
||||||
|
}
|
||||||
|
val dev = bluetoothAdapter?.bondedDevices?.firstOrNull { device -> device.name == "ICOM BT(IC-705)"}
|
||||||
|
if(dev==null) {
|
||||||
|
Log.e("BluetoothCivManager", "Gerät nicht gefunden")
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
try{
|
||||||
|
val localSocket = dev.createRfcommSocketToServiceRecord(SERIAL_UUID)
|
||||||
|
localSocket.connect()
|
||||||
|
outputStream = localSocket.outputStream
|
||||||
|
inputStream = localSocket.inputStream
|
||||||
|
bluetoothSocket = localSocket
|
||||||
|
// delay(100);
|
||||||
|
Thread.sleep(100);
|
||||||
|
Log.d("BluetoothCivManager", "Verbindung aufgebaut")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("BluetoothCivManager", "Verbindung nicht aufgebaut")
|
||||||
|
bluetoothSocket = null // Falls fehlgeschlagen, Socket auf null setzen
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendCommand(cmd: Int, sub: Int? = null, data: ByteArray? = null) {
|
||||||
|
outputStream!!.write(0xfe);
|
||||||
|
outputStream!!.write(0xfe);
|
||||||
|
outputStream!!.write(CIV_ADDRESS);
|
||||||
|
outputStream!!.write(0xe0);
|
||||||
|
outputStream!!.write(cmd);
|
||||||
|
if (sub != null) {
|
||||||
|
outputStream!!.write(sub)
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
outputStream!!.write(data)
|
||||||
|
}
|
||||||
|
outputStream!!.write(0xfd)
|
||||||
|
outputStream!!.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RMsg(val broadcast: Boolean, val cmd: Int, val payload: ByteArray)
|
||||||
|
private fun receiveCommand():RMsg? {
|
||||||
|
if (inputStream!!.read()!=0xFE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (inputStream!!.read()!=0xFE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
val to = inputStream!!.read();
|
||||||
|
if (inputStream!!.read()!= CIV_ADDRESS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
val cmd = inputStream!!.read();
|
||||||
|
|
||||||
|
val buffer = mutableListOf<Byte>()
|
||||||
|
var byte: Int
|
||||||
|
|
||||||
|
while (inputStream!!.read().also { byte = it } != -1) {
|
||||||
|
if (byte == 0xFD) break
|
||||||
|
buffer.add(byte.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
return RMsg(to==0x00, cmd, buffer.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun receiveSpecificCommand():RMsg? {
|
||||||
|
while (true) {
|
||||||
|
val msg = receiveCommand() ?: return null
|
||||||
|
if (!msg.broadcast) {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callProcedure(cmd: Int, sub: Int? = null, data: ByteArray? = null):Boolean {
|
||||||
|
if(ensureConnected()){
|
||||||
|
sendCommand(cmd, sub, data)
|
||||||
|
val res = receiveSpecificCommand();
|
||||||
|
return res?.cmd == 0xFB;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Result(val subcmd: Int?, val data: ByteArray)
|
||||||
|
private fun callFunction(cmd: Int, sub: Int? = null, retsub: Boolean): Result{
|
||||||
|
if(ensureConnected()){
|
||||||
|
sendCommand(cmd, sub)
|
||||||
|
val res = receiveSpecificCommand();
|
||||||
|
if (res!=null){
|
||||||
|
if (cmd!=res.cmd) {
|
||||||
|
Log.d("BluetoothCivManager", "Wrong command $cmd != $res.cmd")
|
||||||
|
return Result(null, byteArrayOf());
|
||||||
|
}
|
||||||
|
val subcmd = if (retsub) res.payload[0].toInt() else null;
|
||||||
|
val data = res.payload.copyOfRange((if (retsub) 1 else 0), res.payload.size);
|
||||||
|
return Result(subcmd, data)
|
||||||
|
}else {
|
||||||
|
Log.d("BluetoothCivManager", "No Reply")
|
||||||
|
return Result(null, byteArrayOf());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("BluetoothCivManager", "NotSure")
|
||||||
|
return Result(null, byteArrayOf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var selected: String = "NONE";
|
||||||
|
public var isTransponder: Boolean = false;
|
||||||
|
public var lastDownlinkLow: Long? = null;
|
||||||
|
public var lastDownlinkHigh: Long? = null;
|
||||||
|
|
||||||
|
|
||||||
|
private fun frequencyToBCD(frequency: Long): ByteArray {
|
||||||
|
var n = frequency
|
||||||
|
val bcd = ByteArray(5)
|
||||||
|
// Process each two-digit group, starting from the rightmost group.
|
||||||
|
for (i in 4 downTo 0) {
|
||||||
|
// Extract the last two digits (a value between 0 and 99)
|
||||||
|
val twoDigits = (n % 100).toInt()
|
||||||
|
// Pack the tens digit into the high nibble and the ones digit into the low nibble
|
||||||
|
bcd[i] = (((twoDigits / 10) shl 4) or (twoDigits % 10)).toByte()
|
||||||
|
// Remove the two digits we just processed
|
||||||
|
n /= 100
|
||||||
|
}
|
||||||
|
return bcd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to convert BCD format back to a frequency (Long)
|
||||||
|
private fun bcdToFrequency(bcd: ByteArray): Long {
|
||||||
|
var frequency = 0L
|
||||||
|
for (byte in bcd) {
|
||||||
|
// Extract the high nibble (first decimal digit)
|
||||||
|
val high = (byte.toInt() shr 4) and 0x0F
|
||||||
|
// Extract the low nibble (second decimal digit)
|
||||||
|
val low = byte.toInt() and 0x0F
|
||||||
|
// Combine them into the frequency
|
||||||
|
frequency = frequency * 100 + high * 10 + low
|
||||||
|
}
|
||||||
|
return frequency
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setVFO(A: Boolean): Boolean {
|
||||||
|
return callProcedure(0x07, if (A) 0x00 else 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSplit(On: Boolean): Boolean {
|
||||||
|
return callProcedure(0x0F, if (On) 0x01 else 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setFrequency(Main: Boolean, Frequency: Long): Boolean {
|
||||||
|
return callProcedure(0x25, if (Main) 0x00 else 0x01, frequencyToBCD(Frequency).reversedArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFrequency(Main: Boolean): Long {
|
||||||
|
val res = callFunction(0x25, if (Main) 0x00 else 0x01, true)
|
||||||
|
return if (res.data.isNotEmpty()){
|
||||||
|
bcdToFrequency(res.data.reversedArray());
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMode(Main: Boolean, mode: String): Boolean {
|
||||||
|
return callProcedure(0x26, if (Main) 0x00 else 0x01, byteArrayOf(Mode.fromString(mode).toByte(), 0x00.toByte(), 0x01.toByte()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
public fun connect(radio: SatRadio) {
|
||||||
|
setVFO(true)
|
||||||
|
setSplit(true)
|
||||||
|
setMode(true, radio.downlinkMode?:"USB")
|
||||||
|
setMode(false, radio.uplinkMode?:"USB")
|
||||||
|
selected = radio.uuid;
|
||||||
|
isTransponder = radio.downlinkHigh != null;
|
||||||
|
lastDownlinkLow = null;
|
||||||
|
lastDownlinkHigh = null;
|
||||||
|
// Info on tone?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Double.clamp(min: Double, max: Double): Double {
|
||||||
|
return when {
|
||||||
|
this < min -> min
|
||||||
|
this > max -> max
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send the CI-V command to the ICOM radio over Bluetooth
|
||||||
|
public fun updateOnce(radio: SatRadio) {
|
||||||
|
if(radio.uuid != selected) return;
|
||||||
|
|
||||||
|
if(isTransponder) {
|
||||||
|
if (lastDownlinkLow != null && lastDownlinkHigh != null) {
|
||||||
|
val actual = getFrequency(true)
|
||||||
|
val pos = ((actual - lastDownlinkLow!!).toDouble() / (lastDownlinkHigh!! - lastDownlinkLow!!)).clamp(0.0,1.0);
|
||||||
|
Log.d("BluetoothCivManager", "Pos: $pos");
|
||||||
|
radio.downlinkLow?.let{
|
||||||
|
setFrequency(true, (radio.downlinkLow!! + pos * (radio.downlinkHigh!!-radio.downlinkLow!!)).toLong());
|
||||||
|
}
|
||||||
|
radio.uplinkLow?.let {
|
||||||
|
setFrequency(false, (radio.uplinkLow!! + pos * (radio.uplinkHigh!!-radio.uplinkLow!!)).toLong())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
radio.downlinkLow?.let{
|
||||||
|
setFrequency(true, it);
|
||||||
|
}
|
||||||
|
radio.uplinkLow?.let {
|
||||||
|
setFrequency(false, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastDownlinkLow = radio.downlinkLow;
|
||||||
|
lastDownlinkHigh = radio.downlinkHigh;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
radio.downlinkLow?.let{
|
||||||
|
setFrequency(true, it);
|
||||||
|
}
|
||||||
|
radio.uplinkLow?.let {
|
||||||
|
setFrequency(false, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,7 +82,9 @@ class SatelliteRepo(
|
||||||
val copiedList = radios.map { it.copy() }
|
val copiedList = radios.map { it.copy() }
|
||||||
copiedList.forEach { transmitter ->
|
copiedList.forEach { transmitter ->
|
||||||
transmitter.downlinkLow?.let { transmitter.downlinkLow = satPos.getDownlinkFreq(it) }
|
transmitter.downlinkLow?.let { transmitter.downlinkLow = satPos.getDownlinkFreq(it) }
|
||||||
|
transmitter.downlinkHigh?.let { transmitter.downlinkHigh = satPos.getDownlinkFreq(it) }
|
||||||
transmitter.uplinkLow?.let { transmitter.uplinkLow = satPos.getUplinkFreq(it) }
|
transmitter.uplinkLow?.let { transmitter.uplinkLow = satPos.getUplinkFreq(it) }
|
||||||
|
transmitter.uplinkHigh?.let { transmitter.uplinkHigh = satPos.getUplinkFreq(it) }
|
||||||
}
|
}
|
||||||
copiedList.map { it.copy() }
|
copiedList.map { it.copy() }
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue