package com.example.androidapp.nmea import com.example.androidapp.gps.GpsPosition import java.util.Calendar import java.util.TimeZone class NmeaParser { /** * Parses an NMEA RMC sentence and returns a [GpsPosition], or null if the * sentence is void (status=V), malformed, or cannot be parsed. * * Supported talker IDs: GP, GN, and any other standard prefix. * SOG and COG default to 0.0 when the fields are absent. */ fun parseRmc(sentence: String): GpsPosition? { if (sentence.isBlank()) return null val body = if ('*' in sentence) sentence.substringBefore('*') else sentence val fields = body.split(',') if (fields.size < 10) return null if (!fields[0].endsWith("RMC")) return null if (fields[2] != "A") return null val latStr = fields.getOrNull(3) ?: return null val latDir = fields.getOrNull(4) ?: return null val lonStr = fields.getOrNull(5) ?: return null val lonDir = fields.getOrNull(6) ?: return null val latitude = parseNmeaDegrees(latStr) * if (latDir == "S") -1.0 else 1.0 val longitude = parseNmeaDegrees(lonStr) * if (lonDir == "W") -1.0 else 1.0 val sog = fields.getOrNull(7)?.toDoubleOrNull() ?: 0.0 val cog = fields.getOrNull(8)?.toDoubleOrNull() ?: 0.0 val timestampMs = parseTimestamp( timeStr = fields.getOrNull(1) ?: "", dateStr = fields.getOrNull(9) ?: "" ) if (timestampMs == 0L) return null return GpsPosition(latitude, longitude, sog, cog, timestampMs) } /** * Converts NMEA degree-minutes format (DDDMM.MMMM) to decimal degrees. */ private fun parseNmeaDegrees(value: String): Double { val raw = value.toDoubleOrNull() ?: return 0.0 val degrees = (raw / 100.0).toInt() val minutes = raw - degrees * 100.0 return degrees + minutes / 60.0 } /** * Combines NMEA time (HHMMSS.ss) and date (DDMMYY) into Unix epoch millis UTC. * Returns 0 on any parse failure. */ private fun parseTimestamp(timeStr: String, dateStr: String): Long { return try { val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) cal.isLenient = false if (dateStr.length >= 6) { val day = dateStr.substring(0, 2).toInt() val month = dateStr.substring(2, 4).toInt() - 1 val yy = dateStr.substring(4, 6).toInt() val year = if (yy < 70) 2000 + yy else 1900 + yy cal.set(Calendar.YEAR, year) cal.set(Calendar.MONTH, month) cal.set(Calendar.DAY_OF_MONTH, day) } if (timeStr.length >= 6) { val hours = timeStr.substring(0, 2).toInt() val minutes = timeStr.substring(2, 4).toInt() val seconds = timeStr.substring(4, 6).toInt() val millis = if (timeStr.length > 7) { val fracStr = timeStr.substring(7) (("0.$fracStr").toDoubleOrNull()?.times(1000.0))?.toInt() ?: 0 } else 0 cal.set(Calendar.HOUR_OF_DAY, hours) cal.set(Calendar.MINUTE, minutes) cal.set(Calendar.SECOND, seconds) cal.set(Calendar.MILLISECOND, millis) } cal.timeInMillis } catch (e: Exception) { 0L } } }