防止apc线程池爆了
This commit is contained in:
parent
d6a6c505a1
commit
10857ce335
|
|
@ -16,5 +16,22 @@
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"elementType": "File"
|
"elementType": "File",
|
||||||
|
"baselineProfiles": [
|
||||||
|
{
|
||||||
|
"minApi": 28,
|
||||||
|
"maxApi": 30,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/1/app-release.dm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minApi": 31,
|
||||||
|
"maxApi": 2147483647,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/0/app-release.dm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minSdkVersionForDexing": 24
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,8 @@ import android.view.WindowManager
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import com.aros.apron.tools.ToastUtil
|
||||||
|
import dji.sdk.keyvalue.key.RemoteControllerKey
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
|
@ -653,10 +655,11 @@ open class MainActivity : BaseActivity() {
|
||||||
StickManager.getInstance().enableVirtualStick1()
|
StickManager.getInstance().enableVirtualStick1()
|
||||||
|
|
||||||
}
|
}
|
||||||
// btn_test3?.setOnClickListener {
|
btn_test3 = findViewById(R.id.btn_test3)
|
||||||
// StreamManager.getInstance().startstream()
|
btn_test3?.setOnClickListener {
|
||||||
//
|
// DJI按钮方式急停测试
|
||||||
// }
|
//FlightManager.getInstance().emergencyHoverByPauseButton(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
initClickListener()
|
initClickListener()
|
||||||
|
|
@ -819,7 +822,9 @@ open class MainActivity : BaseActivity() {
|
||||||
GeoidManager.getInstance().init(this)
|
GeoidManager.getInstance().init(this)
|
||||||
MissionV3Manager.getInstance().initMissionManager()
|
MissionV3Manager.getInstance().initMissionManager()
|
||||||
enableStream()
|
enableStream()
|
||||||
|
|
||||||
initFpvStream()
|
initFpvStream()
|
||||||
|
|
||||||
startVtxHeartbeat()
|
startVtxHeartbeat()
|
||||||
SpeakerManager.getInstance().addMegaphoneInfoListener()
|
SpeakerManager.getInstance().addMegaphoneInfoListener()
|
||||||
GimbalManager.getInstance().setmode()
|
GimbalManager.getInstance().setmode()
|
||||||
|
|
@ -1102,8 +1107,6 @@ open class MainActivity : BaseActivity() {
|
||||||
try {
|
try {
|
||||||
Synchronizedstatus.setIsruningframe(true)
|
Synchronizedstatus.setIsruningframe(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (startArucoType == 1) {
|
if (startArucoType == 1) {
|
||||||
Aprondown.getInstance()?.detectArucoTags(
|
Aprondown.getInstance()?.detectArucoTags(
|
||||||
height,
|
height,
|
||||||
|
|
@ -1138,7 +1141,7 @@ open class MainActivity : BaseActivity() {
|
||||||
@SuppressLint("SuspiciousIndentation")
|
@SuppressLint("SuspiciousIndentation")
|
||||||
private fun initFpvStream() {
|
private fun initFpvStream() {
|
||||||
cameraManager.addFrameListener(
|
cameraManager.addFrameListener(
|
||||||
ComponentIndexType.PORT_1,
|
ComponentIndexType.FPV,
|
||||||
ICameraStreamManager.FrameFormat.YUV420_888
|
ICameraStreamManager.FrameFormat.YUV420_888
|
||||||
) { frameData, _, _, width, height, _ ->
|
) { frameData, _, _, width, height, _ ->
|
||||||
Movement.getInstance().isVtx = true
|
Movement.getInstance().isVtx = true
|
||||||
|
|
@ -1159,17 +1162,27 @@ open class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
if (startArucoType == 1) {
|
if (startArucoType == 1) {
|
||||||
|
|
||||||
|
|
||||||
|
ApronArucoDetect.getInstance()?.detectArucoTags(
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
frameData,
|
||||||
|
dictionary
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
// ApronArucodownmany.getInstance()?.detectArucoTags(
|
// ApronArucodownmany.getInstance()?.detectArucoTags(
|
||||||
// height,
|
// height,
|
||||||
// width,
|
// width,
|
||||||
// frameData,
|
// frameData,
|
||||||
// dictionary
|
// dictionary
|
||||||
// )
|
// )
|
||||||
AprilTagPort.getInstance().processFrame(
|
|
||||||
frameData,
|
// AprilTagPort.getInstance().processFrame(
|
||||||
width,
|
// frameData,
|
||||||
height
|
// width,
|
||||||
)
|
// height
|
||||||
|
// )
|
||||||
} else if (startArucoType == 2) {
|
} else if (startArucoType == 2) {
|
||||||
AlternateArucoDetect.getInstance()?.detectArucoTags(
|
AlternateArucoDetect.getInstance()?.detectArucoTags(
|
||||||
height,
|
height,
|
||||||
|
|
@ -1178,12 +1191,23 @@ open class MainActivity : BaseActivity() {
|
||||||
dictionary
|
dictionary
|
||||||
)
|
)
|
||||||
} else if (startArucoType == 3) {
|
} else if (startArucoType == 3) {
|
||||||
|
|
||||||
|
ApronArucoDetect.getInstance()?.detectForceTriggerTags(
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
frameData,
|
||||||
|
dictionary
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
// ApronArucodownmany.getInstance()?.detectForceTriggerTags(
|
// ApronArucodownmany.getInstance()?.detectForceTriggerTags(
|
||||||
// height,
|
// height,
|
||||||
// width,
|
// width,
|
||||||
// frameData,
|
// frameData,
|
||||||
// dictionary
|
// dictionary
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Synchronizedstatus.setIsruningframe(false)
|
Synchronizedstatus.setIsruningframe(false)
|
||||||
|
|
@ -1300,7 +1324,7 @@ open class MainActivity : BaseActivity() {
|
||||||
LogUtil.log(TAG, "取消降落,识别机库二维码")
|
LogUtil.log(TAG, "取消降落,识别机库二维码")
|
||||||
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
|
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
|
||||||
Handler().postDelayed(Runnable {
|
Handler().postDelayed(Runnable {
|
||||||
if (!ApronArucodownmany.getInstance().isTriggerSuccess) {
|
if (!AprilTagPort.getInstance().isTriggerSuccess) {
|
||||||
LogUtil.log(TAG, "图传异常:飞往备降点")
|
LogUtil.log(TAG, "图传异常:飞往备降点")
|
||||||
//测试图传丢失
|
//测试图传丢失
|
||||||
AlternateLandingManager.getInstance().startTaskProcess(null)
|
AlternateLandingManager.getInstance().startTaskProcess(null)
|
||||||
|
|
@ -1309,7 +1333,10 @@ open class MainActivity : BaseActivity() {
|
||||||
} else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) {
|
} else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) {
|
||||||
Handler().postDelayed(Runnable {
|
Handler().postDelayed(Runnable {
|
||||||
if (!Aprongim.getInstance().isTriggerSuccess) {
|
if (!Aprongim.getInstance().isTriggerSuccess) {
|
||||||
LogUtil.log(TAG, "图传异常:飞往备降点"+ Movement.getInstance().isVtx)
|
LogUtil.log(
|
||||||
|
TAG,
|
||||||
|
"图传异常:飞往备降点" + Movement.getInstance().isVtx
|
||||||
|
)
|
||||||
//测试图传丢失
|
//测试图传丢失
|
||||||
AlternateLandingManager.getInstance().startTaskProcess(null)
|
AlternateLandingManager.getInstance().startTaskProcess(null)
|
||||||
}
|
}
|
||||||
|
|
@ -1371,7 +1398,7 @@ open class MainActivity : BaseActivity() {
|
||||||
LogUtil.log(TAG, "取消降落,识别备降点二维码")
|
LogUtil.log(TAG, "取消降落,识别备降点二维码")
|
||||||
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
|
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
|
||||||
Handler().postDelayed(Runnable {
|
Handler().postDelayed(Runnable {
|
||||||
if (!ApronArucoDetect.getInstance().isTriggerSuccess) {
|
if (!AprilTagPort.getInstance().isTriggerSuccess) {
|
||||||
LogUtil.log(TAG, "图传异常:飞往备降点")
|
LogUtil.log(TAG, "图传异常:飞往备降点")
|
||||||
//测试图传丢失
|
//测试图传丢失
|
||||||
AlternateLandingManager.getInstance().startTaskProcess(null)
|
AlternateLandingManager.getInstance().startTaskProcess(null)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ public abstract class BaseManager {
|
||||||
mqttMessage.setQos(1);
|
mqttMessage.setQos(1);
|
||||||
org.eclipse.paho.client.mqttv3.IMqttDeliveryToken token =
|
org.eclipse.paho.client.mqttv3.IMqttDeliveryToken token =
|
||||||
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_SERVICES_REPLY, mqttMessage);
|
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_SERVICES_REPLY, mqttMessage);
|
||||||
LogUtil.log(TAG, "回复已提交, tid=" + entity.getTid() + ", msgId=" + token.getMessageId() + ", method=" + entity.getMethod());
|
//LogUtil.log(TAG, "回复已提交, tid=" + entity.getTid() + ", msgId=" + token.getMessageId() + ", method=" + entity.getMethod());
|
||||||
} else {
|
} else {
|
||||||
LogUtil.log(TAG, "回复失败:mqtt 未连接, tid=" + entity.getTid());
|
LogUtil.log(TAG, "回复失败:mqtt 未连接, tid=" + entity.getTid());
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +237,7 @@ public abstract class BaseManager {
|
||||||
if( Movement.getInstance().getTask_media_count()!=0){
|
if( Movement.getInstance().getTask_media_count()!=0){
|
||||||
LogUtil.log(TAG ,"getTask_media_count"+Movement.getInstance().getTask_media_count());
|
LogUtil.log(TAG ,"getTask_media_count"+Movement.getInstance().getTask_media_count());
|
||||||
}
|
}
|
||||||
LogUtil.log(TAG ,"QWQsendFlightTaskProgress2Server");
|
// LogUtil.log(TAG ,"QWQsendFlightTaskProgress2Server");
|
||||||
try {
|
try {
|
||||||
if (MqttManager.getInstance().mqttAndroidClient.isConnected()) {
|
if (MqttManager.getInstance().mqttAndroidClient.isConnected()) {
|
||||||
// 必须加 final,否则内部类(回调)里访问不了
|
// 必须加 final,否则内部类(回调)里访问不了
|
||||||
|
|
@ -613,7 +613,9 @@ public abstract class BaseManager {
|
||||||
|
|
||||||
|
|
||||||
public boolean getGimbalAndCameraEnabled() {
|
public boolean getGimbalAndCameraEnabled() {
|
||||||
if (!PreferenceUtils.getInstance().getNeedTriggerApronArucoLand() && !PreferenceUtils.getInstance().getNeedTriggerAlterArucoLand() && Movement.getInstance().getGoHomeState() != 2) {
|
if (!PreferenceUtils.getInstance().getNeedTriggerApronArucoLand()
|
||||||
|
&& !PreferenceUtils.getInstance().getNeedTriggerAlterArucoLand()
|
||||||
|
&& Movement.getInstance().getGoHomeState() != 2) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
LogUtil.log(TAG, "降落时不允许操作云台/相机/虚拟摇杆");
|
LogUtil.log(TAG, "降落时不允许操作云台/相机/虚拟摇杆");
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.os.Looper;
|
||||||
|
|
||||||
import com.aros.apron.constant.AMSConfig;
|
import com.aros.apron.constant.AMSConfig;
|
||||||
import com.aros.apron.tools.LogUtil;
|
import com.aros.apron.tools.LogUtil;
|
||||||
|
import com.aros.apron.tools.MqttManager;
|
||||||
import com.aros.apron.tools.ToastUtil;
|
import com.aros.apron.tools.ToastUtil;
|
||||||
|
|
||||||
import org.eclipse.paho.android.service.MqttAndroidClient;
|
import org.eclipse.paho.android.service.MqttAndroidClient;
|
||||||
|
|
@ -68,7 +69,11 @@ public class MqttActionCallBack implements IMqttActionListener {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
mqttAndroidClient.connect(options, null, MqttActionCallBack.this);
|
// 使用MqttManager当前持有的client,避免使用已失效的旧引用导致"Invalid ClientHandle"
|
||||||
|
MqttAndroidClient currentClient = MqttManager.getInstance().mqttAndroidClient;
|
||||||
|
if (currentClient != null && !currentClient.isConnected()) {
|
||||||
|
currentClient.connect(MqttManager.getInstance().mMqttConnectOptions, null, MqttActionCallBack.this);
|
||||||
|
}
|
||||||
} catch (MqttException e) {
|
} catch (MqttException e) {
|
||||||
LogUtil.log(TAG, "mqtt重连异常:" + e.toString());
|
LogUtil.log(TAG, "mqtt重连异常:" + e.toString());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.aros.apron.manager.FlightManager;
|
||||||
import com.aros.apron.manager.FlyToPointManager;
|
import com.aros.apron.manager.FlyToPointManager;
|
||||||
import com.aros.apron.manager.GimbalManager;
|
import com.aros.apron.manager.GimbalManager;
|
||||||
import com.aros.apron.manager.MLTEManager;
|
import com.aros.apron.manager.MLTEManager;
|
||||||
|
import com.aros.apron.manager.MediaManager;
|
||||||
import com.aros.apron.manager.MissionV3Manager;
|
import com.aros.apron.manager.MissionV3Manager;
|
||||||
import com.aros.apron.manager.OSDManager;
|
import com.aros.apron.manager.OSDManager;
|
||||||
import com.aros.apron.manager.PayloadlightManager;
|
import com.aros.apron.manager.PayloadlightManager;
|
||||||
|
|
@ -249,6 +250,8 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
|
||||||
LogUtil.log(TAG, "收到:服务端响应TaskFail" + jsonString);
|
LogUtil.log(TAG, "收到:服务端响应TaskFail" + jsonString);
|
||||||
ApronExecutionStatus.getInstance().setServerReplyTaskFail(true);
|
ApronExecutionStatus.getInstance().setServerReplyTaskFail(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
case Constant.TAKEOFF_TO_POINT:
|
case Constant.TAKEOFF_TO_POINT:
|
||||||
// //1.检查图传是否连接
|
// //1.检查图传是否连接
|
||||||
// MissionDataBean data = new Gson().fromJson(new Gson().toJson(message.getData()), MissionDataBean.class);
|
// MissionDataBean data = new Gson().fromJson(new Gson().toJson(message.getData()), MissionDataBean.class);
|
||||||
|
|
@ -680,6 +683,23 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
|
||||||
case Constant.HANGXIANTEST:
|
case Constant.HANGXIANTEST:
|
||||||
MLTEManager.getInstance().test();
|
MLTEManager.getInstance().test();
|
||||||
break;
|
break;
|
||||||
|
case Constant.RESTART_RTSP:
|
||||||
|
LogUtil.log(TAG, "收到:重启视频推流" + jsonString);
|
||||||
|
//重启视频推流
|
||||||
|
StreamManager.getInstance().restart(message);
|
||||||
|
break;
|
||||||
|
case Constant.RTMP_PUSH:
|
||||||
|
LogUtil.log(TAG, "收到:RTMP推流" + jsonString);
|
||||||
|
StreamManager.getInstance().startLiveWithRTMP(message);
|
||||||
|
break;
|
||||||
|
case Constant.STOP_RTMP:
|
||||||
|
LogUtil.log(TAG, "收到:停止RTMP推流" + jsonString);
|
||||||
|
StreamManager.getInstance().stopRTMP(message);
|
||||||
|
break;
|
||||||
|
case Constant.FLY_TO_LAND_POINT:
|
||||||
|
LogUtil.log(TAG,"收到一键备降点任务"+jsonString);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1036,9 +1056,9 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
|
||||||
@Override
|
@Override
|
||||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||||
try {
|
try {
|
||||||
LogUtil.log(TAG, "消息送达确认, msgId=" + token.getMessageId()
|
// LogUtil.log(TAG, "消息送达确认, msgId=" + token.getMessageId()
|
||||||
+ ", complete=" + token.isComplete()
|
// + ", complete=" + token.isComplete()
|
||||||
+ ", topics=" + java.util.Arrays.toString(token.getTopics()));
|
// + ", topics=" + java.util.Arrays.toString(token.getTopics()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtil.log(TAG, "deliveryComplete 异常: " + e);
|
LogUtil.log(TAG, "deliveryComplete 异常: " + e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,23 @@ public class Constant {
|
||||||
* 航线测试
|
* 航线测试
|
||||||
*/
|
*/
|
||||||
public static final String HANGXIANTEST="hangxiantest";
|
public static final String HANGXIANTEST="hangxiantest";
|
||||||
|
|
||||||
|
|
||||||
|
public static final String RESTART_RTSP="restart_rtsp";
|
||||||
|
|
||||||
|
public static final String FLY_TO_LAND_POINT="fly_to_land_point";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTMP推流
|
||||||
|
*/
|
||||||
|
public static final String RTMP_PUSH="push_rtmp";
|
||||||
|
|
||||||
|
public static final String STOP_RTMP="stop_rtmp";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package com.aros.apron.entity;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
import dji.sdk.wpmz.value.mission.WaylineExecuteWaypoint;
|
import dji.sdk.wpmz.value.mission.WaylineExecuteWaypoint;
|
||||||
import dji.sdk.wpmz.value.mission.WaylineWaypoint;
|
import dji.sdk.wpmz.value.mission.WaylineWaypoint;
|
||||||
|
|
@ -41,4 +43,46 @@ public class CurrentWayline {
|
||||||
public void setRouteWaypoints(List<WaylineExecuteWaypoint> waypoints) {
|
public void setRouteWaypoints(List<WaylineExecuteWaypoint> waypoints) {
|
||||||
this.routeWaypoints = waypoints;
|
this.routeWaypoints = waypoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解析后的航点列表(含经纬度、高度、悬停时间、到达/离开状态) */
|
||||||
|
private List<ParsedWaypoint> parsedWaypoints = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 航点队列,用于FIFO处理:peek=当前目标,poll=处理完成出队 */
|
||||||
|
private final Queue<ParsedWaypoint> waypointQueue = new LinkedList<>();
|
||||||
|
|
||||||
|
public List<ParsedWaypoint> getParsedWaypoints() {
|
||||||
|
return parsedWaypoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParsedWaypoints(List<ParsedWaypoint> parsedWaypoints) {
|
||||||
|
this.parsedWaypoints = parsedWaypoints;
|
||||||
|
// 同步初始化队列:清空旧数据,新航点全部入队
|
||||||
|
waypointQueue.clear();
|
||||||
|
if (parsedWaypoints != null) {
|
||||||
|
waypointQueue.addAll(parsedWaypoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取队列(直接操作,用于peek/poll) */
|
||||||
|
public Queue<ParsedWaypoint> getWaypointQueue() {
|
||||||
|
return waypointQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查看当前待处理的航点(不出队),队列空返回null */
|
||||||
|
public ParsedWaypoint peekNextWaypoint() {
|
||||||
|
return waypointQueue.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前航点处理完成,出队 */
|
||||||
|
public void pollNextWaypoint() {
|
||||||
|
waypointQueue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据航点序号查找 */
|
||||||
|
public ParsedWaypoint getParsedWaypoint(int index) {
|
||||||
|
if (index >= 0 && index < parsedWaypoints.size()) {
|
||||||
|
return parsedWaypoints.get(index);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ public class MessageDown {
|
||||||
public static class Data {
|
public static class Data {
|
||||||
private int result;
|
private int result;
|
||||||
private String url;
|
private String url;
|
||||||
|
private String rtmp_url;
|
||||||
private int video_quality;
|
private int video_quality;
|
||||||
private int ideo_quality;
|
private int ideo_quality;
|
||||||
private AlternateLandPoint alternate_land_point;
|
private AlternateLandPoint alternate_land_point;
|
||||||
|
|
@ -632,6 +633,14 @@ public class MessageDown {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRtmp_url() {
|
||||||
|
return rtmp_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRtmp_url(String rtmp_url) {
|
||||||
|
this.rtmp_url = rtmp_url;
|
||||||
|
}
|
||||||
|
|
||||||
public int getVideo_quality() {
|
public int getVideo_quality() {
|
||||||
return video_quality;
|
return video_quality;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,11 @@ public class Movement {
|
||||||
private int landingPower;//降落电量所需百分比
|
private int landingPower;//降落电量所需百分比
|
||||||
private int lowBatteryRTHState;//智能低电量返航状态 0未触发智能低电量返航 1触发智能低电量返航,飞行器正在倒计时 2执行智能低电量返航 3智能低电量返航被取消
|
private int lowBatteryRTHState;//智能低电量返航状态 0未触发智能低电量返航 1触发智能低电量返航,飞行器正在倒计时 2执行智能低电量返航 3智能低电量返航被取消
|
||||||
private String missionName;//当前正在执行的航线名
|
private String missionName;//当前正在执行的航线名
|
||||||
private int currentWaypointIndex = 0;//当前航点下标
|
private int currentWaypointIndex = 0;//当前航点下标(DJI回调)
|
||||||
|
private int targetWaypointIndex = 0;//GPS检测目标航点下标(自研)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private boolean planeWing;//飞机是否在飞
|
private boolean planeWing;//飞机是否在飞
|
||||||
private boolean isMotorsOn;//电机是否起转
|
private boolean isMotorsOn;//电机是否起转
|
||||||
|
|
@ -2525,6 +2529,14 @@ public class Movement {
|
||||||
this.currentWaypointIndex = currentWaypointIndex;
|
this.currentWaypointIndex = currentWaypointIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTargetWaypointIndex() {
|
||||||
|
return targetWaypointIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetWaypointIndex(int targetWaypointIndex) {
|
||||||
|
this.targetWaypointIndex = targetWaypointIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public String getMissionName() {
|
public String getMissionName() {
|
||||||
return missionName;
|
return missionName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -747,7 +747,6 @@ public class CameraManager extends BaseManager {
|
||||||
KeyConnection, ComponentIndexType.PORT_1));
|
KeyConnection, ComponentIndexType.PORT_1));
|
||||||
if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) {
|
if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) {
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
|
|
||||||
int cameraMode = message.getData().getCamera_mode();
|
int cameraMode = message.getData().getCamera_mode();
|
||||||
// 新增:如果当前已经是该模式,直接返回成功
|
// 新增:如果当前已经是该模式,直接返回成功
|
||||||
if (cameraMode == Movement.getInstance().getCamera_mode()) {
|
if (cameraMode == Movement.getInstance().getCamera_mode()) {
|
||||||
|
|
@ -1466,24 +1465,24 @@ public class CameraManager extends BaseManager {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
// 切换到红外视频源时,自动设置调色盘为铁红色(6)
|
// 切换到红外视频源时,自动设置调色盘为铁红色(6)
|
||||||
if (type == 3) {
|
// if (type == 3) {
|
||||||
KeyManager.getInstance().setValue(
|
// KeyManager.getInstance().setValue(
|
||||||
KeyTools.createCameraKey(CameraKey.KeyThermalPalette,
|
// KeyTools.createCameraKey(CameraKey.KeyThermalPalette,
|
||||||
ComponentIndexType.PORT_1,
|
// ComponentIndexType.PORT_1,
|
||||||
CameraLensType.CAMERA_LENS_THERMAL),
|
// CameraLensType.CAMERA_LENS_THERMAL),
|
||||||
CameraThermalPalette.find(6),
|
// CameraThermalPalette.find(6),
|
||||||
new CommonCallbacks.CompletionCallback() {
|
// new CommonCallbacks.CompletionCallback() {
|
||||||
@Override
|
// @Override
|
||||||
public void onSuccess() {
|
// public void onSuccess() {
|
||||||
LogUtil.log(TAG, "红外切换成功,已设置调色盘为铁红");
|
// LogUtil.log(TAG, "红外切换成功,已设置调色盘为铁红");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Override
|
// @Override
|
||||||
public void onFailure(@NonNull IDJIError error) {
|
// public void onFailure(@NonNull IDJIError error) {
|
||||||
LogUtil.log(TAG, "设置红外调色盘失败:" + getIDJIErrorMsg(error));
|
// LogUtil.log(TAG, "设置红外调色盘失败:" + getIDJIErrorMsg(error));
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
sendMsg2Server(message);
|
sendMsg2Server(message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@ import androidx.annotation.Nullable;
|
||||||
import com.aros.apron.base.BaseManager;
|
import com.aros.apron.base.BaseManager;
|
||||||
import com.aros.apron.constant.AMSConfig;
|
import com.aros.apron.constant.AMSConfig;
|
||||||
import com.aros.apron.entity.ApronExecutionStatus;
|
import com.aros.apron.entity.ApronExecutionStatus;
|
||||||
|
import com.aros.apron.entity.CurrentWayline;
|
||||||
import com.aros.apron.entity.MessageDown;
|
import com.aros.apron.entity.MessageDown;
|
||||||
import com.aros.apron.entity.Movement;
|
import com.aros.apron.entity.Movement;
|
||||||
|
import com.aros.apron.entity.ParsedWaypoint;
|
||||||
import com.aros.apron.activity.MainActivity;
|
import com.aros.apron.activity.MainActivity;
|
||||||
import com.aros.apron.mix.Aprondown;
|
import com.aros.apron.mix.Aprondown;
|
||||||
import com.aros.apron.mix.Aprongim;
|
import com.aros.apron.mix.Aprongim;
|
||||||
|
|
@ -47,6 +49,7 @@ import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||||
import dji.sdk.keyvalue.key.GimbalKey;
|
import dji.sdk.keyvalue.key.GimbalKey;
|
||||||
import dji.sdk.keyvalue.key.KeyTools;
|
import dji.sdk.keyvalue.key.KeyTools;
|
||||||
import dji.sdk.keyvalue.key.ProductKey;
|
import dji.sdk.keyvalue.key.ProductKey;
|
||||||
|
import dji.sdk.keyvalue.key.RemoteControllerKey;
|
||||||
import dji.sdk.keyvalue.key.RtkMobileStationKey;
|
import dji.sdk.keyvalue.key.RtkMobileStationKey;
|
||||||
import dji.sdk.keyvalue.value.camera.CameraExposureCompensation;
|
import dji.sdk.keyvalue.value.camera.CameraExposureCompensation;
|
||||||
import dji.sdk.keyvalue.value.camera.CameraExposureMode;
|
import dji.sdk.keyvalue.value.camera.CameraExposureMode;
|
||||||
|
|
@ -351,9 +354,10 @@ public class FlightManager extends BaseManager {
|
||||||
if (newValue.getAltitude() != null) {
|
if (newValue.getAltitude() != null) {
|
||||||
Movement.getInstance().setElevation(newValue.getAltitude());
|
Movement.getInstance().setElevation(newValue.getAltitude());
|
||||||
Movement.getInstance().setTask_height(newValue.getAltitude());
|
Movement.getInstance().setTask_height(newValue.getAltitude());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
double distance = LocationUtils.getDistance(String.valueOf(Movement.getInstance().getHomepoint_longitude()),
|
double distance = LocationUtils.getDistance(String.valueOf(Movement.getInstance().getHomepoint_longitude()),
|
||||||
String.valueOf(Movement.getInstance().getHomepoint_latitude()),
|
String.valueOf(Movement.getInstance().getHomepoint_latitude()),
|
||||||
String.valueOf(newValue.getLongitude()),
|
String.valueOf(newValue.getLongitude()),
|
||||||
|
|
@ -370,6 +374,67 @@ public class FlightManager extends BaseManager {
|
||||||
Movement.getInstance().setLatitude(newValue.getLatitude());
|
Movement.getInstance().setLatitude(newValue.getLatitude());
|
||||||
Movement.getInstance().setLongitude(newValue.getLongitude());
|
Movement.getInstance().setLongitude(newValue.getLongitude());
|
||||||
pushFlightAttitude();
|
pushFlightAttitude();
|
||||||
|
|
||||||
|
|
||||||
|
//航点到达/离开检测:队列FIFO,peek队头,处理完poll出队
|
||||||
|
// 进入:距离≤1m 且 速度≈0;离开:主动(速度>0.2且距离>1m) 或 兜底(hoverTime+1s)
|
||||||
|
ParsedWaypoint pw = CurrentWayline.getInstance().peekNextWaypoint();
|
||||||
|
if (pw != null) {
|
||||||
|
final ParsedWaypoint finalPw = pw;
|
||||||
|
final int targetIdx = finalPw.getIndex();
|
||||||
|
|
||||||
|
double dist = Gpsdistance.calculateDistance(
|
||||||
|
newValue.getLatitude(), newValue.getLongitude(),
|
||||||
|
finalPw.getLatitude(), finalPw.getLongitude());
|
||||||
|
double speed = Movement.getInstance().getHorizontal_speed();
|
||||||
|
|
||||||
|
// 进入:距离≤1m 且 速度为0 且 未到达 → 发"进入"并启动兜底定时器
|
||||||
|
if (dist <= 1.0 && Math.abs(speed) < 0.1 && !finalPw.isReached()) {
|
||||||
|
finalPw.markReached();
|
||||||
|
sendWaypointReachOrLeave("0", String.valueOf(targetIdx));
|
||||||
|
LogUtil.log(TAG, "到达航点[" + targetIdx + "] 距离=" + dist + "m 速度=" + speed + "m/s 悬停=" + finalPw.getHoverTime() + "s");
|
||||||
|
// 兜底:hoverTime+1s 后若仍未离开则强制离开并出队
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
if (!finalPw.isLeft()) {
|
||||||
|
finalPw.markLeft();
|
||||||
|
sendWaypointReachOrLeave("1", String.valueOf(targetIdx));
|
||||||
|
LogUtil.log(TAG, "离开航点[" + targetIdx + "] 兜底超时(hoverTime+1s)");
|
||||||
|
CurrentWayline.getInstance().pollNextWaypoint();
|
||||||
|
Movement.getInstance().setTargetWaypointIndex(targetIdx + 1);
|
||||||
|
}
|
||||||
|
}, (finalPw.getHoverTime() + 1) * 1000L);
|
||||||
|
}
|
||||||
|
// 主动离开:已到达未离开 且 速度>0.2 且 距离>1m → 发"离开"并出队
|
||||||
|
if (finalPw.isReached() && !finalPw.isLeft() && speed > 0.2 && dist > 1.0) {
|
||||||
|
finalPw.markLeft();
|
||||||
|
sendWaypointReachOrLeave("1", String.valueOf(targetIdx));
|
||||||
|
LogUtil.log(TAG, "离开航点[" + targetIdx + "] 距离=" + dist + "m 速度=" + speed + "m/s");
|
||||||
|
CurrentWayline.getInstance().pollNextWaypoint();
|
||||||
|
Movement.getInstance().setTargetWaypointIndex(targetIdx + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========== 判断是否到达FlyTo目标点 ==========
|
// ========== 判断是否到达FlyTo目标点 ==========
|
||||||
double targetLat = Movement.getInstance().getFlyto_target_latitude();
|
double targetLat = Movement.getInstance().getFlyto_target_latitude();
|
||||||
|
|
@ -1288,11 +1353,15 @@ public class FlightManager extends BaseManager {
|
||||||
heightLogCounter = 0;
|
heightLogCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFlying && (Movement.getInstance().getElevation() < 15 && Movement.getInstance().getUltrasonicHeight() < 50 || forceTriggerDetection) && !isSendDetect) {
|
// 超声波卡住(≥50dm)时,飞控高度 <5m 也允许触发
|
||||||
double flyingHeight = Movement.getInstance().getElevation();
|
double currElevation = Movement.getInstance().getElevation();
|
||||||
|
int currUlt = Movement.getInstance().getUltrasonicHeight();
|
||||||
|
boolean altOk = (currElevation < 15 && currUlt < 50) // 正常:两个传感器一致
|
||||||
|
|| (currElevation < 5); // 兜底:飞控高度够低,跳过超声波
|
||||||
|
if (isFlying && (altOk || forceTriggerDetection) && !isSendDetect) {
|
||||||
double thresholdMin = triggerToAlternatePoint ? FLYING_HEIGHT_THRESHOLD_MIN_ALTERNATE : FLYING_HEIGHT_THRESHOLD_MIN;
|
double thresholdMin = triggerToAlternatePoint ? FLYING_HEIGHT_THRESHOLD_MIN_ALTERNATE : FLYING_HEIGHT_THRESHOLD_MIN;
|
||||||
|
|
||||||
if (flyingHeight > thresholdMin || forceTriggerDetection) {
|
if (currElevation > thresholdMin || forceTriggerDetection) {
|
||||||
|
|
||||||
boolean shouldTriggerDetection;
|
boolean shouldTriggerDetection;
|
||||||
|
|
||||||
|
|
@ -1339,7 +1408,7 @@ public class FlightManager extends BaseManager {
|
||||||
PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false);
|
PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false);
|
||||||
PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(true);
|
PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(true);
|
||||||
LogUtil.log(TAG, "开始识别机库二维码,椭球高度:" + Movement.getInstance().getElevation() + "米" + "--超声波高度:" + Movement.getInstance().getUltrasonicHeight() + "分米");
|
LogUtil.log(TAG, "开始识别机库二维码,椭球高度:" + Movement.getInstance().getElevation() + "米" + "--超声波高度:" + Movement.getInstance().getUltrasonicHeight() + "分米");
|
||||||
sendEvent2Server("开始视觉降落", 1);
|
sendEvent2Server("开始视觉降落"+Movement.getInstance().getCapacity_percent(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//PerceptionManager.getInstance().setPerceptionEnable(false);
|
//PerceptionManager.getInstance().setPerceptionEnable(false);
|
||||||
|
|
@ -1670,17 +1739,23 @@ public class FlightManager extends BaseManager {
|
||||||
if (flightMode != null) {
|
if (flightMode != null) {
|
||||||
switch (flightMode) {
|
switch (flightMode) {
|
||||||
case GO_HOME:
|
case GO_HOME:
|
||||||
|
|
||||||
KeyManager.getInstance().performAction(createKey(FlightControllerKey.KeyStopGoHome), new CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>() {
|
KeyManager.getInstance().performAction(createKey(FlightControllerKey.KeyStopGoHome), new CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(EmptyMsg emptyMsg) {
|
public void onSuccess(EmptyMsg emptyMsg) {
|
||||||
LogUtil.log(TAG, "紧急悬停,取消返航成功");
|
//只有在取消返航时才能显示取消返航失败
|
||||||
|
Movement.getInstance().setMode_code(3);
|
||||||
|
isGimbalReset = false;
|
||||||
sendMsg2Server(message);
|
sendMsg2Server(message);
|
||||||
resetAircrftLandingStatus();
|
resetAircrftLandingStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull IDJIError error) {
|
public void onFailure(@NonNull IDJIError error) {
|
||||||
|
LogUtil.log(TAG, "取消返航执行失败" + error);
|
||||||
|
//isGimbalReset = false;
|
||||||
sendFailMsg2Server(message, "紧急悬停,取消返航失败:" + getIDJIErrorMsg(error));
|
sendFailMsg2Server(message, "紧急悬停,取消返航失败:" + getIDJIErrorMsg(error));
|
||||||
|
resetAircrftLandingStatus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
@ -1770,5 +1845,66 @@ public class FlightManager extends BaseManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ★ 急停:清理视觉降落定时器,解除 isStartAruco 入口 guard,确保下次可重新触发
|
||||||
|
ApronArucoDetect.getInstance().stopAndReset();
|
||||||
|
ApronArucoDetectPort.getInstance().stopAndReset();
|
||||||
|
Aprongim.getInstance().stopAndReset();
|
||||||
|
Aprondown.getInstance().stopAndReset();
|
||||||
|
ApronArucodownmany.getInstance().stopAndReset();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public void emergencyHoverByPauseButton(MessageDown message) {
|
||||||
|
// LogUtil.log(TAG, "紧急悬停(DJI按钮方式):setValue KeyPauseButtonDown=true");
|
||||||
|
// KeyManager.getInstance().setValue(createKey(RemoteControllerKey.KeyPauseButtonDown), true, new CommonCallbacks.CompletionCallback() {
|
||||||
|
// @Override
|
||||||
|
// public void onSuccess() {
|
||||||
|
// LogUtil.log(TAG, "紧急悬停(DJI按钮方式) 成功");
|
||||||
|
// resetAircrftLandingStatus();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onFailure(@NonNull IDJIError error) {
|
||||||
|
// LogUtil.log(TAG, "紧急悬停(DJI按钮方式) 失败: " + getIDJIErrorMsg(error));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送航点进入/离开到服务端,格式与350demo WaypointEventSender一致。
|
||||||
|
* @param state "0"=进入 "1"=离开
|
||||||
|
* @param index 航点序号
|
||||||
|
*/
|
||||||
|
private void sendWaypointReachOrLeave(String state, String index) {
|
||||||
|
try {
|
||||||
|
if (MqttManager.getInstance().mqttAndroidClient != null
|
||||||
|
&& MqttManager.getInstance().mqttAndroidClient.isConnected()) {
|
||||||
|
com.google.gson.JsonObject msg = new com.google.gson.JsonObject();
|
||||||
|
msg.addProperty("msg_type", 60203);
|
||||||
|
msg.addProperty("result", 1);
|
||||||
|
msg.addProperty("waypointActionState", state);
|
||||||
|
msg.addProperty("waypointIndex", index);
|
||||||
|
|
||||||
|
org.eclipse.paho.client.mqttv3.MqttMessage mqttMessage =
|
||||||
|
new org.eclipse.paho.client.mqttv3.MqttMessage(
|
||||||
|
new Gson().toJson(msg).getBytes("UTF-8"));
|
||||||
|
mqttMessage.setQos(1);
|
||||||
|
|
||||||
|
MqttManager.getInstance().mqttAndroidClient.publish(
|
||||||
|
AMSConfig.UP_UAV_SERVICES_REPLY,
|
||||||
|
mqttMessage);
|
||||||
|
LogUtil.log(TAG, "航点事件发送: " + ("0".equals(state) ? "进入" : "离开") + " index=" + index);
|
||||||
|
} else {
|
||||||
|
LogUtil.log(TAG, "航点事件发送失败:MQTT未连接");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.log(TAG, "航点事件发送异常: " + e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public class LTEManager extends BaseManager {
|
||||||
@Override
|
@Override
|
||||||
public void onLTELinkInfoUpdate(LTELinkInfo t1) {
|
public void onLTELinkInfoUpdate(LTELinkInfo t1) {
|
||||||
if (t1 != null) {
|
if (t1 != null) {
|
||||||
LogUtil.log(TAG, "信号质量" + t1.getLinkQualityLevel());
|
// LogUtil.log(TAG, "信号质量" + t1.getLinkQualityLevel());
|
||||||
if(t1.getLinkQualityLevel()!=null){
|
if(t1.getLinkQualityLevel()!=null){
|
||||||
Movement.getInstance().setQuality_4g(t1.getLinkQualityLevel().getLteGndSingalBar().value()+ "");
|
Movement.getInstance().setQuality_4g(t1.getLinkQualityLevel().getLteGndSingalBar().value()+ "");
|
||||||
Movement.getInstance().setGnd_quality_4g(t1.getLinkQualityLevel().getOcuSyncSignalQualityLevel().value()+ "");
|
Movement.getInstance().setGnd_quality_4g(t1.getLinkQualityLevel().getOcuSyncSignalQualityLevel().value()+ "");
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.aros.apron.manager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
@ -19,6 +20,7 @@ import com.amazonaws.services.s3.model.ProgressListener;
|
||||||
import com.amazonaws.services.s3.model.PutObjectRequest;
|
import com.amazonaws.services.s3.model.PutObjectRequest;
|
||||||
import com.aros.apron.base.BaseManager;
|
import com.aros.apron.base.BaseManager;
|
||||||
import com.aros.apron.entity.ApronExecutionStatus;
|
import com.aros.apron.entity.ApronExecutionStatus;
|
||||||
|
import com.aros.apron.entity.MessageDown;
|
||||||
import com.aros.apron.entity.Movement;
|
import com.aros.apron.entity.Movement;
|
||||||
import com.aros.apron.tools.LogUtil;
|
import com.aros.apron.tools.LogUtil;
|
||||||
import com.aros.apron.tools.PreferenceUtils;
|
import com.aros.apron.tools.PreferenceUtils;
|
||||||
|
|
@ -36,6 +38,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||||
import dji.sdk.keyvalue.key.KeyTools;
|
import dji.sdk.keyvalue.key.KeyTools;
|
||||||
|
|
@ -74,6 +77,10 @@ public class MediaManager extends BaseManager {
|
||||||
/* ===== 下载失败重试计数 ===== */
|
/* ===== 下载失败重试计数 ===== */
|
||||||
private int downloadFailTimes = 0;
|
private int downloadFailTimes = 0;
|
||||||
private static final int MAX_DOWNLOAD_RETRY = 3;
|
private static final int MAX_DOWNLOAD_RETRY = 3;
|
||||||
|
|
||||||
|
/* ===== 统一主线程Handler ===== */
|
||||||
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
private MediaManager() {
|
private MediaManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,240 +99,328 @@ public class MediaManager extends BaseManager {
|
||||||
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
||||||
MediaDataCenter.getInstance().getMediaManager().addMediaFileListStateListener(new MediaFileListStateListener() {
|
MediaDataCenter.getInstance().getMediaManager().addMediaFileListStateListener(new MediaFileListStateListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onUpdate(MediaFileListState mediaFileListState) {
|
public void onUpdate(MediaFileListState state) {
|
||||||
mState = mediaFileListState;
|
mState = state;
|
||||||
LogUtil.log(TAG, "当前媒体文件状态:" + mediaFileListState.name());
|
LogUtil.log(TAG, "【状态监听】state=" + state.name() + " | isPulling=" + isPulling + " | retryCycle=" + retryCycle);
|
||||||
|
sendEvent2Server("媒体文件状态"+mState,1);
|
||||||
|
if (state == MediaFileListState.UP_TO_DATE && isPulling) {
|
||||||
|
LogUtil.log(TAG, "【状态监听】UP_TO_DATE 到达,开始处理文件列表");
|
||||||
|
processFileList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
* enablePlayback / 拉列表 — 两层重试:DJI方式(3次) → 自有方式(20次)
|
||||||
|
* DJI方式:enable → IDLE调pull/UPDATING只等 → 等20s UP_TO_DATE
|
||||||
|
* 自有方式:enable → 2s后第一次pull → 20s后第二次pull → 检查数据
|
||||||
|
* 每步都挂超时兜底,SDK不给回调也不卡死
|
||||||
|
* ================================================================= */
|
||||||
|
|
||||||
private int enterPlayBackFailTimes;
|
private int enterPlayBackFailTimes;
|
||||||
private boolean isEnablePlayback;
|
private volatile boolean isPlaybackEnabling;
|
||||||
private volatile boolean isPlaybackEnabling; // 防止并发调用
|
private static final int PULL_TIMEOUT_S = 20;
|
||||||
private Handler playbackTimeoutHandler = new Handler(android.os.Looper.getMainLooper());
|
private int retryCycle = 0;
|
||||||
private Runnable playbackTimeoutRunnable = null;
|
private boolean isPulling;
|
||||||
private static final int PLAYBACK_ENABLE_TIMEOUT_MS = 15000; // 进入媒体模式超时 15 秒
|
private Runnable pullTimeoutRunnable;
|
||||||
|
|
||||||
|
// ★ 两层重试计数器
|
||||||
|
private static final int DJI_MAX_RETRIES = 3;
|
||||||
|
private static final int OWN_MAX_RETRIES = 20;
|
||||||
|
private int djiRetries = 0;
|
||||||
|
private int ownRetries = 0;
|
||||||
|
private boolean useOwnMethod = false;
|
||||||
|
|
||||||
public void enablePlayback() {
|
public void enablePlayback() {
|
||||||
|
LogUtil.log(TAG, "【enablePlayback】调用 | isPlaybackEnabling=" + isPlaybackEnabling
|
||||||
|
+ " | useOwnMethod=" + useOwnMethod + " | dji=" + djiRetries + "/" + DJI_MAX_RETRIES
|
||||||
|
+ " | own=" + ownRetries + "/" + OWN_MAX_RETRIES);
|
||||||
if (isPlaybackEnabling) {
|
if (isPlaybackEnabling) {
|
||||||
LogUtil.log(TAG, "媒体模式已在启用中,跳过重复调用");
|
LogUtil.log(TAG, "【enablePlayback】已在启用中,跳过");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean isFirstEntry = !isPlaybackEnabling;
|
|
||||||
|
// ★ DJI方式3次耗尽 → 降级自有方式
|
||||||
|
if (!useOwnMethod && djiRetries >= DJI_MAX_RETRIES) {
|
||||||
|
LogUtil.log(TAG, "【enablePlayback】DJI方式" + DJI_MAX_RETRIES + "次均失败 → 降级自有方式");
|
||||||
|
useOwnMethod = true;
|
||||||
|
ownRetries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ 自有方式次数耗尽 → 放弃,发关机兜底
|
||||||
|
if (useOwnMethod && ownRetries >= OWN_MAX_RETRIES) {
|
||||||
|
LogUtil.log(TAG, "【enablePlayback】自有方式" + OWN_MAX_RETRIES + "次也失败,彻底放弃 → 发关机");
|
||||||
|
sendEvent2Server("媒体列表拉取彻底失败(DJI3+自有20)", 2);
|
||||||
|
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||||
|
disablePlayback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isPlaybackEnabling = true;
|
isPlaybackEnabling = true;
|
||||||
|
|
||||||
// 停止端口扫描和 RTSP 刷新,关闭 RTSP 推流,确保媒体上传稳定
|
if (!useOwnMethod) {
|
||||||
// StreamManager.getInstance().stopStreamRefreshTimer();
|
djiRetries++;
|
||||||
//com.aros.apron.tools.SimplePortScanner.getInstance().stopScan();
|
LogUtil.log(TAG, "【DJI方式】第" + djiRetries + "/" + DJI_MAX_RETRIES + "次");
|
||||||
StreamManager.getInstance().stopstream();
|
} else {
|
||||||
|
ownRetries++;
|
||||||
// 仅首次调用时清理状态,重试时保留计数器
|
LogUtil.log(TAG, "【自有方式】第" + ownRetries + "/" + OWN_MAX_RETRIES + "次");
|
||||||
if (isFirstEntry) {
|
}
|
||||||
|
|
||||||
|
// 首次进入清理状态
|
||||||
|
if (!useOwnMethod && djiRetries == 1) {
|
||||||
uploadedFileNames.clear();
|
uploadedFileNames.clear();
|
||||||
bucketChecked = false;
|
bucketChecked = false;
|
||||||
downloadFailTimes = 0;
|
downloadFailTimes = 0;
|
||||||
enterPlayBackFailTimes = 0;
|
enterPlayBackFailTimes = 0;
|
||||||
isEnablePlayback = false;
|
LogUtil.log(TAG, "【enablePlayback】首次进入,清理状态");
|
||||||
pullMediaFileListFromCameraFailTimes = 0;
|
|
||||||
updatingWaitCount = 0;
|
|
||||||
pullqwq = false;
|
|
||||||
pullStartTime = System.currentTimeMillis();
|
|
||||||
LogUtil.log(TAG, "已清空上传文件集合");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ★ 只在推流活跃时才停,避免无流时产生"直播未启动"的无效日志
|
||||||
|
if (MediaDataCenter.getInstance().getLiveStreamManager().isStreaming()) {
|
||||||
|
StreamManager.getInstance().stopstream();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ SDK enable 可能不给回调 → 15s 超时兜底,防止 isPlaybackEnabling 锁死
|
||||||
|
final java.util.concurrent.atomic.AtomicBoolean enableDone = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
|
Runnable enableTimeout = () -> {
|
||||||
|
if (enableDone.compareAndSet(false, true)) {
|
||||||
|
LogUtil.log(TAG, "【enablePlayback】⏰ SDK enable 15s 无回调!强制重置");
|
||||||
|
isPlaybackEnabling = false;
|
||||||
|
retryAfterDisable("SDK enable无回调");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mainHandler.postDelayed(enableTimeout, 15000);
|
||||||
|
|
||||||
MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() {
|
MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
LogUtil.log(TAG, "进入媒体模式成功");
|
if (!enableDone.compareAndSet(false, true)) return; // 超时已触发,忽略
|
||||||
isPlaybackEnabling = false; // 成功后释放锁
|
mainHandler.removeCallbacks(enableTimeout);
|
||||||
new Handler().postDelayed(new Runnable() {
|
LogUtil.log(TAG, "【enablePlayback】SDK enable 成功 | method=" + (useOwnMethod ? "自有" : "DJI"));
|
||||||
@Override
|
isPlaybackEnabling = false;
|
||||||
public void run() {
|
|
||||||
MediaFileListDataSource source = new
|
mainHandler.postDelayed(() -> {
|
||||||
MediaFileListDataSource.Builder().setIndexType(ComponentIndexType.PORT_1).build();
|
MediaFileListDataSource source = new MediaFileListDataSource.Builder()
|
||||||
|
.setIndexType(ComponentIndexType.PORT_1).build();
|
||||||
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
||||||
|
|
||||||
new Handler().postDelayed(new Runnable() {
|
if (!useOwnMethod) {
|
||||||
@Override
|
// ★ DJI方式:走状态驱动流程
|
||||||
public void run() {
|
LogUtil.log(TAG, "【DJI方式】6s延迟到,开始 pullMediaFileListFromCamera");
|
||||||
pullMediaFileListFromCamera();
|
mainHandler.postDelayed(() -> pullMediaFileListFromCamera(), 3000);
|
||||||
}
|
} else {
|
||||||
}, 3000);
|
// ★ 自有方式:enable成功后2s就拉,不等状态
|
||||||
|
LogUtil.log(TAG, "【自有方式】enable成功,2s后第一次拉取");
|
||||||
|
mainHandler.postDelayed(() -> forcePullInOwnMode(), 2000);
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull IDJIError idjiError) {
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
LogUtil.log(TAG, "第" + enterPlayBackFailTimes + "次进入媒体模式失败:" + new Gson().toJson(idjiError));
|
if (!enableDone.compareAndSet(false, true)) return; // 超时已触发,忽略
|
||||||
if (!isEnablePlayback) {
|
mainHandler.removeCallbacks(enableTimeout);
|
||||||
new Handler().postDelayed(new Runnable() {
|
LogUtil.log(TAG, "【enablePlayback】SDK enable 失败: " + e.description()
|
||||||
@Override
|
+ " | method=" + (useOwnMethod ? "自有" : "DJI"));
|
||||||
public void run() {
|
isPlaybackEnabling = false;
|
||||||
if (enterPlayBackFailTimes < 10) {
|
retryAfterDisable("enable失败");
|
||||||
enterPlayBackFailTimes++;
|
|
||||||
isPlaybackEnabling = false; // 释放锁
|
|
||||||
LogUtil.log(TAG, "第" + enterPlayBackFailTimes + "次重试进入媒体模式");
|
|
||||||
retryEnablePlayback();
|
|
||||||
} else {
|
|
||||||
isPlaybackEnabling = false; // 失败放弃后释放锁
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
|
||||||
sendEvent2Server("媒体模式进入失败:关机",1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重试进入媒体模式(不清理状态,保留计数器) */
|
/**
|
||||||
private void retryEnablePlayback() {
|
* ★ 自有方式:enable 成功 → 2s后第一次拉 → 等20s → 第二次拉 → 检查数据
|
||||||
isPlaybackEnabling = true;
|
* SDK 可能不给任何回调,每步都挂超时兜底
|
||||||
MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() {
|
*/
|
||||||
|
private void forcePullInOwnMode() {
|
||||||
|
LogUtil.log(TAG, "【自有方式】2s后第一次拉取 | ownRetries=" + ownRetries);
|
||||||
|
isPulling = true;
|
||||||
|
|
||||||
|
MediaFileListState state = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState();
|
||||||
|
LogUtil.log(TAG, "【自有方式】当前SDK状态=" + state);
|
||||||
|
|
||||||
|
// ★ 第一次拉:不管状态直接调 SDK pull
|
||||||
|
MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(
|
||||||
|
new PullMediaFileListParam.Builder().count(-1).build(),
|
||||||
|
new CommonCallbacks.CompletionCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
LogUtil.log(TAG, "进入媒体模式成功(重试)");
|
LogUtil.log(TAG, "【自有方式】第一次pull onSuccess → 20s后第二次拉");
|
||||||
isPlaybackEnabling = false;
|
}
|
||||||
new Handler().postDelayed(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
MediaFileListDataSource source = new
|
LogUtil.log(TAG, "【自有方式】第一次pull onFailure: " + e.description() + " → 继续等20s");
|
||||||
MediaFileListDataSource.Builder().setIndexType(ComponentIndexType.PORT_1).build();
|
|
||||||
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
|
||||||
|
|
||||||
new Handler().postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
pullMediaFileListFromCamera();
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull IDJIError idjiError) {
|
|
||||||
LogUtil.log(TAG, "第" + enterPlayBackFailTimes + "次进入媒体模式失败:" + new Gson().toJson(idjiError));
|
|
||||||
if (!isEnablePlayback) {
|
|
||||||
new Handler().postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (enterPlayBackFailTimes < 10) {
|
|
||||||
enterPlayBackFailTimes++;
|
|
||||||
isPlaybackEnabling = false;
|
|
||||||
LogUtil.log(TAG, "第" + enterPlayBackFailTimes + "次重试进入媒体模式");
|
|
||||||
retryEnablePlayback();
|
|
||||||
} else {
|
|
||||||
isPlaybackEnabling = false;
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
|
||||||
sendEvent2Server("媒体模式进入失败:关机",1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ★ SDK 可能不给回调 → 20s后不管结果直接第二次拉
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
LogUtil.log(TAG, "【自有方式】20s到,开始第二次拉取");
|
||||||
|
MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(
|
||||||
|
new PullMediaFileListParam.Builder().count(-1).build(),
|
||||||
|
new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "【自有方式】第二次pull onSuccess → 检查数据");
|
||||||
|
if (isPulling) checkDataInOwnMode();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
|
LogUtil.log(TAG, "【自有方式】第二次pull onFailure: " + e.description());
|
||||||
|
if (isPulling) checkDataInOwnMode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ★ 第二次拉也可能不给回调,再挂 20s 兜底
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
if (isPulling) {
|
||||||
|
LogUtil.log(TAG, "【自有方式】第二次pull 20s兜底超时,强制检查数据");
|
||||||
|
checkDataInOwnMode();
|
||||||
|
}
|
||||||
|
}, PULL_TIMEOUT_S * 1000L);
|
||||||
|
}, PULL_TIMEOUT_S * 1000L);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int pullMediaFileListFromCameraFailTimes;
|
/**
|
||||||
|
* ★ 自有方式:不管 SDK 状态,直接拿数据尝试处理
|
||||||
|
*/
|
||||||
|
private void checkDataInOwnMode() {
|
||||||
|
if (!isPulling) return;
|
||||||
|
isPulling = false;
|
||||||
|
|
||||||
private int updatingWaitCount = 0;
|
try {
|
||||||
private static final int MAX_UPDATING_WAIT = 15; // 最多等待15秒,无文件时快速跳过
|
List<MediaFile> data = MediaDataCenter.getInstance().getMediaManager()
|
||||||
private boolean pullqwq = false;
|
.getMediaFileListData().getData();
|
||||||
private boolean isPullMediaFileListFromCameraSuccess;
|
LogUtil.log(TAG, "【自有方式】SDK返回文件数=" + (data != null ? data.size() : 0)
|
||||||
private long pullStartTime = 0; // 记录整个拉取流程开始时间
|
+ " | state=" + MediaDataCenter.getInstance().getMediaManager().getMediaFileListState());
|
||||||
private static final int MAX_PULL_DURATION = 25; // 整个拉取流程最多25秒,超时强制关机
|
if (data != null && !data.isEmpty()) {
|
||||||
|
// 拉到了!重置所有计数器,走正常处理流程
|
||||||
|
LogUtil.log(TAG, "【自有方式】✅ 成功拉到" + data.size() + "个文件,切回正常流程");
|
||||||
|
djiRetries = 0;
|
||||||
|
ownRetries = 0;
|
||||||
|
retryCycle = 0;
|
||||||
|
useOwnMethod = false;
|
||||||
|
processFileList();
|
||||||
|
} else {
|
||||||
|
LogUtil.log(TAG, "【自有方式】无数据,退出重进");
|
||||||
|
retryAfterDisable("自有方式无数据");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.log(TAG, "【自有方式】获取数据异常: " + e.getMessage());
|
||||||
|
retryAfterDisable("自有方式获取数据异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retryAfterDisable(String reason) {
|
||||||
|
retryCycle++;
|
||||||
|
LogUtil.log(TAG, "【retry】" + reason + " → 先disable再enable | method=" + (useOwnMethod ? "自有" : "DJI")
|
||||||
|
+ " | retryCycle=" + retryCycle);
|
||||||
|
disablePlaybackAndThen(() -> {
|
||||||
|
LogUtil.log(TAG, "【retry】disable完成,3s后enablePlayback");
|
||||||
|
mainHandler.postDelayed(() -> enablePlayback(), 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void pullMediaFileListFromCamera() {
|
private void pullMediaFileListFromCamera() {
|
||||||
// 全局超时检查:防止状态机异常导致无限循环
|
// ★ 防重复post:先取消旧的超时
|
||||||
long elapsed = (System.currentTimeMillis() - pullStartTime) / 1000;
|
if (pullTimeoutRunnable != null) {
|
||||||
if (elapsed >= MAX_PULL_DURATION) {
|
mainHandler.removeCallbacks(pullTimeoutRunnable);
|
||||||
LogUtil.log(TAG, "拉取流程总耗时 " + elapsed + "s,超时强制关机");
|
LogUtil.log(TAG, "【pullList】取消旧超时");
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
}
|
||||||
sendEvent2Server("媒体文件拉取超时", 2);
|
isPulling = true;
|
||||||
disablePlayback();
|
|
||||||
|
// ★ 定义一个统一的超时runnable(SDK不给回调的兜底)
|
||||||
|
pullTimeoutRunnable = () -> {
|
||||||
|
LogUtil.log(TAG, "【pullList】⏰ 20s超时触发!当前state="
|
||||||
|
+ MediaDataCenter.getInstance().getMediaManager().getMediaFileListState()
|
||||||
|
+ " | djiRetries=" + djiRetries + " → 退出重进");
|
||||||
|
isPulling = false;
|
||||||
|
retryAfterDisable("媒体文件拉取超时(SDK无回调)");
|
||||||
|
};
|
||||||
|
// 启动20s看门狗
|
||||||
|
mainHandler.postDelayed(pullTimeoutRunnable, PULL_TIMEOUT_S * 1000L);
|
||||||
|
LogUtil.log(TAG, "【pullList】20s超时看门狗已启动");
|
||||||
|
|
||||||
|
MediaFileListState state = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState();
|
||||||
|
LogUtil.log(TAG, "【pullList】当前SDK状态=" + state + " | isPulling=" + isPulling + " | djiRetries=" + djiRetries);
|
||||||
|
|
||||||
|
// 状态已是 UP_TO_DATE:数据已就绪,直接处理
|
||||||
|
if (state == MediaFileListState.UP_TO_DATE) {
|
||||||
|
LogUtil.log(TAG, "【pullList】状态已是UP_TO_DATE,数据已就绪,直接处理");
|
||||||
|
processFileList();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaFileListState currentState = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState();
|
// ★ DJI方式:IDLE 才调 pull,UPDATING 只等状态(严格按 DJI 建议)
|
||||||
LogUtil.log(TAG, "当前状态:" + currentState + ",准备拉取文件列表(已耗时" + elapsed + "s)");
|
if (state == MediaFileListState.IDLE) {
|
||||||
|
LogUtil.log(TAG, "【pullList】状态=IDLE → 调用SDK pullMediaFileList");
|
||||||
if (currentState == MediaFileListState.IDLE) {
|
MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(
|
||||||
LogUtil.log(TAG, "状态为IDLE,开始拉取文件列表");
|
new PullMediaFileListParam.Builder().count(-1).build(),
|
||||||
MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(new PullMediaFileListParam.Builder().count(-1).build(), new CommonCallbacks.CompletionCallback() {
|
new CommonCallbacks.CompletionCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
LogUtil.log(TAG, "拉取请求已接受,等待状态变为UP_TO_DATE");
|
if (pullTimeoutRunnable == null) {
|
||||||
pullMediaFileListFromCameraFailTimes = 0;
|
LogUtil.log(TAG, "【pullList】SDK pull onSuccess 但数据已处理完毕,忽略");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtil.log(TAG, "【pullList】SDK pull onSuccess → 续时20s,等待UP_TO_DATE");
|
||||||
|
mainHandler.removeCallbacks(pullTimeoutRunnable);
|
||||||
|
mainHandler.postDelayed(pullTimeoutRunnable, PULL_TIMEOUT_S * 1000L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull IDJIError idjiError) {
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
LogUtil.log(TAG, "拉取请求失败: " + new Gson().toJson(idjiError));
|
LogUtil.log(TAG, "【pullList】SDK pull onFailure: " + e.description()
|
||||||
if (pullMediaFileListFromCameraFailTimes < 5) {
|
+ " → 继续等状态监听器或超时");
|
||||||
pullMediaFileListFromCameraFailTimes++;
|
|
||||||
LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试...");
|
|
||||||
} else {
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
|
||||||
sendEvent2Server("拉取媒体文件失败",2);
|
|
||||||
disablePlayback();
|
|
||||||
LogUtil.log(TAG, "发送关闭无人机");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 不依赖回调续命,无条件调度下一轮轮询
|
|
||||||
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000);
|
|
||||||
} else if (currentState == MediaFileListState.UP_TO_DATE) {
|
|
||||||
// 状态为UP_TO_DATE,获取文件列表数据
|
|
||||||
LogUtil.log(TAG, "状态为UP_TO_DATE,获取文件列表数据");
|
|
||||||
try {
|
|
||||||
// 确保获取文件列表数据
|
|
||||||
List<MediaFile> rawList = MediaDataCenter.getInstance().getMediaManager().getMediaFileListData().getData();
|
|
||||||
|
|
||||||
// 检查文件列表是否为空
|
|
||||||
if (rawList == null || rawList.isEmpty()) {
|
|
||||||
LogUtil.log(TAG, "文件列表为空,重试拉取");
|
|
||||||
// 状态已经是UP_TO_DATE时,空列表可能确实无文件,快速重试2次后放弃
|
|
||||||
if (pullMediaFileListFromCameraFailTimes < 2) {
|
|
||||||
pullMediaFileListFromCameraFailTimes++;
|
|
||||||
LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试...");
|
|
||||||
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 3000);
|
|
||||||
} else {
|
|
||||||
LogUtil.log(TAG, "UP_TO_DATE状态文件列表持续为空,确认无文件");
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
|
||||||
sendEvent2Server("拉取媒体文件失败",2);
|
|
||||||
disablePlayback();
|
|
||||||
LogUtil.log(TAG, "发送关闭无人机");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogUtil.log(TAG, "原始文件列表数量: " + rawList.size());
|
// ★ DJI方式:UPDATING 不主动拉,只等状态监听器通知 UP_TO_DATE 或超时
|
||||||
|
LogUtil.log(TAG, "【pullList】状态=" + state + ",等状态监听器通知 UP_TO_DATE 或超时");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 状态监听器回调——SDK 通知 UP_TO_DATE 时处理 */
|
||||||
|
private void processFileList() {
|
||||||
|
LogUtil.log(TAG, "【processFileList】进入 | isPulling=" + isPulling + " | cycle=" + retryCycle);
|
||||||
|
if (pullTimeoutRunnable != null) {
|
||||||
|
mainHandler.removeCallbacks(pullTimeoutRunnable);
|
||||||
|
pullTimeoutRunnable = null;
|
||||||
|
LogUtil.log(TAG, "【processFileList】超时看门狗已取消");
|
||||||
|
}
|
||||||
|
isPulling = false;
|
||||||
|
retryCycle = 0;
|
||||||
|
djiRetries = 0;
|
||||||
|
ownRetries = 0;
|
||||||
|
useOwnMethod = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<MediaFile> rawList = MediaDataCenter.getInstance().getMediaManager()
|
||||||
|
.getMediaFileListData().getData();
|
||||||
|
LogUtil.log(TAG, "【processFileList】SDK返回文件数=" + (rawList != null ? rawList.size() : 0));
|
||||||
|
if (rawList == null || rawList.isEmpty()) {
|
||||||
|
LogUtil.log(TAG, "【processFileList】文件列表为空,关机");
|
||||||
|
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||||
|
sendEvent2Server("无媒体文件", 1);
|
||||||
|
disablePlayback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtil.log(TAG, "文件列表数量: " + rawList.size());
|
||||||
|
|
||||||
// 过滤已上传文件
|
|
||||||
mediaFiles = new ArrayList<>();
|
mediaFiles = new ArrayList<>();
|
||||||
for (MediaFile mf : rawList) {
|
for (MediaFile mf : rawList) {
|
||||||
if (!uploadedFileNames.contains(mf.getFileName())) {
|
if (!uploadedFileNames.contains(mf.getFileName())) {
|
||||||
mediaFiles.add(mf);
|
mediaFiles.add(mf);
|
||||||
} else {
|
|
||||||
LogUtil.log(TAG, "跳过已上传文件: " + mf.getFileName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 修复:在过滤后设置任务媒体计数
|
|
||||||
Movement.getInstance().setTask_media_count(mediaFiles.size());
|
Movement.getInstance().setTask_media_count(mediaFiles.size());
|
||||||
LogUtil.log(TAG, "过滤后文件数量: " + mediaFiles.size());
|
if (PreferenceUtils.getInstance().getMissionType() == 0) sendFlightTaskProgress2Server();
|
||||||
if(PreferenceUtils.getInstance().getMissionType()==0){
|
|
||||||
sendFlightTaskProgress2Server();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (mediaFiles.isEmpty()) {
|
if (mediaFiles.isEmpty()) {
|
||||||
LogUtil.log(TAG, "所有文件均已上传,直接清理");
|
LogUtil.log(TAG, "全部已上传,直接清理");
|
||||||
downLoadMediaFileIndex = 0;
|
|
||||||
// 提前设置关机标志,让 aircraftStoredReply 能立即回复成功
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||||
removeAllFiles();
|
removeAllFiles();
|
||||||
return;
|
return;
|
||||||
|
|
@ -335,39 +430,16 @@ public class MediaManager extends BaseManager {
|
||||||
pullOriginalMediaFileFromCamera();
|
pullOriginalMediaFileFromCamera();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtil.log(TAG, "获取文件列表数据失败: " + e.getMessage());
|
LogUtil.log(TAG, "处理文件列表异常: " + e.getMessage());
|
||||||
// 发生异常时快速重试2次
|
|
||||||
if (pullMediaFileListFromCameraFailTimes < 2) {
|
|
||||||
pullMediaFileListFromCameraFailTimes++;
|
|
||||||
LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试...");
|
|
||||||
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000);
|
|
||||||
} else {
|
|
||||||
LogUtil.log(TAG, "重试次数达到上限,拉取失败");
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||||
sendEvent2Server("拉取媒体文件失败",2);
|
sendEvent2Server("处理文件列表失败", 2);
|
||||||
disablePlayback();
|
disablePlayback();
|
||||||
LogUtil.log(TAG, "发送关闭无人机");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 其他状态(如UPDATING),等待状态变化,不要重复调用pullMediaFileListFromCamera
|
|
||||||
LogUtil.log(TAG, "状态为" + currentState + ",等待状态变化... (count=" + updatingWaitCount + ")");
|
|
||||||
updatingWaitCount++;
|
|
||||||
|
|
||||||
// 增加超时处理,避免无限等待
|
|
||||||
if (updatingWaitCount >= MAX_UPDATING_WAIT) {
|
|
||||||
LogUtil.log(TAG, "等待状态变化超时,强制关机");
|
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
|
||||||
sendEvent2Server("媒体文件状态更新超时",2);
|
|
||||||
disablePlayback();
|
|
||||||
LogUtil.log(TAG, "发送关闭无人机");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
* 文件下载/上传 — 保持原始逻辑不变
|
||||||
|
* ================================================================= */
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
public void pullOriginalMediaFileFromCamera() {
|
public void pullOriginalMediaFileFromCamera() {
|
||||||
|
|
@ -379,7 +451,6 @@ public class MediaManager extends BaseManager {
|
||||||
downLoadMediaFileIndex++;
|
downLoadMediaFileIndex++;
|
||||||
|
|
||||||
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
||||||
// This refers to when all files have been downloaded or failed. Clear SD card, cache, exit media mode, and shut down the drone
|
|
||||||
downLoadMediaFileIndex = 0;
|
downLoadMediaFileIndex = 0;
|
||||||
removeAllFiles();
|
removeAllFiles();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -498,15 +569,19 @@ public class MediaManager extends BaseManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
* MinIO上传 — 原始逻辑不变
|
||||||
|
* ================================================================= */
|
||||||
|
|
||||||
private AmazonS3 s3 = new AmazonS3Client(new AWSCredentials() {
|
private AmazonS3 s3 = new AmazonS3Client(new AWSCredentials() {
|
||||||
@Override
|
@Override
|
||||||
public String getAWSAccessKeyId() {
|
public String getAWSAccessKeyId() {
|
||||||
return PreferenceUtils.getInstance().getAccessKey(); // minio的key
|
return PreferenceUtils.getInstance().getAccessKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAWSSecretKey() {
|
public String getAWSSecretKey() {
|
||||||
return PreferenceUtils.getInstance().getSecretKey(); // minio的密钥
|
return PreferenceUtils.getInstance().getSecretKey();
|
||||||
}
|
}
|
||||||
}, Region.getRegion(Regions.US_EAST_1), new ClientConfiguration()
|
}, Region.getRegion(Regions.US_EAST_1), new ClientConfiguration()
|
||||||
.withConnectionTimeout(30000)
|
.withConnectionTimeout(30000)
|
||||||
|
|
@ -520,7 +595,7 @@ public class MediaManager extends BaseManager {
|
||||||
@Override
|
@Override
|
||||||
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
|
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
|
||||||
// 服务器地址
|
// 服务器地址
|
||||||
s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl()); // http://ip:端口号
|
s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl());
|
||||||
// Bucket只在首次上传时检查创建,后续上传不再重复请求
|
// Bucket只在首次上传时检查创建,后续上传不再重复请求
|
||||||
if (!bucketChecked) {
|
if (!bucketChecked) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
|
@ -534,17 +609,11 @@ public class MediaManager extends BaseManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传文件到网关MINIO存储服务
|
|
||||||
s3.putObject(
|
s3.putObject(
|
||||||
new PutObjectRequest(
|
new PutObjectRequest(
|
||||||
PreferenceUtils.getInstance().getBucketName(),
|
PreferenceUtils.getInstance().getBucketName(),
|
||||||
"/" + PreferenceUtils.getInstance().getObjectKey() + "/" + mediaFile.getFileName(),
|
"/" + PreferenceUtils.getInstance().getObjectKey() + "/" + mediaFile.getFileName(),
|
||||||
file
|
file
|
||||||
// new PutObjectRequest(
|
|
||||||
// PreferenceUtils.getInstance().getBucketName(),
|
|
||||||
// "/" + PreferenceUtils.getInstance().getObjectKey() + "/" +
|
|
||||||
// PreferenceUtils.getInstance().getFlightId() + "/" + mediaFile.getFileName(),
|
|
||||||
// file
|
|
||||||
).withProgressListener(new ProgressListener() {
|
).withProgressListener(new ProgressListener() {
|
||||||
@Override
|
@Override
|
||||||
public void progressChanged(ProgressEvent progressEvent) {
|
public void progressChanged(ProgressEvent progressEvent) {
|
||||||
|
|
@ -572,7 +641,6 @@ public class MediaManager extends BaseManager {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取文件上传后访问地址url
|
|
||||||
GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(
|
GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(
|
||||||
PreferenceUtils.getInstance().getBucketName(),
|
PreferenceUtils.getInstance().getBucketName(),
|
||||||
"/" + PreferenceUtils.getInstance().getObjectKey() + "/"
|
"/" + PreferenceUtils.getInstance().getObjectKey() + "/"
|
||||||
|
|
@ -580,7 +648,6 @@ public class MediaManager extends BaseManager {
|
||||||
);
|
);
|
||||||
String url = s3.generatePresignedUrl(urlRequest).toString();
|
String url = s3.generatePresignedUrl(urlRequest).toString();
|
||||||
|
|
||||||
// 文件上传后访问地址url
|
|
||||||
emitter.onNext(url);
|
emitter.onNext(url);
|
||||||
emitter.onComplete();
|
emitter.onComplete();
|
||||||
}
|
}
|
||||||
|
|
@ -590,28 +657,23 @@ public class MediaManager extends BaseManager {
|
||||||
.subscribe(new Observer<String>() {
|
.subscribe(new Observer<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Disposable d) {
|
public void onSubscribe(Disposable d) {
|
||||||
// Handle on subscribe (optional)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(String url) {
|
public void onNext(String url) {
|
||||||
// 上传成功,重置下载重试计数
|
|
||||||
downloadFailTimes = 0;
|
downloadFailTimes = 0;
|
||||||
//上传完成发送事件
|
|
||||||
sendMediaUpload2Server(mediaFile.getFileName(), downLoadMediaFileIndex + 1, mediaFiles.size());
|
sendMediaUpload2Server(mediaFile.getFileName(), downLoadMediaFileIndex + 1, mediaFiles.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable e) {
|
public void onError(Throwable e) {
|
||||||
// 每上传失败一张就清除缓存
|
|
||||||
uploadedFileNames.add(mediaFile.getFileName());
|
uploadedFileNames.add(mediaFile.getFileName());
|
||||||
FileUtil.deleteFile(file);
|
FileUtil.deleteFile(file);
|
||||||
LogUtil.log(TAG, "Error uploading file " + downLoadMediaFileIndex + ": " + e.getMessage());
|
LogUtil.log(TAG, "Error uploading file " + downLoadMediaFileIndex + ": " + e.getMessage());
|
||||||
|
|
||||||
downLoadMediaFileIndex++;
|
downLoadMediaFileIndex++;
|
||||||
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
||||||
// 所有文件已经下载完成或失败,清空SD卡,缓存,退出媒体模式,发送无人机关机
|
|
||||||
downLoadMediaFileIndex = 0;
|
downLoadMediaFileIndex = 0;
|
||||||
removeAllFiles();
|
removeAllFiles();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -622,7 +684,6 @@ public class MediaManager extends BaseManager {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() {
|
||||||
// 每上传一张就清除缓存,并记录已上传文件名
|
|
||||||
uploadedFileNames.add(mediaFile.getFileName());
|
uploadedFileNames.add(mediaFile.getFileName());
|
||||||
FileUtil.deleteFile(file);
|
FileUtil.deleteFile(file);
|
||||||
LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " uploaded successfully.");
|
LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " uploaded successfully.");
|
||||||
|
|
@ -630,7 +691,6 @@ public class MediaManager extends BaseManager {
|
||||||
|
|
||||||
downLoadMediaFileIndex++;
|
downLoadMediaFileIndex++;
|
||||||
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
||||||
// 所有文件已上传完成,清空SD卡,缓存,退出媒体模式,发送无人机关机
|
|
||||||
sendEvent2Server("媒体文件已上传完毕", 1);
|
sendEvent2Server("媒体文件已上传完毕", 1);
|
||||||
removeAllFiles();
|
removeAllFiles();
|
||||||
downLoadMediaFileIndex = 0;
|
downLoadMediaFileIndex = 0;
|
||||||
|
|
@ -641,8 +701,11 @@ public class MediaManager extends BaseManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
* 删除文件 / 退出媒体模式
|
||||||
|
* ================================================================= */
|
||||||
|
|
||||||
public void removeAllFiles() {
|
public void removeAllFiles() {
|
||||||
// 确保即使没有文件也能正常关机
|
|
||||||
if (mediaFiles == null || mediaFiles.isEmpty()) {
|
if (mediaFiles == null || mediaFiles.isEmpty()) {
|
||||||
LogUtil.log(TAG, "没有文件需要清除,直接关机");
|
LogUtil.log(TAG, "没有文件需要清除,直接关机");
|
||||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||||
|
|
@ -672,10 +735,7 @@ public class MediaManager extends BaseManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//退出媒体模式
|
|
||||||
public void disablePlayback() {
|
public void disablePlayback() {
|
||||||
// 任务结束,停止视频流刷新定时器
|
|
||||||
// StreamManager.getInstance().stopStreamRefreshTimer();
|
|
||||||
MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() {
|
MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
|
|
@ -689,22 +749,60 @@ public class MediaManager extends BaseManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final long DISABLE_TIMEOUT_MS = 15_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出媒体模式 + 超时保护 + 完成后执行后续。
|
||||||
|
* disable 正常回调回来就执行;如果SDK不回调用,15s后强制执行,防卡死。
|
||||||
|
*/
|
||||||
|
private void disablePlaybackAndThen(Runnable then) {
|
||||||
|
LogUtil.log(TAG, "【disablePlayback】开始退出媒体模式(15s超时兜底)");
|
||||||
|
final AtomicBoolean done = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
Runnable disableTimeout = () -> {
|
||||||
|
if (done.compareAndSet(false, true)) {
|
||||||
|
LogUtil.log(TAG, "【disablePlayback】⏰ 15s超时!SDK disable无响应,强制执行后续");
|
||||||
|
then.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mainHandler.postDelayed(disableTimeout, DISABLE_TIMEOUT_MS);
|
||||||
|
|
||||||
|
MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
if (done.compareAndSet(false, true)) {
|
||||||
|
mainHandler.removeCallbacks(disableTimeout);
|
||||||
|
LogUtil.log(TAG, "【disablePlayback】SDK disable成功 → 执行后续");
|
||||||
|
then.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError idjiError) {
|
||||||
|
if (done.compareAndSet(false, true)) {
|
||||||
|
mainHandler.removeCallbacks(disableTimeout);
|
||||||
|
LogUtil.log(TAG, "【disablePlayback】SDK disable失败: " + idjiError.description() + " → 仍执行后续");
|
||||||
|
then.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
* 工具方法
|
||||||
|
* ================================================================= */
|
||||||
|
|
||||||
private int downLoadMediaFileIndex = 0;
|
private int downLoadMediaFileIndex = 0;
|
||||||
|
|
||||||
private String getSDCardPath() {
|
private String getSDCardPath() {
|
||||||
if (checkSDCard()) {
|
if (checkSDCard()) {
|
||||||
return Environment.getExternalStorageDirectory()
|
return Environment.getExternalStorageDirectory().getPath();
|
||||||
.getPath();
|
|
||||||
} else {
|
} else {
|
||||||
return Environment.getExternalStorageDirectory()
|
return Environment.getExternalStorageDirectory().getParentFile().getPath();
|
||||||
.getParentFile().getPath();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean checkSDCard() {
|
private boolean checkSDCard() {
|
||||||
return TextUtils.equals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
|
return TextUtils.equals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ import com.aros.apron.constant.ErrorCode;
|
||||||
import com.aros.apron.entity.CurrentWayline;
|
import com.aros.apron.entity.CurrentWayline;
|
||||||
import com.aros.apron.entity.MessageDown;
|
import com.aros.apron.entity.MessageDown;
|
||||||
import com.aros.apron.entity.Movement;
|
import com.aros.apron.entity.Movement;
|
||||||
|
import com.aros.apron.entity.ParsedWaypoint;
|
||||||
import com.aros.apron.entity.Synchronizedstatus;
|
import com.aros.apron.entity.Synchronizedstatus;
|
||||||
import com.aros.apron.tools.LogUtil;
|
import com.aros.apron.tools.LogUtil;
|
||||||
import com.aros.apron.tools.PreferenceUtils;
|
import com.aros.apron.tools.PreferenceUtils;
|
||||||
import com.aros.apron.tools.RestartAPPTool;
|
import com.aros.apron.tools.RestartAPPTool;
|
||||||
import com.aros.apron.tools.TakeoffProgressScheduler;
|
import com.aros.apron.tools.TakeoffProgressScheduler;
|
||||||
|
import com.aros.apron.tools.Utils;
|
||||||
import com.dji.wpmzsdk.common.data.KMZInfo;
|
import com.dji.wpmzsdk.common.data.KMZInfo;
|
||||||
import com.dji.wpmzsdk.manager.WPMZManager;
|
import com.dji.wpmzsdk.manager.WPMZManager;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
@ -33,6 +35,7 @@ import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import dji.sdk.keyvalue.key.CameraKey;
|
import dji.sdk.keyvalue.key.CameraKey;
|
||||||
|
|
@ -821,6 +824,13 @@ public class MissionV3Manager extends BaseManager {
|
||||||
if (kmzInfo != null) {
|
if (kmzInfo != null) {
|
||||||
// Utils.printJson(TAG,"航点详情:"+new Gson().toJson(kmzInfo));
|
// Utils.printJson(TAG,"航点详情:"+new Gson().toJson(kmzInfo));
|
||||||
WaylineWaylinesParseInfo waylineWaylinesParseInfo = kmzInfo.getWaylineWaylinesParseInfo();
|
WaylineWaylinesParseInfo waylineWaylinesParseInfo = kmzInfo.getWaylineWaylinesParseInfo();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//LogUtil.log(TAG,"航点详情:"+new Gson().toJson(kmzInfo));
|
||||||
|
//LogUtil.log(TAG,"航点详情:"+waylineWaylinesParseInfo.getWaylines());
|
||||||
|
|
||||||
|
//解析航点
|
||||||
if (waylineWaylinesParseInfo != null) {
|
if (waylineWaylinesParseInfo != null) {
|
||||||
List<Wayline> waylines = waylineWaylinesParseInfo.getWaylines();
|
List<Wayline> waylines = waylineWaylinesParseInfo.getWaylines();
|
||||||
if (waylines != null && waylines.size() > 0) {
|
if (waylines != null && waylines.size() > 0) {
|
||||||
|
|
@ -828,6 +838,95 @@ public class MissionV3Manager extends BaseManager {
|
||||||
if (waypoints != null && waypoints.size() > 0) {
|
if (waypoints != null && waypoints.size() > 0) {
|
||||||
CurrentWayline.getInstance().setWaypoints(waypoints);
|
CurrentWayline.getInstance().setWaypoints(waypoints);
|
||||||
LogUtil.log(TAG, "该航线有" + waypoints.size() + "个航点");
|
LogUtil.log(TAG, "该航线有" + waypoints.size() + "个航点");
|
||||||
|
|
||||||
|
|
||||||
|
// 打印每个航点的详细信息
|
||||||
|
// 构建解析后的航点列表
|
||||||
|
// 先从航线级 actionGroups 提取每个航点的 hoverTime(startIndex→endIndex)
|
||||||
|
java.util.Map<Integer, Integer> hoverTimeMap = new java.util.HashMap<>();
|
||||||
|
try {
|
||||||
|
String waylineJson = new Gson().toJson(waylines.get(0));
|
||||||
|
com.google.gson.JsonObject waylineObj = new Gson().fromJson(waylineJson, com.google.gson.JsonObject.class);
|
||||||
|
com.google.gson.JsonArray actionGroups = waylineObj.getAsJsonArray("actionGroups");
|
||||||
|
if (actionGroups != null) {
|
||||||
|
for (int g = 0; g < actionGroups.size(); g++) {
|
||||||
|
com.google.gson.JsonObject group = actionGroups.get(g).getAsJsonObject();
|
||||||
|
int startIdx = group.has("startIndex") ? group.get("startIndex").getAsInt() : 0;
|
||||||
|
int endIdx = group.has("endIndex") ? group.get("endIndex").getAsInt() : startIdx;
|
||||||
|
com.google.gson.JsonArray actions = group.getAsJsonArray("actions");
|
||||||
|
if (actions != null) {
|
||||||
|
int totalHover = 0;
|
||||||
|
for (int a = 0; a < actions.size(); a++) {
|
||||||
|
com.google.gson.JsonObject action = actions.get(a).getAsJsonObject();
|
||||||
|
if (action.has("aircraftHoverParam")) {
|
||||||
|
com.google.gson.JsonObject hoverParam = action.getAsJsonObject("aircraftHoverParam");
|
||||||
|
if (hoverParam != null && hoverParam.has("hoverTime")) {
|
||||||
|
totalHover += (int) Math.round(hoverParam.get("hoverTime").getAsDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int idx = startIdx; idx <= endIdx && idx < waypoints.size(); idx++) {
|
||||||
|
hoverTimeMap.put(idx, totalHover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.log(TAG, "航线级actionGroups解析跳过: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ParsedWaypoint> parsedList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < waypoints.size(); i++) {
|
||||||
|
WaylineExecuteWaypoint wp = waypoints.get(i);
|
||||||
|
// 先打印完整 JSON,方便查看所有字段
|
||||||
|
String wpJson = new Gson().toJson(wp);
|
||||||
|
// LogUtil.log(TAG, "航点[" + i + "]完整JSON: " + wpJson);
|
||||||
|
|
||||||
|
// 从 JSON 提取各字段(WaylineExecuteWaypoint 无直接 getter)
|
||||||
|
double lat = 0, lng = 0, alt = 0, speed = 0;
|
||||||
|
int hoverTime = hoverTimeMap.getOrDefault(i, 0);
|
||||||
|
try {
|
||||||
|
com.google.gson.JsonObject jsonObj = new Gson().fromJson(wpJson, com.google.gson.JsonObject.class);
|
||||||
|
// 经纬度:优先顶层latitude → waylineWaypoint.latitude → location.latitude
|
||||||
|
if (jsonObj.has("latitude") && !jsonObj.get("latitude").isJsonNull()) {
|
||||||
|
lat = jsonObj.get("latitude").getAsDouble();
|
||||||
|
} else if (jsonObj.has("waylineWaypoint") && jsonObj.getAsJsonObject("waylineWaypoint").has("latitude")) {
|
||||||
|
lat = jsonObj.getAsJsonObject("waylineWaypoint").get("latitude").getAsDouble();
|
||||||
|
} else if (jsonObj.has("location") && jsonObj.getAsJsonObject("location").has("latitude")) {
|
||||||
|
lat = jsonObj.getAsJsonObject("location").get("latitude").getAsDouble();
|
||||||
|
}
|
||||||
|
if (jsonObj.has("longitude") && !jsonObj.get("longitude").isJsonNull()) {
|
||||||
|
lng = jsonObj.get("longitude").getAsDouble();
|
||||||
|
} else if (jsonObj.has("waylineWaypoint") && jsonObj.getAsJsonObject("waylineWaypoint").has("longitude")) {
|
||||||
|
lng = jsonObj.getAsJsonObject("waylineWaypoint").get("longitude").getAsDouble();
|
||||||
|
} else if (jsonObj.has("location") && jsonObj.getAsJsonObject("location").has("longitude")) {
|
||||||
|
lng = jsonObj.getAsJsonObject("location").get("longitude").getAsDouble();
|
||||||
|
}
|
||||||
|
// 高度:优先altitude → waylineWaypoint.altitude → executeHeight
|
||||||
|
if (jsonObj.has("altitude") && !jsonObj.get("altitude").isJsonNull()) {
|
||||||
|
alt = jsonObj.get("altitude").getAsDouble();
|
||||||
|
} else if (jsonObj.has("waylineWaypoint") && jsonObj.getAsJsonObject("waylineWaypoint").has("altitude")) {
|
||||||
|
alt = jsonObj.getAsJsonObject("waylineWaypoint").get("altitude").getAsDouble();
|
||||||
|
} else if (jsonObj.has("executeHeight") && !jsonObj.get("executeHeight").isJsonNull()) {
|
||||||
|
alt = jsonObj.get("executeHeight").getAsDouble();
|
||||||
|
}
|
||||||
|
if (jsonObj.has("speed") && !jsonObj.get("speed").isJsonNull()) {
|
||||||
|
speed = jsonObj.get("speed").getAsDouble();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.log(TAG, "航点[" + i + "]部分字段解析跳过: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedWaypoint pw = new ParsedWaypoint(i, lat, lng, alt, hoverTime, speed);
|
||||||
|
parsedList.add(pw);
|
||||||
|
|
||||||
|
// LogUtil.log(TAG, "航点[" + i + "] " + pw.toString());
|
||||||
|
}
|
||||||
|
// 保存解析后的航点列表
|
||||||
|
CurrentWayline.getInstance().setParsedWaypoints(parsedList);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
LogUtil.log(TAG, "WPMZManager getWaypointInfo有误");
|
LogUtil.log(TAG, "WPMZManager getWaypointInfo有误");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package com.aros.apron.manager;
|
package com.aros.apron.manager;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
|
@ -16,8 +14,7 @@ import com.aros.apron.tools.PreferenceUtils;
|
||||||
import com.aros.apron.tools.SimplePortScanner;
|
import com.aros.apron.tools.SimplePortScanner;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import dji.sdk.keyvalue.key.CameraKey;
|
import dji.sdk.keyvalue.key.CameraKey;
|
||||||
import dji.sdk.keyvalue.key.DJIKey;
|
import dji.sdk.keyvalue.key.DJIKey;
|
||||||
|
|
@ -44,8 +41,6 @@ import dji.v5.manager.interfaces.ILiveStreamManager;
|
||||||
public class StreamManager extends BaseManager {
|
public class StreamManager extends BaseManager {
|
||||||
private static final String TAG = "StreamManager";
|
private static final String TAG = "StreamManager";
|
||||||
|
|
||||||
// ========== 【新增】线程池和主线程 Handler,防止 ANR ==========
|
|
||||||
private final ExecutorService streamExecutor = Executors.newSingleThreadExecutor();
|
|
||||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
// ========== 5秒定时刷新视频流,防止起飞卡死 ==========
|
// ========== 5秒定时刷新视频流,防止起飞卡死 ==========
|
||||||
|
|
@ -110,48 +105,66 @@ public class StreamManager extends BaseManager {
|
||||||
return StreamHolder.INSTANCE;
|
return StreamHolder.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void restart(MessageDown message) {
|
||||||
|
sendMsg2Server(message);
|
||||||
|
LogUtil.log(TAG, "重启推流:先停后启");
|
||||||
|
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||||
|
if (mgr == null) return;
|
||||||
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "旧流已停,重新启动");
|
||||||
|
resetStreamState();
|
||||||
|
startLiveWithRTSP();
|
||||||
|
}
|
||||||
|
@Override public void onFailure(@NonNull IDJIError e) {
|
||||||
|
LogUtil.log(TAG, "停流失败,直接重启");
|
||||||
|
resetStreamState();
|
||||||
|
startLiveWithRTSP();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========== 【新增】重置推流状态,用于端口关闭后重启 ==========
|
// ========== 【新增】重置推流状态,用于端口关闭后重启 ==========
|
||||||
public void resetStreamState() {
|
public void resetStreamState() {
|
||||||
//stopStreamRefreshTimer();
|
//stopStreamRefreshTimer();
|
||||||
// SimplePortScanner.getInstance().stopScan(); // 同时停止端口扫描
|
// SimplePortScanner.getInstance().stopScan(); // 同时停止端口扫描
|
||||||
mainHandler.removeCallbacksAndMessages(null); // 清理所有待执行的回调
|
mainHandler.removeCallbacksAndMessages(null); // 清理所有待执行的回调
|
||||||
startLiveFailTimes = 0;
|
startLiveFailTimes = 0;
|
||||||
isLiveStreamAlreadyStart = false;
|
isStartingRTSP.set(false);
|
||||||
isStartingRTSP = false;
|
|
||||||
LogUtil.log(TAG, "推流状态已重置");
|
LogUtil.log(TAG, "推流状态已重置");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopstream() {
|
public void stopstream() {
|
||||||
streamExecutor.execute(() -> {
|
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
liveStreamManager.stopStream(new CommonCallbacks.CompletionCallback() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
LogUtil.log(TAG, "直播关闭成功");
|
LogUtil.log(TAG, "直播关闭成功,重置状态");
|
||||||
|
isStartingRTSP.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull IDJIError idjiError) {
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
LogUtil.log(TAG, "直播关闭失败");
|
LogUtil.log(TAG, "直播关闭失败:" + e.description());
|
||||||
|
isStartingRTSP.set(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startstream() {
|
public void startstream() {
|
||||||
streamExecutor.execute(() -> {
|
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
mgr.startStream(new CommonCallbacks.CompletionCallback() {
|
||||||
liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() { LogUtil.log(TAG, "直播开启成功"); }
|
||||||
LogUtil.log(TAG, "直播开启成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull IDJIError idjiError) {
|
public void onFailure(@NonNull IDJIError e) { LogUtil.log(TAG, "直播开启失败"); }
|
||||||
LogUtil.log(TAG, "直播开启成功失败");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,8 +177,8 @@ public class StreamManager extends BaseManager {
|
||||||
public void onLiveStreamStatusUpdate(LiveStreamStatus status) {
|
public void onLiveStreamStatusUpdate(LiveStreamStatus status) {
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
Movement.getInstance().setLiveStatus(status.isStreaming() ? 1 : 0);
|
Movement.getInstance().setLiveStatus(status.isStreaming() ? 1 : 0);
|
||||||
Log.d(TAG, "推流状态" + status.isStreaming() + "帧率:" + status.getFps() + "--" + "码率:" + status.getVbps() + "---" + "延迟:" + status.getRtt());
|
LogUtil.log(TAG, "推流状态" + status.isStreaming() + "帧率:" + status.getFps() + "--" + "码率:" + status.getVbps() + "---" + "延迟:" + status.getRtt());
|
||||||
|
sendEvent2Server("推流状态" + status.isStreaming() + "帧率:" + status.getFps() + "--" + "码率:" + status.getVbps() + "---" + "延迟:" + status.getRtt(),1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,8 +217,9 @@ public class StreamManager extends BaseManager {
|
||||||
|
|
||||||
|
|
||||||
private volatile int startLiveFailTimes;
|
private volatile int startLiveFailTimes;
|
||||||
private volatile boolean isLiveStreamAlreadyStart;
|
// ★ 用 AtomicBoolean 替代 volatile boolean,CAS 防并发,不会卡死
|
||||||
private volatile boolean isStartingRTSP = false; // 防止并发调用
|
private final AtomicBoolean isStartingRTSP = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean isStartingRTMP = new AtomicBoolean(false);
|
||||||
|
|
||||||
// 无限重试:指数退避控制
|
// 无限重试:指数退避控制
|
||||||
private static final long RETRY_BASE_MS = 3000; // 初始 3 秒
|
private static final long RETRY_BASE_MS = 3000; // 初始 3 秒
|
||||||
|
|
@ -214,7 +228,6 @@ public class StreamManager extends BaseManager {
|
||||||
|
|
||||||
// 知眸测试
|
// 知眸测试
|
||||||
public void startLiveWithCustom() {
|
public void startLiveWithCustom() {
|
||||||
streamExecutor.execute(() -> {
|
|
||||||
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(ProductKey.KeyConnection));
|
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(ProductKey.KeyConnection));
|
||||||
if (isAircraftConnected == null || !isAircraftConnected) {
|
if (isAircraftConnected == null || !isAircraftConnected) {
|
||||||
LogUtil.log(TAG, "飞行器未连接");
|
LogUtil.log(TAG, "飞行器未连接");
|
||||||
|
|
@ -236,9 +249,8 @@ public class StreamManager extends BaseManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!liveStreamManager.isStreaming()) {
|
if (!liveStreamManager.isStreaming()) {
|
||||||
doStartLiveCustom(liveStreamManager);
|
mainHandler.postDelayed(() -> doStartLiveCustom(liveStreamManager), 3000);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doStartLiveCustom(ILiveStreamManager liveStreamManager) {
|
private void doStartLiveCustom(ILiveStreamManager liveStreamManager) {
|
||||||
|
|
@ -247,7 +259,6 @@ public class StreamManager extends BaseManager {
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
mainHandler.post(() -> {
|
mainHandler.post(() -> {
|
||||||
LogUtil.log(TAG, "自定义推流启动成功");
|
LogUtil.log(TAG, "自定义推流启动成功");
|
||||||
isLiveStreamAlreadyStart = true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,16 +266,16 @@ public class StreamManager extends BaseManager {
|
||||||
public void onFailure(@NonNull IDJIError error) {
|
public void onFailure(@NonNull IDJIError error) {
|
||||||
mainHandler.post(() -> {
|
mainHandler.post(() -> {
|
||||||
LogUtil.log(TAG, "第" + startLiveFailTimes + "次自定义推流失败:" + new Gson().toJson(error));
|
LogUtil.log(TAG, "第" + startLiveFailTimes + "次自定义推流失败:" + new Gson().toJson(error));
|
||||||
if (!isLiveStreamAlreadyStart) {
|
// ★ 用 SDK 真实状态判断,不用手动标志位
|
||||||
|
if (!liveStreamManager.isStreaming()) {
|
||||||
long retryDelay = Math.min(RETRY_BASE_MS * (1L << Math.min(startLiveFailTimes, 4)), RETRY_MAX_MS);
|
long retryDelay = Math.min(RETRY_BASE_MS * (1L << Math.min(startLiveFailTimes, 4)), RETRY_MAX_MS);
|
||||||
startLiveFailTimes++;
|
startLiveFailTimes++;
|
||||||
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
||||||
LogUtil.log(TAG, "自定义推流准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
|
LogUtil.log(TAG, "自定义推流准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
|
||||||
}
|
}
|
||||||
isStartingRTSP = false;
|
isStartingRTSP.set(false);
|
||||||
isLiveStreamAlreadyStart = false;
|
|
||||||
mainHandler.postDelayed(() -> {
|
mainHandler.postDelayed(() -> {
|
||||||
streamExecutor.execute(() -> startLiveWithCustom());
|
startLiveWithCustom();
|
||||||
}, retryDelay);
|
}, retryDelay);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -273,240 +284,370 @@ public class StreamManager extends BaseManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private int isliveindex = 1; //1 代表 port 2 代表 fpv
|
// ========== RTMP 推流(MQTT rtmp_push 协议) ==========
|
||||||
|
|
||||||
public void switchptspfpv(ComponentIndexType ComponentIndex, MessageDown message) {
|
/**
|
||||||
if(isliveindex==2){
|
* MQTT 收到 rtmp_push 协议后调用:先停止当前推流,再启动 RTMP 推流
|
||||||
sendMsg2Server(message);
|
* @param message MQTT 下发的消息,data.rtmp_url 为 RTMP 推流地址
|
||||||
|
*/
|
||||||
|
public void startLiveWithRTMP(MessageDown message) {
|
||||||
|
|
||||||
|
// CAS 防并发
|
||||||
|
if (!isStartingRTMP.compareAndSet(false, true)) {
|
||||||
|
LogUtil.log(TAG, "startLiveWithRTMP 正在执行中,忽略本次调用");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isliveindex = 2;
|
|
||||||
|
String rtmpUrl = message.getData() != null ? message.getData().getRtmp_url() : null;
|
||||||
|
if (rtmpUrl == null || rtmpUrl.isEmpty()) {
|
||||||
|
LogUtil.log(TAG, "RTMP URL 为空");
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
sendFailMsg2Server(message, "RTMP URL 为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtil.log(TAG, "========== 开始 RTMP 推流流程 ==========");
|
||||||
|
LogUtil.log(TAG, "RTMP URL: " + rtmpUrl);
|
||||||
|
|
||||||
|
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection));
|
||||||
|
if (isAircraftConnected == null || !isAircraftConnected) {
|
||||||
|
LogUtil.log(TAG, "飞行器未连接");
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
sendFailMsg2Server(message, "飞行器未连接");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||||
|
if (mgr == null) {
|
||||||
|
LogUtil.log(TAG, "LiveStreamManager 为 null");
|
||||||
|
sendFailMsg2Server(message, "LiveStreamManager 为 null");
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ 杀掉所有 RTSP 排队中的回调,重置 RTSP 状态,防止 RTSP 延迟任务覆盖 RTMP 配置
|
||||||
|
resetStreamState();
|
||||||
|
|
||||||
|
// 先发送回执,表示收到指令
|
||||||
sendMsg2Server(message);
|
sendMsg2Server(message);
|
||||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
|
||||||
LogUtil.log(TAG, "切换 RTSP 推流 fpv:" + PreferenceUtils.getInstance().getRtspUserName()
|
|
||||||
+ "--" + PreferenceUtils.getInstance().getRtspPort() + "--" + PreferenceUtils.getInstance().getRtspPassWord());
|
|
||||||
|
|
||||||
LiveStreamSettings.Builder streamSettingBuilder = new LiveStreamSettings.Builder();
|
// 先停止当前推流,成功或失败都继续启动 RTMP
|
||||||
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "旧流已停止,开始配置 RTMP");
|
||||||
|
configureAndStartRTMP(mgr, rtmpUrl, message);
|
||||||
|
}
|
||||||
|
|
||||||
LiveStreamSettings streamSettings = streamSettingBuilder.setLiveStreamType(LiveStreamType.RTSP)
|
@Override
|
||||||
.setRtspSettings(new RtspSettings.Builder().setPassWord(PreferenceUtils.getInstance().getRtspPassWord()).
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
setPort(Integer.parseInt(PreferenceUtils.getInstance().getRtspPort())).
|
LogUtil.log(TAG, "停流失败,仍尝试启动 RTMP: " + e.description());
|
||||||
setUserName(PreferenceUtils.getInstance().getRtspUserName()).build()).build();
|
configureAndStartRTMP(mgr, rtmpUrl, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
liveStreamManager.setLiveStreamSettings(streamSettings);
|
private void configureAndStartRTMP(ILiveStreamManager mgr, String rtmpUrl, MessageDown message) {
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
LiveStreamSettings streamSettings = new LiveStreamSettings.Builder()
|
||||||
|
.setLiveStreamType(LiveStreamType.RTMP)
|
||||||
|
.setRtmpSettings(new RtmpSettings.Builder()
|
||||||
|
.setUrl(rtmpUrl)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
mgr.setLiveStreamSettings(streamSettings);
|
||||||
|
mgr.setCameraIndex((isliveindex == 1) ? ComponentIndexType.PORT_1 : ComponentIndexType.FPV);
|
||||||
|
|
||||||
liveStreamManager.setCameraIndex(ComponentIndex);
|
mgr.setLiveStreamQuality(StreamQuality.FULL_HD);
|
||||||
|
mgr.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
||||||
|
|
||||||
liveStreamManager.setLiveStreamQuality(StreamQuality.FULL_HD);
|
LogUtil.log(TAG, "RTMP 配置完成,3s 后启动推流");
|
||||||
liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
if (mgr.isStreaming()) {
|
||||||
|
// ★ 旧流未停干净:主动再停一次,然后重试启动
|
||||||
|
LogUtil.log(TAG, "SDK显示仍在推流,再次停止后重试");
|
||||||
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "二次停流成功,重试 RTMP 启动");
|
||||||
|
mainHandler.postDelayed(() -> doStartRTMPStream(mgr, message), 2000);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
|
LogUtil.log(TAG, "二次停流失败,仍尝试 RTMP 启动: " + e.description());
|
||||||
|
mainHandler.postDelayed(() -> doStartRTMPStream(mgr, message), 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doStartRTMPStream(mgr, message);
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doStartRTMPStream(ILiveStreamManager mgr, MessageDown message) {
|
||||||
|
if (mgr.isStreaming()) {
|
||||||
|
// ★ 二次停流后仍显示推流中:再做最后一次 stop + retry,不轻易放弃
|
||||||
|
LogUtil.log(TAG, "RTMP 启动前检查:SDK仍在推流,最后一次强制停流");
|
||||||
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "三次停流成功,最终尝试 RTMP 启动");
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
mgr.startStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "RTMP 推流启动成功(终极重试)");
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
LogUtil.log(TAG, "========== RTMP 推流启动成功 ==========");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError error) {
|
||||||
|
LogUtil.log(TAG, "RTMP 推流最终失败: " + error.description());
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
sendFailMsg2Server(message, "RTMP 推流最终失败: " + error.description());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
|
LogUtil.log(TAG, "三次停流失败,彻底放弃");
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
sendFailMsg2Server(message, "RTMP 推流失败:多次停流无效");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtil.log(TAG, "开始调用 RTMP startStream...");
|
||||||
|
mgr.startStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "RTMP 推流启动成功");
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
LogUtil.log(TAG, "========== RTMP 推流启动成功 ==========");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError error) {
|
||||||
|
LogUtil.log(TAG, "RTMP 推流启动失败: " + error.description());
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
sendFailMsg2Server(message, "RTMP 推流失败: " + error.description());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 停止 RTMP 推流(MQTT stop_rtmp 协议) ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MQTT 收到 stop_rtmp 协议后调用:停止 RTMP 推流,切回默认 RTSP
|
||||||
|
* @param message MQTT 下发的消息
|
||||||
|
*/
|
||||||
|
public void stopRTMP(MessageDown message) {
|
||||||
|
sendMsg2Server(message);
|
||||||
|
LogUtil.log(TAG, "========== 停止 RTMP 推流,切回 RTSP ==========");
|
||||||
|
|
||||||
|
// ★ 立即释放 RTMP 锁 + 清场,不阻塞快速连发的 push_rtmp
|
||||||
|
isStartingRTMP.set(false);
|
||||||
|
resetStreamState();
|
||||||
|
|
||||||
|
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||||
|
if (mgr == null) {
|
||||||
|
LogUtil.log(TAG, "LiveStreamManager 为 null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "RTMP 已停,启动 RTSP");
|
||||||
|
startLiveWithRTSP();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
|
LogUtil.log(TAG, "停流失败,仍启动 RTSP: " + e.description());
|
||||||
|
startLiveWithRTSP();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile int isliveindex = 1; //1 代表 port 2 代表 fpv
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换 RTSP 推流相机源(和 DJI 示例一样:直接设 cameraIndex,SDK 内部切换)
|
||||||
|
*/
|
||||||
|
public void switchptspfpv(ComponentIndexType ComponentIndex, MessageDown message) {
|
||||||
|
if (isliveindex == 2) { sendMsg2Server(message); return; }
|
||||||
|
isliveindex = 2;
|
||||||
|
LogUtil.log(TAG, "切换推流到 FPV");
|
||||||
|
MediaDataCenter.getInstance().getLiveStreamManager().setCameraIndex(ComponentIndexType.FPV);
|
||||||
|
sendMsg2Server(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchptspport(ComponentIndexType ComponentIndex, MessageDown message) {
|
public void switchptspport(ComponentIndexType ComponentIndex, MessageDown message) {
|
||||||
if(isliveindex==1){
|
if (isliveindex == 1) { sendMsg2Server(message); return; }
|
||||||
sendMsg2Server(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isliveindex = 1;
|
isliveindex = 1;
|
||||||
|
LogUtil.log(TAG, "切换推流到 PORT_1");
|
||||||
|
MediaDataCenter.getInstance().getLiveStreamManager().setCameraIndex(ComponentIndexType.PORT_1);
|
||||||
sendMsg2Server(message);
|
sendMsg2Server(message);
|
||||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
|
||||||
LogUtil.log(TAG, "切换 RTSP 推流 port:" + PreferenceUtils.getInstance().getRtspUserName()
|
|
||||||
+ "--" + PreferenceUtils.getInstance().getRtspPort() + "--" + PreferenceUtils.getInstance().getRtspPassWord());
|
|
||||||
|
|
||||||
LiveStreamSettings.Builder streamSettingBuilder = new LiveStreamSettings.Builder();
|
|
||||||
|
|
||||||
LiveStreamSettings streamSettings = streamSettingBuilder.setLiveStreamType(LiveStreamType.RTSP)
|
|
||||||
.setRtspSettings(new RtspSettings.Builder().setPassWord(PreferenceUtils.getInstance().getRtspPassWord()).
|
|
||||||
setPort(Integer.parseInt(PreferenceUtils.getInstance().getRtspPort())).
|
|
||||||
setUserName(PreferenceUtils.getInstance().getRtspUserName()).build()).build();
|
|
||||||
|
|
||||||
liveStreamManager.setLiveStreamSettings(streamSettings);
|
|
||||||
|
|
||||||
liveStreamManager.setCameraIndex(ComponentIndex);
|
|
||||||
|
|
||||||
liveStreamManager.setLiveStreamQuality(StreamQuality.FULL_HD);
|
|
||||||
liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 【核心修复】RTSP 推流入口,全部在子线程执行 ==========
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void startLiveWithRTSP() {
|
public void startLiveWithRTSP() {
|
||||||
// 防止并发调用
|
// ★ CAS 防并发:只有一个线程能拿到 true,其他被拦截
|
||||||
if (isStartingRTSP) {
|
if (!isStartingRTSP.compareAndSet(false, true)) {
|
||||||
LogUtil.log(TAG, "startLiveWithRTSP 正在执行中,忽略本次调用");
|
LogUtil.log(TAG, "startLiveWithRTSP 正在执行中,忽略本次调用");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
streamExecutor.execute(() -> {
|
// ★ RTMP 推流正在进行中,不再启动 RTSP,防止覆盖
|
||||||
isStartingRTSP = true;
|
if (isStartingRTMP.get()) {
|
||||||
|
LogUtil.log(TAG, "RTMP 推流正在进行中,取消 RTSP 启动");
|
||||||
|
isStartingRTSP.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (startLiveFailTimes == 0) {
|
if (startLiveFailTimes == 0) {
|
||||||
isLiveStreamAlreadyStart = false;
|
|
||||||
LogUtil.log(TAG, "========== 开始 RTSP 推流流程 ==========");
|
LogUtil.log(TAG, "========== 开始 RTSP 推流流程 ==========");
|
||||||
}
|
}
|
||||||
|
|
||||||
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection));
|
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection));
|
||||||
if (isAircraftConnected == null || !isAircraftConnected) {
|
if (isAircraftConnected == null || !isAircraftConnected) {
|
||||||
LogUtil.log(TAG, "飞行器未连接");
|
LogUtil.log(TAG, "飞行器未连接");
|
||||||
isStartingRTSP = false;
|
isStartingRTSP.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||||
if (liveStreamManager == null) {
|
if (mgr == null) {
|
||||||
LogUtil.log(TAG, "LiveStreamManager 为 null");
|
LogUtil.log(TAG, "LiveStreamManager 为 null");
|
||||||
isStartingRTSP = false;
|
isStartingRTSP.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (liveStreamManager.isStreaming()) {
|
// ★ 用 SDK 真实状态判断是否已在推流
|
||||||
LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动");
|
if (mgr.isStreaming()) {
|
||||||
isLiveStreamAlreadyStart = true;
|
LogUtil.log(TAG, "RTSP 推流已在运行(SDK状态),无需重复启动");
|
||||||
isStartingRTSP = false;
|
isStartingRTSP.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MainActivity.Companion.getStreamReceive()) {
|
if (!MainActivity.Companion.getStreamReceive()) {
|
||||||
LogUtil.log(TAG, "相机流未准备好,尝试模拟点击 FPV Widget 恢复");
|
LogUtil.log(TAG, "相机流未准备好,3s 后重试");
|
||||||
mainHandler.post(() -> {
|
|
||||||
MainActivity mainActivity = MainActivity.Companion.getInstance();
|
|
||||||
if (mainActivity != null) {
|
|
||||||
mainActivity.smartRefreshVideoStream();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mainHandler.postDelayed(() -> {
|
|
||||||
streamExecutor.execute(() -> {
|
|
||||||
startLiveFailTimes++;
|
startLiveFailTimes++;
|
||||||
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
isStartingRTSP.set(false);
|
||||||
LogUtil.log(TAG, "相机流未准备好,第" + startLiveFailTimes + "次重试");
|
// ★ 重试前检查 RTMP 是否已接管
|
||||||
}
|
mainHandler.postDelayed(() -> {
|
||||||
startLiveWithRTSP();
|
if (isStartingRTMP.get()) {
|
||||||
});
|
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 重试(相机流就绪等待)");
|
||||||
}, 2000);
|
return;
|
||||||
isStartingRTSP = false; // 释放锁,让重试能正常进入
|
}
|
||||||
|
startLiveWithRTSP();
|
||||||
|
}, 3000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String rtspUser = PreferenceUtils.getInstance().getRtspUserName();
|
String rtspUser = PreferenceUtils.getInstance().getRtspUserName();
|
||||||
String rtspPort = PreferenceUtils.getInstance().getRtspPort();
|
String rtspPort = PreferenceUtils.getInstance().getRtspPort();
|
||||||
String rtspPass = PreferenceUtils.getInstance().getRtspPassWord();
|
String rtspPass = PreferenceUtils.getInstance().getRtspPassWord();
|
||||||
|
|
||||||
if (rtspUser == null || rtspPort == null || rtspPass == null ||
|
if (rtspUser == null || rtspPort == null || rtspPass == null ||
|
||||||
rtspUser.isEmpty() || rtspPort.isEmpty() || rtspPass.isEmpty()) {
|
rtspUser.isEmpty() || rtspPort.isEmpty() || rtspPass.isEmpty()) {
|
||||||
LogUtil.log(TAG, "RTSP 配置参数有误:user=" + rtspUser + ", port=" + rtspPort);
|
LogUtil.log(TAG, "RTSP 配置参数有误");
|
||||||
isStartingRTSP = false;
|
isStartingRTSP.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogUtil.log(TAG, "RTSP 配置检查通过:user=" + rtspUser + ", port=" + rtspPort + ", camera=" + (isliveindex == 1 ? "PORT_1" : "FPV"));
|
LogUtil.log(TAG, "RTSP 配置检查通过:user=" + rtspUser + ", port=" + rtspPort);
|
||||||
|
|
||||||
//等待相机模式稳定(避免刚切换模式就推流)
|
LiveStreamSettings streamSettings = new LiveStreamSettings.Builder()
|
||||||
try {
|
.setLiveStreamType(LiveStreamType.RTSP)
|
||||||
Thread.sleep(500);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
//设置 RTSP 参数
|
|
||||||
final ILiveStreamManager finalLiveStreamManager = liveStreamManager;
|
|
||||||
mainHandler.post(() -> {
|
|
||||||
LiveStreamSettings.Builder streamSettingBuilder = new LiveStreamSettings.Builder();
|
|
||||||
LiveStreamSettings streamSettings = streamSettingBuilder.setLiveStreamType(LiveStreamType.RTSP)
|
|
||||||
.setRtspSettings(new RtspSettings.Builder()
|
.setRtspSettings(new RtspSettings.Builder()
|
||||||
.setPassWord(rtspPass)
|
.setPassWord(rtspPass)
|
||||||
.setPort(Integer.parseInt(rtspPort))
|
.setPort(Integer.parseInt(rtspPort))
|
||||||
.setUserName(rtspUser)
|
.setUserName(rtspUser)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
mgr.setLiveStreamSettings(streamSettings);
|
||||||
|
mgr.setCameraIndex((isliveindex == 1) ? ComponentIndexType.PORT_1 : ComponentIndexType.FPV);
|
||||||
|
mgr.setLiveStreamQuality(StreamQuality.FULL_HD);
|
||||||
|
mgr.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
||||||
|
|
||||||
finalLiveStreamManager.setLiveStreamSettings(streamSettings);
|
LogUtil.log(TAG, "参数设置完成,3s 后启动推流");
|
||||||
|
|
||||||
// 设置相机源
|
|
||||||
ComponentIndexType cameraIndex = (isliveindex == 1) ? ComponentIndexType.PORT_1 : ComponentIndexType.FPV;
|
|
||||||
finalLiveStreamManager.setCameraIndex(cameraIndex);
|
|
||||||
|
|
||||||
finalLiveStreamManager.setLiveStreamQuality(StreamQuality.FULL_HD);
|
|
||||||
finalLiveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
|
||||||
|
|
||||||
LogUtil.log(TAG, "RTSP 参数设置完成,等待 500ms 后启动推流");
|
|
||||||
|
|
||||||
mainHandler.postDelayed(() -> {
|
mainHandler.postDelayed(() -> {
|
||||||
streamExecutor.execute(() -> {
|
// ★ RTMP 已接管,取消 RTSP 启动
|
||||||
// 9. 启动推流前再次检查
|
if (isStartingRTMP.get()) {
|
||||||
if (finalLiveStreamManager.isStreaming()) {
|
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 启动(3s 等待期间 RTMP 介入)");
|
||||||
LogUtil.log(TAG, "推流已在运行,跳过启动");
|
isStartingRTSP.set(false);
|
||||||
isLiveStreamAlreadyStart = true;
|
return;
|
||||||
isStartingRTSP = false;
|
}
|
||||||
|
// ★ 启动前再次用 SDK 状态确认
|
||||||
|
if (mgr.isStreaming()) {
|
||||||
|
LogUtil.log(TAG, "SDK显示已在推流,跳过");
|
||||||
|
isStartingRTSP.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogUtil.log(TAG, "开始调用 startStream...");
|
LogUtil.log(TAG, "开始调用 startStream...");
|
||||||
doStartLiveWithRTSP(finalLiveStreamManager, false);
|
mgr.startStream(new CommonCallbacks.CompletionCallback() {
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========== 【新增】实际启动流的私有方法,确保在子线程执行 ==========
|
|
||||||
private void doStartLiveWithRTSP(ILiveStreamManager liveStreamManager, boolean isRestart) {
|
|
||||||
if (liveStreamManager.isStreaming()) {
|
|
||||||
LogUtil.log(TAG, "推流已在运行,跳过启动 (isRestart=" + isRestart + ")");
|
|
||||||
isLiveStreamAlreadyStart = true;
|
|
||||||
isStartingRTSP = false; // 重置并发标志
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogUtil.log(TAG, "开始调用 startStream... (isRestart=" + isRestart + ")");
|
|
||||||
|
|
||||||
liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
mainHandler.post(() -> {
|
LogUtil.log(TAG, "RTSP 推流启动成功");
|
||||||
LogUtil.log(TAG, "自定义 RTSP 推流启动成功" + (isRestart ? "(重启)" : ""));
|
startLiveFailTimes = 0;
|
||||||
isliveindex = 1;
|
isStartingRTSP.set(false);
|
||||||
isLiveStreamAlreadyStart = true;
|
|
||||||
startLiveFailTimes = 0; // 重置失败计数
|
|
||||||
isStartingRTSP = false; // 重置并发标志
|
|
||||||
LogUtil.log(TAG, "========== RTSP 推流启动成功 ==========");
|
LogUtil.log(TAG, "========== RTSP 推流启动成功 ==========");
|
||||||
// 启动5秒定时刷新视频流,防止起飞卡死
|
|
||||||
// startStreamRefreshTimer();
|
|
||||||
// 开始端口扫描
|
|
||||||
// SimplePortScanner.getInstance().startScan();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull IDJIError error) {
|
public void onFailure(@NonNull IDJIError error) {
|
||||||
mainHandler.post(() -> {
|
LogUtil.log(TAG, "第" + startLiveFailTimes + "次 RTSP 推流失败:" + error.description());
|
||||||
String detailedError = "第" + startLiveFailTimes + "次开始 RTSP 推流失败:type=" + error.errorType()+ ", code=" + error.errorCode() + ", hint=" + error.hint();
|
|
||||||
LogUtil.log(TAG, detailedError);
|
|
||||||
LogUtil.log(TAG, "完整错误:" + new Gson().toJson(error));
|
|
||||||
|
|
||||||
if (!isLiveStreamAlreadyStart) {
|
// ★ 用 SDK 真实状态判断是否要重试
|
||||||
// 计算指数退避间隔:3s → 6s → 12s → 24s → 30s(封顶)
|
if (!mgr.isStreaming()) {
|
||||||
|
// ★ RTMP 已接管,不再重试 RTSP
|
||||||
|
if (isStartingRTMP.get()) {
|
||||||
|
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 失败重试");
|
||||||
|
isStartingRTSP.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
long retryDelay = Math.min(RETRY_BASE_MS * (1L << Math.min(startLiveFailTimes, 4)), RETRY_MAX_MS);
|
long retryDelay = Math.min(RETRY_BASE_MS * (1L << Math.min(startLiveFailTimes, 4)), RETRY_MAX_MS);
|
||||||
startLiveFailTimes++;
|
startLiveFailTimes++;
|
||||||
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
isStartingRTSP.set(false);
|
||||||
LogUtil.log(TAG, "准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置所有状态,让下次重试是干净的
|
// 停流 → 等回调 → 重试
|
||||||
isStartingRTSP = false;
|
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||||
isLiveStreamAlreadyStart = false;
|
@Override
|
||||||
stopstream(); // 先关再开,避免端口占用
|
public void onSuccess() {
|
||||||
|
LogUtil.log(TAG, "重试前停流成功," + retryDelay + "ms 后重试");
|
||||||
// 指数退避重试
|
// ★ 重试前再次检查 RTMP 状态
|
||||||
mainHandler.postDelayed(() -> {
|
mainHandler.postDelayed(() -> {
|
||||||
|
if (isStartingRTMP.get()) {
|
||||||
|
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 重试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
startLiveWithRTSP();
|
startLiveWithRTSP();
|
||||||
}, retryDelay);
|
}, retryDelay);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull IDJIError e) {
|
||||||
|
LogUtil.log(TAG, "重试前停流失败,仍重试");
|
||||||
|
// ★ 重试前再次检查 RTMP 状态
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
if (isStartingRTMP.get()) {
|
||||||
|
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 重试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startLiveWithRTSP();
|
||||||
|
}, retryDelay);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
isStartingRTSP = false;
|
isStartingRTSP.set(false);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -414,7 +414,7 @@ public class Aprondown {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogUtil.log(TAG_LOG, "执行位移");
|
LogUtil.log(TAG_LOG, "执行位移");
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.5f);
|
||||||
}
|
}
|
||||||
} else if (lostDuration > 8000) {
|
} else if (lostDuration > 8000) {
|
||||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||||
|
|
@ -570,6 +570,30 @@ public class Aprondown {
|
||||||
Apronmixvalue.getInstance().setIsaglinetrue(false); // 下次降落重新对准
|
Apronmixvalue.getInstance().setIsaglinetrue(false); // 下次降落重新对准
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ★ 急停清理 */
|
||||||
|
public void stopAndReset() {
|
||||||
|
isStartAruco = false;
|
||||||
|
startFastStick = false;
|
||||||
|
frameCounter = 0;
|
||||||
|
if (lastFuture != null && !lastFuture.isDone()) {
|
||||||
|
lastFuture.cancel(true);
|
||||||
|
lastFuture = null;
|
||||||
|
}
|
||||||
|
handler.removeCallbacks(runnable);
|
||||||
|
handlerCallbackCount = 0;
|
||||||
|
isHeightStableMonitoring = false;
|
||||||
|
lastHeightCheckTime = 0;
|
||||||
|
lastUltrasonicHeight = 0;
|
||||||
|
arucoNotFoundTag = false;
|
||||||
|
startTime = 0;
|
||||||
|
endTime = 0;
|
||||||
|
dropTimesTag = false;
|
||||||
|
dropTimes = 0;
|
||||||
|
if (pidControlX != null) pidControlX.reset();
|
||||||
|
if (pidControlY != null) pidControlY.reset();
|
||||||
|
LogUtil.log(TAG_LOG, "【急停清理】已重置");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private double calculateYawErrorFromCorners(Point[] pts) {
|
private double calculateYawErrorFromCorners(Point[] pts) {
|
||||||
double dxTop = pts[1].x - pts[0].x;
|
double dxTop = pts[1].x - pts[0].x;
|
||||||
|
|
@ -702,7 +726,7 @@ public class Aprondown {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
performOperation();
|
performOperation();
|
||||||
if (handlerCallbackCount < 15) {
|
if (handlerCallbackCount < 18) {
|
||||||
handler.postDelayed(this, 50);
|
handler.postDelayed(this, 50);
|
||||||
} else {
|
} else {
|
||||||
performNextStep();
|
performNextStep();
|
||||||
|
|
|
||||||
|
|
@ -1035,7 +1035,7 @@ public class Aprongim {
|
||||||
} else {
|
} else {
|
||||||
// 高空丢失:下降寻找
|
// 高空丢失:下降寻找
|
||||||
LogUtil.log(TAG_LOG, "【执行移动】丢失下降 vz=-0.3");
|
LogUtil.log(TAG_LOG, "【执行移动】丢失下降 vz=-0.3");
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.5f);
|
||||||
}
|
}
|
||||||
} else if (lostDuration > 8000) {
|
} else if (lostDuration > 8000) {
|
||||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||||
|
|
@ -1079,13 +1079,43 @@ public class Aprongim {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ★ 急停清理 */
|
||||||
|
public void stopAndReset() {
|
||||||
|
isStartAruco = false;
|
||||||
|
startFastStick = false;
|
||||||
|
frameCounter = 0;
|
||||||
|
if (lastFuture != null && !lastFuture.isDone()) {
|
||||||
|
lastFuture.cancel(true);
|
||||||
|
lastFuture = null;
|
||||||
|
}
|
||||||
|
handler.removeCallbacks(runnable);
|
||||||
|
handlerCallbackCount = 0;
|
||||||
|
isYawAligned = false;
|
||||||
|
currentLandingMode = 0;
|
||||||
|
counterRound = 0;
|
||||||
|
for (int i = 0; i < landingCounters.length; i++) {
|
||||||
|
landingCounters[i] = 0;
|
||||||
|
}
|
||||||
|
isHeightStableMonitoring = false;
|
||||||
|
lastHeightCheckTime = 0;
|
||||||
|
lastUltrasonicHeight = 0;
|
||||||
|
arucoNotFoundTag = false;
|
||||||
|
startTime = 0;
|
||||||
|
endTime = 0;
|
||||||
|
dropTimesTag = false;
|
||||||
|
dropTimes = 0;
|
||||||
|
if (pidControlX != null) pidControlX.reset();
|
||||||
|
if (pidControlY != null) pidControlY.reset();
|
||||||
|
LogUtil.log(TAG_LOG, "【急停清理】已重置");
|
||||||
|
}
|
||||||
|
|
||||||
private int handlerCallbackCount = 0;
|
private int handlerCallbackCount = 0;
|
||||||
private Handler handler = new Handler(Looper.getMainLooper());
|
private Handler handler = new Handler(Looper.getMainLooper());
|
||||||
private Runnable runnable = new Runnable() {
|
private Runnable runnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
performOperation();
|
performOperation();
|
||||||
if (handlerCallbackCount < 15) {
|
if (handlerCallbackCount < 18) {
|
||||||
handler.postDelayed(this, 50);
|
handler.postDelayed(this, 50);
|
||||||
} else {
|
} else {
|
||||||
performNextStep();
|
performNextStep();
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ public class Apronmixvalue {
|
||||||
Aprondown.getInstance().setDropTimes(0);
|
Aprondown.getInstance().setDropTimes(0);
|
||||||
|
|
||||||
// 如果高度已经大于 40 分米,直接切换,不用上升
|
// 如果高度已经大于 40 分米,直接切换,不用上升
|
||||||
if (Movement.getInstance().getUltrasonicHeight() >= 40) {
|
if (Movement.getInstance().getUltrasonicHeight() >= 40 || Movement.getInstance().getElevation()>4) {
|
||||||
Synchronizedstatus.setSwitchtime(true);
|
Synchronizedstatus.setSwitchtime(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +69,7 @@ public class Apronmixvalue {
|
||||||
Runnable runnable = new Runnable() {
|
Runnable runnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (Movement.getInstance().getUltrasonicHeight() < 50) {
|
if (Movement.getInstance().getUltrasonicHeight() < 50 && Movement.getInstance().getElevation() <5) {
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||||
handler.postDelayed(this, 200);
|
handler.postDelayed(this, 200);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -311,7 +311,7 @@ public class ApronArucoDetect {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogUtil.log(TAG_LOG, "执行位移");
|
LogUtil.log(TAG_LOG, "执行位移");
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
isHeightStableMonitoring = false;
|
isHeightStableMonitoring = false;
|
||||||
|
|
@ -390,23 +390,21 @@ public class ApronArucoDetect {
|
||||||
dropTimesTag = true;
|
dropTimesTag = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 原有丢失处理
|
// 丢失处理
|
||||||
LogUtil.log(TAG_LOG, "找不到了二维码");
|
int lostUltHeight = Movement.getInstance().getUltrasonicHeight();
|
||||||
|
|
||||||
// 【新增】识别失败截图保存
|
|
||||||
if (saveFailScreenshot) {
|
|
||||||
saveFailScreenshot(grayImgMat, width, height);
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!arucoNotFoundTag) {
|
if (!arucoNotFoundTag) {
|
||||||
startTime = System.currentTimeMillis();
|
startTime = System.currentTimeMillis();
|
||||||
arucoNotFoundTag = true;
|
arucoNotFoundTag = true;
|
||||||
|
LogUtil.log(TAG_LOG, String.format("【丢失开始】ult=%d dropTimes=%d", lostUltHeight, dropTimes));
|
||||||
}
|
}
|
||||||
endTime = System.currentTimeMillis();
|
endTime = System.currentTimeMillis();
|
||||||
long lostDuration = endTime - startTime;
|
long lostDuration = endTime - startTime;
|
||||||
|
|
||||||
if (lostDuration > 1000 && lostDuration <= 12000) {
|
if (lostDuration > 1000 && lostDuration <= 12000) {
|
||||||
if (Movement.getInstance().getUltrasonicHeight() <= 20) {
|
if (lostUltHeight <= 20) {
|
||||||
|
LogUtil.log(TAG_LOG, String.format("【丢失拉高】vz=3.0 ult=%d 丢失=%dms dropTimes=%d",
|
||||||
|
lostUltHeight, lostDuration, dropTimes));
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||||
if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) {
|
if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) {
|
||||||
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
||||||
|
|
@ -420,11 +418,11 @@ public class ApronArucoDetect {
|
||||||
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogUtil.log(TAG_LOG, "执行位移");
|
LogUtil.log(TAG_LOG, String.format("【丢失下降】vz=-0.3 ult=%d 丢失=%dms", lostUltHeight, lostDuration));
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
||||||
}
|
}
|
||||||
} else if (lostDuration > 8000) {
|
} else if (lostDuration > 8000) {
|
||||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
LogUtil.log(TAG_LOG, String.format("【丢失超时】触发备降 丢失=%dms", lostDuration));
|
||||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||||
Movement.getInstance().setAlternate(true);
|
Movement.getInstance().setAlternate(true);
|
||||||
}
|
}
|
||||||
|
|
@ -697,6 +695,30 @@ public class ApronArucoDetect {
|
||||||
failScreenshotIndex = 0;
|
failScreenshotIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ★ 急停清理 */
|
||||||
|
public void stopAndReset() {
|
||||||
|
isStartAruco = false;
|
||||||
|
startFastStick = false;
|
||||||
|
frameCounter = 0;
|
||||||
|
if (lastFuture != null && !lastFuture.isDone()) {
|
||||||
|
lastFuture.cancel(true);
|
||||||
|
lastFuture = null;
|
||||||
|
}
|
||||||
|
handler.removeCallbacks(runnable);
|
||||||
|
handlerCallbackCount = 0;
|
||||||
|
isHeightStableMonitoring = false;
|
||||||
|
lastHeightCheckTime = 0;
|
||||||
|
lastUltrasonicHeight = 0;
|
||||||
|
arucoNotFoundTag = false;
|
||||||
|
startTime = 0;
|
||||||
|
endTime = 0;
|
||||||
|
dropTimesTag = false;
|
||||||
|
dropTimes = 0;
|
||||||
|
if (pidControlX != null) pidControlX.reset();
|
||||||
|
if (pidControlY != null) pidControlY.reset();
|
||||||
|
LogUtil.log(TAG_LOG, "【急停清理】已重置");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private double calculateYawErrorFromCorners(Point[] pts) {
|
private double calculateYawErrorFromCorners(Point[] pts) {
|
||||||
double dxTop = pts[1].x - pts[0].x;
|
double dxTop = pts[1].x - pts[0].x;
|
||||||
|
|
@ -750,6 +772,13 @@ public class ApronArucoDetect {
|
||||||
float vx = (float) Math.max(-0.2, Math.min(0.2, rawVx));
|
float vx = (float) Math.max(-0.2, Math.min(0.2, rawVx));
|
||||||
float vy = (float) Math.max(-0.2, Math.min(0.2, rawVy));
|
float vy = (float) Math.max(-0.2, Math.min(0.2, rawVy));
|
||||||
|
|
||||||
|
LogUtil.log(TAG_LOG, String.format(
|
||||||
|
"【PID链路】errX=%.0f errY=%.0f scale=%.0f | 输入: pidX_in=%.4f pidY_in=%.4f | PID输出: rawVx=%.4f rawVy=%.4f | 钳位后: vx=%.4f vy=%.4f",
|
||||||
|
errX, errY, scaleFactor,
|
||||||
|
errX / scaleFactor, -errY / scaleFactor,
|
||||||
|
rawVx, rawVy,
|
||||||
|
vx, vy));
|
||||||
|
|
||||||
double pixelWidth = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) +
|
double pixelWidth = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) +
|
||||||
Math.pow(pts[1].y - pts[0].y, 2));
|
Math.pow(pts[1].y - pts[0].y, 2));
|
||||||
|
|
||||||
|
|
@ -780,31 +809,12 @@ public class ApronArucoDetect {
|
||||||
float vz;
|
float vz;
|
||||||
if (currentHeight <= 4) {
|
if (currentHeight <= 4) {
|
||||||
vz = 0.0f;
|
vz = 0.0f;
|
||||||
if (Math.abs(errX) > 120) {
|
// 【修复】低空改用 PID 连续输出 + 低速度上限,去掉阶梯式开关控制
|
||||||
vx = rawVx > 0 ? 0.135f : -0.135f;
|
// 之前 bang-bang 固定档位导致来回震荡(0.05→0.09→0.135→-0.09...)
|
||||||
} else if (Math.abs(errX) > 80) {
|
// 现在用 PID 平滑输出,上限压到 0.06m/s,避免低空猛冲晃出二维码范围
|
||||||
vx = rawVx > 0 ? 0.09f : -0.09f;
|
float lowMaxSpeed = 0.06f;
|
||||||
} else if (Math.abs(errX) > 60) {
|
vx = (float) Math.max(-lowMaxSpeed, Math.min(lowMaxSpeed, rawVx));
|
||||||
vx = rawVx > 0 ? 0.07f : -0.07f;
|
vy = (float) Math.max(-lowMaxSpeed, Math.min(lowMaxSpeed, rawVy));
|
||||||
} else if (Math.abs(errX) > 30) {
|
|
||||||
vx = rawVx > 0 ? 0.05f : -0.05f;
|
|
||||||
} else {
|
|
||||||
vx = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (Math.abs(errY) > 120) {
|
|
||||||
vy = rawVy > 0 ? 0.135f : -0.135f;
|
|
||||||
} else if (Math.abs(errY) > 80) {
|
|
||||||
vy = rawVy > 0 ? 0.09f : -0.09f;
|
|
||||||
} else if (Math.abs(errY) > 60) {
|
|
||||||
vy = rawVy > 0 ? 0.07f : -0.07f;
|
|
||||||
} else if (Math.abs(errY) > 30) {
|
|
||||||
vy = rawVy > 0 ? 0.05f : -0.05f;
|
|
||||||
} else {
|
|
||||||
vy = 0f; // 【修正】
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (currentHeight <= 8) {
|
} else if (currentHeight <= 8) {
|
||||||
vz = SLOW_SUPER_SPEED;
|
vz = SLOW_SUPER_SPEED;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -813,8 +823,11 @@ public class ApronArucoDetect {
|
||||||
|
|
||||||
DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, yawRate, vz);
|
DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, yawRate, vz);
|
||||||
|
|
||||||
LogUtil.log(TAG_LOG, "vx" + vx + "vy" + vy + " errX=" + (int) errX + " errY=" + (int) errY +
|
LogUtil.log(TAG_LOG, String.format(
|
||||||
" pixelW=" + (int) pixelWidth + " vz=" + vz + " ult=" + currentHeight + " yaw=" + yawRate);
|
"【摇杆下发】vx=%.3f vy=%.3f vz=%.2f yaw=%.1f | errX=%d errY=%d pixelW=%d ult=%d | LENS=(%d,%d)",
|
||||||
|
vx, vy, vz, yawRate,
|
||||||
|
(int)errX, (int)errY, (int)pixelWidth, currentHeight,
|
||||||
|
(int)LENS_OFFSET_X, (int)LENS_OFFSET_Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int handlerCallbackCount = 0;
|
private int handlerCallbackCount = 0;
|
||||||
|
|
@ -823,7 +836,7 @@ public class ApronArucoDetect {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
performOperation();
|
performOperation();
|
||||||
if (handlerCallbackCount < 15) {
|
if (handlerCallbackCount < 18) {
|
||||||
handler.postDelayed(this, 50);
|
handler.postDelayed(this, 50);
|
||||||
} else {
|
} else {
|
||||||
performNextStep();
|
performNextStep();
|
||||||
|
|
|
||||||
|
|
@ -1013,13 +1013,61 @@ public class ApronArucoDetectPort {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ★ 急停清理:重置所有状态,取消所有定时器,确保下次降落可以重新触发 */
|
||||||
|
public void stopAndReset() {
|
||||||
|
// 1. 取消入口 guard,否则下次 detectArucoTags 直接 return
|
||||||
|
isStartAruco = false;
|
||||||
|
startFastStick = false;
|
||||||
|
frameCounter = 0;
|
||||||
|
|
||||||
|
// 2. 取消 ScheduledExecutorService 里的 pending 检测任务
|
||||||
|
if (lastFuture != null && !lastFuture.isDone()) {
|
||||||
|
lastFuture.cancel(true);
|
||||||
|
lastFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 停止快速下降 handler
|
||||||
|
handler.removeCallbacks(runnable);
|
||||||
|
handlerCallbackCount = 0;
|
||||||
|
|
||||||
|
// 4. 重置旋转阶段
|
||||||
|
isYawAligned = false;
|
||||||
|
currentLandingMode = 0;
|
||||||
|
|
||||||
|
// 5. 重置分支计数器
|
||||||
|
counterRound = 0;
|
||||||
|
for (int i = 0; i < landingCounters.length; i++) {
|
||||||
|
landingCounters[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 重置高度稳定性监测
|
||||||
|
isHeightStableMonitoring = false;
|
||||||
|
lastHeightCheckTime = 0;
|
||||||
|
lastUltrasonicHeight = 0;
|
||||||
|
|
||||||
|
// 7. 重置丢失计时器
|
||||||
|
arucoNotFoundTag = false;
|
||||||
|
startTime = 0;
|
||||||
|
endTime = 0;
|
||||||
|
|
||||||
|
// 8. 重置复降计数
|
||||||
|
dropTimesTag = false;
|
||||||
|
dropTimes = 0;
|
||||||
|
|
||||||
|
// 9. PID 复位
|
||||||
|
if (pidControlX != null) pidControlX.reset();
|
||||||
|
if (pidControlY != null) pidControlY.reset();
|
||||||
|
|
||||||
|
LogUtil.log(TAG_LOG, "【急停清理】所有状态和定时器已重置");
|
||||||
|
}
|
||||||
|
|
||||||
private int handlerCallbackCount = 0;
|
private int handlerCallbackCount = 0;
|
||||||
private Handler handler = new Handler(Looper.getMainLooper());
|
private Handler handler = new Handler(Looper.getMainLooper());
|
||||||
private Runnable runnable = new Runnable() {
|
private Runnable runnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
performOperation();
|
performOperation();
|
||||||
if (handlerCallbackCount < 15) {
|
if (handlerCallbackCount < 18) {
|
||||||
handler.postDelayed(this, 50);
|
handler.postDelayed(this, 50);
|
||||||
} else {
|
} else {
|
||||||
performNextStep();
|
performNextStep();
|
||||||
|
|
|
||||||
|
|
@ -899,6 +899,34 @@ public class ApronArucodownmany {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ★ 急停清理 */
|
||||||
|
public void stopAndReset() {
|
||||||
|
isStartAruco = false;
|
||||||
|
startFastStick = false;
|
||||||
|
frameCounter = 0;
|
||||||
|
if (lastFuture != null && !lastFuture.isDone()) {
|
||||||
|
lastFuture.cancel(true);
|
||||||
|
lastFuture = null;
|
||||||
|
}
|
||||||
|
handler.removeCallbacks(runnable);
|
||||||
|
handlerCallbackCount = 0;
|
||||||
|
counterRound = 0;
|
||||||
|
for (int i = 0; i < landingCounters.length; i++) {
|
||||||
|
landingCounters[i] = 0;
|
||||||
|
}
|
||||||
|
isHeightStableMonitoring = false;
|
||||||
|
lastHeightCheckTime = 0;
|
||||||
|
lastUltrasonicHeight = 0;
|
||||||
|
arucoNotFoundTag = false;
|
||||||
|
startTime = 0;
|
||||||
|
endTime = 0;
|
||||||
|
dropTimesTag = false;
|
||||||
|
dropTimes = 0;
|
||||||
|
if (pidControlX != null) pidControlX.reset();
|
||||||
|
if (pidControlY != null) pidControlY.reset();
|
||||||
|
LogUtil.log(TAG_LOG, "【急停清理】已重置");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private double calculateYawErrorFromCorners(Point[] pts) {
|
private double calculateYawErrorFromCorners(Point[] pts) {
|
||||||
double dxTop = pts[1].x - pts[0].x;
|
double dxTop = pts[1].x - pts[0].x;
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,26 @@ public class MqttManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void needConnect() {
|
public void needConnect() {
|
||||||
|
// 已经连接就不要再建新的,避免生成新handle导致旧handle失效
|
||||||
|
if (mqttAndroidClient != null && mqttAndroidClient.isConnected()) {
|
||||||
|
LogUtil.log(TAG, "MQTT已连接,跳过重复needConnect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
initMqttClientParams();
|
initMqttClientParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMqttClientParams() {
|
private void initMqttClientParams() {
|
||||||
mqttAndroidClient = new MqttAndroidClient(ApronApp.Companion.getApplication(), AMSConfig.getInstance().getMqttServerUri(), generateRandomString(10));
|
// 先关闭旧的client,释放MqttService里的旧handle,防止"Invalid ClientHandle"
|
||||||
|
if (mqttAndroidClient != null) {
|
||||||
|
try {
|
||||||
|
mqttAndroidClient.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.log(TAG, "关闭旧MQTT客户端异常:" + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用固定的clientId前缀 + 时间戳,避免每次随机导致Service端残留多个无效handle
|
||||||
|
String clientId = "ams_" + System.currentTimeMillis();
|
||||||
|
mqttAndroidClient = new MqttAndroidClient(ApronApp.Companion.getApplication(), AMSConfig.getInstance().getMqttServerUri(), clientId);
|
||||||
mMqttConnectOptions = new MqttConnectOptions();
|
mMqttConnectOptions = new MqttConnectOptions();
|
||||||
mMqttConnectOptions.setAutomaticReconnect(true);
|
mMqttConnectOptions.setAutomaticReconnect(true);
|
||||||
mMqttConnectOptions.setMaxInflight(1000);// 避免消息积压导致连接拥塞
|
mMqttConnectOptions.setMaxInflight(1000);// 避免消息积压导致连接拥塞
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ public class PsdkWidgetScheduler {
|
||||||
report.setData(data);
|
report.setData(data);
|
||||||
|
|
||||||
String jsonPayload = new Gson().toJson(report);
|
String jsonPayload = new Gson().toJson(report);
|
||||||
LogUtil.log(TAG, "PSDK widget JSON payload: " + jsonPayload);
|
// LogUtil.log(TAG, "PSDK widget JSON payload: " + jsonPayload);
|
||||||
MqttMessage mqttMessage = new MqttMessage(jsonPayload.getBytes("UTF-8"));
|
MqttMessage mqttMessage = new MqttMessage(jsonPayload.getBytes("UTF-8"));
|
||||||
mqttMessage.setQos(1);
|
mqttMessage.setQos(1);
|
||||||
client.publish(AMSConfig.UP_UAV_EVENT, mqttMessage);
|
client.publish(AMSConfig.UP_UAV_EVENT, mqttMessage);
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,34 @@ public class ApriltagDetector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 联合 PnP(多 tag 时使用所有角点一次求解)──
|
||||||
|
if (detections.size() >= 2) {
|
||||||
|
try {
|
||||||
|
int n = detections.size();
|
||||||
|
ApriltagDetection[] arr = new ApriltagDetection[n];
|
||||||
|
double[] sizes = new double[n];
|
||||||
|
double[] offsX = new double[n];
|
||||||
|
double[] offsY = new double[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
arr[i] = detections.get(i);
|
||||||
|
sizes[i] = getTagSize(arr[i].id);
|
||||||
|
double[] off = getTagOffset(arr[i].id);
|
||||||
|
offsX[i] = off[0];
|
||||||
|
offsY[i] = off[1];
|
||||||
|
}
|
||||||
|
ApriltagPose joint = ApriltagNative.estimate_joint_pose(
|
||||||
|
arr, sizes, offsX, offsY, fx, fy, cx, cy);
|
||||||
|
if (joint != null) {
|
||||||
|
result.setJointPose(joint);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "joint PnP failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else if (detections.size() == 1) {
|
||||||
|
ApriltagPose lp = result.getBestLandingPose();
|
||||||
|
if (lp != null) result.setJointPose(lp);
|
||||||
|
}
|
||||||
|
|
||||||
// ── 统计 ──
|
// ── 统计 ──
|
||||||
long elapsed = System.currentTimeMillis() - t0;
|
long elapsed = System.currentTimeMillis() - t0;
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -318,6 +318,12 @@
|
||||||
android:layout_width="100dp"
|
android:layout_width="100dp"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_test3"
|
||||||
|
android:text="DJI急停"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,12 @@
|
||||||
android:layout_width="100dp"
|
android:layout_width="100dp"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_test3"
|
||||||
|
android:text="DJI急停"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue