Where to Look First

Start with the status text on the affected page and the logs. OCTAVE has no global error-notification layer, but individual views can show details on request. These are the main places to look:

  1. The Connection Log / status text on the relevant page — the OBD settings card shows each connection step and rfcomm / permission errors; the phone-mirror view shows the adb / authorization message.
  2. Settings → Device → Diagnostics card (diagnosticsManager) — a 2×2 grid: Refresh (last 200 log lines), Copy (last 400 lines to the clipboard), Export logs (copies the log files to the Downloads folder — the way to get logs off an Android tablet or a head unit without a shell) and Open folder (hidden on Android/iOS, where canOpenFolder is false). The log directory path is printed on the card.
  3. The rotating log files described next. A hung UI, a crash and a dropped adapter all leave a trace there even when the screen shows nothing.

Log Files, Crash Traces & Debug Mode

Log directory

OCTAVE writes to a logs/ subdirectory of the app data directory (SettingsManager::getAppDataDir()):

PlatformPath
Windows%APPDATA%\OCTAVE\logs\
macOS~/Library/Application Support/OCTAVE/logs/
Linux$XDG_CONFIG_HOME/OCTAVE/logs/, i.e. ~/.config/OCTAVE/logs/
AndroidgetAppDataDir() has no Android branch, so the Android build falls through the Linux rule into the app's private home directory. It is not browsable from a file manager — use Export logs on the Diagnostics card, which writes into Download/. On Android and iOS the message handler also forwards every line to Qt's default handler (stderr goes nowhere on a phone), so adb logcat / os_log show the same messages.

Log files

FileLevelMax sizeBackupsNotes
octave-cpp.logINFO+5 MB3src/util/logger.cpp, custom size-based rotation
octave-cpp-error.logWARNING+2 MB5also the raw crash descriptor for the signal handler
octave-cpp-debug.logeverything10 MB2only with --debug

Total disk is bounded at roughly 32 MB without debug, ~62 MB with it.

Line format

# file and stderr (millisecond timestamps, Qt category in the module column):
YYYY-MM-DD HH:MM:SS.zzz | LEVEL   | octave.elm327 | obdmanager.cpp:1772 | message

Each manager logs through its own QLoggingCategory: octave.settings, octave.media, octave.network, octave.phonemirror (+ octave.phonemirror.client for the scrcpy-protocol client), octave.androidauto (+ octave.androidauto.session and octave.androidauto.connection for the built-in receiver), octave.embeddeddhu, octave.audioanalyzer, octave.berryimu, octave.gesture, octave.esp32volume, octave.dashboards, octave.diagnostics, octave.ui_watchdog and octave.elm327 (protocol transcript and the Android BLE bridge lines). The exceptions are obdmanager.cpp, clock.cpp and volumecontroller.cpp, which log through plain qDebug()/qWarning() and therefore appear under the category default — so there is no octave.obd category to filter on; look for the [OBD Worker] message prefix instead. With --debug the filter rules are *.debug=true, qt.*.debug=false; without it *.debug=false, so category-debug output costs nothing.

Crash traces

  • Unix: a signal handler for SIGSEGV, SIGABRT, SIGFPE, SIGBUS and SIGILL writes ==== FATAL SIGSEGV - backtrace (addresses; symbolise with addr2line against this build) ==== plus backtrace_symbols_fd() frames to octave-cpp-error.log and stderr, then restores the default action and re-raises so the OS still produces a core / tombstone. The addresses are only meaningful against the exact binary that crashed. The handler never touches Qt (every log line is already flushed as it is written, so it has nothing to flush); a qFatal goes through the message handler first, which flushes the files before Qt aborts, and a normal exit flushes them last in the aboutToQuit cleanup. Windows: SetUnhandledExceptionFilter writes the exception code, address and a CaptureStackBackTrace frame list.
  • SIGTERM / SIGINT (launcher scripts, systemd, Ctrl-C) on desktop Unix are turned into app.quit() through a self-pipe so the aboutToQuit cleanup runs and child processes / adb forwards are not orphaned (src/main.cpp).

UI watchdog: "UI thread blocked for N s and counting"

