Skip to content Link Search Menu Expand Document
ModalAI DOCS
Store

SDK 1.8.X Release Notes

SDK 1.8.0 release notes, package list, MD5 hashes and full changelog from SDK 1.6.8.

Available now at https://downloads.modalai.com

SDK 1.8.0 is the current release for VOXL 2, VOXL 2 Mini, VRX and QCS6490. It follows SDK 1.6.8; SDK 1.7 did not get a full release, and its pre-release notes are kept on the SDK 1.7 release notes page. See Which SDK Should I Flash? for the SDK each product ships with.

Table of contents

  1. Overview
  2. SDK 1.8.0 Support Matrix
  3. SDK 1.8.0 System Images
  4. SDK 1.8.0 MD5 Hashes
  5. SDK 1.8.0 Package List
  6. SDK 1.8.0 Full Changelog (from SDK 1.6.8)

Overview

SDK 1.8.0 was released on October 12, 2026. Highlights:

  • PX4 moves from 1.14 to 1.18: voxl-px4 and voxl-fpv-px4 are rebased on upstream PX4 release/1.18.
  • PX4 sensor and thermal calibration files are now stored in /data/modalai/px4 instead of /data/px4/param.
  • voxl-open-vins-server 0.7.8: asynchronous multi-camera VIO, ceres-free initialization, and in-process targetless calibration (--calibrate), with a VINS Calibration page in voxl-portal.
  • voxl-vtx 3.3 and voxl-osd 1.2: Qt-based OSD rendering over DRM/KMS, adaptive link, an RF occupancy scanner and AR waypoints. voxl-vtx requires voxl-osd 1.2.0 or newer on the drone, so run SDK 1.8.0 on both the drone and the VRX.
  • voxl-camera-server 2.2.43: an imx664-or-imx412 sensor type that detects which hires sensor is fitted, and nyctis640 support.
  • voxl-elrs 1.2: faster parameter enumeration, M0220 firmware updates over SMP, and presets matched by parameter name.
  • voxl-configurator 1.1.54: hardware sub-revision SKUs (for example V6A).
  • libmodal-pipe 2.15 adds px4_vehicle_attitude_t and vtx_state_t pipe interfaces.
  • New packages: voxl-wireguard, voxl-quectel-cm, voxl-aprilgrid-camera-calibration, voxl-bf-osd-font, voxl-portal-web-libs and libvoxl-qt.

SDK 1.8.0 Support Matrix

PlatformPCB TargetsSupport Status
VOXL 2M0054-1, M0054-2, M0154-1, M0154-2Yes
VOXL 2 MiniM0104-1, M0204-1Yes
MVX-T0001M0204-1Yes
MVX-R0001M0205-1, M0206-1Yes
QCS6490M0197Yes

SDK 1.8.0 System Images

See the system image release notes.

PlatformSystem image
VOXL 21.8.09
VOXL 2 Mini1.8.09
VRX (ubun2.0-multi)2.0.2-perf-20261004
QCS6490v0.3.4

SDK 1.8.0 MD5 Hashes

SDKHash
voxl2_SDK_1.8.0.tar.gz7c83184458a3d4c89cf153fc8231f712
voxl2-mini_SDK_1.8.0.tar.gz1d487cea9c66d2f1ade4a394caee478d
ubun2.0-multi_SDK_1.8.0.tar.gz3b8eaeb1e61610402ef86562698f65e7
qcs6490_SDK_1.8.0.tar.gzdf4166c97c8bdb2bebedeb91e9326ec3

SDK 1.8.0 Package List

Release date: October 12, 2026

Meta-package: voxl-suite sdk-1.8.0 tag on GitLab, the exact package set pinned for this release.

Changes from SDK 1.6.8:

PackageVersion
libmodal-cvv0.6.1 –> v0.6.2
libmodal-flowv1.0.5 –> v1.0.10
libmodal-journalv0.2.7 –> v0.2.8
libmodal-pipev2.14.12 –> v2.15.1
libvoxl-cci-directv0.4.0 –> v0.4.1
libvoxl-codecv0.1.1 –> v0.1.5
modalai-adspv1.0.8 –> v1.0.10
modalai-slpiv1.2.3 –> v1.2.4
qrb5165-dfs-serverv0.2.0 –> v0.3.0
qrb5165-rangefinder-serverv0.1.6 –> v0.1.7
qrb5165-tflitev2.17.2 –> v2.17.4
voxl-camera-calibrationv0.6.2 –> v0.6.3
voxl-camera-serverv2.2.29 –> v2.2.43
voxl-configuratorv1.1.35 –> v1.1.54
voxl-elrsv1.1.7 –> v1.2.3
voxl-escv1.6.3 –> v1.6.5
voxl-esptoolv0.2.1 –> v0.2.2
voxl-focus-camerav0.6.2 –> v0.6.3
voxl-fpv-px4v1.14.0-8.148.0 –> v1.18.0-8.0.25
voxl-imu-serverv2.0.2 –> v2.0.6
voxl-io-serverv0.1.3 –> v0.1.6
voxl-lepton-serverv1.4.1 –> v1.4.2
voxl-loggerv0.6.3 –> v0.6.9
voxl-mavcam-managerv0.6.1 –> v0.6.2
voxl-mavlink-serverv1.4.16 –> v1.4.20
voxl-modemv1.2.5 –> v1.2.6
voxl-mpa-toolsv1.5.8 –> v1.5.10
voxl-open-vins-serverv0.6.2 –> v0.7.8
voxl-osdv0.4.12 –> v1.2.1
voxl-portalv0.9.4 –> v0.9.16
voxl-px4v1.14.0-2.0.142 –> v1.18.0-1.0.9
voxl-px4-paramsv0.9.15 –> v0.9.35
voxl-rc-serverv0.0.5 –> v0.0.10
voxl-remote-idv0.0.9 –> v0.1.0
voxl-state-estimatorv0.0.6 –> v0.0.7
voxl-utilsv2.0.5 –> v2.0.10
voxl-uvc-serverv0.1.8 –> v0.1.9
voxl-vision-hubv1.9.21 –> v1.10.3
voxl-vtxv2.2.42 –> v3.3.4
libvoxl-qtv6.5.3-r1 (new)
voxl-aprilgrid-camera-calibrationv1.2.0 (new)
voxl-bf-osd-fontv1.0.0 (new)
voxl-portal-web-libsv1.0.0 (new)
voxl-quectel-cmv1.0.0 (new)
voxl-wireguardv1.0.1 (new)
qrb5165-mini-tof-serverremoved (was v0.2.2)
libmodal-exposureremoved (was v0.1.5)

SDK 1.8.0 Full Changelog (from SDK 1.6.8)

libmodal-cv (v0.6.1 to v0.6.2)
    * support fisheye calibrations in stereo rectification, both the CVP grid and CPU map paths
      previously applied the pinhole model to fisheye coefficients
    * accept "equidistant" as an alias for "fisheye" in mono and stereo distortion_model fields

libmodal-flow (v1.0.5 to v1.0.10)
    * Add optional strided exclusion masks for detection, temporal tracking and stereo matching.
    * Validate mask geometry, preserve feature/descriptor alignment and update the public input ABI.
    * version stamp: pkg/control Version is the single source; cmake generates + installs
      modal_flow/Version.hpp (MODAL_FLOW_VERSION_STR, C-linkage modal_flow_runtime_version(),
      modal_flow::version_matches()) so consumers can refuse to run against a .so other than the
      release they compiled with -- the public headers carry ManagerCL/StereoMatcherCL layouts (ABI)
    * stereo matcher: cold-sweep sample count derived from the z band (2.5 px disparity per sample,
      pow2 8..128; 0.1-100 m still 128, z_min 0.3 -> 64), scalar sliding-window ZNCC inner loop (2
      texture reads/pixel, identical accept sets, 1.6x cold / 2x rematch), fused reverse pass
      (reverse kernel undistorts the forward match on the GPU -- one host sync per call; two-sync
      legacy path removed), optional local reverse window (set_reverse_window_px), non-blocking
      readbacks
    * ManagerCL::set_queue_profiling(bool) (before add_camera) turns on CL event profiling so
      kernel-time accessors report; the benches use it
    * examples/modal-flow-stereo-sweep: cost/agreement sweep over z band, band, sample step and
      reverse window on logged frame pairs
    * stereo: perpendicular-band ZNCC tuning + offline stereo tooling merged from stereo-exp
    * stereo: source/target StereoMatchInput/Result field names, with legacy left/right reference
      aliases for pre-rename callers
    * ocl manager: immortal heap singleton -- exit-order clRelease segfault in libgsl fixed
      (explicit shutdown() unchanged)
    * Fix critical and high severity warnings reported in defect dojo
    * Add ZNCC epipolar-band stereo matcher (match_stereo / StereoMatcherCL)

libmodal-journal (v0.2.7 to v0.2.8)
    * fix the destructor sometimes blocking for 1 s at exit (lost wakeup of the scan thread)
    * time the scan interval on the monotonic clock
    * don't join the scan thread when it was never started

libmodal-pipe (v2.14.12 to v2.15.1)
    * VFC altitude flow submode shows as "3D HOLD (THERMAL FLOW)" on the OSD
    * add px4_vehicle_attitude_t interface
    * add vtx_state_t interface
    * fix warnings

libvoxl-cci-direct (v0.4.0 to v0.4.1)
    * qcs6490: discover camera sensor subdevs by scanning sysfs for cam-sensor-driver nodes and
      sorting by kernel-reported slot id, instead of assuming subdev numbering (enumeration order is
      not consistent on qcs6490)
    * qcs6490: finding zero probed sensors is no longer a fatal init error since sensors are only
      probed by the camera stack once a camera opens. init re-scans on subsequent calls until probed
      sensors appear
    * make voxl_cci_init thread safe

