Files
venus/axiom-nmea/raymarine_nmea/nmea/sentences/gps.py
dev 9756538f16 Initial commit: Venus OS boat addons monorepo
Organizes 11 projects for Cerbo GX/Venus OS into a single repository:
- axiom-nmea: Raymarine LightHouse protocol decoder
- dbus-generator-ramp: Generator current ramp controller
- dbus-lightning: Blitzortung lightning monitor
- dbus-meteoblue-forecast: Meteoblue weather forecast
- dbus-no-foreign-land: noforeignland.com tracking
- dbus-tides: Tide prediction from depth + harmonics
- dbus-vrm-history: VRM cloud history proxy
- dbus-windy-station: Windy.com weather upload
- mfd-custom-app: MFD app deployment package
- venus-html5-app: Custom Victron HTML5 app fork
- watermaker: Watermaker PLC control UI

Adds root README, .gitignore, project template, and per-project
.gitignore files. Sensitive config files excluded via .gitignore
with .example templates provided.

Made-with: Cursor
2026-03-16 17:04:16 +00:00

267 lines
8.0 KiB
Python

"""
GPS-related NMEA sentences.
GGA - GPS Fix Data
GLL - Geographic Position
RMC - Recommended Minimum Navigation Information
"""
from datetime import datetime
from typing import Optional
from ..sentence import NMEASentence
class GGASentence(NMEASentence):
"""GGA - GPS Fix Data.
Format:
$GPGGA,HHMMSS.SS,DDMM.MMMMM,N,DDDMM.MMMMM,W,Q,SS,H.H,A.A,M,G.G,M,A.A,XXXX*CC
Fields:
1. Time (UTC) - HHMMSS.SS
2. Latitude - DDMM.MMMMM
3. N/S indicator
4. Longitude - DDDMM.MMMMM
5. E/W indicator
6. GPS Quality (0=invalid, 1=GPS, 2=DGPS, 4=RTK fixed, 5=RTK float)
7. Number of satellites
8. HDOP (Horizontal Dilution of Precision)
9. Altitude above mean sea level
10. Altitude units (M)
11. Geoidal separation
12. Geoidal separation units (M)
13. Age of differential GPS data
14. Differential reference station ID
Example:
$GPGGA,123519.00,4807.038000,N,01131.000000,E,1,08,0.9,545.4,M,47.0,M,,*47
"""
talker_id = "GP"
sentence_type = "GGA"
def __init__(
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
time: Optional[datetime] = None,
quality: int = 1,
num_satellites: int = 8,
hdop: float = 1.0,
altitude: Optional[float] = None,
geoid_sep: Optional[float] = None,
):
"""Initialize GGA sentence.
Args:
latitude: Latitude in decimal degrees
longitude: Longitude in decimal degrees
time: UTC time, or None for current time
quality: GPS quality indicator (1=GPS, 2=DGPS)
num_satellites: Number of satellites in use
hdop: Horizontal dilution of precision
altitude: Altitude above MSL in meters
geoid_sep: Geoidal separation in meters
"""
self.latitude = latitude
self.longitude = longitude
self.time = time
self.quality = quality
self.num_satellites = num_satellites
self.hdop = hdop
self.altitude = altitude
self.geoid_sep = geoid_sep
def format_fields(self) -> Optional[str]:
"""Format GGA fields."""
if self.latitude is None or self.longitude is None:
return None
time_str = self.format_time(self.time)
lat_str = self.format_latitude(self.latitude)
lon_str = self.format_longitude(self.longitude)
# Format altitude - not yet available from Raymarine
if self.altitude is not None:
alt_str = f"{self.altitude:.1f},M"
else:
alt_str = "0.0,M" # Default to 0.0 for parsers that require a value
# Format geoidal separation - not available from Raymarine
if self.geoid_sep is not None:
geoid_str = f"{self.geoid_sep:.1f},M"
else:
geoid_str = "-22.0,M" # Default typical value for compatibility
return (
f"{time_str},"
f"{lat_str},"
f"{lon_str},"
f"{self.quality},"
f"{self.num_satellites:02d},"
f"{self.hdop:.1f},"
f"{alt_str},"
f"{geoid_str},"
f"," # Age of DGPS
) # DGPS station ID (empty)
class GLLSentence(NMEASentence):
"""GLL - Geographic Position (Latitude/Longitude).
Format:
$GPGLL,DDMM.MMMMM,N,DDDMM.MMMMM,W,HHMMSS.SS,A,A*CC
Fields:
1. Latitude - DDMM.MMMMM
2. N/S indicator
3. Longitude - DDDMM.MMMMM
4. E/W indicator
5. Time (UTC) - HHMMSS.SS
6. Status (A=valid, V=invalid)
7. Mode indicator (A=autonomous, D=differential, N=invalid)
Example:
$GPGLL,4916.45000,N,12311.12000,W,225444.00,A,A*6A
"""
talker_id = "GP"
sentence_type = "GLL"
def __init__(
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
time: Optional[datetime] = None,
status: str = "A",
mode: str = "A",
):
"""Initialize GLL sentence.
Args:
latitude: Latitude in decimal degrees
longitude: Longitude in decimal degrees
time: UTC time, or None for current time
status: Status (A=valid, V=invalid)
mode: Mode indicator (A=autonomous, D=differential)
"""
self.latitude = latitude
self.longitude = longitude
self.time = time
self.status = status
self.mode = mode
def format_fields(self) -> Optional[str]:
"""Format GLL fields."""
if self.latitude is None or self.longitude is None:
return None
lat_str = self.format_latitude(self.latitude)
lon_str = self.format_longitude(self.longitude)
time_str = self.format_time(self.time)
return f"{lat_str},{lon_str},{time_str},{self.status},{self.mode}"
class RMCSentence(NMEASentence):
"""RMC - Recommended Minimum Navigation Information.
Format:
$GPRMC,HHMMSS.SS,A,DDMM.MMMMM,N,DDDMM.MMMMM,W,S.S,C.C,DDMMYY,M.M,E,A*CC
Fields:
1. Time (UTC) - HHMMSS.SS
2. Status (A=valid, V=warning)
3. Latitude - DDMM.MMMMM
4. N/S indicator
5. Longitude - DDDMM.MMMMM
6. E/W indicator
7. Speed over ground (knots)
8. Course over ground (degrees true)
9. Date - DDMMYY
10. Magnetic variation (degrees)
11. Magnetic variation direction (E/W)
12. Mode indicator (A=autonomous, D=differential)
Example:
$GPRMC,225446.00,A,4916.45000,N,12311.12000,W,000.5,054.7,191194,020.3,E,A*68
"""
talker_id = "GP"
sentence_type = "RMC"
def __init__(
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
time: Optional[datetime] = None,
status: str = "A",
sog: Optional[float] = None,
cog: Optional[float] = None,
mag_var: Optional[float] = None,
mode: str = "A",
):
"""Initialize RMC sentence.
Args:
latitude: Latitude in decimal degrees
longitude: Longitude in decimal degrees
time: UTC time, or None for current time
status: Status (A=valid, V=warning)
sog: Speed over ground in knots
cog: Course over ground in degrees true
mag_var: Magnetic variation in degrees (positive=E, negative=W)
mode: Mode indicator (A=autonomous, D=differential)
"""
self.latitude = latitude
self.longitude = longitude
self.time = time
self.status = status
self.sog = sog
self.cog = cog
self.mag_var = mag_var
self.mode = mode
def format_fields(self) -> Optional[str]:
"""Format RMC fields."""
if self.latitude is None or self.longitude is None:
return None
dt = self.time if self.time else datetime.utcnow()
time_str = self.format_time(dt)
date_str = self.format_date(dt)
lat_str = self.format_latitude(self.latitude)
lon_str = self.format_longitude(self.longitude)
# Format SOG - sourced from Field 5.3
if self.sog is not None:
sog_str = f"{self.sog:.1f}"
else:
sog_str = "0.0" # Default to 0.0 for parsers that require a value
# Format COG - sourced from Field 5.1
if self.cog is not None:
cog_str = f"{self.cog:.1f}"
else:
cog_str = "0.0" # Default to 0.0 for parsers that require a value
# Format magnetic variation
if self.mag_var is not None:
mag_dir = 'E' if self.mag_var >= 0 else 'W'
mag_str = f"{abs(self.mag_var):.1f},{mag_dir}"
else:
mag_str = ","
return (
f"{time_str},"
f"{self.status},"
f"{lat_str},"
f"{lon_str},"
f"{sog_str},"
f"{cog_str},"
f"{date_str},"
f"{mag_str},"
f"{self.mode}"
)