"""Migrated from SWE6 COM Management Validation Test Plan. Source: ``25IMR003_ForSeven-SWVTD_01-COM Management (Validation Test Plan).xlsm`` Translation strategy -------------------- Both qualification tests in this workbook reference frames and signals that are **not present in the current production LDF** (``vendor/4SEVEN_color_lib_test.ldf``): - ``ALM_NodeSelection`` (per-NAD selection bytes) - ``ALM_Req_B`` (a second request frame for LED-on/-off commit) - ``ALM_LED_Idx`` (per-LED bitmask within ``ALM_Req_A``) The current LDF uses a different addressing scheme: each ALM_Node has a single LED, addressed by the ``AmbLightLIDFrom``/``AmbLightLIDTo`` range inside ``ALM_Req_A``. Until the LDF is updated to expose the workbook's signals (or the workbook is updated to match the deployed LDF), these tests cannot be executed end-to-end. Each test below performs a **real LDF probe** for the missing signals. If the LDF later starts exposing them, the probe stops skipping and the remaining steps execute against the real bus. The skip reason names the exact missing signal so a reviewer can see what's blocking. Marker: ``COM_VTD`` — see ``pytest.ini``. Run only this module: pytest -m "COM_VTD" tests/hardware/swe6/test_com_management.py """ from __future__ import annotations import sys import time from pathlib import Path import pytest _HW_DIR = Path(__file__).resolve().parent.parent if str(_HW_DIR) not in sys.path: sys.path.insert(0, str(_HW_DIR)) from ecu_framework.config import EcuTestConfig from ecu_framework.lin.base import LinInterface from frame_io import FrameIO from alm_helpers import ( AlmTester, LED_STATE_ON, STATE_POLL_INTERVAL, STATE_TIMEOUT_DEFAULT, ) pytestmark = [pytest.mark.COM_VTD] # --- fixtures -------------------------------------------------------------- @pytest.fixture(scope="module") def fio(config: EcuTestConfig, lin: LinInterface, ldf) -> FrameIO: if config.interface.type != "mum": pytest.skip("interface.type must be 'mum' for this suite") return FrameIO(lin, ldf) @pytest.fixture(scope="module") def alm(fio: FrameIO) -> AlmTester: decoded = fio.receive("ALM_Status", timeout=1.0) if decoded is None: pytest.skip("ECU not responding on ALM_Status — check wiring/power") nad = int(decoded["ALMNadNo"]) if not (0x01 <= nad <= 0xFE): pytest.skip(f"ECU reports invalid NAD {nad:#x} — auto-addressing first") return AlmTester(fio, nad) @pytest.fixture(autouse=True) def _reset_to_off(alm: AlmTester): alm.force_off() yield alm.force_off() def _require_signals_in_frame(fio: FrameIO, frame_name: str, signal_names: list[str]) -> None: """Skip if the LDF doesn't define ``frame_name`` with all ``signal_names``. Lets the test become live automatically when the LDF is updated. """ try: f = fio.frame(frame_name) except Exception as e: pytest.skip(f"LDF does not define frame {frame_name!r}: {e!r}") return declared = {s.name for s in getattr(f, "signals", []) or []} missing = [s for s in signal_names if s not in declared] if missing: pytest.skip( f"LDF frame {frame_name!r} is missing signal(s) {missing!r}; " f"this validation test cannot run against the current LDF." ) # --- tests ----------------------------------------------------------------- def test_com_vtd_0001(fio: FrameIO, alm: AlmTester, rp): """ Title: SW responds only when its NAD bit is set in ALM_NodeSelection Description: Verify the SW responds only when the current NAD's bit is set in the ``ALM_NodeSelection`` bytes; the LED-on/-off command is then committed by a follow-up ``ALM_Req_B`` frame. Steps from workbook: 1. Send ALM_Req_A with current NAD bit = 1 + LED_0 ON parameters 2. Send ALM_Req_B → expect LED_0 ON 3. Send ALM_Req_A with current NAD bit = 0 + LED_0 OFF parameters 4. Send ALM_Req_B → expect LED_0 still ON (command was not addressed) Status: ``ALM_NodeSelection`` and ``ALM_Req_B`` are NOT in the current production LDF (``vendor/4SEVEN_color_lib_test.ldf``); addressing is currently done via ``AmbLightLIDFrom``/ ``AmbLightLIDTo`` inside ``ALM_Req_A``. The probe below names the exact missing artifact and the test becomes live automatically when the LDF is updated. Requirements: SWRS_LIN_0008 Test ID: COM_VTD_0001 """ # Step: Probe LDF for the validation-spec frames/signals. # If/when the LDF is updated to expose these, the skip below # disappears and the remaining steps will execute. _require_signals_in_frame(fio, "ALM_Req_A", ["ALM_NodeSelection"]) _require_signals_in_frame(fio, "ALM_Req_B", []) # frame existence # The steps below are ready to run as soon as the LDF exposes the # missing signals. They mirror the workbook procedure. # Step 1: ALM_Req_A with this NAD selected + LED_0 ON parameters fio.send( "ALM_Req_A", ALM_NodeSelection=(1 << (alm.nad - 1)), # bitmask for this NAD ALM_LED_Idx=0x01, # LED_0 AmbLightColourRed=255, AmbLightColourGreen=255, AmbLightColourBlue=255, AmbLightIntensity=255, AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0, ) # Step 2: ALM_Req_B commits the command — expect LED_0 ON fio.send("ALM_Req_B") reached, _, history = alm.wait_for_state(LED_STATE_ON, timeout=STATE_TIMEOUT_DEFAULT) rp("on_history", history) assert reached, f"LED_0 did not turn ON after ALM_Req_B commit: {history}" # Step 3: ALM_Req_A with this NAD NOT selected + LED_0 OFF parameters fio.send( "ALM_Req_A", ALM_NodeSelection=0, ALM_LED_Idx=0x01, AmbLightColourRed=0, AmbLightColourGreen=0, AmbLightColourBlue=0, AmbLightIntensity=0, AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0, ) # Step 4: ALM_Req_B — LED_0 must remain ON (un-addressed command) fio.send("ALM_Req_B") deadline = time.monotonic() + 1.0 history = [] while time.monotonic() < deadline: st = alm.read_led_state() if not history or history[-1] != st: history.append(st) time.sleep(STATE_POLL_INTERVAL) rp("post_history", history) assert LED_STATE_ON in history, ( f"LED_0 turned off — un-addressed command was wrongly applied: {history}" ) @pytest.mark.parametrize( "led_idx,description", [ pytest.param(0x00, "all OFF", id="led_idx_0x00"), pytest.param(0xAA, "LEDs 1,3,5,7 ON; 0,2,4,6 OFF", id="led_idx_0xAA"), pytest.param(0x55, "LEDs 0,2,4,6 ON; 1,3,5,7 OFF", id="led_idx_0x55"), pytest.param(0xFF, "all ON", id="led_idx_0xFF"), ], ) def test_com_vtd_0002(fio: FrameIO, alm: AlmTester, rp, led_idx, description): """ Title: SW interprets ALM_LED_Idx as a per-LED bitmask Description: Verify ``ALM_LED_Idx`` is interpreted as a bitmask, one bit per LED: - 0x00 → all LEDs OFF - 0xAA → LEDs 1,3,5,7 ON, LEDs 0,2,4,6 OFF - 0x55 → LEDs 0,2,4,6 ON, LEDs 1,3,5,7 OFF - 0xFF → all LEDs ON Status: ``ALM_LED_Idx`` is NOT in the current production LDF; the deployed ECU exposes a single LED via ``AmbLightLIDFrom/To``. Per-LED verification additionally requires either an extended ALM_Status frame or external optical instrumentation — individual ON/OFF states of 8 LEDs are not LIN-observable from the current ALM_Status payload. The probe below skips the test naming the exact missing signal so the test becomes live automatically when the LDF is updated. Requirements: SWRS_LIN_0009, SWRS_LIN_0010, SWRS_LIN_0011 Test ID: COM_VTD_0002 """ # Step: Probe LDF for ALM_LED_Idx and ALM_NodeSelection _require_signals_in_frame(fio, "ALM_Req_A", ["ALM_LED_Idx", "ALM_NodeSelection"]) # Step: Send ALM_Req_A with this NAD selected, ALM_LED_Idx= fio.send( "ALM_Req_A", ALM_NodeSelection=(1 << (alm.nad - 1)), ALM_LED_Idx=led_idx, AmbLightColourRed=255, AmbLightColourGreen=255, AmbLightColourBlue=255, AmbLightIntensity=255, AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0, ) # Step: Verify per-LED ON/OFF pattern matches mask rp("led_idx_mask", f"0x{led_idx:02X}") rp("expected_pattern", description) # When the LDF is extended with a per-LED status frame, replace # this skip with an actual signal read + bit-by-bit assertion. pytest.skip( "Per-LED state is not exposed in the current ALM_Status frame; " "individual LED verification requires either an extended status " "frame or external optical instrumentation." )