---
name: bmcli
description: Control BUSMUST automotive bus hardware with BMCLI for ECU tool development, verification and validation, diagnostics, test automation, CAN/CAN FD/LIN traffic, logging, replay, the embedded Web/REST/SSE/WebSocket automation service, a remote BMAPI gateway, DBC or LIN communication-matrix work, ISO-TP, UDS DID/DTC access, security access, and firmware download. Use when an engineer asks to inspect or initialize a CAN or LIN bench, implement or test LIN master/slave behavior, expose local analyzer channels to an existing remote BMAPI application, turn DBC/LDF/communication matrices or diagnostic/bootloader specifications into maintainable tests and evidence, build a browser dashboard or other application backed by BUSMUST hardware, investigate ECU firmware behavior, or automate repeatable hardware workflows from direct commands, shell, Python, LabVIEW, HTTP, or another harness.
---

# BMCLI

Use `bmcli` as the application-level interface to BUSMUST hardware. Prefer it
over writing new BMAPI bindings when the requested operation is already exposed
by the CLI.

## Establish the bench context

Every hardware, development, validation, or debugging workflow must begin with
`bmcli bench show --format=json`. Use the CLI output as the effective
configuration contract; do not independently merge `.bmcli/target.json` and
`bench.local.json`.
Follow it with `bmcli bench validate --hardware --format=json` before hardware
access, or without `--hardware` when deliberately planning offline.

1. Read explicit values from the current request first: analyzer serial number,
   port, bus type, bitrate, CAN termination/mode or LIN role/pull-up/supply,
   UDS request/response IDs, addressing mode, padding, database paths, and target
   variant.
2. Run `bmcli bench show --format=json` to resolve `.bmcli/target.json` and
   `.bmcli/bench.local.json` through BMCLI. See
   [environment.md](references/environment.md) for ownership and schema.
3. Treat explicit current-request values as authoritative, followed by project
   target/network/node configuration, local bench bindings, then discovery.
4. Validate cached physical values before a hardware action. Run `bmcli doctor`
   and `bmcli bench validate --hardware --format=json`. Never assume enumeration
   index 0 still means the same analyzer.
5. Classify the state as `READY`, `DEGRADED`, `NEEDS_INPUT`,
   `DISCOVERY_REQUIRED`, or `OFFLINE`, then follow the state rules in
   [environment.md](references/environment.md).

Do not silently guess a port, bitrate, UDS ID, extended addressing byte, or DBC
variant. When discovery has more than one plausible answer, present the evidence
and ask the user to select.

## Discover only with permission

- Enumerating USB analyzers is read-only and may be done automatically.
- If exactly one suitable analyzer exists, propose its stable `SN/port` values;
  do not infer which port reaches the ECU without evidence.
- AutoSet requires exclusive hardware access. If the daemon is running, ask
  before stopping it. Start with passive detection, which needs existing CAN
  traffic but does not transmit:

  `bmcli channel autoset --channel=<SN/port> --mode=passive --timeout=10000 --format=json`

- If passive detection fails because the bus is silent, offer active detection.
  Explain that it transmits probe frames and may affect a live network; run it
  only after explicit authorization, preferably on an isolated bench:

  `bmcli channel autoset --channel=<SN/port> --mode=active --timeout=10000 --format=json`

- Treat passive success as RX traffic evidence and active success as repeated
  TX-ACK evidence at the detected timing, not proof of a specific ECU identity.
- Functional-address UDS discovery can transmit onto the bus and may wake
  multiple ECUs. Use it only after explicit authorization and only on an
  isolated bench. Dry-run first, then run `bmcli uds discover --channel=<SN/port>
  --allow-functional --format=json`. If TesterPresent gets no response, retry
  only with a documented harmless read request such as `--data="22 F1 90"`.
  Accept only IDs marked `correlated`, meaning they answered every repeated
  probe. If `selection_required=true`, list all response IDs and ask the user
  which ECU is the target. Never auto-select or persist one of multiple IDs.
- Persist confirmed values with `bmcli bench init`; do not hand-edit files when
  the CLI can represent the field. Keep `bench.local.json` out of version
  control and portable target/network/node requirements in `target.json`.
- One `.bmcli` directory is one target and one bench. Do not invent profile
  activation. A bench may contain several named networks and channels. Prefer a
  logical network in `--channel=<network-name>` for maintainable project scripts;
  use explicit `SN/port` when bypassing project configuration intentionally.

## Plan before acting

Read [command-discovery.md](references/command-discovery.md) before composing
commands. Query the installed binary's JSON help; never treat remembered syntax
or a copied example as the command contract.

Use these exact discovery forms before naming a callable command:

```text
bmcli help --format=json
bmcli help <category> --format=json
bmcli help <category> <action> --format=json
```

Do not substitute remembered spellings such as `stream`, `filter`, `stats`, or
`channel listen`. If runtime help does not list a capability, describe it as an
application-layer workflow or a future requirement, not as a BMCLI command.

