Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/backend/app/arq/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ async def move_task_images_for_processing(
"Please retry by marking this task as fully flown again. "
f"Details: {str(e)}"
)
conn = None
try:
async with db_pool.connection() as conn:
transition = await task_logic.update_task_state_system(
Expand All @@ -687,6 +688,8 @@ async def move_task_images_for_processing(
if transition is not None:
await conn.commit()
except Exception as state_error:
if conn is not None:
await conn.rollback()
log.error(
f"Failed to persist transfer failure state for task {task_id}: "
f"{state_error}"
Expand Down
13 changes: 1 addition & 12 deletions src/backend/tests/test_project_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,6 @@ def connection(self):
return _FakePoolConnection(self.conn)


class _FakeDbPoolContext:
def __init__(self, conn):
self.conn = conn

async def __aenter__(self):
return _FakePool(self.conn)

async def __aexit__(self, exc_type, exc, tb):
return False


class _FakeZipFile:
def __init__(self, *args, **kwargs):
self._members = ["odm_orthophoto/odm_orthophoto.tif", "odm_report/report.pdf"]
Expand Down Expand Up @@ -523,7 +512,7 @@ def get_task(self, odm_task_id):
return FakeTask()

async def fake_get_db_connection_pool():
return _FakeDbPoolContext(conn)
return _FakePool(conn)

async def fake_update_task_state_system(**kwargs):
return {
Expand Down
10 changes: 5 additions & 5 deletions src/qfield-plugin/FlightplanDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,11 @@ QfDialog {
Label {
visible: generationState === "transfer_failed"
text: qsTr("To transfer the flightplan to your DJI controller:\n\n" +
"1. Ensure the controller is connected via USB and has at least one prior waypoint mission\n" +
"2. Use a file manager app to copy the .kmz from this project's flightplans/ folder to:\n" +
" Android/data/dji.go.v5/files/waypoint/<mission-id>/\n" +
"3. Or transfer later via the DroneTM web app (requires internet) using ADB Web transfer\n\n" +
"Tip: If this is a new controller, fly one test waypoint mission first so DJI creates the waypoint directory.")
"1. Connect the controller to your phone via USB cable\n" +
"2. Tap 'Copy to Flight Controller' below β€” this opens the DroneTM Transfer app\n" +
"3. In the Transfer app: grant USB permission, select the mission slot, and tap Transfer\n\n" +
"If the DroneTM Transfer app is not installed, you'll be prompted to save the file manually.\n\n" +
"Tip: If this is a new controller, fly one short waypoint mission first so DJI creates the waypoint directory.")
font.pixelSize: Theme.defaultFont.pixelSize * 0.8
wrapMode: Text.WordWrap
Layout.fillWidth: true
Expand Down
29 changes: 21 additions & 8 deletions src/qfield-plugin/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -577,15 +577,27 @@ Item {

log("Attempting to export KMZ to flight controller...")

// First try the direct-write path (works when QField runs on the DJI RC
// itself or the controller is mounted with relaxed scoped-storage rules).
// Strategy 1: Open DroneTM Transfer app via deeplink.
// The transfer app handles MTP/SAF to write onto the DJI controller.
try {
var fileUri = "file://" + lastKmzPath
var deeplink = "dronetm://transfer?file=" + encodeURIComponent(fileUri)
log("Trying DroneTM Transfer deeplink: " + deeplink)
Qt.openUrlExternally(deeplink)
mainWindow.displayToast(qsTr('Opening DroneTM Transfer...'))
flightplanDialog.resultMessage = qsTr('Sent to DroneTM Transfer app. Follow the steps there to complete the transfer.')
return
} catch (e) {
log("DroneTM Transfer deeplink failed: " + e)
}

// Strategy 2: Direct filesystem write (works when QField runs on the DJI
// RC itself or the controller is mounted with relaxed scoped-storage).
if (lastKmzData && _tryDirectCopyToController()) {
return
}

// Fall back to the platform file-picker so the user can choose where to
// save. platformUtilities.exportDatasetTo() shows Android's SAF folder
// picker and copies the file for us (not yet available on iOS).
// Strategy 3: Platform file-picker (Android SAF folder picker).
try {
if (typeof platformUtilities !== 'undefined' && platformUtilities.exportDatasetTo) {
log("Opening file picker via platformUtilities.exportDatasetTo")
Expand All @@ -599,14 +611,15 @@ Item {
log("platformUtilities.exportDatasetTo failed: " + e)
}

// Neither method worked
log("Could not export KMZ via direct copy or file picker")
// No method worked
log("Could not export KMZ via any method")
mainWindow.displayToast(
qsTr('DJI controller not found - see transfer options below')
)
flightplanDialog.generationState = "transfer_failed"
flightplanDialog.resultMessage = qsTr(
'Could not find DJI controller storage. The KMZ is saved in the project flightplans/ folder.'
'Could not find DJI controller storage. The KMZ is saved in the project flightplans/ folder.\n\n' +
'Install the DroneTM Transfer app for reliable USB transfer.'
)
}

Expand Down
48 changes: 48 additions & 0 deletions src/transfer-util/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

# Builds
*.apk
30 changes: 30 additions & 0 deletions src/transfer-util/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "44a626f4f0027bc38a46dc68aed5964b05a83c18"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18
base_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18
- platform: android
create_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18
base_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
17 changes: 17 additions & 0 deletions src/transfer-util/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# dronetm_transfer

A new Flutter project.

## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
28 changes: 28 additions & 0 deletions src/transfer-util/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
14 changes: 14 additions & 0 deletions src/transfer-util/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/

# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
45 changes: 45 additions & 0 deletions src/transfer-util/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}

android {
namespace = "org.hotosm.dronetm_transfer"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}

defaultConfig {
applicationId = "org.hotosm.drone_tm"
minSdk = 24
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}

dependencies {
implementation("androidx.documentfile:documentfile:1.0.1")
}

flutter {
source = "../.."
}
7 changes: 7 additions & 0 deletions src/transfer-util/android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
83 changes: 83 additions & 0 deletions src/transfer-util/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!-- USB Host mode for MTP communication -->
<uses-feature android:name="android.hardware.usb.host" android:required="false" />

<!-- Storage access for SAF fallback -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<application
android:label="DroneTM Transfer"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>

<!-- Main launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

<!-- USB device attached (auto-launch when DJI RC2 connected) -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />

<!-- Share intent for KMZ files -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kmz" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/octet-stream" />
</intent-filter>

<!-- Custom deeplink: dronetm://transfer -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="dronetm" android:host="transfer" />
</intent-filter>

<!-- Open .kmz files directly -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.google-earth.kmz" />
</intent-filter>
</activity>

<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>

<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
Loading
Loading