voxl-osd
Renders the on-screen telemetry overlay for FPV video as MSP DisplayPort text, sent to voxl-vtx or an HDZero VTX.
Table of contents
- Overview
- Data Flow
- voxl-configure-osd
- Configuration File
- OSD Menu
- External Flight Controller (MSP)
- HDZero Output
- Debugging
- Source Code
Overview
voxl-osd draws the on-screen display (OSD) for FPV video: battery, RC link, flight mode, arm state, position, heading, VIO state, VTX temperature and power, and warnings. It does not touch video pixels. It lays out a character-cell text canvas and streams it as MSP DisplayPort commands, which the video transmitter composites over the live video. On ModalAI FPV products the consumer is voxl-vtx; the same canvas can also be sent over UART to a third-party HDZero VTX.
The service also owns the OSD menu, a stick-driven menu shown in the goggles for changing flight modes, failsafe behavior, logging, and VTX settings without a ground station.
voxl-osd is enabled by voxl-configure-mpa on FPV SKUs such as Stinger and the VTX Air Unit, and disabled by default everywhere else. It runs as the systemd service voxl-osd.service, starting after voxl-px4 (without requiring it) and restarting automatically.

Data Flow
By default the service reads everything it displays from MPA pipes and writes the finished canvas to one output pipe.
| Pipe | Direction | Used for |
|---|---|---|
mavlink_onboard | in | PX4 telemetry from voxl-mavlink-server: battery, attitude, GPS, RC channels, arm state, flight mode |
mavlink_from_gcs | in | MANUAL_CONTROL from the ground, so the menu also responds to a joystick sent over IP |
ov, qvio | in | VIO quality from voxl-open-vins-server or voxl-qvio-server |
vfc | in | Assisted flight mode state from voxl-vision-hub, shown instead of PX4’s “OFFBOARD” |
vtx_latency | in | VTX temperature, TX power, and link stats from voxl-vtx |
cpu_stats2 | in | CPU and memory usage from voxl-cpu-monitor, for the optional usage lines and overload warnings |
payload_status | in | Optional payload arm and position strings shown at the top right |
msp_osd | out | MSP DisplayPort packets consumed by voxl-vtx |
msp_ext_fc_status | out | Arm state of an external flight controller, published only in MSP input mode |
In external flight controller mode (see below) the MAVLink, VIO, and VFC inputs are replaced by an MSP UART link to the other flight controller.
voxl-configure-osd
voxl-configure-osd writes /etc/modalai/voxl-osd.conf and enables or disables the service. It must be run as root.
voxl-configure-osd factory_enable # reset the config to defaults and enable
voxl-configure-osd factory_enable_no_compass # defaults with the compass tape and degrees hidden
voxl-configure-osd external_fc # take input from an external MSP flight controller over UART
voxl-configure-osd hdzero # send the canvas over UART to an HDZero VTX
voxl-configure-osd enable # enable the service without touching the config
voxl-configure-osd disable # stop and disable the service
external_fc and hdzero pick the UART by platform:
| Platform | external_fc input port | hdzero output port |
|---|---|---|
| M0054, M0154 (VOXL 2) | /dev/ttyHS1 | /dev/ttyHS1 |
| M0104, M0204 (VOXL 2 Mini) | /dev/ttyHS0 | /dev/ttyHS0 |
Other platforms fall back to /dev/ttyHS1 for input and leave the output port empty. Edit input_port or output_port in the config file if your wiring differs.
Configuration File
/etc/modalai/voxl-osd.conf is JSON. It is created with defaults the first time the service runs, and any missing keys are added with their defaults on load. To print the loaded values without starting the service:
voxl-osd -c
Input and Output
| Field | Default | Meaning |
|---|---|---|
input_protocol | 1 | 1 = MAVLink, 2 = MSP |
input_type | 1 | 1 = MPA pipes, 2 = UART |
input_port | "" | UART device when input_type is 2 |
output_type | 1 | 0 = none, 1 = MPA (to voxl-vtx), 2 = UART (HDZero), 3 = both |
output_port | "" | UART device when output includes UART |
emit_canvas | true | Emit the DisplayPort text canvas on the MPA output. UART output is never gated by this |
resolution | 1 | Canvas size in character cells: 0 = SD 30x16, 1 = HD 50x18, 2 = HD 30x16, 3 = HD 53x20 |
font_type | 0 | Font index reported to the VTX |
Display Toggles
| Field | Default | Meaning |
|---|---|---|
show_compass_tape | true | Scrolling compass tape along the top of the canvas |
show_compass_degrees | true | Numeric heading in degrees. Shown even when the tape is off |
compass_fov_deg | 120 | Field of view of the compass tape |
show_mgrs | true | MGRS grid reference line |
mgrs_precision | 5 | MGRS digits per axis, 0 to 5 |
craft_name | "" | Free-text craft name drawn on the canvas |
show_vtx_cpu_mem_usage | false | VTX CPU and memory usage lines |
show_vrx_stream_stats | false | VRX stream statistics |
show_vio_overlay | false | VIO overlay |
show_thermal_flow | false | Thermal flow status |
show_mavlink | false | Raw MAVLink status |
Element Positions
Every movable element has a *_col / *_row pair giving its character cell, origin top left. Set either value to -1 to hide the element. Defaults are for the HD 50x18 canvas.
| Element | Fields | Default (col, row) |
|---|---|---|
| RC link RSSI | rssi_col, rssi_row | 0, 0 |
| Compass tape | compass_col, compass_row | 22, 0 |
| VIO state | vio_col, vio_row | 1, 1 |
| Payload actuator state | payload_actuator_col, payload_actuator_row | 42, 2 |
| Payload status | payload_status_col, payload_status_row | 42, 3 |
| Craft name | craft_name_col, craft_name_row | 10, 3 |
| VTX temperature | vtx_temp_col, vtx_temp_row | 0, 4 |
| VTX TX power | vtx_dbm_col, vtx_dbm_row | 0, 5 |
| VTX CPU usage | vtx_cpu_usage_col, vtx_cpu_usage_row | 0, 6 |
| VTX memory usage | vtx_mem_usage_col, vtx_mem_usage_row | 0, 7 |
| Crosshair | crosshair_col, crosshair_row | 26, 8 |
| Velocity | velocity_col, velocity_row | 47, 13 |
| Altitude | altitude_col, altitude_row | 47, 14 |
| Distance and direction to home | to_home_col, to_home_row | 39, 14 |
| VTX overheat warning | vtx_overheat_warn_col, vtx_overheat_warn_row | 0, 14 |
| VTX overheat critical | vtx_overheat_crit_col, vtx_overheat_crit_row | 0, 15 |
| MGRS grid reference | mgrs_col, mgrs_row | 39, 15 |
| Armed / disarmed | disarmed_col, disarmed_row | 25, 15 |
| VTX CPU overload warning | vtx_overload_col, vtx_overload_row | 0, 16 |
| Latitude | latitude_col, latitude_row | 46, 16 |
| Status text | status_col, status_row | 24, 17 |
| VTX memory warning | vtx_mem_leak_col, vtx_mem_leak_row | 0, 17 |
| Longitude | longitude_col, longitude_row | 46, 17 |
| Flight mode | flight_mode_col, flight_mode_row | 24, 19 |
Current draw, pack voltage, and per-cell voltage are pinned to the bottom right (column 43, rows 17 to 19) and cannot be moved.
Menu and Flight Mode Fields
| Field | Default | Meaning |
|---|---|---|
menu_up_threshold | 1550 | Pitch stick value (us) above which the cursor moves up |
menu_down_threshold | 1450 | Pitch stick value below which the cursor moves down |
menu_right_threshold | 1550 | Roll stick value above which a value is incremented |
menu_left_threshold | 1450 | Roll stick value below which a value is decremented |
fltmode1_pset_mode, fltmode4_pset_mode, fltmode6_pset_mode | 0 | VFC assisted mode assigned to each flight-mode switch position, written by the Flight Mode menu |
shortcut | "OPT_A" | Unused, kept for compatibility |
Menu thresholds were 1650 / 1350 / 1850 / 1150 in voxl-osd 0.4.x. Versions from 1.0.0 on use the values above, so the menu needs less stick travel.
OSD Menu
With the vehicle disarmed, hold yaw up, throttle down, roll down, pitch down to open the menu. It always opens at the main menu. Pitch up and down moves the cursor. Roll left and right changes the value on the selected row or enters the highlighted submenu. Every menu ends with BACK, EXIT (discard), and SAVE AND EXIT rows. When flying over IP with voxl-joystick-server instead of an RC link, the same inputs come from the joystick.
| Menu | Rows | What it changes |
|---|---|---|
| Main | FLIGHT MODE, ADVANCED | Entry point |
| Flight Mode | FLIGHT MODE, SWITCH VALUE | Assigns a mode to the switch position currently selected on the transmitter. Writes COM_FLTMODE* and PSET_MODE and pushes a live SET_MODE over MAVLink. Modes that need GPS are hidden when SYS_HAS_GPS is off. Not shown for an external flight controller |
| Advanced | LOGGING, RC CHANNELS, FAILSAFE, and on newer versions ELRS and AR WAYPOINTS | Submenu index |
| Logging | VOXL-LOGGER-AUTO, DEBUG LOG | Toggles automatic voxl-logger recording on arm, and sets the VTX debug log to DISABLED, ENABLED, or ON ARM via voxl-send-command vtx_latency debug_log |
| RC Channels | live table | Read-only view of every RC channel value |
| Failsafe | RC LOSS TIME, RC LOSS ACT, RC FINAL DELAY, RC FINAL ACT, GCS LOSS TIME, GCS LOSS ACT | Writes COM_RC_LOSS_T, NAV_RCL_ACT, COM_RCL_ACT_T, COM_RCL_ACT, COM_DL_LOSS_T, and NAV_DLL_ACT. Actions: AUTO RTL, AUTO LAND, HOLD, TERMINATE, DISARM, DESCEND, plus DISABLED for GCS loss |
| ELRS | Custom Domain RX | ELRS receiver option, voxl-osd 0.4.15 and later |
| AR Waypoints | setup prompts | Augmented-reality waypoint markers on the video, voxl-osd 1.1.0 and later |
The menu can also change VTX channel, video profile, and PIT mode. Those rows are driven by voxl-vtx and are described in the VTX Air Unit Software Guide.
External Flight Controller (MSP)
The VTX can act as the digital video system for a non-VOXL flight controller that speaks MSP over UART, such as Betaflight:
voxl-configure-osd external_fc
This sets input_type to UART and input_protocol to MSP. The service then polls the external flight controller at 20 Hz for RC channels, status, box IDs, battery, analog, and GPS, renders the OSD from those, and publishes the arm state on msp_ext_fc_status. No MAVLink or PX4 is involved. Wiring and cable options are in the VTX Air Unit Quick Start Guide.
HDZero Output
To drive a third-party HDZero VTX instead of, or as well as, voxl-vtx:
voxl-configure-osd hdzero
This sets output_type to UART and the platform’s default output_port. Set output_type to 3 to keep the MPA output as well. See VOXL 2 with Third-Party VTX for the hardware side.
Debugging
voxl-inspect-services # is voxl-osd running and enabled?
journalctl -u voxl-osd -f # live service log
voxl-osd -c # print the loaded config and exit
ls /run/mpa/ # msp_osd should exist once the service is up
- No OSD in the goggles but video is fine: check that
voxl-osdis running and thatoutput_typeis1or3.voxl-vtxmust be running to composite the canvas. - Telemetry fields blank: the service is up but
mavlink_onboardhas no data. Check voxl-mavlink-server and voxl-px4. - Menu does not open: the vehicle must be disarmed, and the sticks must pass the thresholds in the config. The RC Channels menu is the quickest way to see the values the service is reading.
- After editing the config file, restart the service with
systemctl restart voxl-osd.
Source Code
Source is at voxl-public/voxl-sdk/services/voxl-osd. It builds in the voxl-cross docker image per the repository README. Package versions shipped in each SDK are in the release notes.