canair investigate
usage: canair investigate [-h] <kind> ...
[UDS+CAN] Point this at an unknown signal and get one ranked table telling you
everything worth knowing about each of its bytes. Choose a domain kind:
uds a diagnostic PID (default) — per byte: mapped? / state F /
best co-polled anchor / unit guess (domain A). A bare
`canair investigate MCU 2102` is shorthand for this.
can an arbitration ID in a raw broadcast-CAN frame log (domain B) —
per byte: best cross-ID anchor + linear fit + unit guess.
Read-only: analyses captures/ only, never talks to the device.
positional arguments:
<kind>
uds Explain a diagnostic PID (domain A)
can Explain an arbitration ID in a raw broadcast-CAN frame log
(domain B)
options:
-h, --help show this help message and exit
canair investigate uds
usage: canair investigate uds [-h] [--min-r R] [--min-n N]
[--join-tol SECONDS] [--fill {auto,hold,none}]
[--max-hold SECONDS] [--all] [--bits] [--events]
[--dwell] [--field NAME] [--counters]
[--min-bits BITS] [--counter-width N]
[--unmapped-only]
[--independent-of ECU:PID:PARAM]
[--independent-of-file FILE] [--json] [--top N]
[--notation NAME] [--since WHEN] [--until WHEN]
[--date YYYY-MM-DD] [--today]
[--last-sessions [N]] [--last-session]
[--state STATE] [--label SUBSTR]
[ecu] [pid]
Point this at an unknown PID and get one ranked table telling you
everything worth knowing about each of its bytes — the fastest way to
start decoding.
For every varying data byte of ECU PID it reports, in one pass:
- mapped? whether a defined parameter already decodes this byte
(a verified param hides the byte by default; an
unverified [param?] mapping is shown as still-open work)
- stateF how cleanly the byte separates across power states
(sleep/acc/ready/charging) — high F = a mode/relay/thermal
signal a driving correlation would miss
- anchor the strongest-correlating known signal on another
co-polled ECU/PID (Pearson r + linear fit y=m·x+c)
- unit a physical-unit guess for that fit (e.g. raw-40 degC,
x1.609 mph->km/h)
Bytes are ranked strongest-anchor-first, then by state separation, so
the most decodable bytes float to the top. This bundles the manual
coverage -> discriminate -> correlate -> hunt loop into a single call.
ECU and PID are optional: give both for the full per-byte deep-dive,
an ECU (or a QUERY) alone to sweep its PIDs, or nothing to sweep the
whole profile. A sweep prints a ranked SUMMARY per PID (--top caps it)
rather than N full reports.
--counters switches to a different question — 'which bytes here only
ever go UP?' — sweeping multi-byte windows for odometers, operating-hour
tallies, power-cycle counts and uptime timers. Those are invisible to
the default view (a slow counter looks constant within one session),
so they need the whole capture history rather than a scoped window.
Read-only: analyses captures/ only, never talks to the device. Once a
byte looks promising, confirm the exact expression with `canair hunt
ECU PID --against ...` and write it with `canair pids upsert-param`.
positional arguments:
ecu Target ECU (e.g. MCU), or a QUERY (e.g.
"BMS:2101,2102"). Omit ECU and PID to sweep the whole
profile; give an ECU alone to sweep its PIDs.
pid Target PID (e.g. 2102). Omit to sweep the ECU.
options:
-h, --help show this help message and exit
--min-r R Only report an anchor when |r| ≥ this (default 0.6)
--min-n N Min aligned points (default 15)
--all Include bytes a verified param already maps (default:
hide only verified-mapped)
--bits Also analyse individual toggling bits (Bn:k) — the
body/status-ECU finder
--events Report each bit/byte rising/falling edge with its
timestamp, aligned to the nearest capture note (the
narrated event timeline)
--dwell Summarise each event bit/byte by how long it stays ON
— median on-duration + a momentary|sustained class.
Separates a briefly-pulsed bit (a door flicked open)
from one held for minutes (a hood left up), so
body/event signals are identifiable without capture-
note narration. Needs --keep-all/--keep-changes data
(keep:unique drops falling edges)
--field NAME With --events: track ONE defined param (a typed
enum/bitmask/struct date field) as a single logical
signal — emit one transition per change of its DECODED
value (e.g. {Mon 08:00}->{Tue 07:30}), not scattered
per-byte edges. NAME is a parameter of the target
ECU:PID.
--counters Hunt MONOTONIC COUNTERS instead: sweep every 1-4-byte
window x endianness for a value that only ever rises
across the capture corpus (odometer, operating-hours,
ignition/power-cycle count) or ramps and resets per
session (uptime). Ranks by bits of monotonic evidence
--min-bits BITS With --counters: minimum bits of monotonic evidence
(default 4). Each clean up-step with no down-step is 1
bit, so 8 bits ≈ 8 rises and no falls (1-in-256 by
chance). Lower it to surface sparse long-horizon
counters read only a handful of times
--counter-width N With --counters: widest byte window to test (default
4)
--unmapped-only With --counters: hide windows a VERIFIED parameter
already decodes. A window mapped only by an unverified
guess is kept (tagged [NAME?]) — monotonicity is often
the evidence that refutes such a guess
--independent-of ECU:PID:PARAM
Rank bytes that separate by state yet DON'T track this
driver signal — the 'active-but-independent' finder
(e.g. AC voltage: varies while charging but is
uncorrelated with charge current). Adds a driver-r
column and re-ranks by state separation weighted by
independence from the driver
--independent-of-file FILE
Like --independent-of, but the driver is an external
timestamp,value CSV (mutually exclusive with
--independent-of)
--json Machine-readable output
--top N In a corpus/ECU sweep (ECU or PID omitted): cap the
ranked summary to the top N rows (default 40; 0 = no
cap). Ignored for a single PID.
--notation NAME byte-index notation for output labels: wican
(default), isotp, torque, bix. Overrides the
display.byte_notation config key.
time joining:
--join-tol SECONDS Nearest-timestamp join window (default 5s)
--fill {auto,hold,none}
Carry a run-length (keep:changes) value forward to
reference instants it has no sample at: 'auto'
(default) fills only keep:changes sessions, 'hold'
forces it everywhere, 'none' keeps strict point
semantics
--max-hold SECONDS Cap how long a filled value may be carried (default:
until the next sample or the end of its recording
session)
scoping:
Restrict to captures within a date/time range (inclusive) and/or by session state (token-matched) or label substring. --since/--until accept a date (YYYY-MM-DD) or a timestamp (YYYY-MM-DD HH:MM[:SS[.ffffff]])
--since WHEN Only captures on or after this date/time (YYYY-MM-DD[
HH:MM:SS])
--until WHEN Only captures on or before this date/time (YYYY-MM-DD[
HH:MM:SS])
--date YYYY-MM-DD Only captures on this exact date (shorthand for
--since X --until X)
--today Only captures recorded today (shorthand for --date
<today>)
--last-sessions [N] Only the most recent N recorded sessions in scope (N
defaults to 1)
--last-session Only the most recent recorded session in scope (alias
for --last-sessions 1)
--state STATE Only captures recorded in STATE, matched by token and
widened by the profile's implies: hierarchy (--state
ready also matches DRIVING). Comma-separate
alternatives (--state ready,driving); repeat the flag
to require several at once (--state charging --state
parked)
--label SUBSTR Only captures whose session/capture label contains
SUBSTR (case-insensitive)
examples:
canair investigate MCU 2102 # rank unmapped + unverified-mapped bytes of MCU 2102
canair investigate MCU 2102 --all # include bytes a verified param already maps
canair investigate BMS # sweep every captured BMS PID (ranked summary)
canair investigate # sweep the whole profile (ranked summary)
canair investigate --counters # find every monotonic counter in the car
canair investigate BMS --counters # every counter across BMS's PIDs
canair investigate IGPM 22BC03 --bits # rank toggling bits (body/status-ECU work)
canair investigate IGPM 22BC03 --events # bit/byte edges aligned to the event timeline
canair investigate CLU 22B002 --counters # hunt monotonic counters (odometer / cycle count)
canair investigate BMS 2101 --counters --unmapped-only # only counters not settled yet
canair investigate BMS 2101 --state driving # only consider drive captures
canair investigate ESC 22C101 --min-r 0.8 # only show strong anchors (|r| >= 0.8)
canair investigate AAF 2181 --json # machine-readable output
# active-but-independent: rank bytes that separate by state yet DON'T track a
# named driver — the fingerprint of AC voltage vs charge current
canair investigate OBC 2101 --independent-of OBC:2101:OBC_DC_A --state charging
tip: no anchors found? widen scope (drop --state), lower --min-r, or grow the
capture set — an anchor needs another co-polled signal it can align to. For
a body/comfort PID with no co-polled partner, use --bits / --events (the
signals are toggling status bits, ranked by state separation + edge time).
canair investigate can
usage: canair investigate can [-h] --id ID
[--can-format {auto,asc,blf,csv,log,gvret}]
[--min-r R] [--min-n N] [--join-tol SECONDS]
[--fill {auto,hold,none}] [--max-hold SECONDS]
[--bits] [--json]
FILE
Explain one arbitration ID in a raw broadcast-CAN frame log: for every
varying data byte, report its strongest cross-ID anchor (Pearson r +
linear fit y=m·x+c) and a physical-unit guess, ranked strongest first.
The domain-B analogue of `investigate uds`: frames have no defined-param
mapping (signals live in signals/, decoded via Stage-4 tooling) and no
power-state metadata, so the report is anchor-centric. Bytes are
labelled 0xID:rN (raw-CAN space, no PCI). Read-only.
To pin the exact byte against a known reference use `canair hunt can`;
to see every relationship at once use `canair correlate can`.
positional arguments:
FILE Path to a raw broadcast-CAN frame log
(.asc/.blf/candump .log/.trc/GVRET .csv)
options:
-h, --help show this help message and exit
--id ID Arbitration ID to explain (e.g. 0x386)
--can-format {auto,asc,blf,csv,log,gvret}
Log format (default: auto-detect by extension)
--min-r R Only report an anchor when |r| ≥ this (default 0.6)
--min-n N Min aligned points (default 15)
--bits Also analyse individual toggling bits (rN:k)
--json Machine-readable output
time joining:
--join-tol SECONDS Nearest-timestamp join window (default 5s)
--fill {auto,hold,none}
Carry a run-length (keep:changes) value forward to
reference instants it has no sample at: 'auto'
(default) fills only keep:changes sessions, 'hold'
forces it everywhere, 'none' keeps strict point
semantics
--max-hold SECONDS Cap how long a filled value may be carried (default:
until the next sample or the end of its recording
session)
examples:
canair investigate can drive.blf --id 0x386 # rank each byte of 0x386 by best cross-ID anchor
canair investigate can drive.csv --id 0x220 --bits # include toggling bits
canair investigate can drive.asc --id 0x386 --json # machine-readable