In the current command model, read a DID with `bmcli uds request 22 F1 90
--channel=<SN/port> --request-id=<CAN-ID> --response-id=<CAN-ID> --format=json` and inspect
DTCs with `bmcli uds dtc active --format=json` after resolving the endpoint from
bench context. There is no `uds read`, `read-did`, or `uds dtc read` convenience
command unless a future runtime help explicitly adds one.

For a UDS service with a subfunction, use the presence-only
`--suppress-positive-response` option only when the specification permits it.
BMCLI sets bit 7 and treats a successful transmission without a positive reply
as success. Do not apply this option to DID/address services; BMCLI rejects
those requests to prevent payload corruption. During a long diagnostic
sequence, query `bmcli help uds present start --format=json`, start one
daemon-owned TesterPresent task, inspect `uds present status` counters, and stop
the task in cleanup. Without suppression, each `3E 00` must receive `7E 00`;
with suppression, BMCLI sends `3E 80` and does not wait for that reply.

For raw capture, the current command is `bmcli message recv
--channel=<SN/port> --duration=60 --format=jsonl`; for a file-backed recording,
query `bmcli help logging start --format=json` and use `logging start`. There is
no `message listen`, `channel listen`, or `stream` command. `duration` is in
seconds; `timeout`, `cycle`, `interval`, and `delay` are in milliseconds.

When planning offline and unable to execute runtime help, include only executable
BMCLI snippets that appear verbatim in this skill. Describe every other operation
as a capability whose exact command must be resolved from JSON help at execution
time. Never fill a syntax gap from general CAN/LIN/UDS tool experience.

## Work with LIN

Select LIN by enumerated capability, not product name or numeric index. Query
current help for `channel open`, `lin config`, `lin response`, `lin schedule`,
`message send`, and `message recv` before use. A bounded master write can run
standalone:

```text
bmcli message send --channel=<SN/port> --id=0x12 --type=master-write --data="01 02 03 04" --bitrate=19200 --format=json
```

Use the daemon for persistent master/slave interaction. Open every endpoint
before arming the slave response table:

```text
bmcli daemon start
bmcli channel open --channel=<slave-SN/port> --mode=slave --bitrate=19200 --pullup=off --power=off
bmcli channel open --channel=<master-SN/port> --mode=master --bitrate=19200 --pullup=1k --power=on
bmcli lin response set --channel=<slave-SN/port> --id=0x12 --data="01 02 03 04" --checksum=enhanced
bmcli message send --channel=<master-SN/port> --id=0x12 --type=master-read --dlc=4 --timeout=1000 --format=json
```

Treat role, bitrate, power direction, pull-up, protocol version, checksum model,
ID, DLC, and payload as explicit test inputs. Validate response ID, exact bytes,
checksum, error flags, `completed`, and response count. Master-read and
sync-break emit the same header bytes on the wire but have opposite success
criteria: master-read requires payload+checksum; sync-break requires no slave
response and must fail on an unexpected payload. Never substitute one for the
other. Use `lin response` for per-ID slave mappings and the reserved `system`
schedule for repeated master slots. The first system entry auto-activates; use
`lin schedule deactivate/activate` for deliberate switching and `clear` to stop
all slots.

For an LDF, load it as a typed resource, inspect the named schedule before any
hardware action, then activate only after the selected table, master channel,
power state, and connected slave nodes have been confirmed:

```text
bmcli ldf load --file=<network.ldf> --name=<network>
bmcli ldf info --database=<network> --format=json
bmcli ldf schedule show <table> --database=<network> --format=json
bmcli ldf schedule activate <table> --database=<network> --channel=<master-SN/port> --format=json
```

BMCLI parses LDF files read-only and never edits them. It can materialize
unconditional and diagnostic frame slots into the daemon `system` table.
Event-triggered, sporadic, and configuration commands remain queryable but are
not automatically executed; when activation rejects one, explain the boundary
and ask for the intended runtime policy. Without an LDF, continue using
explicit `message`, `lin response`, and `lin schedule` primitives. Persist LIN
timing and local pull-up/power/channel bindings only through fields reported by
current `bench init --help`; do not invent unsupported schema members.

Load exactly the task methodology needed:

- Read [development.md](references/development.md) when building a dashboard,
  prototype upper-computer application, service, Python/LabVIEW integration, or
  other software that uses BMCLI as its hardware backend.
- A browser dashboard requires both [development.md](references/development.md)
  and [web-automation.md](references/web-automation.md). Read
  `web-automation.md` for every REST client, SSE/WebSocket consumer, or
  application that needs concurrent raw-frame and decoded-signal access.
- Read [verification-validation.md](references/verification-validation.md) when
  deriving requirements, test cases, automation, reports, or regression suites
  from a DBC, communication matrix, diagnostic survey, CDD, bootloader flow, or
  enterprise protocol specification.
- Read [debugging.md](references/debugging.md) for ECU fault isolation, DID/DTC
  inspection, captures, controlled traffic generation, firmware investigation,
  or one-off programming support.

