# MUM Adapter Internals (Melexis Universal Master) This document describes how the `MumLinInterface` adapter wraps the Melexis `pymumclient` and `pylin` packages, how frames flow across the LIN bus, and which MUM-specific behaviors callers need to understand. ## Overview - Location: `ecu_framework/lin/mum.py` - Vendor reference scripts: `vendor/automated_lin_test/` (`test_led_control.py`, `test_auto_addressing.py`, `power_cycle.py`) - Default MUM endpoint: `192.168.7.2` over USB-RNDIS - LIN device name on MUM: `lin0` - Power-control device on MUM: `power_out0` - Required Python packages: `pylin`, `pymumclient` (Melexis-supplied; not on PyPI). See `vendor/automated_lin_test/install_packages.sh`. ## What the MUM gives you that BabyLIN doesn't - **Built-in power control** on `power_out0` — the adapter calls `power_up()` in `connect()` and `power_down()` in `disconnect()`. No external Owon PSU needed for the standard flow. - **Network access**: the MUM is IP-reachable, so the host machine (Windows, Linux, Pi) does not need vendor native libraries — only the two Python packages. - **Direct transport-layer access** for sending raw frames with LIN 1.x **Classic** checksum (required for BSM-SNPD diagnostic frames). ## What it doesn't give you - **No passive listen.** The MUM is master-driven. To "receive" a slave-published frame, the master sends a header on that frame ID and the slave must respond. `MumLinInterface.receive(id=None)` raises `NotImplementedError` for that reason. - **No SDF / schedule manager.** The adapter does not run a schedule; tests publish frames explicitly (or pull slave frames explicitly) on each call. ## Mermaid: connect / receive / send ```mermaid sequenceDiagram autonumber participant T as Test/Fixture participant A as MumLinInterface participant MM as pymumclient (MelexisUniversalMaster) participant PL as pylin (LinDevice22 / TransportLayer) participant E as ECU T->>A: connect() A->>MM: MelexisUniversalMaster() A->>MM: open_all(host) A->>MM: get_device('power_out0') A->>MM: get_device('lin0') A->>MM: linmaster.setup() A->>PL: LinBusManager(linmaster) A->>PL: LinDevice22(lin_bus); set baudrate A->>PL: get_device('bus/transport_layer') A->>MM: power_control.power_up() Note over A: sleep(boot_settle_seconds) A-->>T: connected T->>A: receive(id=0x11) A->>PL: send_message(master_to_slave=False, frame_id=0x11, data_length=4) PL->>E: header for 0x11 E-->>PL: response bytes PL-->>A: bytes A-->>T: LinFrame(id=0x11, data=...) T->>A: send(LinFrame(0x0A, payload)) A->>PL: send_message(master_to_slave=True, frame_id=0x0A, data_length=8, data=payload) PL->>E: header + payload (Enhanced checksum) T->>A: send_raw(b"\x7F\x06\xB5...") A->>PL: transport_layer.ld_put_raw(data, baudrate) Note over PL,E: LIN 1.x Classic checksum (required for BSM-SNPD) T->>A: disconnect() A->>MM: power_control.power_down() A->>MM: linmaster.teardown() ``` ## Public API `MumLinInterface(host, lin_device='lin0', power_device='power_out0', baudrate=19200, frame_lengths=None, default_data_length=8, boot_settle_seconds=0.5)` LinInterface contract (matches Mock and BabyLIN adapters): - `connect()` — opens MUM, sets up LIN, **and powers up the ECU** - `disconnect()` — powers down and tears down (best-effort) - `send(frame: LinFrame)` — publishes a master-to-slave frame using Enhanced checksum - `receive(id: int, timeout: float = 1.0) -> LinFrame | None` — triggers a slave read for `id`. The `timeout` argument is informational; the underlying `pylin` call is synchronous. Any pylin exception is treated as "no data" and returns `None`. Passing `id=None` raises `NotImplementedError`. MUM-only extras: - `send_raw(bytes)` — sends a raw LIN frame using **Classic** checksum via the transport layer's `ld_put_raw`. Use this for BSM-SNPD diagnostic frames; the firmware will reject them if Enhanced is used. - `power_up()` / `power_down()` — direct control over `power_out0` - `power_cycle(wait=2.0)` — convenience: `power_down()`, sleep, `power_up()`, then `boot_settle_seconds` sleep ## Frame-length resolution Because the MUM is master-driven, every receive needs to know how many bytes to ask for. The adapter resolves this from `frame_lengths`: 1. Built-in defaults for the 4SEVEN library (ALM_Status=4, ALM_Req_A=8, ConfigFrame=3, PWM_Frame=8, VF_Frame=8, Tj_Frame=8, PWM_wo_Comp=8, NVM_Debug=8). 2. Anything in the constructor's `frame_lengths` argument **overrides** the defaults. 3. If a frame ID isn't in the map, `default_data_length` (default 8) is used. In YAML, hex keys work: ```yaml interface: type: mum frame_lengths: 0x0A: 8 0x11: 4 ``` The config loader coerces hex strings (`"0x0A"`) and integers alike. ## Diagnostic frames (BSM-SNPD) The vendor's `test_auto_addressing.py` flow runs LIN 2.1 BSM-SNPD via raw frames on `0x3C` (MasterReq). The framework supports the same flow: ```python # inside a test that already has the MUM 'lin' fixture data = bytearray([ 0x7F, # NAD broadcast 0x06, # PCI: 6 data bytes 0xB5, # SID: BSM-SNPD 0xFF, # Supplier ID LSB 0x7F, # Supplier ID MSB 0x01, # subfunction (INIT) 0x02, # param 1 0xFF, # param 2 ]) lin.send_raw(bytes(data)) ``` `send_raw()` calls `transport_layer.ld_put_raw(data=..., baudrate=...)` which uses LIN 1.x Classic checksum. Using `lin.send()` for these frames would compute Enhanced checksum and the firmware would discard the frame. ## Error surfaces - **`pymumclient is not installed`** / **`pylin is not installed`** — raised on `connect()` if the Melexis packages aren't importable. The error message points at `vendor/automated_lin_test/install_packages.sh`. - **`MUM not connected`** — calling `send` / `receive` / `send_raw` before `connect()` (or after `disconnect()`). - **`MUM transport layer not available`** — raised by `send_raw` when the LIN device didn't expose `bus/transport_layer`. Practically always available on MUM firmware that supports diagnostic frames. - **pylin exceptions during `receive`** — converted to `None` (treated as a timeout / no-data). Use this to drive timeout-tolerant tests without try/except in the test body. ## Unit testing without hardware The adapter accepts `mum_module=` and `pylin_module=` constructor arguments that bypass the real package imports. Tests in `tests/unit/test_mum_adapter_mocked.py` use simple in-memory fakes to drive the connect / send / receive / send_raw / power-cycle paths end to end. See that file for a complete shim implementation. ```python from ecu_framework.lin.mum import MumLinInterface iface = MumLinInterface( host="10.0.0.1", boot_settle_seconds=0.0, mum_module=fake_mum, pylin_module=fake_pylin, ) iface.connect() # ... assertions ... iface.disconnect() ``` ## Notes and pitfalls - **Boot settling**: After `power_up()` the adapter sleeps `boot_settle_seconds` (default 0.5 s) so the ECU has time to come up before the first frame. Increase if your ECU boots slowly. - **Owon PSU coexistence**: the MUM provides power on `power_out0` independently of `ecu_framework/power/`. Leave `power_supply.enabled: false` for the standard MUM flow; enable it only for over/under-voltage scenarios that need a separate, programmable rail. - **Networking**: USB-RNDIS bring-up can take a few seconds after plugging in the MUM. If `connect()` fails with a connection-refused, `ping 192.168.7.2` first. - **Multiple MUMs**: only one MUM is supported per `MumLinInterface` instance. Different `host` addresses can run different fixture sessions side-by-side.