Skip to content

BMCLI Gateway project format

Reference version: BMCLI 1.6.3. Command names, parameters and machine-readable fields retain their source spelling.

This document describes the implemented normalized-table parser for manual editing, AI-generated configuration, and plugin integration. Files use the .bmgw extension and contain UTF-8 JSON. First run bmcli gateway validate --file=<project>; only a complete, validated configuration can be loaded. See the Reference for commands, Bench settings, and runtime lifecycle, and the examples for projects that can be built and run directly.

  • The root object must contain format:"bmgw", schema_version:1, project, and at least one endpoints entry. project.id is required; name/description are optional.
  • Other normalized tables are arrays and default to empty: signals/codecs/messages/pdus/resources/bindings/mappings/tx_tasks/e2e/plugins/waveforms/faults. metadata/provenance retain descriptive data and are not executed.
  • Each table’s id must be nonempty, shorter than 128 UTF-8 bytes, and unique within that table. References use the table’s ID strings; inputs/outputs/reads and similar fields are arrays of ID strings. Configuration order does not determine graph propagation order.
  • Unknown fields, duplicate JSON members, NUL, invalid UTF-8, broken references, cycles, and multiple writers are rejected. Place additional information only in metadata/provenance, descriptive fields, or plugin config.
  • bool values use JSON true/false; semantic int64/uint64/enum values (such as initial and special.value) use exact decimal strings. float64 uses finite numbers. Other unsigned fields may use nonnegative JSON integers; values above 2^53-1 must use exact strings. Raw values/IDs and similar fields accept strings such as "0xFFFF". Do not use NaN to represent invalidity.
  • *_ms fields are nonnegative integer milliseconds. offset/length generally use bytes, and start_bit/bits use bits. Whether zero disables a feature depends on the individual field’s rules.
  • Mapping k/b/threshold, Codec factor/offset/minimum/maximum, and non-step waveform parameters remain Float64. Integer parameters must be exactly representable as Float64; strings, .0, and scientific notation cannot bypass this check. Ordinary fractions use Float64 rounding. Use typed Signals or step values when full 64-bit integer values are needed.
  • Saving retains the complete static configuration, excluding current values, Counters, plugin Context, actual device bindings, and expanded paths. After DBC import, runtime no longer depends on the original file.
Table Fields and rules
endpoints id/provider/network are required. provider is can or modbus. network is a local Bench logical network name
CAN Endpoint No role/node; device UID, bitrate, mode, and termination come from the Bench
Modbus Endpoint role must be client or server; node is a logical Bench node. The Bench resolves TCP/RTU/ASCII, host/serial port, Unit ID, and serial parameters
messages id/endpoint/can_id are required; extended/fd/brs default to false; BRS requires FD; length defaults to 8, cycle_ms to 0 (no period declared)
Message payload Classic length is 0..8; FD allows 0..8,12,16,20,24,32,48,64. default_data is a hex string without spaces and must cover exactly length bytes; it defaults to all zeros
pdus id/message are required; offset defaults to 0 and length to the entire Message length. A PDU must be nonempty and fit completely within the message

If the entire pdus table is omitted or empty, <message-id>.pdu is created automatically for every nonempty Message. Once any PDU is specified explicitly, list all required PDUs. Define only one Message per CAN ID/IDE on a logical port; use multiple Bindings to describe its signals.

RX consumes only data frames with complete headers and all declared data. RTR, TX completion, and events with insufficient storage do not update values or freshness. Classic DLC9..15 means8 bytes; FD uses the standard DLC table. ID/IDE distinguishes Messages; FDF and actual length must match the declaration. BRS/ESI does not invalidate received data. Raw observations retain RTR/IDE/FDF/BRS/ESI; the current native frame ABI exposes only IDE/FDF/BRS, without ESI metadata. There is no LIN Gateway provider.

