Skip to content

MFD Plugin

The MFD (Multi-Functional Display) plugin lets users build their own instrument panel by adding, removing, and configuring display blocks from the settings page. Each block can show different telemetry data, and the entire layout is persisted across sessions using QtCore -- so the UI a user creates survives app restarts.

It demonstrates advanced patterns including dynamic component loading, shared JavaScript modules, SVG assets, settings persistence of dynamic lists, and rich vehicle telemetry access.

What it does

  • Users compose their own dashboard by adding MFD blocks from the settings panel.
  • Three display types are available: textual (numeric readouts), graphical (compass with wind direction), and gauges (vertical bar gauges).
  • Each block's type and data slots are configurable and persisted in settings, so the user's layout is restored on next launch.
  • Reads extensive boat telemetry: speed, heading, course, wind, engine, fuel, gearbox, rudder, and more.

File structure

MFD/
├── plugin.json
├── build.sh
├── MFD.qrc                  ← Qt resource file
├── qmldir                   ← QML module definition
├── src/
│   ├── MFDData.qml          ← data singleton
│   ├── MFDSettings.qml      ← settings panel
│   ├── MFDUpperFlyView.qml  ← instrument displays
│   └── Consts.js            ← shared constants
└── img/
    ├── compass_needle_blue.svg
    ├── compassWind.svg
    ├── compassOuter.svg
    └── expand.svg

Shared JavaScript: Consts.js

MFD uses a .pragma library JavaScript file to define shared constants. This pattern keeps magic numbers out of QML files and ensures consistency across all components.

.pragma library

const MFD_TYPES = Object.freeze({
    TEXTUAL: 0,
    GRAPHICAL: 1,
    GAUGES: 2
});

const MFD_TYPE_LABELS = ["Textual", "Graphical", "Gauges"];

const DATA = Object.freeze({
    NONE: 0, SPEED: 1, COURSE: 2, COG: 3, SOG: 4,
    HEADING: 5, WIND_SPEED: 6, WIND_DIRECTION: 7,
    ENGINE_STATE: 8, ENGINE_LOAD: 9, ENGINE_RPM: 10,
    // ... and more
});

const DATA_LABELS = [
    "Disabled", "Speed", "Course", "Course Over Ground",
    "Speed Over Ground", "Heading", "Wind Spd", "Wind Dir",
    // ... and more
];

const MFD_DATA_SLOTS = [
    ["Upper", "Lower"],                        // Textual
    ["Upper/Compass", "Lower", "Direction Arrow"], // Graphical
    ["Left", "Center", "Right"]                // Gauges
];

Import it in QML files:

import "Consts.js" as Consts

// Use constants
text: Consts.DATA_LABELS[dataIndex]
visible: dataIndex !== Consts.DATA.NONE

MFDData.qml -- Data Singleton

The MFD data singleton manages a dynamic list of MFD instances using ListModel and persists them via Settings.

Key patterns

Vehicle telemetry access via the boat shorthand:

readonly property var boat: {
    const vehicle = QGroundControl.multiVehicleManager.activeVehicle
    if (vehicle && vehicle.boat) {
        return vehicle
    } else {
        return null
    }
}

Dynamic data lookup function:

function getDataValue(dataIndex) {
    switch (dataIndex) {
    case Consts.DATA.SPEED:
        return boat && !isNaN(boat.groundSpeed.value)
            ? boat.groundSpeed.value.toFixed(1) : ""

    case Consts.DATA.HEADING:
        return boat && !isNaN(boat.heading.value)
            ? ((boat.heading.value + 360) % 360).toFixed(0) : ""

    case Consts.DATA.SOG:
        return boat && boat.sog
            ? QGroundControl.unitsConversion
                .metersPerSecondToAppSettingsSpeedUnits(boat.sog)
                .toFixed(1) : ""

    case Consts.DATA.WIND_SPEED:
        return boat && !isNaN(boat.wind.speed.value)
            ? boat.wind.speed.value : ""

    case Consts.DATA.ENGINE_RPM:
        return boat ? boat.engineRPM.toFixed(0) : ""

    // ... more cases
    }
}

Units lookup function:

function getDataUnits(dataIndex) {
    switch (dataIndex) {
    case Consts.DATA.SPEED:
        return QGroundControl.unitsConversion.appSettingsSpeedUnitsString
    case Consts.DATA.HEADING:
        return "°"
    case Consts.DATA.ENGINE_RPM:
        return "RPM"
    case Consts.DATA.ENGINE_TEMP:
        return "°C"
    case Consts.DATA.FUEL_LEVEL:
        return "L"
    // ...
    }
}

ListModel management:

ListModel { id: mfdList; dynamicRoles: true }

function addMFD(type) {
    mfdList.append({
        "type": type,
        "enabled": true,
        "data1": getSlotDefault(type, 0),
        "data2": getSlotDefault(type, 1),
        "data3": getSlotDefault(type, 2)
    })
    saveMfdListToSettings()
}

function removeMFD(index) {
    if (index >= 0 && index < mfdList.count) {
        mfdList.remove(index)
        saveMfdListToSettings()
    }
}

MFDSettings.qml -- Settings Panel

The settings panel uses a Repeater to generate a configuration box for each MFD instance, plus buttons to add new MFDs of each type.

Key patterns:

  • QGCFlickable for scrollable content
  • Repeater over MFDData.mfdList with per-item configuration
  • QGCComboBox for data slot selection, writing back to the model
  • Dynamic "Add" buttons with count badges
