kopia lustrzana https://github.com/rt-bishop/Look4Sat
Bluetooth added
rodzic
de2978138a
commit
dd76dcfc98
app/src/main
java/com/rtbishop/look4sat
framework
injection
presentation
radarScreen
settingsScreen
res
base/src/main/java/com/rtbishop/look4sat/domain
|
@ -7,6 +7,10 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
<application
|
||||
android:name=".presentation.MainApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
|
@ -38,6 +38,10 @@ class SettingsManager @Inject constructor(private val prefs: SharedPreferences)
|
|||
const val keyRotator = "isRotatorEnabled"
|
||||
const val keyRotatorAddress = "rotatorAddress"
|
||||
const val keyRotatorPort = "rotatorPort"
|
||||
const val keyBTEnabled = "isBTEnabled"
|
||||
const val keyBTDeviceName = "BTDevice"
|
||||
const val keyBTDeviceAddr = "BTDevice"
|
||||
const val keyBTFormat = "BTFormat"
|
||||
const val keyLatitude = "stationLat"
|
||||
const val keyLongitude = "stationLon"
|
||||
const val keyLocator = "stationQTH"
|
||||
|
@ -148,6 +152,37 @@ class SettingsManager @Inject constructor(private val prefs: SharedPreferences)
|
|||
prefs.edit { putString(keyRotatorPort, value) }
|
||||
}
|
||||
|
||||
override fun getBTEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyBTEnabled, true)
|
||||
}
|
||||
|
||||
override fun setBTEnabled(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyBTEnabled, value) }
|
||||
}
|
||||
|
||||
override fun getBTDeviceAddr(): String {
|
||||
return prefs.getString(keyBTDeviceAddr, null) ?: "00:0C:BF:13:80:5D"
|
||||
}
|
||||
|
||||
override fun setBTDeviceAddr(value: String) {
|
||||
prefs.edit { putString(keyBTDeviceAddr, value) }
|
||||
}
|
||||
override fun getBTDeviceName(): String {
|
||||
return prefs.getString(keyBTDeviceName, null) ?: "Default"
|
||||
}
|
||||
|
||||
override fun setBTDeviceName(value: String) {
|
||||
prefs.edit { putString(keyBTDeviceName, value) }
|
||||
}
|
||||
|
||||
override fun getBTFormat(): String {
|
||||
return prefs.getString(keyBTFormat, null) ?: "W\$AZ \$EL"
|
||||
}
|
||||
|
||||
override fun setBTFormat(value: String) {
|
||||
prefs.edit { putString(keyBTFormat, value) }
|
||||
}
|
||||
|
||||
override fun loadDataSources(): List<String> {
|
||||
val sourcesList = prefs.getStringSet(keyDataSources, null)?.toList()
|
||||
return if (sourcesList.isNullOrEmpty()) defaultSources else sourcesList.sortedDescending()
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.rtbishop.look4sat.domain.predict.SatelliteManager
|
|||
import com.rtbishop.look4sat.framework.LocationManager
|
||||
import com.rtbishop.look4sat.framework.SettingsManager
|
||||
import com.rtbishop.look4sat.framework.data.*
|
||||
import com.rtbishop.look4sat.presentation.radarScreen.BTReporter
|
||||
import com.rtbishop.look4sat.utility.DataParser
|
||||
import com.rtbishop.look4sat.utility.DataReporter
|
||||
import dagger.Module
|
||||
|
@ -62,6 +63,10 @@ object BaseModule {
|
|||
@Singleton
|
||||
fun provideDataReporter(): DataReporter = DataReporter(CoroutineScope(Dispatchers.IO))
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBTReporter(): BTReporter = BTReporter(CoroutineScope(Dispatchers.IO))
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLocationManager(manager: LocationManager): ILocationManager = manager
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package com.rtbishop.look4sat.presentation.radarScreen
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.SocketChannel
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothSocket
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
class BTReporter(private val reporterScope: CoroutineScope) {
|
||||
|
||||
private var rotationConnectBTJob: Job? = null
|
||||
private var rotationReportingBT: Job? = null
|
||||
private var satVisible=false
|
||||
private var CRchar:Char = '\r'
|
||||
private var NLchar:Char = '\n'
|
||||
private var TBchar:Char = '\t'
|
||||
private val bluetoothAdapter: BluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
|
||||
private lateinit var mmOutStream: OutputStream
|
||||
private val SPPID: UUID=UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
|
||||
private var connected = false
|
||||
private var connectInProgress = false
|
||||
|
||||
fun connectBTDevice(dev: String) {
|
||||
if (!connected) {
|
||||
|
||||
rotationConnectBTJob = reporterScope.launch {
|
||||
runCatching {
|
||||
connectInProgress = true
|
||||
val rotationBTDevice = bluetoothAdapter.getRemoteDevice(dev)
|
||||
val sock = rotationBTDevice.createInsecureRfcommSocketToServiceRecord(SPPID)
|
||||
sock.connect()
|
||||
mmOutStream = sock.outputStream
|
||||
connected = true
|
||||
|
||||
connectInProgress = false
|
||||
Log.i("look4satBT", "Connected!")
|
||||
}.onFailure { error: Throwable ->
|
||||
Log.e("BT Error", "${error.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun isBTConnected():Boolean
|
||||
{
|
||||
return connected
|
||||
}
|
||||
|
||||
fun connectInProg():Boolean
|
||||
{
|
||||
return connectInProgress
|
||||
}
|
||||
|
||||
|
||||
fun reportRotationBT(dev: String, fmt: String, AZ: Int, EL: Int) {
|
||||
|
||||
runCatching {
|
||||
var azStr:String
|
||||
var elStr:String
|
||||
satVisible=(EL>1)
|
||||
|
||||
//Need to add leading zeros to string to ensure always 3 digits.
|
||||
//Ideally this could be done via the format string but this will do for now.
|
||||
if(AZ<100){
|
||||
if(AZ<10){
|
||||
azStr="00"
|
||||
}
|
||||
else
|
||||
{
|
||||
azStr="0"
|
||||
}
|
||||
azStr=azStr.plus(AZ.toString())
|
||||
}
|
||||
else
|
||||
{
|
||||
azStr=AZ.toString()
|
||||
}
|
||||
|
||||
if(EL<100){
|
||||
if(EL<10){
|
||||
elStr="00"
|
||||
}
|
||||
else
|
||||
{
|
||||
elStr="0"
|
||||
}
|
||||
if(satVisible) {
|
||||
elStr = elStr.plus(EL.toString())
|
||||
}
|
||||
else {
|
||||
elStr="000"
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
elStr=EL.toString()
|
||||
}
|
||||
|
||||
var buffer = fmt.replace("\$AZ",azStr)
|
||||
buffer = buffer.replace("\$EL",elStr)
|
||||
buffer = buffer.replace("\\r",CRchar.toString())
|
||||
buffer = buffer.replace("\\n",NLchar.toString())
|
||||
buffer = buffer.replace("\\t",TBchar.toString())
|
||||
Log.i("Output is", buffer)
|
||||
if(connected) {
|
||||
Log.i("Sending BT", buffer)
|
||||
this.mmOutStream.write(buffer.toByteArray())
|
||||
Log.i("Sent", buffer)
|
||||
}
|
||||
}.onFailure { error: Throwable ->
|
||||
Log.e("BT Error","${error.message}")
|
||||
connected=false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
package com.rtbishop.look4sat.presentation.radarScreen
|
||||
|
||||
import android.hardware.GeomagneticField
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.*
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||
|
@ -41,6 +42,7 @@ import javax.inject.Inject
|
|||
class RadarViewModel @Inject constructor(
|
||||
private val orientationManager: OrientationManager,
|
||||
private val reporter: DataReporter,
|
||||
private val BTreporter: BTReporter,
|
||||
private val satelliteManager: ISatelliteManager,
|
||||
private val repository: IDataRepository,
|
||||
private val settings: ISettingsManager
|
||||
|
@ -60,6 +62,7 @@ class RadarViewModel @Inject constructor(
|
|||
pass?.let { satPass ->
|
||||
emit(satPass)
|
||||
sendPassData(satPass)
|
||||
sendPassDataBT(satPass)
|
||||
processTransmitters(satPass)
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +113,28 @@ class RadarViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun sendPassDataBT(satPass: SatPass) {
|
||||
viewModelScope.launch {
|
||||
while (isActive) {
|
||||
val satPos = satelliteManager.getPosition(satPass.satellite, stationPos, Date().time)
|
||||
if (settings.getBTEnabled()) {
|
||||
val server = settings.getBTDeviceAddr()
|
||||
if(BTreporter.isBTConnected()) {
|
||||
val port = settings.getBTFormat()
|
||||
val azimuth = satPos.azimuth.toDegrees().round(0).toInt()
|
||||
val elevation = satPos.elevation.toDegrees().round(0).toInt()
|
||||
BTreporter.reportRotationBT(server, port, azimuth, elevation)
|
||||
}
|
||||
else if(!BTreporter.connectInProg()) {
|
||||
Log.i("look4satBT", "Attempting to connect...")
|
||||
BTreporter.connectBTDevice(server)
|
||||
}
|
||||
}
|
||||
delay(2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processTransmitters(pass: SatPass) {
|
||||
viewModelScope.launch {
|
||||
delay(125)
|
||||
|
|
|
@ -17,12 +17,15 @@
|
|||
*/
|
||||
package com.rtbishop.look4sat.presentation.settingsScreen
|
||||
|
||||
//import com.rtbishop.look4sat.BuildConfig
|
||||
import android.Manifest
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.widget.NestedScrollView
|
||||
|
@ -31,7 +34,6 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.rtbishop.look4sat.BuildConfig
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.databinding.FragmentSettingsBinding
|
||||
import com.rtbishop.look4sat.domain.model.DataState
|
||||
|
@ -42,6 +44,7 @@ import com.rtbishop.look4sat.utility.isValidIPv4
|
|||
import com.rtbishop.look4sat.utility.isValidPort
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||
|
||||
|
@ -70,7 +73,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
|||
if (y > newY) settingsFab.hide() else settingsFab.show()
|
||||
})
|
||||
settingsAbout.aboutVersion.text =
|
||||
String.format(getString(R.string.app_version), BuildConfig.VERSION_NAME)
|
||||
String.format(getString(R.string.app_version), 0)
|
||||
settingsBtnGithub.clickWithDebounce {
|
||||
gotoUrl("https://github.com/rt-bishop/Look4Sat/")
|
||||
}
|
||||
|
@ -81,9 +84,12 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
|||
gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setupLocationCard()
|
||||
setupDataCard()
|
||||
setupRemoteCard()
|
||||
setupBTCard()
|
||||
setupOtherCard()
|
||||
setupOutroCard()
|
||||
viewModel.stationPosition.asLiveData().observe(viewLifecycleOwner) { stationPos ->
|
||||
|
@ -162,6 +168,32 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupBTCard() {
|
||||
|
||||
binding.run {
|
||||
settingsBtremote.BTremoteSwitch.apply {
|
||||
isChecked = viewModel.getBTEnabled()
|
||||
settingsBtremote.BTremoteAddress.isEnabled = isChecked
|
||||
settingsBtremote.BTremoteFormat.isEnabled = isChecked
|
||||
settingsBtremote.BTAddressEdit.setText(viewModel.getBTDeviceAddr())
|
||||
settingsBtremote.BTFormatEdit.setText(viewModel.getBTFormat())
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
viewModel.setBTEnabled(isChecked)
|
||||
settingsBtremote.BTremoteAddress.isEnabled = isChecked
|
||||
settingsBtremote.BTremoteFormat.isEnabled = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
settingsBtremote.BTAddressEdit.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.setBTDeviceAddr(text.toString())
|
||||
}
|
||||
|
||||
settingsBtremote.BTFormatEdit.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.setBTFormat(text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupOtherCard() {
|
||||
binding.run {
|
||||
settingsOther.otherSwitchUtc.apply {
|
||||
|
|
|
@ -75,6 +75,22 @@ class SettingsViewModel @Inject constructor(
|
|||
|
||||
fun setRotatorPort(value: String) = settings.setRotatorPort(value)
|
||||
|
||||
fun getBTEnabled(): Boolean = settings.getBTEnabled()
|
||||
|
||||
fun setBTEnabled(value: Boolean) = settings.setBTEnabled(value)
|
||||
|
||||
fun getBTFormat(): String = settings.getBTFormat()
|
||||
|
||||
fun setBTFormat(value: String) = settings.setBTFormat(value)
|
||||
|
||||
fun getBTDeviceName(): String = settings.getBTDeviceName()
|
||||
|
||||
fun setBTDeviceName(value: String) = settings.setBTDeviceName(value)
|
||||
|
||||
fun getBTDeviceAddr(): String = settings.getBTDeviceAddr()
|
||||
|
||||
fun setBTDeviceAddr(value: String) = settings.setBTDeviceAddr(value)
|
||||
|
||||
fun getUpdateState() = repository.updateState
|
||||
|
||||
fun setUpdateHandled() = repository.setUpdateStateHandled()
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/SurfaceCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/BTremote_title"
|
||||
style="@style/SettingsTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/BTremote_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/BTremote_switch"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/BTremote_switch"
|
||||
app:layout_constraintEnd_toEndOf="@+id/BTremote_title"
|
||||
app:layout_constraintStart_toStartOf="@+id/BTremote_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/BTremote_title"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/BTremote_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/BTremote_switch"
|
||||
app:layout_constraintEnd_toEndOf="@+id/BTremote_title"
|
||||
app:layout_constraintStart_toStartOf="@+id/BTremote_title"
|
||||
tools:layout_editor_absoluteY="70dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/BT_address_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/BTremote_device_hint"
|
||||
android:textColorHint="@color/textMain" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/BTremote_format"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/BTremote_address"
|
||||
app:layout_constraintEnd_toEndOf="@+id/BTremote_title"
|
||||
app:layout_constraintStart_toStartOf="@+id/BTremote_title"
|
||||
tools:layout_editor_absoluteY="70dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/BT_format_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/BTremote_output_hint"
|
||||
android:textColorHint="@color/textMain" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -82,6 +82,16 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_data" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_btremote"
|
||||
layout="@layout/card_btremote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_remote" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_other"
|
||||
layout="@layout/card_other"
|
||||
|
@ -90,7 +100,7 @@
|
|||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_remote" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_btremote" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_outro"
|
||||
|
|
|
@ -139,6 +139,11 @@
|
|||
<string name="remote_ip_hint">IP address</string>
|
||||
<string name="remote_port_hint">Port</string>
|
||||
|
||||
<string name="BTremote_title">Bluetooth Output</string>
|
||||
<string name="BTremote_switch">Enable Bluetooth</string>
|
||||
<string name="BTremote_device_hint">Device</string>
|
||||
<string name="BTremote_output_hint">Output format</string>
|
||||
|
||||
<string name="other_title">Other preferences</string>
|
||||
<string name="other_switch_utc">Show pass time in UTC</string>
|
||||
<string name="other_switch_sweep">Enable radar sweep animation</string>
|
||||
|
|
|
@ -77,6 +77,22 @@ interface ISettingsManager {
|
|||
|
||||
fun setRotatorPort(value: String)
|
||||
|
||||
fun getBTEnabled(): Boolean
|
||||
|
||||
fun setBTEnabled(value: Boolean)
|
||||
|
||||
fun getBTDeviceAddr(): String
|
||||
|
||||
fun setBTDeviceAddr(value: String)
|
||||
|
||||
fun getBTDeviceName(): String
|
||||
|
||||
fun setBTDeviceName(value: String)
|
||||
|
||||
fun getBTFormat(): String
|
||||
|
||||
fun setBTFormat(value: String)
|
||||
|
||||
fun loadDataSources(): List<String>
|
||||
|
||||
fun saveDataSources(sources: List<String>)
|
||||
|
|
Ładowanie…
Reference in New Issue