Configuration¶
canair reads user config from ~/.config/canair/config.yaml
($XDG_CONFIG_HOME/canair/config.yaml). It's created on first run. View and edit
it from the CLI rather than by hand:
canair config show # config file locations + effective settings
canair config example # print config.example.yaml (the authoritative reference)
canair config get KEY
canair config set KEY VALUE # dotted keys create nested mappings
canair config unset KEY
canair config edit # open in $EDITOR
Authoritative source
Every key is documented with inline comments in config.example.yaml in the
repo root — print it anytime with canair config example. If this page and
that file ever disagree, the example file wins — it's kept next to the code.
Keys¶
| Key | Purpose |
|---|---|
default_profile |
Which profile to use when none is given. Overridden by --profile / CANAIR_PROFILE. Optional if only one profile is discovered. |
profiles_dir |
Extra directory to search for profiles (never committed). |
devices |
Named devices for the --wican flag: each alias has a host and optional per-device transport/port/bitrate (see below). |
default_wican |
Which device alias to use by default. |
wican_addresses |
Legacy flat alias: host map (host-only). Read only when no devices: block exists, and auto-migrated to devices: on first run. |
wican_model |
pro (default) or classic. classic makes canair cleanly refuse Pro-only features. |
check_for_updates |
true (default) or false. Disables the automatic once-a-day update check (also disabled by CANAIR_NO_UPDATE_CHECK). |
grid_region |
Charging-grid region for the physical-value scan: EU, UK, US, JP, CN, or AU (case-insensitive). Sets the mains-voltage / line-frequency bands (see below). |
display.byte_notation |
Default byte-index notation for analysis output labels, and for the byte ruler / signal byte-reference column in monitor and captures --diff/--step: wican (default), isotp, torque, or bix. Overridden per-command by --notation. |
transport |
Advanced: explicit CAN transport selection (see below). |
grid_region — physical-scan grid bands¶
The reference-free physical-value scan (canair hunt --physical /
canair investigate) flags a raw byte whose scaled value lands in a named
physical range. The mains-voltage and line-frequency bands depend on where
the car charges, not the car — the same EV charges from 230 V / 50 Hz in Berlin
and split-phase 120/240 V / 60 Hz in Denver — so they're set once per location
here (and apply across every profile), rather than in a shared vehicle profile.
Unset assumes EU (230 V / 50 Hz); the first time a physical scan runs, canair offers to set the region (a one-time prompt on a TTY, a single stderr note when piped). Presets:
| Region | Nominal | Line frequency |
|---|---|---|
EU / UK / AU |
230 V | 50 Hz |
CN |
220 V | 50 Hz |
US |
120 / 240 V split-phase | 60 Hz |
JP |
100 / 200 V | 50 Hz (east) + 60 Hz (west) |
The vehicle-axis bands (HV pack voltage, 12 V rail) are a fact about the car
model, so they live in the profile's
physical_bands instead. A profile's
physical_bands override has final say over the grid_region preset.
devices — named devices, one per line¶
Each alias maps to a device with a host (IP or hostname) and, optionally, its
own transport, port, and bitrate. This lets a multi-device setup bind each
device to the transport that suits it — e.g. a low-latency home LAN device to
slcan-tcp and a laggy cellular/VPN device to the device-side-ISO-TP wican-ws
(see cellular timeouts).
devices:
home:
host: "192.168.1.100"
transport: slcan-tcp # optional; overrides transport.type for this device
port: 3333 # optional
vpn:
host: "10.0.0.100"
transport: wican-ws
clone:
host: "192.168.0.10"
transport: elm327-tcp # generic WiFi ELM327 dongle (no WiCAN)
port: 35000
default_wican: home
Per-device values override the global transport: block; an explicit
--transport/--wican on the command line still wins over both.
Set them from the CLI:
canair config set devices.home.host 10.0.2.86
canair config set devices.home.transport wican-ws # validated: slcan-tcp | wican-ws | elm327-tcp
Legacy wican_addresses
The old flat wican_addresses: {alias: host} form still works when no
devices: block is present, and is auto-migrated into devices: on the
next run (comment-preserving; a one-line notice is printed). Once a
devices: block exists, wican_addresses is ignored — setting it warns.
Auto-fallback across devices¶
When the selected device is unreachable at connect time, canair tries the other configured devices instead of failing. It's on by default; a configurable probe timeout decides how long each candidate gets to answer.
transport:
fallback: true # default true
connect_timeout: 5.0 # seconds — per-device liveness probe
fallback_order: [home, vpn, ap] # optional; default = selected device, then the rest
reconnect_max_wait: 60.0 # seconds — bounded mid-session reconnect window
- The explicitly selected device (
--wican X, elsetransport.host, elsedefault_wican) is always tried first;fallback_orderonly sequences the rest. --no-fallbackdisables it for a single command;transport.fallback: falsedisables it globally.- A
wican-wsdevice is skipped as a fallback on aclassicWiCAN (it can't use that transport). - Set the order from the CLI with a comma-separated value:
canair config set transport.fallback_order home,vpn,ap. canair statusruns the same probe, so it reports the device a live command would actually use — it says ready when the selected device is down but another one answers, and names the device it fell back to.connect_timeoutis paid once per candidate, so it trades "never call a reachable device dead" against "skip a dead one quickly". The5.0default is sized for a mobile link: a cellular radio's idle-to-connected transition can burn a second or two before the SYN even leaves. Lower it on a LAN.
Mid-session reconnect & --wait¶
canair monitor also re-homes a session that drops mid-run, rather than
giving up: on a disconnect it re-probes the reachable same-transport devices,
reconnects, re-opens any sessions, and resumes — a --save recording simply
continues (the gap shows in the timestamps). By default this is bounded to
transport.reconnect_max_wait seconds (default 60.0); --wait makes it retry
forever (Ctrl-C to stop).
--wait also governs the initial connect for every live command: it blocks,
retrying indefinitely, and starts as soon as the device comes online — so
canair monitor @driving --save --wait waits for the WiCAN, then records the
moment it appears.
Mid-session re-home stays on the connected transport (raw↔raw / ws↔ws); the
initial connect can still cross transports via fallback_order.
Link-health watchdogs¶
Reconnecting only on a raised error is not enough: a session can stop being
usable while nothing raises at all. A half-open socket (mobile NAT eviction, VPN
re-key) keeps accepting writes forever, and a desynchronised ELM327 pipe keeps
returning perfectly well-formed replies — to the previous request. Both look
identical to "the car isn't answering", so the monitor would show every signal as
stale indefinitely until restarted by hand.
transport:
ws_ping_interval: 20.0 # seconds — wican-ws keepalive ping (0 = off)
stale_cycles_before_reconnect: 3 # all-stale poll cycles before reconnecting (0 = off)
ws_ping_intervalturns a silently deadwican-wslink into a realConnectionError, which the mid-session reconnect above already handles. The WiCAN's firmware does not claim WebSocket control frames, so its HTTP server answers the ping itself — no firmware support needed. Lower it on a flaky mobile link, set0to disable.stale_cycles_before_reconnectis the correctness backstop: after this many consecutive poll cycles in which nothing answered coherently, the monitor reconnects even though no error was raised. A negative response (NRC) counts as answering — it proves the request reached the ECU and its reply came back in the right slot. It is a cycle count, not a duration, so it scales with the poll rate.
On the ELM327 transports canair also realigns the pipe by itself before
escalating: a reply that fails its echo check triggers a drain-and-probe resync,
tallied as resync on the monitor's health line. A rising stale/resync count
is how a marginal link looks while it is still coping.
canair also measures the link (the TCP handshake on slcan-tcp, adapter-only
AT commands on wican-ws) and adds that allowance to its ISO-TP and per-request
budgets, so these are the only latency-related keys you should need to touch. See
Remote & cellular links for the whole
picture, including which transport to pick and what slcan-tcp costs in data.
Expected response counts (ELM327 transports)¶
An ELM327 adapter has no way to know a multi-frame reply has ended, so by default
it waits out its whole ATST ECU-response budget after the last frame just to be
sure another is not coming. That wait, not the car and not the network, is the
dominant per-request cost on wican-ws and elm327-tcp.
The protocol has an escape hatch: a data request with an odd hex length has its final nibble read as the number of response frames to expect, letting the adapter return the moment the reply is whole. Measured on a WiCAN Pro, this is about 4x faster per read (~206 ms → ~53 ms), and it also collapses the variance.
canair never guesses the count. The first read of a request goes out in the plain form and the count is learned from a reply that already passed its echo and ISO-TP length checks. Asking for too few frames is the one genuinely unsafe move — the frames the adapter did not wait for stay queued and would come back as the next request's answer — so a reply that does not match what was asked for makes canair realign the pipe, retry in the plain form, and stop optimizing that request for the rest of the session. The retry is not charged to the caller's retry budget: a wrong count can cost latency, never a reading.
Leave it on. Set it false to rule it out while debugging a misbehaving adapter,
or if a clone rejects the extra nibble in a way canair has not learned to detect.
It has no effect on slcan-tcp, where canair runs ISO-TP itself and already knows
when a response is complete.
The transport block¶
Transport is chosen explicitly, never auto-detected. type and host are
overridden per-command by --transport/--wican; port and bitrate are
config-only.
transport:
type: slcan-tcp # slcan-tcp (default) | wican-ws (Pro-only) | elm327-tcp (direct ELM327)
host: 192.168.3.2 # device host/IP (all transports)
port: 35000 # slcan-tcp (Pro 35000, classic 3333) / elm327-tcp (usually 35000); auto for slcan-tcp if omitted
bitrate: 500000 # slcan-tcp only; overrides all else (falls back to profile can_bitrate)
When transport is omitted, canair defaults to slcan-tcp using
devices/default_wican for the host. A per-device transport: (see above)
overrides transport.type for that device. See
Architecture for what the transports are.
The elm327-tcp transport talks to a generic ELM327 adapter (WiFi clones, or
the ELM327-Emulator) over a plain TCP
socket — no WiCAN, so its HTTP-only affordances (device mode/firmware, wican
subcommands) don't apply; reachability is a direct probe of the ELM socket port.
canair wican mode set MODE keeps transport.type in step with the device's
mode: switching to slcan sets slcan-tcp, switching to elm327 sets
wican-ws (it prints the old -> new change, or says it's already aligned).
Pass --no-transport to switch the device mode without touching the config.