ecu-tests/docs/16_mum_internals.md

7.5 KiB

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

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:

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:

# 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.

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.