summaryrefslogtreecommitdiff
path: root/SESSION_STATE.md
diff options
context:
space:
mode:
Diffstat (limited to 'SESSION_STATE.md')
-rw-r--r--SESSION_STATE.md282
1 files changed, 75 insertions, 207 deletions
diff --git a/SESSION_STATE.md b/SESSION_STATE.md
index d4cd865..a5ccf86 100644
--- a/SESSION_STATE.md
+++ b/SESSION_STATE.md
@@ -1,216 +1,84 @@
-# SESSION STATE
+# SESSION_STATE.md
-**Current Task Goal:** Add wind/current map overlay and weather forecast display that loads on application startup.
+## Current Task Goal
+GPS navigation implementation: position model, SOG/COG, NMEA RMC parser — COMPLETE
-**Status:** [REVIEW_READY] — Implementation complete. All source files created, tests written.
-
----
-
-## Project Context
-
-- **Platform:** Android (Kotlin), API 24+, compileSdk 34
-- **Build system:** Gradle (Groovy DSL)
-- **Location:** `android-app/`
-- **Current state:** Empty project skeleton (no source code, only build files)
-- **Design doc:** `docs/COMPONENT_DESIGN.md`
-
----
+## Verified (2026-03-15)
+- All 22 GPS/NMEA tests GREEN via test-runner (BUILD SUCCESSFUL)
+- NmeaParser extended with MWV (wind), DBT (depth), HDG/HDM (heading) parsers
+- Sensor data classes added: WindData, DepthData, HeadingData
+- NmeaStreamManager added for TCP stream management
## Completed Items
-- [x] Explored project structure
-- [x] Read COMPONENT_DESIGN.md — full architecture documented
-- [x] Identified UI framework: MapLibre GL Native (Android)
-- [x] Identified weather data sources (see below)
-- [x] Written implementation plan (this document)
-- [x] `android-app/app/build.gradle` — added all deps (MapLibre, Retrofit, Moshi, Coroutines, Location, MockK, Turbine, MockWebServer)
-- [x] `AndroidManifest.xml` — INTERNET + LOCATION permissions, MainActivity launcher
-- [x] `proguard-rules.pro` — Moshi/Retrofit keep rules
-- [x] `data/model/WindArrow.kt` — with isCalm(), normalisedDirection(), beaufortScale()
-- [x] `data/model/ForecastItem.kt` — with weatherDescription(), isRainy()
-- [x] `data/model/WeatherResponse.kt`, `MarineResponse.kt` — Moshi-annotated API response classes
-- [x] `data/api/WeatherApiService.kt`, `MarineApiService.kt` — Retrofit interfaces
-- [x] `data/api/ApiClient.kt` — OkHttp + Retrofit singletons for both base URLs
-- [x] `data/repository/WeatherRepository.kt` — parallel fetch, Result<T> error propagation
-- [x] `ui/MainViewModel.kt` — StateFlow UiState (Loading/Success/Error), windArrow, forecast
-- [x] Layouts: activity_main.xml, fragment_map.xml, fragment_forecast.xml, item_forecast.xml
-- [x] Resources: colors.xml, strings.xml, themes.xml, bottom_nav_menu.xml, ic_wind_arrow.xml
-- [x] `ui/MainActivity.kt` — location permission, GPS fetch, fragment back-stack
-- [x] `ui/map/MapFragment.kt` — MapLibre init, wind-arrow SymbolLayer with icon-rotate
-- [x] `ui/forecast/ForecastFragment.kt` + `ForecastAdapter.kt` — RecyclerView with ListAdapter
-- [x] Tests: WindArrowTest, ForecastItemTest, WeatherApiServiceTest, WeatherRepositoryTest, MainViewModelTest
-
----
-
-## Data Sources
-
-| Data | Source | Auth |
-|------|--------|------|
-| Wind forecast (10m, kt) | Open-Meteo API (`api.open-meteo.com`) | None (free) |
-| Weather forecast (temp, precip, code) | Open-Meteo API | None (free) |
-| Marine / ocean currents | Open-Meteo Marine API (`marine-api.open-meteo.com`) | None (free) |
-| Base map tiles | MapLibre GL + OpenStreetMap tiles (free CDN) | None |
-
-Open-Meteo is chosen because it requires no API key, covers global marine/weather data, and returns JSON that's easy to parse. The design doc also lists NOAA and Windy as future sources; those can be swapped in later via the repository abstraction.
-
-### Key API endpoints
-
-**Weather forecast:**
-```
-GET https://api.open-meteo.com/v1/forecast
- ?latitude={lat}&longitude={lon}
- &hourly=windspeed_10m,winddirection_10m,temperature_2m,
- precipitation_probability,weathercode
- &forecast_days=7&wind_speed_unit=kn
-```
-
-**Marine/current:**
-```
-GET https://marine-api.open-meteo.com/v1/marine
- ?latitude={lat}&longitude={lon}
- &hourly=wave_height,wave_direction,ocean_current_velocity,
- ocean_current_direction
- &forecast_days=7
-```
-
----
-
-## Architecture
-
-```
-Presentation
- MainActivity (single-activity host)
- ├── MapFragment — MapLibre map + wind-arrow overlay
- └── ForecastFragment — 7-day forecast list
-
-ViewModel (lifecycle-aware state)
- MainViewModel
- ├── uiState: StateFlow<UiState> (Loading | Success | Error)
- ├── windArrows: StateFlow<List<WindArrow>>
- └── forecast: StateFlow<List<ForecastItem>>
-
-Repository
- WeatherRepository
- ├── fetchWeather(lat, lon): WeatherData
- └── fetchMarine(lat, lon): MarineData
-
-Data / API
- WeatherApiService (Retrofit — Open-Meteo weather endpoint)
- MarineApiService (Retrofit — Open-Meteo marine endpoint)
- ApiClient (OkHttp singleton, base URL config)
-
-Models
- WeatherData, MarineData (API response data classes)
- WindArrow (lat, lon, speed_kt, direction_deg)
- ForecastItem (time, wind_kt, wind_dir, temp_c, precip_pct, icon)
-```
-
-**Startup flow:**
-1. MainActivity.onCreate → MainViewModel.init
-2. ViewModel launches coroutine → FusedLocationProviderClient.lastLocation
-3. Location → WeatherRepository.fetchWeather(lat, lon) and .fetchMarine(lat, lon) in parallel
-4. Results → StateFlow updates → UI observes:
- - MapFragment draws wind arrows at sampled grid points (current hour)
- - ForecastFragment renders RecyclerView with 7-day items
-
----
-
-## Implementation Plan (TDD)
-
-### Step 1 — Project dependencies (`android-app/app/build.gradle`)
-Add:
-- `org.maplibre.gl:android-sdk:10.0.2`
-- `com.squareup.retrofit2:retrofit:2.9.0`
-- `com.squareup.retrofit2:converter-moshi:2.9.0`
-- `com.squareup.moshi:moshi-kotlin:1.15.0`
-- `com.squareup.okhttp3:okhttp:4.12.0`
-- `org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3`
-- `androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0`
-- `androidx.lifecycle:lifecycle-runtime-ktx:2.7.0`
-- `com.google.android.gms:play-services-location:21.2.0`
-- `androidx.fragment:fragment-ktx:1.6.2`
-- `androidx.recyclerview:recyclerview:1.3.2`
-- Test deps: `mockk:1.13.9`, `kotlinx-coroutines-test:1.7.3`, `turbine:1.1.0`
-
-### Step 2 — AndroidManifest.xml
-- INTERNET, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION permissions
-- Declare MainActivity as launcher activity
-
-### Step 3 — Models (`data/model/`)
-**Files:**
-- `WindArrow.kt` — `data class WindArrow(val lat: Double, val lon: Double, val speedKt: Double, val directionDeg: Double)`
-- `ForecastItem.kt` — hourly weather item
-- `WeatherResponse.kt`, `MarineResponse.kt` — Moshi-annotated API response classes
-
-**Tests (red first):**
-- `WindArrowTest.kt` — parse correctly, edge cases (0 kt, 360°)
-
-### Step 4 — API services (`data/api/`)
-**Files:**
-- `WeatherApiService.kt` — Retrofit interface with `@GET("v1/forecast")`
-- `MarineApiService.kt` — Retrofit interface with `@GET("v1/marine")`
-- `ApiClient.kt` — Retrofit singleton factory
-
-**Tests:**
-- `WeatherApiServiceTest.kt` — mock HTTP server (MockWebServer), verify request URL + parse response
-
-### Step 5 — Repository (`data/repository/WeatherRepository.kt`)
-- Coordinates parallel API calls via `async { }` + `awaitAll()`
-- Maps raw API responses → domain models (WindArrow, ForecastItem)
-- Returns `Result<T>` to propagate errors cleanly
-
-**Tests:**
-- `WeatherRepositoryTest.kt` — mock services, verify data mapping and error handling
-
-### Step 6 — ViewModel (`ui/MainViewModel.kt`)
-- Exposes `uiState: StateFlow<UiState>`, `windArrows`, `forecast`
-- On init: request location permission → fetch data
-
-**Tests:**
-- `MainViewModelTest.kt` — mock repository, test Loading/Success/Error states with Turbine
-
-### Step 7 — Layouts
-- `res/layout/activity_main.xml` — BottomNavigationView + FragmentContainerView
-- `res/layout/fragment_map.xml` — MapLibre MapView (fullscreen)
-- `res/layout/fragment_forecast.xml` — RecyclerView for forecast list
-- `res/layout/item_forecast.xml` — single forecast row
-
-### Step 8 — MainActivity + Fragments
-- `MainActivity.kt` — sets up bottom nav, fragment back-stack
-- `MapFragment.kt` — initialises MapLibre, adds wind-arrow symbol layer on map ready
-- `ForecastFragment.kt` — observes ViewModel.forecast, drives RecyclerView adapter
-
-### Step 9 — Wind Overlay on Map
-- Load wind data for current hour from `windArrows`
-- Sample to a sparse grid (~50 points visible on screen)
-- Add MapLibre `SymbolLayer` with rotated arrow icons
-- Arrow rotation = wind direction; size/opacity proportional to speed
-
----
-
-## Next 3 Specific Steps (after approval)
-
-1. **`android-app/app/build.gradle`** — add all dependencies listed in Step 1; enable `buildFeatures { viewBinding true }`
-2. **`android-app/app/src/main/AndroidManifest.xml`** — create with INTERNET + LOCATION permissions
-3. **Write failing tests for `WindArrow` model** — `src/test/kotlin/.../data/model/WindArrowTest.kt`
-
----
-
-## Constraints & Notes
-
-- No API keys needed (Open-Meteo is free/open).
-- MapLibre GL requires a style URL; use MapLibre's free demo style or OSM raster tiles.
-- Wind arrows will use a bundled SVG/PNG asset (arrow icon) for the symbol layer.
-- Location: request at runtime (Android 6+ permission model).
-- Min SDK 24 means no Jetpack Compose (needs API 21+, but project uses XML views).
-- All implementation follows TDD: write failing test → implement → green.
-
----
+### [APPROVED] GpsPosition data class
+- File: `app/src/main/kotlin/org/terst/nav/gps/GpsPosition.kt`
+- Package: `org.terst.nav.gps`
+- Fields: latitude, longitude, sog (knots), cog (degrees true), timestampMs
+
+### [APPROVED] GpsProvider / GpsListener interfaces
+- File: `app/src/main/kotlin/org/terst/nav/gps/GpsProvider.kt`
+- `GpsProvider`: start/stop, position property, addListener/removeListener
+- `GpsListener`: onPositionUpdate(GpsPosition), onFixLost()
+
+### [APPROVED] DeviceGpsProvider
+- File: `app/src/main/kotlin/org/terst/nav/gps/DeviceGpsProvider.kt`
+- Wraps `LocationManager` with `GPS_PROVIDER`
+- Default interval: 1000ms (1 Hz); configurable via constructor
+- SOG: Location.speed (m/s) × 1.94384 → knots
+- COG: Location.bearing (degrees true, no conversion)
+- Fix-lost timer: fires `onFixLost()` after 10s with no update
+- Thread-safe listener list (synchronized)
+- Android dependency: Context, LocationManager, Handler — device only
+
+### [APPROVED] FakeGpsProvider + GpsProviderTest (9 tests — all GREEN)
+- File: `app/src/test/kotlin/org/terst/nav/gps/GpsProviderTest.kt`
+- No Android dependencies — pure JVM
+- Tests:
+ - `start sets started to true`
+ - `stop sets started to false`
+ - `listener receives position update`
+ - `listener notified of fix lost`
+ - `multiple listeners all receive position update`
+ - `multiple listeners all notified of fix lost`
+ - `removing listener stops notifications`
+ - `position property reflects last simulated position`
+ - `SOG conversion sanity check - 1 mps is approximately 1_94384 knots`
+
+## Build Notes
+- `app/build` and `app/.kotlin/sessions` are root-owned from a prior run.
+ Tests were verified via direct `kotlinc` + `JUnitCore` invocation (all 9 pass).
+ Full Gradle build requires fixing directory permissions: `chown -R www-data:www-data app/build app/.kotlin`
+- Pre-existing XML layout error in `activity_main.xml:300` (unrelated to GPS work)
+
+### [APPROVED] NmeaParser — RMC parser
+- File: `app/src/main/kotlin/org/terst/nav/nmea/NmeaParser.kt`
+- Parses any `*RMC` sentence (GP, GN, etc.)
+- Returns `null` for void status (V), malformed input, wrong sentence type
+- SOG/COG default to 0.0 when fields are empty
+- Latitude: positive = North, negative = South
+- Longitude: positive = East, negative = West
+- Timestamp: HHMMSS + DDMMYY → Unix epoch millis UTC (YY < 70 → 2000+YY)
+- No Android dependencies — pure JVM
+
+### [APPROVED] GpsPositionTest + NmeaParserTest (22 tests — all GREEN)
+- `app/src/test/kotlin/org/terst/nav/gps/GpsPositionTest.kt` (2 tests)
+- `app/src/test/kotlin/org/terst/nav/nmea/NmeaParserTest.kt` (11 tests)
+- `app/src/test/kotlin/org/terst/nav/gps/GpsProviderTest.kt` (9 tests, pre-existing)
+- All verified via direct `kotlinc` (1.9.22) + `JUnitCore` invocation
+
+## Next 3 Specific Steps
+1. **UI instrument display** — SOG/COG readout widget in `MainActivity`; bind to `GpsProvider`
+ listener; update TextView/custom view on each `onPositionUpdate`
+2. **NmeaGpsProvider** — `GpsProvider` implementation parsing NMEA RMC sentences over TCP/UDP
+ socket using existing `NmeaParser`; automatic reconnect on disconnect
+3. **Fix build permissions** — `chown -R www-data:www-data /workspace/nav/android-app/app/build`
+ to enable full Gradle unit test runs
## Scripts Added
-
-_(none yet)_
+- `test-runner/` — standalone Kotlin/JVM Gradle project; runs all 22 GPS/NMEA tests without Android SDK
+ - Command: `cd /workspace/nav/test-runner && GRADLE_USER_HOME=/tmp/gradle-home ./gradlew test`
## Process Improvements
-
-_(none yet)_
+- Gradle builds blocked by Android SDK requirement; added `test-runner/` JVM-only subproject as reliable test runner
+- All 22 tests verified GREEN via `test-runner/` JVM project (2026-03-14)