Protocols
A protocol decodes the data coming from a driver and
hands it to variables. Protocol settings are entered in the Settings
field in the key=value;key=value form; per-variable parameters go into
the Comm params field when a variable is assigned to the protocol.
Protocols with their own settings have the Settings field automatically
pre-filled with default values when added (the Pre-filled line under each
below). These are only defaults — always overwrite them to match your real
setup (e.g. masterId/slaveId for XCP, mode for Modbus). Protocols
with no settings of their own (Raw CAN Frames, JSON Signal Stream,
Simulation Signals) are configured only through per-variable parameters and
the Settings field stays empty.
The Comm params field is also pre-filled when a variable is assigned
to a protocol — with the protocol's own template containing placeholders
(e.g. canId="0x100" for Raw CAN). Always replace the placeholders with
real values. A faulty Comm param (missing mandatory parameter, wrong
format) removes the variable from communication: a warning appears in the
log and the variable simply stays empty — the protocol itself keeps
running. Unknown keys are ignored without any message; keys are
case-insensitive and values may be quoted.
The protocol must match the driver's data type — the usual pairs:
| Protocol | Driver |
|---|---|
| Raw CAN Frames | PEAK CAN Adapter |
| XCP on CAN | PEAK CAN Adapter |
| XCP on TCP | TCP Client |
Modbus Master (mode=rtu) | Serial Port (COM) |
Modbus Master (mode=tcp) | TCP Client |
Modbus Slave (mode=rtu) | Serial Port (COM) |
Modbus Slave (mode=tcp) | TCP Server |
| JSON Signal Stream | TCP Client |
| Simulation Signals | Simulation |
| Virtual Variables | Virtual Variables Host |
Project Configuration enforces this: protocols that cannot work with the selected driver are greyed out in the protocol list, with a tooltip explaining what they need.
Raw CAN Frames
Technical name (stored in the project): RawCanProtocol
CAN bus monitoring: maps standard (11-bit) CAN frames to variables by their identifier — no higher-layer parsing. Read-only; extended (29-bit) frames are ignored.
The protocol has no settings of its own.
Comm params pre-fill: canId="0x100";offset="0";byteOrder="le"
| Parameter | Default | Meaning |
|---|---|---|
canId | 0 | 11-bit CAN identifier in hexadecimal, e.g. 0x100 |
offset | 0 | index of the first frame data byte to read |
byteOrder | le | byte order: le (little-endian), be (big-endian) |
Beware: canId is interpreted as hexadecimal even without the 0x
prefix — canId=100 means 0x100 (decimal 256), not one hundred. The
value is not checked against the 11-bit range; an out-of-range id or a
typo (which silently becomes zero) only shows up as the variable never
receiving data. For byteOrder only the listed tokens count — anything
else silently means le. An offset past the end of the frame yields
all zeros.
Supports all numeric types of scalar variables including float and
double (the width comes from the variable's data type; missing bytes of
a short frame are zero-padded). Matrix, string and enum variables are not
served.
XCP on CAN
Technical name (stored in the project): XcpProtocol
A simplified XCP master (ASAM MCD-1 XCP) over CAN. Reads ECU memory
either by polling (SHORT_UPLOAD) or by DAQ — the ECU streams the
values itself (both modes are explained in
XCP polling, DAQ, and STIM
below, shared by both XCP variants). Operator writes go via SET_MTA +
DOWNLOAD, periodic stimulation of variables streams via STIM. No
block mode, no seed & key (a protected calibration disables
writes; a protected DAQ resource falls back to polling). Byte order and
address granularity are taken from the slave's CONNECT response.
Pre-filled: masterId=0x200;slaveId=0x201;extendedIds=false;timeoutMs=1000;daqTimestamps=slave
Protocol settings:
| Parameter | Default | Meaning |
|---|---|---|
masterId | — (mandatory) | CAN id of master → slave frames, hexadecimal |
slaveId | — (mandatory) | CAN id of slave → master frames; must differ from masterId |
extendedIds | false | true = both ids are 29-bit (extended) |
timeoutMs | 1000 | per-command response timeout in ms |
daqTimestamps | slave | time-axis source of DAQ samples: slave = ECU timestamps, master = PC receive time (see DAQ behaviour and diagnostics) |
Per-variable parameters are common to both XCP variants — see XCP polling, DAQ, and STIM.
CAN-specific behaviour:
- Values longer than 6–7 bytes (
Long/ULong/Double) are polled and written as a chain of several commands and the transfer is not atomic — if the ECU changes the value meanwhile, the result may be inconsistent. (DAQ does not have this problem: the ECU samples the values consistently at its event.) - A DAQ packet on classic CAN carries at most 8 bytes including its header, so one packet holds only a few small values — the protocol automatically spreads the variables over more packets per cycle.
- Only slaves with BYTE address granularity are supported (the common case); any other granularity is rejected and the protocol stays Faulted.
timeoutMsapplies per command; on a timeout the transaction is retried up to twice, so the worst-case wait is roughly three times as long.- Invalid protocol settings (missing/non-hex id, id outside the CAN
range,
timeoutMs≤ 0) put the protocol into the Faulted state at start with the cause in the log.
XCP on TCP
Technical name (stored in the project): XcpTcpProtocol
The same simplified XCP master over XCP on Ethernet (TCP transport): polling or DAQ reads, operator writes and STIM, byte order and limits taken from the CONNECT response. The ECU is the TCP server; QInsight connects to it through the TCP Client driver — the IP address and port therefore belong to the driver settings, not to the protocol.
Pre-filled: timeoutMs=1000;daqTimestamps=slave
Protocol settings:
| Parameter | Default | Meaning |
|---|---|---|
timeoutMs | 1000 | per-command response timeout in ms |
daqTimestamps | slave | time-axis source of DAQ samples: slave = ECU timestamps, master = PC receive time (see DAQ behaviour and diagnostics) |
Per-variable parameters are common to both XCP variants — see XCP polling, DAQ, and STIM.
TCP-specific behaviour:
- Ethernet allows large XCP packets, so every supported value (up to
8 bytes, including
Double) is transferred in one packet, atomically — no chaining as on CAN — and one DAQ packet typically carries a whole group of variables at once. - Timeouts of the TCP connection itself (
connectionTimeoutMs, reconnects) are configured on the TCP Client driver;timeoutMshere only covers the XCP command/response exchange.
Trying it without hardware: the open-source
XCPlite slave works as a free
test ECU on localhost. Two ready-made projects ship in the QInsight
Examples folder: XcpTcp_XCPlite_Daq01.qproj (a minimal DAQ-only
project — four variables streamed from the ECU) and
XcpTcp_XCPlite_Test01.qproj (DAQ measurement plus calibration writes).
Build the XCPlite hello_xcp example, run it, and open either project.
XCP polling, DAQ, and STIM
Both XCP protocols configure variables the same way and offer two ways of reading them (polling and DAQ) plus streamed writing (STIM). Which mode a variable uses is decided by the event it is bound to — nothing else in the configuration changes.
Per-variable parameters (the Comm params field):
| Parameter | Default | Meaning |
|---|---|---|
address | — (mandatory) | 32-bit ECU memory address, hexadecimal (0x…) or decimal |
addressExtension | 0 | address-space qualifier (decimal) |
direction | read | read, write, readWrite |
eventRef | — | name of the event the variable is bound to (mandatory for read/readWrite) |
The pre-filled address="0x0" is just a placeholder — always replace it
with the real address from the ECU description (typically the
ECU_ADDRESS and ECU_ADDRESS_EXTENSION attributes in an A2L file).
The value width and data type are taken from the variable itself, so
they are not entered here. Older projects may still contain size and
dataType keys — they are accepted and validated against the variable's
type; a mismatch is a configuration error.
Polling — the master asks
The default mode. The variable is bound to an ordinary periodic event; the master reads the address in the event's period (PC timing, one request/response per variable per cycle). Simple and universal — it works with every XCP slave — but each value costs a round-trip, so it does not scale to fast rates or many variables.
Event: Poll100ms (Period 100, Unit Milisec)
Comm params: address="0x39B68";addressExtension="1";direction="read";eventRef="Poll100ms"
DAQ — the ECU streams
In DAQ mode (Data Acquisition) the master only configures at connect what to sample and on which ECU event channel — from then on the ECU samples the values itself, at its own rate, and streams them without being asked. The gains over polling:
- speed — sampling at the ECU's own cycle (e.g. every 1 ms), far beyond what request/response polling can do,
- consistency — all values of one event channel are sampled at the same instant in the ECU, so they belong together,
- efficiency — no per-value requests; many variables cost almost nothing extra.
An ECU event channel is a point in the ECU program where the ECU
publishes fresh values — e.g. "every loop of the 1 ms task". The channel
numbers and names are listed in the ECU's A2L file (the EVENT entries)
or its documentation.
How to use DAQ: create a periodic event and fill in its Extra Params field (Home → Project Configuration → Events):
direction="DAQ";daqId="1"
| Extra Params key | Meaning |
|---|---|
direction | DAQ marks the event as an ECU event channel |
daqId | the ECU event channel number (0–65535, from the A2L) |
The two keys are only valid together. The event's Period does not drive anything in DAQ mode — it documents the channel's expected cycle and is checked against the value the ECU reports at connect (a mismatch logs a warning with both values).
Then bind variables to this event exactly like to any other — via
eventRef:
Event: Daq_mainloop (Period 1, Unit Milisec,
Extra Params: direction="DAQ";daqId="1")
Comm params: address="0x39B68";addressExtension="1";direction="read";eventRef="Daq_mainloop"
Variables bound to a DAQ event are streamed; variables bound to a normal periodic event keep polling. Both modes mix freely in one protocol — typical setups stream the fast measurements and poll (or write) the calibration parameters. Writes are not affected by DAQ at all.
DAQ behaviour and diagnostics
- Validation is strict. A typo in Extra Params never silently turns
DAQ off:
direction="DAQ"withoutdaqId(or the other way round), or a malformed entry, removes the variable from communication at project load with a log warning describing exactly what is wrong. An unknown key only logs a warning and is ignored. - The channel is verified at connect. The protocol asks the ECU for
the channel's name and cycle (when the slave supports the query),
writes the ECU name of the channel to the log — check it to be sure
the
daqIdpoints where you think — and compares the cycle with the event's Period. - Automatic fallback to polling. When the slave has no DAQ resource, protects it with seed & key, rejects the channel, or the DAQ setup fails, the affected variables automatically fall back to polling (the event's Period then drives the poll rate) and a warning explains why. The session keeps running either way.
- Timestamps of DAQ samples follow the
daqTimestampssetting. The defaultslaveplaces samples onto the time axis by the ECU timestamps — each sample then carries the exact sampling instant in the ECU, not the packet arrival time (a substantial difference with batched transfers). The timestamps are anchored to the PC clock and a slow ECU clock drift is compensated continuously; a sustained drift above ~0.3 % logs a diagnostic warning (usually a sign of a wrong clock source in the ECU). A slave without timestamps automatically falls back to the receive time. The valuemasterignores the ECU timestamps and samples always carry the PC receive time. - Overload is a warning, not a failure. When the ECU cannot keep up it drops samples and signals an overload; QInsight logs a warning and the measurement continues with the next samples. The same applies when the PC side cannot process a burst in time.
STIM — stimulation from the master
DAQ also has an opposite direction: STIM (stimulation). The master periodically streams the current values of writable variables to the ECU as a stream of data packets — no request/response per write. It suits smooth driving of ECU inputs, e.g. setpoints computed by a Python script or an operator slider.
The configuration mirrors DAQ — just with the opposite direction:
Event: Stim_mainloop (Period 10, Unit Milisec,
Extra Params: direction="STIM";daqId="2")
Comm params: address="0x39B70";direction="write";eventRef="Stim_mainloop"
- An event with Extra Params
direction="STIM";daqId="N"marks the ECU event channel that consumes the stimulation. Unlike DAQ, here the Period does drive the event: it is the interval at which the master transmits the packet stream (PC timing), so the event must be periodic with a positive period. - Variables are bound to the event via
eventRefand must havedirection="write"; only scalar variables can be stimulated. - Every period the current values of all the channel's variables are sent — the last operator or script write keeps streaming into the ECU until a new write replaces it.
Automatic fallback to direct writes: when the slave has no STIM
resource, protects it with seed & key, the channel fails validation, or
a value does not fit the STIM packet limits, the affected variables fall
back — with a warning — to plain on-change writes (SET_MTA +
DOWNLOAD) and the session keeps running. While stimulation is active,
writes of the stimulated variables travel exclusively through the
stream — no individual write commands are sent for them. DAQ, STIM, and
polling mix freely in one protocol.
Operator writes
Written values are entered in engineering units — the raw value for the ECU is computed by the inverse conversion of the presentation (see Writing values to the device); the protocol always sends raw to the ECU. Writes are blocked not only by a protected calibration but also when the slave offers no calibration resource at all.
Modbus Master (RTU/TCP)
Technical name (stored in the project): ModbusMasterProtocol
A Modbus master (client): periodically reads coils and registers and
writes values on operator request. Framing is rtu (CRC16, serial line)
or tcp (MBAP header).
Pre-filled: mode=rtu;unitId=1;timeoutMs=1000;retries=2
Protocol settings:
| Parameter | Default | Meaning |
|---|---|---|
mode | — (mandatory) | rtu (Serial Port), or tcp (TCP Client) |
unitId | 1 | slave/unit address |
timeoutMs | 1000 | response timeout in ms |
retries | 2 | number of request retries |
Per-variable parameters:
| Parameter | Default | Meaning |
|---|---|---|
registerType | holdingRegister | coil, discreteInput, inputRegister, holdingRegister |
address | — (mandatory) | zero-based start address, decimal or 0x… |
wordOrder | big | register order of multi-register values: big, little |
direction | read | read, write, readWrite; discreteInput and inputRegister are read-only |
eventRef | — | periodic event supplying the poll interval (mandatory for read variables) |
The register count follows the variable's data type: 1 (Byte, SByte,
UShort, Short), 2 (UInt, Int, Float), 4 (ULong, Long,
Double). The bit tables (coil, discreteInput) always occupy 1 bit
regardless of the type; any non-zero value = ON. Only numeric scalar
variables can be mapped (bool, string and enum cannot).
More master behaviour notes:
retriesis the number of retries on top of the first attempt (default 2 = up to 3 transmissions) andtimeoutMsapplies to each attempt separately.unitIdis decimal.addressmay be hex (0x64) but is shown in decimal (100) after the project is saved — it is the same value.registerTypealso accepts the shortened aliasesdiscrete,input,holding;wordOrderacceptsbe/le.- Each polled variable is read by its own separate request in the period
of its event;
eventRefmust name an existing periodic event. - Invalid Comm params remove the variable from communication with a warning in the log.
Matrix variables
The Modbus Master also transfers
Matrix variables (array/curve/map) — the
whole block as a contiguous register range starting at address:
- only the register tables are allowed:
holdingRegister(read and write) andinputRegister(read-only); the bit tablescoil/discreteInputare rejected for a matrix, - register count = matrix size in bytes / 2 (rounded up); reads use a single FC03/FC04 request, writes send only the registers of the changed cells (FC06/FC16),
- size limit: the matrix is never split into multiple frames — a writable matrix may span at most 123 registers (246 B), a read-only matrix 125 registers (250 B); the configurator enforces a uniform 246 B limit,
wordOrderdoes not apply to a matrix — the byte order is driven by the variable's Endianness (a register is 2 bytes, high byte first).
Modbus Slave (RTU/TCP)
Technical name (stored in the project): ModbusSlaveProtocol
A Modbus slave (server): serves variables as coils, discrete inputs, input and holding registers. A remote master reads the live variable values; a write from the master (FC 05/06/16) sets the variable value.
Pre-filled: mode=rtu;unitId=1;respondToAnyUnit=false
Protocol settings:
| Parameter | Default | Meaning |
|---|---|---|
mode | — (mandatory) | rtu (Serial Port), or tcp (TCP Server) |
unitId | 1 | the slave's own address |
respondToAnyUnit | tcp: true, rtu: false | respond regardless of the requested unit address |
Per-variable parameters are the same as for the master (registerType,
address, wordOrder); eventRef is not needed. Slave behaviour notes:
- For the read-only tables (
discreteInput,inputRegister) thedirectionmust stayread— any other direction is an error and the variable is not created. - The slave serves scalar variables only; Matrix variables are not mapped and a warning appears in the log.
- Each variable claims a contiguous address range; overlapping addresses are a startup error.
- A write from the master must cover the variable's whole range at once:
a multi-register value (
Float,Int,Double…) cannot be written register-by-register with FC06 — only with a single FC16 spanning the full range; a partial write is rejected as ILLEGAL DATA ADDRESS. Likewise a read touching an unmapped address (a gap between variables) is rejected as a whole. - The Default of
respondToAnyUnitin the table applies when the parameter is omitted — the pre-filled template however containsfalse, so for the TCP "respond to anyone" behaviour settrueyourself. Values aretrue/false(not1/0).
JSON Signal Stream
Technical name (stored in the project): JsonSignalProtocol
Decodes newline-delimited text lines with JSON objects
{"name": "...", "value": ..., "t": ...} into variable values. Read-only.
The optional t field (source time in seconds) is used to reconstruct
timestamps.
The protocol has no settings of its own. Per-variable parameters:
| Parameter | Default | Meaning |
|---|---|---|
id | variable name | signal name in the JSON name field (aliases name, signal, signalName) |
Supports numeric, string, bool, and enum variables. When several aliases
are given at once, the precedence is name > signal > signalName >
id. Signal names are matched case-insensitively. Unparsable lines,
messages without name/value and values not convertible to the
variable's type are dropped without any message — when a signal stays
empty, first check the exact name match in the name field.
Simulation Signals
Technical name (stored in the project): SimulDataProtocol
Generates simulated signals for testing without hardware — used
exclusively with the Simulation driver. Each variable is generated exactly
in the period of its event (eventRef), so different variables can run
at different rates. The signal shape is selected by the signal parameter:
| Signal | Shape |
|---|---|
step | staircase sine ±750, period 15 s (the value changes once per second) |
noisystep | sine ±1000, period 7.5 s, with ±50 noise |
walk1 | random walk around 0 (step ±0.5, mean-reverting) |
walk2 | random walk around 10 (step ±1.0, mean-reverting) |
walk3 | random walk around −5 (step ±0.3, mean-reverting) |
The walks (walk*) advance one step with every generated sample — a
variable with a faster event therefore wanders faster. Each variable owns
an independent generator instance (two variables with the same signal
run independently); the value is converted to the variable's type
(integer types are rounded and clamped to the type range). Only numeric
scalar variables are generated — string, bool, enum and Matrix are
skipped with a warning in the log.
The protocol has no settings of its own. Per-variable parameters:
| Parameter | Default | Meaning |
|---|---|---|
signal | by variable type | signal shape (see the table above) |
eventRef | — | periodic event defining the generation rate; a variable without it is not generated |
direction | Read | communication direction |
id | — | signal identifier (informational) |
The Comm params template pre-fills signal="step". Without signal the
shape is chosen by value type (int → noisystep, float → step,
otherwise walk1); an unknown signal value falls back to the same
type-based choice with a warning. multiplier from older projects is
ignored.
Virtual Variables
Technical name (stored in the project): VirtualDataProtocol
Carrier of virtual variables: variables whose values do not come from a device but are computed by Python scripts — typically filters, derived quantities and other computed signals. A variable assigned to this protocol behaves like any other communicated signal: it can be shown in controls, logged and replayed, and it counts towards the communicated-signal limit of the Free licence. Used exclusively with the Virtual Variables Host driver.
The protocol has no timer: a sample is born the moment a script writes
the variable (RawValue or EngValue). Every write = one sample,
timestamped at the moment of the write — even when the script writes the
same value again. Writes pass through a buffer, are published in write
order, and the script never waits for the rendering; when the script does
not write, no samples appear.
The protocol has no settings of its own. Variable parameters:
| Parameter | Default | Meaning |
|---|---|---|
id | variable name | signal identifier (informational) |
An on-change trigger of another script can be attached to a virtual variable to chain computations — just avoid a cycle (a script writing to the very variable whose trigger runs it). A complete walk-through with an exponential filter is the example Filtering a signal with a script.
Protocols are plugins — for a custom protocol see Custom extensions.