libvoxl-codec (v0.1.1 to v0.1.5)
    * Get rid of strcpy calls to fix potential security issues
    * [ROI] Remove the tier-2 QP-bias-budget rate controller: the FW clamps realized per-LCU deltas
      to ~±5, so the map is now static (offsets applied as configured)
    * [ROI] Simplify the user knobs: drop roi_floor and the budget controls; the only strength knobs
      are the ROI/periphery QP offsets in [-5,0] and [0,+5]
    * [ROI] Drop the base-QP NALU-stream readback (only fed the removed controller)
    * [ROI] Handle runtime fps and # of p frame changes to maintain bitrate
    * [OMX] Add support for ROI control mode
    * [OMX] Fix FPS Hack workaround and fix CBR mode

modalai-adsp (v1.0.8 to v1.0.10)
    * Disable options that override UsbPhy driver
    * Increase flight controller UART RX buffer size from 512 to 2048

modalai-slpi (v1.2.3 to v1.2.4)
    * Added i2c bus uninitialization to slpi_link

qrb5165-dfs-server (v0.2.0 to v0.3.0)
    * support two independent hardware-synced RAW8 input pipes per pair (e.g. dual AR0144) with
      timestamp-based frame pairing, enabled with the new input_pipe_right and sync_tolerance_ns
      config options
    * independent pipe mode locates calibration automatically: per-camera intrinsics resolved from
      the pipe names (tracking_left_misp_norm -> opencv_tracking_left_intrinsics.yml) and camera-to-
      camera extrinsics chained from /etc/modalai/extrinsics.conf via libvoxl_common_config, new
      dependency on voxl-mpa-tools
    * remove dead occlusion buffer work, the DFS calls never populated it and nothing consumed the
      result
    * link modal libs by name so both /usr/lib64 and /usr/lib layouts work
    * fix pipe name trimming for paths with trailing slashes and full /run/mpa paths which
      previously kept a leading slash

qrb5165-rangefinder-server (v0.1.6 to v0.1.7)
    * change comp id in outoing distance sensor mavlink message

qrb5165-tflite (v2.17.2 to v2.17.4)
    * set CMAKE_POLICY_VERSION_MINIMUM=3.5 so fetched deps configure under CMake 4 (voxl-cross 4.9)
    * disable qcs6490 build in ci
    * enable qcs6490 build

voxl-camera-calibration (v0.6.2 to v0.6.3)
    * shift target boxes for D0008 V6 Stereo Tracking cameras to avoid gimbal occlusion

voxl-camera-server (v2.2.29 to v2.2.43)
    * misp debayer accounts for imx412 en_rotate, fixes cyan colors on flipped imx412 with the
      generated config
    * qcs6490: apply imx412 cci settings (en_rotate) before stream start, on init and on re-init,
      fixes the stale band at the top of flipped frames
    * qcs6490 imx412-fpv-v2 sensormodules only expose 1936x1080 30/60fps to work around camx mode
      selection on M0197
    * add imx412-fpv-v2 sensormodules for qcs6490
    * qcs6490 (M0197) hires configs use imx664-or-imx412, Seeker V6 configs 60-62 flip the imx412
    * add imx664-or-imx412 sensor type: voxl-configure-cameras probes the slot and sets up
      imx664-fpv or imx412-fpv-v2, with an optional _ROTATE_IMX412 override
    * Seeker V5+ and V6 hires configs (46-51, 60-64) and Stinger configs (33-35) use imx664-or-
      imx412
    * new _ROTATE_IMX412_V6 override applies only when the SKU hardware version is V6, Seeker
      configs shared by V5 and V6 (46-51) flip the forward imx412 on V6 only, V6-only configs (46A,
      47A, 60-64) always flip it
    * rename those config files to say "IMX664 or IMX412"
    * add imx412-fpv-v2 sensor modules for qrb5165, C36 and C37 now use imx412-fpv-v2
    * install chi-cdk sensor modules per platform from chi-cdk/<board>/
    * nyctis640: support Y16 frames with min/max normalization to 8-bit, publish raw stream as RAW16
    * print-camera-configs hides lettered sub-revision configs (46A, 47A, 60A, 61A, 62A) from the
      menu, they are selected through the SKU hardware sub-revision instead
    * 46A/47A: forward tracking camera rotated 180 degrees, downward tracking camera uses the
      rotated sensor type (90 degree encoded stream rotation) to match rev A mounting
    * add rev A camera configs 46A, 47A, 60A, 61A, 62A for VOXL 2 platform_config_1: hires IMX664 on
      J8 upper instead of J7 lower (60A identical to 60)
    * voxl-configure-cameras accepts config ids with a sub-revision letter (e.g. 61A), falls back to
      the base config if no lettered file exists
    * allow mono cameras to share exposure
    * sync exposure on D0008 V6 stereo tracking cams C61 and C62
    * add script to generate html showing all cam config options
    * add nyctis640 support
    * add option for independent zomm / drag control to misp channels
    * version bump for libvoxl-codec ROI feature set framerate changes
    * version bump to ensure libvoxl-codec changes are pulled in
    * remove libmodal_exposure dependency from dpkg control file

voxl-configurator (v1.1.35 to v1.1.54)
    * Seeker (D0008, D0019, D0020) and Stinger camera config menu text says "IMX664 or IMX412" to
      match the hires autodetect in voxl-camera-server 2.2.39
    * D0008 V6 selects the d8v6 open-vins preset on QRB5165 and d8v6m on M0197/QCS6490 (needs the
      voxl-open-vins-server with the d8v6m preset)
    * modem 33 (Silvus): D0008/D0019/D0020 set up voxl-vtx, voxl-osd, voxl-static-ip-watcher and the
      elrs_ip_light_bar like DTC, MVX-R0002 sets up voxl-vrx, voxl-rc-server and voxl-static-ip-
      watcher (needs voxl-vtx 3.3.1, voxl-rc-server 0.0.10, voxl-utils 2.0.9)
    * D0008/D0019/D0020 IP, DTC and fiber paths use the single voxl-io-server elrs_ip_light_bar
      preset (needs voxl-io-server 0.1.6), XBright vs micro bright is only the voxl-elrs preset
      choice
    * load the renamed "Seeker Micro Bright LEDs" voxl-elrs preset in place of "XBright Patrios
      LEDs" (needs voxl-elrs 1.2.1)
    * D0008, D0019 and D0020 V6 select the rotated lepton preset (m0173_rotated_manual), the lepton
      is mounted upside down on all V6 seekers
    * Pass --tracking_stereo flag to voxl-vtx configuration script on stereo tracking camera configs
      (C61-C64)
    * D0019 handles the S, S1 and SL strike extras codes like D0020: vision-hub disabled,
      MRB-D0019-XS px4 params, strike voxl-vtx setup
    * MVX-S0001 V3 (M0197, board code 9): voxl-elrs configures the M0193 TX on /dev/ttyHS6 instead
      of /dev/ttyHS1
    * voxl-configure-sku: MVX-S0001 defaults to V3 on board code 9 and V0 otherwise, and no longer
      overrides a version passed with -v
    * select new voxl-vision-hub d0008v6 VFC config for D0008 V6 on M0197 (board code 9) only
    * load the "XBright Patrios LEDs" (micro bright) voxl-elrs preset on all D0008 and D0020 V6 and
      newer, not just XS1
    * voxl-io-server light bar profile picks micro bright on V6 and newer or XS1 across the IP, DTC
      and fiber paths, older D0008 IP units with XBright bars get the XBright profile
    * voxl-configure-sku: the V6 sub-revision prompt now states what rev A changes (hires IMX664 on
      J8 upper instead of J7 lower, extrinsics unchanged, lettered camera config); the letter stays
      optional
    * VRX SKUs: check for voxl-px4 units when the disable step runs, not when queued, so the
      modalai-adsp-vrx install removing voxl-px4 no longer reports a failed step
    * VRX SKUs: set_px4_conf_param steps are no longer queued at all instead of being queued and
      skipped
    * MVX-S0001 installs modalai-adsp-vrx on board code 9 like the other VRX SKUs
    * select the fpvm open-vins preset on M0197 (board code 9) for its BMI270 800Hz IMU
    * VRX SKUs (MVX-R0001, MVX-R0002, MVX-H0001, MVX-S0001) no longer call any px4 configure scripts
      in voxl-configure-mpa, installed voxl-px4 services are only disabled
    * support hardware sub-revision letters in SKUs (e.g. V6A), parsed as VOXL_HW_REV with
      VOXL_HW_VERSION still numeric
    * voxl-configure-sku accepts -v 6A or -r A, wizard asks for sub-revision on D0008/D0019/D0020 V6
    * voxl-configure-mpa applies sub-revision overrides on top of the base version config, camera
      config gets the letter appended (61 -> 61A)
    * add D0019 version prompt
    * change px4 calibration files path from /data/px4/param to /data/modalai/px4
    * move D0008-V6 to fpv estimator params for robustness
    * add XS1 sku option, XS behavior plus the micro bright LEDs elrs preset (then named XBright
      Patrios LEDs)
    * no imu thermal cal required on XS drones
    * add handling for D0008-V6-XS
    * disable mavcam-manager on platforms with M26 family modems
    * add V6 handling for D0008
    * enable mavlink-server elrs rc mux on D0008 and D0020

