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
253 lines
6.9 KiB
Python
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"
|
|
)
|