大疆无人机 MobileSDK(遥控器/手机端)开发 v4版<1>

大疆无人机飞控开发

  • 大疆无人机
  • SDK开发包
    • 功能概述
    • 飞行控制
    • 相机
    • 实时视频流
    • 传感器数据
    • 下载媒体文件
    • 遥控器,电池和无线链路
    • 连接应用程序和产品
    • v4版sdk 二次开发
      • 注册成为DJI开发者
      • 生成 App Key
      • Android 示例代码配置
      • Android Studio项目集成
        • 创建一个新的应用
        • 配置Gradle 脚本
        • 实现应用注册和SDK回调

          大疆无人机

          刚刚结束了项目交付,趁热打铁分享一下这次遇到的新东西。首先了解一下大疆的无人机,它大致可以分为三级。

          • 入门级:适合新手,没事干在野外飞一飞拍拍风景啥的。操作也简单,基本上看飞行教程都能懂,也不需要太高的专业性,飞机也相对较小安全系数相对较高。如:御2系列的一些飞机。

          • 消费级:形体上会比入门级的大一点,但是飞机搭载了一些高精度的东西,比如RTK、高精度摄像头、红外镜头等。这类飞机则需要飞手具备一定的专业知识,并且需要考取大疆的初级飞手证书才能上手飞行。如:精灵系列、悟系列的一些飞机。

          • 专业级:形体上更大,操作不当会出现一些严重后果。它的优势在于高续航、高精度定位、热成像镜头、搭载第三方喊话器等。一般用于大型工程作业,使用这个级别的飞机则需要考取大疆专业飞手证书,一般这个会在你购买专业级飞机的时候赠送你培训课程,一般全部听完就能考过。这类飞机的典型代表有:M30系列、M300系列。

            SDK开发包

            大疆的SDK开发包目前分为两个大版本:

            1. v4版

            Mobile SDK是一款软件开发套件,旨在让开发者能够访问DJI无人机和手持相机产品的丰富功能。该SDK通过兼顾更底层的功能,诸如飞行稳定,电池管理,信号传输和通信等,简化了应用程序开发的过程。这样,开发者就不需要具备丰富的机器人或嵌入式系统背景知识,而可以专注于DJI产品相关的行业应用开发。

            该SDK包括:

            • 可导入Android或iOS应用程序的库/框架,用于访问DJI产品的功能
            • 飞行模拟器和可视化工具
            • 适用于iOS的调试工具和远程logger
            • 示例代码和教程
            • 开发者指南和API文档

              功能概述

              开发者可以通过SDK访问许多DJI产品的功能。开发者可以实现自主飞行,控制相机和云台,接收实时视频图传和传感器数据,下载保存好的媒体文件,以及监听其他组件的状态。

              飞行控制

              Mobile SDK提供三种控制无人机飞行的方式:

              手动操控: 用户使用遥控器操控无人机,而SDK支持监控实时视频流和传感器数据。

              虚拟摇杆命令: SDK支持产生模拟遥控器摇杆的控制指令。

              智能任务: 方便,易于实现无人机的高级控制。例如,可以通过航点任务,让无人机按预定义的飞行路径飞行。

              虚拟摇杆命令和智能任务允许对DJI无人机进行简单而功能强大的自主飞行控制。

              相机

              相机和云台的功能都支持编程调用, 例如:

              相机模式: 视频和静态图像拍摄

              曝光: 快门,ISO,光圈和曝光补偿均支持定制,以实现最大的灵活性

              图像参数: 屏幕长宽比,对比度,色相,清晰度,饱和度和滤镜

              视频参数: 分辨率和帧频

              方向: 使用云台时,相机的朝向和运动可以自动控制

              实时视频流

              开发者可以通过Mobile SDK获取无人机主摄像头的实时视频流。即使摄像头正在将图像或视频捕获到存储介质中,也可以获取实时视频流。

              传感器数据

              开发者可以通过SDK获得丰富的传感器数据。GPS位置,指南针,气压计,飞行速度和海拔高度都是通过Mobile SDK获取的一些传感器数据,频率最高可达10 Hz。

              下载媒体文件

              开发者通过Mobile SDK可以查看和下载保存在相机存储介质(SD卡或固态硬盘)中的照片和视频。预览图和完整的图像数据都可以被访问。

              遥控器,电池和无线链路

              遥控器,电池和无线链路都可以通过SDK进行访问。通常,这些组件会提供相关的状态信息,但开发者也可以对它们进行一些控制。

              连接应用程序和产品

              下图说明了Mobile SDK如何与移动应用程序进行融合以及如何与DJI飞行器进行连接。

              对于手持摄像机产品,遥控器已替换为手持控制器,并且没有飞行器或其他无线链路。

              移动应用程序由Mobile SDK,平台SDK(iOS或Android)构建而成,并在移动设备(Apple iPhone,iPad,Nexus手机,Nexus平板电脑等)上运行。

              移动设备可以通过WiFi无线连接到DJI产品上,也可以通过USB线缆连接到DJI产品上。

              2. v5版

              v5版现在只适用于M30、M300系列,目前还在持续更新,因为是新版的SDK,大疆的工程师也是在不断地再完善里面的内容,这里就不详细说了,下篇文章会详细说这个v5版的SDK。

              v4版sdk 二次开发

              多的不说少的不唠,上主菜。“工欲善其事,必先利其器”,首先准备好开发软件,因为我们使用的是Android版的SDK所以开发软件我们使用Android Studio,本人使用的是Android Studio Fox版,版本之间感觉都一样没有什么开发上的区别。准备好工具之后,先去大疆无人机的开发者官网注册一个开发者账号,并且注册好自己的应用,拿到sdk的专用Key值,申请的方法大疆官网有我就不赘述了。给大家个地址【大疆开发者官网】自己看吧,挺简单的。

              注册成为DJI开发者

              在注册过程中,需要您提供电子邮件信息和信用卡或手机号码用于注册验证。您所提供的任何信用卡信息将仅用于验证,不会收取任何费用。

              本指南假定您使用 Xcode 7.3 以及 Android Studio 2.1.1 以上版本。

              生成 App Key

              每个应用程序都需要一个唯一的应用程序密钥(App Key)来初始化SDK。

              要创建一个应用程序App Key:

              请访问DJI开发者网站的 开发者中心

              • 选择左侧栏的 "应用 "。
              • 选择右侧的 “创建应用” 按钮。
              • 输入应用程序的名称, 开发平台, Package Name,分类和描述信息。
              • 会收到一封应用程序激活邮件,以完成App Key的生成。
              • 可以在开发者中心中找到AppKey,复制粘贴到应用程序配置中。

                Android 示例代码配置

                下载或者克隆Github上的Android示例代码工程。

                在Android Studio中打开项目工程,将生成的App Key字符串粘贴到 “AndroidManifest.xml” 文件中 “com.dji.sdk.API_KEY” meda-data element下的 android:value。

                Android Studio项目集成

                本节中的屏幕截图是使用Android Studio 4.1生成的。

                创建一个新的应用

                可以使用一个新的应用程序来演示如何将DJI SDK集成到Android Studio项目中。

                • 打开Android Studio,然后在初始屏幕上选择Start a new Android Studio project。
                  • 在 New Project 界面:
                    • 设置 Application name 为 “ImportSDKDemo”。
                    • 设置 Company Domain 和Package name 为 “com.dji.ImportSDKDemo”。

                      注意: Package name是 生成App Key 所需的标识字符串。在这个工程中Package name为“com.dji.ImportSDKDemo”

                      • 在 Target Android Devices 界面:

                        • 选择 Phone and Tablet 尺寸。
                        • 选择API 23:Android 6.0 (Marshmallow)。

                        • 在 Add an Activity to Mobile 界面选择 Empty Activity。

                          • 在Configure Activity 界面:
                          • 设置 Activity Name: 为 “MainActivity”。
                          • 确认勾选Generate Layout File 。
                          • 设置Layout Name: 为"activity_main"。
                          • 点击Finish 。

                            配置Gradle 脚本

                            • 在Gradle Scripts 中双击 build.gradle (Module: app)

                              使用以下内容进行更新:

                              apply plugin: 'com.android.application'
                              android { ...
                                  defaultConfig { ...
                                  }
                                  ...
                                  packagingOptions{ doNotStrip "*/*/libdjivideo.so"
                                      doNotStrip "*/*/libSDKRelativeJNI.so"
                                      doNotStrip "*/*/libFlyForbid.so"
                                      doNotStrip "*/*/libduml_vision_bokeh.so"
                                      doNotStrip "*/*/libyuv2.so"
                                      doNotStrip "*/*/libGroudStation.so"
                                      doNotStrip "*/*/libFRCorkscrew.so"
                                      doNotStrip "*/*/libUpgradeVerify.so"
                                      doNotStrip "*/*/libFR.so"
                                      doNotStrip "*/*/libDJIFlySafeCore.so"
                                      doNotStrip "*/*/libdjifs_jni.so"
                                      doNotStrip "*/*/libsfjni.so"
                                      exclude 'META-INF/rxjava.properties'
                                  }
                              }
                              dependencies { ...
                                  implementation('com.dji:dji-sdk:4.15', { exclude module: 'library-anti-distortion'
                                  
                                  })
                                compileOnly 'com.dji:dji-sdk-provided:4.15
                              }
                              
                              • 主要变更为:

                              • 添加 packagingOptions以防止应用程序意外崩溃。

                              • 添加compile和provided依赖项以导入最新的DJIAndroid SDK Maven依赖项。

                                • 选择 Tools -> Android -> Sync Project with Gradle Files 然后等待Gradle项目同步完成。
                                • 再次确认 Maven 依赖

                                  • 在Android Studio菜单中选择File->Project Structure,以打开"Project Structure"界面。然后选择“app”模块,然后单击Dependencies选项卡。

                                    实现应用注册和SDK回调

                                    右键单击com.dji.importSDKDemo,然后选择 New->Java Class以创建一个新的Java类,并将其命名为“MApplication”。

                                    打开MApplication.java文件,并将内容替换为以下内容:

                                    package com.dji.importSDKDemo;
                                    import android.app.Application;
                                    import android.content.Context;
                                    import com.secneo.sdk.Helper;
                                    public class MApplication extends Application { @Override
                                        protected void attachBaseContext(Context paramContext) { super.attachBaseContext(paramContext);
                                            Helper.install(MApplication.this);
                                        }
                                    }
                                    
                                    • 在这里,重写了attachBaseContext()方法,添加了Helper.install(MApplication.this);代码。

                                      注意:由于某些SDK类现在需要在使用之前进行加载,因此加载过程由Helper.install()完成。开发人员需要在使用任何SDK功能之前调用此方法, 否则可能会导致意外崩溃。

                                      修改完成后需要在AndroidManifest中 配置 Application name。

                                      双击app模块中的MainActivity.java。

                                      MainActivity类需要注册应用程序以获得使用Mobile SDK的授权。 它还需要实现SDK回调方法。

                                      • 首先将MainActivity类修改为包括几个类变量,其中包括mProduct,它是代表连接到移动设备的DJI产品的对象。
                                      • 另外,onCreate方法将被修改以调用checkAndRequestPermissions方法来检查和请求运行时权限。

                                        同样,checkAndRequestPermissions方法将有助于调用startSDKRegistration()方法来注册应用程序。

                                        此外,重写onRequestPermissionsResult方法将有助于检查应用程序是否具有足够的权限,如果有,请调用startSDKRegistration()方法来注册应用程序。

                                      • 最后,将MainActivity类替换为:
                                        public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getName();
                                            public static final String FLAG_CONNECTION_CHANGE = "dji_sdk_connection_change";
                                            private static BaseProduct mProduct;
                                            private Handler mHandler;
                                            private static final String[] REQUIRED_PERMISSION_LIST = new String[]{ Manifest.permission.VIBRATE,
                                                    Manifest.permission.INTERNET,
                                                    Manifest.permission.ACCESS_WIFI_STATE,
                                                    Manifest.permission.WAKE_LOCK,
                                                    Manifest.permission.ACCESS_COARSE_LOCATION,
                                                    Manifest.permission.ACCESS_NETWORK_STATE,
                                                    Manifest.permission.ACCESS_FINE_LOCATION,
                                                    Manifest.permission.CHANGE_WIFI_STATE,
                                                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                                    Manifest.permission.BLUETOOTH,
                                                    Manifest.permission.BLUETOOTH_ADMIN,
                                                    Manifest.permission.READ_EXTERNAL_STORAGE,
                                                    Manifest.permission.READ_PHONE_STATE,
                                            };
                                            private List missingPermission = new ArrayList<>();
                                            private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false);
                                            private static final int REQUEST_PERMISSION_CODE = 12345;
                                            @Override
                                            protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
                                                // When the compile and target version is higher than 22, please request the following permission at runtime to ensure the SDK works well.
                                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { checkAndRequestPermissions();
                                                }
                                                setContentView(R.layout.activity_main);
                                                //Initialize DJI SDK Manager
                                                mHandler = new Handler(Looper.getMainLooper());
                                            }
                                            /**
                                             * Checks if there is any missing permissions, and
                                             * requests runtime permission if needed.
                                             */
                                            private void checkAndRequestPermissions() { // Check for permissions
                                                for (String eachPermission : REQUIRED_PERMISSION_LIST) { if (ContextCompat.checkSelfPermission(this, eachPermission) != PackageManager.PERMISSION_GRANTED) { missingPermission.add(eachPermission);
                                                    }
                                                }
                                                // Request for missing permissions
                                                if (missingPermission.isEmpty()) { startSDKRegistration();
                                                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { showToast("Need to grant the permissions!");
                                                    ActivityCompat.requestPermissions(this,
                                                            missingPermission.toArray(new String[missingPermission.size()]),
                                                            REQUEST_PERMISSION_CODE);
                                                }
                                            }
                                            /**
                                             * Result of runtime permission request
                                             */
                                            @Override
                                            public void onRequestPermissionsResult(int requestCode,
                                                                                   @NonNull String[] permissions,
                                                                                   @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                                                // Check for granted permission and remove from missing list
                                                if (requestCode == REQUEST_PERMISSION_CODE) { for (int i = grantResults.length - 1; i >= 0; i--) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { missingPermission.remove(permissions[i]);
                                                        }
                                                    }
                                                }
                                                // If there is enough permission, we will start the registration
                                                if (missingPermission.isEmpty()) { startSDKRegistration();
                                                } else { showToast("Missing permissions!!!");
                                                }
                                            }
                                        }
                                        

                                        DJISDKManager的registerApp()方法具有一个回调,该回调需要处理两种方法:

                                        一、用于处理应用程序注册结果。

                                        二、用于通知硬件产品和移动设备的连接变更。

                                        继续添加如下所示的 startSDKRegistration() 方法,并实现 onRegister(), onProductDisconnect(), onProductConnect(), onComponentChange(), onInitProcess() and onDatabaseDownloadProgress() 和SDKManagerCallback方法:

                                        private void startSDKRegistration() { if (isRegistrationInProgress.compareAndSet(false, true)) { AsyncTask.execute(new Runnable() { @Override
                                                    public void run() { showToast("registering, pls wait...");
                                                        DJISDKManager.getInstance().registerApp(MainActivity.this.getApplicationContext(), new DJISDKManager.SDKManagerCallback() { @Override
                                                            public void onRegister(DJIError djiError) { if (djiError == DJISDKError.REGISTRATION_SUCCESS) { showToast("Register Success");
                                                                    DJISDKManager.getInstance().startConnectionToProduct();
                                                                } else { showToast("Register sdk fails, please check the bundle id and network connection!");
                                                                }
                                                                Log.v(TAG, djiError.getDescription());
                                                            }
                                                            @Override
                                                            public void onProductDisconnect() { Log.d(TAG, "onProductDisconnect");
                                                                showToast("Product Disconnected");
                                                                notifyStatusChange();
                                                            }
                                                            @Override
                                                            public void onProductConnect(BaseProduct baseProduct) { Log.d(TAG, String.format("onProductConnect newProduct:%s", baseProduct));
                                                                showToast("Product Connected");
                                                                notifyStatusChange();
                                                            }
                                                            @Override
                                                            public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent,
                                           BaseComponent newComponent) { if (newComponent != null) { newComponent.setComponentListener(new BaseComponent.ComponentListener() { @Override
                                                                        public void onConnectivityChange(boolean isConnected) { Log.d(TAG, "onComponentConnectivityChanged: " + isConnected);
                                                                            notifyStatusChange();
                                                                        }
                                                                    });
                                                                }
                                                                Log.d(TAG,
                                                                        String.format("onComponentChange key:%s, oldComponent:%s, newComponent:%s",
                                                                                componentKey,
                                                                                oldComponent,
                                                                                newComponent));
                                                            }
                                                            @Override
                                                            public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int i) { }
                                                            @Override
                                                            public void onDatabaseDownloadProgress(long l, long l1) { }
                                                        });
                                                    }
                                                });
                                            }
                                        }
                                        

                                        最后需要实现 notifyStatusChange, Runnable 和 showToast 方法:

                                        private void notifyStatusChange() { mHandler.removeCallbacks(updateRunnable);
                                            mHandler.postDelayed(updateRunnable, 500);
                                        }
                                        private Runnable updateRunnable = new Runnable() { @Override
                                            public void run() { Intent intent = new Intent(FLAG_CONNECTION_CHANGE);
                                                sendBroadcast(intent);
                                            }
                                        };
                                        private void showToast(final String toastMsg) { Handler handler = new Handler(Looper.getMainLooper());
                                            handler.post(new Runnable() { @Override
                                                public void run() { Toast.makeText(getApplicationContext(), toastMsg, Toast.LENGTH_LONG).show();
                                                }
                                            });
                                        }
                                        

                                        必须授予应用程序权限,DJI SDK才能运行。

                                        • 双击 app 模块中的 AndroidManifest.xml 。

                                          在 package=com.dji.ImportSDKDemo 后,

                                          • 在 application 元素的开发添加 android:name=“.MApplication” :
                                            • 在 android:theme=“@style/AppTheme”> 之后, 之前插入如下代码:
                                                  
                                              • 如下所示,在activity元素中插入android:configChanges ="orientation"和android:screenOrientation =“portrait”,以防止在屏幕方向变更时重启activity,并将activity的屏幕方向设置为纵向模式 :
                                                    

                                                生成App Key, 然后用App Key 字符串替换AndroidManifest.xml文件内Please enter your App Key here. 字段。