voxl-elrs (v1.1.7 to v1.2.3)
    * cache parameter indexes per boot in /run/voxl-elrs, so bind, unbind, arm and other name
      lookups cost one ping and one read instead of a full enumeration the name at the cached index
      is checked before use, and a miss or mismatch re-enumerates firmware updates and the first
      device info after a ModalAI receiver reboot clear the cache, ELRS_PARAM_CACHE_DIR moves it and
      an empty value disables it
    * request parameter chunks and parameters back to back instead of sleeping 15 ms between them,
      enumeration takes ~2 s instead of ~8.9 s over mpa:crsf_raw and ~0.6 s instead of ~1.75 s over
      qup7
    * drop the fixed 200 ms settle after the crsf_raw pipe connects, every mpa command starts ~0.2 s
      sooner
    * bind and unbind no longer re-read the command parameter after the lookup
    * update M0220 RX and TX firmware over SMP v2 (MCUboot serial) instead of MAVLink FTP, the input
      is still an MRC bundle app and XIP go to the primary slots and both MCUboot hashes are checked
      before the SMP reset the bootloader runs at 420000 baud for HWID 0x01 and 921600 otherwise,
      receiver updates at 420000 need the updated bootloader
    * upload-firmware <bundle> smp recovers an M0220 left in its bootloader without CRSF discovery,
      add -T for a TX
    * update-mrc stays available for explicit legacy MAVLink FTP uploads
    * SMP uploads work over the adb: transport
    * bundle private static copies of libsmp and libcbor, SMP requires BUILD_JSON=ON
    * ship the libcbor license that the package installs, its missing file stopped every cross build
      at CPack
    * rename the "XBright Patrios LEDs" preset to "Seeker Micro Bright LEDs"
      (seeker_micro_bright_leds.json), it maps white to 1300us for micro bright bars XBright bars
      (white 1500us) use the default "Seeker LEDs" preset
    * presets match folders and parameters by name instead of field_id, so they survive parameter
      index changes between firmware builds config files no longer need field_id/type and use a
      compact layout: {"Output Mapping": {"Pin 1": {"Input Channel": 7}}} verbose/save-json configs
      still load, a stale field_id only logs a warning
    * configure checks every preset entry against the device before writing, so an unknown name or
      wrongly typed value no longer leaves a partial config
    * merge the "Custom Domain RX" and "Custom Domain TX" presets into one "Custom Domain" preset
      (custom_domain.json) that works on both
    * fix receiver firmware updates over SLPI with voxl-px4 running: wait for the DSP after stopping
      voxl-px4 and before esptool, reset it before restarting voxl-px4
    * pass --before default_reset to esptool so the first sync attempt already asks ELRS for the
      bootloader
    * verify a flashed receiver with a ping from a fresh process and stop reporting a verified flash
      as a failure
    * bound the esptool subprocess with a deadline and say whether to power-cycle or hold the boot
      button when it fails
    * update ELRS to 3.5.3.31 to fix crsf compatibility
    * Warning this breaks compatibility with older voxl-elrs!
    * Added GUI frontend + adb bridge

voxl-esc (v1.6.3 to v1.6.5)
    * update prod/test tools
    * update to 39.26 fw

voxl-esptool (v0.2.1 to v0.2.2)
    * set the SLPI UART read timeout from esptool's requested timeout (1 s when unset, clamped to
      500 ms..5 s) instead of libvoxl_io's fixed 100 ms default, and re-apply it after every baud
      change
    * inWaiting reports the bytes actually buffered on SLPI ports instead of always 256, and
      flushInput/reset_input_buffer clear the SLPI rx buffer instead of doing nothing
    * accept /dev/slpi-uart-<qup> for the SLPI QUPs libvoxl_io supports (2, 6, 7), not just
      /dev/slpi-uart-7
    * SLPI ports use the CRSF "bl" bootloader reset (Voxl2Reset) instead of the DTR/RTS resets that
      crashed with AttributeError 'fileno', so plain "esptool chip_id" works and sends "bl" once per
      connect attempt
    * before no_reset still makes its first attempt a plain sync, for a receiver already held in the
      ROM bootloader
    * Voxl2Reset restores the caller's connect baud (normally 115200) instead of leaving the port at
      460800, so a stub already running at that baud stays reachable
    * the ESP8266 stub loader inherits the ROM loader cache, so flash_id, --flash_size detect and
      the pre-operation flash chip checks work with the stub instead of raising AttributeError
    * no zero-length reads, and a failed read returns nothing instead of stale buffer bytes
    * do not close and re-init the SLPI UART when the baud is set to its current rate, e.g.
      connecting at 420000 with --passthrough re-initialised the port right after the "bl" frame

voxl-focus-camera (v0.6.2 to v0.6.3)
    * added workstation script to loop the focus tool
    * fix locked awb

voxl-fpv-px4 (v1.14.0-8.148.0 to v1.18.0-8.0.25)
    * ekf2: add gyro-frame filtering and adaptive weighting for gravity fusion
    * Wrap Linux settime to prevent GPS from setting the Linux realtime clock and affecting other
      services
    * Honor loopback interface when binding UDP sockets in Mavlink
    * Change RC position mode switch behavior. Engage once it can run.
    * Removed obsolete QURT definition that was preventing logging of GPS time
    * Fix to prevent qcs6490 system from deleting px4 lock files
    * Clean up BMI270 diagnostics, startup probes, and comments
    * BMI270: 800 Hz OSR4 filtering, FIFO fixes, gyro timing and cross-axis corrections.
    * Add optional BMI270 gyro retrimming and NVM storage support.
    * Add optional EKF gravity-fusion rate gate, disabled by default.
    * Restore turtle mode reporting in MAVLink heartbeat
    * Made sure that the virtual serial endpoint baud rate is set to 460800
    * Allow virtual serial endpoint baud rate parameter to be optional with 420000 default
    * Added new MPA bridge for PX4 attitude information including reset count and delta
    * Changed where sensor and thermal calibration files are stored to /data/modalai/px4
    * Switched to upstream tag v1.18.0-rc1
    * Rebased the FPV changes on upstream release/1.18 branch at commit
      75395e11aa33f3cfa9496deeef314f4711368829
    * Don't start DPS baro on M0226

voxl-imu-server (v2.0.2 to v2.0.6)
    * voxl-calibrate-imu-temp: baro temp cal pipe changed from 'px4_vehicle_air_data' to
      'px4_sensor_baro' (raw pressure + real die temp)
    * voxl-calibrate-imu-temp: add --no-apps to skip apps-proc IMUs (px4/baro only), leaves voxl-
      imu-server.cal untouched
    * voxl-calibrate-imu-temp: pace the heating loop when only the baro is enabled
    * voxl-calibrate-imu-temp: line-buffer stdout so stage messages reach voxl-portal promptly
    * bmi270: load Bosch's standard config blob (bmi270_config.c) so the gyro cross-axis factor_zx
      is readable; custom blob reports 0
    * bmi270: add BMI270_BWP_MODE selector (normal / OSR2 / OSR4) with per-mode timestamp delay
      compensation, documented in bmi270.cpp
    * bmi270: use OSR4 on-chip filter mode for accel and gyro (was normal mode) to attenuate motor
      vibration before the FIFO; ODR unchanged
    * Fixes for security warnings
    * Moved px4 thermal cal files to /data/modalai/px4
    * fix LSB indices of 42688 IMU in 20-bit mode
    * fix PX4 temp cal param names

voxl-io-server (v0.1.3 to v0.1.6)
    * d0020_ip_light_bar, d0008_ip_light_bar and d0008_v5_ip_light_bar replaced by one
      elrs_ip_light_bar preset for IP control, the receiver's voxl-elrs pin preset sets the widths
      for XBright vs micro bright bars and voxl-configurator picks it per SKU
    * remove en_xbright (it set the micro bright widths), the receiver's voxl-elrs preset sets the
      bar widths, a stale en_xbright is removed from the config
    * rc handling reads all channels from the rc_active pipe published by voxl-mavlink-server,
      joystick button config removed

voxl-lepton-server (v1.4.1 to v1.4.2)
    * only reset the lepton at startup if it has not already booted (M0195 cannot recover a booted
      lepton from reset)
    * resync SPI without rebooting while the lepton still reports booted
    * resync if no frames arrive for 3 seconds

voxl-logger (v0.6.3 to v0.6.9)
    * add bash completions for voxl-logger-auto
    * fix voxl-logger-auto configure crashing with UnicodeEncodeError on drones without a UTF-8
      locale
    * add voxl-logger-auto configure, interactive and scriptable config editor
    * add V6 tracking_left/tracking_right pipes to -b, -j and -o presets
    * fix -j using its skip argument as the baro pipe name, now logs px4_baro
    * fix -j short option requiring an argument, skip is optional as documented
    * add raw buffer logging for raw8, raw10, raw12, yuv
    * add raw and uncompressed yuv playback to voxl-replay
    * clean up default voxl-logger-auto.conf
    * fix -Wstringop-truncation in control pipe info fields
    * add voxl-logger-auto
    * add logging support with -O for offboard_log, logs the mpa offboard data from VFC. Also logs
      the px4_baro pipe in addition

voxl-mavcam-manager (v0.6.1 to v0.6.2)
    * fix debug flag being set incorrectly

voxl-mavlink-server (v1.4.16 to v1.4.20)
    * fix external uart fc
    * add rc_active pipe muxing rc_channels and manual_control
    * pipe_io: add libelrs failsafe awareness to rc_active mux
    * fix qcs6490 build by adding voxl-elrs as a dependency
    * simplify RC active mux selection logic
    * Controlled FTP/Download helpers for ATAK

voxl-modem (v1.2.5 to v1.2.6)
    * move Quectel Connect Manager to the voxl-quectel-cm dependency

voxl-mpa-tools (v1.5.8 to v1.5.10)
    * changed baro tc cal param directory from /data/px4/param to /data/modalai/px4
    * add conf files for D0008-V6
    * voxl-logger: update to allow raw images to be recorded as jpegs
    * add voxl-record-raw-image

