Skip to content

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, else transport.host, else default_wican) is always tried first; fallback_order only sequences the rest.
  • --no-fallback disables it for a single command; transport.fallback: false disables it globally.
  • A wican-ws device is skipped as a fallback on a classic WiCAN (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 status runs 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_timeout is paid once per candidate, so it trades "never call a reachable device dead" against "skip a dead one quickly". The 5.0 default 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.

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_interval turns a silently dead wican-ws link into a real ConnectionError, 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, set 0 to disable.
  • stale_cycles_before_reconnect is 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.

transport:
  expected_responses: true   # default true

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.

Example

default_profile: my-car

devices:
  ap:
    host: "192.168.80.1"    # WiCAN AP (factory default)
  home:
    host: "192.168.1.100"   # device on your home LAN
default_wican: home

wican_model: classic     # regular / non-Pro WiCAN