Files
AR-Autopilot/arautopilot/tests/test_nmea_data.py
T
alro65 0f00ad10da sprint-5: True Course + Track Keeping + XTE + PGN 129026/129284
- Python: NmeaNavData (COG/SOG/XTE data models with staleness tracking)
- Python: TrueCoursePilot with TRUE_COURSE and TRACK_KEEPING modes
- Python: 26 new tests (test_nmea_data, test_true_course)
- Modbus: COG/SOG/XTE input registers + TC setpoint/XTE-gain holdings
- Firmware: nmea2000_consumer handles PGN 129026 + 129284
- Firmware: pid_outer_task wired for TC + TK modes with live SOG scheduling
- YAML regenerated; 284 tests pass, firmware compiles clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:12:57 -04:00

101 lines
2.9 KiB
Python

"""Tests for arautopilot.core.nmea_data -- Sprint 5."""
from __future__ import annotations
import math
import time
from unittest.mock import patch
import pytest
from arautopilot.core.nmea_data import CogSogData, HeadingData, NmeaNavData, XteData
class TestCogSogData:
def test_initial_state_invalid(self):
d = CogSogData()
assert not d.is_valid
assert math.isnan(d.cog_deg)
assert math.isnan(d.sog_kn)
def test_update_marks_valid(self):
d = CogSogData()
d.update(270.0, 8.5)
assert d.is_valid
assert d.cog_deg == pytest.approx(270.0)
assert d.sog_kn == pytest.approx(8.5)
def test_cog_wraps_360(self):
d = CogSogData()
d.update(370.0, 5.0)
assert d.cog_deg == pytest.approx(10.0)
def test_cog_zero_stays_zero(self):
d = CogSogData()
d.update(0.0, 5.0)
assert d.cog_deg == pytest.approx(0.0)
def test_stale_after_max_age(self):
d = CogSogData(max_age_s=1.0)
d.update(90.0, 6.0)
assert d.is_valid
with patch("arautopilot.core.nmea_data.time") as mock_time:
mock_time.monotonic.return_value = d.timestamp + 2.0
assert not d.is_valid
def test_age_ms(self):
d = CogSogData()
d.update(180.0, 10.0)
assert d.age_ms >= 0
class TestXteData:
def test_initial_invalid(self):
d = XteData()
assert not d.is_valid
assert math.isnan(d.xte_m)
def test_update_positive_xte(self):
"""Positive XTE = vessel to starboard of track."""
d = XteData()
d.update(xte_m=5.0, dtw_m=200.0, waypoint_name="WP01")
assert d.is_valid
assert d.xte_m == pytest.approx(5.0)
assert d.dtw_m == pytest.approx(200.0)
assert d.waypoint_name == "WP01"
def test_update_negative_xte(self):
d = XteData()
d.update(xte_m=-3.5)
assert d.xte_m == pytest.approx(-3.5)
def test_stale_after_max_age(self):
d = XteData(max_age_s=2.0)
d.update(1.0)
with patch("arautopilot.core.nmea_data.time") as mock_time:
mock_time.monotonic.return_value = d.timestamp + 3.0
assert not d.is_valid
def test_update_without_optional_fields(self):
d = XteData()
d.update(xte_m=0.0)
assert d.is_valid
assert math.isnan(d.dtw_m)
assert d.waypoint_name == ""
class TestNmeaNavData:
def test_aggregate_has_all_fields(self):
nav = NmeaNavData()
assert isinstance(nav.heading, HeadingData)
assert isinstance(nav.cog_sog, CogSogData)
assert isinstance(nav.xte, XteData)
def test_independent_updates(self):
nav = NmeaNavData()
nav.cog_sog.update(45.0, 7.0)
nav.xte.update(2.5)
assert nav.cog_sog.is_valid
assert nav.xte.is_valid
assert not nav.heading.is_valid