BMCLI simulation — CAN/CAN FD Node Simulation and Application-Layer Fault Injection
Reference version: BMCLI 1.6.3. Command names, parameters and machine-readable fields retain their source spelling.
Host resources independent of hardware txtask: software periodic transmission, task-local signals, waveforms,
E2E, and explicit fault rules. validate performs local static validation without starting a daemon; all other state operations
are owned by the daemon. At runtime, a resource binds only to an already open CAN channel with control/claim permission.
It does not open devices automatically, change the hardware TX Task table, or create another RX reader. Use an isolated test bench.
All operations below are under the BMCLI top-level simulation category; that prefix is omitted. Every operation supports the global
--format=json option. This resource has no HTTP endpoint. Read operations require read permission;
runtime changes follow existing write/control arbitration, and structural load/unload operations require admin permission.
| Operation | Parameters and purpose |
|---|---|
| validate | --file=<path>: fully validate a JSON manifest/BMSIM file, DBC/plugin dependencies, and references locally; neither register resources nor access hardware |
| load | --file=<path> [--name=<resource>]: load a JSON manifest or BMSIM bundle without transmitting |
| unload | <resource> or --all: stop and drain before releasing; do not overwrite a resource with the same name |
| list | List resource summaries and limits |
| info | <resource> [--offset=0 --limit=256]: definition summary and task states |
| start | <resource> --channel=<SN/port>: explicitly bind an existing CAN channel and start all tasks |
| stop | <resource>: stop and wait for candidates to finish, retaining the definition |
| status | [<resource>] [--offset=0 --limit=256]: state of one or all resources |
| task list | --simulation=<resource> [--offset=0 --limit=256] |
| task show/start/stop | <task> --simulation=<resource>: inspect or start/stop an individual task |
| signal get | <signal> --simulation=<resource> --task=<task> |
| signal set | --simulation=<resource> --task=<task> --signal=<name=value>... |
| source set | <signal> --simulation=<resource> --task=<task> --type=<kind> [options] |
| source clear | <signal> --simulation=<resource> --task=<task> |
| fault list | --simulation=<resource> |
| fault show/enable/disable | <fault> --simulation=<resource> |
Names are case-sensitive. Task/fault names are unique within a simulation, and signal names within a message.
signal set and source set/clear accept --if-generation=<integer>.
Read the generation, then update conditionally to detect concurrent changes and stale references after unload/reload.
Repeated --signal options form one atomic transaction within a task: validate everything before publishing a new generation.
Any error leaves the old snapshot unchanged. A frame that has already captured the old snapshot may finish first; subsequent candidates
use the new snapshot, so a payload cannot mix old and new values. There is no implicit broadcast transaction across tasks.
Example (first open the test channel and obtain permission using the existing channel/claim commands):
bmcli simulation validate --file=examples/simulation/vehicle.json --format=jsonbmcli simulation load --file=examples/simulation/vehicle.jsonbmcli simulation fault enable bad-checksum --simulation=powertrainbmcli simulation start powertrain --channel=10356/0bmcli simulation signal set --simulation=powertrain --task=vehicle --signal=Speed=80 --signal=Gear=Dbmcli simulation source set Speed --simulation=powertrain --task=vehicle --type=sine --offset=60 --amplitude=20 --period=2000bmcli simulation source set Speed --simulation=powertrain --task=vehicle --type=step --point=0:20 --point=1000:80bmcli simulation fault show bad-checksum --simulation=powertrain --format=jsonbmcli simulation status powertrain --format=jsonbmcli simulation stop powertrainbmcli simulation unload powertrainFor a complete workflow from DBC to a normal node, then dropped/repeated/unprotected/checksum-corrupted frames, recovery, and coexistence, see Node Simulation Practice. Load and enable faults before starting the timer; do not enable them after a finite window has ended.
Manifest, DBC, and Value Encoding
Section titled “Manifest, DBC, and Value Encoding”The entry file is UTF-8 JSON. schema_version:1, name, and database are required.
DBC/plugin paths resolve relative to the manifest directory. Selecting node:"Powertrain" generates all messages
for that DBC transmitter. Alternatively, specify messages:["VehicleStatus"] explicitly, or use objects as array entries.
Explicit messages take precedence over node selection; a supplied node is still checked for existence. Unknown, ambiguous, and empty selections are rejected.
Message object fields:
| Field | Default / semantics |
|---|---|
| message / name | DBC message name / task name (defaults to the message name); one message can instantiate multiple named tasks |
| cycle_ms | Message override > manifest cycle_ms > DBC GenMsgCycleTime; must quantize to a positive number of microseconds |
| phase_ms / rounds | Initial transmission delay relative to this task start, default 0; accepted-frame limit per run, with 0 meaning unlimited |
| default_data | Hex bytes for the complete wire payload; length must match the DBC; defaults to zero. Tasks sharing a message must agree |
| values | {"Speed":20,"Gear":"D"}: initial task values; unlisted signals are unchanged |
| sources | Bind source objects by signal name |
| e2e | Protect configuration for a built-in or allowlisted plugin; omission means no protection |
| faults | Array of named task-local fault objects; disabled by default |
The DBC preserves ID/IDE/FDF/BRS, valid CAN FD wire lengths, Intel/Motorola layout, integer signedness, factor/offset/range, enumerations, and simple multiplexing. Floating-point signals and nested/extended multiplexing are outside the initial feature set. Transmission rebuilds undefined bits from default_data and encodes application signals from the task’s current raw image; inactive mux branches are not written. Explicit updates to an inactive branch are rejected; changing the selector and updating the new branch in one set is allowed.
Physical values convert using round((physical-offset)/factor), with exact halves rounded away from zero,
followed by bit-width and DBC range checks. DBC min==max means no physical range is declared. Enumeration labels must match exactly.
raw:0xffffffffffffffff passes a 64-bit raw value exactly (use a string in JSON), without conversion through double.
Normal set/source operations cannot modify E2E-owned bits. signal set removes the corresponding source;
source clear restores the configured constant rather than freezing the last waveform sample.
signal get returns configured raw/physical/source_type/generation, not an instantaneous waveform sample or E2E counter.
source_type is always none, constant, step, sine, or sawtooth; internal numeric enums are not exposed.
Source Contract
Section titled “Source Contract”A source object selects its type. CLI options map to JSON as follows:
| Type | CLI parameters | JSON fields |
|---|---|---|
| constant | --value=<physical/enum/raw> |
value |
| step | Repeated --point=<ms:value> |
points: [{at_ms,value}, …], with strictly increasing times |
| sine / sawtooth | --offset=<center> --amplitude=<nonnegative> --period=<ms> |
offset, amplitude, period_ms |
| Common | `–start= |
clip>` |
| Repeating step | --repeat=true --period=<ms> |
repeat:true, period_ms |
Before start, the configured constant is retained. A step takes its new value exactly at the keyframe time; a non-repeating step holds its last value. For repeat, period must be strictly greater than the final keyframe time. Periodic sources repeat indefinitely. A sine starts at center and rises; a sawtooth rises from center-amplitude to center+amplitude, then drops at the cycle boundary. Phase is normalized within one period; offset/amplitude use physical units. range defaults to error. Only explicit clip clamps to the intersection of representable DBC and raw ranges. Internally, parameters are quantized to integers with Q32 phase and Q30 CORDIC; no floating-point sin is calculated per frame. constant/step support the full U64 range; periodic waveform center/amplitude use the int64-representable domain.
A resource shares one simulation epoch. Waveforms and time faults use scheduled time, not actual wake-up time. Software scheduling uses absolute monotonic deadlines. After missed cycles, at most one frame is generated in the current iteration, missed is accumulated, and historical frames are not replayed. Sequence faults number actual candidate opportunities from 0 within the current task run. Missed scheduler slots only increase missed and do not skip the fault’s begin/count window. Restarting a task resets numbering to 0; unload/load also clears runtime context and accumulated state. Sub-millisecond precision is not guaranteed. Sustainable periods depend on load, the OS, and bus capacity.
Built-in E2E and OEM Plugins
Section titled “Built-in E2E and OEM Plugins”Built-in provider:"builtin" declares only two generic profiles and does not claim AUTOSAR compliance:
counter-sum: write the counter, then sum the DataID prefix and payload (excluding checksum bytes), apply xorout, truncate to the checksum width, and store in LE order.counter-crc: use the same byte coverage order with the configured polynomial/initial/xorout/reflect.
Both require counter and checksum signal names. The counter is Intel, 1..32 bits;
the checksum is byte-aligned Intel, 8/16/32 bits; the fields must not overlap.
counter_initial defaults to 0; counter_max defaults to the maximum for the bit width and must be >0. The counter returns to zero after max.
data_id defaults to 0; data_id_length defaults to 0 (0..4 allowed, included as an LE byte prefix in protection, not written to the payload).
polynomial defaults by width to 0x1D/0x1021/0x04C11DB7; initial/xorout default to 0 and reflect to false.
These parameters are not implicit defaults for any AUTOSAR profile. The initial built-in implementation accepts only variant 0.
Each task owns its context. Normal order: application encoding → Protect → explicit fault overlay → asynchronous BMAPI Write. Normal/overlay paths commit canonical context only when BMAPI accepts the frame; partial batch success commits per frame. Rejection, missed scheduling, suppression, and Protect failure do not advance it. Explicit frame-drop is the only fault path that commits successful Protect without sending; repeat/bypass do not commit context (see the table below). Acceptance for transmission is not equivalent to a physical bus ACK.
The plugins array is an allowlist: name/path/sha256 are required; timeout_ms defaults to 10 (>0, <=1000),
and context_limit defaults to 4096 (maximum 65536). E2E references the corresponding provider name, integer profile/variant,
context_size, hex config, and protected_signals. Path, hash, provider identity, and ABI must match.
Libraries load directly into the BMCLI process: a DLL of matching bitness on Windows, or an SO of matching architecture on Linux.
Simulation retains bounded worker-lane scheduling. Gateway shares its native E2E ABI, loader, and serialization of provider calls.
A normal nonzero return fails the affected operation. Plugin crashes or infinite loops are not isolated; there is no standby or runtime fallback.
The legacy manifest timeout_ms field remains accepted for parsing compatibility; it no longer means that a native plugin call can be interrupted.
Plugin validation and loading do not hold the daemon’s global command lock. Preparation still registers a channel lease and
checks resource/channel generation and owner before start succeeds; another start during preparation returns busy. Native functions must return promptly themselves;
stop cannot forcibly terminate a running C function.
For the SDK C ABI and a compilable template, see examples/simulation/demo_oem.c and bm_e2e_plugin_api.h. Protect is required. Init/Check/Reset/Destroy are optional negotiated fields; the current transmission engine does not integrate RX Check. Context is a byte image copied transactionally by the host. It cannot contain pointers/handles; a plugin cannot transmit independently or start background work. Payload changes outside protected_signals reject the entire candidate. Native plugins share the BMCLI process. Load only trusted, reviewed plugins.
Faults, Lifecycle, and State
Section titled “Faults, Lifecycle, and State”Faults use the manifest naming rules and the simulation fault command tree; no separate fault task is created.
| type | Current transmission opportunity | Context / generated | Actual BMAPI accepted / separate count |
|---|---|---|---|
| suppress-send | Suppress before Protect; generate no protected frame | Unchanged | No transmission; suppressed +1 |
| frame-drop | Deliberately discard the protected frame after successful normal encoding and Protect | Commit once; generated +1 | No transmission; dropped +1 |
| frame-repeat | Resend the most recent complete, normally protected, accepted frame without resampling application values or running Protect | Unchanged | After acceptance: accepted/repeated +1 |
| e2e-bypass | Encode current application values and skip Protect; E2E bits come from immutable default_data | Unchanged | After acceptance: accepted/unprotected +1 |
| payload-xor / payload-replace / counter-freeze / counter-jump / checksum-corrupt | Modify the candidate after Protect; the normal transaction still commits on acceptance | Commit once after acceptance; generated +1 | After acceptance: accepted/corrupted +1 |
With a 50 ms period, initial counter 0, and one frame-drop at the second opportunity, the receiver sees 0@0ms and 2@about100ms; both checksums are valid. A frame-repeat in the same window yields 0, 0, 1, with identical bytes in the first two frames. Suppression yields 0@0ms and 1@about100ms. Times are scheduled values plus host/bus jitter; hard real-time behavior is not guaranteed. The repeat cache excludes dropped, bypassed, and overlaid frames. Without an established cache, last_error is repeat_frame_unavailable; nothing is sent and no first frame is generated implicitly. stop/start retains the cache; unload/load clears it. A bypassed checksum may happen to be valid; use checksum-corrupt to create a repeatable checksum error rather than assuming that unprotected means necessarily invalid.
At most one flow rule (suppress/drop/repeat/bypass) may apply to an opportunity. A flow rule triggered alongside any overlay also returns fault_conflict, sends nothing, and does not advance context; rules can be combined in separate windows. Pure overlays apply in manifest order. frame-drop/frame-repeat/e2e-bypass also support OEM providers without requiring the host to understand their private counter layouts. Plugin failures always fail closed and never switch implicitly to bypass.
Sequence triggers number from 0 by default. begin defaults to 0, end to infinity (a half-open interval), and every to 1. Time triggers use begin_ms/end_ms/every_ms. Interval 0 fires once at the first due frame in the window; a nonzero interval fires at most once per time slot. count limits scheduled trigger slots; missed slots are not made up. enable/disable does not clear trigger history. Starting the task again rearms triggers while preserving cumulative fire counts. Payload rules use offset (bytes) and data (hex). counter-freeze value is a fixed raw value; counter-jump value is a signed increment that wraps at the field width; checksum-corrupt uses value as an XOR mask. After truncation to the checksum’s 8/16/32 bits, the mask must be nonzero; otherwise load rejects it so an ineffective injection is not counted as successful. Dedicated counter/checksum faults apply only to the built-in provider; plugins use explicit payload overlays. counter-freeze/jump changes only counter bits after protection and may invalidate the checksum; it is not equivalent to repeating/dropping a valid frame. The three new flow rules require E2E and do not accept value/offset/data. default_data can specify protection-field values for bypass. Even when signal raw defaults in an external binary bundle differ from PDU defaults, bypass uses the E2E-owned bit mask to take protection bits from PDU default_data while retaining application bits from the current encoding. enable does not restart a finite window. Execute it before start or before a future window, then check the fired increment and actual RX.
stop/start retains signals, sources, counter/context, and cumulative statistics. Starting the entire resource establishes a new epoch; task start retains the resource epoch but begins its own sequence/rounds/phase as a new run. Once all tasks finish or stop, the channel binding is released; run the whole-resource start again to resume. Only unload/load restores initial state. Channel close/reset/reset-defaults/reconfigure, owner invalidation, and daemon stop perform cancel/drain and stop using the old handle. Hardware txtask configuration is also a channel modification: configure hardware tasks before starting simulation.
Top-level raw/physical/generation in simulation signal get retain configured-value semantics. The added
current object returns unified current state: type, exact string value/raw (null if unobserved),
valid, reason, and string version/epoch/last_observed_us/last_valid_us.
It is read under the same lock as the returned configuration snapshot. Data updates do not increase configuration generation.
Existing waveforms retain the task’s scheduled sample time and phase; observation time records actual execution. E2E plugin waits,
dropped/repeated/suppressed frames, and writer rejection do not prevent waveform current-state updates. Invalid validity/MUX states retain
the last valid value. Protection fields update after the Protect result is actually committed and show the counter/checksum in that result,
rather than treating the next counter as already generated. Task frame records remain authoritative for final transmission and fault outcomes.
Successful JSON is {"status":"ok","data":...}; failures contain a stable error code/message.
The summary includes name/schema_version/channel/active/task_count/messages/signals/provider_count,
memory_budget_bytes/limits/counter_commit. Task state includes
state/generation/accepted/generated/failed/missed/suppressed/dropped/repeated/unprotected/injected/corrupted/provider_failures、
sequence/next_counter/last_generation/last_sequence/last_payload/last_injected/last_error。
last_payload is the last actual frame accepted by BMAPI; last_generated_payload/last_generated_sequence/
last_generated_generation describe the last committed Protect output before overlays, so the two groups can differ after a drop.
next_counter is meaningful only for the built-in provider; plugin context is not presented as a generic counter.
injected counts successfully constructed fault candidates (and may increase even if the writer rejects them); corrupted counts only accepted
overlay frames. Other faults have separate counts. fault list/show provides type/trigger/window/enabled/fired;
fired counts opportunities where a rule actually matched, including failures afterward, and is not equivalent to successful transmission. rounds still limits only accepted frames;
drop/suppress do not consume that quota. Normal failures and deliberate injections are counted separately.
info/status/task list returns the first 256 tasks by default. limit is 1..256; offset starts at 0, and next_offset is null at the last page. start/stop responses include only the first batch of states; query subsequent pages for large resources. Current limits: 16 resources; per resource, 65536 tasks/1048576 signals/ 16 providers/4096 faults; bundle size 64 MiB; memory budgets of 128 MiB per resource and 256 MiB per daemon; provider queue 64. The first applicable limit wins; these maxima are not guaranteed to be simultaneously achievable. The memory budget includes all portable tables (including unreferenced records) and each task’s expanded source/step spans, plus conservative allowances for runtime generations and control-plane copies. JSON simulation input allows at most 64 levels of container nesting. Daemon quota is reserved before loading. Each resource also reserves 16384 runtime step points (reported as runtime_extra_step_points in limits); the total across all current tasks cannot exceed the load-time expanded count plus this allowance. Replacement/clear returns quota. Over-budget updates are rejected before publication, retaining the old generation and transmission state. The budget is not process RSS. Structural changes require stop, unload, and reload. There is no persistent automatic recovery, Web UI, firmware execution, or physical-layer error injection.
BMCLI is a command-line interface for BUSMUST CAN FD / LIN analyzers. It communicates with hardware through the BMAPI library.
BMCLI also provides an OS-native Modbus master/client service. Modbus RTU and ASCII use Win32 COM or Linux tty devices; Modbus TCP uses ordinary sockets and does not require BMAPI hardware.
Invocation: bmcli <category> <action> [options]
Global options:
| Option | Description |
|---|---|
--format=<FMT> |
Output format: text (default), json, jsonl, csv |
--to=<ADDR:PORT> |
Target daemon address. Default: 127.0.0.1:4502. For remote: 192.168.1.100:4502 |
--verbose |
Print detailed diagnostic output |
--help, -h |
Show usage |
--version, -v |
Show version |
Every callable command’s JSON Help includes max_positionals; null means an
explicitly variadic command. Only those declared positional arguments are
accepted. Any additional bare token is rejected as invalid_argument before
daemon IPC, file changes, or hardware access; the error names both the token
and resolved command path and exits with code 1 in text, JSON, and JSONL.
Source of truth: BMCLI 1.6.3 repository documentation. Run bmcli help <category> <action> --format=json for the exact contract of the installed version.