固定翼无人机航线任务教程
航线任务教程
1. 概述
固定翼无人机的航线任务支持用户手动选择目标点,通过不同的航线规划算法,自动生成飞行航线。用户可以自定义航线飞行参数以及航点的相机动作。算法库支持的任务有地面任务(包括航点任务、多边形任务、返航任务、降落任务、在线任务规划)
说明:航线规划可以参考GitHub上文档说明:航线规划算法文档
2. 航线任务流程
2.1 规划航线
航线规划参考第3章 航线算法规划
2.2 规划航线写入本地文件
通过NativeHelper.writeNewMissionFile接口可以把规划的航点信息转为飞机可以识别的二进制格式,并保存到本地
public static native int writeNewMissionFile(String filePath, PathPlanningParameter parameter);
说明: 写入航线文件
输入参数:
filePath:文件路径
parameter:航线规划参数
输出参数: 0 成功;非零失败
相关参数: PathPlanningParameter
示例代码:
val path = ctx.cacheDir?.absolutePath + "/task_${System.currentTimeMillis()}.aut"
ctx.assets?.open("task.json")?.use {
val parameter = Gson().fromJson(it.bufferedReader(), PathPlanningParameter::class.java)
val result = NativeHelper.writeNewMissionFile(path, parameter)
}
2.3 自检
任务执行前飞机需要先自检, 自检流程参加固定翼无人机自检教程。 同时需要检查飞机电量、基站电量、遥控器电量及上报心跳中各个模块是否存在异常
2.3.1 检查飞机电量
val droneStateData = DeviceManager.getDeviceManager().getFirstDroneDevice()?.getDeviceStateData()
isBatteryOk = (droneStateData?.flightControlData?.batteryRemainingPower ?:) > 15
2.3.2 遥控器电量
遥控器电量可以通过广播消息来获取
val batteryReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val remaining = intent.extras?.getInt("level") // 获得当前电量百分比
val isRCBatteryOK = (remaining ?: 0) > 15
}
}
//register broadcast receive
val filter = IntentFilter()
filter.addAction(Intent.ACTION_BATTERY_CHANGED)
context.registerReceiver(batteryReceiver, filter)
2.3.3 基站电量
val stationStatusNtfy = KeyTools.createKey(WifiBaseStationKey.KeyBaseStationStatusNtfy)
val stationKeyManager = DeviceManager.getDeviceManager().getBaseStationDevice()?.getKeyManager()
stationKeyManager?.listen(stationStatusNtfy, object : CommonCallbacks.KeyListener<WifiBaseStationStatusNtfyBean> {
override fun onValueChange(oldValue: WifiBaseStationStatusNtfyBean?, newValue: WifiBaseStationStatusNtfyBean) {
val remaining = newValue.batteryLevel //BaseStation battery remaining, [0,100]
}
})
data class WifiBaseStationStatusNtfyBean(
val satellitesNum: Int, //基站RTK卫星个数
val status: Int, //基站状态信息 bit 0 基站关机标志位 1-即将关机。0-正常运行中。 bit 1-31 Reserve
val reserve: Int, //预留
val batteryLevel: Int, //基站电量信息,范围0-100
val rtkSignalQuality: Int, //基站RTK信号质量,范围0-5
)
2.4 上传任务
2.4.1 设置任务状态监听
fun addWaypointMissionExecuteStateListener(
listener: CommonCallbacks.KeyListener<MissionWaypointStatusReportNtfyBean>
)
说明: 设置航线任务执行状态的监听器。可以用来监听航线任务的执行状态,比如:航线任务文件上传中,进入航线任务飞行,航线任务完成等状态
输入参数:
listener: 任务状态回调
输出参数: 无
相关参数: MissionTypeEnum MissionCurrentStateEnum MissionWaypointStatusReportNtfyBean
data class MissionWaypointStatusReportNtfyBean(
/**
* 系统时间戳; uint: ms
*/
var timestamp: Long = 0L,
/**
* 任务ID
*/
var missionId: Int = 0,
/**
* 航点序号
*/
var wpSeq: Int = 0,
/**
* 剩余距离
*/
var remainDistance: Int = 0,
/**
* 任务类型
*/
var missionType: MissionTypeEnum = MissionTypeEnum.UNKNOWN,
/**
* 当前拍照张数
*/
var photoNum: Int = 0,
/**
* 剩余时间; uint:s
*/
var remainTime: Int = 0,
/**
* 当前任务状态
*/
var status: MissionCurrentStateEnum = MissionCurrentStateEnum.UNKNOWN,
/**
* 任务guid
*/
var guid: Int = 0,
/**
* 当前动作序号
*/
var actionSeq: Int = 0,
/**
* 航点抵达状态; 0 - arrived; 1 - Not arrived
*/
var arrived: Int = 0,
/**
* 任务设置速度; Uint: 10E-3 m/s
* 注:未使用字段,改为区分 kml 任务 还是 bin 任务
*/
var speedSet: Int = 0,
/**
* 任务进度百分比
*/
var percent: Int = 0,
// 当前的任务阶段
var taskStageIndex: Int = 4,
//总里程 2025/2/13 龙鱼新增
var distance: Int = 0
)
示例:
val missionManager = DeviceManager.getDeviceManager().getFirstDroneDevice()?.getWayPointMissionManager()
missionManager?.addWaypointMissionExecuteStateListener(object : CommonCallbacks.KeyListener<MissionWaypointStatusReportNtfyBean> {
override fun onValueChange(oldValue: MissionWaypointStatusReportNtfyBean?, newValue: MissionWaypointStatusReportNtfyBean) {
SDKLog.i(TAG, "mission[${newValue.missionId}, ${newValue.guid}] status: ${newValue.status}")
}
})
2.4.2 开始上传任务
任务上传使用IMissionManager的uploadMissionByPath接口上传,详细参数信息如下:
fun uploadMissionByPath(
filePath: String,
missionId: Long,
listener: CommonCallbacks.CompletionCallbackWithProgressAndParam<Long>
)
说明: 上传任务到飞机
输入参数:
filePath: 文件路径,需要与规划航线写入本地文件中的路径保持一致
guid: 任务唯一标识
listener: 上传进度及结果回调
输出参数: 无
示例:
val missionId = System.currentTimeMillis().toInt()
val missionManager = DeviceManager.getDeviceManager().getFirstDroneDevice()?.getWayPointMissionManager()
missionManager.uploadMissionByPath(path, missionId.toLong(), object: CommonCallbacks.CompletionCallbackWithProgressAndParam<Long> {
override fun onProgressUpdate(progress: Double) {
SDKLog.i(TAG, "prepareMission onProgressUpdate = $progress")
}
override fun onSuccess(guid: Long?) {
SDKLog.e(TAG, "prepareMission onSuccess = $guid")
}
override fun onFailure(error: IAutelCode, msg: String?) {
SDKLog.e(TAG, "prepareMission onFailure error = $error msg = $msg")
}
})
2.5 开始执行任务
文件上传完成后,使用IMissionManager的startDragonFishMission函数通知飞机开始执行任务,该函数详细参数信息如下:
fun startDragonFishMission(
mission: DFStartMissionBean,
callback: CommonCallbacks.CompletionCallbackWithParam<DFCommonAck>
)
说明: 上传任务到飞机
输入参数:
mission: 任务信息
callback: 结果回调
输出参数: 无
相关参数: DFStartMissionBean, DFCommonAck
data class DFStartMissionBean(
var data0: Int = 0, //Default: 0
var missionId: Int = 0 //任务唯一标识,与上传时的一致
)
//开始任务结果信息
data class DFCommonAck(
var ackStatus: Int = 0, //0: 成功,否则异常
var errorCode1: Int = 0, //错误码
var errorCode2: Int = 0,
) {
fun isSuccess():Boolean{
return ackStatus == 0
}
/**
* 左舵机异常
*/
fun isLeftSteerFault(): Boolean {
return (errorCode1 and 0x1) == 1
}
/**
* 右舵机异常
*/
fun isRightSteerFault(): Boolean {
return (errorCode1 shr 1 and 0x1) == 1
}
/**
* 升降舵异常
*/
fun isFontSteerFault(): Boolean {
return (errorCode1 shr 2 and 0x1) == 1
}
/**
* 空速计异常
*/
fun isAirSpeedFault(): Boolean {
return (errorCode1 shr 3 and 0x1) == 1
}
/**
* RTK异常
*/
fun isRtkFault(): Boolean {
return (errorCode1 shr 4 and 0x1) == 1
}
/**
* GPS异常
*/
fun isGpsFault(): Boolean {
return (errorCode1 shr 5 and 0x1) == 1
}
/**
* 磁力计异常
*/
fun isMagnetometerFault(): Boolean {
return (errorCode1 shr 6 and 0x1) == 1
}
/**
*IMU异常
*/
fun isImuFault(): Boolean {
return (errorCode1 shr 7 and 0x1) == 1
}
/**
* 气压计异常
*/
fun isBarometerFault(): Boolean {
return (errorCode1 shr 8 and 0x1) == 1
}
/**
* 遥控器未连接
*/
fun isRcDisconnect(): Boolean {
return (errorCode1 shr 9 and 0x1) == 1
}
/**
* 姿态不可用
*/
fun isAttitudeNotUse(): Boolean {
return (errorCode1 shr 10 and 0x1) == 1
}
/**
* 航向不可用
*/
fun isHeadingNotUse(): Boolean {
return (errorCode1 shr 11 and 0x1) == 1
}
/**
* 速度不可用
*/
fun isSpeedNotUse(): Boolean {
return (errorCode1 shr 12 and 0x1) == 1
}
/**
* 位置不可用
*/
fun isPositionNotUse(): Boolean {
return (errorCode1 shr 13 and 0x1) == 1
}
/**
* 高度不可用
*/
fun isHeightNotUse(): Boolean {
return (errorCode1 shr 14 and 0x1) == 1
}
/**
* 剩余电量不可用
*/
fun isElectricityNotUse(): Boolean {
return (errorCode1 shr 15 and 0x1) == 1
}
/**
* 速度欧拉角不可用
*/
fun isSpeedEulerNotUse(): Boolean {
return (errorCode1 shr 16 and 0x1) == 1
}
/**
* 强制锁定
*/
fun isMandatoryLock(): Boolean {
return (errorCode1 shr 17 and 0x1) == 1
}
/**
* 空速计校准故障
*/
fun isAirSpeedCalibrationFault(): Boolean {
return (errorCode1 shr 18 and 0x1) == 1
}
/**
* 磁力计校准故障
*/
fun isMagnetometerCalibrationFault(): Boolean {
return (errorCode1 shr 19 and 0x1) == 1
}
/**
* 飞机处于禁飞区
*/
fun isNoFlyZone(): Boolean {
return (errorCode1 shr 20 and 0x1) == 1
}
/**
* 1号电池温度过高
*/
fun isOneBatteryHighTemp(): Boolean {
return (errorCode1 shr 21 and 0x1) == 1
}
/**
* 1号电池温度过低
*/
fun isOneBatteryLowTemp(): Boolean {
return (errorCode1 shr 22 and 0x1) == 1
}
/**
* 1号电池损坏
*/
fun isOneBatteryBad(): Boolean {
return (errorCode1 shr 23 and 0x1) == 1
}
/**
* 2号电池温度过高
*/
fun isTwoBatteryHighTemp(): Boolean {
return (errorCode1 shr 24 and 0x1) == 1
}
/**
* 2号电池温度过低
*/
fun isTwoBatteryLowTemp(): Boolean {
return (errorCode1 shr 25 and 0x1) == 1
}
/**
* 2号电池损坏
*/
fun isTwoBatteryBad(): Boolean {
return (errorCode1 shr 26 and 0x1) == 1
}
/**
* 电池电能不匹配
*/
fun isBatteryElectricityNotBalance(): Boolean {
return (errorCode1 shr 27 and 0x1) == 1
}
/**
* 电池数量不匹配
*/
fun isBatteryNumNotBalance(): Boolean {
return (errorCode1 shr 28 and 0x1) == 1
}
/**
* 导航姿态是否初始化中
*/
fun isNavigationInitialization(): Boolean {
return (errorCode1 shr 29 and 0x1) == 1
}
/**
* 保养提醒 30
*/
fun isMaintenancReminder(): Boolean {
return (errorCode1 shr 30 and 0x1) == 1
}
/**
* 版本匹配
*/
fun isVersionMatching(): Boolean {
return (errorCode1 shr 31 and 0x1) == 1
}
/**
* 移动基站RTK信息是否有效
* true-有效,false-无效
*/
fun isBaseStationRTKInvalid(): Boolean {
return (errorCode2 and 0x1) == 1
}
/**
* 移动基站RTK Heading2 信息是否有效
* true-有效,false-无效
*/
fun isBaseStationRTKHeadingTwoInvalid(): Boolean {
return (errorCode2 shr 1 and 0x1) == 1
}
/**
* 移动基站RTK Heading 信息是否有效
* true-有效,false-无效
*/
fun isBaseStationRTKHeadingInvalid(): Boolean {
return (errorCode2 shr 2 and 0x1) == 1
}
/**
* 移动基站RTK 位置信息是否有效
* true-有效,false-无效
*/
fun isBaseStationRTKPositionInvalid(): Boolean {
return (errorCode2 shr 3 and 0x1) == 1
}
/**
* 移动基站RTK 速度信息是否有效
* true-有效,false-无效
*/
fun isBaseStationRTKSpeedInvalid(): Boolean {
return (errorCode2 shr 4 and 0x1) == 1
}
/**
* 移动基站RTK 姿态信息是否正常
* true-正常,false-异常
*/
fun isBaseStationRTKAttitudeInvalid(): Boolean {
return (errorCode2 shr 5 and 0x1) == 1
}
/**
* 移动返航点信息是否正常
* true-正常,false-异常
*/
fun isMoveHomePositionInvalid(): Boolean {
return (errorCode2 shr 6 and 0x1) == 1
}
/**
* 移动基返航点精度差是否正常
* true-正常,false-异常
*/
fun isMoveHomePrecisionInvalid(): Boolean {
return (errorCode2 shr 7 and 0x1) == 1
}
/**
* 靠近禁飞区
*/
fun isCloseNFZ(): Boolean {
return (errorCode2 shr 8 and 0x1) == 1
}
/**
* 在禁飞区内
*/
fun isInTheNFZ(): Boolean {
return (errorCode2 shr 9 and 0x1) == 1
}
/**
* 靠近可飞区边缘
*/
fun isCloseEdgeFlyZone(): Boolean {
return (errorCode2 shr 10 and 0x1) == 1
}
/**
* 在可飞区外
*/
fun isOutFlyZone(): Boolean {
return (errorCode2 shr 11 and 0x1) == 1
}
/**
* 飞机靠近强制可飞区边缘
*/
fun isCloseEdgeMandFlyZone(): Boolean {
return (errorCode2 shr 12 and 0x1) == 1
}
/**
* 是否同步电子围栏信息
*/
fun isNeedUploadGenFence(): Boolean {
return (errorCode2 shr 13 and 0x1) == 1
}
/**
* 电池温度低于25℃,且电量低于60%,请勿起飞
*/
fun isBatteryLowTempAndHeating(): Boolean {
return (errorCode2 shr 14 and 0x1) == 1
}
/**
* 是否飞控板或飞控CPU过热情况下禁止解锁
*/
fun isFlightControllerOverheating(): Boolean {
return (errorCode2 shr 15 and 0x1) == 1
}
//16-21预留给75KG
/**
* 飞机配置状态不匹配,禁止起飞
*/
fun isStatusMismatch(): Boolean {
return (errorCode2 shr 22 and 0x1) == 1
}
/**
* 左翼达到保养期限,禁止起飞
*/
fun isLeftWingReachedMaintenance(): Boolean {
return (errorCode2 shr 23 and 0x1) == 1
}
/**
* 右翼达到保养期限,禁止起飞
*/
fun isRightWingReachedMaintenance(): Boolean {
return (errorCode2 shr 24 and 0x1) == 1
}
/**
* 升降舵达到保养期限,禁止起飞
*/
fun isTailWingReachedMaintenance(): Boolean {
return (errorCode2 shr 25 and 0x1) == 1
}
/**
* 自检是否成功
*/
fun isAutoCheckSuc(): Boolean {
return (errorCode2 shr 26 and 0x1) == 0
}
}
示例:
val bean = DFStartMissionBean(0, missionId)
val missionManager = DeviceManager.getDeviceManager().getFirstDroneDevice()?.getWayPointMissionManager()
missionManager?.startDragonFishMission(bean, object: CommonCallbacks.CompletionCallbackWithParam<DFCommonAck> {
override fun onSuccess(t: DFCommonAck?) {
if (t?.ackStatus == 0) {
SDKLog.i(TAG, "startDragonFishMission onSuccess")
} else {
SDKLog.i(TAG, "startDragonFishMission error: $t")
}
}
override fun onFailure(error: IAutelCode, msg: String?) {
SDKLog.e(TAG, "startDragonFishMission onFailure error = $error msg = $msg")
}
})
2.6 取消任务
航线任务执行过程中,可以提前终止任务
fun stopDragonFishMission(callback: CommonCallbacks.CompletionCallbackWithParam<Void>)
说明: 取消当前执行的任务
输入参数:
callback: 结果回调
输出参数: 无
相关参数: 无
示例:
val missionManager = DeviceManager.getDeviceManager().getFirstDroneDevice()?.getWayPointMissionManager()
missionManager?.stopDragonFishMission(object: CommonCallbacks.CompletionCallbackWithParam<Void> {
override fun onSuccess(t: Void?) {
SDKLog.i(TAG, "stopDragonFishMission onSuccess")
}
override fun onFailure(error: IAutelCode, msg: String?) {
SDKLog.e(TAG, "stopDragonFishMission onFailure error = $error msg = $msg")
}
})
3. 航线算法规划
航线任务包含多个航点,当飞行器飞到对应的航点时,会执行预设动作,当动作完成以后,飞行器会飞到下一个航点执行预设动作,直到所有航点的任务完成。
航线字段具体含义见 PathPlanningParameter
3.1 航点任务算法规划
下面示例航点任务数据中包含4个航点,分别设置了相机动作定时拍照,定距拍照,录像和无动作;转弯模式分别设置为提前转弯,过点转弯,定时环绕和定圈环绕。
{
"forceLandInfo": [],
"introInfo": {
"CycleMode": 0,
"EndSubID": 1,
"EndWPID": 1.0,
"MaxVz": 3.0,
"MinRadius": 200.0,
"StartSubID": 1,
"StartWPID": 1.0,
"forceLandNum": 6,
"initAlt": 0.0,
"initLat": 22.6711518,
"initLon": 114.401592,
"planningType": 11,
"subMisNum": 1
},
"landInfo": {
"altType": 2,
"approachAlt": 100.0,
"approachLat": 22.67115217201107,
"approachLon": 114.40852555068489,
"approachR": 200.0,
"approachVel": 20.0,
"homeAlt": 0.0,
"homeAltType": 2,
"homeLat": 22.671151890825084,
"homeLon": 114.40159203995298,
"transAlt": 40.0
},
"launchInfo": {
"altType": 2,
"departureAlt": 100.0,
"departureLat": 22.67115217201107,
"departureLon": 114.39465852922104,
"departureR": 200.0,
"departureVel": 20.0,
"launchAlt": 0.0,
"launchLat": 22.6711518,
"launchLon": 114.401592,
"transAlt": 40.0
},
"missionInfo": [
{
"FinishMove": 1,
"IANum": 0,
"InterestArea": [],
"LinkLostMove": 2,
"WPNum": 4,
"subMissionInfo": {
"airLineDir": 0.0,
"baseAlt": 0.0,
"focalLength": 0.0,
"overlapAlong": 0,
"overlapSide": 0,
"pixNumX": 0,
"pixNumY": 0,
"resolution": 0.0,
"sensorOrient": 1,
"sensorSizeX": 0.0,
"sensorSizeY": 0.0
},
"subMissionType": 1,
"wpInfo": [
{
"actionParam1": 2.0,
"gimbalPitch": -20.0,
"gimbalYaw": 20.0,
"payloadAction": 2,
"wpAlt": 100.0,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 1,
"wpLat": 22.674328180678019,
"wpLon": 114.4047683298059,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 1,
"wpTurnParam1": 1.0,
"wpType": 4,
"wpVel": 20.0
},
{
"actionParam1": 30.0,
"gimbalPitch": -21.0,
"gimbalYaw": -12.0,
"payloadAction": 3,
"wpAlt": 100.0,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 2,
"wpLat": 22.675962253677754,
"wpLon": 114.41498908190119,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 2,
"wpTurnParam1": 1.0,
"wpType": 4,
"wpVel": 20.0
},
{
"actionParam1": 0.0,
"gimbalPitch": 0.0,
"gimbalYaw": 0.0,
"payloadAction": 4,
"wpAlt": 100.0,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 3,
"wpLat": 22.670988151474945,
"wpLon": 114.41920251410528,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 3,
"wpTurnParam1": 1.0,
"wpType": 4,
"wpVel": 20.0
},
{
"actionParam1": 0.0,
"gimbalPitch": 0.0,
"gimbalYaw": 0.0,
"payloadAction": 0,
"wpAlt": 100.0,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 4,
"wpLat": 22.664984623824226,
"wpLon": 114.41750881521046,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 4,
"wpTurnParam1": 1.0,
"wpType": 4,
"wpVel": 20.0
}
]
}
]
}
3.2 多边形任务算法规划
多边形任务顶点个数必须大于等于4,下面为一个矩形任务示例:
{
"forceLandInfo": [],
"introInfo": {
"CycleMode": 0,
"EndSubID": 1,
"EndWPID": 1.0,
"MaxVz": 3.0,
"MinRadius": 200.0,
"StartSubID": 1,
"StartWPID": 1.0,
"forceLandNum": 0,
"initAlt": 0.0,
"initLat": 22.6711518,
"initLon": 114.401592,
"planningType": 11,
"subMisNum": 1
},
"landInfo": {
"altType": 2,
"approachAlt": 100.0,
"approachLat": 22.675126780890964,
"approachLon": 114.40010098056912,
"approachR": 200.0,
"approachVel": 19.820269,
"homeAlt": 0.0,
"homeAltType": 2,
"homeLat": 22.6711518,
"homeLon": 114.401592,
"transAlt": 40.0
},
"launchInfo": {
"altType": 1,
"departureAlt": 800.0,
"departureLat": 22.675126780890964,
"departureLon": 114.38623355723455,
"departureR": 200.0,
"departureVel": 19.820269,
"launchAlt": 0.0,
"launchLat": 22.6711518,
"launchLon": 114.401592,
"transAlt": 40.0
},
"missionInfo": [
{
"FinishMove": 1,
"IANum": 0,
"InterestArea": [],
"LinkLostMove": 2,
"WPNum": 4,
"subMissionInfo": {
"airLineDir": 0.0,
"baseAlt": 0.0,
"focalLength": 0.0,
"overlapAlong": 80,
"overlapSide": 70,
"pixNumX": 0,
"pixNumY": 0,
"resolution": 5.0,
"sensorOrient": 1,
"sensorSizeX": 0.0,
"sensorSizeY": 0.0
},
"subMissionType": 2,
"wpInfo": [
{
"actionParam1": 0.0,
"gimbalPitch": -90.0,
"gimbalYaw": 0.0,
"payloadAction": 1,
"wpAlt": 800.00006,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 1,
"wpLat": 22.684655369221415,
"wpLon": 114.3899909790489,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 2,
"wpTurnParam1": 0.0,
"wpType": 5,
"wpVel": 19.820269
},
{
"actionParam1": 0.0,
"gimbalPitch": -90.0,
"gimbalYaw": 0.0,
"payloadAction": 1,
"wpAlt": 800.00006,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 2,
"wpLat": 22.684655369221415,
"wpLon": 114.40269613846066,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 2,
"wpTurnParam1": 0.0,
"wpType": 5,
"wpVel": 19.820269
},
{
"actionParam1": 0.0,
"gimbalPitch": -90.0,
"gimbalYaw": 0.0,
"payloadAction": 1,
"wpAlt": 800.00006,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 3,
"wpLat": 22.67195020980967,
"wpLon": 114.40269613846066,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 2,
"wpTurnParam1": 0.0,
"wpType": 5,
"wpVel": 19.820269
},
{
"actionParam1": 0.0,
"gimbalPitch": -90.0,
"gimbalYaw": 0.0,
"payloadAction": 1,
"wpAlt": 800.00006,
"wpAltType": 2,
"wpClimbMode": 1,
"wpIndex": 4,
"wpLat": 22.67195020980967,
"wpLon": 114.3899909790489,
"wpRadius": 200.0,
"wpReserved1": 0.0,
"wpTurnMode": 2,
"wpTurnParam1": 0.0,
"wpType": 5,
"wpVel": 19.820269
}
]
}
]
}
4. 电子围栏
电子围栏(Geofencing)是一种基于位置的技术,通过定义虚拟地理边界来触发特定操作(如通知、数据上报或设备控制等)。Autel的固定翼飞机支持用户自定义电子围栏,并将电子围栏上传到飞行器中。
4.1 电子围栏示例
Autel的固定翼飞机支持圆形和多边形的电子围栏,以下分别为圆形和多边形的示例,具体字段定义参考GeoFenceModel
圆形电子围栏示例:
{
"areaColor": "#F20000",
"areaLevel": 0,
"areaShape": "CIRCLE",
"areaType": "NO_FLY",
"createTime": 1741179913420,
"effectiveTimeEnd": -1,
"effectiveTimeStart": 0,
"height": 8000.0,
"id": 2,
"isHeightValid": true,
"latLngs": [],
"latestUpdateTime": 1741179913420,
"latitude": 22.64629050297846,
"longitude": 114.3346175862734,
"maxHeight": 0,
"minHeight": 0,
"name": "fence2",
"polygonNum": 0,
"radius": 2000.0,
"updateStatus": 0,
"userId": "",
"uuid": "72ddf9d232c948aa8e68e5e2b9e4b7e7"
}
多边形电子围栏示例:
{
"areaColor": "#F20000",
"areaLevel": 0,
"areaShape": "POLYGON",
"areaType": "NO_FLY",
"createTime": 1741179851929,
"effectiveTimeEnd": -1,
"effectiveTimeStart": 0,
"height": 8000.0,
"id": 1,
"isHeightValid": true,
"latLngs": [
{
"altitude": 0.0,
"id": 3,
"latitude": 22.675001560243169,
"longitude": 114.37438646253925
},
{
"altitude": 0.0,
"id": 4,
"latitude": 22.68069474562877,
"longitude": 114.39080895728216
},
{
"altitude": 0.0,
"id": 5,
"latitude": 22.675001560243169,
"longitude": 114.40202524789506
},
{
"altitude": 0.0,
"id": 6,
"latitude": 22.649501312682724,
"longitude": 114.40202267856681
},
{
"altitude": 0.0,
"id": 7,
"latitude": 22.646094901064495,
"longitude": 114.38854917796918
},
{
"altitude": 0.0,
"id": 8,
"latitude": 22.649501312682724,
"longitude": 114.37438903186752
}
],
"latestUpdateTime": 1741179851929,
"latitude": 22.68069474562877,
"longitude": 114.39080895728216,
"maxHeight": 0,
"minHeight": 0,
"name": "fence1",
"polygonNum": 6,
"radius": 2000.0,
"updateStatus": 0,
"userId": "",
"uuid": "ed64b0abe17e4bde8ddcaa373a7a88c6"
}
4.2 电子围栏上传
4.2.1 生成二进制文件
通过FlyZoneManager.writeNoFlyZoneFile接口可以把电子围栏信息转为飞机可以识别的二进制格式,并保存到本地
fun writeNoFlyZoneFile(
filePath: String,
fileType: FileTypeEnum,
flyZoneInformation: List<FlyZoneInformation>,
authZoneInformation: List<FlyZoneAuthInformation>,
callback: CommonCallbacks.CompletionCallback
)
说明: 写入航线文件
输入参数:
filePath:文件路径
fileType: 文件类型,固定使用FileTypeEnum.ELECTRIC_BARRIER
flyZoneInformation: 禁飞区信息,包括临时禁飞区和电子围栏等
authZoneInformation: 授权区信息
callback: 结果回调
输出参数: 无
相关参数: FileTypeEnum
enum class FileTypeEnum(var value: Int) {
/**
* 高程文件
*/
ELEVATION,
/**
* 固定禁飞区文件
*/
FIXED_NO_FLY_ZONE,
/**
* 临时禁飞区文件
*/
TEMP_NO_FLY_ZONE(3),
/**
* 授权区文件
*/
AUTHORIZED_ZONE(4),
/**
* 电子围栏
*/
ELECTRIC_BARRIER(5),
/**
* 可飞区文件
*/
FLY_ZONE(14),
/**
* Unknown
*/
UNKNOWN(-1)
}
示例代码:
val path = ctx.cacheDir?.absolutePath + "/geofence_${System.currentTimeMillis()}"
ctx.assets?.open("task.json")?.use {
val model = Gson().fromJson(it.bufferedReader(), GeoFenceModel::class.java)
val result = FlyZoneManager.get().writeNoFlyZoneFile(
path,
FileTypeEnum.ELECTRIC_BARRIER,
listOf(model),
listOf(),
object: CommonCallbacks.CompletionCallback {
override fun onSuccess() {
SDKLog.i(TAG, "writeNoFlyZoneFile -> onSuccess")
}
override fun onFailure(code: IAutelCode, msg: String?) {
SDKLog.e(TAG, "writeNoFlyZoneFile -> onFailure[$code, $msg]")
}
}
)
}
4.2.2 上传电子围栏
fun uploadNoFlyZoneFile(file: File, fileType: FileTypeEnum, listener: FileTransmitListener<String?>)
说明: 上传禁飞区域文件到飞机
输入参数:
file:文件
fileType:禁飞区域类型
listener:上传进度和结果回调
输出参数: 无
相关参数: FileTypeEnum
示例代码:
val fileManager = DeviceManager.getDeviceManager().getFirstDroneDevice()?.getFileServiceManager()
fileManager?.uploadNoFlyZoneFile(file, FileTypeEnum.ELECTRIC_BARRIER, object : FileTransmitListener<String?> {
override fun onSuccess(result: String?) {
SDKLog.i(TAG, "uploadNoFlyZoneFile onSuccess: ${file.absoluteFile}, result: $result")
}
override fun onProgress(sendLength: Long, totalLength: Long, speed: Long) {
SDKLog.d(TAG, "uploadNoFlyZoneFile onProgress: ${file.absoluteFile}, sendLength: $sendLength, totalLength: $totalLength, speed: $speed")
}
override fun onFailed(code: IAutelCode, msg: String?) {
SDKLog.e(TAG, "uploadNoFlyZoneFile onFailed: ${file.absoluteFile}, msg : $msg")
}
})
5. 参考代码
航线相关的示例代码,可以参考Sample中的MissionFragment