summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'android-app/app/src/main/kotlin')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/BarometerData.kt42
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/BarometerSensorManager.kt99
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/BarometerTrendView.kt72
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt15
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt30
5 files changed, 255 insertions, 3 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/BarometerData.kt b/android-app/app/src/main/kotlin/org/terst/nav/BarometerData.kt
new file mode 100644
index 0000000..5a8ccce
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/BarometerData.kt
@@ -0,0 +1,42 @@
+package org.terst.nav
+
+import java.util.Locale
+
+data class BarometerReading(
+ val pressureHpa: Float,
+ val timestamp: Long = System.currentTimeMillis()
+)
+
+enum class PressureTrend {
+ RISING_FAST,
+ RISING,
+ STEADY,
+ FALLING,
+ FALLING_FAST;
+
+ override fun toString(): String {
+ return when (this) {
+ RISING_FAST -> "Rising Fast"
+ RISING -> "Rising"
+ STEADY -> "Steady"
+ FALLING -> "Falling"
+ FALLING_FAST -> "Falling Fast"
+ }
+ }
+}
+
+data class BarometerStatus(
+ val currentPressureHpa: Float = 1013.25f,
+ val trend: PressureTrend = PressureTrend.STEADY,
+ val pressureChange3h: Float = 0f,
+ val history: List<BarometerReading> = emptyList()
+) {
+ fun formatPressure(): String {
+ return String.format(Locale.getDefault(), "%.1f hPa", currentPressureHpa)
+ }
+
+ fun formatTrend(): String {
+ val sign = if (pressureChange3h >= 0) "+" else ""
+ return String.format(Locale.getDefault(), "%s (%s%.1f hPa/3h)", trend.toString(), sign, pressureChange3h)
+ }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/BarometerSensorManager.kt b/android-app/app/src/main/kotlin/org/terst/nav/BarometerSensorManager.kt
new file mode 100644
index 0000000..cdd7f76
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/BarometerSensorManager.kt
@@ -0,0 +1,99 @@
+package org.terst.nav
+
+import android.content.Context
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.util.concurrent.TimeUnit
+import android.util.Log
+
+class BarometerSensorManager(context: Context) : SensorEventListener {
+
+ private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ private val pressureSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE)
+
+ private val _barometerStatus = MutableStateFlow(BarometerStatus())
+ val barometerStatus: StateFlow<BarometerStatus> = _barometerStatus.asStateFlow()
+
+ private val historyMaxDurationMs = TimeUnit.HOURS.toMillis(24) // Keep 24h history
+ private val historySampleIntervalMs = TimeUnit.MINUTES.toMillis(15) // Sample every 15 min for history
+ private var lastHistorySampleTime = 0L
+
+ fun start() {
+ if (pressureSensor != null) {
+ sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_NORMAL)
+ Log.d("BarometerManager", "Pressure sensor registered")
+ } else {
+ Log.w("BarometerManager", "No pressure sensor found on this device")
+ }
+ }
+
+ fun stop() {
+ sensorManager.unregisterListener(this)
+ Log.d("BarometerManager", "Pressure sensor unregistered")
+ }
+
+ override fun onSensorChanged(event: SensorEvent) {
+ if (event.sensor.type == Sensor.TYPE_PRESSURE) {
+ val pressure = event.values[0]
+ updateCurrentPressure(pressure)
+ }
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ // Not used
+ }
+
+ private fun updateCurrentPressure(pressure: Float) {
+ val now = System.currentTimeMillis()
+
+ _barometerStatus.update { currentStatus ->
+ val isFirstSample = currentStatus.history.isEmpty()
+ val newHistory = if (isFirstSample || now - lastHistorySampleTime >= historySampleIntervalMs) {
+ lastHistorySampleTime = now
+ val updatedHistory = currentStatus.history + BarometerReading(pressure, now)
+ // Trim history to 24h
+ updatedHistory.filter { now - it.timestamp <= historyMaxDurationMs }
+ } else {
+ currentStatus.history
+ }
+
+ val change3h = calculatePressureChange(newHistory, now, TimeUnit.HOURS.toMillis(3))
+ val trend = determineTrend(change3h)
+
+ currentStatus.copy(
+ currentPressureHpa = pressure,
+ trend = trend,
+ pressureChange3h = change3h,
+ history = newHistory
+ )
+ }
+ }
+
+ private fun calculatePressureChange(history: List<BarometerReading>, now: Long, durationMs: Long): Float {
+ if (history.isEmpty()) return 0f
+
+ val targetTime = now - durationMs
+ val oldReading = history.find { it.timestamp >= targetTime } ?: history.first()
+ val currentReading = history.last()
+
+ // If we don't have enough history, we might not be able to calculate a meaningful 3h change
+ // but we'll return the difference between the oldest available and current.
+ return currentReading.pressureHpa - oldReading.pressureHpa
+ }
+
+ private fun determineTrend(change3h: Float): PressureTrend {
+ return when {
+ change3h >= 2.0f -> PressureTrend.RISING_FAST
+ change3h >= 0.5f -> PressureTrend.RISING
+ change3h <= -2.0f -> PressureTrend.FALLING_FAST
+ change3h <= -0.5f -> PressureTrend.FALLING
+ else -> PressureTrend.STEADY
+ }
+ }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/BarometerTrendView.kt b/android-app/app/src/main/kotlin/org/terst/nav/BarometerTrendView.kt
new file mode 100644
index 0000000..944d198
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/BarometerTrendView.kt
@@ -0,0 +1,72 @@
+package org.terst.nav
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Path
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.content.ContextCompat
+
+class BarometerTrendView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ private var history: List<BarometerReading> = emptyList()
+
+ private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = ContextCompat.getColor(context, R.color.instrument_text_normal)
+ strokeWidth = 4f
+ style = Paint.Style.STROKE
+ strokeCap = Paint.Cap.ROUND
+ strokeJoin = Paint.Join.ROUND
+ }
+
+ private val gridPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = ContextCompat.getColor(context, R.color.instrument_text_secondary)
+ strokeWidth = 1f
+ style = Paint.Style.STROKE
+ }
+
+ fun setHistory(newHistory: List<BarometerReading>) {
+ history = newHistory
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (history.size < 2) return
+
+ val padding = 20f
+ val w = width.toFloat() - 2 * padding
+ val h = height.toFloat() - 2 * padding
+
+ val minP = history.minOf { it.pressureHpa }
+ val maxP = history.maxOf { it.pressureHpa }
+ val rangeP = (maxP - minP).coerceAtLeast(1.0f) // Show at least 1 hPa range
+
+ val minT = history.first().timestamp
+ val maxT = history.last().timestamp
+ val rangeT = (maxT - minT).coerceAtLeast(1L)
+
+ // Draw simple grid
+ canvas.drawLine(padding, padding, padding, h + padding, gridPaint)
+ canvas.drawLine(padding, h + padding, w + padding, h + padding, gridPaint)
+
+ val path = Path()
+ history.forEachIndexed { index, reading ->
+ val x = padding + (reading.timestamp - minT).toFloat() / rangeT * w
+ val y = padding + h - (reading.pressureHpa - minP) / rangeP * h
+
+ if (index == 0) {
+ path.moveTo(x, y)
+ } else {
+ path.lineTo(x, y)
+ }
+ }
+
+ canvas.drawPath(path, linePaint)
+ }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt b/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
index 22290a5..24eb498 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
@@ -47,6 +47,7 @@ class LocationService : Service() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private lateinit var anchorAlarmManager: AnchorAlarmManager
+ private lateinit var barometerSensorManager: BarometerSensorManager
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val NOTIFICATION_CHANNEL_ID = "location_service_channel"
@@ -59,8 +60,16 @@ class LocationService : Service() {
Log.d("LocationService", "Service created")
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
anchorAlarmManager = AnchorAlarmManager(this) // Initialize with service context
+ barometerSensorManager = BarometerSensorManager(this)
createNotificationChannel()
+ // Observe barometer status and update our public state
+ serviceScope.launch {
+ barometerSensorManager.barometerStatus.collect { status ->
+ _barometerStatus.value = status
+ }
+ }
+
// Mock tidal current data generator
serviceScope.launch {
while (true) {
@@ -121,10 +130,12 @@ class LocationService : Service() {
Log.d("LocationService", "Starting foreground service")
startForeground(NOTIFICATION_ID, createNotification())
startLocationUpdatesInternal()
+ barometerSensorManager.start()
}
ACTION_STOP_FOREGROUND_SERVICE -> {
Log.d("LocationService", "Stopping foreground service")
stopLocationUpdatesInternal()
+ barometerSensorManager.stop()
stopSelf()
}
ACTION_START_ANCHOR_WATCH -> {
@@ -158,6 +169,7 @@ class LocationService : Service() {
Log.d("LocationService", "Service destroyed")
stopLocationUpdatesInternal()
anchorAlarmManager.stopAlarm()
+ barometerSensorManager.stop()
_anchorWatchState.value = AnchorWatchState(isActive = false)
isAlarmTriggered = false // Reset alarm trigger state
serviceScope.cancel() // Cancel the coroutine scope
@@ -284,9 +296,12 @@ class LocationService : Service() {
get() = _anchorWatchState
val tidalCurrentState: StateFlow<TidalCurrentState>
get() = _tidalCurrentState
+ val barometerStatus: StateFlow<BarometerStatus>
+ get() = _barometerStatus
private val _locationFlow = MutableSharedFlow<GpsData>(replay = 1)
private val _anchorWatchState = MutableStateFlow(AnchorWatchState())
private val _tidalCurrentState = MutableStateFlow(TidalCurrentState())
+ private val _barometerStatus = MutableStateFlow(BarometerStatus())
}
}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
index b638136..e208892 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
@@ -104,6 +104,9 @@ class MainActivity : AppCompatActivity() {
private lateinit var valueVmg: TextView
private lateinit var valueDepth: TextView
private lateinit var valuePolarPct: TextView
+ private lateinit var valueBaro: TextView
+ private lateinit var labelTrend: TextView
+ private lateinit var barometerTrendView: BarometerTrendView
private lateinit var polarDiagramView: PolarDiagramView // Reference to the custom view
// Anchor Watch UI elements
@@ -134,6 +137,7 @@ class MainActivity : AppCompatActivity() {
startLocationService()
observeLocationUpdates() // Start observing location updates
observeAnchorWatchState() // Start observing anchor watch state
+ observeBarometerStatus() // Start observing barometer status
} else {
// Permissions denied, handle the case (e.g., show a message to the user)
Toast.makeText(this, "Location permissions denied", Toast.LENGTH_LONG).show()
@@ -167,6 +171,7 @@ class MainActivity : AppCompatActivity() {
startLocationService()
observeLocationUpdates() // Start observing location updates
observeAnchorWatchState() // Start observing anchor watch state
+ observeBarometerStatus() // Start observing barometer status
}
mapView = findViewById<MapView>(R.id.mapView)
@@ -201,6 +206,9 @@ class MainActivity : AppCompatActivity() {
valueVmg = findViewById(R.id.value_vmg)
valueDepth = findViewById(R.id.value_depth)
valuePolarPct = findViewById(R.id.value_polar_pct)
+ valueBaro = findViewById(R.id.value_baro)
+ labelTrend = findViewById(R.id.label_trend)
+ barometerTrendView = findViewById(R.id.barometer_trend_view)
// Initialize PolarDiagramView
polarDiagramView = findViewById(R.id.polar_diagram_view)
@@ -226,7 +234,8 @@ class MainActivity : AppCompatActivity() {
sog = "%.1f".format(Locale.getDefault(), simulatedBsp * 0.95), // SOG usually slightly less than BSP
vmg = "%.1f".format(Locale.getDefault(), mockPolarTable.curves.firstOrNull { it.twS == simulatedTws }?.calculateVmg(simulatedTwa, simulatedBsp) ?: 0.0),
depth = getString(R.string.placeholder_depth_value),
- polarPct = "%.0f%%".format(Locale.getDefault(), mockPolarTable.calculatePolarPercentage(simulatedTws, simulatedTwa, simulatedBsp))
+ polarPct = "%.0f%%".format(Locale.getDefault(), mockPolarTable.calculatePolarPercentage(simulatedTws, simulatedTwa, simulatedBsp)),
+ baro = getString(R.string.placeholder_baro_value)
)
polarDiagramView.setCurrentPerformance(simulatedTws, simulatedTwa, simulatedBsp)
@@ -259,7 +268,8 @@ class MainActivity : AppCompatActivity() {
sog = getString(R.string.placeholder_sog_value),
vmg = getString(R.string.placeholder_vmg_value),
depth = getString(R.string.placeholder_depth_value),
- polarPct = getString(R.string.placeholder_polar_value)
+ polarPct = getString(R.string.placeholder_polar_value),
+ baro = getString(R.string.placeholder_baro_value)
)
fabToggleInstruments.setOnClickListener {
@@ -535,6 +545,18 @@ class MainActivity : AppCompatActivity() {
}
}
+ private fun observeBarometerStatus() {
+ lifecycleScope.launch {
+ LocationService.barometerStatus.collect { status ->
+ withContext(Dispatchers.Main) {
+ valueBaro.text = String.format(Locale.getDefault(), "%.1f", status.currentPressureHpa)
+ labelTrend.text = String.format(Locale.getDefault(), "TREND: %s", status.formatTrend())
+ barometerTrendView.setHistory(status.history)
+ }
+ }
+ }
+ }
+
private fun observeTidalCurrentState() {
lifecycleScope.launch {
LocationService.tidalCurrentState.collect { state ->
@@ -708,7 +730,8 @@ class MainActivity : AppCompatActivity() {
sog: String,
vmg: String,
depth: String,
- polarPct: String
+ polarPct: String,
+ baro: String
) {
valueAws.text = aws
valueTws.text = tws
@@ -719,6 +742,7 @@ class MainActivity : AppCompatActivity() {
valueVmg.text = vmg
valueDepth.text = depth
valuePolarPct.text = polarPct
+ valueBaro.text = baro
}
override fun onStart() {