A watchdog thread (src/util/uiwatchdog.cpp) posts a ping to the GUI thread every 2 s; if the GUI thread has not answered within 5 s a WARNING is logged under octave.ui_watchdog, once per stall, followed by UI thread responsive again after N s when it recovers. A frozen head unit therefore leaves a timestamped trace even though nothing was drawn. Look at the lines immediately before the warning to see which manager was busy; the watchdog is stopped before Qt tears down the QObjects so a shutdown never shows as a stall.

Enabling debug mode

./build/octave --debug      # debug logging (--smoke is also available)

Creates the -debug.log file with DEBUG-level output. Useful for: the ELM327 TX/RX transcript ([OBD Worker] TX/RX lines with round-trip times at debug level under octave.elm327), serial data from the ESP32 knob, settings load/save, media library scanning and tag extraction, and the phone-mirror adb / server launch sequence.

Installation Issues

These concern installing and starting a packaged build. Build-time problems are on the Building page.

Windows

ProblemSolution
"DLL not found" launching the shipped .exeThe current installer bundles the VC++ runtime DLLs and has no registry warning. Reinstall the complete package if files are missing; for an older or hand-built copy missing MSVCP140/VCRUNTIME140, see Build Troubleshooting.

macOS

ProblemSolution
"App is damaged" Gatekeeper error on the .dmg buildThe workflow does not configure Developer ID signing or notarization. Check the downloaded package and follow Apple's Gatekeeper guidance; a damaged-app message can also indicate modified or corrupted files.

Linux

ProblemSolution
Missing libxcb / libGL libraries (a binary built from source)Qt's xcb platform plugin and the renderer need the host's XCB, GL and audio libraries. On Debian/Ubuntu the core set is libxcb-xinerama0 libxcb-cursor0 libxkbcommon0 libgl1 libegl1 libpulse0; the full XCB list is under Display.
AppImage won't runchmod +x OCTAVE-<version>-linux-x86_64.AppImage; if FUSE is missing, sudo apt install libfuse2 or run ./OCTAVE-<version>-linux-x86_64.AppImage --appimage-extract && ./squashfs-root/AppRun.
AppImage from a GitHub release fails with a glibc error on an old distroCI builds on Ubuntu 22.04; older glibc cannot load it. Build locally instead (Building → Linux).
Locally built AppImage starts, no window appearsThe QML tree is not at usr/frontend/ next to usr/bin/octave; see AppImage structure.

Display & Rendering Issues

XCB platform plugin errors (Linux)

Error
qt.qpa.plugin: Could not load the Qt platform plugin "xcb"

Install the X11/XCB libraries Qt's xcb plugin loads (Debian/Ubuntu package names):

sudo apt install libxcb1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
  libxcb-randr0 libxcb-render0 libxcb-render-util0 libxcb-shape0 libxcb-shm0 \
  libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxcb-util1 \
  libxkbcommon0 libxkbcommon-x11-0

The AppImage ships its own platform plugins and sets QT_QPA_PLATFORM_PLUGIN_PATH in its AppRun, but host libraries and a working display are still required. Run with QT_DEBUG_PLUGINS=1 to identify missing dependencies or plugin conflicts, and check DISPLAY/WAYLAND_DISPLAY.

Wayland vs X11

Qt picks the platform plugin from the session. To force one:

export QT_QPA_PLATFORM=xcb       # force X11 / XWayland
export QT_QPA_PLATFORM=wayland   # force Wayland
./build/octave

The dev screenshot and recording tools do not care which one is active: they grab the window from inside Qt (grabWindow()) rather than through the compositor — see Development → Screenshots.

Black UI, missing icons or album art

The nav icons and media controls are SVGs wrapped in ColorOverlay and the album art uses MultiEffect / DropShadow — GPU shader effects that need a real GL context. In some headless configurations the rendering backend cannot provide those effects, so an offscreen smoke test does not verify their appearance. On a real display, a black window usually means a broken GL stack: try QT_QUICK_BACKEND=software (slow but reliable) and check the log for module "..." is not installed, which means the Qt build lacks QtQuick.Effects / Qt5Compat.GraphicalEffects (Building).

Nothing appears at startup

  • QML import path: the binary looks for frontend/ next to itself, then one level up; running a binary copied elsewhere logs Could not locate frontend/ directory.
  • Controls style: src/main.cpp forces the Basic style (QQuickStyle::setStyle("Basic")) because the sliders are fully custom-drawn, so setting QT_QUICK_CONTROLS_STYLE does not change it.
  • Run with --debug and read the QML warnings in the debug log.

