From a3f76854bd6056eebdfa82ff6812e19e30f2db19 Mon Sep 17 00:00:00 2001 From: Attila Kobor Date: Sat, 24 Oct 2026 23:04:56 +0200 Subject: [PATCH 01/16] Implement flight session workflow and native GCS build/release CI --- .github/workflows/desktop.yml | 65 ++- .github/workflows/gcs.yml | 188 +++++++++ .github/workflows/tests.yml | 64 +++ COMPATIBILITY.md | 22 + DEVELOPING.md | 82 +++- README.md | 36 +- build/config.yml | 4 +- build/darwin/Info.dev.plist | 4 +- build/darwin/Info.plist | 4 +- build/gcs-versions.json | 12 + build/linux/nfpm/nfpm.yaml | 2 +- build/windows/info.json | 4 +- build/windows/msix/app_manifest.xml | 2 +- build/windows/msix/template.xml | 2 +- build/windows/nsis/project.nsi | 2 +- build/windows/nsis/wails_tools.nsh | 2 +- build/windows/wails.exe.manifest | 2 +- cmd/mission-planner-plugin/main.go | 22 + frontend/bindings/encoding/json/index.ts | 6 + frontend/bindings/encoding/json/models.ts | 9 + .../utm-adapter/internal/adapter/index.ts | 7 + .../utm-adapter/internal/adapter/models.ts | 68 ++++ .../utm-adapter/internal/adapter/service.ts | 37 ++ frontend/package-lock.json | 4 +- frontend/package.json | 2 +- frontend/src/App.svelte | 54 ++- go.mod | 8 + go.sum | 46 +++ integrations/mission-planner/README.md | 16 + .../qgroundcontrol/AdapterRestrictions.qml | 103 +++++ .../qgroundcontrol/custom/CMakeLists.txt | 5 + .../custom/cmake/CustomOverrides.cmake | 2 + integrations/qgroundcontrol/custom/custom.qrc | 6 + .../custom/src/OpenUTMPlugin.cc | 40 ++ .../qgroundcontrol/custom/src/OpenUTMPlugin.h | 22 + .../qgroundcontrol/tests/tst_Restrictions.qml | 78 ++++ internal/adapter/blender.go | 10 +- internal/adapter/config.go | 20 +- internal/adapter/gcsconfigure.go | 51 ++- internal/adapter/gcsconfigure_test.go | 35 ++ internal/adapter/integration_test.go | 370 +++++++++++++++++ internal/adapter/native/Restrictions.cs.txt | 59 +++ internal/adapter/records.go | 375 ++++++++++++++++++ internal/adapter/records_test.go | 162 ++++++++ internal/adapter/restrictions.go | 154 +++++++ internal/adapter/service.go | 131 ++++-- internal/adapter/service_test.go | 13 +- internal/adapter/sessions.go | 335 ++++++++++++++++ internal/adapter/sync.go | 163 ++++++++ main.go | 2 +- scripts/build-mission-planner.ps1 | 25 ++ scripts/check-version.mjs | 11 + scripts/package-gcs.py | 62 +++ scripts/prepare-qgc.py | 32 ++ scripts/test-flight-blender.sh | 40 ++ scripts/test_mission_planner_package.ps1 | 33 ++ scripts/test_package_gcs.py | 52 +++ scripts/test_prepare_qgc.py | 38 ++ 58 files changed, 3108 insertions(+), 97 deletions(-) create mode 100644 .github/workflows/gcs.yml create mode 100644 COMPATIBILITY.md create mode 100644 build/gcs-versions.json create mode 100644 cmd/mission-planner-plugin/main.go create mode 100644 frontend/bindings/encoding/json/index.ts create mode 100644 frontend/bindings/encoding/json/models.ts create mode 100644 integrations/mission-planner/README.md create mode 100644 integrations/qgroundcontrol/AdapterRestrictions.qml create mode 100644 integrations/qgroundcontrol/custom/CMakeLists.txt create mode 100644 integrations/qgroundcontrol/custom/cmake/CustomOverrides.cmake create mode 100644 integrations/qgroundcontrol/custom/custom.qrc create mode 100644 integrations/qgroundcontrol/custom/src/OpenUTMPlugin.cc create mode 100644 integrations/qgroundcontrol/custom/src/OpenUTMPlugin.h create mode 100644 integrations/qgroundcontrol/tests/tst_Restrictions.qml create mode 100644 internal/adapter/integration_test.go create mode 100644 internal/adapter/native/Restrictions.cs.txt create mode 100644 internal/adapter/records.go create mode 100644 internal/adapter/records_test.go create mode 100644 internal/adapter/restrictions.go create mode 100644 internal/adapter/sessions.go create mode 100644 internal/adapter/sync.go create mode 100644 scripts/build-mission-planner.ps1 create mode 100644 scripts/check-version.mjs create mode 100644 scripts/package-gcs.py create mode 100755 scripts/prepare-qgc.py create mode 100755 scripts/test-flight-blender.sh create mode 100644 scripts/test_mission_planner_package.ps1 create mode 100644 scripts/test_package_gcs.py create mode 100644 scripts/test_prepare_qgc.py diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index f220c1f..affb402 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -2,7 +2,7 @@ name: Desktop binaries on: push: - tags: ['**'] + tags: ['v*'] workflow_dispatch: permissions: @@ -13,6 +13,13 @@ concurrency: cancel-in-progress: true jobs: + gcs: + name: GCS packages + uses: ./.github/workflows/gcs.yml + permissions: + contents: read + actions: read + build: name: ${{ matrix.target }} strategy: @@ -57,6 +64,8 @@ jobs: run: bash build/ci/install-linux-deps.sh - name: Install pinned Wails CLI run: go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.34 + - name: Validate version metadata + run: node scripts/check-version.mjs - name: Backend tests and vet run: | go test -race ./internal/... @@ -80,6 +89,22 @@ jobs: run: | wails3 build GOARCH=amd64 tar -czf bin/utm-adapter-linux-amd64.tar.gz -C bin utm-adapter + - name: Package Linux AppImage + if: runner.os == 'Linux' + run: | + wails3 task linux:create:appimage + image=$(find bin -maxdepth 1 -name '*.AppImage' -print -quit) + test -n "$image" + mv "$image" bin/utm-adapter-linux-amd64.AppImage + - name: Package Windows installer with WebView2 bootstrapper + if: runner.os == 'Windows' + run: | + choco install nsis --yes --no-progress + export PATH="/c/Program Files (x86)/NSIS:$PATH" + wails3 task windows:create:nsis:installer INSTALL_SCOPE=user + installer=$(find bin -maxdepth 1 -name '*-installer.exe' -print -quit) + test -n "$installer" + mv "$installer" bin/utm-adapter-windows-amd64-installer.exe - name: Check Svelte and TypeScript run: npm --prefix frontend run check - name: Generate download checksum @@ -87,10 +112,11 @@ jobs: TARGET: ${{ matrix.target }} run: | node --input-type=module <<'JS' - import { readFileSync, writeFileSync } from 'node:fs'; + import { readFileSync, writeFileSync, readdirSync } from 'node:fs'; import { createHash } from 'node:crypto'; - const name = `utm-adapter-${process.env.TARGET}.${process.env.TARGET.startsWith('linux') ? 'tar.gz' : 'zip'}`; - writeFileSync(`bin/${name}.sha256`, `${createHash('sha256').update(readFileSync(`bin/${name}`)).digest('hex')} ${name}\n`); + for (const name of readdirSync('bin').filter(n => n.startsWith('utm-adapter-') && /\.(zip|tar\.gz|exe|AppImage)$/.test(n))) { + writeFileSync(`bin/${name}.sha256`, `${createHash('sha256').update(readFileSync(`bin/${name}`)).digest('hex')} ${name}\n`); + } JS - uses: actions/upload-artifact@v4 with: @@ -98,6 +124,35 @@ jobs: path: | bin/utm-adapter-${{ matrix.target }}.zip bin/utm-adapter-${{ matrix.target }}.tar.gz - bin/utm-adapter-${{ matrix.target }}.*.sha256 + bin/utm-adapter-${{ matrix.target }}.AppImage + bin/utm-adapter-${{ matrix.target }}-installer.exe + bin/utm-adapter-${{ matrix.target }}*.sha256 if-no-files-found: error retention-days: 30 + + release: + name: Publish verified downloads + if: startsWith(github.ref, 'refs/tags/v') + needs: [build, gcs] + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + pattern: utm-adapter-* + merge-multiple: true + path: release + - name: Verify checksums and publish + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + cd release + sha256sum --check ./*.sha256 + cp ../COMPATIBILITY.md release-notes.md + tar -czf utm-adapter-gcs-integrations.tar.gz -C .. integrations scripts/prepare-qgc.py build/gcs-versions.json + sha256sum utm-adapter-gcs-integrations.tar.gz > utm-adapter-gcs-integrations.tar.gz.sha256 + gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --draft --title "UTM Adapter $RELEASE_TAG" --notes-file release-notes.md ./*.zip ./*.tar.gz ./*.exe ./*.AppImage ./*.dmg ./*.build.json ./*.sha256 + gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --draft=false diff --git a/.github/workflows/gcs.yml b/.github/workflows/gcs.yml new file mode 100644 index 0000000..91011bb --- /dev/null +++ b/.github/workflows/gcs.yml @@ -0,0 +1,188 @@ +name: GCS builds + +on: + workflow_call: + workflow_dispatch: + pull_request: + paths: + - '.github/workflows/gcs.yml' + - '.github/workflows/desktop.yml' + - 'build/gcs-versions.json' + - 'cmd/mission-planner-plugin/**' + - 'integrations/**' + - 'internal/adapter/gcsconfigure*' + - 'internal/adapter/native/**' + - 'scripts/*gcs*' + - 'scripts/*qgc*' + - 'scripts/*mission-planner*' + +permissions: + contents: read + actions: read + +jobs: + pins: + runs-on: ubuntu-24.04 + outputs: + qgc-ref: ${{ steps.pins.outputs.qgc-ref }} + qgc-repository: ${{ steps.pins.outputs.qgc-repository }} + steps: + - uses: actions/checkout@v4 + - id: pins + run: | + echo "qgc-ref=$(jq -r .qgroundcontrol.commit build/gcs-versions.json)" >> "$GITHUB_OUTPUT" + echo "qgc-repository=$(jq -r .qgroundcontrol.repository build/gcs-versions.json)" >> "$GITHUB_OUTPUT" + + qgroundcontrol: + name: QGroundControl (${{ matrix.target }}) + needs: pins + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-24.04 + host: linux + arch: linux_gcc_64 + target: linux-amd64 + - runner: windows-2022 + host: windows + arch: win64_msvc2022_64 + target: windows-amd64 + - runner: macos-15 + host: mac + arch: clang_64 + target: macos-universal + runs-on: ${{ matrix.runner }} + timeout-minutes: 180 + defaults: + run: + shell: bash + steps: + # Upstream composite actions expect QGC at GITHUB_WORKSPACE, including nested actions. + - uses: actions/checkout@v4 + with: + repository: ${{ needs.pins.outputs.qgc-repository }} + ref: ${{ needs.pins.outputs.qgc-ref }} + submodules: recursive + fetch-depth: 0 + persist-credentials: false + - uses: actions/checkout@v4 + with: + path: adapter + persist-credentials: false + - name: Verify pinned upstream and prepare minimal overlay + env: + QGC_REF: ${{ needs.pins.outputs.qgc-ref }} + run: | + test "$(git rev-parse HEAD)" = "$QGC_REF" + python3 adapter/scripts/prepare-qgc.py . + python3 -m unittest discover -s adapter/scripts -p 'test_*.py' + - name: Set up upstream Qt, compiler and build tools + uses: ./.github/actions/build-setup + with: + qt-host: ${{ matrix.host }} + qt-arch: ${{ matrix.arch }} + build-type: Release + cache-variant: openutm + aqt-source: git+https://github.com/miurahr/aqtinstall.git@8d961e61720b3c06583517fbc68d57ec0a4e9f95 + - uses: ./.github/actions/install-dependencies + with: + nsis: ${{ runner.os == 'Windows' && 'true' || 'false' }} + - name: Test native restriction map + env: + QSG_RHI_BACKEND: software + run: | + qmllint adapter/integrations/qgroundcontrol/AdapterRestrictions.qml + qmltestrunner -platform offscreen -input adapter/integrations/qgroundcontrol/tests + - uses: ./.github/actions/cmake-configure + with: + build-dir: ${{ runner.temp }}/build + stable: 'true' + extra-args: >- + -DQGC_CUSTOM_DIR=custom-openutm-adapter + -DQGC_BUILD_TESTING=OFF + -DQGC_MACOS_SIGN_WITH_IDENTITY=OFF + - uses: ./.github/actions/cmake-build + with: + build-dir: ${{ runner.temp }}/build + parallel-jobs: '3' + - uses: ./.github/actions/cmake-install + with: + build-dir: ${{ runner.temp }}/build + - name: Install Windows package for smoke test + if: runner.os == 'Windows' + shell: pwsh + run: | + $installers = @(Get-ChildItem "$env:RUNNER_TEMP/build" -Filter '*installer*.exe') + if ($installers.Count -ne 1) { throw 'Expected exactly one QGC installer' } + $destination = "$env:RUNNER_TEMP/qgc-install-test" + $process = Start-Process $installers[0].FullName -ArgumentList '/S', "/D=$destination" -Wait -PassThru + if ($process.ExitCode -ne 0) { throw "Installer failed: $($process.ExitCode)" } + - name: Mount macOS package for smoke test + if: runner.os == 'macOS' + run: hdiutil attach "$RUNNER_TEMP/build/QGroundControl.dmg" -mountpoint "$RUNNER_TEMP/qgc-dmg" -nobrowse + - name: Boot packaged QGC + uses: ./.github/actions/verify-executable + with: + binary-path: ${{ runner.os == 'macOS' && format('{0}/qgc-dmg/QGroundControl.app/Contents/MacOS/QGroundControl', runner.temp) || runner.os == 'Windows' && format('{0}/qgc-install-test/bin/QGroundControl.exe', runner.temp) || format('{0}/build/QGroundControl-x86_64.AppImage', runner.temp) }} + expect-archs: ${{ runner.os == 'macOS' && 'x86_64h arm64' || '' }} + - name: Stage packages, provenance and checksums + env: + TARGET: ${{ matrix.target }} + ADAPTER_COMMIT: ${{ github.sha }} + run: | + case "$RUNNER_OS" in + macOS) package="$RUNNER_TEMP/build/QGroundControl.dmg" ;; + Linux) package="$RUNNER_TEMP/build/QGroundControl-x86_64.AppImage" ;; + Windows) package=$(find "$RUNNER_TEMP/build" -maxdepth 1 -iname '*installer*.exe' -print -quit) ;; + esac + python3 adapter/scripts/package-gcs.py --component qgroundcontrol --target "$TARGET" --input "$package" --output adapter/bin/gcs --adapter-commit "$ADAPTER_COMMIT" + - name: Unmount macOS package + if: always() && runner.os == 'macOS' + run: hdiutil detach "$RUNNER_TEMP/qgc-dmg" || true + - uses: actions/upload-artifact@v4 + with: + name: utm-adapter-gcs-qgroundcontrol-${{ matrix.target }} + path: adapter/bin/gcs/* + if-no-files-found: error + retention-days: 30 + + mission-planner: + name: Mission Planner plugin (Windows x64) + runs-on: windows-2025 + timeout-minutes: 30 + defaults: + run: + shell: pwsh + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Download and verify pinned Mission Planner release + run: | + $pin = (Get-Content build/gcs-versions.json -Raw | ConvertFrom-Json).missionPlanner + $archive = "$env:RUNNER_TEMP/MissionPlanner.zip" + Invoke-WebRequest $pin.url -OutFile $archive + if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLower() -ne $pin.sha256) { throw 'Mission Planner checksum mismatch' } + Expand-Archive $archive -DestinationPath "$env:RUNNER_TEMP/MissionPlanner" + - name: Build release plugin using production generator + run: ./scripts/build-mission-planner.ps1 -MissionPlannerDirectory "$env:RUNNER_TEMP/MissionPlanner" -OutputDirectory bin/mission-planner + - name: Package plugin and source with provenance + env: + ADAPTER_COMMIT: ${{ github.sha }} + run: | + python scripts/package-gcs.py --component missionPlanner --target windows-amd64 --input bin/mission-planner --output bin/gcs --adapter-commit "$env:ADAPTER_COMMIT" + if ($LASTEXITCODE -ne 0) { throw 'Plugin packaging failed' } + - name: Load and initialize the packaged DLL against Mission Planner + run: | + $archive = @(Get-ChildItem bin/gcs -Filter '*.zip') + if ($archive.Count -ne 1) { throw 'Expected exactly one plugin archive' } + powershell.exe -NoProfile -File scripts/test_mission_planner_package.ps1 -Archive $archive[0].FullName -MissionPlannerDirectory "$env:RUNNER_TEMP/MissionPlanner" + if ($LASTEXITCODE -ne 0) { throw 'Packaged plugin integration test failed' } + - uses: actions/upload-artifact@v4 + with: + name: utm-adapter-gcs-mission-planner-windows-amd64 + path: bin/gcs/* + if-no-files-found: error + retention-days: 30 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 76b14cc..da8320a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,11 @@ on: push: branches: [main] workflow_dispatch: + inputs: + flight-blender-ref: + description: 'Optional Flight Blender branch/ref containing the companion adapter API' + type: string + default: '' permissions: contents: read @@ -32,11 +37,70 @@ jobs: node-version: '24' cache: npm cache-dependency-path: frontend/package-lock.json + - name: Verify release version + run: node scripts/check-version.mjs - name: Backend tests and vet run: | go test -race ./internal/... go vet ./internal/... + - name: Compile generated Mission Planner plugin against release 1.3.83 + if: runner.os == 'Windows' + shell: pwsh + run: | + $archive = Join-Path $env:RUNNER_TEMP 'MissionPlanner.zip' + Invoke-WebRequest 'https://github.com/ArduPilot/MissionPlanner/releases/download/MissionPlanner1.3.83/MissionPlanner-1.3.83.zip' -OutFile $archive + if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLower() -ne 'ff3694f7a9038caf756545049dd7aef74e3d0d8fe3586e87aa881045704dd6f9') { throw 'Mission Planner checksum mismatch' } + $env:UTM_TEST_MP_DIRECTORY = Join-Path $env:RUNNER_TEMP 'MissionPlanner' + Expand-Archive $archive -DestinationPath $env:UTM_TEST_MP_DIRECTORY + $env:UTM_TEST_MP_CSC = Join-Path $env:WINDIR 'Microsoft.NET/Framework64/v4.0.30319/csc.exe' + $framework = Get-ChildItem "${env:ProgramFiles(x86)}/Reference Assemblies/Microsoft/Framework/.NETFramework" -Directory | Where-Object { Test-Path (Join-Path $_.FullName 'Facades/netstandard.dll') } | Sort-Object Name -Descending | Select-Object -First 1 + if (-not $framework) { throw '.NET Framework reference assemblies with netstandard facade are required' } + $env:UTM_TEST_MP_FRAMEWORK = $framework.FullName + go test ./internal/adapter -run '^TestMissionPlannerPluginCompilation$' -count=1 -v + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Install locked frontend dependencies run: npm --prefix frontend ci - name: Check Svelte and TypeScript run: npm --prefix frontend run check + + native-map: + name: Native QGC restriction rendering + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: jurplel/install-qt-action@v4 + with: + version: '6.8.3' + modules: 'qtlocation qtpositioning' + cache: true + - name: Verify map rendering and custom-build wiring + env: + QSG_RHI_BACKEND: software + run: | + qmllint integrations/qgroundcontrol/AdapterRestrictions.qml + qmltestrunner -platform offscreen -input integrations/qgroundcontrol/tests + python3 -m unittest discover -s scripts -p 'test_*.py' + + flight-blender-contract: + name: Real Flight Blender session contract + if: github.event_name == 'workflow_dispatch' && inputs.flight-blender-ref != '' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + path: adapter + - uses: actions/checkout@v4 + with: + repository: openutm/flight-blender + ref: ${{ inputs.flight-blender-ref }} + path: flight-blender + - uses: actions/setup-go@v5 + with: + go-version-file: adapter/go.mod + - uses: astral-sh/setup-uv@v6 + - name: Install server dependencies and test actual application + run: | + cd flight-blender + uv sync --locked + cd ../adapter + scripts/test-flight-blender.sh diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 0000000..e58a9ab --- /dev/null +++ b/COMPATIBILITY.md @@ -0,0 +1,22 @@ +# UTM Adapter 0.2.0 compatibility + +This release implements the adapter workflow in [Flight Blender #218](https://github.com/openutm/flight-blender/issues/218). Planning and declaration creation remain in Flight Spotlight / Flight Blender. + +| Component | Integration | Verification | +| --- | --- | --- | +| Flight Blender | Existing telemetry/declaration/geofence APIs plus `/adapter_ops/sessions/{UUID}` | Go client tested against the actual FastAPI/SQLAlchemy application, including real UDP reception, restart, upload acknowledgement, duplicate uploads and exported-report readback. Deploy the companion `a218_session_records` migration before enabling archive synchronization. | +| QGroundControl 5.1.4 | MAVLink forwarding; custom FlyView layer for restriction polygons | CI builds a pinned upstream commit for macOS universal, Windows x64 and Linux x64, tests native restriction rendering, and boots the packaged application before upload. The minimal custom plugin preserves upstream firmware support and UI. Flight trials remain required. | +| Mission Planner 1.3.83 | Generated C# plugin: all connection telemetry and Flight Data restriction overlays | CI compiles the production generator's source against the checksum-pinned official release, then extracts the release ZIP and loads, initializes and unloads the DLL using .NET Framework. Connected-vehicle and Flight Data UI trials remain required. | +| Aerobridge | Optional bearer-authenticated aircraft/pilot/operation registry and flight-log API | HTTP contract tests cover stable batch keys and recovery after a stored upload loses its response. A live Aerobridge deployment remains to be verified. | +| MAVLink 1/2 | Autopilot component 1; distinct system IDs for concurrent aircraft | UDP/TCP tests and two-aircraft session integration test. Live delivery requires fresh MAVLink 2 ellipsoid altitude. Serial hardware remains to be verified. | +| macOS | Wails desktop application | Local native build and UI start/end/export smoke test; CI also builds the universal application. Ad-hoc signing, without notarization. | +| Windows x64 | Portable executable and NSIS installer | Build/test/package CI configured; runtime verification remains pending. | +| Linux x64 | Ubuntu 24.04 archive and AppImage | Build/test/package CI configured; runtime verification remains pending. GTK4, WebKitGTK 6 and a credential-store service are required. | + +GCS downloads use the prefix `utm-adapter-gcs-`: QGroundControl ships as a macOS DMG, Windows installer and Linux AppImage; Mission Planner ships as a Windows plugin ZIP with installation instructions and inspection source. Mission Planner itself is not redistributed. Each artifact includes a checksum and a manifest recording the adapter version/commit and exact upstream build inputs in `build/gcs-versions.json`. These QGC builds retain the standard application/settings name, so install them in place of stock QGC. + +The integration sources also ship as `utm-adapter-gcs-integrations.tar.gz`. Prepare a new QGC custom directory with `python3 scripts/prepare-qgc.py /path/to/qgroundcontrol`, then build QGC using its supported custom-build process. For custom Mission Planner endpoints, use the adapter's Ground stations screen instead of installing the release DLL. Install only one copy of the plugin (source or DLL). Both map integrations use `http://127.0.0.1:14552/v1/restrictions` by default; adjust the QML endpoint if the local bridge address changes. + +Archives and installers include SHA-256 checksum files. Pushing a matching semantic-version tag, such as `v0.2.0`, triggers desktop and GCS builds and publishes a GitHub Release only after all builds and package checks pass. Uploads go to a draft before publication. The Desktop binaries workflow can be dispatched manually to build all artifacts without creating a release; GCS builds can also be dispatched independently. Releases are not automatically installed. macOS packages use ad-hoc signing without notarization. + +Local records contain pilot contact details and flight positions. They are stored alongside `product.json` in `records.sqlite`; exports go in `exports/`. Protect and back up this directory. Start/end audit events and declaration history provide operational records, without cryptographic certification or tamper-proof guarantees. diff --git a/DEVELOPING.md b/DEVELOPING.md index b609fc3..8f91595 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -142,7 +142,7 @@ Ground station discovery, setup, server health, and API read checks run roughly - `HEARTBEAT` supplies armed state and heartbeat freshness. `GPS_RAW_INT` supplies the delivery position, GPS freshness, fix, speed, course, and MAVLink 2 ellipsoid altitude. `GLOBAL_POSITION_INT` supplies displayed MSL/relative altitude and a position fallback before GPS arrives. - Delivery requires heartbeat and GPS no more than five seconds old, a new GPS sample after the last successful send, and populated position, WGS84 altitude, speed, and course. GPS fix types 3–8 are accepted. Unknown speed/course sentinels and missing or zero ellipsoid altitude block delivery; MSL is never substituted for WGS84 altitude. - Delivery sends one observation with one current state for the configured declaration, vehicle serial, and operator. Operational status is `Undeclared`; unknown accuracy and unavailable vertical-speed/pressure-altitude values use the sentinels in `Blender.Publish`. Changing these mappings requires checking the receiving Flight Blender contract. -- A successful send advances the last-sent sample and accepted count. A failed send can be retried while the sample remains fresh. There is no durable queue or historical replay. Vehicle state and counters reset when connections restart. +- A successful send advances the last-sent sample and accepted count. A failed send can be retried while the sample remains fresh. This legacy live path has no historical replay. Session recording has an independent durable archive queue. Vehicle state and counters reset when connections restart. The HTTP client uses these endpoints relative to the configured base URL: @@ -153,7 +153,7 @@ The HTTP client uses these endpoints relative to the configured base URL: | `GET /flight_declaration_ops/flight_declaration/{UUID}` | Authenticated read access check for the selected declaration. | | `PUT /flight_stream/set_telemetry` | Telemetry delivery; successful read access alone does not prove write permission. | -Requests have a six-second timeout, reject redirects, and limit response reads to 1 MiB. OAuth uses client credentials with HTTP Basic client authentication and caches access tokens until 30 seconds before expiry. A missing expiry defaults to 300 seconds; HTTP 401 clears the cached token for a subsequent retry. +Requests have a six-second timeout, reject redirects, and limit response reads to 8 MiB. OAuth uses client credentials with HTTP Basic client authentication and caches access tokens until 30 seconds before expiry. A missing expiry defaults to 300 seconds; HTTP 401 clears the cached token for a subsequent retry. ## Ground station changes @@ -161,7 +161,7 @@ Automatic setup only configures local UDP forwarding, skips direct mode, and wai QGC configuration updates `forwardMavlink=true` and `forwardMavlinkHostName` under `[General]` in `QGroundControl.ini`. The adjacent `.utm-adapter-target` file records an adapter-managed destination so future updates can preserve unrelated forwarding settings. -Mission Planner plugin installation requires explicit opt-in. The generated `plugins/OpenUTMAdapter.cs` forwards received packets to the adapter and follows the primary connection. Files without the managed-plugin marker are preserved. Removal disables the opt-in and renames the managed plugin to an unloaded backup. +Mission Planner plugin installation requires explicit opt-in. The generated `plugins/OpenUTMAdapter.cs` forwards received packets to the adapter and follows all Mission Planner connections. Files without the managed-plugin marker are preserved. Removal disables the opt-in and renames the managed plugin to an unloaded backup. Changed existing files are backed up with `.utm-adapter-backup-`. Disabling automatic configuration does not undo earlier changes. See the [user guide's rollback instructions](README.md#settings-credentials-and-undoing-setup). @@ -175,7 +175,7 @@ Changed existing files are backed up with `.utm-adapter-backup-`. Dis gh workflow run desktop.yml --ref ``` -GitHub exposes manual dispatch once the workflow is present on the default branch. The binary workflow tests the backend, builds the app, checks the frontend, and uploads macOS universal, Windows amd64, and Linux amd64 archives with SHA-256 checksums. Artifacts are retained for 30 days; they are not published release installers. +GitHub exposes manual dispatch once the workflow is present on the default branch. The binary workflow tests the backend, builds the app, checks the frontend, and uploads macOS universal, Windows amd64, and Linux amd64 archives with SHA-256 checksums. Preview artifacts are retained for 30 days. Matching `vX.Y.Z` tags publish verified archives, Windows NSIS installers, Linux AppImages, integration sources, checksums and the compatibility matrix as a GitHub Release. Linux CI installs native dependencies through [build/ci/install-linux-deps.sh](build/ci/install-linux-deps.sh), using Ubuntu's official HTTPS repositories. Package downloads are cached, indexes refresh each run, and cache keys rotate weekly or when the script changes. @@ -183,6 +183,76 @@ Linux CI installs native dependencies through [build/ci/install-linux-deps.sh](b The existing preview notes record successful QGroundControl 5.1.4 automatic setup and forwarding on macOS with synthetic MAVLink, direct UDP, TCP integration tests, an HTTP Flight Blender test server, and checks against Flight Blender's telemetry dataclass parser. These are separate from live operational validation. -Live authenticated Flight Blender delivery, real autopilots, serial hardware, and Mission Planner runtime integration still need trials. The Mission Planner plugin has been checked against a local source API but not validated at runtime on Windows. Record the OS, GCS version, transport, and receiving Flight Blender version when extending that coverage. +Live authenticated Flight Blender delivery, real autopilots, serial hardware, and Mission Planner runtime integration still need trials. The generated Mission Planner plugin compiles against official 1.3.83 assemblies using C# 5 and .NET Framework reference assemblies; Windows CI repeats that check. Runtime loading on Windows still needs validation. Record the OS, GCS version, transport, and receiving Flight Blender version when extending that coverage. -Declaration creation, mapping/planning, Aerobridge/fleet synchronization, release signing and installers, auto-updates, login startup, and the custom OpenUTM QGC build remain outside the implemented preview. +Declaration creation and planning remain external. Publisher signing, auto-updates, login startup and compiled custom QGC distribution remain outside this release. Session archives, optional Aerobridge registry/log integration, map overlay sources and installer packaging are implemented in 0.2.0. + +## Session workflow and integration verification (0.2.0) + +`records.go` owns the private SQLite database beside `product.json`. Each immutable session assignment binds a MAVLink system to an aircraft, declaration and pilot. The receiver records every incoming GPS sample while that session is active. `sessions.go` handles start/end, streamed JSON export and synchronization. `sync.go` caches declaration versions and integrates the optional Aerobridge registry/log APIs. `restrictions.go` caches geofences and serves a credential-free loopback GeoJSON feed. + +Archive workers persist exact pending payloads before sending, then advance separate Flight Blender / Aerobridge cursors after acknowledgement. Flight Blender stores sequences idempotently. Aerobridge lacks native idempotency keys, so the adapter reconciles `raw_log.adapterBatchKey` before retrying POSTs. This relies on read-after-write visibility and a single adapter owning a session. Active sessions persist through a restart. Ended sessions remain exportable; there is no automatic retention/deletion policy. + +Deploy the companion Flight Blender changes and apply `alembic upgrade head`. Its owner-isolated endpoints require read scope for GET and write scope for PUT. Records are owned by token issuer and subject, so use the same service identity for retries. Then run: + +```sh +# Two real UDP aircraft -> durable sessions -> outage -> restart -> +# authenticated archive/log services -> restriction bridge and JSON export. +go test -race ./internal/adapter -run '^TestFlightSessionIntegration$' -count=1 -v + +# Real UDP -> actual Flight Blender FastAPI + SQLAlchemy -> exported report, isolated +# in a temporary directory. Requires the sibling server's Python environment. +FLIGHT_BLENDER_ROOT=../flight-blender scripts/test-flight-blender.sh + +node scripts/check-version.mjs +``` + +The cross-repository script uses a local JWT-verification bypass only in its isolated server process; Flight Blender's own tests separately enforce real RSA token verification, missing-token failures and wrong-scope failures. It creates schema from ORM metadata; the migration is tested separately in the Flight Blender suite. + +For QGC 5.1.4, prepare a **new** custom directory inside its source checkout: + +```sh +python3 scripts/prepare-qgc.py /path/to/qgroundcontrol +# Configure/build QGC following its native instructions, adding: +# -DQGC_CUSTOM_DIR=custom-openutm-adapter +``` + +This keeps the GCS map native and reads only the adapter's local GeoJSON feed. The preparation script refuses to overwrite an existing directory. To test its native QML component with Qt 6.8+ and Qt Location / Positioning installed: + +```sh +qmllint integrations/qgroundcontrol/AdapterRestrictions.qml +qmltestrunner -platform offscreen -input integrations/qgroundcontrol/tests +python3 -m unittest discover -s scripts -p 'test_*.py' +``` + +Changing the bridge address requires updating the QML `endpoint` or regenerating the installed Mission Planner plugin through the adapter. The generated Mission Planner source contains its forwarding and map integration; Windows runtime validation remains required. See [COMPATIBILITY.md](COMPATIBILITY.md) for exact coverage. + +The Tests workflow accepts an optional `flight-blender-ref` on manual dispatch to run the same real-server contract against a branch containing the companion API. Default checks run the two-aircraft UDP/restart integration test on Linux and Windows, plus native Qt map tests on Linux and generated Mission Planner plugin compilation on Windows. + +## GCS build and release CI + +The [GCS workflow](.github/workflows/gcs.yml) builds QGroundControl from the commit +in `build/gcs-versions.json` using that commit's own Qt/dependency/build actions. +It packages and boots a macOS universal DMG, Windows x64 installer and Linux x64 +AppImage. Upstream is checked out at the workspace root because its composite +actions resolve nested actions and scripts relative to that directory; this +adapter is checked out into `adapter/`. + +The Windows job downloads the checksum-pinned Mission Planner ZIP, compiles the +same C# source used by the application, creates a plugin ZIP, then loads that +ZIP's DLL and checks its version, initialization and unload using Windows +PowerShell/.NET Framework. This does not replace a connected-aircraft/UI trial. +To reproduce its packaging from a Windows developer shell: + +```powershell +./scripts/build-mission-planner.ps1 -MissionPlannerDirectory C:/MissionPlanner -OutputDirectory bin/mission-planner +python scripts/package-gcs.py --component missionPlanner --target windows-amd64 --input bin/mission-planner --output bin/gcs --adapter-commit (git rev-parse HEAD) +powershell.exe -NoProfile -File scripts/test_mission_planner_package.ps1 -Archive (Get-ChildItem bin/gcs/*.zip).FullName -MissionPlannerDirectory C:/MissionPlanner +``` + +Dispatch **GCS builds** for integration packages alone, or **Desktop binaries** +for the complete release set without publishing. Relevant pull requests also run +GCS builds. A matching `v` tag runs both matrices and publishes only +after every job succeeds. All packages and their provenance manifests have +SHA-256 checksums. No signing secrets are required; macOS uses ad-hoc signing. +Update the upstream pins and compatibility matrix together when upgrading a GCS. diff --git a/README.md b/README.md index a2eb809..621871d 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ UTM Adapter connects your vehicle's MAVLink telemetry to Flight Blender. Use it The adapter runs on your computer and keeps working in the system tray when you close its window. It receives telemetry passively and sends no vehicle commands. -**Early access:** this preview supports telemetry monitoring and delivery. It does not create flight declarations or provide flight planning. See [current limitations](#current-limitations) before use. +**Version 0.2.0:** records pilot-assigned flight sessions locally, synchronizes their telemetry and audit records, and supplies restrictions to native GCS map integrations. Planning and declaration creation remain in Flight Spotlight / Flight Blender. See the [compatibility matrix](COMPATIBILITY.md) for tested versions and validation boundaries. ## Get the app -Download a build from a successful run of the [Desktop binaries workflow](https://github.com/openutm/utm-adapter/actions/workflows/desktop.yml). Open the run, scroll to **Artifacts**, and select the download for your computer. Downloading requires a GitHub account. Each artifact contains an application archive and a SHA-256 checksum file. +Download tagged builds from [GitHub Releases](https://github.com/openutm/utm-adapter/releases), or a preview build from a successful run of the [Desktop binaries workflow](https://github.com/openutm/utm-adapter/actions/workflows/desktop.yml). Open the run, scroll to **Artifacts**, and select the download for your computer. Downloading requires a GitHub account. Each artifact contains an application archive and a SHA-256 checksum file. | Your computer | Artifact | How to open it | | --- | --- | --- | @@ -16,7 +16,7 @@ Download a build from a successful run of the [Desktop binaries workflow](https: | Windows, x64 | `utm-adapter-windows-amd64` | Extract the archive and run `utm-adapter.exe`. Microsoft Edge WebView2 Runtime is required. | | Linux, x64 | `utm-adapter-linux-amd64` | Extract the archive, make `utm-adapter` executable if necessary, and launch it from your desktop session. | -These are preview builds, not signed release installers. The macOS app is ad-hoc signed and is not notarized, so macOS may require approval in **System Settings → Privacy & Security** before opening it. +Windows releases include an NSIS installer with WebView2 bootstrapping; Linux releases include an AppImage. Builds are not publisher-signed. The macOS app is ad-hoc signed and is not notarized, so macOS may require approval in **System Settings → Privacy & Security** before opening it. The Linux executable is built on Ubuntu 24.04 and requires compatible GTK4 and WebKitGTK 6.0 libraries, a desktop session, a tray host, and a running Secret Service provider for credential storage. On Ubuntu 24.04, install the runtime libraries with: @@ -75,7 +75,7 @@ You can use Mission Planner's built-in MAVLink mirror or install the optional Op 3. In **Ground stations**, choose **Install OpenUTM plugin** and wait for **File installed**. 4. Reopen Mission Planner and connect your vehicle. Confirm incoming telemetry in **Overview**. -The plugin forwards received telemetry automatically when Mission Planner starts and follows changes to its primary vehicle connection. Automatic configuration alone never installs it; you must select the plugin explicitly. The adapter does not request administrator access, so installation can fail if the Mission Planner directory is not writable. Use the built-in mirror in that case. +The plugin forwards received telemetry automatically when Mission Planner starts and follows changes to all vehicle connections and displays restriction polygons on the Flight Data map. Automatic configuration alone never installs it; you must select the plugin explicitly. The adapter does not request administrator access, so installation can fail if the Mission Planner directory is not writable. Use the built-in mirror in that case. **File installed** confirms installation on disk. Incoming telemetry confirms forwarding is working. Windows runtime validation of the plugin remains pending in this preview. @@ -91,7 +91,23 @@ Choose **Direct MAVLink** in **Connection settings**, then select a connection t Click **Save & connect**. Direct mode does not change ground station settings. The source must already emit heartbeat and GPS data; the adapter does not request telemetry streams. If a ground station already uses the vehicle's serial port, use its forwarding feature. -**MAVLink system ID** selects a vehicle: `0` displays all received vehicles, while `1`–`255` filters to that system. Sending to Flight Blender requires an explicit, nonzero ID. +**MAVLink system ID** selects a vehicle: `0` displays all received vehicles, while `1`–`255` filters to that system. Each flight session requires an explicit, nonzero ID; use filter `0` to record multiple aircraft. + +## Record and synchronize flight sessions + +Deploy the companion Flight Blender adapter API and run `alembic upgrade head` on the server first. The session archive requires `/adapter_ops/sessions/{UUID}` and the `a218_session_records` migration. Existing live telemetry APIs remain supported. + +1. Connect your telemetry source and select **Flight sessions**. +2. Enter the aircraft's MAVLink system ID, declaration UUID, vehicle serial, operator, and pilot name, licence and contact details. Pilot ID is optional. Select system filter `0` in settings when receiving multiple aircraft. +3. Start the session before flight. Each aircraft has one active session; its assignment stays fixed until the session ends. +4. Enable **Send session telemetry** in settings to send fresh positions for each active session. Archive synchronization runs independently and retains failed deliveries for retry. +5. End the session and select **Export** for a JSON report containing pilot/aircraft details, GPS positions, altitudes, speed, declaration history and start/end audit events. The app displays the export's path. + +Sessions and delivery cursors survive restarts. A paused receiver records no new samples. After a crash, the existing session remains active until you end it; receiving telemetry resumes into that assignment. Session records upload in batches of at most 200 samples, with duplicate-safe Flight Blender retries. Historical samples go to the archive API rather than being replayed as live positions. The adapter caches declarations from the configured Flight Blender server, including observed changes to historical declarations. + +For Aerobridge, enable the optional registry in settings and provide its URL and bearer token. **Refresh choices** loads aircraft, pilots and operations. Enter the matching operation UUID when starting a session; the adapter attaches the session batch to that operation's flight log. Disabling Aerobridge keeps its pending records locally. Pending deliveries remain associated with their original server URL and resume when that server is selected again. + +The Mission Planner plugin shows Flight Blender restriction boundaries directly on its map. For QGC map overlays, install the OpenUTM QGC build from the release's `utm-adapter-gcs-qgroundcontrol-*` downloads. CI also provides a compiled Mission Planner plugin ZIP; follow its README and install only one plugin copy (DLL or generated source). Source-build instructions are in [DEVELOPING.md](DEVELOPING.md). Both use the read-only loopback feed at `http://127.0.0.1:14552/v1/restrictions`, without credentials or pilot details. Stale/unavailable feeds retain the cached boundaries and show a warning. Check published altitude and validity limits; the overlay fills polygon exteriors, including any holes. ## Send telemetry to Flight Blender @@ -105,7 +121,7 @@ The declaration must cover the current time and be in a state that accepts telem Delivery requires a vehicle heartbeat and GPS sample no more than five seconds old, a 3D GPS fix, WGS84 ellipsoid altitude from MAVLink 2, speed, and course. An MSL altitude displayed in the app does not satisfy the WGS84 altitude requirement. Missing ellipsoid altitude, including a reported zero value, blocks delivery. -The adapter sends new GPS samples about once per second. It has no offline queue or historical replay, so interrupted delivery does not backfill missed samples. Switch off **Enable telemetry delivery** and save to stop sending while continuing local monitoring. +This legacy single-declaration mode sends fresh GPS samples about once per second. Use Flight sessions for durable recording and archive synchronization; legacy mode alone has no offline queue. Switch off **Enable telemetry delivery** and save to stop sending while continuing local monitoring. ## Understand the overview @@ -137,7 +153,7 @@ Other settings are saved in `OpenUTM/utm-adapter/product.json` under your OS con | Windows | `%AppData%\OpenUTM\utm-adapter\product.json` | | Linux | `$XDG_CONFIG_HOME/OpenUTM/utm-adapter/product.json`, or `~/.config/OpenUTM/utm-adapter/product.json` when unset | -Use the path shown in **Recent activity** to locate your actual file. +Use the path shown in **Recent activity** to locate your actual file. `records.sqlite` beside it contains flight sessions, cached declarations and delivery queues. Reports are written in the adjacent `exports/` directory. These contain personal and flight data; protect them and back them up with your configuration. Automatic setup backs up existing files before changing them, beside the original with the suffix `.utm-adapter-backup-`. It does not rewrite unchanged settings. Turning automatic setup off leaves existing forwarding and installed plugins in place. @@ -162,11 +178,11 @@ If you need help, [open an issue](https://github.com/openutm/utm-adapter/issues) ## Current limitations -- Existing flight declarations only; no declaration creation, maps, flight planning, or fleet synchronization. +- Existing flight declarations only; declaration creation and flight planning remain in external tools. Native GCS maps show restriction boundaries through the supplied integrations. - MAVLink 1 and 2 are supported for monitoring, using autopilot component 1. Delivery requires MAVLink 2 ellipsoid altitude. UDP input expects one MAVLink frame per datagram. - Armed state is displayed locally; delivered operational status is **Undeclared**, because armed state alone does not establish whether a vehicle is airborne. -- No automatic updates, login startup, or signed release installers. The optional OpenUTM custom QGroundControl build is planned and has no downloadable release in this preview. -- Real autopilots, serial hardware, live authenticated Flight Blender connections, and Mission Planner still need user trials. QGroundControl forwarding has been exercised on macOS with synthetic telemetry. +- No automatic updates, login startup, or publisher signing. QGC packages and the Mission Planner plugin are built alongside adapter releases; see [COMPATIBILITY.md](COMPATIBILITY.md) for platforms and verification coverage. +- Real autopilots, serial hardware, live Aerobridge and Mission Planner runtime integration still need trials. The real Flight Blender application is covered by a local cross-repository contract test. See [COMPATIBILITY.md](COMPATIBILITY.md). For source builds, development setup, architecture, and testing, see the separate [developer guide](DEVELOPING.md). diff --git a/build/config.yml b/build/config.yml index fb8255a..a341289 100644 --- a/build/config.yml +++ b/build/config.yml @@ -11,7 +11,7 @@ info: description: "Ground control to Flight Blender" # The application description copyright: "(c) 2026 OpenUTM" # Copyright text comments: "Desktop connection preview" # Comments - version: "0.1.0" # The application version + version: "0.2.0" # The application version # cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional) # # Should match the name of your .icon file without the extension # # If not set and Assets.car exists, defaults to "appicon" @@ -24,7 +24,7 @@ info: # # The display name shown under the app icon (CFBundleDisplayName/CFBundleName) # displayName: "UTM Adapter" # # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion) -# version: "0.1.0" +# version: "0.2.0" # # The company/organisation name for templates and project settings # company: "OpenUTM" # # Additional comments to embed in Info.plist metadata diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist index 95d686c..81bd475 100644 --- a/build/darwin/Info.dev.plist +++ b/build/darwin/Info.dev.plist @@ -10,11 +10,11 @@ CFBundleIdentifier org.openutm.adapter CFBundleVersion - 0.1.0 + 0.2.0 CFBundleGetInfoString This is a comment CFBundleShortVersionString - 0.1.0 + 0.2.0 CFBundleIconFile icons CFBundleIconName diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist index eed6b50..dbf6959 100644 --- a/build/darwin/Info.plist +++ b/build/darwin/Info.plist @@ -10,11 +10,11 @@ CFBundleIdentifier org.openutm.adapter CFBundleVersion - 0.1.0 + 0.2.0 CFBundleGetInfoString Ground control to Flight Blender CFBundleShortVersionString - 0.1.0 + 0.2.0 CFBundleIconFile icons CFBundleIconName diff --git a/build/gcs-versions.json b/build/gcs-versions.json new file mode 100644 index 0000000..0f2010e --- /dev/null +++ b/build/gcs-versions.json @@ -0,0 +1,12 @@ +{ + "qgroundcontrol": { + "repository": "mavlink/qgroundcontrol", + "version": "5.1.4", + "commit": "4568b585648e8c6065cca6c650b52731c1d3baa3" + }, + "missionPlanner": { + "version": "1.3.83", + "url": "https://github.com/ArduPilot/MissionPlanner/releases/download/MissionPlanner1.3.83/MissionPlanner-1.3.83.zip", + "sha256": "ff3694f7a9038caf756545049dd7aef74e3d0d8fe3586e87aa881045704dd6f9" + } +} diff --git a/build/linux/nfpm/nfpm.yaml b/build/linux/nfpm/nfpm.yaml index 57946d7..b0916e0 100644 --- a/build/linux/nfpm/nfpm.yaml +++ b/build/linux/nfpm/nfpm.yaml @@ -6,7 +6,7 @@ name: "utm-adapter" arch: ${GOARCH} platform: "linux" -version: "0.1.0" +version: "0.2.0" section: "default" priority: "extra" maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> diff --git a/build/windows/info.json b/build/windows/info.json index 3f8532a..cf69048 100644 --- a/build/windows/info.json +++ b/build/windows/info.json @@ -1,10 +1,10 @@ { "fixed": { - "file_version": "0.1.0" + "file_version": "0.2.0" }, "info": { "0409": { - "ProductVersion": "0.1.0", + "ProductVersion": "0.2.0", "CompanyName": "OpenUTM", "FileDescription": "Ground control to Flight Blender", "LegalCopyright": "© 2026, OpenUTM", diff --git a/build/windows/msix/app_manifest.xml b/build/windows/msix/app_manifest.xml index e228282..3c1fc1a 100644 --- a/build/windows/msix/app_manifest.xml +++ b/build/windows/msix/app_manifest.xml @@ -10,7 +10,7 @@ diff --git a/build/windows/msix/template.xml b/build/windows/msix/template.xml index 0e3bf45..6a296e2 100644 --- a/build/windows/msix/template.xml +++ b/build/windows/msix/template.xml @@ -17,7 +17,7 @@ PackageDisplayName="UTM Adapter" PublisherName="CN=OpenUTM" PublisherDisplayName="OpenUTM" - Version="0.1.0.0" + Version="0.2.0.0" PackageDescription="Ground control to Flight Blender"> diff --git a/build/windows/nsis/project.nsi b/build/windows/nsis/project.nsi index c13cf05..b545947 100644 --- a/build/windows/nsis/project.nsi +++ b/build/windows/nsis/project.nsi @@ -22,7 +22,7 @@ Unicode true ## !define INFO_PROJECTNAME "my-project" # Default "utm-adapter" ## !define INFO_COMPANYNAME "OpenUTM" # Default "OpenUTM" ## !define INFO_PRODUCTNAME "My Product Name" # Default "UTM Adapter" -## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.2.0" ## !define INFO_COPYRIGHT "(c) Now, OpenUTM" # Default "© 2026, OpenUTM" ### ## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" diff --git a/build/windows/nsis/wails_tools.nsh b/build/windows/nsis/wails_tools.nsh index 1a831a6..0947df7 100644 --- a/build/windows/nsis/wails_tools.nsh +++ b/build/windows/nsis/wails_tools.nsh @@ -14,7 +14,7 @@ !define INFO_PRODUCTNAME "UTM Adapter" !endif !ifndef INFO_PRODUCTVERSION - !define INFO_PRODUCTVERSION "0.1.0" + !define INFO_PRODUCTVERSION "0.2.0" !endif !ifndef INFO_COPYRIGHT !define INFO_COPYRIGHT "© 2026, OpenUTM" diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest index 44d2b5b..466f6d4 100644 --- a/build/windows/wails.exe.manifest +++ b/build/windows/wails.exe.manifest @@ -1,6 +1,6 @@ - + diff --git a/cmd/mission-planner-plugin/main.go b/cmd/mission-planner-plugin/main.go new file mode 100644 index 0000000..5a35882 --- /dev/null +++ b/cmd/mission-planner-plugin/main.go @@ -0,0 +1,22 @@ +// Generate the same source used by the adapter's Mission Planner installer. +package main + +import ( + "flag" + "log" + "os" + "path/filepath" + + "github.com/openutm/utm-adapter/internal/adapter" +) + +func main() { + output := flag.String("out", "OpenUTMAdapter.cs", "generated plugin source path") + flag.Parse() + if err := os.MkdirAll(filepath.Dir(*output), 0755); err != nil { + log.Fatal(err) + } + if err := os.WriteFile(*output, []byte(adapter.MissionPlannerPluginSource()), 0644); err != nil { + log.Fatal(err) + } +} diff --git a/frontend/bindings/encoding/json/index.ts b/frontend/bindings/encoding/json/index.ts new file mode 100644 index 0000000..0533417 --- /dev/null +++ b/frontend/bindings/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + RawMessage +} from "./models.js"; diff --git a/frontend/bindings/encoding/json/models.ts b/frontend/bindings/encoding/json/models.ts new file mode 100644 index 0000000..9fcec0d --- /dev/null +++ b/frontend/bindings/encoding/json/models.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * RawMessage is a raw encoded JSON value. + * It implements [Marshaler] and [Unmarshaler] and can + * be used to delay JSON decoding or precompute a JSON encoding. + */ +export type RawMessage = any; diff --git a/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/index.ts b/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/index.ts index 6ff5e96..a133876 100644 --- a/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/index.ts +++ b/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/index.ts @@ -8,9 +8,16 @@ export { export type { Activity, + Assignment, Config, + Document, + Fleet, GCS, Link, + Pilot, + RecordSummary, + RegistryEntity, + Session, Snapshot, Vehicle } from "./models.js"; diff --git a/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/models.ts b/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/models.ts index 19b7582..ec065c7 100644 --- a/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/models.ts +++ b/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/models.ts @@ -1,12 +1,31 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../encoding/json/models.js"; + export interface Activity { "at": string; "message": string; } +export interface Assignment { + "systemID": number; + "declarationID": string; + "vehicleID": string; + "operatorID": string; + "pilot": Pilot; + "aerobridgeOperationID": string; +} + export interface Config { + "sessionDelivery": boolean; + "aerobridgeEnabled": boolean; + "aerobridgeURL": string; + "aerobridgeToken"?: string; + "hasAerobridgeToken": boolean; + "bridgeAddress": string; "baseURL": string; "authMode": string; "token"?: string; @@ -30,6 +49,20 @@ export interface Config { "hasClientSecret": boolean; } +export interface Document { + "id": string; + "kind": string; + "server": string; + "capturedAt": string; + "data": json$0.RawMessage; +} + +export interface Fleet { + "aircraft": RegistryEntity[] | null; + "pilots": RegistryEntity[] | null; + "operations": RegistryEntity[] | null; +} + export interface GCS { "id": string; "name": string; @@ -49,7 +82,42 @@ export interface Link { "checkedAt": string; } +export interface Pilot { + "id": string; + "name": string; + "license": string; + "contact": string; +} + +export interface RecordSummary { + "session": Session; + "samples": number; + "blenderPending": boolean; + "aerobridgePending": boolean; +} + +export interface RegistryEntity { + "id": string; + "data": json$0.RawMessage; +} + +export interface Session { + "id": string; + "assignment": Assignment; + "startedAt": string; + "endedAt": string | null; + "blenderURL": string; + "aerobridgeURL": string; + "revision": number; +} + export interface Snapshot { + "version": string; + "aerobridge": Link; + "records": Link; + "restrictions": Link; + "sessions": RecordSummary[] | null; + "bridgeURL": string; "config": Config; "configPath": string; "running": boolean; diff --git a/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/service.ts b/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/service.ts index df8fb43..ebf1f5b 100644 --- a/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/service.ts +++ b/frontend/bindings/github.com/openutm/utm-adapter/internal/adapter/service.ts @@ -13,10 +13,40 @@ export function ApplyConfig(c: $models.Config): $CancellablePromise { return $Call.ByID(2974556148, c); } +export function BeginSession(a: $models.Assignment): $CancellablePromise<$models.Session> { + return $Call.ByID(3265027169, a); +} + export function ClearCredentials(): $CancellablePromise { return $Call.ByID(682087843); } +/** + * Close is for process shutdown. Stop only pauses connections. + */ +export function Close(): $CancellablePromise { + return $Call.ByID(682221518); +} + +export function EndSession(id: string): $CancellablePromise { + return $Call.ByID(1813009769, id); +} + +/** + * Export is streamed from SQLite, so a long flight does not have to fit in RAM. + */ +export function ExportSession(id: string): $CancellablePromise { + return $Call.ByID(4174278844, id); +} + +export function GetDeclarations(): $CancellablePromise<$models.Document[] | null> { + return $Call.ByID(2212004943); +} + +export function GetFleet(): $CancellablePromise<$models.Fleet> { + return $Call.ByID(1940079994); +} + export function GetSnapshot(): $CancellablePromise<$models.Snapshot> { return $Call.ByID(3543970594); } @@ -35,3 +65,10 @@ export function Start(): $CancellablePromise { export function Stop(): $CancellablePromise { return $Call.ByID(1890959658); } + +/** + * Synchronize retries durable deliveries without starting a telemetry receiver. + */ +export function Synchronize(): $CancellablePromise { + return $Call.ByID(3073840824); +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5e16834..93a2179 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "utm-adapter-ui", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "utm-adapter-ui", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@wailsio/runtime": "3.0.0-beta.34" }, diff --git a/frontend/package.json b/frontend/package.json index 32feb8a..2986570 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "utm-adapter-ui", "private": true, - "version": "0.1.0", + "version": "0.2.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index c10e5c6..ff797d5 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,11 +1,16 @@