voxl-open-vins-server (v0.6.2 to v0.7.8)
    * ZUPT disabled (try_zupt: false) in fpv, fpvM, fpvhires, fpvhiresM, starling2 and starling2M;
      its noise inflation contaminated the state
    * startup: partial camera connections are cleaned up without recursive manager locking, fixing
      startup hangs; the health thread starts before subscribing and reports stalled camera, IMU or
      estimator inputs without triggering repeated resets
    * configured per-camera masks apply to live, calibration and tracker-only replay TrackOCL
      inputs; requires libmodal-flow 1.0.10
    * d0008-v6 and d0008-v6M: per-camera PNG tracking masks packaged and enabled, updated estimator
      configs, v6M uses the D0008-v6 camera pair calibration
    * new d8v6m preset in voxl-configure-open-vins for D0008 V6 on M0197 (QCS6490, BMI270); both
      D0008 V6 presets set using_stereo=0 and sync_config=true
    * CI replays use center stamps, auto-start and dynamic init with aligned baselines; failed
      replays no longer replace references
    * host tests for the camera manager, forced-sync input status, camera masks and CI scoring
    * voxl-zcalib: VIO forced sync applies only to cameras VIO flies; an any-pipe or VIO-disabled
      camera keeps its own exposure-center stamps, so calibrating one alongside VINS no longer
      requires the VIO reference camera
    * voxl-zcalib: a forced-sync session mixing VIO cameras with a camera outside the VIO chain is
      refused instead of dropping every unpaired frame group; calibrate the outside camera in a
      separate session
    * voxl-calib-to-extrinsics: output is written atomically (temp file, fsync, rename) and keeps
      the conf's permissions
    * host test for the calibration session timing policy
    * forced camera sync: new force_camera_sync and cam_imu_dt_ref_camid estimator keys group synced
      dual-mono frames on the hardware stamp and share the reference camera's center timestamp
    * live ingest, zcalib, vins-lab replay and tracker-only replay all use the same sync grouping
      and decimation
    * zcalib: forced-sync sessions require the reference camera; unpaired frames are counted in the
      run summary
    * zcalib: use the declared-camera intrinsics path from voxl-camera-server as-is (reported by
      Noah Sleeman, ASCEND)
    * calibration result, rollback and session record move to /data/ov_calib/; the calib tools
      follow
    * ov_calib.yaml: track_rate_hz 30, solve_budget_s 120, tg_max_sigma documented as an acceptance
      ceiling, BMI270 noise reference added
    * calibration hotstart: overlays composed in one frame, Tg and camera rotations transported, one
      snapshot path through boot and reset, incoherent reset rejected
    * calibration server: per-camera noise controls, independent IMU/Tg seed policies, gravity
      provenance, collection budget, atomic result writes
    * new fpvM profile and voxl-configure-open-vins fpvm preset for the M0197 synced AR0144 duo
    * fpv and starling2 profiles: forced sync on, eight init poses, 180-degree init tilt limits;
      starling2Bmi restores the master init policy
    * host regression tests under tests/ run with ctest, no board required
    * open_vins submodule: initializer S2 manifold and equidistant derivative fixes, lower warm-
      start overhead, opt-in structureless QR export, fixed-Tg preintegration invalidation
    * added after release: open_vins submodule changes from branch calibration-refinement
      (c6a903e..c698db5) that the original entries missed
    * ov_calib.yaml keys introduced in 0.7.5 (c6a903e) and never listed: bootstrap_epipolar and
      tg_precision_screen (default true), radtan_tangent_refine_sigma (0.001) and
      radtan_tangent_full_sigma (0.01); the shipped profile keeps the defaults
    * correction to the forced-sync line: cam_imu_dt_ref_camid is not new (parsed since 0.7.0), this
      release also makes it the camera that owns the group timestamp; use_stereo rigs (d0008-v6,
      d8spirit) take the same forced-sync path, and epoch_mode has no effect on synchronized rigs
    * correction to the open_vins submodule line: the S2 fix repairs the memory layout of the
      gravity tangent basis in the shared ceres-free solver, which corrupted the gravity Jacobian
      and covariance of every dynamic init since 0.7.0 and of zcalib window solves since 0.7.2; the
      equidistant optical-center derivative fix also applies to the filter camera model; the
      structureless QR export is enabled by the environment variable OV_ZCALIB_EXPORT_QR=1, not a
      YAML key, and applies to the initializer and zcalib
    * static and dynamic init apply the fixed IMU chain intrinsics loaded for the filter (Dw/Da,
      gyro/accel rotations, Tg) instead of raw IMU samples; every shipped IMU chain is non-identity,
      so every init seed changes
    * the initializer retimes each camera with its own timeshift_cam_imu; when offsets differ it
      returns only the IMU marginal and the filter starts a cold clone window, so
      init_warmstart_inject no longer injects clones on fpv, fpvhires, starling2 and starling2Bmi
    * soft reset keeps the live bias prior only for a fully fixed calibration model (no online
      camera/IMU calibration, zero readout, init_dyn_mle_opt_calib and init_dyn_fix_ba_on_reset
      off); every shipped profile estimates camera intrinsics and time offset, so soft-reset re-init
      now uses the configured bias seeds (0.7.5 always used the live prior); a kept prior carries
      the full 6x6 bias covariance, aged to the first new graph node, and excludes pre-reset
      measurements
    * dynamic init applies the gravity re-alignment at every accepted tilt (0.7.5 skipped it below
      0.1 deg) and carries the fitted gravity uncertainty into the initial covariance
    * dynamic init is rejected when an optimized feature lies behind a camera; init outputs with
      non-finite values, non-unit quaternions or an invalid covariance are rejected before import
      and the filter stays uninitialized
    * warm start removes the observations the initializer consumed from the feature database, so the
      first filter update does not reuse them
    * initializer IMU windows require strictly increasing timestamps covering the whole interval;
      zero-dt samples used to be dropped
    * invalid initializer priors or IMU factors fail the attempt instead of exiting the process, and
      initializer exceptions count as a failed attempt; each attempt runs on a snapshot of its
      inputs and stale results are discarded after reset or restore; prior gradient whitening
      corrected (only priors with a nonzero gradient are affected)
    * epoch_mode defaults to true for unsynchronized multi-camera rigs whose estimator yaml omits it
      (shipped d0008-v4 and starling2Bmi)
    * new opt-in estimator keys, false by default and in every shipped profile:
      physical_camera_clones (physical warm start; global shutter and fixed calibration only,
      rolling-shutter cameras are rejected at startup) and async_frame_clones
    * rk4/analytical propagation (rk4 in every shipped profile) integrates continuous IMU white
      noise and bias random walk over each interval, including navigation/bias cross-covariance,
      instead of injecting the interval-average noise; discrete integration keeps its noise model
    * anchored landmarks are expressed in an unwarped virtual camera at the anchor clone; time
      offset, rolling shutter and epoch transport act only on observations (changes results on
      rolling-shutter rigs and for the non-reference camera of dual rigs)
    * time-offset warps of fixed zero-readout cameras use the clone velocity in the estimated world
      frame, in updates and in triangulation
    * SLAM delayed-init chi2 gate counts only the residual rows left after eliminating the landmark
      (tighter acceptance)
    * ZUPT chi2 counts every IMU residual row, including energy removed by QR compression; with non-
      identity IMU intrinsics or Tg (every shipped chain) ZUPT uses a raw-coordinate stationary
      model with matching Jacobians
    * epoch-bridge bias Jacobians include the IMU intrinsics and the Tg accel-bias coupling
    * dt_calib_gate freezes temporal calibration with a Schmidt update instead of dropping its
      Jacobian columns (d0008-v6, d0008-v6M)
    * IMU intervals are never extrapolated: an uncovered, non-increasing or non-finite interval
      skips the frame; propagation and ZUPT share one accepted IMU endpoint
    * numerical failures in EKF propagation, updates and landmark initialization roll back instead
      of exiting the process or admitting NaN (a failed anchor change marginalizes the landmark);
      non-finite innovations are rejected
    * filter camera projection runs fully in double precision (radtan and equidistant); feature
      triangulation refines in double precision and rejects empty tracks and non-finite geometry
    * Kalman gains are solved from the innovation factorization instead of an explicit inverse; chi2
      thresholds above 4095 dof use Wilson-Hilferty instead of clamping
    * zcalib: calibration windows no longer add half the frame exposure to stamps that ingest
      already centers; earned time offsets differ from 0.7.5 by about half the mean exposure
    * zcalib: temporal reprojection optimizes the window velocity (0.7.5 held it fixed)
    * zcalib: bootstrap and window gyro-bias seeds apply the seeded IMU intrinsics and Tg before
      time-offset, hand-eye and bias estimation
    * zcalib: bias priors stay at the harvested window seeds for every candidate; held-out
      verification scores all candidates with fixed weights and noise
    * zcalib: an uncertified Tg triggers a refit with Tg held at its seed (stage A1a-fixed-tg; the
      A0 extrinsics/td are kept if it fails); a fixed nonzero Tg keeps its accel-bias coupling in
      the IMU factor
    * zcalib: the unresolved focal/center pair refit also covers equidistant cameras (stage
      B2-radtan-refit renamed B2-camera-refit)
    * zcalib: early live cutover also waits for the Tg precision target when Tg is estimated;
      guidance counts only windows from the current thermal bin
    * zcalib: the bench profile moves to split-half gate authority with Wald as a diagnostic, as
      flight already was; Wald sizes each retained Tg direction with its own quantile
    * zcalib: stages start only with solve budget for a complete pass; budget-truncated passes and
      gates are discarded, and an over-budget IMU stage keeps the A0 extrinsics/td instead of
      aborting
    * zcalib: ov_calib.yaml is validated strictly -- duplicate keys, wrong types, out-of-range
      values and a profile other than flight/bench fail the load instead of falling back to
      defaults; new keys camera_pixel_sigma (1.0), camera_pixel_sigma_by_name (canonical camera
      names) and gravity_mag (0 = inherit)
    * zcalib: session records are format 7 (per-camera pixel sigma, completion footer); formats 5
      and 6 still replay, a format-7 record without its footer is rejected, and 0.7.5 cannot read
      formats 6 or 7
    * zcalib: the JSON report adds gravity magnitude/source, solve budget, per-camera pixel sigma,
      hand-eye gyro-bias status and verdict/sigma semantics fields; report and result write failures
      are errors
    * zcalib: non-finite posteriors, solver steps, Wald statistics and relative-rotation seeds fail
      closed under fast-math
    * build: ov_core, ov_init and ov_msckf export fixed Eigen ABI definitions
      (EIGEN_MAX_ALIGN_BYTES=16, EIGEN_MAX_STATIC_ALIGN_BYTES=16, EIGEN_MALLOC_ALREADY_ALIGNED=0) to
      every consumer; the GPU tracker builds only with OpenCL plus the libmodal-flow Types.hpp and
      StereoMatcher.hpp headers, otherwise the CPU trackers are built
    * present but not selected by any runtime configuration: sampled-IMU propagation groundwork,
      sampled CPI statistics and BiasLinkedCalib (exercised only by tests)
    * host regression tests and oracles added across ov_core, ov_init, ov_msckf and ov_zcalib
    * add standalone voxl-zcalib live calibration and session replay, with declared or ad-hoc
      cameras
    * share sensor ingest and calibration with the server while preserving a running VIO process
    * add bounded JSONL status publishing, solve/abort/extend controls, and caller provenance
    * reject concurrent calibrator instances; preserve operator profiles during package upgrades
    * export JSON reports with gate evidence, posterior uncertainty, held-out costs, and stage
      timing
    * keep non-finite report values valid under fast-math compilation; serialize them as null
    * use centered frame timestamps consistently in bootstrap
    * detect motion-biased time correlation and refine rotation/time with matched-bearing geometry
    * distinguish provisional sparse-data seeds from geometrically verified timing
    * apply model-specific radtan/equidistant refinement gates and refit unresolved radtan pairs
    * share the solve deadline and worker budget across stages; reject truncated split-half evidence
    * cache exact reprojection transport and use fixed-size radtan derivatives
    * screen weak Tg precision before additional half-solves and improve single-camera guidance
    * accept JPEG log channels, with a warning that lossy captures cannot establish tracker parity
    * libmodal-flow version lock, fails configure below the floor and records the exact version
    * stereo front-end: one ZNCC call per detection frame
    * removed legacy concatenated-stereo camera path (StereoCameraMinimal)
    * "using_stereo" conf key overwrites the estimator YAML when sync_config is true
    * new "stereo_calibration" conf key: path to a voxl_stereo_calibration.yml from the offline
      stereo calibrator (blank = off)
    * stereo: IMU-aided KLT seeding compiled but OFF by default (A/B-togglable);
      up_*_sigma_px_stereo keys dropped from the estimator and the d0008-v6 profile -- stereo and
      mono features share up_*_sigma_px
    * d0008-v6 profile: rs_convention top -- synced pairs bundle on bit-equal SOF stamps; under
      center stamps the per-frame AE exposures desync the pair by tens of us, the ingest never
      bundles, and live dual-mono degrades into micro-epochs (one cam starved, quality reset-loop)
    * [stereo_calib] informational prints gated on using_stereo; warnings and errors stay
      unconditional
    * vins-lab: reliable replay init suite -- det-init by default (init_dyn_mle_max_time=9999,
      multi_threading_subs=false, so init no longer depends on wall-clock load; --wallclock-init
      restores live-like timing), --dyn-init (skip the static path, init via the dynamic MLE at
      motion onset), --auto-start (gyro motion-onset detector starts the feed onset+1s), --static-
      init (init_max_disparity=0 for IMU-quiet logs); overrides are comment-preserving edits to the
      run's config snapshot
    * clients: voxl-log-vins and voxl-evaluate-vins removed (unused; voxl-vins-lab owns
      record/replay/eval -- dpkg drops the stale binaries from devices on upgrade)
    * estimator-yaml comment doctrine: a ':' inside a TRAILING comment makes cv::FileStorage split
      the line and parse the key as a MAP -- the key then silently reads as its parse DEFAULT
      (fpvhiresM on-device: calib_cam_extrinsics/timeoffset/readout, fast_threshold,
      num_opencv_threads all defaulting; latent for years because the old yaml-cpp sync STRIPPED
      comments, exposed by the comment-preserving sync). All 74 offending trailing comments across
      the 11 shipped profiles hoisted to their own lines (verified: every top-level key of every
      profile parses scalar under cv 4.6); voxl-vins-autoconf detects + --apply-fixes the class on
      any device; the strict rs_convention parse explains the trailing-comment cause when it reads
      an empty value
    * Boost retired end to end (build + runtime): rT profiling timers -> std::chrono::steady_clock
      (ov_core/utils/ChronoProf.h; boost's microsec_clock::local_time resolved the TIMEZONE -- glibc
      lock class -- several times per frame on the estimator thread), chi-squared 0.95 gate
      thresholds -> baked constexpr table dof 1..4095 (ov_core/utils/chi_square/, generated
      aarch64-under-qemu against the exact runtime boost::math 1.83 so every entry is BIT-IDENTICAL
      to what the filter computed on-device; StateHelper's delayed-init gate no longer solves a
      quantile per landmark), boost::filesystem -> 3 POSIX wrappers (FsLite.h),
      boost::lockfree::spsc_queue -> ov_core::SpscRing (the zcalib live-feeder ring generalized:
      acquire/release only, alignas(128) indices; pop() MOVES the frame out and resets the slot so
      popped pixel buffers release immediately instead of pinning capacity x frame memory)
    * include hygiene: VoxlVars.h no longer includes the estimator (fwd-decls; it reached every TU
      in server/ + lab/ dragging the full VioManager/tracker/OpenCL closure, ~280k preprocessed
      lines per TU), VoxlCommon/VoxlConfigure/VoxlHK/VoxlIngest/CameraBase slimmed to what their
      declarations use, opencv umbrella narrowed to core/mat.hpp where only cv::Mat is named,
      modal_pipe.h -> modal_pipe_interfaces.h where only types are needed; opencv_compat umbrella
      drops the 5 never-linked modules (videoio/objdetect/photo/ml/dnn) from every TU in the tree;
      OpenCL handles in camera headers now properly HAVE_OPENCL-gated (CPU-only configs compile
      again)
    * cmake: Boost find_package removed (first-party) / optional (submodule, ROS-visualizer-only);
      linker flags out of compile flags; duplicate march/O3 re-appends removed; dead EN_ION_BUF
      compile definition removed; clients/ drops OpenCV/Eigen/Boost discovery entirely (pure
      modal_pipe + libc); host-python matplotlib leak disabled; ov_msckf/ov_init fix the /../ov_core
      include-dir expansion (CMAKE_CURRENT_SOURCE_DIR)
    * rs_convention knob (top | center | bottom): the RS row-anchor convention is a DECLARED single-
      knob choice -- the estimator-yaml key drives BOTH the filter's row model (row v samples at
      stamp + (v/h - anchor) * readout) and the producer stamp anchoring in frame_timestamp_s (top =
      raw HAL3 SOF, the pre-flip system convention; center = center-row mid-exposure, the default;
      bottom = last-row mid-exposure); stamps and model structurally cannot disagree; all shipped
      profiles carry the key explicitly
    * voxl-vins-lab --rs-convention auto (default): a log whose captured conf predates the zcal keys
      is recognized as a pre-flip recording and replays under TOP -- same command, same log
      reproduces the vins-lab-era results on this branch; post-flip logs follow their yaml; explicit
      top/center/bottom overrides; a pre-flip-era stereo log also reverts to that era's
      T_cn_cnm1-first extrinsic source for the ZNCC pack (residual pre-flip deltas are numerical-
      noise class only: exact-SO(3) transport and the ceres-free init refactors are unconditional)
    * voxl-vins-lab: the stereo ZNCC pack runs ONLY when the estimator yaml says use_stereo -- a
      dual-cam mono rig (fpvhires' unrelated hires+tracking pair) no longer gets packed as a stereo
      pair
    * tools/voxl-vins-autoconf: inspect / validate / auto-fix the server .conf + VoxlConfig yaml
      tree as ONE coherent set (ChainYaml-strictness grammar, %YAML directive, shutter/readout/fps
      coherence, max_cameras vs chain, zcal key group, hot-start snapshot presence); writes
      rs_convention explicitly; --log mode reports a capture's stamp-convention era plus the exact
      replay invocation; dry-run by default, --apply fixes textually (comments preserved, atomic
      writes, one-time .bak)
    * voxl-configure-open-vins: presets for the remaining shipped profiles (d8spirit, fpvhiresM,
      starling2M, starling2Bmi, zmono)
    * in-process targetless calibration mode: voxl-open-vins-server --calibrate runs a self-
      calibration session (engine: ov_zcalib) through the identical drivers and frame gating as VIO
      (TrackOCL front-end); lock-free SPSC ingest with a single arrival-sequence merge; mandatory
      session record mirror written at consumption (live == replay structural; no file I/O in RT
      callbacks); atomic result YAML + rollback under /data/modalai/ov_calib/; exit code = session
      verdict; per-pass solver prints gated behind -d
    * calibrator profile /etc/modalai/ov_calib.yaml: operator-controlled seeded-vs-earned policy
      (per-unit intrinsics seeded; extrinsics/td earned blind by default) + raw-Allan calibration
      noise keys
    * calibration CLI: -k/--calibrate, --calib-cam (global-shutter default, rolling explicit-only),
      --calib-out, --calib-flight, --calib-max-sec, --calib-budget, --calib-min-eig, --calib-config,
      --calib-no-perf-mode
    * calibration sessions request the performance CPU governor (measured 3.1x on the solve);
      already-performance rigs are never touched; restore is knob-gated
    * dual-camera calibration ingest: per-camera drop accounting -- one camera's dropped frames no
      longer invalidate the other camera's windows
    * scripts/gauge_probe.py: scores a calibration result against a kalibr reference across gauge
      conventions
    * VIO hot-start from a committed calibration snapshot: values only, committed blocks only;
      filter noise/prior sigmas never touched; the IMU chain applies as the complete {Dw, Da,
      R_AtoI} triplet with a runtime gauge round-trip check
    * calibrate_first: a boot with no committed snapshot runs a calibration session first, then
      cold-starts VIO in-process; bounded retries; a hard reset reverts to the pre-calibration on-
      disk configuration unless opted in; all knobs default OFF (en_calib_hotstart, calibrate_first,
      calib_hotstart_on_reset, calib_restore_governor)
    * startup hygiene: process kill/signal/pidfile/priority/affinity precede any thread creation
      (solver workers inherit the big-core set); non-service runs stop an active systemd unit before
      the raw-kill fallback (cancels pending auto-restarts); SIGINT/SIGTERM attributed via
      SA_SIGINFO
    * SYSTEM TIMESTAMP CONVENTION FLIP: every frame stamp is now the center-row mid-exposure
      instant, applied ONCE at ingest (t = HAL3 SOF + (readout + exposure)/2;
      CameraBase::frame_timestamp_s is the single definition, shared by mono/stereo/VIO/calib and
      mirrored by the host log feeder). Exposure is per-frame (AE-exact); readout is the fixed HAL3
      rolling-shutter skew of the sensor mode, plumbed config -> cam_info (camN_readout_time_s >
      chain t_readout). CameraData::exposures demoted to provenance.
    * rolling shutter is CENTERED inside the estimator and the calibrator: row v samples at stamp +
      (v/h - 0.5)*readout (was top-anchored v/h) -- the calibrator/filter tr-anchor mismatch class
      is gone
    * exact SO(3) manifold transport everywhere the RS/dt kinematic shift is applied: exp_so3
      replaces the first-order (I - skew(w) dt) in ov_msckf updaters/retriangulation and in the
      zcalib reprojection factor (quaternion exponential); Jacobians unchanged by contract
    * readout time is NEVER calibrated: tr estimation removed from ov_zcalib end to end (free_tr, C3
      row-coverage gate, C3b td-aliasing guard, physics band, seed-vs-earned provenance, tr_hw_seed
      record field [kFormat 5], --tr-estimation CLI, estimate/seed_readout_time profile knobs); tr
      is the HAL3 hardware value, a fixed transport constant folded into each observation's dt_ref;
      --tr <s> on the CLI now MEANS that value; calib_cam_readout forced false on RS platforms
    * td convention unified: the calibrated timeshift is defined against center stamps and consumed
      verbatim by the chain/VIO/hotstart -- the mid->SOF conversion (timeshift_cam_imu_sof, td_sof)
      is retired; RS-platform chains re-baselined (IMX664 timeshift 0.006757 -> 0.000, the measured
      ballpark under center stamps)
    * pre-flip artifacts: session .bin records fail loudly (format bump), pre-flip chains carry
      (readout+exposure)/2 inside their timeshift until re-earned (the calib report says so)
    * docs: INFLIGHT_CALIBRATION.md (operator usage) + CALIBRATION_OPEN_ITEMS.md (upstream open
      items with proposed fixes and adjudicated design verdicts)
    * record_session profile key: the session record mirror is optional (default on; disabling it
      forfeits replay and forensics)
    * yaml-cpp dependency removed: the server speaks OpenCV YAML end to end -- estimator-config sync
      patches its keys in place (file comments now survive a sync), and the camera chain loads/saves
      through an order- and key-preserving OpenCV-YAML model (ChainYaml.h)
    * tools/voxl-calib-to-extrinsics: writes committed calibration extrinsics into extrinsics.conf
      (conf-graph frame composition, gimbal-aware RPY extraction, plausibility gates, comment-
      preserving edits; ships in the deb)
    * tools/voxl-calib-commit-chains: writes a committed result permanently into the kalibr IMU +
      imucam chains (gauge-ported with an estimator-parse round-trip check; fed-frame gated;
      sync_config cross-checked against extrinsics.conf) and disarms the hot-start knobs;
      sync_config is set false by default so boot cannot undo the commit (--keep-sync-config
      preserves it, with extrinsics.conf cross-check warnings)
    * calibration tools: quoted provenance strings parse correctly (a quoted imu_frame no longer
      refuses the frame gate); voxl-calib-commit-chains -e also writes the committed extrinsics into
      extrinsics.conf
    * fpvhiresM VoxlConfig profile ships in the package (single-camera hires rigs whose conf points
      yaml_folder at it booted into a missing-profile abort)
    * voxl-vins-lab: offline replay harness that feeds a voxl-logger capture directly into an in-
      process VioManager -- as fast as the filter can consume (--speed 0), Nx, or real-time -- with
      per-frame CSV + summary metrics, optional publishing of the normal ov/ov_extended pipes, an
      interactive rewindable session (filter snapshot/restore/branch), and lineage-aware run
      persistence (a publishing drive writes a portal-loadable ov_extended data.raw)
    * shared sensor ingest (VoxlIngest): IMU batch build/route and mono/stereo CameraData
      construction factored out of the pipe callbacks so the live server and the lab feed the
      estimator through identical code -- the center-stamp convention, per-frame exposure provenance
      and the calibrate-mode routing all live in the ONE shared conversion
    * server build split into vins_server_core static lib + thin spinner executable; lab links the
      same lib; build.sh prefers ninja + ccache with a RAM-aware job count
    * publisher raw ext sink is ASYNC by construction (RecordSpool: bounded SPSC ring + writer
      thread, wait-free producer, drop accounting at close) -- storage latency structurally cannot
      stall the VIO thread; the camera hot path also sheds its staging memcpy (one ownership copy
      per frame remains, in the shared builder) and the ION path's per-frame allocate-and-zero
      placeholder
    * vins-lab replay TIERS against the calibration session record (.bin, kFormat 5): --session
      <bin> drives the FILTER ALONE from recorded RawImu + post-tracking observations (TrackSIM
      feed; no images, no tracker), --tracker-only runs JUST the tracking front-end over a log and
      writes a session record seeded through the live calibrator's own seed builder
      (calib_build_session_seed) -- consumed unchanged by ov_zcalibrate --replay (calibration solve)
      or --session here; with the default full-system tier this decomposes any logged drive into
      system vs filter-math vs tracker
    * vins-lab x zcalib: --calib <ov_calib_result.yaml> replays a log under a committed calibration
      snapshot (cam-side blocks into the run's private chain copy, IMU-side in-memory -- the live
      hot-start mechanics); a flight that consumed a hot-start auto-reapplies the LOG-CAPTURED
      snapshot for parity (--no-calib-hotstart opts out); every run writes calibration.json
      provenance
    * ov_msckf: feed_measurement_simulation initializes via try_to_initialize instead of exiting --
      an observation-stream replay starts from a cold filter like a live boot
    * update cmake min version to 3.5
    * fix LD issue
    * async unsynced multi-camera support: lock-free per-camera ingest rings, epoch-anchored
      cloning, ACI2 preintegration bridge, deferred epoch marginalization (async meets synced;
      synced path bit-identical)
    * per-camera cam-IMU time offset and rolling-shutter readout states with online calibration and
      degenerate-motion gating
    * per-camera acquisition declarations (camN_shutter / camN_readout_time_s / camN_fps) with boot
      validation and config-drift guards
    * ceres-free dynamic initialization (ov_init::zbft_sfm): lock-free LM solver, S2 gravity, ~17-20
      ms init (legacy ceres path was ~65 ms); drop voxl-ceres-solver runtime dep
    * any-attitude init/reset: server gravity gate, heading re-origin, warm-start clone injection
    * divergence-aware soft reset (RESET_VIO_SOFT) with live bias priors; episode purge of used-
      features map and zupt flags
    * async-aware quality metric: windowed feature union + time-constant EMA (replaces exact-
      timestamp scoring that starved async rigs)
    * auto-reset hardening: estimator-health error mask (transport warnings no longer hard-reset),
      warning decay, re-init grace arming, reset-race guards, hard-reset null-deref fix
    * new fpvhires config (IMX664 rolling-shutter + AR0144 tracking, unsynced); fpv restored to
      synced duo AR0144; fpvhires and d8v6 wired into voxl-configure-open-vins
    * estimator verbosity driven by the YAML key (-v only raises to ALL; warnings can no longer be
      swallowed)
    * stale-code purge: CameraQueueFusion removed, dead config knobs/globals/branches removed (~1.3k
      lines); duplicate includes fixed
    * docs: ov_msckf/README_ASYNC.md design doc (epoch cloning, ACI2 bridge, RS model, resets, YAML
      reference); authorship/copyright pass across both repos

voxl-osd (v0.4.12 to v1.2.1)
    * flight mode switch positions with no saved ALT FAST/MED/SLOW choice keep the platform's
      PSET_MODE instead of forcing FAST
    * requires libmodal-pipe >= 2.15.0
    * Fix turtle mode OSD indicator
    * Require voxl-cross 4.9 for all build targets
    * detect D0020 V6 sub-revision SKUs (e.g. V6A)
    * Implemented AR waypoints
    * Integrate QT OSD
    * Add ELRS submenu
    * Integrate LibELRS
    * add LOG ON ARM toggle to Debug OSD menu

voxl-portal (v0.9.4 to v0.9.16)
    * ELRS: the preset and SKU defaults confirm modals list the parameters and current values of the
      compact name-keyed presets from voxl-elrs 1.2.1+
    * web UI scripts: index loops whose counter only reads arr[i] use for-of, Object.keys + obj[key]
      loops use Object.entries, and repeated reads of the same element go through one local (no
      behavior change)
    * suppress semgrep detect-object-injection where the index is provably a number (loop counters,
      index arithmetic, length/modulo clamps, ids decoded from binary frames)
    * Thermal Calibration: live sensor temperatures come from the portal's own imu_apps_raw,
      px4_sensor_imu_raw, px4_sensor_baro and cpu_monitor pipe subscriptions instead of parsing the
      CLI stdout, so they update whether or not a calibration is running and a sensor that stops
      publishing shows as disconnected
    * Thermal Calibration: the Apps IMU can be deselected (--no-apps) and at least one sensor must
      be selected to start
    * Thermal Calibration: the cooling modal is a panel with an adjustable cold-start threshold
      (remembered per browser), a cooling trend readout that reports when the warmest sensor has
      settled, and a Start Now button for enclosed boards that never reach the threshold
    * Thermal Calibration: DONE is reported only when voxl-calibrate-imu-temp reaches COMPUTING
      RESULTS and exits 0; a sensor pipe dropping mid-run now reports an error instead of success
    * Thermal Calibration: a page opened or reloaded mid-run restores the current stage and control
      state
    * Thermal Calibration: websocket messages from the worker threads are queued and sent from the
      mongoose thread
    * VINS Calibration Configure tab edits estimator_config.yaml in the server's yaml_folder; saves
      are refused while voxl-open-vins-server runs and rejected without the %YAML:1.0 directive or
      with a ':' in a trailing comment
    * flag a package-owned estimator config under /usr/share and the keys sync_config rewrites at
      boot (prop_window, zupt_prop_window, max_cameras, use_stereo)
    * Results tab writes a session's committed camera extrinsics into extrinsics.conf through voxl-
      calib-to-extrinsics: preview, map each camera to its entry, then write exactly the previewed
      request; refused while voxl-open-vins-server runs, extrinsics.conf.bak kept
    * flush configuration saves to storage before the atomic rename
    * voxl-logger capture and session record sources are picked from dropdowns like the Any-pipe
      camera: captures from /data/voxl-logger{,-auto} with their cameras and IMU (unusable ones
      disabled), records from /data/ov_calib and vins-lab tracker-only runs; a typed path still
      overrides
    * add estimator config, extrinsics write and source picker regression tests
    * use /data/ov_calib for new VINS calibration sessions
    * enable collection controls only when the backend session state permits them
    * render accepted/retained parameter decisions and raw uncertainty without inferring recovery
      from camera commits
    * show Tg guidance, solve-budget accounting, and timestamp/bootstrap diagnostics consistently
      across calibration and results views
    * add control-state and guidance regression tests
    * Solve now and Extend show the calibrator's accept/reject reply (collection already closed,
      recorded source) and delivery failures, stay disabled for session-record and voxl-logger
      sources, and Extend adds recording time only without changing the solve budget
    * live tiles show tracked rate and feature count for every camera instead of the first camera
      only
    * the collection bar also tracks the Tg precision screen (raw sigma against its target) when Tg
      is estimated
    * an aborted session says when the calibrator could not write its report
    * Results open the report of the session that just finished, remember the last viewed report,
      and ignore a slower earlier report request
    * Results warn when the solve budget skipped the lens refinement stages
    * Results show the gravity magnitude and its source; the magnitude is fixed and the direction
      estimated
    * the block chart and table call 3-sigma posterior/prior the prior ratio and state that it is
      one of several commit checks
    * gates: WALD_UNOBSERVABLE from older reports shows as WALD_NOT_CERTIFIED, a Wald result
      computed next to split-half authority shows as a diagnostic, verdict texts no longer claim
      unobservability, and Tg Wald verdicts are explained
    * page text: calibration runs with VIO stopped or alongside it, the flight profile uses the
      configured solve budget, and the bootstrap prompt asks for smooth yaw, pitch and roll sweeps
    * Solve now, Extend +30 s and Abort controls for a running session; Abort asks the calibrator to
      finalize through its control pipe and sends SIGTERM (since 0.9.12)
    * calibration from voxl-logger captures as well as live cameras and session records (since
      0.9.12)
    * one calibration session at a time; a second start reports the running session's pid (since
      0.9.12)
    * Configure view edits voxl-open-vins-server.conf and ov_calib.yaml; saves are refused while
      voxl-open-vins-server runs, keep a .bak and replace the file atomically, and a leftover
      ov_calib.yaml.dpkg-new is flagged (since 0.9.12)
    * Results view lists past sessions (per-session directories and the legacy flat layout) with
      committed blocks, time offset, prior vs posterior, parameter coupling, held-out verification,
      solve stages, bootstrap, committed values and gates (since 0.9.12)
    * deploy_to_voxl.sh syncs after installing the package (since 0.9.12)
    * add VINS Calibration for live camera sessions and recorded-session replay
    * support declared cameras and ad-hoc camera pipes with explicit intrinsics
    * relay session status and logs through the mongoose polling thread
    * show calibration verdicts, collection guidance, commit margins, uncertainty, and solve timing
    * separate raw time correlation, geometric initialization, provisional seeds, and committed
      offsets
    * fix scalar time-posterior lookup and omit uncertainty bands for retained time values
    * explain weak Tg precision and distinguish split-half gates from Wald gates
    * preserve configuration edits and use packaged defaults when the operator profile is absent
    * moved px4 cal files from /data/px4/param to /data/modalai/px4
    * warnings cleanup
    * fix width-only video dimensions
    * move third-party browser libraries to the voxl-portal-web-libs dependency
    * fix overflow with many camera pipes
    * force level horizon cal to happen after accel cal
    * vio page overhaul with improved live view and vins-lab
    * rendezvous and capture: Add follower/APN inspect
    * add follow_simple page for tracker-agnostic bbox subscribers

