322 lines
13 KiB
Python
322 lines
13 KiB
Python
"""Migrated from SWE5 COM Management Integration Test Plan.
|
|
|
|
Source: ``25IMR003_ForSeven_RGB-SWITD_03-COM Management (Integration Test results).xlsm``
|
|
|
|
Translation strategy
|
|
--------------------
|
|
The COM tests are about LIN communication: NAD addressing, LDF/baudrate,
|
|
ALM_Req_A signal layout, LID-range targeting, and frame periodicity.
|
|
|
|
- ``Watch color table`` (firmware lookup) is exercised end-to-end:
|
|
drive a known RGB at full intensity and verify ``PWM_Frame`` matches the
|
|
``rgb_to_pwm.compute_pwm`` calculator (which encodes the same color
|
|
table).
|
|
- NAD: read ``ALM_Status.ALMNadNo`` and confirm it falls inside the
|
|
valid NAD range declared by the LDF.
|
|
- Baudrate: physical-layer; not measurable from inside the test runner
|
|
(requires a scope) → the step is recorded for traceability and skipped.
|
|
- ``ALM_Req_A`` byte-mapping: send a frame with distinctive RGB+I values
|
|
and confirm the ECU's response (LED reaches ON, PWM matches) — that
|
|
proves byte-level interpretation end-to-end.
|
|
- LID-range flag: drive a frame inside vs. outside the node's range and
|
|
observe whether the LED reacts.
|
|
- 5 ms periodicity: a master-side LIN-master scheduling property that
|
|
the slave does not echo back; documented as not directly observable.
|
|
|
|
Marker: ``COM`` — see ``pytest.ini``.
|
|
|
|
Run only this module:
|
|
pytest -m "COM" tests/hardware/swe5/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_OFF, LED_STATE_ANIMATING, LED_STATE_ON,
|
|
STATE_POLL_INTERVAL, STATE_TIMEOUT_DEFAULT,
|
|
)
|
|
|
|
|
|
pytestmark = [pytest.mark.COM]
|
|
|
|
|
|
# --- 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()
|
|
|
|
|
|
# --- tests -----------------------------------------------------------------
|
|
|
|
|
|
def test_com_itd_0001(fio: FrameIO, alm: AlmTester, rp):
|
|
"""
|
|
Title: LED color table is configured per spec — PWM_Frame matches the calculator
|
|
|
|
Description:
|
|
Verify the SW configures the LED color table as required by
|
|
[SWRS_LIN_0001]. The firmware's color table feeds
|
|
rgb_to_pwm.compute_pwm(). Drive a known RGB at full intensity, wait
|
|
for LED_ON, then assert PWM_Frame matches
|
|
compute_pwm(R,G,B,temp_c=Tj_NTC).pwm_comp within tolerance.
|
|
Mismatch implies the on-ECU table differs from the spec.
|
|
|
|
Requirements: SWRS_LIN_0001
|
|
Test ID: COM_ITD_0001
|
|
"""
|
|
r, g, b = 0, 180, 80
|
|
|
|
# Step: Drive ALM_Req_A mode=0 RGB at full intensity to this NAD
|
|
fio.send(
|
|
"ALM_Req_A",
|
|
AmbLightColourRed=r, AmbLightColourGreen=g, AmbLightColourBlue=b,
|
|
AmbLightIntensity=255,
|
|
AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0,
|
|
AmbLightLIDFrom=alm.nad, AmbLightLIDTo=alm.nad,
|
|
)
|
|
|
|
# Step: Wait for ALMLEDState == LED_ON
|
|
reached, elapsed, history = alm.wait_for_state(LED_STATE_ON, timeout=STATE_TIMEOUT_DEFAULT)
|
|
rp("led_state_history", history)
|
|
rp("on_elapsed_s", round(elapsed, 3))
|
|
assert reached, f"LED_ON never reached: {history}"
|
|
|
|
# Step: Assert PWM_Frame matches the rgb_to_pwm calculator (color-table proxy)
|
|
alm.assert_pwm_matches_rgb(rp, r, g, b)
|
|
|
|
|
|
def test_com_itd_0002(fio: FrameIO, alm: AlmTester, rp, ldf):
|
|
"""
|
|
Title: LDF implementation — NAD and baudrate match the LDF
|
|
|
|
Description:
|
|
Verify the SW implements the LDF from "4SEVEN_LDF.ldf" — confirm
|
|
the NAD and the bus baudrate.
|
|
NAD: read ALMNadNo via ALM_Status; confirm it is a valid LIN slave
|
|
NAD (0x01..0xFE) — i.e. matches the value the LDF declares for ALM_Node.
|
|
Baudrate: physical-layer property of the LIN bus; verifying it
|
|
requires an oscilloscope on the LIN line, so the step is recorded
|
|
for traceability but cannot be asserted from inside the test runner.
|
|
|
|
Requirements: SWRS_LIN_0001, SWRS_LIN_0002, SWRS_LIN_0003
|
|
Test ID: COM_ITD_0002
|
|
"""
|
|
# Step: Read ALM_Status and confirm ALMNadNo is a valid LIN slave NAD
|
|
nad = fio.read_signal("ALM_Status", "ALMNadNo")
|
|
assert nad is not None, "ALM_Status not received within timeout"
|
|
rp("alm_nad", int(nad))
|
|
assert 0x01 <= int(nad) <= 0xFE, (
|
|
f"ALMNadNo {int(nad):#x} is outside the valid LIN slave range 0x01..0xFE"
|
|
)
|
|
|
|
# Step: Confirm the LDF declares the same NAD for ALM_Node (introspection)
|
|
# The LdfDatabase wrapper exposes the parsed ldf via .ldf in some
|
|
# implementations; fall back to attribute access otherwise.
|
|
ldf_nad = None
|
|
try:
|
|
for node in getattr(ldf.ldf, "slaves", []) or []:
|
|
# ldfparser slaves carry .name and .configured_nad
|
|
if getattr(node, "name", "").lower().startswith("alm"):
|
|
ldf_nad = int(getattr(node, "configured_nad", 0))
|
|
break
|
|
except Exception as e: # pragma: no cover — best effort
|
|
rp("ldf_introspection_error", repr(e))
|
|
rp("ldf_declared_nad", ldf_nad)
|
|
if ldf_nad is not None:
|
|
assert int(nad) == ldf_nad, (
|
|
f"Runtime NAD {int(nad):#x} != LDF-declared NAD {ldf_nad:#x}"
|
|
)
|
|
|
|
# Step: Baudrate verification requires an external scope on the LIN bus.
|
|
# LIN baudrate is a physical-layer parameter; the master configures
|
|
# it from the LDF when opening the interface, but it is not echoed
|
|
# back in any frame the slave publishes. Recording for traceability.
|
|
rp("baudrate_check", "requires oscilloscope — not asserted in software")
|
|
|
|
|
|
def test_com_itd_0003(fio: FrameIO, alm: AlmTester, rp):
|
|
"""
|
|
Title: ALM_Req_A interpretation — distinctive RGBI bytes drive the expected PWM
|
|
|
|
Description:
|
|
Verify the SW correctly interprets the bytes of ALM_Req_A
|
|
(AmbLightLIDFrom/To, AmbLightColourRed/Green/Blue, AmbLightIntensity).
|
|
Per-byte verification at the firmware level (Byte_3 ==
|
|
AmbLightColourRed, etc.) is not LIN-observable. Instead, send a frame
|
|
with distinctive R/G/B values, addressed to this node's NAD only,
|
|
then verify (a) the LED reaches ON (LIDFrom/To were honoured) and
|
|
(b) PWM_wo_Comp matches the calculator for those R/G/B at full
|
|
intensity (Red/Green/Blue/Intensity bytes were interpreted correctly).
|
|
|
|
Requirements: SWRS_LIN_0004, SWRS_LIN_0012, SWRS_LIN_0013, SWRS_LIN_0014, SWRS_LIN_0015
|
|
Test ID: COM_ITD_0003
|
|
"""
|
|
# Distinctive values so a swap of two bytes would be detected.
|
|
r, g, b, intensity = 0xA0, 0x40, 0x10, 0xFF
|
|
|
|
# Step: Disable temperature compensation so PWM_wo_Comp == calculator
|
|
fio.send(
|
|
"ConfigFrame",
|
|
ConfigFrame_Calibration=0, ConfigFrame_EnableDerating=1,
|
|
ConfigFrame_EnableCompensation=0, ConfigFrame_MaxLM=3840,
|
|
)
|
|
time.sleep(0.2)
|
|
|
|
try:
|
|
# Step: Send ALM_Req_A LIDFrom=LIDTo=alm.nad, RGBI distinctive values
|
|
fio.send(
|
|
"ALM_Req_A",
|
|
AmbLightColourRed=r, AmbLightColourGreen=g, AmbLightColourBlue=b,
|
|
AmbLightIntensity=intensity,
|
|
AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0,
|
|
AmbLightLIDFrom=alm.nad, AmbLightLIDTo=alm.nad,
|
|
)
|
|
|
|
# Step: LIDFrom/To honoured — LED reaches ON
|
|
reached, elapsed, history = alm.wait_for_state(LED_STATE_ON, timeout=STATE_TIMEOUT_DEFAULT)
|
|
rp("led_state_history", history)
|
|
rp("on_elapsed_s", round(elapsed, 3))
|
|
assert reached, (
|
|
f"ECU did not respond to a frame addressed to its own NAD: {history}"
|
|
)
|
|
|
|
# Step: RGB+Intensity bytes correctly interpreted — PWM_wo_Comp matches calculator
|
|
alm.assert_pwm_wo_comp_matches_rgb(rp, r, g, b)
|
|
finally:
|
|
# Step: Restore EnableCompensation=1
|
|
fio.send(
|
|
"ConfigFrame",
|
|
ConfigFrame_Calibration=0, ConfigFrame_EnableDerating=1,
|
|
ConfigFrame_EnableCompensation=1, ConfigFrame_MaxLM=3840,
|
|
)
|
|
time.sleep(0.2)
|
|
|
|
|
|
def test_com_itd_0006(fio: FrameIO, alm: AlmTester, rp):
|
|
"""
|
|
Title: LID range targeting — broadcast hits, out-of-range frame is ignored
|
|
|
|
Description:
|
|
Verify the SW respects the AmbLightLIDFrom/To range — frames whose
|
|
range covers this node's NAD are processed; out-of-range frames are
|
|
ignored.
|
|
Note: the workbook step ``Watch AmbLightLIDFrom/AmbLightLIDTo range
|
|
flag`` refers to a firmware-internal flag that is not echoed on the
|
|
LIN bus. The LIN-observable proxy is whether the LED reacts (ON) or
|
|
stays at OFF.
|
|
|
|
Requirements: SWRS_LIN_0053
|
|
Test ID: COM_ITD_0006
|
|
"""
|
|
# Step: Broadcast LIDFrom=0x00, LIDTo=0xFF — node is in range, must react
|
|
fio.send(
|
|
"ALM_Req_A",
|
|
AmbLightColourRed=120, AmbLightColourGreen=0, AmbLightColourBlue=255,
|
|
AmbLightIntensity=255,
|
|
AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0,
|
|
AmbLightLIDFrom=0x00, AmbLightLIDTo=0xFF,
|
|
)
|
|
reached_on, elapsed, h_in = alm.wait_for_state(LED_STATE_ON, timeout=STATE_TIMEOUT_DEFAULT)
|
|
rp("in_range_history", h_in)
|
|
rp("in_range_elapsed_s", round(elapsed, 3))
|
|
assert reached_on, f"Node ignored an in-range broadcast: {h_in}"
|
|
|
|
alm.force_off()
|
|
|
|
# Step: Out-of-range LID (range that excludes this NAD) — must be ignored
|
|
# Pick a non-trivial range that intentionally excludes this node.
|
|
if alm.nad <= 0x10:
|
|
lid_from, lid_to = 0x80, 0xFE
|
|
else:
|
|
lid_from, lid_to = 0x01, max(0x02, alm.nad - 1)
|
|
fio.send(
|
|
"ALM_Req_A",
|
|
AmbLightColourRed=255, AmbLightColourGreen=255, AmbLightColourBlue=255,
|
|
AmbLightIntensity=255,
|
|
AmbLightUpdate=0, AmbLightMode=0, AmbLightDuration=0,
|
|
AmbLightLIDFrom=lid_from, AmbLightLIDTo=lid_to,
|
|
)
|
|
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("out_of_range_lid", (lid_from, lid_to))
|
|
rp("out_of_range_history", history)
|
|
assert LED_STATE_ON not in history and LED_STATE_ANIMATING not in history, (
|
|
f"Out-of-range LID frame [{lid_from:#x}..{lid_to:#x}] (NAD={alm.nad:#x}) "
|
|
f"unexpectedly drove the LED: {history}"
|
|
)
|
|
|
|
|
|
def test_com_itd_0001_b(fio: FrameIO, alm: AlmTester, rp):
|
|
"""
|
|
Title: Input frame reading periodicity (5 ms) — master-side scheduling, scope-only
|
|
|
|
Description:
|
|
Verify the SW respects 5 ms periodicity for reading input frames.
|
|
5 ms is the LIN master's schedule cadence: it is configured by the
|
|
master (MUM/BabyLin) when the schedule table is loaded from the LDF,
|
|
and is not echoed back by the slave. Confirming the actual on-bus
|
|
inter-frame gap requires bus-tracing hardware (oscilloscope or LIN
|
|
analyzer). This step is recorded for traceability.
|
|
|
|
Requirements: SWRS_LIN_0001
|
|
Test ID: COM_ITD_0001_b
|
|
"""
|
|
# Step: Document: 5 ms scheduling is master-side and not asserted from the slave
|
|
rp("note", (
|
|
"5 ms periodicity is set by the LIN master's schedule table "
|
|
"(loaded from the LDF). Slave-side timing of inter-frame gaps "
|
|
"requires an external LIN bus analyzer or oscilloscope to "
|
|
"verify; pytest cannot observe it directly."
|
|
))
|
|
# Sanity: confirm we can at least round-trip a frame within a few
|
|
# schedule periods, which verifies the bus is up at the configured
|
|
# baudrate (a coarse sanity check, not a 5 ms timing assertion).
|
|
decoded = fio.receive("ALM_Status", timeout=1.0)
|
|
assert decoded is not None, "ALM_Status not received — bus may be down"
|
|
|
|
pytest.skip(
|
|
"5 ms inter-frame periodicity is master-side / physical-layer; "
|
|
"verify with a LIN bus analyzer or oscilloscope, not pytest."
|
|
)
|