Skip to content

Data Singleton

The Data hook is the backbone of a plugin. It creates a QML singleton that holds shared state and logic accessible from every other plugin file.

How it works

  1. Name your file with the data keyword: e.g., MyPluginData.qml.
  2. Start the file with pragma Singleton.
  3. Other plugin files import Plugin and reference the type by its filename (without .qml).
MyPluginData.qml  →  MyPluginData  (QML type name)
NetFetchData.qml  →  NetFetchData
MFDData.qml       →  MFDData

Naming rules

The filename must start with a capital letter (QML convention for type names). The type name used in other QML files must match the filename exactly.

Minimal example

pragma Singleton

import QtQuick
import QGroundControl.Plugin

Item {
    property bool enabled: false
    property string message: "Hello"
}

Consuming it from another plugin file:

import QGroundControl.Plugin

QGCLabel {
    text: MyPluginData.message
    visible: MyPluginData.enabled
}

Settings persistence

Use QtCore to persist user preferences across sessions. The Settings component maps QML properties to persistent storage using aliases:

pragma Singleton

import QtQuick
import QtCore
import QGroundControl.Plugin

Item {
    id: root

    property bool enabled: false
    property int selectedMode: 0

    Settings {
        id: settings
        category: "MyPlugin"
        property alias enabled: root.enabled
        property alias selectedMode: root.selectedMode
    }
}

The category groups your settings under a namespace to avoid collisions with other plugins or AMC itself.

Logging

Use PluginManager.log() to write messages to the AMC log:

Component.onCompleted: {
    PluginManager.log("MyPlugin: Data initialized")
}

Initialization

Component.onCompleted runs once when the singleton is created. Use it for setup logic:

Component.onCompleted: {
    PluginManager.log("MyPlugin: starting up")
    if (enabled) {
        startFetching()
    }
}

Reacting to property changes

Use on<Property>Changed handlers to respond when properties change:

onEnabledChanged: {
    if (enabled) {
        startFetching()
    } else {
        stopFetching()
    }
}

Full example: NetFetchData

The NetFetch plugin's data singleton demonstrates the complete pattern -- settings persistence, timers, HTTP requests, enums, and logging:

pragma Singleton

import QGroundControl
import QtQuick
import QtCore
import QGroundControl.Plugin

Item {
    id: root

    enum DataSource {
        CurrentTime,
        WeatherDemo,
        ISSLocation
    }

    property bool enabled: true
    property int selectedDataSource: NetFetchData.DataSource.CurrentTime
    property string currentData: "No data"
    property bool loading: false
    property string lastError: ""
    property int updateCount: 0

    readonly property var dataSourceLabels: [
        "Current Time", "Weather Demo", "ISS Location"
    ]

    Timer {
        id: updateTimer
        interval: 10000
        running: root.enabled
        repeat: true
        triggeredOnStart: true
        onTriggered: fetchData()
    }

    function fetchData() {
        if (!enabled) return
        loading = true
        lastError = ""

        switch (selectedDataSource) {
        case NetFetchData.DataSource.CurrentTime:
            fetchTime(); break
        case NetFetchData.DataSource.WeatherDemo:
            fetchWeather(); break
        case NetFetchData.DataSource.ISSLocation:
            fetchISS(); break
        default:
            currentData = "Unknown data source"
            loading = false
        }

        updateCount++
        PluginManager.log("NetFetch: Fetching data, source=" + selectedDataSource + ", count=" + updateCount)
    }

    function fetchTime() {
        Qt.callLater(function() {
            loading = false
            currentData = new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd HH:mm:ss")
            lastError = ""
        })
    }

    function fetchWeather() {
        Qt.callLater(function() {
            loading = false
            var temp = Math.floor(Math.random() * 30) + 10
            var conditions = ["Sunny", "Cloudy", "Rainy", "Windy"]
            currentData = conditions[Math.floor(Math.random() * conditions.length)] + ", " + temp + "°C"
            lastError = ""
        })
    }

    function fetchISS() {
        var xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function() {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                loading = false
                if (xhr.status === 200) {
                    try {
                        var r = JSON.parse(xhr.responseText)
                        var lat = parseFloat(r.iss_position.latitude).toFixed(2)
                        var lon = parseFloat(r.iss_position.longitude).toFixed(2)
                        currentData = "ISS: " + lat + "°, " + lon + "°"
                        lastError = ""
                    } catch (e) {
                        lastError = "Parse error: " + e
                        currentData = "Error: " + lastError
                    }
                } else {
                    lastError = "HTTP " + xhr.status
                    currentData = "Error: " + lastError
                }
            }
        }
        xhr.open("GET", "http://api.open-notify.org/iss-now.json")
        xhr.send()
    }

    function refresh() { fetchData() }

    Settings {
        id: settings
        category: "NetFetch"
        property alias enabled: root.enabled
        property alias selectedDataSource: root.selectedDataSource
    }

    Component.onCompleted: {
        PluginManager.log("NetFetch: Data component initialized")
        if (enabled) fetchData()
    }

    onEnabledChanged: {
        if (enabled) fetchData()
        else currentData = "Disabled"
    }

    onSelectedDataSourceChanged: {
        if (enabled) fetchData()
    }
}

Accessing vehicle data

The data singleton is a natural place to compute derived values from vehicle telemetry:

pragma Singleton

import QtQuick
import QGroundControl
import QGroundControl.Plugin

Item {
    readonly property var vehicle:
        QGroundControl.multiVehicleManager.activeVehicle

    readonly property bool hasVehicle: vehicle !== null

    readonly property string speedText:
        hasVehicle ? vehicle.groundSpeed.value.toFixed(1) : "--"

    readonly property string gpsInfo:
        hasVehicle ? vehicle.gps.count.valueString + " sats" : "No GPS"
}