Table Fields and rules
signals id/type/producer are required; type=bool/int64/uint64/float64/enum; name defaults to id, unit/description are descriptive; enum requires a nonempty enum_domain
producer input: a single RX/Poll/external Server write/Codec/Processor source; software: CLI-updatable; constant: fixed initial; waveform: driven by the waveforms table; mapping: produced by exactly one Mapping
initial Optional except for constant; when destination encoding has no invalid_raw, the bound Signal must have an encodable initial value. An initial value does not prove an actual source observation has occurred
freshness stale_after_ms defaults to 0 (no TTL expiry); derived values inherit input observation age, and recalculation does not renew freshness. Both explicit TTL and invalid inputs can invalidate a value
codecs id is required; bits defaults to 8 (range 1..64), encoding to integer, signed to false; factor=1/offset=0, and factor must be nonzero
Floating-point wire encoding encoding=float32 requires bits=32; float64 requires bits=64; signed must be false. The Binding specifies wire bit ordering
Range/rounding Optional minimum/maximum define the physical range; rounding defaults to nearest_away, also supporting floor/ceil/truncate. Out-of-range values fail encoding and are not clamped automatically
Special raw values Optional invalid_raw decodes as invalid and is output when encoding an invalid state. special is an array of {raw,valid,type?,value?}; valid defaults to false; invalid entries cannot contain value; valid entries must provide type and a semantic value
Description table value_descriptions retains DBC VAL_ descriptions; it neither infers invalidity from text nor replaces special

The Codec identifies invalid_raw/special and validity bits before converting the wire value with physical=factor*wire+offset. Encoding applies the inverse conversion and specified rounding. Valid special values can be reverse-encoded; duplicate raw values or nonunique valid reverse mappings are rejected. An invalid destination prefers invalid_raw; without it, the Binding retains its last encodable raw value, using initial on a cold start. The corresponding validity bit is still cleared when raw is retained, and normal E2E continues.

The unscaled integer path preserves full 64-bit precision. Mathematical operations or cross-type conversions require exactly representable inputs and outputs. An inexact conversion or a calculation beyond platform capabilities reports invalidity/error rather than silently truncating.

minimum/maximum comparisons are also constrained by platform precision: an out-of-range integer cannot first be rounded to a boundary and then accepted. The current platform conservatively rejects a comparison it cannot perform exactly; unscaled integer storage without range limits still retains the complete 64-bit value.

When typed integers are encoded to float32/float64 wire values, both an integer wire result and its actual stored representation must be exact. Rounded versions of 16777217 or 2^53+1 cannot be sent as valid data. Fractional wire results and Float64 sources retain normal floating-point quantization. Integer-source scaling and quantization use the exact integer magnitude directly, never first converting to double and then guessing a decimal value. Integer coefficients use their actual values; fractional coefficients use existing DBC decimal rules. Exceeding bounded intermediate calculation capacity returns encode_error. Existing DBC decimal rounding for Float64 sources (for example, -0.15/0.1) is unchanged.

A Binding requires id/signal/codec/direction and exactly one of message or resource. Optional plugin references a kind=codec configuration; each Codec plugin configuration serves only one Binding.

Binding type Direction and layout
CAN direction=rx/tx; start_bit=0, first_bit=start_bit; byte_order=intel or motorola. Motorola uses DBC sawtooth bit ordering; the importer normalizes first_bit
CAN validity field validity:{start_bit,bits:1,valid_raw:1,invalid_raw:0}, with start_bit required; this separate field uses Intel bit ordering. Business and validity fields are generated from the same state snapshot
CAN MUX mux:{binding:<selector Binding in the same message and direction>,raw:<matching value>}; the selector cannot itself depend on MUX. Branches with different selector values may share bit fields; inactive branches do not refresh source observations
Client direction=read receives Poll input; write is submitted by the shared TxTask; each Resource binds one complete typed value
Server direction=serve is a read-only derived mirror; external is a remotely writable input. Assigning the same resource to both remote and Mapping writers is rejected

