Skip to content

NetFetch Plugin

NetFetch is a minimal but complete plugin that fetches internet data and displays it in AMC. It demonstrates every hook type: data singleton, settings, upper fly view, menu item button, and menu item view.

What it does

  • Fetches data from three sources: current time (JS Date), mock weather, and ISS location (HTTP API).
  • Displays the result in the upper fly view and in a custom full-screen menu view.
  • Provides a settings panel with enable/disable toggle and data source selector.
  • Adds a button in the side menu drawer.

File structure

NetFetch/
├── plugin.json
├── build.sh
├── src/
│   ├── NetFetchData.qml           ← data singleton
│   ├── NetFetchSettings.qml       ← settings panel
│   ├── StatusBox.qml              ← shared status box component
│   ├── NetFetchUpperFlyView.qml   ← flight display widget
│   ├── NetFetchMenuItemButton.qml ← side menu button
│   └── NetFetchMenuItemView.qml   ← full-screen view
└── img/
    └── metadata.svg               ← menu button icon

plugin.json

{
    "name": "NetFetch",
    "description": "Fetch and display internet data",
    "author": "Auterion",
    "copyright": "(c) 2026 Auterion",
    "license": "MIT",
    "type": "qml"
}

NetFetchData.qml -- Data Singleton

This is the heart of the plugin. It holds all shared state and logic.

Key patterns demonstrated:

  • pragma Singleton declaration
  • enum DataSource for type-safe data source selection
  • Timer for periodic data fetching
  • XMLHttpRequest for HTTP calls (ISS location)
  • QtCore for persisting enabled and selectedDataSource
  • PluginManager.log() for 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
        }

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

    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()
    }

    // ... fetchTime() and fetchWeather() omitted for brevity

    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()
    }
}

NetFetchSettings.qml -- Settings Panel

The settings panel uses a collapsible pattern: a top bar with a CustomSwitch toggle and an expand/collapse chevron, followed by a settings grid that animates in/out.

Key patterns demonstrated:

  • CustomSwitch for enable/disable
  • QGCComboBox bound to the data singleton's labels and selected index
  • QGCButton for manual refresh
  • QGCColoredImage for a themed expand/collapse chevron
  • Animated height transition on the settings container
import QtQuick
import QtQuick.Layouts
import QGroundControl.Controls
import QGroundControl.Palette
import QGroundControl.ScreenTools
import QGroundControl.Plugin

ColumnLayout {
    property real _margins: ScreenTools.defaultFontPixelWidth

    // Top bar with switch + title + chevron
    Rectangle {
        Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 4
        Layout.fillWidth: true
        color: qgcPal.windowShade
        radius: ScreenTools.defaultFontPixelWidth * 2

        RowLayout {
            // ...
            CustomSwitch {
                checked: NetFetchData.enabled
                onClicked: NetFetchData.enabled = checked
            }
            QGCLabel {
                text: qsTr("NetFetch")
                font.pointSize: ScreenTools.largeFontPointSize
                font.family: ScreenTools.demiboldFontFamily
                font.bold: true
            }
            // chevron icon...
        }
    }

    // Collapsible settings area
    Rectangle {
        id: settingsContainer
        property bool hidden: !NetFetchData.enabled
        Layout.preferredHeight: hidden ? 0 : settingsGrid.height + _margins * 2
        clip: true
        color: qgcPal.windowShade

        Behavior on Layout.preferredHeight {
            NumberAnimation { duration: 250 }
        }

        GridLayout {
            id: settingsGrid
            columns: 2

            QGCLabel { text: qsTr("Data Source") }
            QGCComboBox {
                model: NetFetchData.dataSourceLabels
                currentIndex: NetFetchData.selectedDataSource
                onActivated: (index) => NetFetchData.selectedDataSource = index
            }

            QGCLabel { text: qsTr("Current Data") }
            QGCLabel {
                text: NetFetchData.currentData
                color: NetFetchData.lastError ?
                    qgcPal.colorOrange : qgcPal.text
            }

            QGCButton {
                text: qsTr("Refresh")
                Layout.columnSpan: 2
                enabled: NetFetchData.enabled && !NetFetchData.loading
                onClicked: NetFetchData.refresh()
            }
        }
    }
}

StatusBox.qml -- Shared Status Box

The status box is the main data display component, reused by both the UpperFlyView and MenuItemView. Extracting it into its own file avoids duplicating the UI across hooks.

Key patterns demonstrated:

  • Conditional border color based on state (loading / error / OK)
  • Pulse animation when loading
  • Spinning status icon with rotation animation
  • Data-change fade animation
  • Click-to-refresh via MouseArea
  • Update count badge

Naming

The filename deliberately avoids the plugin prefix and hook keywords (like data) to make it clear this is a private helper, not a hook target. Non-hook QML files within a plugin are available as local types to other files in the same plugin.

import QtQuick
import QtQuick.Layouts
import QGroundControl.Controls
import QGroundControl.Palette
import QGroundControl.ScreenTools
import QGroundControl.Plugin

Rectangle {
    id: root
    width: contentRow.width + (padding * 2)
    height: contentRow.height + (padding * 2)
    property real padding: ScreenTools.defaultFontPixelWidth

    color: qgcPal.windowShade
    border.width: 2
    border.color: NetFetchData.loading ? qgcPal.colorBlue :
                  NetFetchData.lastError ? qgcPal.colorOrange :
                  qgcPal.colorGreen
    radius: ScreenTools.defaultFontPixelWidth / 2
    visible: NetFetchData.enabled

    // ... pulse animation, status indicator, labels,
    //     update count badge, click-to-refresh MouseArea
}

NetFetchUpperFlyView.qml -- Flight Display Widget

Displays the shared status box in the upper fly view area.

import QtQuick
import QGroundControl.Plugin

StatusBox {
    Component.onCompleted: {
        PluginManager.log("NetFetch: Upper fly view component loaded")
    }
}

NetFetch demonstrates the full MenuItemButton + MenuItemView pattern.

NetFetchMenuItemButton.qml

Uses PluginMenuItemButton as the root element.

import QGroundControl.Widgets

PluginMenuItemButton {
    text: "Widgets"
    icon.source: Qt.resolvedUrl("../img/metadata.svg")
}

NetFetchMenuItemView.qml

A plain Item that centres the shared StatusBox.

import QtQuick

Item {
    anchors.fill: parent

    StatusBox {
        anchors.centerIn: parent
    }
}

Building

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

The build.sh script:

#!/bin/bash
PLUGIN_NAME="NetFetch"
OUTPUT_FILE="${PLUGIN_NAME}.plugin"
zip -r "${OUTPUT_FILE}" plugin.json src/ img/