summaryrefslogtreecommitdiff
path: root/.agent/worklog.md
blob: 7a4467fee50589464ba57f4ba66195b0774b5371 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# SESSION_STATE.md

## Current Task Goal
Section 7.3 AIS display — COMPLETE (2026-03-15): AIS integrated into ViewModel, MapFragment, and MainActivity

## Verified (2026-03-15)
- All 41 tests GREEN: 22 GPS/NMEA + 16 AIS + 3 ViewModel AIS (via /tmp/ais-vm-test-runner/)
- 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

### [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

### [APPROVED] AisVessel data class
- File: `app/src/main/kotlin/org/terst/nav/ais/AisVessel.kt`
- Package: `org.terst.nav.ais`
- Fields: mmsi, name, callsign, lat, lon, sog, cog, heading, vesselType, timestampMs
- Note: `com/example/androidapp` tree is root-owned; files placed under `org/terst/nav/` (actual project package)

### [APPROVED] CpaCalculator object
- File: `app/src/main/kotlin/org/terst/nav/ais/CpaCalculator.kt`
- Flat-earth CPA/TCPA algorithm; returns (cpa_nm, tcpa_min)
- Zero-relative-velocity guard: returns (currentDist, 0.0)

### [APPROVED] AisVdmParser class
- File: `app/src/main/kotlin/org/terst/nav/nmea/AisVdmParser.kt`
- Parses !AIVDM/!AIVDO sentences; multi-part reassembly by seqId
- Type 1/2/3: MMSI, SOG, lon, lat, COG, heading decoded
- Type 5: MMSI, callsign (7 chars), name (20 chars), vesselType decoded; trailing '@'/' ' trimmed
- Not-available sentinel handling: SOG=1023→0.0, COG=3600→0.0, lon=0x6791AC0→0.0, lat=0x3412140→0.0

### [APPROVED] AIS Tests (16 tests — all GREEN)
- `test-runner/src/test/kotlin/org/terst/nav/ais/AisVesselTest.kt` (4 tests)
- `test-runner/src/test/kotlin/org/terst/nav/ais/CpaCalculatorTest.kt` (4 tests)
- `test-runner/src/test/kotlin/org/terst/nav/nmea/AisVdmParserTest.kt` (8 tests)
- Verification harness: `/tmp/ais-test-runner/` (JUnit5, com.example.androidapp package)
- Production test files also in `android-app/app/src/test/kotlin/org/terst/nav/ais/`

### [APPROVED] AisRepository class
- File: `app/src/main/kotlin/org/terst/nav/ais/AisRepository.kt`
- Upserts position targets by MMSI; merges type-5 static data (name/callsign/vesselType)
- Pending static map: holds type-5 data until position arrives for same MMSI
- `evictStale(nowMs)`: removes vessels older than `staleTimeoutMs` (default 10 min)
- `AisDataSource` interface (with Flow<String>) defined in same file

### [APPROVED] AisHubApiService + AisHubVessel
- File: `app/src/main/kotlin/org/terst/nav/ais/AisHubApiService.kt`
- Note: placed in `ais/` directory (writable) with package `org.terst.nav.data.api`
- `AisHubVessel` data class with Moshi `@Json` and `@JsonClass(generateAdapter=true)` annotations
- `AisHubApiService` Retrofit interface: GET /0/down with lat/lon bounding box params

### [APPROVED] AisHubSource object
- File: `app/src/main/kotlin/org/terst/nav/ais/AisHubSource.kt`
- Converts `AisHubVessel` REST response to `AisVessel` domain objects
- Returns null for non-numeric MMSI, lat, or lon; defaults sog/cog=0.0, heading=511, vesselType=0

### [APPROVED] AIS Repository Tests (11 tests — all GREEN)
- `app/src/test/kotlin/org/terst/nav/ais/AisRepositoryTest.kt` (7 tests)
- `app/src/test/kotlin/org/terst/nav/ais/AisHubSourceTest.kt` (4 tests)
- Harness: `/tmp/ais-repo-test-runner/` (JUnit5, org.terst.nav package, stub annotations for offline build)

### [APPROVED] Section 7.3 AIS display — ViewModel + MapFragment integration (2026-03-15)
- `MainViewModel.processAisSentence(sentence)` — delegates to AisRepository, updates `_aisTargets` StateFlow
- `MainViewModel.refreshAisFromInternet(latMin, latMax, lonMin, lonMax, username, password)` — AISHub polling via inline Retrofit; skips if username empty
- `MainViewModel.aisTargets: StateFlow<List<AisVessel>>` — exposed to observers
- `AisRepository.ingestVessel(vessel)` — direct insert for internet-sourced vessels
- `MapFragment.updateAisLayer(style, vessels)` — GeoJSON FeatureCollection, SymbolLayer "ais-vessels"; heading-based iconRotate; zoom-step text (visible at zoom ≥ 12)
- `MapFragment.addShipArrowImage(style)` — rasterises ic_ship_arrow.xml into style
- `ic_ship_arrow.xml` — pink arrow vector drawable for AIS icons
- `MainActivity.startAisHardwareFeed(host, port)` — TCP stub reads !AIVDM lines, forwards to viewModel
- `MainViewModelTest` — 3 new tests: valid type-1 adds vessel, same MMSI deduped, non-AIS stays empty
- JVM test harness: `/tmp/ais-vm-test-runner/` (3 tests — all GREEN)

### [APPROVED] TrackRepository (2026-03-25)
- `android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt`
- `test-runner/src/main/kotlin/org/terst/nav/track/TrackRepository.kt`
- `isRecording` flag; `startTrack()` clears + starts; `stopTrack()`; `addPoint()` guards on isRecording; `getPoints()` returns snapshot
- 8 tests — all GREEN (`TrackRepositoryTest`)

### [APPROVED] Track ViewModel + Map overlay + Record FAB (2026-03-25)
- `MainViewModel`: TrackRepository wired in; exposes `isRecording: StateFlow<Boolean>`, `trackPoints: StateFlow<List<TrackPoint>>`; `startTrack()`, `stopTrack()`, `addGpsPoint(lat, lon, sogKnots, cogDeg)`
- `MapHandler.updateTrackLayer(style, points)`: lazy LineLayer init; red (#E53935) 3dp polyline from List<TrackPoint>
- `MainActivity`: stores `loadedStyle`; GPS flow feeds `viewModel.addGpsPoint()` (m/s→knots); observes `trackPoints` → `mapHandler.updateTrackLayer()`; observes `isRecording` → FAB icon toggle (ic_track_record / ic_close)
- `activity_main.xml`: `fab_record_track` FAB anchored top|end of bottom nav
- `drawable/ic_track_record.xml`: red dot record icon

## Next 3 Specific Steps
1. **Persist track to GPX/Room** — export recorded track as GPX file or store in Room DB
2. **Track stats in Log tab** — show elapsed time, distance, avg SOG while recording
3. **AnchorWatchHandler UI** — wire `AnchorWatchHandler` fully into SafetyFragment (currently stub)

## Scripts Added
- `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
- 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)