"""End-to-end hardware test on the MUM (Melexis Universal Master). Power the ECU via MUM's built-in power output, then activate the RGB LED via the master-published ALM_Req_A frame (ID 0x0A) and verify the slave responds on ALM_Status (ID 0x11). Frame layout (from vendor/4SEVEN_color_lib_test.ldf, ALM_Req_A @ 0x0A, 8B): byte 0 AmbLightColourRed (0..255) byte 1 AmbLightColourGreen (0..255) byte 2 AmbLightColourBlue (0..255) byte 3 AmbLightIntensity (0..255) byte 4 AmbLightUpdate (bits 0-1) | AmbLightMode (bits 2-7) byte 5 AmbLightDuration byte 6 AmbLightLIDFrom byte 7 AmbLightLIDTo The ECU answers ALM_Req_A only when AmbLightLIDFrom <= ALMNadNo <= LIDTo, so we read the current NAD from ALM_Status first and target that NAD exactly. """ from __future__ import annotations import pytest from ecu_framework.config import EcuTestConfig from ecu_framework.lin.base import LinFrame, LinInterface pytestmark = [pytest.mark.hardware, pytest.mark.mum] ALM_REQ_A_ID = 0x0A ALM_STATUS_ID = 0x11 DEFAULT_RGB = (0xFF, 0xFF, 0xFF) DEFAULT_INTENSITY = 0xFF def _build_alm_req_a_payload( r: int, g: int, b: int, intensity: int = DEFAULT_INTENSITY, update: int = 0, mode: int = 0, duration: int = 0, lid_from: int = 0x01, lid_to: int = 0xFF, ) -> bytes: """Pack RGB+mode signals into the 8-byte ALM_Req_A payload.""" byte4 = (update & 0x03) | ((mode & 0x3F) << 2) return bytes([ r & 0xFF, g & 0xFF, b & 0xFF, intensity & 0xFF, byte4 & 0xFF, duration & 0xFF, lid_from & 0xFF, lid_to & 0xFF, ]) def test_mum_e2e_power_on_then_led_activate(config: EcuTestConfig, lin: LinInterface, rp): """ Title: MUM E2E - Power ECU, Read NAD, Activate RGB LED Description: Drives the full hardware path through the Melexis Universal Master: the `lin` fixture has already powered the ECU via power_out0 and set up the LIN bus. This test reads ALM_Status to discover the slave's NAD, publishes ALM_Req_A targeting that NAD with full white at full intensity, and re-reads ALM_Status to confirm the bus is alive. Requirements: REQ-MUM-LED-ACTIVATE Test Steps: 1. Skip unless interface.type == 'mum' 2. Read ALM_Status (0x11) and extract ALMNadNo (byte 0 lower 8 bits) 3. Build ALM_Req_A payload with RGB=(0xFF,0xFF,0xFF), intensity=0xFF, targeting LIDFrom=LIDTo=current_nad 4. Publish ALM_Req_A via lin.send() 5. Re-read ALM_Status and assert it still returns a valid frame Expected Result: - First ALM_Status read returns a 4-byte frame with a NAD in 0x01..0xFE - Second ALM_Status read returns a frame (bus still alive after Tx) """ if config.interface.type != "mum": pytest.skip("interface.type must be 'mum' for this test") # Step 2: read current NAD from ALM_Status status = lin.receive(id=ALM_STATUS_ID, timeout=1.0) assert status is not None, "No ALM_Status received — check MUM/ECU wiring and power" assert len(status.data) >= 1, f"ALM_Status too short: {status.data!r}" current_nad = status.data[0] rp("alm_status_data_hex", bytes(status.data).hex()) rp("current_nad", f"0x{current_nad:02X}") assert 0x01 <= current_nad <= 0xFE, ( f"ALMNadNo {current_nad:#x} is out of valid range; ECU may be unconfigured" ) # Step 3 + 4: target the discovered NAD with full white payload = _build_alm_req_a_payload( *DEFAULT_RGB, intensity=DEFAULT_INTENSITY, lid_from=current_nad, lid_to=current_nad, ) rp("tx_id", f"0x{ALM_REQ_A_ID:02X}") rp("tx_data_hex", payload.hex()) rp("rgb", list(DEFAULT_RGB)) rp("intensity", DEFAULT_INTENSITY) lin.send(LinFrame(id=ALM_REQ_A_ID, data=payload)) # Step 5: confirm bus liveness after the activation frame status_after = lin.receive(id=ALM_STATUS_ID, timeout=1.0) rp("post_status_present", status_after is not None) if status_after is not None: rp("post_status_data_hex", bytes(status_after.data).hex()) assert status_after is not None, ( "ALM_Status not received after publishing ALM_Req_A — ECU may have reset" )