Files
venus/axiom-nmea/raymarine_nmea/nmea/sentences/navigation.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

253 lines
6.9 KiB
Python

"""
Navigation-related NMEA sentences.
HDG - Heading (Deviation & Variation)
HDT - Heading True
VTG - Track Made Good and Ground Speed
VHW - Water Speed and Heading
"""
from typing import Optional
from ..sentence import NMEASentence
class HDGSentence(NMEASentence):
"""HDG - Heading, Deviation & Variation.
Format:
$IIHDG,H.H,D.D,E,V.V,E*CC
Fields:
1. Heading (magnetic) in degrees
2. Magnetic deviation in degrees
3. Deviation direction (E/W)
4. Magnetic variation in degrees
5. Variation direction (E/W)
Example:
$IIHDG,238.5,0.0,E,12.6,W*5F
"""
talker_id = "II"
sentence_type = "HDG"
def __init__(
self,
heading: Optional[float] = None,
deviation: Optional[float] = None,
variation: Optional[float] = None,
):
"""Initialize HDG sentence.
Args:
heading: Magnetic heading in degrees (0-360)
deviation: Magnetic deviation in degrees (positive=E, negative=W)
variation: Magnetic variation in degrees (positive=E, negative=W)
"""
self.heading = heading
self.deviation = deviation
self.variation = variation
def format_fields(self) -> Optional[str]:
"""Format HDG fields."""
if self.heading is None:
return None
heading_str = f"{self.heading:.1f}"
# Format deviation - not yet available from Raymarine
if self.deviation is not None:
dev_dir = 'E' if self.deviation >= 0 else 'W'
dev_str = f"{abs(self.deviation):.1f},{dev_dir}"
else:
dev_str = "0.0,E" # Default to 0.0 for parsers that require a value
# Format variation - configured value, not from Raymarine
if self.variation is not None:
var_dir = 'E' if self.variation >= 0 else 'W'
var_str = f"{abs(self.variation):.1f},{var_dir}"
else:
var_str = "0.0,E" # Default to 0.0 for parsers that require a value
return f"{heading_str},{dev_str},{var_str}"
class HDTSentence(NMEASentence):
"""HDT - Heading True.
Format:
$IIHDT,H.H,T*CC
Fields:
1. Heading (true) in degrees
2. T = True
Example:
$IIHDT,238.5,T*1C
"""
talker_id = "II"
sentence_type = "HDT"
def __init__(self, heading: Optional[float] = None):
"""Initialize HDT sentence.
Args:
heading: True heading in degrees (0-360)
"""
self.heading = heading
def format_fields(self) -> Optional[str]:
"""Format HDT fields."""
if self.heading is None:
return None
return f"{self.heading:.1f},T"
class VTGSentence(NMEASentence):
"""VTG - Track Made Good and Ground Speed.
Format:
$IIVTG,C.C,T,C.C,M,S.S,N,S.S,K,M*CC
Fields:
1. Course over ground (true) in degrees
2. T = True
3. Course over ground (magnetic) in degrees
4. M = Magnetic
5. Speed over ground in knots
6. N = Knots
7. Speed over ground in km/h
8. K = Km/h
9. Mode indicator (A=autonomous, D=differential)
Example:
$IIVTG,054.7,T,034.4,M,005.5,N,010.2,K,A*28
"""
talker_id = "II"
sentence_type = "VTG"
def __init__(
self,
cog_true: Optional[float] = None,
cog_mag: Optional[float] = None,
sog_kts: Optional[float] = None,
mode: str = "A",
):
"""Initialize VTG sentence.
Args:
cog_true: Course over ground (true) in degrees
cog_mag: Course over ground (magnetic) in degrees
sog_kts: Speed over ground in knots
mode: Mode indicator (A=autonomous, D=differential)
"""
self.cog_true = cog_true
self.cog_mag = cog_mag
self.sog_kts = sog_kts
self.mode = mode
def format_fields(self) -> Optional[str]:
"""Format VTG fields."""
# Format COG true - sourced from Field 5.1
if self.cog_true is not None:
cog_t_str = f"{self.cog_true:.1f}"
else:
cog_t_str = "0.0" # Default to 0.0 for parsers that require a value
# Format COG magnetic - derived from true COG
if self.cog_mag is not None:
cog_m_str = f"{self.cog_mag:.1f}"
else:
cog_m_str = "0.0" # Default to 0.0 for parsers that require a value
# Format SOG - sourced from Field 5.3
if self.sog_kts is not None:
sog_kts_str = f"{self.sog_kts:.1f}"
sog_kmh = self.sog_kts * 1.852
sog_kmh_str = f"{sog_kmh:.1f}"
else:
sog_kts_str = "0.0" # Default to 0.0 for parsers that require a value
sog_kmh_str = "0.0"
return (
f"{cog_t_str},T,"
f"{cog_m_str},M,"
f"{sog_kts_str},N,"
f"{sog_kmh_str},K,"
f"{self.mode}"
)
class VHWSentence(NMEASentence):
"""VHW - Water Speed and Heading.
Format:
$IIVHW,H.H,T,H.H,M,S.S,N,S.S,K*CC
Fields:
1. Heading (true) in degrees
2. T = True
3. Heading (magnetic) in degrees
4. M = Magnetic
5. Speed (water) in knots
6. N = Knots
7. Speed (water) in km/h
8. K = Km/h
Example:
$IIVHW,238.5,T,225.9,M,4.5,N,8.3,K*5D
"""
talker_id = "II"
sentence_type = "VHW"
def __init__(
self,
heading_true: Optional[float] = None,
heading_mag: Optional[float] = None,
speed_kts: Optional[float] = None,
):
"""Initialize VHW sentence.
Args:
heading_true: Heading (true) in degrees
heading_mag: Heading (magnetic) in degrees
speed_kts: Speed through water in knots
"""
self.heading_true = heading_true
self.heading_mag = heading_mag
self.speed_kts = speed_kts
def format_fields(self) -> Optional[str]:
"""Format VHW fields."""
# Format headings - true heading sourced from Field 3.2
if self.heading_true is not None:
hdg_t_str = f"{self.heading_true:.1f}"
else:
hdg_t_str = "0.0" # Default to 0.0 for parsers that require a value
# Magnetic heading derived from true heading
if self.heading_mag is not None:
hdg_m_str = f"{self.heading_mag:.1f}"
else:
hdg_m_str = "0.0" # Default to 0.0 for parsers that require a value
# Format speed - water speed not yet available from Raymarine
if self.speed_kts is not None:
spd_kts_str = f"{self.speed_kts:.1f}"
spd_kmh = self.speed_kts * 1.852
spd_kmh_str = f"{spd_kmh:.1f}"
else:
spd_kts_str = "0.0" # Default to 0.0 for parsers that require a value
spd_kmh_str = "0.0"
return (
f"{hdg_t_str},T,"
f"{hdg_m_str},M,"
f"{spd_kts_str},N,"
f"{spd_kmh_str},K"
)