Resource fields: id/endpoint/table/address are required; table=coil/discrete/holding/input. address is a zero-based protocol address, not a display reference such as40001; count defaults to 1. coil/discrete requires count=1 and bits=1; holding/input uses count1/2/4 with bits16/32/64. byte_order/word_order each accept big/little, defaulting to big. writable defaults to false; only coil/holding may be writable. poll_ms defaults to 100 and must be greater than 0 for Client read.

Resource Bindings cover complete values and do not allow partial encoding with start_bit, validity, or MUX. Multiword Server resources reject partial-value writes; resources in one request are read from the same mirror snapshot. Client Write success means a protocol response, not proof of physical relay-contact movement.

Before accepting a complete external Server write, the built-in Codec’s range and type are checked. If any resource cannot be decoded, Modbus exception03 (Illegal Data Value) is returned, with no partial update to the mirror, input queue, or signals. Explicit invalid_raw/invalid special values are valid wire representations of an invalid state and can be written; native Codecs are still handled by executor callbacks.

Holding/Input Registers also support bit_offset:0..15, preserving existing Modbus single-register Boolean-bit semantics: count must be1 and Codec.bits must be1; bit0 is the wire register’s least significant bit. byte_order/word_order then use the default big setting. Without bit_offset, the value remains a complete register. Different Resources may occupy non-overlapping bits of the same register; duplicate bits or overlap with a whole-word resource are rejected.

Bit Resources for the same Client output register must belong to one TxTask. Transmission combines their latest destinations, reads with FC03, and writes back with FC06, preserving unoccupied bits. Both requests share one transaction interval on the existing physical session; Poll, other Units, and CLI access cannot interleave. A failed read prevents the write request. This local transaction does not provide atomicity across masters. Cross-instance ownership is still checked for the whole output register, including the same connection and Unit resolved through Bench aliases.

A Server combines bit Resources at one address into one register response, reading unconfigured bits as0. A complete-word write commits all externally writable bits together; unconfigured bits are not retained as state. If the word contains read-only bits, external whole-word writes are rejected; derived read-only mirrors and external setpoints should use separate addresses.

A normal Server write response means that a complete, valid group of local input setpoints has been received. The protocol thread atomically commits the group and its readable raw mirror. Up to 64 input groups may be queued; a full queue returns Modbus Busy, and rejected requests do not modify the mirror. The state executor later runs source Codecs, Mappings, and derived-mirror encoding in receive order, updating the read-only mirror after publishing the complete batch. The write response does not wait for the downstream graph or device to execute. Externally writable resources read back their latest accepted raw setpoint, so native input Codecs need only DECODE. Reads do not call native encoders or refresh input freshness. Stop first closes new-input acceptance, then processes accepted inputs and exits the protocol service.

When a shared physical Client session undergoes a detectable disconnect/rebuild, completed on_change outputs on that session resend their current setpoint once. A disconnect detected by another Unit, Poll, or CLI operation sharing that session also triggers this mechanism. It does not alter source-signal values or turn ordinary same-value updates into periodic sends; an unobserved internal reset of the remote device cannot trigger this synchronization.

Mapping and built-in Transformation capabilities

Section titled “Mapping and built-in Transformation capabilities”

A Mapping requires id/inputs/outputs; transformation defaults to identity, and reads declares additional dependencies explicitly. max_input_skew_ms defaults to 0 (no input-time-difference limit); when greater than 0, all valid inputs must satisfy that bound. On input invalidation, the output group retains its complete last valid group and propagates invalidity.

Constants and built-in Mapping chains depending only on constants are exempt from skew/TTL. Derived values containing dynamic inputs/reads still inherit the oldest source observation time; native callbacks are not assumed constant. Integer results produced by Linear or basic arithmetic from integer inputs must be exactly representable when converted to float64; otherwise, the output is marked transform_error and retains its last value. Fractions and floating-point calculations use normal floating-point rounding.

