Flutter大疆无人机安全飞行插件dji_saferf的使用

Flutter大疆无人机安全飞行插件dji_saferf的使用

本开源项目旨在将DJI SDK的功能引入到Flutter中。

在开始使用DJI Flutter插件之前,您必须首先进行一些初始设置和配置,如DJI SDK所要求的那样。我们强烈建议先阅读至少DJI硬件介绍:

以熟悉基本术语和概念。

该插件支持以下功能:

  • 注册
  • 连接/断开连接
  • 起飞
  • 降落
  • 开始时间轴(包含起飞、降落、拍照、录像、航点任务等)
  • 移动遥控器(WiFi)
  • 虚拟操纵杆(物理遥控器)
  • 云台旋转(俯仰角)
  • 获取媒体文件列表
  • 按索引下载媒体文件
  • 按索引删除媒体文件
  • 实时视频流(原始视频YUV420p)
  • 开始/停止视频录制

设置

在开始使用DJI Flutter插件之前,您必须首先进行一些初始设置和配置,如DJI SDK所要求的那样。

获取DJI开发者凭证

为了使用DJI SDK,您需要成为DJI开发者:

一旦注册并登录,您需要为Android和iOS各创建两个新应用。确保您填写了创建Flutter项目时定义的应用包名。

当您的应用程序在DJI开发者门户上注册后,您将获得每个应用的“App Key”。此App Key应包含在您的Android Manifest XML和iOS info.plist中,如稍后所述。

您可以在Github的/example文件夹中搜索以找到示例项目中使用的App Keys:

  • iOS上的cloud.dragonx.plugin.flutter.djiExample使用b5245cf16f4b43ed90d436ba
  • Android上的cloud.dragonx.plugin.flutter.djiExample使用23acf68f822be3f065e6f538

提示: 通过命令行和--org参数创建Flutter项目,方法如下:

mkdir yourAppName
cd ./yourAppName
flutter create --org com.yourdomain --project-name yourAppName .

注意命令末尾的.

配置DJI SDK在Flutter iOS项目中的设置

DJI SDK由插件自动添加。然而,我们仍然需要直接在iOS项目中通过Xcode配置几个参数。

1. 配置构建设置
  • 打开您的Flutter iOS工作区在Xcode中。
  • 单击左侧面板顶部左侧的“Runner”标签。
  • 确保您在目标中选择“Runner”(中间侧边栏)。
  • 单击“info”选项卡(位于资源标签和构建设置之间)。
  • 在“自定义iOS目标属性”部分,右键单击最后一行并选择“添加行”,然后添加以下内容:
    • 添加“支持的外部配件协议”,包括三项:
      • Item0 = com.dji.common
      • Item1 = com.dji.protocol
      • Item2 = com.dji.video
    • 添加“App Transport Security Settings”键 > 单击"+“并选择"允许任意负载"并将值更改为"YES”。
2. 更新info.plist
  • 在info.plist文件中创建一个DJISDKAppKey键,并将其字符串值粘贴到其字符串值中(右键单击任何现有行或在其下方添加新行)。
  • 向Info.plist添加NSBluetoothAlwaysUsageDescription键,并用描述解释DJI无人机需要蓝牙连接。
3. 在Xcode中签名您的应用并运行它
  • 在尝试从Flutter运行应用之前,最好先从Xcode构建它。
  • 您还需要确保通过Xcode对应用进行签名:
  • 单击左侧面板顶部左侧的“Runner”标签,然后单击“签名与能力”选项卡。
  • 选择您的团队(根据您的Apple开发者帐户团队),并选择您的签名证书(或简单地勾选“自动管理签名”复选框)。

重要提示: 如果您尝试从Xcode或Flutter存档/构建应用,并收到错误信息:

在导入...期间遇到错误
构建设备时发生错误

