mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 21:41:13 +00:00
[AZ-558] Route C8 outbound encoder bytes through MavlinkTransport seam
All FC adapter outbound MAVLink bytes now go through the AZ-401 MavlinkTransport seam (NoopMavlinkTransport in replay, SerialMavlinkTransport in live). New helpers in _outbound_mavlink_payloads.py extract encode/pack/seq-bump so the four AP _send sites and the iNav statustext _send site become encode -> pack -> transport.write. TlogReplayFcAdapter emits real AP-shape MAVLink bytes through the injected NoopMavlinkTransport, satisfying replay protocol Invariant 5 and unblocking AZ-401 AC-9. Closes AZ-558. Also unskips AZ-401 AC-9 and AZ-404 AC-4b. Live wire output remains byte-identical (proven via two-instance MAVLink byte-equivalence tests). AST scan asserts no .mav.<name>_send( calls remain in the retrofit set (AP / iNav / tlog adapters). Out of scope (logged in review): GCS adapter retrofit; airborne live strategy registration that would activate the SerialMavlinkTransport factory injection path. Tests: 2110 passed, 92 environmental skips, 1 unrelated pre-existing macOS cold-start flake deselected. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -520,23 +520,61 @@ def test_ac8_replay_branch_imports_only_public_apis() -> None:
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# AC-9: NoopMavlinkTransport.bytes_written() > 0 — BLOCKED
|
||||
# AC-9: NoopMavlinkTransport.bytes_written() > 0 (closed by AZ-558)
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason=(
|
||||
"BLOCKED on AZ-399 design choice: TlogReplayFcAdapter raises "
|
||||
"FcEmitError on emit_external_position rather than routing the "
|
||||
"encoder bytes through the MavlinkTransport seam. Closing this "
|
||||
"gap requires retrofitting AP/iNav/QGC encoder code paths to "
|
||||
"consume MavlinkTransport — see batch 61 report. NoopMavlinkTransport "
|
||||
"+ MavlinkTransport Protocol classes are present (covered by "
|
||||
"test_az400_mavlink_transport.py) but the wiring that makes "
|
||||
"bytes_written > 0 in replay mode is deferred."
|
||||
def test_ac9_noop_transport_bytes_written_after_runtime_drive(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""AZ-401 AC-9 / AZ-558 AC-4: replay encoders write through the seam.
|
||||
|
||||
Drives 10 ``EstimatorOutput`` ticks through a replay-wired
|
||||
:class:`TlogReplayFcAdapter` with a :class:`NoopMavlinkTransport`
|
||||
injected as its outbound seam. After the AZ-558 retrofit the
|
||||
adapter encodes ``GPS_INPUT`` + ``NAMED_VALUE_FLOAT`` per tick
|
||||
and writes the packed bytes through the transport — replay
|
||||
protocol Invariant 5 (encoders run in both modes; only the
|
||||
transport differs).
|
||||
"""
|
||||
# Arrange
|
||||
from pymavlink.dialects.v20 import ardupilotmega as _mavlink
|
||||
|
||||
monkeypatch.setenv("BUILD_REPLAY_SINK_JSONL", "ON")
|
||||
transport = NoopMavlinkTransport()
|
||||
outbound_mav = _mavlink.MAVLink(file=None, srcSystem=1, srcComponent=1)
|
||||
fc = TlogReplayFcAdapter.__new__(TlogReplayFcAdapter)
|
||||
# Initialise only the slots the encoder code path consults so the
|
||||
# test stays focused on the wire-routing contract (no tlog file,
|
||||
# no BUILD_TLOG_REPLAY_ADAPTER gate, no decode thread).
|
||||
fc._mavlink_transport = transport
|
||||
fc._outbound_mav = outbound_mav
|
||||
fc._sequence_number = 0
|
||||
fc._clock = WallClock()
|
||||
fc._clock_us_provider = lambda: int(fc._clock.monotonic_ns() // 1000)
|
||||
fc._clock_ms_boot_provider = (
|
||||
lambda: int(fc._clock.monotonic_ns() // 1_000_000) % 0xFFFFFFFF
|
||||
)
|
||||
output = EstimatorOutput(
|
||||
frame_id=uuid4(),
|
||||
position_wgs84=LatLonAlt(lat_deg=50.0, lon_deg=30.0, alt_m=100.0),
|
||||
orientation_world_T_body=Quat(w=1.0, x=0.0, y=0.0, z=0.0),
|
||||
velocity_world_mps=(0.0, 0.0, 0.0),
|
||||
covariance_6x6=np.eye(6, dtype=np.float64) * 0.25,
|
||||
source_label=PoseSourceLabel.VISUAL_PROPAGATED,
|
||||
last_satellite_anchor_age_ms=0,
|
||||
smoothed=False,
|
||||
emitted_at=0,
|
||||
)
|
||||
|
||||
# Act
|
||||
for _ in range(10):
|
||||
fc.emit_external_position(output)
|
||||
|
||||
# Assert
|
||||
assert transport.bytes_written() > 0, (
|
||||
f"NoopMavlinkTransport.bytes_written() = {transport.bytes_written()}; "
|
||||
"expected > 0 after 10 emit_external_position calls"
|
||||
)
|
||||
)
|
||||
def test_ac9_noop_transport_bytes_written_after_runtime_drive() -> None:
|
||||
raise NotImplementedError("see skip reason")
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user