voxl-px4 (v1.14.0-2.0.142 to v1.18.0-1.0.9)
    * Wrap Linux settime to prevent GPS from setting the Linux realtime clock and affecting other
      services
    * Honor loopback interface when binding UDP sockets in Mavlink
    * Removed obsolete QURT definition that was preventing logging of GPS time
    * Fix to prevent qcs6490 system from deleting px4 lock files
    * Changed where sensor and thermal calibration files are stored to /data/modalai/px4
    * Switched to upstream tag v1.18.0-rc1
    * Switched from ModalAI voxl-dev to upstream PX4 release/1.18 at commit
      75395e11aa33f3cfa9496deeef314f4711368829
    * Raised minimum DSP package versions to modalai-slpi 1.2.3 or modalai-adsp 1.0.8
    * Fix Hexagon compiler NaN comparison issues in math library
    * Remove attempt to find DPS baro on bus 1 from DSP
    * Added another fence points QGC upload fix
    * Fix fence points QGC upload

voxl-px4-params (v0.9.15 to v0.9.35)
    * D0008 V6 and D0020 V5/V6 gravity fusion filtering, EKF2_GRAV_FTAU 3, EKF2_GRAV_FTAU_F 0.5,
      EKF2_GRAV_PERS 6, needs px4-firmware with these params, older firmware skips them
    * D0020 V6 and V6-XS mag current compensation for the short GPS mast, CAL_MAG0_XCOMP/YCOMP/ZCOMP
      -4.3/24.7/-3.2 (was the tall mast 13/8/9)
    * move D0020 V6 mag compensation out of the base file into short and tall mast files, mirrors
      D0008
    * voxl-configure-px4-params and -18 skip any params or cal file containing nan/inf, a bad baro
      thermal cal loaded nan into TC_B0_X0/X1 and PX4 then fails every param save verify
    * reset any param left at nan/inf in PX4 to default (PX4 v1.12+ only, older param reset takes an
      exclude list), and exit 5 listing the skipped files and reset params
    * validation reports nan/inf instead of "doesn't match 1", and a nan already in PX4 now shows as
      a diff so the file value overwrites it
    * D0008 MPC_TILTMAX_AIR and MPC_MAN_TILT_MAX 32 to match the ALT SLOW boot mode, fixes param
      validation failing with 32.0 vs 50.0
    * D0008 ALT FAST/MED/SLOW now differ, PSET tilt 70/50/32 to match the deploy overlay (all three
      were 50)
    * D0008 boots in ALT SLOW, PSET_MODE 3
    * D0008 V4/V5 PSET_CHANNEL 0 like V6, voxl-osd picks the ALT speed per switch position instead
      of a fixed low SLOW, mid MED, high FAST
    * D0013 boots in ALT SLOW, PSET_MODE 3, keeps its own PSET tilts and Z velocities
    * D0019 and D0020 get the PSET ALT FAST/MED/SLOW block (tilt 50/40/32, PSET_CHANNEL 0), boot in
      ALT FAST
    * set SENS_IMU_AUTOCAL 0 in VOXL_2_defaults.params, 1.18 enables it by default and it rewrites
      the accel offsets from the EKF2 bias estimate after every flight
    * lower EKF2_GPS_V_NOISE from 3.0 to 0.5 in ekf2_outdoor_stm_gps_mag, the param is a floor so
      EKF2 now uses the accuracy the GPS reports, better GPS position hold and less tilt
    * re-enable fake position for EKF2 alignment when not on the ground, EKF2_FP_ALIM back to the
      PX4 default of 2.0 in VOXL_2_defaults.params and the 0.0 overrides dropped from
      ekf2_universal_tweaks and FPV_Rev0, allows hand launch like 1.14
    * add EKF2_DELAY_MAX 250 to VOXL_2_defaults.params so EKF2 fuses every GPS sample on 1.18, fixes
      dropping out of position mode and tilting outdoors
    * D0019 loads the RTL helper like D0020
    * add MRB-D0019-XS param set for strike extras codes, mirrors MRB-D0020-XS on the D0019 base
    * disable turtle mode on MRB-D0019, MRB-D0020 and MRB-D0020-V6-XS, no prop guards on these
      frames
    * set SDLOG_PROFILE 2083 in VOXL_2_defaults.params, logs full rate sensor data and EKF2 replay
      without the FIFO topics so logs stay a reasonable size
    * remove SDLOG_MODE -1 from VOXL_2_fpv_defaults.params and D0008_V4_V5_base.params and drop the
      SDLOG_PROFILE 0 override from the fpv defaults so the VOXL 2 logging profile applies, artifact
      mode still has to be disabled for logging
    * D0008 V4/V5 (px4 1.18) now loads the same shared helper chain as V6 (battery, ekf2 tweaks,
      velocities, failsafes, rc loadout, turtle mode) plus a small V4/V5 tweaks file for baro
      noise/gate, flight tested
    * strip duplicated RC, battery, EKF2, and MPC params from the D0008 V4/V5 base file now that the
      helpers provide them
    * enable SYS_HAS_GPS on D0008 V4/V5 so the GPS driver runs while EKF2_GPS_CTRL keeps it out of
      the estimator
    * move D0008 V6 land/takeoff speeds and land detector limit into D0008_velocities.params
    * remove retired FLGT_VZ_MAX from all 1.18 param sets, MPC_POS_MODE 1 now uses
      MPC_Z_VEL_MAX_UP/DN
    * set PSET_FST/MED/SLW_VELZ together in the D0008 and D0020 velocity files so param_set_selector
      does not fight MPC_Z_VEL_MAX
    * lower D0020 MPC_Z_VEL_MAX_UP/DN from 6.0 to 5.0
    * restore D0013 PSET_MED_VELZ to 2.0
    * add D0008_V6_tweaks.params, loaded last, with EKF2_GRAV_RMAX 40.0 and EKF2_GRAV_RMODE 2 to
      gate gravity fusion on gyro rate and fix the D0008 V6 tilt drift in aggressive flight, needs
      the matching px4 update to take effect
    * set RC_CHAN_CNT and COM_FLTMODE_BOOT in tbs_tango2_stinger and zorro_blue_fpv_v5 v1.18 radio
      helpers, PX4 1.18 ignores the RC without RC_CHAN_CNT
    * migrate calibration backups to /data/modalai/px4
    * update outdated 1.14 param COM_RC_OVVERIDE to the new 1.18 MAN_OVVERRIDE_SPD param
    * detect px4 version from px4 bin
    * update D0008-XS to use flat pack mag comp params
    * fix incorrect filename reference for D0008 V6 flat battery mag compensation params
    * update D0008 V6 3x2 battery mag compensation
    * slow down D0008 V6 GPS POS mode velocities
    * update old temp cal file to new format during platform param load (during sdk update)
    * fix motor spin direction on D0008 V6
    * remove never-used D0008 V5 params, params stayed the same from V4 to V5
    * cleanup D0008 V6 params and do some tuning
    * update D0008 param set to support gps position mode
    * update alt mode baro params
    * update D0008-V6-XS flight modes
    * add the Mag ID for D0008 V6. This prevents mag params set during flash from being wiped during
      calibration due to the ID being blank
    * set COM_RC_IN_MODE to 2 (RC and Joystick) on D0008
    * add param file for disabling turtle mode
    * add inital px4 1.18 params