UI scale wrong

The uiScale setting ranges 0.5–2.0 in 0.05 steps (Settings → Display, or edit settingsConfigure.json; an old default of 0.6 is migrated to 1.0 on load). On top of it src/main.cpp computes screenAutoScale from the shorter screen edge divided by 720, clamped to 0.4–3.0, so a portrait-reporting Android tablet no longer gets a 3× factor.

Audio Issues

No or very quiet audio

  • The volume slider is mapped through a quadratic curve, (volume/100)^2.0 (src/managers/volumecontroller.cpp): 50 % on the slider is 25 % linear gain, 10 % is 1 %. Low slider values are genuinely quiet.
  • Mute: volumeController.muted (the command server's get_media_status reports it as muted). toggleMute() ignores a press within 150 ms of the previous one, so a bouncing ESP32 button cannot double-toggle. Turning the volume up from any input unmutes.
  • Phone-mirror ducking: while the phone is producing sound, local playback is ducked to scrcpyAudioDuckLevel (default 0.1) and released 1.5 s after the phone goes quiet (scrcpyAudioDuckEnabled, default on). If music seems to drop out during mirroring, this is why.
  • Verify the output device in the system mixer.

PulseAudio / PipeWire (Linux)

pactl list short sinks             # is there a sink at all?
pactl info                         # identifies the audio server (PulseAudio or PipeWire compatibility)

Media backend

OCTAVE assumes Qt Multimedia's FFmpeg backend: the media manager passes probe-size / analyzeduration hints to the player, and the aarch64 AppImage deliberately ships without Qt's GStreamer plugin (Building). OCTAVE does not set QT_MEDIA_BACKEND or any GStreamer variable; if you set one yourself and playback breaks, unset it.

Playback stalls, a track keeps skipping, or playback stops by itself

The media manager recovers like this: an invalid or stalled file is retried at most 2 times, then skipped; after 10 consecutive unplayable tracks (or the playlist length, if shorter) playback stops and the source is cleared rather than looping forever. Check the log for the file names; the usual cause is an unsupported container. Extensions scanned: .mp3 .m4a .mp4 .flac .ogg .opus .wav (.mp4 because an audio-only MP4 is the same container as .m4a).

OBD Connection Issues

Read the Connection Log on Settings → OBD first: every init step, rfcomm command and error the manager sees is appended there. The status dot and label above it follow connectionStatusChanged, and get_connection_status() returns the same vocabulary (initially "Disconnected"). The colour follows the manager's connectionState: Connected (accent); Connecting (warning) for Connecting, Reconnecting, Stale Connection and the Android BLE steps (Connecting BLE..., Discovering services..., Initializing ELM327..., Querying PIDs...); No Vehicle (info); Disconnected (grey); and Failed (error) for Device Not Found, Connection Failed, Error, No MAC Address, BLE connect failed, BLE error and anything else.

Which port / address to enter

The single field (obdBluetoothPort, placeholder "MAC address, /dev path, or COM port") accepts a Bluetooth MAC, a device path or a COM port. Left empty, the default is /dev/rfcomm0 on Linux, COM3 on Windows, /dev/tty.OBD on macOS, and nothing on Android (a MAC is required). While disconnected, the OBD manager rescans every 10 s: Linux globs /dev/rfcomm*, /dev/ttyUSB*, /dev/ttyACM* (/dev/ttyS* is excluded as a false-positive source); Windows filters serial-port descriptions for bluetooth / obd / elm / serial / usb; macOS looks at tty.OBD*, tty.Bluetooth*, cu.*, *ELM*. Note the OBD scanner does not exclude the ESP32 volume knob's USB serial port; the knob's own manager does exclude the OBD port.

Serial port permissions (Linux)

sudo usermod -aG dialout $USER     # log out and back in, then: groups | grep dialout

Bluetooth ELM327 pairing (Linux)

The Linux adapter picker uses direct RFCOMM sockets through connect_direct(); this path does not need a privileged /dev/rfcommN binding. A bare MAC entered through connect_to_adapter() uses the older rfcomm bind path. Pair and trust the adapter in the picker or with bluetoothctl. If using a serial-node connection, you can bind manually and enter the resulting device path:

sudo rfcomm bind 0 XX:XX:XX:XX:XX:XX
ls -la /dev/rfcomm0

"rfcomm bind failed: rfcomm binary not found" in the Connection Log means bluez-utils (Arch) / the package that ships rfcomm is missing; the log also tells you to bind once with sudo and enter /dev/rfcomm0.

Connection is slow or times out

  • The connection runs on a dedicated worker thread (OBDConnectionWorker on a QThread) so the UI never blocks; progress is reported through connectionProgress / connectionDetail.
  • The connection timeout is 5 s by default and is set at runtime through obdManager.set_connection_timeout(seconds), clamped to 5–60 s (there is no obdConnectionTimeout settings key). The worker uses it for the vehicle-verify wait (the RPM probe after init) and fixed per-command timeouts otherwise (300–1500 ms for init commands, 500 ms per poll).
  • obdFastMode (default on): the worker polls with a 0 ms interval instead of 50 ms, and asks the adapter for a single reply per PID (010C1), so it doesn't wait out its timeout for more ECUs; on a CAN car it asks for up to six PIDs per request. Turn it off for an adapter that misreads that.
  • The last successful protocol is not cached: every connect sends the fixed init sequence ending in ATSP0 (auto-detect).
  • obdAutoReconnectAttempts is 0–10 and defaults to 0 = off (shown as "Off" on the slider). With 0, the active retry loop is off, but the 10 s passive scan remains: a saved direct rfcomm:// address can be retried on each scan. Serial-node discovery retries when the detected list changes. With 1–10 a failed connect is retried that many times with a min(10, 2 + 2n) s backoff before falling back to the passive scan.

"Adapter stopped responding" / a dead port pegs a CPU core

An rfcomm port that has gone away can look readable while delivering nothing. The worker guards against spinning on it: non-productive waits are paced to ~10 Hz; after 50 consecutive "readable but zero bytes" polls the worker logs Port reports readable but delivers no data; treating as disconnected, releases the port and emits connectionLost("Adapter stopped responding"); every failed init and the no-vehicle path also release the port so no read notifier stays armed; a USB unplug (ResourceError / DeviceNotFoundError) is treated as connection lost.

"No Data" for a parameter

  • The OBD manager queries the adapter's supported-PID bitmaps (ranges 00/20/40/60/80/A0/C0/E0) automatically 2 s after every successful connect; the OBD settings page shows the scan progress. There is no manual "Scan Vehicle" button.
  • The parameter catalog (src/managers/parametercatalog.cpp, served by frontend/OBDParameterModel.qml) lists 129 entries: 121 vehicle/adapter entries (including the IGNITION_TIMING alias and extended values) and 8 BerryIMU-derived values. The base decoder table has 82 Mode-01 entries plus adapter voltage (ELM_VOLTAGE, ATRV); the extended table adds 37 values from 23 PIDs. These are supported by the software, not a promise that a vehicle provides every value. Parameter toggles apply live without a reconnect. Unsupported parameters are filtered by vehicle support and the active view's polling demand; see Which PIDs Get Polled.
  • Many parameters only report with the engine running.

Data watchdog: "Stale Connection" / "No data received, reconnecting..."

While connected and at least one parameter is being watched, the OBD manager checks every 5 s whether any data arrived in the last 30 s (DATA_WATCHDOG_TIMEOUT_MS = 30000). If not, it logs the messages above, tears the connection down and reconnects after 100 ms. A watchdog warning can reflect the vehicle, adapter, or transport, so inspect the protocol log before diagnosing the cause. Android uses a separate per-PID watchdog; it does not start this desktop timer.

Reading the ELM327 transcript

TX/RX lines with round-trip times are logged at debug level under octave.elm327 (run with --debug and read octave-cpp-debug.log); short responses and decoder exceptions are warnings and always logged.

Black box says "unavailable" / Trip History stays empty after a drive

The OBD Home tile reads "Black box unavailable", the Black Box card in OBD settings shows the error under Status, and octave-cpp.log has a octave.tripstore warning such as Cannot open .../history/blackbox.sqlite: .... The recorder then records nothing rather than pretend. Common causes: the config directory is not writable or the disk is full (the error names the path); or a build older than the bundled SQLite (third_party/sqlite/) running on a Debian / Ubuntu box without the libqt6sql6-sqlite package, which showed as Driver not loaded. Current builds compile SQLite in and need no package; for an old build install that package and restart OCTAVE. See OBD Manager → Black Box.

Android OBD (Bluetooth / BLE)

Android

The APK connects to ELM327 adapters over Bluetooth through android/src/org/octave/app/OctaveOBDBridge.java (Qt's QLowEnergyController is deliberately bypassed). The manifest declares BLUETOOTH_CONNECT, BLUETOOTH_SCAN (neverForLocation, so no location permission is needed on Android 12+) and BLUETOOTH_ADVERTISE; ACCESS_FINE_LOCATION is only declared up to API 30.

SymptomCause / fix
Status "Requesting Bluetooth permission — tap Allow, then tap Connect again"First connect on API 31+: the bridge calls requestPermissions() and returns; answer the system dialog and press Connect again.
Status "Bluetooth permission denied", detail "Grant Bluetooth in Settings -> Apps -> OCTAVE -> Permissions"The permission was refused. Grant it in the system app settings. The permission flow lives entirely in the Java bridge now (OctaveOBDBridge.connect() requests BLUETOOTH_CONNECT); Qt's QBluetoothPermission is no longer involved.
Nothing happens with an empty port fieldAndroid requires the adapter's MAC address; there is no default and no serial scan. Once Bluetooth permission has been granted (first Connect tap), the adapter list on the OBD settings page fills with the phone's paired devices as "<name> · <MAC>" (OctaveOBDBridge.bondedDevices()) — pick the ELM327 there instead of typing the address. An empty list means the permission is still missing or Bluetooth is off.
Where are the BLE logs?The Java side queues I|/W| lines; the OBD manager drains up to 50 per 50 ms poll and re-emits them as [BLE] ... under octave.elm327, so they land in octave-cpp.log, the Connection Log and the Diagnostics export. No adb logcat needed.
Update refuses to installSigning key mismatch (INSTALL_FAILED_UPDATE_INCOMPATIBLE). Releases up to 0.9.3 each used a throwaway key, and a local build uses your own key. Uninstall org.octave.app once, then install; from 0.9.4 on, releases update in place (Building → Android).

Phone Mirror Issues

Desktop mirroring needs adb, the phone server jar, and libavcodec for decoding; the full design is on the Phone Mirror page. Symptoms specific to recent behaviour:

SymptomCause / fix
"adb not found: …" (the rest says how to get it: in a source checkout python3 scripts/fetch_platform_tools.py; on Linux sudo apt install adb or Arch's android-tools; on Windows / macOS reinstall, since releases bundle adb)OCTAVE looks for a bundled adb first (platform-tools/ next to the binary, tools/platform-tools/<os>/ in a checkout, Contents/Resources/platform-tools/ in the bundle), then PATH, then common SDK paths. On ARM boards Google ships no adb; the distro's (sudo apt install adb) works over USB.
"adb is too old for wireless pairing (needs platform-tools 30 or newer)"The adb found has no pair / mdns commands, e.g. Ubuntu 22.04's /usr/bin/adb 28.x. OCTAVE prefers any adb that reports platform-tools 30+ and check ~/Android/Sdk/platform-tools/adb before /usr/bin/adb. On an ARM64 board, an x86-64 SDK adb runs through box64 binfmt; symlink it into PATH (for example ~/.local/bin/adb) or leave it at ~/Android/Sdk/platform-tools/adb. USB mirroring still works with the old adb.
"Phone is connected but USB debugging is not authorized. Unlock the phone and tap 'Allow' on the USB debugging prompt."adb devices shows unauthorized. Unlock, accept the prompt (tick "always allow"); if the prompt never appears, unplug and replug.
Picture freezes for a moment when the cable is bumped, then continuesWorking as designed: for virtual-display sessions the server keeps the session alive for octave_persist_ms = 60 s after the socket drops; the client reattaches to the same session within 55 s with a 4 s deadline, and the view holds the last frame and shows a "Reconnecting to phone…" badge only after 2 s. If the attach fails or the window expires ("No client reconnected within N ms" in the server log) a fresh session starts.
Mirror goes dark when the phone screen is off / shows a black frame with a status barThe phone-side MirrorKeeper reports sleep state; while asleep every incoming frame is dropped and the last good one is held. A stride-2 luma test over the Y plane detects black frames, with a 5 % lenient fraction (a solid status strip is 3–4 % of samples) for 3 s around a reattach.
Cannot get the phone back after it went dark on the dashThe pick-up path: a power press forces the panel back on (relightOnWake), a second press within the 10 s pickup window (the 8 s screen grace plus 2 s) marks the phone as in use, and waking to an unlocked phone counts as in use.
Music ducks while mirroringIntentional: see Audio. The phone's own volume is never changed; OCTAVE applies its own gain (scrcpyAudioGain, default 2.0, soft-limited).
Server refuses to start: version mismatchThe jar in tools/phone-server/ and the client (kServerVersion in src/phone_mirror/scrcpyclient.h) must agree on 3.3.4-octave; rebuild the jar after editing phone_server/src (Building).
Wi-Fi: the phone scans the QR code but nothing happens / "The phone did not scan the code within 120 s"Phone and OCTAVE must be on the same network; client isolation on guest networks blocks the mDNS discovery and the connection. A phone-hosted hotspot does not work: Android only enables Wireless debugging while the phone is a Wi-Fi client, so let the OCTAVE machine host the access point (Mirroring over Wi-Fi). Tap "Show QR code" for a fresh code after it expires.
Wi-Fi phone does not come back after a drive / phone rebootWireless debugging switches itself off when the phone leaves the network and picks a new port when switched on. Turn it back on (there is a quick-settings tile); OCTAVE retries the remembered address every 5 s and follows the new port over mDNS.
Mirror stutters over Wi-FiLower Wi-Fi bit rate in Settings → Accessories → Phone Dock → Picture Quality (default 8 Mbps; USB 16 Mbps). Prefer 5 GHz; a 2.4 GHz link shared with Bluetooth audio in a car is marginal. Plugging in a cable switches the next session back to USB.
phoneMirrorManager.nativeAvailable is false (desktop)The desktop check requires libavcodec, a server jar, and adb. Check the installation diagnostic and bundled files first; if the decoder was omitted at build time, rebuild with FFmpeg development packages. Mobile builds deliberately report mirroring unavailable.

Android Auto Issues

These are for the built-in backend (Settings → Accessories → Phone Dock → Android Auto → Head unit = Built-in); setup and the meaning of every state are on the Android Auto page. The page keeps retrying on its own (every 3 s while it waits for something, 8 s after an error) until its close button is tapped, so after fixing the cause there is nothing to restart in OCTAVE. The status line under the page title, and the Status row on the settings card, give the exact reason; the log has it under octave.androidauto.

SymptomCause / fix
No Head unit choice on the Android Auto settings cardThe build has no built-in receiver (androidAutoManager.nativeAvailable is false): it was built without OpenSSL or FFmpeg, or it is not a desktop build. Rebuild with both (OCTAVE_HAVE_NATIVE_AA); only the Google DHU backend is available until then.
"Waiting for the phone" / "Connect your phone with USB debugging on (or pair it for wireless debugging in Phone Mirror)"adb devices lists no usable phone. Plug it in with USB debugging on and accept the authorization prompt, or pair it for wireless debugging in Phone Mirror; the adb symptoms under Phone Mirror Issues apply unchanged.
"adb was not found (it comes with OCTAVE's platform tools, or the Android SDK)."The same adb search as Phone Mirror found nothing: bundled platform-tools/, then PATH, then the common SDK paths. In a source checkout run python3 scripts/fetch_platform_tools.py; on Linux the distro's adb works too. Retried every 8 s.
"Waiting for the phone's head unit server"The phone is connected but nothing listens on its port 5277. On the phone: Android Auto settings → ⋮ → Start head unit server. No ⋮ entry: developer mode is off; tap Version in Android Auto's settings about 10 times first. Recent Android Auto versions (17.9 checked) do not let adb start the server, so it has to be tapped on the phone.
"the phone's head unit server did not answer (restart it: Android Auto settings > Stop / Start head unit server)"The server accepted the connection but never answered within 10 s. This happens after a session was cut off without a bye-bye at the wrong moment: the server keeps accepting TCP connections but stops answering. On the phone, ⋮ → Stop head unit server, then Start head unit server; OCTAVE reconnects within seconds. OCTAVE always ends with a bye-bye (close button, app quit) to avoid this.
"Android Auto needs a certificate" (state needsCertificate)No head-unit certificate was found. OCTAVE ships none; put a PEM certificate and its key in the folder the status line names (<config dir>/android-auto/headunit.crt and headunit.key) or point androidAutoCertPath at the certificate. "No private key next to the head-unit certificate (looked for …)": the file at androidAutoCertPath holds no key and the sibling .key file is missing. Picked up on the next retry, within 3 s. See The head-unit certificate.
"the head-unit certificate is not a PEM certificate" / "OpenSSL refused the head-unit certificate" / "no private key found for the head-unit certificate" / "the private key does not match the head-unit certificate"The files exist but OpenSSL can't use them: not PEM (convert DER with openssl x509 -inform der), a key for another certificate, or a passphrase-protected key (not supported: OCTAVE has no way to ask for the passphrase). The log line carries OpenSSL's own reason.
"TLS handshake failed: …", or "the phone closed the connection" right after connectingThe phone refused the head unit's certificate. Android Auto only accepts a client certificate that chains to Google's head-unit certificate authority; a self-signed or otherwise unrecognised one is rejected during the handshake. Phone logcat (adb logcat | grep -i CAR.GAL) shows the phone's side.
"the phone refused protocol version 1.6"The phone's Android Auto no longer speaks the protocol version OCTAVE asks for. Note the Android Auto version from the phone's app info and report it; the receiver was checked against 17.9.
"the phone stopped answering" / "Android Auto ended (…); reconnecting..."No ping answer for 10 s (a dropped Wi-Fi or USB link, the phone asleep or killed), or the phone ended the session. OCTAVE reconnects on its own while the page is wanted.
Picture but no soundThe log says "no audio output device; Android Auto audio will not play" or "could not start audio output": check OCTAVE's audio output device (Settings → Media) and its volume / mute, which Android Auto audio follows. The phone's own volume is not used.
Google Assistant / voice commands hear nothingExpected for now: the microphone channel is announced but OCTAVE sends no audio on it.
Video stops when leaving the pageIntentional: a hidden page tells the phone to stop sending video; audio continues and the picture returns when the page is shown again. The close button (X) ends the session.

Hardware Sensor Issues

I²C permissions and bus

sudo usermod -aG i2c $USER
sudo raspi-config                  # Raspberry Pi: Interface Options > I2C > Enable
i2cdetect -y 2
# Expected: 0x6A (LSM6DSL), 0x1C (LIS3MDL), 0x77 (BMP388), 0x73 (PAJ7620U2)

The default bus number is the constant I2C_BUS = 2 (src/managers/berryimumanager.cpp, src/managers/gesturemanager.cpp) — there is no setting. The managers open /dev/i2c-2 directly with ioctl(I2C_SLAVE) (Linux only).

"Device not found" errors

ErrorCauseSolution
/dev/i2c-2 not foundI²C not enabled or the sensors are on another busEnable I²C; move the sensors to bus 2 or change the constant and rebuild/restart.
Permission denied on /dev/i2c-2User not in the i2c groupsudo usermod -aG i2c $USER, then log out/in.
Expected chip ID 0x7620, got 0x0000PAJ7620U2 not respondingCheck wiring, 3.3 V supply, pull-ups.

Reconnection behaviour

BerryIMU and gesture managers retry up to 5 times with linear backoff (5, 10, 15, 20, 25 s), then give up. Both skip the retries on a permission error (it cannot fix itself): when open() fails with EACCES / EPERM they log permission denied on /dev/i2c-2 - add the user to the i2c group; not retrying. Fix the group, log out and in, then toggle the sensor off and on in Settings → Accessories. A bus that fails mid-run is reported as "Error" and retried after 5 consecutive failed block reads (read loop error: 5 consecutive I2C read failures), so one NACK on a marginal cable no longer goes unnoticed as frozen zeros.

IMU updates look slow

The software read/fusion loop stays active, but the signals emitted to QML drop to 5 Hz (IDLE_EMIT_INTERVAL = 0.2) unless a page that draws them is showing — Car, Sensors, Sensor Home, the OBD menu, or the home screen when an IMU parameter is on a home card — where they run at ~60 Hz. This is the idle-CPU optimisation, not a fault.

ESP32 volume knob not connecting

  • Confirm the ESP32-S3 dongle appears in dmesg after plugging in and that you are in dialout.
  • The knob's manager excludes the OBD adapter's port from its scan; esp32VolumePort defaults to empty, which means an ESP32-S3 is auto-selected at startup; no other port is. A dongle that isn't an ESP32-S3 has to be picked in the Volume Knob settings, and then shows "Port … open: turn the knob to confirm" until the first turn or press (nothing is written to it before that). A settings file from an older build may still hold the former "COM7" default; clear it.
  • A dongle that disappears is detected on QSerialPort's ResourceError / DeviceNotFoundError / PermissionError / ReadError / WriteError or a rejected keepalive write; with esp32AutoReconnect on it then retries five times.
  • Baud rate is 115200. The reader is driven by QSerialPort::readyRead.

Performance Issues

UI stuttering

  1. Look for octave.ui_watchdog warnings in the log — they sample every 2 s and warn when the last GUI response is more than 5 s old; this is not a complete record of all UI stalls.
  2. Run with --profile (build/octave --profile on a Debug build) and watch the [PERF] line: event-loop latency is green under 5 ms average, yellow under 16 ms, red above (STUTTER); the jitter column is the delay a background thread sees trying to run every 10 ms (CPU contention); slow slots are those with p95 > 2 ms. Details on the Development page.
  3. Check CPU: if a core is pegged while the OBD adapter is unplugged, see the dead-port note under OBD.

High memory usage

  • The cover cache keeps up to 500 files (m_maxCacheFiles); filenames hash the album identifier, not image contents. The in-memory metadata cache has a 1000-entry limit. The separate persistent metadata_cache.json store is not bounded by that limit; see Metadata Caching.
  • Audio analyzer: the FFT is pre-computed for the entire track when it starts, so a long lossless file costs memory up front.

Slow library scan

The library scan runs synchronously on the GUI thread (progress via scanProgress); a very large first scan will block the UI until it finishes. Desktop neighbouring-track pre-cache (±3 tracks, 80 ms after a track change) and album-colour extraction run off-thread. Android pre-cache is synchronous.

Dev Tooling Issues

SymptomCause / fix
MCP servers octave-perf / octave-benchmark fail to connectThe servers are Python scripts (dev/mcp_server.py, dev/mcp_benchmark.py) that drive the app over its command server. Check the interpreter and script paths in the local, gitignored .mcp.json, install the mcp package for that interpreter, and run the app with --profile. Machine-specific entries copied from another workstation may be invalid. The benchmark server also needs --mcp or it runs its CLI suite and exits.
build/octave --dev / --profile logs --dev / --profile need a build with -DOCTAVE_DEV_TOOLS=ON (the default for Debug); ignoringOnly -DCMAKE_BUILD_TYPE=Debug builds include the dev tooling by default. For just the simulated car, --sim works in every build. Build a Debug tree (cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug) or pass -DOCTAVE_DEV_TOOLS=ON. The tools pick the binary through dev/app_launch.py; point OCTAVE_BIN at the right one if several builds exist.
command server could not listen on 127.0.0.1:9876Another OCTAVE is still running with --profile, often a story or sweep that was interrupted. Stop it, or start this one with --command-port N and pass --port N to the tool.
Screenshots have blank icons / album artOffscreen rendering may lack the effects needed for a faithful screenshot; use the default Xvfb mode (sudo pacman -S xorg-server-xvfb) or --visible.
Screenshot tool navigates but every capture shows the home pageThe page is listed in the command server's navigate map (kPages in src/dev/coreactions.cpp) but missing from Main.qml's navigateTo page map; a name missing from kPages returns Unknown page: … instead. Register a new page in both. Arguments reach the QML function as QVariant (invokeByName() in src/service/commandserver.cpp), since QML functions take untyped parameters.
ctest --test-dir build hangs or a UI test fails to startRun with QT_QPA_PLATFORM=offscreen, as CI does. Use ctest --test-dir build --output-on-failure -V -R <name> to run one test verbosely. The tests are only built with OCTAVE_BUILD_TESTS (on by default for desktop Debug builds).

WSL Display Issues

OCTAVE needs a display: DISPLAY or WAYLAND_DISPLAY must be set. WSLg supports WSL 2 on Windows 10 build 19044+ and Windows 11; follow Microsoft's GUI-app instructions. An external X server needs a DISPLAY value appropriate to its address and WSL network mode; do not assume :0. Software rendering can help diagnose graphics problems, but does not reproduce shader effects or the 3D view:

export QT_QUICK_BACKEND=software
export LIBGL_ALWAYS_SOFTWARE=1
./build/octave