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
267 lines
8.0 KiB
Python
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}"
|
|
)
|