voxl-rc-server (v0.0.5 to v0.0.10)
    * factory_enable_silvus: VRX on a Silvus radio sends joystick RC over MAVLink straight to the
      drone at 172.19.16.10:14550
    * dgcs_tower config uses /dev/ttyHS6 for the CRSF TX on M0197 (MVX-S0001 V3), ttyHS1 elsewhere
    * give each platform it's own build directory
    * fix native build
    * add dtc config
    * Added stock radiomaster zorro loadout
    * skip UDP socket recreation when D2S IP is unchanged
    * fix arm/terminate rejected by PX4 due to broadcast target_system
    * Add unbind feature mirroring the existing bind chain
    * subscribe to voxl-vtx d2s_ip_conn pipe and update UDP destination

voxl-remote-id (v0.0.9 to v0.1.0)
    * Updated to reflect a manufacturer serial number length of 10 instead of 12
    * Updated for new voxl-cross docker and linter

voxl-state-estimator (v0.0.6 to v0.0.7)
    * add qcs6490 build target and CI job
    * linter fixes, use vcc_find_extrinsic

voxl-utils (v2.0.5 to v2.0.10)
    * voxl-configure-static-ip-watcher factory_enable_silvus_vrx: name the Silvus radio silvus0
      instead of usb0, the 0.3.x system images create the adb NCM gadget as usb0 when the USB-C port
      comes up in device mode (seen after a UCSI timeout), the radio's rename then failed and it got
      no IP, so the Silvus link and RC dropped with the display
    * voxl-configure-static-ip-watcher: factory_enable_silvus_drone (eth0 172.19.16.10/12) and
      factory_enable_silvus_vrx (Silvus usb0 172.18.71.10/12, developer USB-Ethernet eth0
      10.0.0.2/24, both marked unmanaged in NetworkManager so its DHCP cannot drop the address),
      plus a udev rule naming the radio (USB 0525:a4a2) usb0 whichever of cdc_ether/cdc_subset binds
      it
    * voxl-static-ip-watcher: treat an interface as up on LOWER_UP, not "state UP", USB NICs like a
      Silvus radio's report UNKNOWN
    * voxl-static-ip-watcher: only add the 10.0.0.0/24 route for an interface on 10.0.0.x, DTC VRX
      eth0/eth1 no longer swap it every loop
    * voxl-static-ip-watcher: match the exact address before assigning, fall back to iproute2 when
      ifconfig is not installed
    * voxl-inspect-services: hide voxl-wireguard and voxl-wg-config-api from the service list
    * voxl-wifi-legacy: quote args so SSIDs containing spaces are not split
    * voxl-wifi-legacy: open-network station mode and wpa_passphrase failure handling
    * voxl-wifi: validate station password length and fail on legacy script errors
    * update DTC default IP loadout