The same check covers abs/floor/ceil/round of integer inputs, integer sqrt results, integer magnitudes from hypot/cartesian-to-polar, and axial/zero-radius polar results. A floating-point calculation yielding an apparently integral value must also prove that integer result exact; otherwise, the complete output group is conservatively invalidated and its last values retained atomically. Arbitrary-precision mathematics is not promised. Ordinary fractions, trigonometric functions, and Float64 inputs still use floating-point calculation.

Exactness classification cannot depend on whether an approximate result has a fractional tail. For float wire encoding from integer sources, comparisons retaining multiplication and subtraction residuals establish the true quotient’s relationship to adjacent integers. Norms of integer inputs use exact squared relationships to compare adjacent integers. A truly integral result must preserve integer precision; a result strictly between adjacent integers retains normal fractional rounding, including ordinary or tiny fractional offsets. Conservative invalidation occurs only when the platform cannot prove the classification.

transformation Inputs → outputs Parameters and mathematical relationship
identity 1→1 Equivalent physical values with exact type/enum-domain checks; numeric units must match and be nonempty, or units_compatible:true must be explicit
duplicate 1→2 Copy one physical value to two outputs using Identity type/unit rules
linear 1→1 k=1/b=0, y=kx+b; for example,Celsius→Fahrenheit uses k=1.8,b=32
threshold / compare 1→1 Output must be bool; defaults are threshold=0, comparison=ge; supported comparisons are gt/ge/lt/le/eq/ne
math 1→1 Required function: sin,cos,tan,asin,acos,atan,log,log10,exp,sqrt,abs,floor,ceil,round
polar_to_cartesian 2→2 inputs=[r,phi],outputs=[x,y]; angles are in radians
cartesian_to_polar 2→2 inputs=[x,y],outputs=[r,phi]; phi=atan2 (y,x),in radians
sum/difference/product/ratio/hypot/atan2 2→1 Respectively x+y,x−y,xy,x/y,hypot(x,y),atan2 (x,y); x,y follow inputs order
custom N→M plugin is required and must have kind=transformation; input/output/reads sets and order must match the plugin declaration

Except for Identity/duplicate’s special type rules, built-in mathematical inputs are int64/uint64/float64. Invalid domains, division by zero, nonfinite results, and type-conversion overflow invalidate the output group. There is no expression syntax, function nesting, or Transformation LUT pipeline. Use Codec.special for common special raw values and plugins for complex calculations.

TxTask fields: id is required; choose message or a nonempty resources array. A Resource group must belong to the same Client Endpoint and all members must be writable. The group is written sequentially, without promising atomic remote execution across multiple requests. timeout_ms defaults to 1000 and must exceed0; it covers waiting for acceptance and completion. A timed-out task stops without fabricating completion.

mode Behavior
periodic (default) period_ms must exceed0; CAN may inherit Message.cycle_ms; Modbus requires an explicit period. Missed periods are not caught up in a burst
on_change Compares business values, actual encoding validity, and active MUX; excludes time/version/Counter/CRC. Destinations not yet accepted coalesce to the latest value; in-flight requests remain frozen
request Only for explicit CAN payload requests from a Processor; does not trigger periodically by itself. See plugin examples for complete rules

Each E2E entry has id/pdu/direction/config, with direction=check/protect and independent source/destination Contexts. profile defaults to counter_crc, with counter_sum/custom also available. Built-in CRC/SUM are configurable protection primitives and do not automatically claim compliance with an AUTOSAR Profile.

Each CAN Message supports at most one E2E configuration per direction. Multiple Checks or Protects are unsupported, including non-overlapping PDUs and mixed built-in/native configurations. One Check and one Protect may exist independently for the same message. Validation/loading returns the common error unsupported_gateway_feature, a message containing currently unsupported, details={}, and retryable=false; CLI exit code1, REST status422.