For every operation:

1. State the target channel and bus configuration.
2. Use `--format=json` for bounded commands and `--format=jsonl` for streams.
3. Use stable `SN/port`, never a legacy enumeration index in generated assets.
4. Dry-run UDS request construction before first transmission when supported.
5. Capture the exact command, exit code, structured output, timestamps, and
   relevant artifacts in generated test evidence.
6. Separate transport failure, negative UDS response, assertion failure, and
   infrastructure failure in test results.
7. Clean up daemon, cyclic tasks, routes, logging, replay, and TesterPresent
   tasks that the workflow started. Do not stop pre-existing user-owned
   activity without approval.

For engineering databases, query `bmcli help database --format=json` and the
typed category Help before constructing commands. Use `database
load/list/info/validate/unload` only for persistent resource lifecycle. Use
`dbc message/signal/decode/encode` for DBC semantics. `dbc load` and `database
load` of the same DBC share one resource; they are not category-wide aliases.
When more than one DBC is registered, always select the intended variant with
`--database=<resource>`. A direct `--dbc=<file>` is appropriate for a one-off
operation that must not change persistent registry state.

## Use the least risky interface

- Use direct BMCLI commands for inspection and one-off operations.
- Use shell scripts for fixed, serial command sequences with simple pass/fail.
- Use Python `subprocess` for loops, matrices, retries, JSON/JSONL parsing,
  artifact generation, richer assertions, and long-term test maintenance.
- Use a DBC for matching CAN variants and reviewed LDF/matrix data for LIN.
  Without a database, degrade to explicit IDs and payloads rather than blocking
  the whole workflow.
- When sending by DBC message name, preserve its extended-ID, CAN FD, and BRS
  attributes. Supply `--flags` only when the user or test intentionally overrides
  the DBC metadata.
- Keep OEM security algorithms external. `uds unlock` requires the approved
  DLL/shared library; never invent seed-key logic.

## Apply safety gates

Read/listen operations may proceed after context validation. Ask for explicit
confirmation before transmitting unspecified traffic, changing ECU state,
clearing DTCs, invoking routines, unlocking security, writing identifiers,
starting download, flashing firmware, or changing analyzer persistent state.
LIN master power output, role changes, headers, and master writes are active bus
operations and require the same authorization discipline.

Before flashing, verify the exact ECU identity, firmware artifact and checksum,
address/segment map, programming preconditions, power stability, diagnostic
session, security level, rollback/recovery path, and user authorization. The
built-in `uds download` implements the transfer phase, not every OEM bootloader
precondition or post-programming validation step.

Analyzer firmware is a separate physical-device workflow. First query `bmcli
channel enumerate --format=json` and retain the intended analyzer's `sn` and
`uid`. Use `bmcli device update <serial-or-uid> --check --format=json` to inspect
the CID changelist; this is read-only. A selector may include `/port` to identify
the parent physical device, but the update affects every channel on that device.
Never substitute another enumerated device when an explicit selector is absent,
stale, or not found. Run `bmcli device update <serial-or-uid>` only after the user
explicitly authorizes analyzer downtime and all channels on that device are idle.
The current implementation supports generation-3 HPM devices only, downloads
firmware before entering BMBOOT, and must report the same UID and expected
version after re-enumeration. Never turn an available-update check into an
automatic flash during routine diagnosis or release testing.

Analyzer reset is also a physical-device operation, not a channel recovery or an
ECU diagnostic reset. Query `bmcli help device reset --format=json`, stop the
daemon only with authorization, and use a stable serial or UID selector. Run it
only when the user requests an analyzer reboot or lower-risk channel recovery has
failed. Accept success only when the bounded JSON result has exit code 0,
`action=device_reset`, `disconnect_observed=true`, `re_enumerated=true`, and the
same requested UID. The nested `api_status` may describe an interrupted USB
control transfer; never override a failed top-level result or substitute another
connected analyzer.

Starting `gateway` transmits and remotely exposes analyzer control, so require
authorization for the exact physical `SN/port` set, intended client IPv4, bus
timing/role, and trusted-network boundary. Query `bmcli help gateway start
--format=json`; do not invent a UDP protocol or bypass remote BMAPI. The current
GWR-compatible transport has no authentication or encryption and must not be
placed on an untrusted network. Stop the gateway before local BMCLI traffic work
and accept success only after checking its structured counters and cleanup.

The current canonical start shape is `bmcli gateway start
--channel=<SN/port>[,<SN/port>...] --target=<client-IPv4>
[--advertise=<gateway-IPv4>] [--name=<display-name>]`. UDP control port 3502 and
IO port 2502 are fixed protocol ports, not `--bind` or `--port` options. An
existing upper computer keeps the public `BM_Init -> BM_EnumerateRemote ->
BM_OpenEx -> BM_Read/BM_Write -> BM_Close -> BM_UnInit` lifecycle.
