diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassData.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassData.kt new file mode 100644 index 00000000..8f444460 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassData.kt @@ -0,0 +1,5 @@ +package com.rtbishop.look4sat.presentation.satPassInfoScreen + +import com.rtbishop.look4sat.domain.SatPos + +data class PassData(val position: SatPos, val positions: List = emptyList()) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoFragment.kt index da83153f..cc8f03bc 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoFragment.kt @@ -28,11 +28,11 @@ import com.rtbishop.look4sat.R import com.rtbishop.look4sat.data.PreferencesSource import com.rtbishop.look4sat.databinding.FragmentPolarBinding import com.rtbishop.look4sat.domain.SatPass +import com.rtbishop.look4sat.domain.SatPos import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.navigateSafe import com.rtbishop.look4sat.utility.toTimerString import dagger.hilt.android.AndroidEntryPoint -import java.util.* import javax.inject.Inject @AndroidEntryPoint @@ -69,49 +69,43 @@ class PassInfoFragment : Fragment(R.layout.fragment_polar) { viewModel.disableSensor() } - private fun setupObservers(satTransAdapter: SatTransAdapter, binding: FragmentPolarBinding) { + private fun setupObservers(transAdapter: SatTransAdapter, binding: FragmentPolarBinding) { val catNum = requireArguments().getInt("catNum") val aosTime = requireArguments().getLong("aosTime") - val stationPos = preferences.loadStationPosition() viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass -> passInfoView = PassInfoView(requireContext()).apply { setShowAim(preferences.shouldUseCompass()) - setPass(pass) - setStationPos(stationPos) setScanning(preferences.shouldShowSweep()) } binding.frame.addView(passInfoView) - observeTransmitters(pass, satTransAdapter, binding) + viewModel.passData.observe(viewLifecycleOwner, { passData -> + passInfoView?.setPosition(passData.position) + passInfoView?.setPositions(passData.positions) + setPassText(pass, passData.position, binding) + }) + viewModel.transmitters.observe(viewLifecycleOwner, { list -> + if (list.isNotEmpty()) { + transAdapter.submitList(list) + binding.recycler.visibility = View.VISIBLE + binding.noTransMsg.visibility = View.INVISIBLE + } else { + binding.recycler.visibility = View.INVISIBLE + binding.noTransMsg.visibility = View.VISIBLE + } + passInfoView?.invalidate() + }) + viewModel.orientation.observe(viewLifecycleOwner, { orientation -> + passInfoView?.setOrientation( + orientation.first, + orientation.second, + orientation.third + ) + }) } - viewModel.orientation.observe(viewLifecycleOwner, { orientation -> - passInfoView?.setOrientation(orientation.first, orientation.second, orientation.third) - }) } - private fun observeTransmitters( - satPass: SatPass, - satTransAdapter: SatTransAdapter, - binding: FragmentPolarBinding - ) { - viewModel.transmitters.observe(viewLifecycleOwner, { list -> - if (list.isNotEmpty()) { - satTransAdapter.submitList(list) - binding.recycler.visibility = View.VISIBLE - binding.noTransMsg.visibility = View.INVISIBLE - } else { - binding.recycler.visibility = View.INVISIBLE - binding.noTransMsg.visibility = View.VISIBLE - } - setPassText(satPass, binding) - passInfoView?.invalidate() - }) - } - - private fun setPassText(satPass: SatPass, binding: FragmentPolarBinding) { - val dateNow = Date() + private fun setPassText(satPass: SatPass, satPos: SatPos, binding: FragmentPolarBinding) { val timeNow = System.currentTimeMillis() - val stationPos = preferences.loadStationPosition() - val satPos = satPass.satellite.getPosition(stationPos, dateNow.time) val polarAz = getString(R.string.pat_azimuth) val polarEl = getString(R.string.pat_elevation) val polarRng = getString(R.string.pat_distance) @@ -123,13 +117,13 @@ class PassInfoFragment : Fragment(R.layout.fragment_polar) { binding.satName.text = satPass.name if (!satPass.isDeepSpace) { - if (dateNow.time < satPass.aosTime) { + if (timeNow < satPass.aosTime) { val millisBeforeStart = satPass.aosTime.minus(timeNow) binding.polarTimer.text = millisBeforeStart.toTimerString() } else { val millisBeforeEnd = satPass.losTime.minus(timeNow) binding.polarTimer.text = millisBeforeEnd.toTimerString() - if (dateNow.time > satPass.losTime) { + if (timeNow > satPass.losTime) { binding.polarTimer.text = 0L.toTimerString() findNavController().navigateSafe(R.id.action_polar_to_passes) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoView.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoView.kt index 8b0f0d4d..a33e5b73 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoView.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoView.kt @@ -22,23 +22,21 @@ import android.graphics.* import android.view.View import androidx.core.content.ContextCompat import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.domain.GeoPos -import com.rtbishop.look4sat.domain.SatPass -import java.util.* +import com.rtbishop.look4sat.domain.SatPos import kotlin.math.cos import kotlin.math.min import kotlin.math.sin class PassInfoView(context: Context) : View(context) { - private lateinit var satPass: SatPass private val defaultColor = ContextCompat.getColor(context, R.color.themeLight) private val scale = resources.displayMetrics.density private val radarWidth = resources.displayMetrics.widthPixels private val radarRadius = radarWidth * 0.48f private val piDiv2 = Math.PI / 2.0 private val strokeSize = scale * 2f - private var stationPos: GeoPos = GeoPos(0.0, 0.0, 0.0) + private var position: SatPos? = null + private var positions: List = emptyList() private var radarColor = ContextCompat.getColor(context, R.color.greyLight) private var radarCircleNum = 3 @@ -103,12 +101,12 @@ class PassInfoView(context: Context) : View(context) { shouldShowSweep = isScanning } - fun setPass(satPass: SatPass) { - this.satPass = satPass + fun setPosition(position: SatPos) { + this.position = position } - fun setStationPos(stationPos: GeoPos) { - this.stationPos = stationPos + fun setPositions(positions: List) { + this.positions = positions } fun setShowAim(showAim: Boolean) { @@ -131,8 +129,8 @@ class PassInfoView(context: Context) : View(context) { val cx = paddingLeft + radarWidth / 2f val cy = paddingTop + radarHeight / 2f - if (!satPass.isDeepSpace && !isTrackCreated) { - createPassTrajectory(satPass) + if (positions.isNotEmpty() && !isTrackCreated) { + createPassTrajectory() createPassTrajectoryArrow() isTrackCreated = true } @@ -143,12 +141,12 @@ class PassInfoView(context: Context) : View(context) { drawRadarText(canvas, cx, radarRadius) canvas.translate(cx, cy) - if (!satPass.isDeepSpace) { + if (positions.isNotEmpty()) { canvas.drawPath(trackPath, trackPaint) canvas.drawPath(trackPath, arrowPaint) } if (shouldShowBeacons) { - drawSatellite(canvas, satPass) + drawSatellite(canvas) } if (shouldShowAim) { drawCrosshair(canvas, azimuth, pitch) @@ -179,12 +177,13 @@ class PassInfoView(context: Context) : View(context) { } } - private fun drawSatellite(canvas: Canvas, satPass: SatPass) { - val satPos = satPass.satellite.getPosition(stationPos, Date().time) - if (satPos.elevation > 0) { - val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble()) - val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble()) - canvas.drawCircle(satX, -satY, beaconSize, beaconPaint) + private fun drawSatellite(canvas: Canvas) { + position?.let { satPos -> + if (satPos.elevation > 0) { + val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble()) + val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble()) + canvas.drawCircle(satX, -satY, beaconSize, beaconPaint) + } } } @@ -209,18 +208,15 @@ class PassInfoView(context: Context) : View(context) { return (radius * sin(piDiv2 - azimuth)).toFloat() } - private fun createPassTrajectory(satPass: SatPass) { - var currentTime = satPass.aosTime - while (currentTime < satPass.losTime) { - val satPos = satPass.satellite.getPosition(stationPos, currentTime) + private fun createPassTrajectory() { + positions.forEachIndexed { index, satPos -> val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble()) val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble()) - if (currentTime == satPass.aosTime) { + if (index == 0) { trackPath.moveTo(passX, -passY) } else { trackPath.lineTo(passX, -passY) } - currentTime += 15000 } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoViewModel.kt index 08efe72b..7a86fb76 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satPassInfoScreen/PassInfoViewModel.kt @@ -18,14 +18,19 @@ package com.rtbishop.look4sat.presentation.satPassInfoScreen import androidx.lifecycle.* +import androidx.navigation.fragment.findNavController +import com.rtbishop.look4sat.R import com.rtbishop.look4sat.data.PreferencesSource import com.rtbishop.look4sat.data.SatelliteRepo import com.rtbishop.look4sat.domain.Predictor import com.rtbishop.look4sat.domain.SatPass +import com.rtbishop.look4sat.domain.SatPos import com.rtbishop.look4sat.domain.Transmitter import com.rtbishop.look4sat.framework.OrientationProvider import com.rtbishop.look4sat.injection.IoDispatcher +import com.rtbishop.look4sat.utility.navigateSafe import com.rtbishop.look4sat.utility.round +import com.rtbishop.look4sat.utility.toTimerString import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.collect @@ -44,8 +49,10 @@ class PassInfoViewModel @Inject constructor( ) : ViewModel(), OrientationProvider.OrientationListener { private val stationPos = preferences.loadStationPosition() + private val _passData = MutableLiveData() private val _transmitters = MutableLiveData>() private val _orientation = MutableLiveData>() + val passData: LiveData = _passData val transmitters: LiveData> = _transmitters val orientation: LiveData> = _orientation @@ -53,9 +60,10 @@ class PassInfoViewModel @Inject constructor( predictor.passes.collect { passes -> val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime } pass?.let { satPass -> + emit(satPass) + sendPassData(satPass) processTransmitters(satPass) initRotatorControl(satPass) - emit(satPass) } } } @@ -72,6 +80,22 @@ class PassInfoViewModel @Inject constructor( _orientation.value = Triple(azimuth + preferences.getMagDeclination(), pitch, roll) } + private fun sendPassData(satPass: SatPass) { + viewModelScope.launch { + var track: List = emptyList() + if (!satPass.isDeepSpace) { + val startDate = Date(satPass.aosTime) + val endDate = Date(satPass.losTime) + track = predictor.getSatTrack(satPass.satellite, stationPos, startDate, endDate) + } + while (isActive) { + val pos = predictor.getSatPos(satPass.satellite, stationPos, Date()) + _passData.postValue(PassData(pos, track)) + delay(1000) + } + } + } + private fun initRotatorControl(satPass: SatPass) { viewModelScope.launch { val rotatorPrefs = preferences.getRotatorServer() diff --git a/app/src/main/java/com/rtbishop/look4sat/utility/CustomHandler.kt b/app/src/main/java/com/rtbishop/look4sat/utility/CustomHandler.kt new file mode 100644 index 00000000..387d09a4 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/utility/CustomHandler.kt @@ -0,0 +1,18 @@ +package com.rtbishop.look4sat.utility + +import android.app.Activity + +class CustomHandler(private val activity: Activity) : Thread.UncaughtExceptionHandler { + + private val rootHandler = Thread.getDefaultUncaughtExceptionHandler() + + init { + Thread.setDefaultUncaughtExceptionHandler(this) + } + + override fun uncaughtException(thread: Thread, throwable: Throwable) { +// Custom exception handling, use intents instead of dialogs +// Process.killProcess(Process.myPid()) +// exitProcess(0) + } +} diff --git a/core/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt b/core/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt index 87431a8b..f1d1fc6b 100644 --- a/core/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt +++ b/core/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt @@ -90,7 +90,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) { deep = DeepSpaceCalculator(dsv) } - fun calculateSDP4(tSince: Double) { + internal fun calculateSDP4(tSince: Double) { synchronized(this) { val temp = DoubleArray(12) val xmdf = params.xmo + dsv.xmdot * tSince diff --git a/core/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt b/core/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt index 8bf5dd57..38a526df 100644 --- a/core/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt +++ b/core/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt @@ -135,7 +135,7 @@ class NearEarthSat(params: TLE) : Satellite(params) { } } - fun calculateSGP4(tSince: Double) { + internal fun calculateSGP4(tSince: Double) { synchronized(this) { val temp = DoubleArray(9) val xmdf = params.xmo + xmdot * tSince diff --git a/core/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt b/core/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt index df4b022f..585119a4 100644 --- a/core/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt +++ b/core/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt @@ -42,7 +42,7 @@ abstract class Satellite(val params: TLE) { var qoms24 = 0.0 var s4 = 0.0 - fun willBeSeen(pos: GeoPos): Boolean { + internal fun willBeSeen(pos: GeoPos): Boolean { return if (params.meanmo < 1e-8) false else { val sma = 331.25 * exp(ln(1440.0 / params.meanmo) * (2.0 / 3.0)) @@ -53,7 +53,7 @@ abstract class Satellite(val params: TLE) { } } - fun getPosition(pos: GeoPos, time: Long): SatPos { + internal fun getPosition(pos: GeoPos, time: Long): SatPos { val satPos = SatPos() // Date/time at which the position and velocity were calculated val julUTC = calcCurrentDaynum(time) + 2444238.5 @@ -89,7 +89,7 @@ abstract class Satellite(val params: TLE) { return julianDateOfYear(year) + day } - fun julianDateOfYear(theYear: Double): Double { + internal fun julianDateOfYear(theYear: Double): Double { val aYear = theYear - 1 var i = floor(aYear / 100).toLong() val a = i @@ -207,7 +207,7 @@ abstract class Satellite(val params: TLE) { } } - fun calculatePosAndVel( + internal fun calculatePosAndVel( rk: Double, uk: Double, xnodek: Double, xinck: Double, rdotk: Double, rfdotk: Double ) { @@ -234,7 +234,7 @@ abstract class Satellite(val params: TLE) { velocity.z = rdotk * uz + rfdotk * vz } - class Vector4 { + internal class Vector4 { var w = 0.0 var x = 0.0 var y = 0.0 @@ -253,16 +253,16 @@ abstract class Satellite(val params: TLE) { } } - fun sqr(arg: Double): Double { + internal fun sqr(arg: Double): Double { return arg * arg } - fun invert(value: Double): Double { + internal fun invert(value: Double): Double { return 1.0 / value } // Calculates the modulus of 2 * PI - fun mod2PI(value: Double): Double { + internal fun mod2PI(value: Double): Double { var retVal = value val i = (retVal / twoPi).toInt() retVal -= i * twoPi @@ -271,7 +271,7 @@ abstract class Satellite(val params: TLE) { } // Solves Keplers' Equation - fun converge(temp: DoubleArray, axn: Double, ayn: Double, capu: Double) { + internal fun converge(temp: DoubleArray, axn: Double, ayn: Double, capu: Double) { var converged = false var i = 0 do { @@ -287,7 +287,7 @@ abstract class Satellite(val params: TLE) { } // Sets perigee and checks and adjusts the calculation if the perigee is less tan 156KM - fun setPerigee(perigee: Double) { + internal fun setPerigee(perigee: Double) { this.perigee = perigee checkPerigee() }