防止apc线程池爆了
This commit is contained in:
parent
d6a6c505a1
commit
10857ce335
|
|
@ -16,5 +16,22 @@
|
|||
"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.Toast
|
||||
import android.widget.TextView
|
||||
import com.aros.apron.tools.ToastUtil
|
||||
import dji.sdk.keyvalue.key.RemoteControllerKey
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.GravityCompat
|
||||
|
|
@ -134,7 +136,7 @@ open class MainActivity : BaseActivity() {
|
|||
var isAppStarted: Boolean = false
|
||||
var streamReceive: Boolean = false
|
||||
|
||||
var isscousse: Boolean=false;
|
||||
var isscousse: Boolean = false;
|
||||
private var instance: MainActivity? = null
|
||||
|
||||
fun getInstance(): MainActivity? {
|
||||
|
|
@ -653,10 +655,11 @@ open class MainActivity : BaseActivity() {
|
|||
StickManager.getInstance().enableVirtualStick1()
|
||||
|
||||
}
|
||||
// btn_test3?.setOnClickListener {
|
||||
// StreamManager.getInstance().startstream()
|
||||
//
|
||||
// }
|
||||
btn_test3 = findViewById(R.id.btn_test3)
|
||||
btn_test3?.setOnClickListener {
|
||||
// DJI按钮方式急停测试
|
||||
//FlightManager.getInstance().emergencyHoverByPauseButton(null);
|
||||
}
|
||||
|
||||
|
||||
initClickListener()
|
||||
|
|
@ -819,7 +822,9 @@ open class MainActivity : BaseActivity() {
|
|||
GeoidManager.getInstance().init(this)
|
||||
MissionV3Manager.getInstance().initMissionManager()
|
||||
enableStream()
|
||||
|
||||
initFpvStream()
|
||||
|
||||
startVtxHeartbeat()
|
||||
SpeakerManager.getInstance().addMegaphoneInfoListener()
|
||||
GimbalManager.getInstance().setmode()
|
||||
|
|
@ -848,14 +853,14 @@ open class MainActivity : BaseActivity() {
|
|||
// 每个 tag 离降落点的偏移(米):[dx右, dy前]
|
||||
val tagOffsetMap = mapOf(
|
||||
248 to doubleArrayOf(-0.262, 0.052),
|
||||
247 to doubleArrayOf( 0.267, 0.052),
|
||||
247 to doubleArrayOf(0.267, 0.052),
|
||||
246 to doubleArrayOf(-0.052, 0.0),
|
||||
244 to doubleArrayOf( 0.056, 0.0),
|
||||
244 to doubleArrayOf(0.056, 0.0),
|
||||
242 to doubleArrayOf(-0.262, -0.194),
|
||||
241 to doubleArrayOf( 0.267, -0.192),
|
||||
245 to doubleArrayOf( 0.0, -0.404),
|
||||
241 to doubleArrayOf(0.267, -0.192),
|
||||
245 to doubleArrayOf(0.0, -0.404),
|
||||
250 to doubleArrayOf(-0.262, -0.483),
|
||||
249 to doubleArrayOf( 0.267, -0.480)
|
||||
249 to doubleArrayOf(0.267, -0.480)
|
||||
)
|
||||
ApriltagDetector.getInstance().setTagOffsets(tagOffsetMap)
|
||||
|
||||
|
|
@ -1043,9 +1048,9 @@ open class MainActivity : BaseActivity() {
|
|||
try {
|
||||
Synchronizedstatus.setIsruningframe(true)
|
||||
|
||||
if(!isscousse){
|
||||
isscousse=true;
|
||||
LogUtil.log(TAG,"mix视频帧回调了")
|
||||
if (!isscousse) {
|
||||
isscousse = true;
|
||||
LogUtil.log(TAG, "mix视频帧回调了")
|
||||
}
|
||||
|
||||
if (startArucoType == 1) {
|
||||
|
|
@ -1102,8 +1107,6 @@ open class MainActivity : BaseActivity() {
|
|||
try {
|
||||
Synchronizedstatus.setIsruningframe(true)
|
||||
|
||||
|
||||
|
||||
if (startArucoType == 1) {
|
||||
Aprondown.getInstance()?.detectArucoTags(
|
||||
height,
|
||||
|
|
@ -1138,7 +1141,7 @@ open class MainActivity : BaseActivity() {
|
|||
@SuppressLint("SuspiciousIndentation")
|
||||
private fun initFpvStream() {
|
||||
cameraManager.addFrameListener(
|
||||
ComponentIndexType.PORT_1,
|
||||
ComponentIndexType.FPV,
|
||||
ICameraStreamManager.FrameFormat.YUV420_888
|
||||
) { frameData, _, _, width, height, _ ->
|
||||
Movement.getInstance().isVtx = true
|
||||
|
|
@ -1152,24 +1155,34 @@ open class MainActivity : BaseActivity() {
|
|||
try {
|
||||
Synchronizedstatus.setIsruningframe(true)
|
||||
|
||||
if(!isscousse){
|
||||
isscousse=true;
|
||||
LogUtil.log(TAG,"port视频帧回调了")
|
||||
if (!isscousse) {
|
||||
isscousse = true;
|
||||
LogUtil.log(TAG, "port视频帧回调了")
|
||||
}
|
||||
|
||||
if (startArucoType == 1) {
|
||||
|
||||
|
||||
ApronArucoDetect.getInstance()?.detectArucoTags(
|
||||
height,
|
||||
width,
|
||||
frameData,
|
||||
dictionary
|
||||
)
|
||||
|
||||
|
||||
// ApronArucodownmany.getInstance()?.detectArucoTags(
|
||||
// height,
|
||||
// width,
|
||||
// frameData,
|
||||
// dictionary
|
||||
// )
|
||||
AprilTagPort.getInstance().processFrame(
|
||||
frameData,
|
||||
width,
|
||||
height
|
||||
)
|
||||
|
||||
// AprilTagPort.getInstance().processFrame(
|
||||
// frameData,
|
||||
// width,
|
||||
// height
|
||||
// )
|
||||
} else if (startArucoType == 2) {
|
||||
AlternateArucoDetect.getInstance()?.detectArucoTags(
|
||||
height,
|
||||
|
|
@ -1178,12 +1191,23 @@ open class MainActivity : BaseActivity() {
|
|||
dictionary
|
||||
)
|
||||
} else if (startArucoType == 3) {
|
||||
|
||||
ApronArucoDetect.getInstance()?.detectForceTriggerTags(
|
||||
height,
|
||||
width,
|
||||
frameData,
|
||||
dictionary
|
||||
)
|
||||
|
||||
//
|
||||
// ApronArucodownmany.getInstance()?.detectForceTriggerTags(
|
||||
// height,
|
||||
// width,
|
||||
// frameData,
|
||||
// dictionary
|
||||
// )
|
||||
|
||||
|
||||
}
|
||||
} finally {
|
||||
Synchronizedstatus.setIsruningframe(false)
|
||||
|
|
@ -1212,9 +1236,9 @@ open class MainActivity : BaseActivity() {
|
|||
try {
|
||||
Synchronizedstatus.setIsruningframe(true)
|
||||
|
||||
if(!isscousse){
|
||||
isscousse=true;
|
||||
LogUtil.log(TAG,"fpv视频帧回调了")
|
||||
if (!isscousse) {
|
||||
isscousse = true;
|
||||
LogUtil.log(TAG, "fpv视频帧回调了")
|
||||
}
|
||||
|
||||
if (startArucoType == 1) {
|
||||
|
|
@ -1300,7 +1324,7 @@ open class MainActivity : BaseActivity() {
|
|||
LogUtil.log(TAG, "取消降落,识别机库二维码")
|
||||
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
|
||||
Handler().postDelayed(Runnable {
|
||||
if (!ApronArucodownmany.getInstance().isTriggerSuccess) {
|
||||
if (!AprilTagPort.getInstance().isTriggerSuccess) {
|
||||
LogUtil.log(TAG, "图传异常:飞往备降点")
|
||||
//测试图传丢失
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null)
|
||||
|
|
@ -1309,7 +1333,10 @@ open class MainActivity : BaseActivity() {
|
|||
} else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) {
|
||||
Handler().postDelayed(Runnable {
|
||||
if (!Aprongim.getInstance().isTriggerSuccess) {
|
||||
LogUtil.log(TAG, "图传异常:飞往备降点"+ Movement.getInstance().isVtx)
|
||||
LogUtil.log(
|
||||
TAG,
|
||||
"图传异常:飞往备降点" + Movement.getInstance().isVtx
|
||||
)
|
||||
//测试图传丢失
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null)
|
||||
}
|
||||
|
|
@ -1371,7 +1398,7 @@ open class MainActivity : BaseActivity() {
|
|||
LogUtil.log(TAG, "取消降落,识别备降点二维码")
|
||||
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
|
||||
Handler().postDelayed(Runnable {
|
||||
if (!ApronArucoDetect.getInstance().isTriggerSuccess) {
|
||||
if (!AprilTagPort.getInstance().isTriggerSuccess) {
|
||||
LogUtil.log(TAG, "图传异常:飞往备降点")
|
||||
//测试图传丢失
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public abstract class BaseManager {
|
|||
mqttMessage.setQos(1);
|
||||
org.eclipse.paho.client.mqttv3.IMqttDeliveryToken token =
|
||||
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 {
|
||||
LogUtil.log(TAG, "回复失败:mqtt 未连接, tid=" + entity.getTid());
|
||||
}
|
||||
|
|
@ -237,7 +237,7 @@ public abstract class BaseManager {
|
|||
if( Movement.getInstance().getTask_media_count()!=0){
|
||||
LogUtil.log(TAG ,"getTask_media_count"+Movement.getInstance().getTask_media_count());
|
||||
}
|
||||
LogUtil.log(TAG ,"QWQsendFlightTaskProgress2Server");
|
||||
// LogUtil.log(TAG ,"QWQsendFlightTaskProgress2Server");
|
||||
try {
|
||||
if (MqttManager.getInstance().mqttAndroidClient.isConnected()) {
|
||||
// 必须加 final,否则内部类(回调)里访问不了
|
||||
|
|
@ -613,7 +613,9 @@ public abstract class BaseManager {
|
|||
|
||||
|
||||
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;
|
||||
} else {
|
||||
LogUtil.log(TAG, "降落时不允许操作云台/相机/虚拟摇杆");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.os.Looper;
|
|||
|
||||
import com.aros.apron.constant.AMSConfig;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.MqttManager;
|
||||
import com.aros.apron.tools.ToastUtil;
|
||||
|
||||
import org.eclipse.paho.android.service.MqttAndroidClient;
|
||||
|
|
@ -68,7 +69,11 @@ public class MqttActionCallBack implements IMqttActionListener {
|
|||
@Override
|
||||
public void run() {
|
||||
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) {
|
||||
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.GimbalManager;
|
||||
import com.aros.apron.manager.MLTEManager;
|
||||
import com.aros.apron.manager.MediaManager;
|
||||
import com.aros.apron.manager.MissionV3Manager;
|
||||
import com.aros.apron.manager.OSDManager;
|
||||
import com.aros.apron.manager.PayloadlightManager;
|
||||
|
|
@ -249,6 +250,8 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
|
|||
LogUtil.log(TAG, "收到:服务端响应TaskFail" + jsonString);
|
||||
ApronExecutionStatus.getInstance().setServerReplyTaskFail(true);
|
||||
break;
|
||||
|
||||
|
||||
case Constant.TAKEOFF_TO_POINT:
|
||||
// //1.检查图传是否连接
|
||||
// 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:
|
||||
MLTEManager.getInstance().test();
|
||||
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
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
try {
|
||||
LogUtil.log(TAG, "消息送达确认, msgId=" + token.getMessageId()
|
||||
+ ", complete=" + token.isComplete()
|
||||
+ ", topics=" + java.util.Arrays.toString(token.getTopics()));
|
||||
// LogUtil.log(TAG, "消息送达确认, msgId=" + token.getMessageId()
|
||||
// + ", complete=" + token.isComplete()
|
||||
// + ", topics=" + java.util.Arrays.toString(token.getTopics()));
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "deliveryComplete 异常: " + e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,6 +366,23 @@ public class Constant {
|
|||
* 航线测试
|
||||
*/
|
||||
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.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import dji.sdk.wpmz.value.mission.WaylineExecuteWaypoint;
|
||||
import dji.sdk.wpmz.value.mission.WaylineWaypoint;
|
||||
|
|
@ -41,4 +43,46 @@ public class CurrentWayline {
|
|||
public void setRouteWaypoints(List<WaylineExecuteWaypoint> 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 {
|
||||
private int result;
|
||||
private String url;
|
||||
private String rtmp_url;
|
||||
private int video_quality;
|
||||
private int ideo_quality;
|
||||
private AlternateLandPoint alternate_land_point;
|
||||
|
|
@ -632,6 +633,14 @@ public class MessageDown {
|
|||
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() {
|
||||
return video_quality;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ public class Movement {
|
|||
private int landingPower;//降落电量所需百分比
|
||||
private int lowBatteryRTHState;//智能低电量返航状态 0未触发智能低电量返航 1触发智能低电量返航,飞行器正在倒计时 2执行智能低电量返航 3智能低电量返航被取消
|
||||
private String missionName;//当前正在执行的航线名
|
||||
private int currentWaypointIndex = 0;//当前航点下标
|
||||
private int currentWaypointIndex = 0;//当前航点下标(DJI回调)
|
||||
private int targetWaypointIndex = 0;//GPS检测目标航点下标(自研)
|
||||
|
||||
|
||||
|
||||
|
||||
private boolean planeWing;//飞机是否在飞
|
||||
private boolean isMotorsOn;//电机是否起转
|
||||
|
|
@ -2525,6 +2529,14 @@ public class Movement {
|
|||
this.currentWaypointIndex = currentWaypointIndex;
|
||||
}
|
||||
|
||||
public int getTargetWaypointIndex() {
|
||||
return targetWaypointIndex;
|
||||
}
|
||||
|
||||
public void setTargetWaypointIndex(int targetWaypointIndex) {
|
||||
this.targetWaypointIndex = targetWaypointIndex;
|
||||
}
|
||||
|
||||
public String getMissionName() {
|
||||
return missionName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -747,7 +747,6 @@ public class CameraManager extends BaseManager {
|
|||
KeyConnection, ComponentIndexType.PORT_1));
|
||||
if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) {
|
||||
if (message != null) {
|
||||
|
||||
int cameraMode = message.getData().getCamera_mode();
|
||||
// 新增:如果当前已经是该模式,直接返回成功
|
||||
if (cameraMode == Movement.getInstance().getCamera_mode()) {
|
||||
|
|
@ -1466,24 +1465,24 @@ public class CameraManager extends BaseManager {
|
|||
@Override
|
||||
public void onSuccess() {
|
||||
// 切换到红外视频源时,自动设置调色盘为铁红色(6)
|
||||
if (type == 3) {
|
||||
KeyManager.getInstance().setValue(
|
||||
KeyTools.createCameraKey(CameraKey.KeyThermalPalette,
|
||||
ComponentIndexType.PORT_1,
|
||||
CameraLensType.CAMERA_LENS_THERMAL),
|
||||
CameraThermalPalette.find(6),
|
||||
new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "红外切换成功,已设置调色盘为铁红");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtil.log(TAG, "设置红外调色盘失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (type == 3) {
|
||||
// KeyManager.getInstance().setValue(
|
||||
// KeyTools.createCameraKey(CameraKey.KeyThermalPalette,
|
||||
// ComponentIndexType.PORT_1,
|
||||
// CameraLensType.CAMERA_LENS_THERMAL),
|
||||
// CameraThermalPalette.find(6),
|
||||
// new CommonCallbacks.CompletionCallback() {
|
||||
// @Override
|
||||
// public void onSuccess() {
|
||||
// LogUtil.log(TAG, "红外切换成功,已设置调色盘为铁红");
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onFailure(@NonNull IDJIError error) {
|
||||
// LogUtil.log(TAG, "设置红外调色盘失败:" + getIDJIErrorMsg(error));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
sendMsg2Server(message);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ import androidx.annotation.Nullable;
|
|||
import com.aros.apron.base.BaseManager;
|
||||
import com.aros.apron.constant.AMSConfig;
|
||||
import com.aros.apron.entity.ApronExecutionStatus;
|
||||
import com.aros.apron.entity.CurrentWayline;
|
||||
import com.aros.apron.entity.MessageDown;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.entity.ParsedWaypoint;
|
||||
import com.aros.apron.activity.MainActivity;
|
||||
import com.aros.apron.mix.Aprondown;
|
||||
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.KeyTools;
|
||||
import dji.sdk.keyvalue.key.ProductKey;
|
||||
import dji.sdk.keyvalue.key.RemoteControllerKey;
|
||||
import dji.sdk.keyvalue.key.RtkMobileStationKey;
|
||||
import dji.sdk.keyvalue.value.camera.CameraExposureCompensation;
|
||||
import dji.sdk.keyvalue.value.camera.CameraExposureMode;
|
||||
|
|
@ -351,9 +354,10 @@ public class FlightManager extends BaseManager {
|
|||
if (newValue.getAltitude() != null) {
|
||||
Movement.getInstance().setElevation(newValue.getAltitude());
|
||||
Movement.getInstance().setTask_height(newValue.getAltitude());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
double distance = LocationUtils.getDistance(String.valueOf(Movement.getInstance().getHomepoint_longitude()),
|
||||
String.valueOf(Movement.getInstance().getHomepoint_latitude()),
|
||||
String.valueOf(newValue.getLongitude()),
|
||||
|
|
@ -370,6 +374,67 @@ public class FlightManager extends BaseManager {
|
|||
Movement.getInstance().setLatitude(newValue.getLatitude());
|
||||
Movement.getInstance().setLongitude(newValue.getLongitude());
|
||||
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目标点 ==========
|
||||
double targetLat = Movement.getInstance().getFlyto_target_latitude();
|
||||
|
|
@ -1288,11 +1353,15 @@ public class FlightManager extends BaseManager {
|
|||
heightLogCounter = 0;
|
||||
}
|
||||
|
||||
if (isFlying && (Movement.getInstance().getElevation() < 15 && Movement.getInstance().getUltrasonicHeight() < 50 || forceTriggerDetection) && !isSendDetect) {
|
||||
double flyingHeight = Movement.getInstance().getElevation();
|
||||
// 超声波卡住(≥50dm)时,飞控高度 <5m 也允许触发
|
||||
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;
|
||||
|
||||
if (flyingHeight > thresholdMin || forceTriggerDetection) {
|
||||
if (currElevation > thresholdMin || forceTriggerDetection) {
|
||||
|
||||
boolean shouldTriggerDetection;
|
||||
|
||||
|
|
@ -1339,7 +1408,7 @@ public class FlightManager extends BaseManager {
|
|||
PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false);
|
||||
PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(true);
|
||||
LogUtil.log(TAG, "开始识别机库二维码,椭球高度:" + Movement.getInstance().getElevation() + "米" + "--超声波高度:" + Movement.getInstance().getUltrasonicHeight() + "分米");
|
||||
sendEvent2Server("开始视觉降落", 1);
|
||||
sendEvent2Server("开始视觉降落"+Movement.getInstance().getCapacity_percent(), 1);
|
||||
}
|
||||
|
||||
//PerceptionManager.getInstance().setPerceptionEnable(false);
|
||||
|
|
@ -1670,17 +1739,23 @@ public class FlightManager extends BaseManager {
|
|||
if (flightMode != null) {
|
||||
switch (flightMode) {
|
||||
case GO_HOME:
|
||||
|
||||
KeyManager.getInstance().performAction(createKey(FlightControllerKey.KeyStopGoHome), new CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>() {
|
||||
@Override
|
||||
public void onSuccess(EmptyMsg emptyMsg) {
|
||||
LogUtil.log(TAG, "紧急悬停,取消返航成功");
|
||||
//只有在取消返航时才能显示取消返航失败
|
||||
Movement.getInstance().setMode_code(3);
|
||||
isGimbalReset = false;
|
||||
sendMsg2Server(message);
|
||||
resetAircrftLandingStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtil.log(TAG, "取消返航执行失败" + error);
|
||||
//isGimbalReset = false;
|
||||
sendFailMsg2Server(message, "紧急悬停,取消返航失败:" + getIDJIErrorMsg(error));
|
||||
resetAircrftLandingStatus();
|
||||
}
|
||||
});
|
||||
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
|
||||
public void onLTELinkInfoUpdate(LTELinkInfo t1) {
|
||||
if (t1 != null) {
|
||||
LogUtil.log(TAG, "信号质量" + t1.getLinkQualityLevel());
|
||||
// LogUtil.log(TAG, "信号质量" + t1.getLinkQualityLevel());
|
||||
if(t1.getLinkQualityLevel()!=null){
|
||||
Movement.getInstance().setQuality_4g(t1.getLinkQualityLevel().getLteGndSingalBar().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.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -19,6 +20,7 @@ import com.amazonaws.services.s3.model.ProgressListener;
|
|||
import com.amazonaws.services.s3.model.PutObjectRequest;
|
||||
import com.aros.apron.base.BaseManager;
|
||||
import com.aros.apron.entity.ApronExecutionStatus;
|
||||
import com.aros.apron.entity.MessageDown;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PreferenceUtils;
|
||||
|
|
@ -36,6 +38,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||
import dji.sdk.keyvalue.key.KeyTools;
|
||||
|
|
@ -74,6 +77,10 @@ public class MediaManager extends BaseManager {
|
|||
/* ===== 下载失败重试计数 ===== */
|
||||
private int downloadFailTimes = 0;
|
||||
private static final int MAX_DOWNLOAD_RETRY = 3;
|
||||
|
||||
/* ===== 统一主线程Handler ===== */
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private MediaManager() {
|
||||
}
|
||||
|
||||
|
|
@ -92,240 +99,328 @@ public class MediaManager extends BaseManager {
|
|||
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
||||
MediaDataCenter.getInstance().getMediaManager().addMediaFileListStateListener(new MediaFileListStateListener() {
|
||||
@Override
|
||||
public void onUpdate(MediaFileListState mediaFileListState) {
|
||||
mState = mediaFileListState;
|
||||
LogUtil.log(TAG, "当前媒体文件状态:" + mediaFileListState.name());
|
||||
public void onUpdate(MediaFileListState state) {
|
||||
mState = state;
|
||||
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 boolean isEnablePlayback;
|
||||
private volatile boolean isPlaybackEnabling; // 防止并发调用
|
||||
private Handler playbackTimeoutHandler = new Handler(android.os.Looper.getMainLooper());
|
||||
private Runnable playbackTimeoutRunnable = null;
|
||||
private static final int PLAYBACK_ENABLE_TIMEOUT_MS = 15000; // 进入媒体模式超时 15 秒
|
||||
private volatile boolean isPlaybackEnabling;
|
||||
private static final int PULL_TIMEOUT_S = 20;
|
||||
private int retryCycle = 0;
|
||||
private boolean isPulling;
|
||||
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() {
|
||||
LogUtil.log(TAG, "【enablePlayback】调用 | isPlaybackEnabling=" + isPlaybackEnabling
|
||||
+ " | useOwnMethod=" + useOwnMethod + " | dji=" + djiRetries + "/" + DJI_MAX_RETRIES
|
||||
+ " | own=" + ownRetries + "/" + OWN_MAX_RETRIES);
|
||||
if (isPlaybackEnabling) {
|
||||
LogUtil.log(TAG, "媒体模式已在启用中,跳过重复调用");
|
||||
LogUtil.log(TAG, "【enablePlayback】已在启用中,跳过");
|
||||
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;
|
||||
|
||||
// 停止端口扫描和 RTSP 刷新,关闭 RTSP 推流,确保媒体上传稳定
|
||||
// StreamManager.getInstance().stopStreamRefreshTimer();
|
||||
//com.aros.apron.tools.SimplePortScanner.getInstance().stopScan();
|
||||
StreamManager.getInstance().stopstream();
|
||||
|
||||
// 仅首次调用时清理状态,重试时保留计数器
|
||||
if (isFirstEntry) {
|
||||
if (!useOwnMethod) {
|
||||
djiRetries++;
|
||||
LogUtil.log(TAG, "【DJI方式】第" + djiRetries + "/" + DJI_MAX_RETRIES + "次");
|
||||
} else {
|
||||
ownRetries++;
|
||||
LogUtil.log(TAG, "【自有方式】第" + ownRetries + "/" + OWN_MAX_RETRIES + "次");
|
||||
}
|
||||
|
||||
// 首次进入清理状态
|
||||
if (!useOwnMethod && djiRetries == 1) {
|
||||
uploadedFileNames.clear();
|
||||
bucketChecked = false;
|
||||
downloadFailTimes = 0;
|
||||
enterPlayBackFailTimes = 0;
|
||||
isEnablePlayback = false;
|
||||
pullMediaFileListFromCameraFailTimes = 0;
|
||||
updatingWaitCount = 0;
|
||||
pullqwq = false;
|
||||
pullStartTime = System.currentTimeMillis();
|
||||
LogUtil.log(TAG, "已清空上传文件集合");
|
||||
LogUtil.log(TAG, "【enablePlayback】首次进入,清理状态");
|
||||
}
|
||||
|
||||
// ★ 只在推流活跃时才停,避免无流时产生"直播未启动"的无效日志
|
||||
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() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "进入媒体模式成功");
|
||||
isPlaybackEnabling = false; // 成功后释放锁
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MediaFileListDataSource source = new
|
||||
MediaFileListDataSource.Builder().setIndexType(ComponentIndexType.PORT_1).build();
|
||||
if (!enableDone.compareAndSet(false, true)) return; // 超时已触发,忽略
|
||||
mainHandler.removeCallbacks(enableTimeout);
|
||||
LogUtil.log(TAG, "【enablePlayback】SDK enable 成功 | method=" + (useOwnMethod ? "自有" : "DJI"));
|
||||
isPlaybackEnabling = false;
|
||||
|
||||
mainHandler.postDelayed(() -> {
|
||||
MediaFileListDataSource source = new MediaFileListDataSource.Builder()
|
||||
.setIndexType(ComponentIndexType.PORT_1).build();
|
||||
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pullMediaFileListFromCamera();
|
||||
}
|
||||
}, 3000);
|
||||
if (!useOwnMethod) {
|
||||
// ★ DJI方式:走状态驱动流程
|
||||
LogUtil.log(TAG, "【DJI方式】6s延迟到,开始 pullMediaFileListFromCamera");
|
||||
mainHandler.postDelayed(() -> pullMediaFileListFromCamera(), 3000);
|
||||
} else {
|
||||
// ★ 自有方式:enable成功后2s就拉,不等状态
|
||||
LogUtil.log(TAG, "【自有方式】enable成功,2s后第一次拉取");
|
||||
mainHandler.postDelayed(() -> forcePullInOwnMode(), 2000);
|
||||
}
|
||||
}, 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);
|
||||
}
|
||||
public void onFailure(@NonNull IDJIError e) {
|
||||
if (!enableDone.compareAndSet(false, true)) return; // 超时已触发,忽略
|
||||
mainHandler.removeCallbacks(enableTimeout);
|
||||
LogUtil.log(TAG, "【enablePlayback】SDK enable 失败: " + e.description()
|
||||
+ " | method=" + (useOwnMethod ? "自有" : "DJI"));
|
||||
isPlaybackEnabling = false;
|
||||
retryAfterDisable("enable失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 重试进入媒体模式(不清理状态,保留计数器) */
|
||||
private void retryEnablePlayback() {
|
||||
isPlaybackEnabling = true;
|
||||
MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() {
|
||||
/**
|
||||
* ★ 自有方式:enable 成功 → 2s后第一次拉 → 等20s → 第二次拉 → 检查数据
|
||||
* SDK 可能不给任何回调,每步都挂超时兜底
|
||||
*/
|
||||
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
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "进入媒体模式成功(重试)");
|
||||
isPlaybackEnabling = false;
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
LogUtil.log(TAG, "【自有方式】第一次pull onSuccess → 20s后第二次拉");
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
MediaFileListDataSource source = new
|
||||
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);
|
||||
}
|
||||
public void onFailure(@NonNull IDJIError e) {
|
||||
LogUtil.log(TAG, "【自有方式】第一次pull onFailure: " + e.description() + " → 继续等20s");
|
||||
}
|
||||
});
|
||||
|
||||
// ★ 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;
|
||||
private static final int MAX_UPDATING_WAIT = 15; // 最多等待15秒,无文件时快速跳过
|
||||
private boolean pullqwq = false;
|
||||
private boolean isPullMediaFileListFromCameraSuccess;
|
||||
private long pullStartTime = 0; // 记录整个拉取流程开始时间
|
||||
private static final int MAX_PULL_DURATION = 25; // 整个拉取流程最多25秒,超时强制关机
|
||||
try {
|
||||
List<MediaFile> data = MediaDataCenter.getInstance().getMediaManager()
|
||||
.getMediaFileListData().getData();
|
||||
LogUtil.log(TAG, "【自有方式】SDK返回文件数=" + (data != null ? data.size() : 0)
|
||||
+ " | state=" + MediaDataCenter.getInstance().getMediaManager().getMediaFileListState());
|
||||
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() {
|
||||
// 全局超时检查:防止状态机异常导致无限循环
|
||||
long elapsed = (System.currentTimeMillis() - pullStartTime) / 1000;
|
||||
if (elapsed >= MAX_PULL_DURATION) {
|
||||
LogUtil.log(TAG, "拉取流程总耗时 " + elapsed + "s,超时强制关机");
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("媒体文件拉取超时", 2);
|
||||
disablePlayback();
|
||||
// ★ 防重复post:先取消旧的超时
|
||||
if (pullTimeoutRunnable != null) {
|
||||
mainHandler.removeCallbacks(pullTimeoutRunnable);
|
||||
LogUtil.log(TAG, "【pullList】取消旧超时");
|
||||
}
|
||||
isPulling = true;
|
||||
|
||||
// ★ 定义一个统一的超时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;
|
||||
}
|
||||
|
||||
MediaFileListState currentState = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState();
|
||||
LogUtil.log(TAG, "当前状态:" + currentState + ",准备拉取文件列表(已耗时" + elapsed + "s)");
|
||||
|
||||
if (currentState == MediaFileListState.IDLE) {
|
||||
LogUtil.log(TAG, "状态为IDLE,开始拉取文件列表");
|
||||
MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(new PullMediaFileListParam.Builder().count(-1).build(), new CommonCallbacks.CompletionCallback() {
|
||||
// ★ DJI方式:IDLE 才调 pull,UPDATING 只等状态(严格按 DJI 建议)
|
||||
if (state == MediaFileListState.IDLE) {
|
||||
LogUtil.log(TAG, "【pullList】状态=IDLE → 调用SDK pullMediaFileList");
|
||||
MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(
|
||||
new PullMediaFileListParam.Builder().count(-1).build(),
|
||||
new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "拉取请求已接受,等待状态变为UP_TO_DATE");
|
||||
pullMediaFileListFromCameraFailTimes = 0;
|
||||
if (pullTimeoutRunnable == null) {
|
||||
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
|
||||
public void onFailure(@NonNull IDJIError idjiError) {
|
||||
LogUtil.log(TAG, "拉取请求失败: " + new Gson().toJson(idjiError));
|
||||
if (pullMediaFileListFromCameraFailTimes < 5) {
|
||||
pullMediaFileListFromCameraFailTimes++;
|
||||
LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试...");
|
||||
} else {
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("拉取媒体文件失败",2);
|
||||
disablePlayback();
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
}
|
||||
public void onFailure(@NonNull IDJIError e) {
|
||||
LogUtil.log(TAG, "【pullList】SDK pull onFailure: " + e.description()
|
||||
+ " → 继续等状态监听器或超时");
|
||||
}
|
||||
});
|
||||
// 不依赖回调续命,无条件调度下一轮轮询
|
||||
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;
|
||||
}
|
||||
|
||||
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<>();
|
||||
for (MediaFile mf : rawList) {
|
||||
if (!uploadedFileNames.contains(mf.getFileName())) {
|
||||
mediaFiles.add(mf);
|
||||
} else {
|
||||
LogUtil.log(TAG, "跳过已上传文件: " + mf.getFileName());
|
||||
}
|
||||
}
|
||||
// 修复:在过滤后设置任务媒体计数
|
||||
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()) {
|
||||
LogUtil.log(TAG, "所有文件均已上传,直接清理");
|
||||
downLoadMediaFileIndex = 0;
|
||||
// 提前设置关机标志,让 aircraftStoredReply 能立即回复成功
|
||||
LogUtil.log(TAG, "全部已上传,直接清理");
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
removeAllFiles();
|
||||
return;
|
||||
|
|
@ -335,39 +430,16 @@ public class MediaManager extends BaseManager {
|
|||
pullOriginalMediaFileFromCamera();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
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, "重试次数达到上限,拉取失败");
|
||||
LogUtil.log(TAG, "处理文件列表异常: " + e.getMessage());
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("拉取媒体文件失败",2);
|
||||
sendEvent2Server("处理文件列表失败", 2);
|
||||
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)
|
||||
public void pullOriginalMediaFileFromCamera() {
|
||||
|
|
@ -379,7 +451,6 @@ public class MediaManager extends BaseManager {
|
|||
downLoadMediaFileIndex++;
|
||||
|
||||
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;
|
||||
removeAllFiles();
|
||||
} else {
|
||||
|
|
@ -445,7 +516,7 @@ public class MediaManager extends BaseManager {
|
|||
} catch (IOException error) {
|
||||
LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " error: " + error.getMessage());
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("文件流关闭失败",2);
|
||||
sendEvent2Server("文件流关闭失败", 2);
|
||||
disablePlayback();
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
}
|
||||
|
|
@ -474,7 +545,7 @@ public class MediaManager extends BaseManager {
|
|||
}, 2000);
|
||||
} else {
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("第" + downLoadMediaFileIndex + "个文件下载失败(已重试" + MAX_DOWNLOAD_RETRY + "次)",2);
|
||||
sendEvent2Server("第" + downLoadMediaFileIndex + "个文件下载失败(已重试" + MAX_DOWNLOAD_RETRY + "次)", 2);
|
||||
disablePlayback();
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
}
|
||||
|
|
@ -485,7 +556,7 @@ public class MediaManager extends BaseManager {
|
|||
Log.e(TAG, "Error opening file: " + e.getMessage());
|
||||
// 发生异常时也要确保关机
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("文件打开失败",2);
|
||||
sendEvent2Server("文件打开失败", 2);
|
||||
disablePlayback();
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
// 关闭文件流
|
||||
|
|
@ -498,15 +569,19 @@ public class MediaManager extends BaseManager {
|
|||
}
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* MinIO上传 — 原始逻辑不变
|
||||
* ================================================================= */
|
||||
|
||||
private AmazonS3 s3 = new AmazonS3Client(new AWSCredentials() {
|
||||
@Override
|
||||
public String getAWSAccessKeyId() {
|
||||
return PreferenceUtils.getInstance().getAccessKey(); // minio的key
|
||||
return PreferenceUtils.getInstance().getAccessKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAWSSecretKey() {
|
||||
return PreferenceUtils.getInstance().getSecretKey(); // minio的密钥
|
||||
return PreferenceUtils.getInstance().getSecretKey();
|
||||
}
|
||||
}, Region.getRegion(Regions.US_EAST_1), new ClientConfiguration()
|
||||
.withConnectionTimeout(30000)
|
||||
|
|
@ -520,7 +595,7 @@ public class MediaManager extends BaseManager {
|
|||
@Override
|
||||
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
|
||||
// 服务器地址
|
||||
s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl()); // http://ip:端口号
|
||||
s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl());
|
||||
// Bucket只在首次上传时检查创建,后续上传不再重复请求
|
||||
if (!bucketChecked) {
|
||||
synchronized (this) {
|
||||
|
|
@ -534,17 +609,11 @@ public class MediaManager extends BaseManager {
|
|||
}
|
||||
}
|
||||
|
||||
// 上传文件到网关MINIO存储服务
|
||||
s3.putObject(
|
||||
new PutObjectRequest(
|
||||
PreferenceUtils.getInstance().getBucketName(),
|
||||
"/" + PreferenceUtils.getInstance().getObjectKey() + "/" + mediaFile.getFileName(),
|
||||
file
|
||||
// new PutObjectRequest(
|
||||
// PreferenceUtils.getInstance().getBucketName(),
|
||||
// "/" + PreferenceUtils.getInstance().getObjectKey() + "/" +
|
||||
// PreferenceUtils.getInstance().getFlightId() + "/" + mediaFile.getFileName(),
|
||||
// file
|
||||
).withProgressListener(new ProgressListener() {
|
||||
@Override
|
||||
public void progressChanged(ProgressEvent progressEvent) {
|
||||
|
|
@ -572,7 +641,6 @@ public class MediaManager extends BaseManager {
|
|||
})
|
||||
);
|
||||
|
||||
// 获取文件上传后访问地址url
|
||||
GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(
|
||||
PreferenceUtils.getInstance().getBucketName(),
|
||||
"/" + PreferenceUtils.getInstance().getObjectKey() + "/"
|
||||
|
|
@ -580,7 +648,6 @@ public class MediaManager extends BaseManager {
|
|||
);
|
||||
String url = s3.generatePresignedUrl(urlRequest).toString();
|
||||
|
||||
// 文件上传后访问地址url
|
||||
emitter.onNext(url);
|
||||
emitter.onComplete();
|
||||
}
|
||||
|
|
@ -590,28 +657,23 @@ public class MediaManager extends BaseManager {
|
|||
.subscribe(new Observer<String>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
// Handle on subscribe (optional)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(String url) {
|
||||
// 上传成功,重置下载重试计数
|
||||
downloadFailTimes = 0;
|
||||
//上传完成发送事件
|
||||
sendMediaUpload2Server(mediaFile.getFileName(), downLoadMediaFileIndex + 1, mediaFiles.size());
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
// 每上传失败一张就清除缓存
|
||||
uploadedFileNames.add(mediaFile.getFileName());
|
||||
FileUtil.deleteFile(file);
|
||||
LogUtil.log(TAG, "Error uploading file " + downLoadMediaFileIndex + ": " + e.getMessage());
|
||||
|
||||
downLoadMediaFileIndex++;
|
||||
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
||||
// 所有文件已经下载完成或失败,清空SD卡,缓存,退出媒体模式,发送无人机关机
|
||||
downLoadMediaFileIndex = 0;
|
||||
removeAllFiles();
|
||||
} else {
|
||||
|
|
@ -622,16 +684,14 @@ public class MediaManager extends BaseManager {
|
|||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public void onComplete() {
|
||||
// 每上传一张就清除缓存,并记录已上传文件名
|
||||
uploadedFileNames.add(mediaFile.getFileName());
|
||||
FileUtil.deleteFile(file);
|
||||
LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " uploaded successfully.");
|
||||
sendEvent2Server( "第" + downLoadMediaFileIndex + "个文件已上传",1);
|
||||
sendEvent2Server("第" + downLoadMediaFileIndex + "个文件已上传", 1);
|
||||
|
||||
downLoadMediaFileIndex++;
|
||||
if (downLoadMediaFileIndex == mediaFiles.size()) {
|
||||
// 所有文件已上传完成,清空SD卡,缓存,退出媒体模式,发送无人机关机
|
||||
sendEvent2Server( "媒体文件已上传完毕",1);
|
||||
sendEvent2Server("媒体文件已上传完毕", 1);
|
||||
removeAllFiles();
|
||||
downLoadMediaFileIndex = 0;
|
||||
} else {
|
||||
|
|
@ -641,12 +701,15 @@ public class MediaManager extends BaseManager {
|
|||
});
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 删除文件 / 退出媒体模式
|
||||
* ================================================================= */
|
||||
|
||||
public void removeAllFiles() {
|
||||
// 确保即使没有文件也能正常关机
|
||||
if (mediaFiles == null || mediaFiles.isEmpty()) {
|
||||
LogUtil.log(TAG, "没有文件需要清除,直接关机");
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
sendEvent2Server("没有媒体文件需要清除",1);
|
||||
sendEvent2Server("没有媒体文件需要清除", 1);
|
||||
disablePlayback();
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
return;
|
||||
|
|
@ -657,7 +720,7 @@ public class MediaManager extends BaseManager {
|
|||
public void onSuccess() {
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
LogUtil.log(TAG, "清除文件成功 ");
|
||||
sendEvent2Server("媒体文件已清除",1);
|
||||
sendEvent2Server("媒体文件已清除", 1);
|
||||
disablePlayback();
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
}
|
||||
|
|
@ -666,16 +729,13 @@ public class MediaManager extends BaseManager {
|
|||
public void onFailure(@NonNull IDJIError idjiError) {
|
||||
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
|
||||
LogUtil.log(TAG, "清除文件失败: " + new Gson().toJson(idjiError));
|
||||
sendEvent2Server("媒体文件清除失败",2);
|
||||
sendEvent2Server("媒体文件清除失败", 2);
|
||||
LogUtil.log(TAG, "发送关闭无人机");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//退出媒体模式
|
||||
public void disablePlayback() {
|
||||
// 任务结束,停止视频流刷新定时器
|
||||
// StreamManager.getInstance().stopStreamRefreshTimer();
|
||||
MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
|
|
@ -684,27 +744,65 @@ public class MediaManager extends BaseManager {
|
|||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError idjiError) {
|
||||
LogUtil.log(TAG, "退出媒体模式失败:"+new Gson().toJson(idjiError));
|
||||
LogUtil.log(TAG, "退出媒体模式失败:" + new Gson().toJson(idjiError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 String getSDCardPath(){
|
||||
private String getSDCardPath() {
|
||||
if (checkSDCard()) {
|
||||
return Environment.getExternalStorageDirectory()
|
||||
.getPath();
|
||||
return Environment.getExternalStorageDirectory().getPath();
|
||||
} else {
|
||||
return Environment.getExternalStorageDirectory()
|
||||
.getParentFile().getPath();
|
||||
return Environment.getExternalStorageDirectory().getParentFile().getPath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean checkSDCard() {
|
||||
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.MessageDown;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.entity.ParsedWaypoint;
|
||||
import com.aros.apron.entity.Synchronizedstatus;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PreferenceUtils;
|
||||
import com.aros.apron.tools.RestartAPPTool;
|
||||
import com.aros.apron.tools.TakeoffProgressScheduler;
|
||||
import com.aros.apron.tools.Utils;
|
||||
import com.dji.wpmzsdk.common.data.KMZInfo;
|
||||
import com.dji.wpmzsdk.manager.WPMZManager;
|
||||
import com.google.gson.Gson;
|
||||
|
|
@ -33,6 +35,7 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dji.sdk.keyvalue.key.CameraKey;
|
||||
|
|
@ -821,6 +824,13 @@ public class MissionV3Manager extends BaseManager {
|
|||
if (kmzInfo != null) {
|
||||
// Utils.printJson(TAG,"航点详情:"+new Gson().toJson(kmzInfo));
|
||||
WaylineWaylinesParseInfo waylineWaylinesParseInfo = kmzInfo.getWaylineWaylinesParseInfo();
|
||||
|
||||
|
||||
|
||||
//LogUtil.log(TAG,"航点详情:"+new Gson().toJson(kmzInfo));
|
||||
//LogUtil.log(TAG,"航点详情:"+waylineWaylinesParseInfo.getWaylines());
|
||||
|
||||
//解析航点
|
||||
if (waylineWaylinesParseInfo != null) {
|
||||
List<Wayline> waylines = waylineWaylinesParseInfo.getWaylines();
|
||||
if (waylines != null && waylines.size() > 0) {
|
||||
|
|
@ -828,6 +838,95 @@ public class MissionV3Manager extends BaseManager {
|
|||
if (waypoints != null && waypoints.size() > 0) {
|
||||
CurrentWayline.getInstance().setWaypoints(waypoints);
|
||||
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 {
|
||||
LogUtil.log(TAG, "WPMZManager getWaypointInfo有误");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
|
@ -16,8 +14,7 @@ import com.aros.apron.tools.PreferenceUtils;
|
|||
import com.aros.apron.tools.SimplePortScanner;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import dji.sdk.keyvalue.key.CameraKey;
|
||||
import dji.sdk.keyvalue.key.DJIKey;
|
||||
|
|
@ -44,8 +41,6 @@ import dji.v5.manager.interfaces.ILiveStreamManager;
|
|||
public class StreamManager extends BaseManager {
|
||||
private static final String TAG = "StreamManager";
|
||||
|
||||
// ========== 【新增】线程池和主线程 Handler,防止 ANR ==========
|
||||
private final ExecutorService streamExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// ========== 5秒定时刷新视频流,防止起飞卡死 ==========
|
||||
|
|
@ -110,48 +105,66 @@ public class StreamManager extends BaseManager {
|
|||
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() {
|
||||
//stopStreamRefreshTimer();
|
||||
// SimplePortScanner.getInstance().stopScan(); // 同时停止端口扫描
|
||||
mainHandler.removeCallbacksAndMessages(null); // 清理所有待执行的回调
|
||||
startLiveFailTimes = 0;
|
||||
isLiveStreamAlreadyStart = false;
|
||||
isStartingRTSP = false;
|
||||
isStartingRTSP.set(false);
|
||||
LogUtil.log(TAG, "推流状态已重置");
|
||||
}
|
||||
|
||||
public void stopstream() {
|
||||
streamExecutor.execute(() -> {
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
liveStreamManager.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "直播关闭成功");
|
||||
LogUtil.log(TAG, "直播关闭成功,重置状态");
|
||||
isStartingRTSP.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError idjiError) {
|
||||
LogUtil.log(TAG, "直播关闭失败");
|
||||
public void onFailure(@NonNull IDJIError e) {
|
||||
LogUtil.log(TAG, "直播关闭失败:" + e.description());
|
||||
isStartingRTSP.set(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void startstream() {
|
||||
streamExecutor.execute(() -> {
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {
|
||||
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
mgr.startStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "直播开启成功");
|
||||
}
|
||||
|
||||
public void onSuccess() { LogUtil.log(TAG, "直播开启成功"); }
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError idjiError) {
|
||||
LogUtil.log(TAG, "直播开启成功失败");
|
||||
}
|
||||
});
|
||||
public void onFailure(@NonNull IDJIError e) { LogUtil.log(TAG, "直播开启失败"); }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +177,8 @@ public class StreamManager extends BaseManager {
|
|||
public void onLiveStreamStatusUpdate(LiveStreamStatus status) {
|
||||
if (status != null) {
|
||||
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 boolean isLiveStreamAlreadyStart;
|
||||
private volatile boolean isStartingRTSP = false; // 防止并发调用
|
||||
// ★ 用 AtomicBoolean 替代 volatile boolean,CAS 防并发,不会卡死
|
||||
private final AtomicBoolean isStartingRTSP = new AtomicBoolean(false);
|
||||
private final AtomicBoolean isStartingRTMP = new AtomicBoolean(false);
|
||||
|
||||
// 无限重试:指数退避控制
|
||||
private static final long RETRY_BASE_MS = 3000; // 初始 3 秒
|
||||
|
|
@ -214,7 +228,6 @@ public class StreamManager extends BaseManager {
|
|||
|
||||
// 知眸测试
|
||||
public void startLiveWithCustom() {
|
||||
streamExecutor.execute(() -> {
|
||||
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(ProductKey.KeyConnection));
|
||||
if (isAircraftConnected == null || !isAircraftConnected) {
|
||||
LogUtil.log(TAG, "飞行器未连接");
|
||||
|
|
@ -236,9 +249,8 @@ public class StreamManager extends BaseManager {
|
|||
});
|
||||
|
||||
if (!liveStreamManager.isStreaming()) {
|
||||
doStartLiveCustom(liveStreamManager);
|
||||
mainHandler.postDelayed(() -> doStartLiveCustom(liveStreamManager), 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doStartLiveCustom(ILiveStreamManager liveStreamManager) {
|
||||
|
|
@ -247,7 +259,6 @@ public class StreamManager extends BaseManager {
|
|||
public void onSuccess() {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "自定义推流启动成功");
|
||||
isLiveStreamAlreadyStart = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -255,16 +266,16 @@ public class StreamManager extends BaseManager {
|
|||
public void onFailure(@NonNull IDJIError error) {
|
||||
mainHandler.post(() -> {
|
||||
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);
|
||||
startLiveFailTimes++;
|
||||
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
||||
LogUtil.log(TAG, "自定义推流准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
|
||||
}
|
||||
isStartingRTSP = false;
|
||||
isLiveStreamAlreadyStart = false;
|
||||
isStartingRTSP.set(false);
|
||||
mainHandler.postDelayed(() -> {
|
||||
streamExecutor.execute(() -> startLiveWithCustom());
|
||||
startLiveWithCustom();
|
||||
}, 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){
|
||||
sendMsg2Server(message);
|
||||
/**
|
||||
* MQTT 收到 rtmp_push 协议后调用:先停止当前推流,再启动 RTMP 推流
|
||||
* @param message MQTT 下发的消息,data.rtmp_url 为 RTMP 推流地址
|
||||
*/
|
||||
public void startLiveWithRTMP(MessageDown message) {
|
||||
|
||||
// CAS 防并发
|
||||
if (!isStartingRTMP.compareAndSet(false, true)) {
|
||||
LogUtil.log(TAG, "startLiveWithRTMP 正在执行中,忽略本次调用");
|
||||
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);
|
||||
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)
|
||||
.setRtspSettings(new RtspSettings.Builder().setPassWord(PreferenceUtils.getInstance().getRtspPassWord()).
|
||||
setPort(Integer.parseInt(PreferenceUtils.getInstance().getRtspPort())).
|
||||
setUserName(PreferenceUtils.getInstance().getRtspUserName()).build()).build();
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError e) {
|
||||
LogUtil.log(TAG, "停流失败,仍尝试启动 RTMP: " + e.description());
|
||||
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);
|
||||
liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
||||
LogUtil.log(TAG, "RTMP 配置完成,3s 后启动推流");
|
||||
|
||||
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) {
|
||||
if(isliveindex==1){
|
||||
sendMsg2Server(message);
|
||||
return;
|
||||
}
|
||||
if (isliveindex == 1) { sendMsg2Server(message); return; }
|
||||
isliveindex = 1;
|
||||
LogUtil.log(TAG, "切换推流到 PORT_1");
|
||||
MediaDataCenter.getInstance().getLiveStreamManager().setCameraIndex(ComponentIndexType.PORT_1);
|
||||
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() {
|
||||
// 防止并发调用
|
||||
if (isStartingRTSP) {
|
||||
// ★ CAS 防并发:只有一个线程能拿到 true,其他被拦截
|
||||
if (!isStartingRTSP.compareAndSet(false, true)) {
|
||||
LogUtil.log(TAG, "startLiveWithRTSP 正在执行中,忽略本次调用");
|
||||
return;
|
||||
}
|
||||
|
||||
streamExecutor.execute(() -> {
|
||||
isStartingRTSP = true;
|
||||
// ★ RTMP 推流正在进行中,不再启动 RTSP,防止覆盖
|
||||
if (isStartingRTMP.get()) {
|
||||
LogUtil.log(TAG, "RTMP 推流正在进行中,取消 RTSP 启动");
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (startLiveFailTimes == 0) {
|
||||
isLiveStreamAlreadyStart = false;
|
||||
LogUtil.log(TAG, "========== 开始 RTSP 推流流程 ==========");
|
||||
}
|
||||
|
||||
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection));
|
||||
if (isAircraftConnected == null || !isAircraftConnected) {
|
||||
LogUtil.log(TAG, "飞行器未连接");
|
||||
isStartingRTSP = false;
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
if (liveStreamManager == null) {
|
||||
ILiveStreamManager mgr = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
if (mgr == null) {
|
||||
LogUtil.log(TAG, "LiveStreamManager 为 null");
|
||||
isStartingRTSP = false;
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (liveStreamManager.isStreaming()) {
|
||||
LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动");
|
||||
isLiveStreamAlreadyStart = true;
|
||||
isStartingRTSP = false;
|
||||
// ★ 用 SDK 真实状态判断是否已在推流
|
||||
if (mgr.isStreaming()) {
|
||||
LogUtil.log(TAG, "RTSP 推流已在运行(SDK状态),无需重复启动");
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MainActivity.Companion.getStreamReceive()) {
|
||||
LogUtil.log(TAG, "相机流未准备好,尝试模拟点击 FPV Widget 恢复");
|
||||
mainHandler.post(() -> {
|
||||
MainActivity mainActivity = MainActivity.Companion.getInstance();
|
||||
if (mainActivity != null) {
|
||||
mainActivity.smartRefreshVideoStream();
|
||||
}
|
||||
});
|
||||
|
||||
mainHandler.postDelayed(() -> {
|
||||
streamExecutor.execute(() -> {
|
||||
LogUtil.log(TAG, "相机流未准备好,3s 后重试");
|
||||
startLiveFailTimes++;
|
||||
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
||||
LogUtil.log(TAG, "相机流未准备好,第" + startLiveFailTimes + "次重试");
|
||||
}
|
||||
startLiveWithRTSP();
|
||||
});
|
||||
}, 2000);
|
||||
isStartingRTSP = false; // 释放锁,让重试能正常进入
|
||||
isStartingRTSP.set(false);
|
||||
// ★ 重试前检查 RTMP 是否已接管
|
||||
mainHandler.postDelayed(() -> {
|
||||
if (isStartingRTMP.get()) {
|
||||
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 重试(相机流就绪等待)");
|
||||
return;
|
||||
}
|
||||
startLiveWithRTSP();
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String rtspUser = PreferenceUtils.getInstance().getRtspUserName();
|
||||
String rtspPort = PreferenceUtils.getInstance().getRtspPort();
|
||||
String rtspPass = PreferenceUtils.getInstance().getRtspPassWord();
|
||||
|
||||
if (rtspUser == null || rtspPort == null || rtspPass == null ||
|
||||
rtspUser.isEmpty() || rtspPort.isEmpty() || rtspPass.isEmpty()) {
|
||||
LogUtil.log(TAG, "RTSP 配置参数有误:user=" + rtspUser + ", port=" + rtspPort);
|
||||
isStartingRTSP = false;
|
||||
LogUtil.log(TAG, "RTSP 配置参数有误");
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG, "RTSP 配置检查通过:user=" + rtspUser + ", port=" + rtspPort + ", camera=" + (isliveindex == 1 ? "PORT_1" : "FPV"));
|
||||
LogUtil.log(TAG, "RTSP 配置检查通过:user=" + rtspUser + ", port=" + rtspPort);
|
||||
|
||||
//等待相机模式稳定(避免刚切换模式就推流)
|
||||
try {
|
||||
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)
|
||||
LiveStreamSettings streamSettings = new LiveStreamSettings.Builder()
|
||||
.setLiveStreamType(LiveStreamType.RTSP)
|
||||
.setRtspSettings(new RtspSettings.Builder()
|
||||
.setPassWord(rtspPass)
|
||||
.setPort(Integer.parseInt(rtspPort))
|
||||
.setUserName(rtspUser)
|
||||
.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);
|
||||
|
||||
// 设置相机源
|
||||
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 后启动推流");
|
||||
|
||||
LogUtil.log(TAG, "参数设置完成,3s 后启动推流");
|
||||
mainHandler.postDelayed(() -> {
|
||||
streamExecutor.execute(() -> {
|
||||
// 9. 启动推流前再次检查
|
||||
if (finalLiveStreamManager.isStreaming()) {
|
||||
LogUtil.log(TAG, "推流已在运行,跳过启动");
|
||||
isLiveStreamAlreadyStart = true;
|
||||
isStartingRTSP = false;
|
||||
// ★ RTMP 已接管,取消 RTSP 启动
|
||||
if (isStartingRTMP.get()) {
|
||||
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 启动(3s 等待期间 RTMP 介入)");
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
// ★ 启动前再次用 SDK 状态确认
|
||||
if (mgr.isStreaming()) {
|
||||
LogUtil.log(TAG, "SDK显示已在推流,跳过");
|
||||
isStartingRTSP.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG, "开始调用 startStream...");
|
||||
doStartLiveWithRTSP(finalLiveStreamManager, false);
|
||||
});
|
||||
}, 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() {
|
||||
mgr.startStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "自定义 RTSP 推流启动成功" + (isRestart ? "(重启)" : ""));
|
||||
isliveindex = 1;
|
||||
isLiveStreamAlreadyStart = true;
|
||||
startLiveFailTimes = 0; // 重置失败计数
|
||||
isStartingRTSP = false; // 重置并发标志
|
||||
LogUtil.log(TAG, "RTSP 推流启动成功");
|
||||
startLiveFailTimes = 0;
|
||||
isStartingRTSP.set(false);
|
||||
LogUtil.log(TAG, "========== RTSP 推流启动成功 ==========");
|
||||
// 启动5秒定时刷新视频流,防止起飞卡死
|
||||
// startStreamRefreshTimer();
|
||||
// 开始端口扫描
|
||||
// SimplePortScanner.getInstance().startScan();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
mainHandler.post(() -> {
|
||||
String detailedError = "第" + startLiveFailTimes + "次开始 RTSP 推流失败:type=" + error.errorType()+ ", code=" + error.errorCode() + ", hint=" + error.hint();
|
||||
LogUtil.log(TAG, detailedError);
|
||||
LogUtil.log(TAG, "完整错误:" + new Gson().toJson(error));
|
||||
LogUtil.log(TAG, "第" + startLiveFailTimes + "次 RTSP 推流失败:" + error.description());
|
||||
|
||||
if (!isLiveStreamAlreadyStart) {
|
||||
// 计算指数退避间隔:3s → 6s → 12s → 24s → 30s(封顶)
|
||||
// ★ 用 SDK 真实状态判断是否要重试
|
||||
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);
|
||||
startLiveFailTimes++;
|
||||
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
|
||||
LogUtil.log(TAG, "准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
|
||||
}
|
||||
isStartingRTSP.set(false);
|
||||
|
||||
// 重置所有状态,让下次重试是干净的
|
||||
isStartingRTSP = false;
|
||||
isLiveStreamAlreadyStart = false;
|
||||
stopstream(); // 先关再开,避免端口占用
|
||||
|
||||
// 指数退避重试
|
||||
// 停流 → 等回调 → 重试
|
||||
mgr.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "重试前停流成功," + retryDelay + "ms 后重试");
|
||||
// ★ 重试前再次检查 RTMP 状态
|
||||
mainHandler.postDelayed(() -> {
|
||||
if (isStartingRTMP.get()) {
|
||||
LogUtil.log(TAG, "RTMP 已接管,取消 RTSP 重试");
|
||||
return;
|
||||
}
|
||||
startLiveWithRTSP();
|
||||
}, 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 {
|
||||
isStartingRTSP = false;
|
||||
}
|
||||
});
|
||||
isStartingRTSP.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ public class Aprondown {
|
|||
}
|
||||
} else {
|
||||
LogUtil.log(TAG_LOG, "执行位移");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.5f);
|
||||
}
|
||||
} else if (lostDuration > 8000) {
|
||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
|
|
@ -570,6 +570,30 @@ public class Aprondown {
|
|||
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) {
|
||||
double dxTop = pts[1].x - pts[0].x;
|
||||
|
|
@ -702,7 +726,7 @@ public class Aprondown {
|
|||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 15) {
|
||||
if (handlerCallbackCount < 18) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
performNextStep();
|
||||
|
|
|
|||
|
|
@ -1035,7 +1035,7 @@ public class Aprongim {
|
|||
} else {
|
||||
// 高空丢失:下降寻找
|
||||
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) {
|
||||
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 Handler handler = new Handler(Looper.getMainLooper());
|
||||
private Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 15) {
|
||||
if (handlerCallbackCount < 18) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
performNextStep();
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class Apronmixvalue {
|
|||
Aprondown.getInstance().setDropTimes(0);
|
||||
|
||||
// 如果高度已经大于 40 分米,直接切换,不用上升
|
||||
if (Movement.getInstance().getUltrasonicHeight() >= 40) {
|
||||
if (Movement.getInstance().getUltrasonicHeight() >= 40 || Movement.getInstance().getElevation()>4) {
|
||||
Synchronizedstatus.setSwitchtime(true);
|
||||
return;
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ public class Apronmixvalue {
|
|||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Movement.getInstance().getUltrasonicHeight() < 50) {
|
||||
if (Movement.getInstance().getUltrasonicHeight() < 50 && Movement.getInstance().getElevation() <5) {
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||
handler.postDelayed(this, 200);
|
||||
} else {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -311,7 +311,7 @@ public class ApronArucoDetect {
|
|||
}
|
||||
} else {
|
||||
LogUtil.log(TAG_LOG, "执行位移");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.5f);
|
||||
}
|
||||
|
||||
isHeightStableMonitoring = false;
|
||||
|
|
@ -390,23 +390,21 @@ public class ApronArucoDetect {
|
|||
dropTimesTag = true;
|
||||
|
||||
} else {
|
||||
// 原有丢失处理
|
||||
LogUtil.log(TAG_LOG, "找不到了二维码");
|
||||
// 丢失处理
|
||||
int lostUltHeight = Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
// 【新增】识别失败截图保存
|
||||
if (saveFailScreenshot) {
|
||||
saveFailScreenshot(grayImgMat, width, height);
|
||||
|
||||
}
|
||||
if (!arucoNotFoundTag) {
|
||||
startTime = System.currentTimeMillis();
|
||||
arucoNotFoundTag = true;
|
||||
LogUtil.log(TAG_LOG, String.format("【丢失开始】ult=%d dropTimes=%d", lostUltHeight, dropTimes));
|
||||
}
|
||||
endTime = System.currentTimeMillis();
|
||||
long lostDuration = endTime - startTime;
|
||||
|
||||
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);
|
||||
if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) {
|
||||
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
||||
|
|
@ -420,11 +418,11 @@ public class ApronArucoDetect {
|
|||
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
} else if (lostDuration > 8000) {
|
||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
LogUtil.log(TAG_LOG, String.format("【丢失超时】触发备降 丢失=%dms", lostDuration));
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
}
|
||||
|
|
@ -697,6 +695,30 @@ public class ApronArucoDetect {
|
|||
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) {
|
||||
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 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) +
|
||||
Math.pow(pts[1].y - pts[0].y, 2));
|
||||
|
||||
|
|
@ -780,31 +809,12 @@ public class ApronArucoDetect {
|
|||
float vz;
|
||||
if (currentHeight <= 4) {
|
||||
vz = 0.0f;
|
||||
if (Math.abs(errX) > 120) {
|
||||
vx = rawVx > 0 ? 0.135f : -0.135f;
|
||||
} else if (Math.abs(errX) > 80) {
|
||||
vx = rawVx > 0 ? 0.09f : -0.09f;
|
||||
} else if (Math.abs(errX) > 60) {
|
||||
vx = rawVx > 0 ? 0.07f : -0.07f;
|
||||
} 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; // 【修正】
|
||||
}
|
||||
|
||||
// 【修复】低空改用 PID 连续输出 + 低速度上限,去掉阶梯式开关控制
|
||||
// 之前 bang-bang 固定档位导致来回震荡(0.05→0.09→0.135→-0.09...)
|
||||
// 现在用 PID 平滑输出,上限压到 0.06m/s,避免低空猛冲晃出二维码范围
|
||||
float lowMaxSpeed = 0.06f;
|
||||
vx = (float) Math.max(-lowMaxSpeed, Math.min(lowMaxSpeed, rawVx));
|
||||
vy = (float) Math.max(-lowMaxSpeed, Math.min(lowMaxSpeed, rawVy));
|
||||
} else if (currentHeight <= 8) {
|
||||
vz = SLOW_SUPER_SPEED;
|
||||
} else {
|
||||
|
|
@ -813,8 +823,11 @@ public class ApronArucoDetect {
|
|||
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, yawRate, vz);
|
||||
|
||||
LogUtil.log(TAG_LOG, "vx" + vx + "vy" + vy + " errX=" + (int) errX + " errY=" + (int) errY +
|
||||
" pixelW=" + (int) pixelWidth + " vz=" + vz + " ult=" + currentHeight + " yaw=" + yawRate);
|
||||
LogUtil.log(TAG_LOG, String.format(
|
||||
"【摇杆下发】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;
|
||||
|
|
@ -823,7 +836,7 @@ public class ApronArucoDetect {
|
|||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 15) {
|
||||
if (handlerCallbackCount < 18) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
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 Handler handler = new Handler(Looper.getMainLooper());
|
||||
private Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 15) {
|
||||
if (handlerCallbackCount < 18) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
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) {
|
||||
double dxTop = pts[1].x - pts[0].x;
|
||||
|
|
|
|||
|
|
@ -40,10 +40,26 @@ public class MqttManager {
|
|||
}
|
||||
|
||||
public void needConnect() {
|
||||
// 已经连接就不要再建新的,避免生成新handle导致旧handle失效
|
||||
if (mqttAndroidClient != null && mqttAndroidClient.isConnected()) {
|
||||
LogUtil.log(TAG, "MQTT已连接,跳过重复needConnect");
|
||||
return;
|
||||
}
|
||||
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.setAutomaticReconnect(true);
|
||||
mMqttConnectOptions.setMaxInflight(1000);// 避免消息积压导致连接拥塞
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public class PsdkWidgetScheduler {
|
|||
report.setData(data);
|
||||
|
||||
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.setQos(1);
|
||||
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;
|
||||
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_height="100dp"
|
||||
/>
|
||||
<Button
|
||||
android:id="@+id/btn_test3"
|
||||
android:text="DJI急停"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -315,6 +315,12 @@
|
|||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
/>
|
||||
<Button
|
||||
android:id="@+id/btn_test3"
|
||||
android:text="DJI急停"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue