mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-03 11:37:24 +00:00
Improvised solution to transmit frequencies to IC705
This commit is contained in:
1 parent
11841ca3d7
commit
483d846233
7 files changed
+318
-6
No files matched your search
@@ -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() }
|
||||
copiedList.forEach { transmitter ->
|
||||
transmitter.downlinkLow?.let { transmitter.downlinkLow = satPos.getDownlinkFreq(it) }
|
||||
transmitter.uplinkLow?.let { transmitter.uplinkLow = satPos.getUplinkFreq(it) }
|
||||
transmitter.downlinkHigh?.let { transmitter.downlinkHigh = satPos.getDownlinkFreq(it) }
|
||||
transmitter.uplinkLow?.let { transmitter.uplinkLow = satPos.getUplinkFreq(it) }
|
||||
transmitter.uplinkHigh?.let { transmitter.uplinkHigh = satPos.getUplinkFreq(it) }
|
||||
}
|
||||
copiedList.map { it.copy() }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user