Built-in config fields are counter_start/counter_bits/counter_max/counter_initial/checksum_offset/data_id/polynomial/initial/xorout/width/reflect/data_id_length. Numeric defaults are0, width defaults to 8, and reflect to false; valid Counter and checksum layouts must be configured explicitly. Counter bits and checksum bytes are relative to the owning PDU; the built-in implementation supports width8/16/32, and parameters must match the width. Validate known algorithm parameters with independent vectors.

Custom E2E requires a kind=e2e plugin. Its config contains provider_id/profile_id/variant/context_size/data/owned_bits; the first four are unsigned integers and data/owned_bits are hex byte strings. provider_id must be nonzero, and owned_bits must equal the PDU length. Each set bit denotes a protection bit the plugin may write; business encoding owns all other bits. This reuses the existing E2E ABI, not the three Gateway plugin ABIs.

Each Fault has id/task/kind; task must be a CAN TxTask. Defaults are enabled=false and every=1/start=0/count=0, with count=0 meaning unlimited triggers. kind=drop/repeat/bypass/suppress/xor_byte/set_byte/freeze_counter/counter_jump/corrupt_checksum. byte_offset/mask/value are used according to the rule; value is an exact int64 string. mask defaults to 0, except counter_jump/corrupt_checksum default to 1. E2E field rules require the corresponding protection layout; use explicit payload faults when native E2E has no built-in layout. Conflicting flow-control rules cannot be combined in one opportunity.

Each Waveform has signal/kind; the signal’s producer must be waveform and kind=sine/ramp/square/step. Defaults are period_ms=1000/update_ms=10/phase_ms=0, with positive periods and update intervals, plus offset=0/amplitude=1/minimum=0/maximum=1. sine uses offset/amplitude; ramp/square use minimum/maximum. step requires nonempty points:[{time_ms,value}], starting at 0 with strictly increasing later times below the period. It has an independent update cadence and does not wait for the first external event.

Plugin fields: id/kind are required; abi_version=1; at least one project-relative DLL/SO path must be configured in windows/linux, and startup requires the current platform’s path. config defaults to {} and is passed unchanged to the plugin. kind=transformation/codec/processor/e2e. inputs/outputs/reads declare signal access permissions and static dependencies; Processors may also declare endpoints/send_tasks.

Processor trigger defaults to state, requiring at least inputs or reads. frame triggers on received frames from declared endpoints; periodic requires positive interval_ms. At least outputs or send_tasks must be declared; undeclared host-object modification is prohibited. A Source Codec must output exactly its Binding Signal. A Target Codec declares required inputs/reads, has empty outputs, and returns its encoding through raw_value. See plugin SDK examples for call order, Context, borrowed views, port state, atomic staging, and E2E-bit rules.

Item Hard limit
JSON / nesting 64 MiB /64 levels
Endpoint / Plugin 256 / 64
Signal / Codec / Mapping / Resource / Fault / Waveform 65,536 each; internal Codec/Processor nodes also count toward expanded Signal/Mapping limits
Binding 131,072
Message / PDU / E2E / TxTask 16,384 / 16,384 / 16,384 / 32,768
Each reference list 256; internal trigger inputs for Processor frame/periodic and Source Codec also consume input entries
Codec special / step points 65,536 per Codec /16,384 per waveform
Native E2E Context / config data 65,536 bytes each
Processor requests 16 per callback;64 queued and in-flight combined per instance
Paths Expanded paths shorter than 4,096 bytes, additionally constrained by the OS
Shared instance/memory budgets 16instances and 256 MiB across Simulation and Gateway; conservative host budget128 MiB per Gateway

These count limits do not bypass memory budgets; large projects may be rejected earlier. Memory allocated by native plugins is outside the host-controlled budget. load/validate errors do not partially replace existing instances; startup preparation failure reclaims only resources acquired by that attempt.


Source of truth: BMCLI 1.6.3 repository documentation. Run bmcli help <category> <action> --format=json for the exact contract of the installed version.