这可能是因为它试图构建的架构不是arm64。您可以通过打开Xcode > 并在“构建设置”标签下搜索“架构”,然后将值更改为arm64来解决此问题(而不是默认的“标准架构”)。

配置DJI SDK在Flutter Android项目中的设置

DJI SDK由插件自动添加。然而,我们仍然需要直接在Android项目中通过Android Studio配置几个参数。

重要提示: 在Android上,DJI SDK无法运行模拟器。如果您尝试在Android模拟器上运行它,它会启动并立即崩溃。这是由于DJI SDK的限制。无论如何,为了真正开发并与无人机连接,您必须在实际设备上运行应用(适用于iOS和Android)。

1. 升级到Kotlin

编辑android/gradle/wrapper/gradle-wrapper.properties文件,并将最后一行更新为:

distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip

然后,在android/build.gradle文件中的buildScript部分更新为:

buildscript {
    ext.kotlin_version = '1.7.0'
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.2.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

最后,打开项目并在Android Studio中让Gradle同步。请注意,这个“同步”可能会花费几分钟。

2. 更新Android Manifest XML

</manifest>标签下的<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="...">中添加以下内容,并填写您的Android DJI应用密钥:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="...">
  <!-- 权限和功能 -->
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  <uses-permission android:name="android.permission.VIBRATE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
  <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
  <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
  <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

  <uses-feature android:name="android.hardware.camera" />
  <uses-feature android:name="android.hardware.camera.autofocus" />
  <uses-feature
      android:name="android.hardware.usb.host"
      android:required="false" />
  <uses-feature
      android:name="android.hardware.usb.accessory"
      android:required="true" />
  
  <application
    ...
    android:usesCleartextTraffic="true">
    ...
    <!-- 开始DJI SDK -->
    <uses-library android:name="com.android.future.usb.accessory" />
    <uses-library android:name="org.apache.http.legacy" android:required="false" />
    <meta-data android:name="com.dji.sdk.API_KEY" android:value="{{your-dji-app-key}}" />
    <!-- 结束DJI SDK -->
    
    <activity...
3. 更新android/app/build.gradle

打开android/app/build.gradle文件并更新以下内容:

  • compileSdkVersion设置为33。
  • defaultConfig参数中的minSdkVersion设置为24,targetSdkVersion设置为31。此外,确保multiDexEnabled设置为TRUE
android {
    compileSdkVersion 33
    ...
    defaultConfig {
        ...
        minSdkVersion 24
        targetSdkVersion 31
        ...

        multiDexEnabled true
    }
}
  • buildTypes下添加packagingOptions部分以排除rxjava.properties
packagingOptions {
  exclude 'META-INF/rxjava.properties'
}
  • 在依赖项部分中包含以下依赖项:
dependencies {
    ...
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0'
}
4. 验证gradle.properties

打开android/gradle.properties并验证您是否有以下两行且值为TRUE:

android.useAndroidX=true
android.enableJetifier=true

准备开发环境及模拟器

  • 您的DJI无人机应通过USB连接到计算机。
  • 您应该安装DJI助手 > 连接到无人机 > 打开并启动模拟器。
  • 遥控器应通过电缆连接到您的开发移动设备。
  • 移动设备应连接到与您的计算机相同的Wi-Fi网络。
    • 对于iOS Xcode,这应该足以允许Xcode安装并调试您的应用。
    • 对于Android,使用这些说明允许通过Wi-Fi安装和调试。

使用

完成上述设置后,您可以开始在Flutter代码中使用DJI Flutter插件。

一般而言,Dji类使用DjiHostApi类触发“主机平台”(原生部分)的方法。DjiFlutterApi类负责允许主机在Flutter端触发方法。

完整的示例可以在example/lib/example.dart文件中找到:

获取无人机状态的连续更新

我们希望无人机能向我们发送状态。这由DjiFlutterApi类处理。

DjiFlutterApi.setStatus(Drone drone)方法由原生平台部分每次无人机状态发生变化时触发,例如位置、角度等。 目前,无人机类有一个名为status的String属性,其中包含状态的描述。将来,这将被一个独立的DroneState类替换,其中包含一个枚举代码和一个字符串描述。

当前,可能的drone.status字符串如下:

Registered
Connected
Disconnected
Delegated
Error
Mobile Remote
Mobile Remote Failed
Virtual Stick
Virtual Stick Failed
Gimbal Rotated
Gimbal Failed
Takeoff
Takeoff Failed
Land
Land Failed
Started
Start Failed
Got Media List
Media List Failed
Download Started (一旦下载开始 - 无人机的状态变为实际的百分比进度)
Downloaded
Download Failed
Deleted
Delete Failed
Video Started
Video Start Failed
Video Stopped
Video Stop Failed
Record Started
Record Start Failed
Record Stopped
Record Stop Failed
延伸您的小部件以包含DjiFlutterApi

首先,延伸您的小部件以包含DjiFlutterApi,定义无人机属性,并覆盖initState()方法,添加_initDroneState()DjiFlutterApi.setup()方法以满足您的需求。

class _ExampleWidgetState extends State<ExampleWidget> implements DjiFlutterApi {
  String _platformVersion = 'Unknown';
  String _droneStatus = 'Disconnected';
  String _droneError = '';
  String _droneBatteryPercent = '0';
  String _droneAltitude = '0.0';
  String _droneLatitude = '0.0';
  String _droneLongitude = '0.0';
  String _droneSpeed = '0.0';
  String _droneRoll = '0.0';
  String _dronePitch = '0.0';
  String _droneYaw = '0.0';

  FlightLocation? droneHomeLocation;

  @override
  void initState() {
    super.initState();

    DjiFlutterApi.setup(this);

    ...
  }
  ...
}
setStatus

一旦我们定义了希望从无人机获取的私有属性,让我们重写setStatus()方法。 setStatus()方法是由插件的原生主机部分在无人机状态更改时触发的。

示例:

@override
void setStatus(Drone drone) async {
  setState(() {
    _droneStatus = drone.status ?? 'Disconnected';
    _droneError = drone.error ?? '';
    _droneAltitude = drone.altitude?.toStringAsFixed(2) ?? '0.0';
    _droneBatteryPercent = drone.batteryPercent?.toStringAsFixed(0) ?? '0';
    _droneLatitude = drone.latitude?.toStringAsFixed(7) ?? '0.0';
    _droneLongitude = drone.longitude?.toStringAsFixed(7) ?? '0.0';
    _droneSpeed = drone.speed?.toStringAsFixed(2) ?? '0.0';
    _droneRoll = drone.roll?.toStringAsFixed(3) ?? '0.0';
    _dronePitch = drone.pitch?.toStringAsFixed(3) ?? '0.0';
    _droneYaw = drone.yaw?.toStringAsFixed(3) ?? '0.0';
  });

  // 设置初始无人机位置为无人机的家位置。
  if (droneHomeLocation == null &&
      drone.latitude != null &&
      drone.longitude != null &&
      drone.altitude != null) {
    droneHomeLocation = FlightLocation(
        latitude: drone.latitude!,
        longitude: drone.longitude!,
        altitude: drone.altitude!);
  }
}

请注意,一旦我们从无人机获得第一个状态,我们就设置了droneHomeLocation属性。这仅仅是为了其他部分的示例项目有用。

发送命令到无人机

Dji类提供了连接和操作无人机的方法。

Dji.platformVersion

Dji.platformVersion获取主机(原生平台)版本(例如iOS或Android版本)。

示例:

// 平台消息是异步的,所以我们初始化在一个异步方法中。
Future<void> _getPlatformVersion() async {
  String platformVersion;

  // 平台消息可能会失败,所以我们使用try/catch PlatformException。
  // 我们还处理消息可能返回null的情况。
  try {
    platformVersion = await Dji.platformVersion ?? 'Unknown platform version';
  } on PlatformException {
    platformVersion = 'Failed to get platform version.';
  }

  // 如果在异步平台消息飞行时小部件从树中移除,我们应该丢弃回复而不是调用setState来更新我们的不存在的外观。
  if (!mounted) return;

  setState(() {
    _platformVersion = platformVersion;
  });
}
Dji.registerApp

Dji.registerApp触发DJI SDK注册方法。这是每次启动应用时都需要执行的操作,并在尝试连接到无人机之前。

示例:

Future<void> _registerApp() async {
  try {
    await Dji.registerApp;
    developer.log(
      'registerApp succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'registerApp PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'registerApp Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.connectDrone

Dji.connectDrone方法触发连接到无人机的过程。

远程控制器和DJI无人机需要打开。远程控制器需要通过USB电缆连接到运行应用的移动设备。一旦连接,DjiFlutterApi.setStatus()方法将被触发,并状态更改为“已连接”。

示例:

Future<void> _connectDrone() async {
  try {
    await Dji.connectDrone;
    developer.log(
      'connectDrone succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'connectDrone PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'connectDrone Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.disconnectDrone

Dji.disconnectDrone方法触发从无人机断开连接的过程。

一旦断开连接,DjiFlutterApi.setStatus()方法将被触发,并状态更改为“已断开连接”。

示例:

Future<void> _disconnectDrone() async {
  try {
    await Dji.disconnectDrone;
    developer.log(
      'disconnectDrone succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'disconnectDrone PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'disconnectDrone Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.delegateDrone

Dji.delegateDrone方法触发无人机状态的“监听器”。无人机状态属性的任何更改都会触发SetState()方法(在Flutter端)。

示例:

Future<void> _delegateDrone() async {
  try {
    await Dji.delegateDrone;
    developer.log(
      'delegateDrone succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'delegateDrone PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'delegateDrone Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.takeOff

Dji.takeOff方法命令无人机起飞。

Future<void> _takeOff() async {
  try {
    await Dji.takeOff;
    developer.log(
      'Takeoff succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'Takeoff PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'Takeoff Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.land

Dji.land方法命令无人机降落。

Future<void> _land() async {
  try {
    await Dji.land;
    developer.log(
      'Land succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'Land PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'Land Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.start

Dji.start方法接收一个飞行时间线对象并命令无人机开始执行它。

Flight类定义了不同的飞行属性(例如位置、航点等)并提供了从JSON转换的工具。

在下面的例子中,我们首先验证DroneHomeLocation是否存在,然后我们定义我们的飞行对象,其中包括几个飞行元素。

飞行元素有几种类型:

  • takeOff
  • land
  • waypointMission
  • singleShootPhoto
  • startRecordVideo
  • stopRecordVideo

waypointMission由一系列航点组成。每个航点可以用两种方式定义:locationvector

位置通过纬度、经度和高度定义一个航点。

向量相对于“兴趣点”定义了一个航点。它由以下三个属性定义:

  • distanceFromPointOfInterest
    • “兴趣点”和无人机之间的距离(以米为单位)。
  • headingRelativeToPointOfInterest
    • 如果你想象一条线连接“兴趣点”(例如,你站在哪里)和无人机的家位置——这是方向“0”。
      • 因此,headingRelativeToPointOfInterest的值是从方向“0”到你想要无人机所在的位置的角度。
      • 正的航向角度在你的右边,而负的角度在你的左边。
  • destinationAltitude
    • 无人机在航点处的高度。

举例说明: 想象一下,你是“兴趣点”,拿着一个“激光笔”。 一切都是相对于“线”在你和无人机之间。 如果你指向激光笔到无人机本身——那是“航向0”。 如果你想让第一个航点在你的右边30度,距离100米,高度10米,你应该设置:

'vector': {
  'distanceFromPointOfInterest': 100,
  'headingRelativeToPointOfInterest': 30,
  'destinationAltitude': 10,
},

一个展示如何使用Flight对象并使用Dji.start方法的完整示例:

Future<void> _start() async {
  try {
    droneHomeLocation = FlightLocation(
        latitude: 32.2181125, longitude: 34.8674920, altitude: 0);

    if (droneHomeLocation == null) {
      developer.log(
          'No drone home location exist - unable to start the flight',
          name: kLogKindDjiFlutterPlugin);
      return;
    }

    Flight flight = Flight.fromJson({
      'timeline': [
        {
          'type': 'takeOff',
        },
        {
          'type': 'startRecordVideo',
        },
        {
          'type': 'waypointMission',
          // 为了演示目的,我们将兴趣点设置为距无人机家位置几米远
          'pointOfInterest': {
            'latitude': droneHomeLocation!.latitude + (5 * 0.00000899322),
            'longitude': droneHomeLocation!.longitude + (5 * 0.00000899322),
            'altitude': droneHomeLocation!.altitude,
          },
          'maxFlightSpeed':
              15.0, // 最大飞行速度为15.0。如果您输入更高的值——由于DJI限制,航点任务将不会启动。
          'autoFlightSpeed': 10.0,
          'finishedAction': 'noAction',
          'headingMode': 'towardPointOfInterest',
          'flightPathMode': 'curved',
          'rotateGimbalPitch': true,
          'exitMissionOnRCSignalLost': true,
          'waypoints': [
            {
              // 'location': {
              //   'latitude': 32.2181125,
              //   'longitude': 34.8674920,
              //   'altitude': 20.0,
              // },
              'vector': {
                'distanceFromPointOfInterest': 20,
                'headingRelativeToPointOfInterest': 45,
                'destinationAltitude': 5,
              },
              //'heading': 0,
              'cornerRadiusInMeters': 5,
              'turnMode': 'clockwise',
              // 'gimbalPitch': 0,
            },
            {
              // 'location': {
              //   'latitude': 32.2181125,
              //   'longitude': 34.8674920,
              //   'altitude': 5.0,
              // },
              'vector': {
                'distanceFromPointOfInterest': 10,
                'headingRelativeToPointOfInterest': -45,
                'destinationAltitude': 3,
              },
              //'heading': 0,
              'cornerRadiusInMeters': 5,
              'turnMode': 'clockwise',
              // 'gimbalPitch': 0,
            },
          ],
        },
        {
          'type': 'stopRecordVideo',
        },
        {
          'type': 'singleShootPhoto',
        },
        {
          'type': 'land',
        },
      ],
    });

    // 将航点任务中的向量定义转换为位置
    for (dynamic element in flight.timeline) {
      if (element.type == FlightElementType.waypointMission) {
        CoordinatesConvertion
            .convertWaypointMissionVectorsToLocationsWithGimbalPitch(
                flightElementWaypointMission: element,
                droneHomeLocation: droneHomeLocation!);
      }
    }

    developer.log(
      'Flight Object: ${jsonEncode(flight)}',
      name: kLogKindDjiFlutterPlugin,
    );

    await Dji.start(flight: flight);
    developer.log(
      'Start Flight succeeded',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'Start Flight PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'Start Flight Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}

注意事项:

  • 航点任务的最大飞行速度可以达到15.0。如果您输入更高的值——由于DJI限制,航点任务将不会启动。
  • 如果您不指定gimbalPitch,Flutter DJI插件将自动计算摄像机的云台角度指向兴趣点。
Dji.mobileRemoteController

更新移动遥控器棒数据(通过WiFi)。 控制移动遥控器——一个屏幕上的棒控制器。 仅在无人机通过WiFi连接时可用。

示例:

Future<void> _updateMobileRemoteController() async {
  await Dji.mobileRemoteController(
    enabled: true,
    leftStickHorizontal: _leftStickHorizontal,
    leftStickVertical: _leftStickVertical,
    rightStickHorizontal: _rightStickHorizontal,
    rightStickVertical: _rightStickVertical,
  );
}
Dji.virtualStick

更新虚拟操纵杆飞行控制器数据(通过物理遥控器)。 控制虚拟操纵杆模式是否启用,以及虚拟操纵杆的俯仰、滚转、偏航和垂直油门。 仅在无人机连接到物理遥控器时可用。

示例:

Future<void> _updateVirtualStick() async {
  await Dji.virtualStick(
    enabled: true,
    pitch: _virtualStickPitch,
    roll: _virtualStickRoll,
    yaw: _virtualStickYaw,
    verticalThrottle: _virtualStickVerticalThrottle,
  );
}
Dji.gimbalRotatePitch

更新云台俯仰值(以度为单位)。 控制云台在绝对模式下的俯仰(-90…0)。 0度俯仰与无人机的“鼻子”(航向)对齐,-90度表示云台将相机完全向下指。

示例:

Future<void> _updateGimbalRotatePitch() async {
  await Dji.gimbalRotatePitch(
    degrees: _gimbalPitchInDegrees,
  );
}
Dji.getMediaList

从无人机(SD卡)获取媒体文件列表。 Dji.getMediaList返回一个媒体文件列表。 每个文件的索引用于下载或删除文件。

示例:

Future<List<Media?>?> _getMediaList() async {
  List<Media?>? mediaList;

  try {
    developer.log(
      'Get Media List requested',
      name: kLogKindDjiFlutterPlugin,
    );

    mediaList = await Dji.getMediaList();

    developer.log(
      'Media List: $mediaList',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'Get Media List PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'Get Media List Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }

  return mediaList;
}
Dji.downloadMedia

通过索引从无人机的SD卡下载特定媒体文件。 fileIndex用于从媒体列表中定位相关的文件并下载它。 必须先触发getMediaList()才能使用downloadMedia()

示例:

Future<void> _download() async {
  try {
    developer.log(
      'Download requested',
      name: kLogKindDjiFlutterPlugin,
    );
    // 下载索引为0的媒体文件(即文件号0)
    final fileUrl = await Dji.downloadMedia(0);
    developer.log(
      'Download successful: $fileUrl',
      name: kLogKindDjiFlutterPlugin,
    );
  } on PlatformException catch (e) {
    developer.log(
      'Download PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'Download Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.deleteMedia

通过索引从无人机的SD卡删除特定媒体文件。 fileIndex用于从媒体列表中定位相关的文件并删除它。 必须先触发getMediaList()才能使用downloadMedia()

示例:

Future<void> _delete() async {
  try {
    developer.log(
      'Delete requested',
      name: kLogKindDjiFlutterPlugin,
    );
    // 删除索引为0的媒体文件(即文件号0)
    final deleted = await Dji.deleteMedia(0);
    if (deleted == true) {
      developer.log(
        'Deleted successfully',
        name: kLogKindDjiFlutterPlugin,
      );
    } else {
      developer.log(
        'Delete failed',
        name: kLogKindDjiFlutterPlugin,
      );
    }
  } on PlatformException catch (e) {
    developer.log(
      'Delete PlatformException Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  } catch (e) {
    developer.log(
      'Delete Error',
      error: e,
      name: kLogKindDjiFlutterPlugin,
    );
  }
}
Dji.videoFeedStart

启动DJI视频流。 触发DJI相机预览并流式传输YUV420p原始视频字节流到DjiFlutterApi.sendVideo(Stream stream)方法。 Stream类有一个类型为Uint8Listdata属性(可选)。 字节流可以使用FFMPEG转换为MP4、HLS或其他格式(见示例代码)。

示例:

Future<void> _videoFeedStart() async {
  await Dji.videoFeedStart();
}
Dji.videoFeedStop

停止DJI视频流。

示例:

Future<void> _videoFeedStop() async {
  await Dji.videoFeedStop();
}
sendVideo

sendVideo()方法是由插件的原生主机部分在每次发送视频字节流时触发的。

示例:

@override
void sendVideo(Stream stream) {
  if (stream.data != null && _videoFeedFile != null) {
    _videoFeedSink?.add(stream.data!);
  }
}
Dji.videoRecordStart

启动DJI视频记录器。

示例:

Future<void> _videoRecordStart() async {
  await Dji.videoRecordStart();
}
Dji.videoRecordStop

停止DJI视频记录器。

示例:

Future<void> _videoRecordStop() async {
  await Dji.videoRecordStop();
}

更多关于Flutter大疆无人机安全飞行插件dji_saferf的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter大疆无人机安全飞行插件dji_saferf的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


关于Flutter大疆无人机安全飞行插件dji_saferf的使用,下面是一个基本的代码示例,展示了如何集成和使用该插件来实现无人机的安全飞行功能。请注意,由于实际开发环境和依赖版本可能有所不同,以下代码可能需要根据实际情况进行调整。

首先,确保你已经在Flutter项目中添加了dji_saferf插件的依赖。在你的pubspec.yaml文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  dji_saferf: ^最新版本号  # 请替换为实际的最新版本号

然后,运行flutter pub get来安装依赖。

接下来,在你的Flutter应用中,你需要进行以下步骤来使用dji_saferf插件:

  1. 初始化DJI SDK: 在AndroidManifest.xmlInfo.plist中配置大疆SDK所需的权限和配置。然后,在你的Flutter应用的入口文件(如main.dart)中初始化DJI SDK。

  2. 连接无人机: 使用dji_saferf插件提供的API来连接无人机。

  3. 启用安全飞行功能: 使用dji_saferf插件提供的安全飞行相关API来配置和启用安全飞行功能。

以下是一个简化的代码示例:

import 'package:flutter/material.dart';
import 'package:dji_saferf/dji_saferf.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  DJISafeFlightManager? _safeFlightManager;

  @override
  void initState() {
    super.initState();
    // 初始化DJI SDK(这里省略了具体的初始化代码,请参考大疆SDK文档)
    
    // 假设DJI SDK已经初始化成功,并且已经连接到无人机
    _initSafeFlightManager();
  }

  void _initSafeFlightManager() {
    // 获取SafeFlightManager实例
    _safeFlightManager = DJISafeFlightManager.getInstance();

    // 启用安全飞行功能(这里以设置地理围栏为例)
    _safeFlightManager?.enableGeoFence(true, (DJISafeFlightError? error) {
      if (error == null) {
        print("GeoFence enabled successfully.");
      } else {
        print("Failed to enable GeoFence: ${error.message}");
      }
    });

    // 设置安全飞行高度(例如:设置为50米)
    _safeFlightManager?.setMaxFlightAltitude(50, (DJISafeFlightError? error) {
      if (error == null) {
        print("Max flight altitude set to 50 meters successfully.");
      } else {
        print("Failed to set max flight altitude: ${error.message}");
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DJI Safe Flight Example'),
        ),
        body: Center(
          child: Text('Check console for Safe Flight status updates.'),
        ),
      ),
    );
  }
}

请注意,上述代码仅作为示例,展示了如何获取DJISafeFlightManager实例并启用一些基本的安全飞行功能。在实际应用中,你需要根据大疆SDK的文档和API参考来配置和使用更多的安全飞行功能,如设置禁飞区、返航点等。

此外,确保你已经按照大疆SDK的要求处理了无人机连接状态的变化、错误处理等逻辑,并在应用中添加了必要的用户界面和提示信息。

最后,由于dji_saferf插件的具体API可能会随着版本更新而变化,请务必参考最新的官方文档和API参考。

回到顶部