voxl-uvc-server (v0.1.8 to v0.1.9)
    * Fix linter errors
    * Updated min cmake version

voxl-vision-hub (v1.9.21 to v1.10.3)
    * add d0008v6 VFC config (hover 0.27, vxy_max 4, kp/kd_xy_vio 4/3, xy_acc_limit_vio 4)
    * add vfc params for d0008v6 vehicle
    * add attitude interface to avoid px4 ekf2 yaw snaps in VFC
    * seed z integrator from px4 thrust on transition into offboard, fixes altitude drop
    * pre-stream px4's live thrust before offboard instead of config hover thrust
    * no vertical velocity lead on position entry setpoint
    * revert atr changes, back to 1.9.21

voxl-vtx (v2.2.42 to v3.3.4)
    * Fix voxl-configure-vtx failing on SDK install: re-enumerate the VTX radio if it comes up with
      no network interface after the driver swap, and wait longer for wlan0 in set_efuse.sh
    * Update button mapping on gen2 VRX
    * Migrate production power measurement tool  backend to realtek mp
    * Fix hires zoom issue when not on a hires camera stream
    * Add support for zoom and white/black hot control via MSP (external fc use case)
    * voxl-configure-vtx/vrx --factory_enable_silvus: UDP over a Silvus radio, drone 172.19.16.10
      <-> VRX 172.18.71.10 (video to VRX:50000, back-channel to VTX:50001), VRX binds MAVLink
      telemetry on 172.18.71.10
    * Update FEC metrics: added effective FEC to VRX logging, debug OSD, reasons for lost FEC blocks
      are reported
    * Add secured state to OSD menu next to SDK version
    * requires voxl-osd >= 1.2.0 on the drone (MSP_VOXL_STRINGS v2)
    * Add --tracking_stereo to voxl-configure-vtx for stereo (tracking left/right) camera setups
    * Refactored vrx_encoded into it's own class and added headless mode that runs without
      constructing decoder
    * Fix hires zoom and thermal white/black hot conflict on the shared RC wheel
    * Fix turtle mode OSD indicator
    * Add RF occupancy scanner: sweeps the tunable band and shows per-channel occupancy and noise
      floor on the Qt OSD by pressing function + up/down button
    * restoring tracking-down crosshair rendering updates
    * Publish vtx_state pipe
    * Implemented AR Waypoint system
    * Update efuse setting script
    * Add framework for sending generic requests from vrx to vtx
    * Defer libelrs init until the first PX4 heartbeat when voxl-px4 is enabled, so loading
      libslpi_qrb5165_io.so onto the SLPI no longer blocks PX4 startup
    * Migrate to DRM/KMS rendering backend
    * Integrate QT OSD
    * Add adaptive link functionality and replace default profile with adaptive link profile
    * Add set_elrs_hardened_state VTX control command and OSD icon showing ELRS hardening state on
      the VRX