mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-03 11:37:24 +00:00
Implemented extended data output (#195)
This commit is contained in:
1 parent
7adbf951bc
commit
85ffe94266
19 files changed
+965
-322
No files matched your search
@@ -18,6 +18,7 @@
|
||||
package com.rtbishop.look4sat.data.framework
|
||||
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothSocket
|
||||
import android.util.Log
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
@@ -25,69 +26,110 @@ import kotlin.math.abs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import com.rtbishop.look4sat.domain.repository.IReporterParams
|
||||
import com.rtbishop.look4sat.domain.repository.IReporterRepo
|
||||
|
||||
data class WithoutExtParams(
|
||||
val dummy: Int
|
||||
) : IReporterParams
|
||||
|
||||
enum class BtService { ROTATOR, FREQUENCY }
|
||||
|
||||
data class DeviceConnection(
|
||||
var socket: BluetoothSocket? = null,
|
||||
var outputStream: OutputStream? = null,
|
||||
var connected: Boolean = false,
|
||||
var connecting: Boolean = false,
|
||||
var connectionJob: Job? = null
|
||||
)
|
||||
|
||||
class BluetoothReporter(
|
||||
private val bluetoothManager: BluetoothManager,
|
||||
private val reporterScope: CoroutineScope
|
||||
) {
|
||||
) : IReporterRepo<WithoutExtParams> {
|
||||
|
||||
private val tag = "BTReporter"
|
||||
private val sppid: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
|
||||
private lateinit var outputStream: OutputStream
|
||||
private var rotationConnectionJob: Job? = null
|
||||
private var rotationReportingJob: Job? = null
|
||||
private var connected = false
|
||||
private var connecting = false
|
||||
|
||||
fun isConnected(): Boolean = connected
|
||||
private val serviceToDevice = mutableMapOf<BtService, String>()
|
||||
private val deviceConnections = mutableMapOf<String, DeviceConnection>()
|
||||
private val writeMutex = Mutex()
|
||||
|
||||
fun isConnecting(): Boolean = connecting
|
||||
fun isConnected(service: BtService): Boolean {
|
||||
val deviceId = serviceToDevice[service] ?: return false
|
||||
return deviceConnections[deviceId]?.connected == true
|
||||
}
|
||||
fun isConnecting(service: BtService): Boolean {
|
||||
val deviceId = serviceToDevice[service] ?: return false
|
||||
return deviceConnections[deviceId]?.connecting == true
|
||||
}
|
||||
|
||||
fun connectBTDevice(deviceId: String) {
|
||||
if (!connected) {
|
||||
rotationConnectionJob = reporterScope.launch {
|
||||
try {
|
||||
bluetoothManager.adapter.getRemoteDevice(deviceId)?.let { device ->
|
||||
device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket ->
|
||||
connecting = true
|
||||
socket.connect()
|
||||
outputStream = socket.outputStream
|
||||
connected = true
|
||||
connecting = false
|
||||
Log.i(tag, "$tag: Connected!")
|
||||
}
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(tag, "$tag: ${e.message}")
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "$tag: ${e.message}")
|
||||
}
|
||||
fun connect(service: BtService, deviceId: String) {
|
||||
serviceToDevice[service] = deviceId
|
||||
val connection = deviceConnections.getOrPut(deviceId) {
|
||||
DeviceConnection()
|
||||
}
|
||||
if (connection.connected || connection.connecting) return
|
||||
connection.connectionJob = reporterScope.launch {
|
||||
try {
|
||||
connection.connecting = true
|
||||
val device = bluetoothManager.adapter.getRemoteDevice(deviceId)
|
||||
val socket = device.createInsecureRfcommSocketToServiceRecord(sppid)
|
||||
socket.connect()
|
||||
connection.socket = socket
|
||||
connection.outputStream = socket.outputStream
|
||||
connection.connected = true
|
||||
Log.i(tag, "$tag: Connected to $deviceId")
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "$tag: ${e.message}")
|
||||
connection.connected = false
|
||||
} finally {
|
||||
connection.connecting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reportRotation(format: String, azimuth: Int, elevation: Int) {
|
||||
if (connected) {
|
||||
rotationReportingJob = reporterScope.launch {
|
||||
val newElevation = if (elevation > 0) elevation else 0
|
||||
try {
|
||||
val azimuthString = intToStringWithLeadingZeroes(azimuth)
|
||||
val elevationString = intToStringWithLeadingZeroes(newElevation)
|
||||
val crChar = '\r'
|
||||
val nlChar = '\n'
|
||||
val tbChar = '\t'
|
||||
var buffer = format.replace("\$AZ", azimuthString)
|
||||
buffer = buffer.replace("\$EL", elevationString)
|
||||
buffer = buffer.replace("\\r", crChar.toString())
|
||||
buffer = buffer.replace("\\n", nlChar.toString())
|
||||
buffer = buffer.replace("\\t", tbChar.toString())
|
||||
Log.i(tag, "$tag: Sending $buffer")
|
||||
if (connected) outputStream.write(buffer.toByteArray())
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "$tag: ${e.message}")
|
||||
connected = false
|
||||
}
|
||||
private suspend fun write(service: BtService, buffer: String) {
|
||||
val deviceId = serviceToDevice[service] ?: return
|
||||
val connection = deviceConnections[deviceId] ?: return
|
||||
if (!connection.connected) return
|
||||
try {
|
||||
writeMutex.withLock {
|
||||
connection.outputStream?.write(buffer.toByteArray())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "$tag: Write failed ${e.message}")
|
||||
connection.connected = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportRotation(format: String, azimuth: Double, elevation: Double, params: WithoutExtParams) {
|
||||
reporterScope.launch {
|
||||
if (!isConnected(BtService.ROTATOR)) return@launch
|
||||
val newElevation = if (elevation > 0.0) elevation else 0.0
|
||||
val azimuthString = intToStringWithLeadingZeroes(azimuth.toInt())
|
||||
val elevationString = intToStringWithLeadingZeroes(newElevation.toInt())
|
||||
var buffer = format
|
||||
.replace("\$AZ", azimuthString)
|
||||
.replace("\$EL", elevationString)
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\t", "\t")
|
||||
write(BtService.ROTATOR, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportFrequency(format: String, frequency: Long, params: WithoutExtParams) {
|
||||
reporterScope.launch {
|
||||
if (!isConnected(BtService.FREQUENCY)) return@launch
|
||||
var buffer = format
|
||||
.replace("\$FREQ", frequency.toString())
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\t", "\t")
|
||||
write(BtService.FREQUENCY, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,49 +22,94 @@ import java.nio.ByteBuffer
|
||||
import java.nio.channels.SocketChannel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.launch
|
||||
import com.rtbishop.look4sat.domain.repository.IReporterParams
|
||||
import com.rtbishop.look4sat.domain.repository.IReporterRepo
|
||||
|
||||
class NetworkReporter(private val reporterScope: CoroutineScope) {
|
||||
data class ExtendedParams(
|
||||
val server: String,
|
||||
val port: Int
|
||||
) : IReporterParams
|
||||
|
||||
private var rotationSocketChannel: SocketChannel? = null
|
||||
private var rotationReporting: Job? = null
|
||||
enum class NetworkService { ROTATOR, FREQUENCY }
|
||||
|
||||
// private var frequencySocketChannel: SocketChannel? = null
|
||||
// private var frequencyReporting: Job? = null
|
||||
//
|
||||
// fun reportFrequency(server: String, port: Int, frequency: Long) {
|
||||
// frequencyReporting = reporterScope.launch {
|
||||
// runCatching {
|
||||
// if (frequencySocketChannel == null) {
|
||||
// frequencySocketChannel = SocketChannel.open(InetSocketAddress(server, port))
|
||||
// } else {
|
||||
// val buffer = ByteBuffer.wrap("\\set_freq $frequency\n".toByteArray())
|
||||
// frequencySocketChannel?.write(buffer)
|
||||
// }
|
||||
// }.onFailure { error ->
|
||||
// println(error.localizedMessage)
|
||||
// frequencySocketChannel = null
|
||||
// frequencyReporting?.cancelAndJoin()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
data class SocketConnection(
|
||||
var socket: SocketChannel? = null,
|
||||
var connected: Boolean = false,
|
||||
var connecting: Boolean = false,
|
||||
var connectionJob: Job? = null
|
||||
)
|
||||
|
||||
fun reportRotation(server: String, port: Int, azimuth: Double, elevation: Double) {
|
||||
rotationReporting = reporterScope.launch {
|
||||
val newElevation = if (elevation > 0.0) elevation else 0.0
|
||||
runCatching {
|
||||
if (rotationSocketChannel == null) {
|
||||
rotationSocketChannel = SocketChannel.open(InetSocketAddress(server, port))
|
||||
} else {
|
||||
val buffer = ByteBuffer.wrap("\\P $azimuth $newElevation\n".toByteArray())
|
||||
rotationSocketChannel?.write(buffer)
|
||||
}
|
||||
}.onFailure { error ->
|
||||
println(error.localizedMessage)
|
||||
rotationSocketChannel = null
|
||||
rotationReporting?.cancelAndJoin()
|
||||
class NetworkReporter(private val reporterScope: CoroutineScope) : IReporterRepo<ExtendedParams> {
|
||||
private val serviceToAddress = mutableMapOf<NetworkService, String>()
|
||||
private val connections = mutableMapOf<String, SocketConnection>()
|
||||
private val writeMutex = Mutex()
|
||||
|
||||
fun isConnected(service: NetworkService): Boolean {
|
||||
val addr = serviceToAddress[service] ?: return false
|
||||
return connections[addr]?.connected == true
|
||||
}
|
||||
|
||||
private fun getOrCreateConnection(addr: String): SocketConnection {
|
||||
return connections.getOrPut(addr) { SocketConnection() }
|
||||
}
|
||||
|
||||
fun connect(service: NetworkService, server: String, port: Int) {
|
||||
val addr = "$server:$port"
|
||||
serviceToAddress[service] = addr
|
||||
val connection = getOrCreateConnection(addr)
|
||||
if (connection.connected || connection.connecting) return
|
||||
connection.connectionJob = reporterScope.launch {
|
||||
try {
|
||||
connection.connecting = true
|
||||
val socket = SocketChannel.open(InetSocketAddress(server, port))
|
||||
connection.socket = socket
|
||||
connection.connected = true
|
||||
println("NetworkReporter: $service connected to $addr")
|
||||
} catch (e: Exception) {
|
||||
println("NetworkReporter connect error: ${e.message}")
|
||||
connection.connected = false
|
||||
} finally {
|
||||
connection.connecting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun write(service: NetworkService, command: String) {
|
||||
val addr = serviceToAddress[service] ?: return
|
||||
val connection = connections[addr] ?: return
|
||||
if (!connection.connected) return
|
||||
try {
|
||||
writeMutex.withLock {
|
||||
val buffer = ByteBuffer.wrap("\\$command\n".toByteArray())
|
||||
connection.socket?.write(buffer)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("NetworkReporter write error: ${e.message}")
|
||||
connection.connected = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportRotation(format: String, azimuth: Double, elevation: Double, params: ExtendedParams) {
|
||||
reporterScope.launch {
|
||||
connect(NetworkService.ROTATOR, params.server, params.port)
|
||||
if (!isConnected(NetworkService.ROTATOR)) return@launch
|
||||
val el = if (elevation > 0.0) elevation else 0.0
|
||||
val command = format
|
||||
.replace("\$AZ", azimuth.toString())
|
||||
.replace("\$EL", el.toString())
|
||||
write(NetworkService.ROTATOR, command)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportFrequency(format: String, frequency: Long, params: ExtendedParams) {
|
||||
reporterScope.launch {
|
||||
connect(NetworkService.FREQUENCY, params.server, params.port)
|
||||
if (!isConnected(NetworkService.FREQUENCY)) return@launch
|
||||
val command = format.replace("\$FREQ", frequency.toString())
|
||||
write(NetworkService.FREQUENCY, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,13 @@ class SettingsRepo(
|
||||
private val preferences: SharedPreferences
|
||||
) : ISettingsRepo, LocationListenerCompat {
|
||||
|
||||
private val keyBluetoothAddress = "bluetoothAddress"
|
||||
private val keyBluetoothName = "bluetoothName"
|
||||
private val keyBluetoothFormat = "bluetoothFormat"
|
||||
private val keyBluetoothState = "bluetoothState"
|
||||
private val keyBluetoothRotatorAddress = "bluetoothAddress"
|
||||
private val keyBluetoothRotatorName = "bluetoothName"
|
||||
private val keyBluetoothRotatorFormat = "bluetoothFormat"
|
||||
private val keyBluetoothRotatorState = "bluetoothState"
|
||||
private val keyBluetoothFrequencyState = "bluetoothFrequencyState"
|
||||
private val keyBluetoothFrequencyAddress = "bluetoothFrequencyAddress"
|
||||
private val keyBluetoothFrequencyFormat = "bluetoothFrequencyFormat"
|
||||
private val keyFilterHoursAhead = "filterHoursAhead"
|
||||
private val keyFilterMinElevation = "filterMinElevation"
|
||||
private val keyNumberOfRadios = "numberOfRadios"
|
||||
@@ -53,6 +56,11 @@ class SettingsRepo(
|
||||
private val keyRotatorAddress = "rotatorAddress"
|
||||
private val keyRotatorPort = "rotatorPort"
|
||||
private val keyRotatorState = "rotatorState"
|
||||
private val keyRotatorFormat = "rotatorFormat"
|
||||
private val keyFrequencyState = "frequencyState"
|
||||
private val keyFrequencyAddress = "frequencyAddress"
|
||||
private val keyFrequencyPort = "frequencyPort"
|
||||
private val keyFrequencyFormat = "frequencyFormat"
|
||||
private val keySelectedIds = "selectedIds"
|
||||
private val keySelectedTypes = "selectedTypes"
|
||||
private val keySelectedModes = "selectedModes"
|
||||
@@ -240,24 +248,39 @@ class SettingsRepo(
|
||||
private val _rcSettings = MutableStateFlow(getRCSettings())
|
||||
override val rcSettings: StateFlow<RCSettings> = _rcSettings
|
||||
|
||||
override fun setBluetoothAddress(value: String) {
|
||||
preferences.edit { putString(keyBluetoothAddress, value) }
|
||||
_rcSettings.update { it.copy(bluetoothAddress = value) }
|
||||
override fun setBluetoothRotatorAddress(value: String) {
|
||||
preferences.edit { putString(keyBluetoothRotatorAddress, value) }
|
||||
_rcSettings.update { it.copy(bluetoothRotatorAddress = value) }
|
||||
}
|
||||
|
||||
override fun setBluetoothFormat(value: String) {
|
||||
preferences.edit { putString(keyBluetoothFormat, value) }
|
||||
_rcSettings.update { it.copy(bluetoothFormat = value) }
|
||||
override fun setBluetoothRotatorFormat(value: String) {
|
||||
preferences.edit { putString(keyBluetoothRotatorFormat, value) }
|
||||
_rcSettings.update { it.copy(bluetoothRotatorFormat = value) }
|
||||
}
|
||||
|
||||
override fun setBluetoothName(value: String) {
|
||||
preferences.edit { putString(keyBluetoothName, value) }
|
||||
_rcSettings.update { it.copy(bluetoothName = value) }
|
||||
override fun setBluetoothRotatorName(value: String) {
|
||||
preferences.edit { putString(keyBluetoothRotatorName, value) }
|
||||
_rcSettings.update { it.copy(bluetoothRotatorName = value) }
|
||||
}
|
||||
|
||||
override fun setBluetoothState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyBluetoothState, value) }
|
||||
_rcSettings.update { it.copy(bluetoothState = value) }
|
||||
override fun setBluetoothRotatorState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyBluetoothRotatorState, value) }
|
||||
_rcSettings.update { it.copy(bluetoothRotatorState = value) }
|
||||
}
|
||||
|
||||
override fun setBluetoothFrequencyState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyBluetoothFrequencyState, value) }
|
||||
_rcSettings.update { it.copy(bluetoothFrequencyState = value) }
|
||||
}
|
||||
|
||||
override fun setBluetoothFrequencyAddress(value: String) {
|
||||
preferences.edit { putString(keyBluetoothFrequencyAddress, value) }
|
||||
_rcSettings.update { it.copy(bluetoothFrequencyAddress = value) }
|
||||
}
|
||||
|
||||
override fun setBluetoothFrequencyFormat(value: String) {
|
||||
preferences.edit { putString(keyBluetoothFrequencyFormat, value) }
|
||||
_rcSettings.update { it.copy(bluetoothFrequencyFormat = value) }
|
||||
}
|
||||
|
||||
override fun setRotatorAddress(value: String) {
|
||||
@@ -275,14 +298,47 @@ class SettingsRepo(
|
||||
_rcSettings.update { it.copy(rotatorState = value) }
|
||||
}
|
||||
|
||||
override fun setRotatorFormat(value: String) {
|
||||
preferences.edit { putString(keyRotatorFormat, value) }
|
||||
_rcSettings.update { it.copy(rotatorFormat = value) }
|
||||
}
|
||||
|
||||
override fun setFrequencyState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyFrequencyState, value) }
|
||||
_rcSettings.update { it.copy(frequencyState = value) }
|
||||
}
|
||||
|
||||
override fun setFrequencyAddress(value: String) {
|
||||
preferences.edit { putString(keyFrequencyAddress, value) }
|
||||
_rcSettings.update { it.copy(frequencyAddress = value) }
|
||||
}
|
||||
|
||||
override fun setFrequencyPort(value: String) {
|
||||
preferences.edit { putString(keyFrequencyPort, value) }
|
||||
_rcSettings.update { it.copy(frequencyPort = value) }
|
||||
}
|
||||
|
||||
override fun setFrequencyFormat(value: String) {
|
||||
preferences.edit { putString(keyFrequencyFormat, value) }
|
||||
_rcSettings.update { it.copy(frequencyFormat = value) }
|
||||
}
|
||||
|
||||
private fun getRCSettings(): RCSettings = RCSettings(
|
||||
rotatorState = preferences.getBoolean(keyRotatorState, false),
|
||||
rotatorAddress = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1",
|
||||
rotatorPort = preferences.getString(keyRotatorPort, null) ?: "4533",
|
||||
bluetoothState = preferences.getBoolean(keyBluetoothState, false),
|
||||
bluetoothFormat = preferences.getString(keyBluetoothFormat, null) ?: $$"W$AZ $EL",
|
||||
bluetoothName = preferences.getString(keyBluetoothName, null) ?: "Default",
|
||||
bluetoothAddress = preferences.getString(keyBluetoothAddress, null) ?: "00:0C:BF:13:80:5D"
|
||||
rotatorFormat = preferences.getString(keyRotatorFormat, null) ?: $$"P $AZ $EL",
|
||||
frequencyState = preferences.getBoolean(keyFrequencyState, false),
|
||||
frequencyAddress = preferences.getString(keyFrequencyAddress, null) ?: "127.0.0.1",
|
||||
frequencyPort = preferences.getString(keyFrequencyPort, null) ?: "4532",
|
||||
frequencyFormat = preferences.getString(keyFrequencyFormat, null) ?: $$"set_freq $FREQ",
|
||||
bluetoothRotatorState = preferences.getBoolean(keyBluetoothRotatorState, false),
|
||||
bluetoothRotatorFormat = preferences.getString(keyBluetoothRotatorFormat, null) ?: $$"W$AZ $EL",
|
||||
bluetoothRotatorName = preferences.getString(keyBluetoothRotatorName, null) ?: "Default",
|
||||
bluetoothRotatorAddress = preferences.getString(keyBluetoothRotatorAddress, null) ?: "00:0C:BF:13:80:5D",
|
||||
bluetoothFrequencyState = preferences.getBoolean(keyBluetoothFrequencyState, false),
|
||||
bluetoothFrequencyAddress = preferences.getString(keyBluetoothFrequencyAddress, null) ?: "00:0C:BF:13:80:5D",
|
||||
bluetoothFrequencyFormat = preferences.getString(keyBluetoothFrequencyFormat, null) ?: $$"FA$FREQ"
|
||||
)
|
||||
//endregion
|
||||
|
||||
|
||||
Reference in new issue
Block a user