238 lines
8.8 KiB
Python
238 lines
8.8 KiB
Python
"""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=<bitmask>
|
|
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."
|
|
)
|