// Per-MFD configuration
Repeater {
    model: MFDData.mfdList.count
    delegate: Rectangle {
        readonly property int mfdIndex: modelData
        readonly property var mfd: MFDData.mfdList.get(mfdIndex)

        // Header: type label + delete button
        RowLayout {
            QGCLabel { text: Consts.MFD_TYPE_LABELS[mfd.type] }
            QGCButton {
                text: qsTr("Delete")
                onClicked: MFDData.removeMFD(mfdIndex)
            }
        }

        // Data slot combo boxes
        Flow {
            Repeater {
                model: Consts.MFD_DATA_SLOTS[mfd.type].length
                Column {
                    readonly property string keyName: "data" + (modelData + 1)
                    QGCLabel { text: Consts.MFD_DATA_SLOTS[mfd.type][modelData] }
                    QGCComboBox {
                        model: Consts.DATA_LABELS
                        currentIndex: mfd[keyName]
                        onActivated: (index) => {
                            MFDData.mfdList.setProperty(mfdIndex, keyName, index)
                            MFDData.saveMfdListToSettings()
                        }
                    }
                }
            }
        }
    }
}

MFDUpperFlyView.qml -- Instrument Displays

The upper fly view uses a Loader per MFD to dynamically instantiate the correct component based on type.

Dynamic component loading

Repeater {
    model: MFDData.mfdList
    delegate: Loader {
        readonly property var thisMFD: model
        readonly property int data1: thisMFD ? thisMFD.data1 : 0
        readonly property int data2: thisMFD ? thisMFD.data2 : 0
        readonly property int data3: thisMFD ? thisMFD.data3 : 0

        sourceComponent: {
            switch (model.type) {
            case Consts.MFD_TYPES.TEXTUAL:
                return textualMFDComponent
            case Consts.MFD_TYPES.GRAPHICAL:
                return graphicalMFDComponent
            case Consts.MFD_TYPES.GAUGES:
                return gaugesMFDComponent
            }
        }
    }
}

Textual MFD

Displays two numeric values with labels and units in a bordered rectangle:

Component {
    id: textualMFDComponent
    Rectangle {
        width: mfdSideLength
        height: width
        color: qgcPal.windowShadeDark
        border.color: qgcPal.colorRed

        // Upper data: label + value + units
        QGCLabel { text: Consts.DATA_LABELS[data1] }
        QGCLabel { text: MFDData.getDataValue(data1) }
        QGCLabel { text: MFDData.getDataUnits(data1) }

        // Lower data: value + units + label
        QGCLabel { text: MFDData.getDataValue(data2) }
        QGCLabel { text: MFDData.getDataUnits(data2) }
        QGCLabel { text: Consts.DATA_LABELS[data2] }
    }
}

Graphical MFD (Compass)

Displays a rotating compass with a directional arrow and data overlays:

  • Compass ring rotates based on heading data
  • Wind direction arrow drawn with QtQuick.Shapes
  • Vehicle icon overlay using QGCColoredImage with SVG
  • Horizontal rudder gauge at the bottom
Component {
    id: graphicalMFDComponent
    Rectangle {
        readonly property real compassHeading:
            Number(MFDData.getDataValue(data1)) * -1

        // Rotating compass ring
        Rectangle {
            rotation: compassHeading
            // N/S/E/W labels, inner ring
        }

        // Wind direction arrow using Shape + ShapePath
        Shape {
            ShapePath {
                fillColor: "transparent"
                strokeColor: qgcPal.colorBlue
                // Arrow geometry
            }
        }

        // Vehicle icon
        QGCColoredImage {
            source: "/images/compass_needle_blue.svg"
            color: qgcPal.colorGreen
        }
    }
}

Gauges MFD

Displays 1-3 vertical bar gauges with auto-scaling min/max:

Component {
    id: gaugesMFDComponent
    Rectangle {
        RowLayout {
            Repeater {
                model: Consts.MAX_GAUGES
                delegate: ColumnLayout {
                    readonly property real value:
                        Number(MFDData.getDataValue(dataIndex))
                    property real valueMin: 0
                    property real valueMax: 1

                    onValueChanged: {
                        valueMin = Math.min(value, valueMin)
                        valueMax = Math.max(value, valueMax)
                    }

                    // Label, vertical bar, min/max/units
                }
            }
        }
    }
}

Telemetry accessed

The MFD plugin reads the following vehicle properties:

Data Access path
Ground speed vehicle.groundSpeed.value
Heading vehicle.heading.value
Course vehicle.usvCourse
Course over ground vehicle.gps.courseOverGround.value
Speed over ground vehicle.sog
Wind speed vehicle.wind.speed.value
Wind direction vehicle.wind.direction.value
Engine state vehicle.engineState
Engine load vehicle.engineLoad
Engine RPM vehicle.engineRPM
Gearbox state vehicle.gearboxState
Gearbox position vehicle.gearboxPos
Engine temp vehicle.engineTemp
Fuel level vehicle.fuel.level
Fuel max vehicle.fuel.maximum
Fuel consumed vehicle.fuel.consumed
Fuel rate vehicle.fuel.consumptionRate
Trim position vehicle.engineTrimPos
Trim state vehicle.engineTrimState
Throttle vehicle.throttlePos
Rudder position vehicle.rudderPos
Rudder state vehicle.rudderState
Oil pressure vehicle.oilPressure

Building

cd examples/v2/MFD
./build.sh
cp MFD.plugin ~/Documents/"Auterion Mission Control"/Plugins/