From dd76dcfc98d67144a9c0f5f944c090a0ff5c62dd Mon Sep 17 00:00:00 2001 From: Andrew <ajohns1288@gmail.com> Date: Fri, 18 Mar 2022 14:35:14 -0400 Subject: [PATCH] Bluetooth added --- app/src/main/AndroidManifest.xml | 4 + .../look4sat/framework/SettingsManager.kt | 35 +++++ .../rtbishop/look4sat/injection/BaseModule.kt | 5 + .../presentation/radarScreen/BTReporter.kt | 134 ++++++++++++++++++ .../radarScreen/RadarViewModel.kt | 25 ++++ .../settingsScreen/SettingsFragment.kt | 36 ++++- .../settingsScreen/SettingsViewModel.kt | 16 +++ app/src/main/res/layout/card_btremote.xml | 74 ++++++++++ app/src/main/res/layout/fragment_settings.xml | 12 +- app/src/main/res/values/strings.xml | 5 + .../look4sat/domain/ISettingsManager.kt | 16 +++ 11 files changed, 359 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/BTReporter.kt create mode 100644 app/src/main/res/layout/card_btremote.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 998bbbdc..71c49a71 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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" diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt index d353c008..2eb4ae18 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt @@ -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() diff --git a/app/src/main/java/com/rtbishop/look4sat/injection/BaseModule.kt b/app/src/main/java/com/rtbishop/look4sat/injection/BaseModule.kt index 266b9cab..dadb4d59 100644 --- a/app/src/main/java/com/rtbishop/look4sat/injection/BaseModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/injection/BaseModule.kt @@ -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 diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/BTReporter.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/BTReporter.kt new file mode 100644 index 00000000..e4f0acac --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/BTReporter.kt @@ -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 + } + } +} + diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt index 2cf6af1b..501d0d4e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt @@ -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) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt index 85be5278..350e7ada 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt @@ -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 { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt index ac78f754..4d69f906 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt @@ -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() diff --git a/app/src/main/res/layout/card_btremote.xml b/app/src/main/res/layout/card_btremote.xml new file mode 100644 index 00000000..ca72fc19 --- /dev/null +++ b/app/src/main/res/layout/card_btremote.xml @@ -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> diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 3cfa5139..d3604d78 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -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" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c419165..d20ef091 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -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> diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsManager.kt b/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsManager.kt index 17ec4b63..1bc0a946 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsManager.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsManager.kt @@ -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>)