diff --git a/app/src/main/java/com/aros/apron/activity/MainActivity.kt b/app/src/main/java/com/aros/apron/activity/MainActivity.kt index d5eab416..a44347c5 100644 --- a/app/src/main/java/com/aros/apron/activity/MainActivity.kt +++ b/app/src/main/java/com/aros/apron/activity/MainActivity.kt @@ -245,62 +245,64 @@ open class MainActivity : BaseActivity() { override fun onResume() { super.onResume() -// while (!OpenCVLoader.initLocal()) { -// LogUtil.log("qwq", "opencv 初始化失败,阻塞等待重试...") -// try { -// Thread.sleep(300) // 休眠 300ms 再试,防止 CPU 爆炸 -// } catch (e: InterruptedException) { -// LogUtil.log("qwq", "初始化被中断,跳出循环") -// break -// } -// } -// LogUtil.log("qwq", "opencv 初始化完成,继续执行后续代码") - dictionary = Objdetect.getPredefinedDictionary(Objdetect.DICT_6X6_250) compositeDisposable = CompositeDisposable() - compositeDisposable!!.add( - systemStatusListPanelWidget!!.closeButtonPressed() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { pressed: Boolean -> - if (pressed) { - systemStatusListPanelWidget!!.hide() - } - }) - compositeDisposable!!.add( - simulatorControlWidget!!.getUIStateUpdates() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { simulatorControlWidgetState: SimulatorControlWidget.UIState? -> - if (simulatorControlWidgetState is VisibilityUpdated) { - if ((simulatorControlWidgetState as VisibilityUpdated).isVisible) { - hideOtherPanels(simulatorControlWidget!!) + // 延迟订阅 RxJava 流,避免在初始 layout 阶段触发大量 widget 更新导致 ANR + Handler(Looper.getMainLooper()).postDelayed({ + if (compositeDisposable == null) return@postDelayed + compositeDisposable!!.add( + systemStatusListPanelWidget!!.closeButtonPressed() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { pressed: Boolean -> + if (pressed) { + systemStatusListPanelWidget!!.hide() } - } - }) - compositeDisposable!!.add( - cameraSourceProcessor.toFlowable() - .observeOn(io()) - .throttleLast(500, TimeUnit.MILLISECONDS) - .subscribeOn(io()) - .subscribe(Consumer { result: CameraSource -> - runOnUiThread { onCameraSourceUpdated(result.devicePosition, result.lensType) } - }) - ) - compositeDisposable!!.add( - ObservableInMemoryKeyedStore.getInstance() - .addObserver(UXKeys.create(GlobalPreferenceKeys.GIMBAL_ADJUST_CLICKED)) - .observeOn(ui()) - .subscribe { broadcastValues: BroadcastValues? -> - isGimableAdjustClicked( - broadcastValues!! - ) - }) + }) + compositeDisposable!!.add( + simulatorControlWidget!!.getUIStateUpdates() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { simulatorControlWidgetState: SimulatorControlWidget.UIState? -> + if (simulatorControlWidgetState is VisibilityUpdated) { + if ((simulatorControlWidgetState as VisibilityUpdated).isVisible) { + hideOtherPanels(simulatorControlWidget!!) + } + } + }) + compositeDisposable!!.add( + cameraSourceProcessor.toFlowable() + .observeOn(io()) + .throttleLast(500, TimeUnit.MILLISECONDS) + .subscribeOn(io()) + .subscribe(Consumer { result: CameraSource -> + runOnUiThread { onCameraSourceUpdated(result.devicePosition, result.lensType) } + }) + ) + compositeDisposable!!.add( + ObservableInMemoryKeyedStore.getInstance() + .addObserver(UXKeys.create(GlobalPreferenceKeys.GIMBAL_ADJUST_CLICKED)) + .observeOn(ui()) + .subscribe { broadcastValues: BroadcastValues? -> + isGimableAdjustClicked( + broadcastValues!! + ) + }) + }, 500) ViewUtil.setKeepScreen(this, true) } override fun onPause() { - if (compositeDisposable != null) { - compositeDisposable!!.dispose() + // 在后台线程 dispose RxJava 订阅,避免阻塞主线程导致 ANR + // DJI UX widget 的 unregisterReactions() 会同步清理 RxJava 队列,可能耗时很长 + val disposable = compositeDisposable + if (disposable != null && !disposable.isDisposed) { + Thread { + try { + disposable.dispose() + } catch (e: Exception) { + LogUtil.log(TAG, "dispose compositeDisposable error: ${e.message}") + } + }.start() compositeDisposable = null } super.onPause() @@ -471,6 +473,43 @@ open class MainActivity : BaseActivity() { } } + /** + * 强制刷新两个FPVWidget的视频流(重新向MediaDataCenter注册) + * 用于起飞/航线执行后视频流卡死的场景 + */ + fun refreshVideoStream() { + val primarySource = primaryFpvWidget?.widgetModel?.getCameraIndex() + val secondarySource = secondaryFPVWidget?.widgetModel?.getCameraIndex() + if (primarySource != null && primarySource != ComponentIndexType.UNKNOWN) { + primaryFpvWidget?.updateVideoSource(primarySource) + } + if (secondarySource != null && secondarySource != ComponentIndexType.UNKNOWN) { + secondaryFPVWidget?.updateVideoSource(secondarySource) + } + LogUtil.log(TAG, "强制刷新视频流: primary=$primarySource, secondary=$secondarySource") + } + + /** + * 智能刷新视频流:有云台(secondary)就模拟点击切换,没有就直接刷新当前流 + * 兼容只有FPV没有云台的设备 + */ + fun smartRefreshVideoStream() { + val secondarySource = secondaryFPVWidget?.widgetModel?.getCameraIndex() + if (secondarySource != null && secondarySource != ComponentIndexType.UNKNOWN) { + // 有云台:模拟真实点击,触发 swapVideoSource() + secondaryFPVWidget?.post { + secondaryFPVWidget?.performClick() + } + LogUtil.log(TAG, "智能刷新:有云台,模拟点击 FPV Widget") + } else { + // 没有云台:直接刷新当前流 + primaryFpvWidget?.post { + refreshVideoStream() + } + LogUtil.log(TAG, "智能刷新:无云台,直接刷新主视频流") + } + } + private fun updateInteractionEnabled() { fpvInteractionWidget!!.isInteractionEnabled = !CameraUtil.isFPVTypeView( primaryFpvWidget!!.widgetModel.getCameraIndex() @@ -514,16 +553,13 @@ open class MainActivity : BaseActivity() { WPMZManager.getInstance().init(this) MqttManager.getInstance().needConnect() - initDJIManager() -// //开启相机图传 -// enableStream() -// if(PreferenceUtils.getInstance().cameraLocationType==3){ -// initFpvStream() -// }else{ -// initCameraStream() -// } initView() + // 后台线程初始化 DJI 相关模块,避免阻塞主线程导致 ANR + Thread { + initDJIManager() + }.start() + } @@ -683,213 +719,181 @@ open class MainActivity : BaseActivity() { DJINetworkManager.getInstance().removeNetworkStatusListener(networkStatusListener) } - private val handler: Handler = Handler(Looper.getMainLooper()) private var initTimes = 0 private fun initDJIManager() { - val isFlightControllerConnect = + // 在后台线程中轮询等待飞控/相机连接,避免阻塞主线程 + var isFlightControllerConnect = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection)) - val isCameraConnect = + var isCameraConnect = KeyManager.getInstance() .getValue(KeyTools.createKey(CameraKey.KeyConnection, ComponentIndexType.PORT_1)) - // LogUtil.log(TAG,isCameraConnect.toString()) - if (PreferenceUtils.getInstance().cameraLocationType == 3) { - if (isFlightControllerConnect == null || isFlightControllerConnect != true) { - handler.postDelayed({ - initDJIManager() - }, 1000) - } else { - initTimes++ - LogUtil.log(TAG, "下视觉初始化$initTimes") - cameraManager.setKeepAliveDecoding(true); - FlightManager.getInstance().initFlightInfo() - BatteryManager.getInstance().initBatteryInfo() - StickManager.getInstance().initStickInfo() - GimbalManager.getInstance().initGimbalInfo() - AlternateLandingManager.getInstance().initAlterLandingInfo() - OSDManager.getInstance().initOsd() - FlightTaskProgressManager.getInstance().initFlightTaskProgress() - MediaManager.getInstance().init() - StreamManager.getInstance().initStreamManager() - LEDsSettingsManager.getInstance().initLEDsInfo() - RTKManager.getInstance().initRTKInfo() - //负载 - //PayloadWidgetManager.getInstance().initPayloadInfo() - //初始化上报 - NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() - NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() + val cameraLocationType = PreferenceUtils.getInstance().cameraLocationType + // 根据 cameraLocationType 轮询等待连接 + while (true) { + val flightConnected = isFlightControllerConnect != null && isFlightControllerConnect == true + val cameraConnected = isCameraConnect != null && isCameraConnect == true - LTEManager.getInstance().initLTEInfo() - WirelessLinkManager.getInstance().initWirelessLink() - CameraManager.getInstance().initCameraInfo() - //ApronArucoDetect.getInstance().init() - GeoidManager.getInstance().init(this); - - MissionV3Manager.getInstance().initMissionManager() - enableStream() - initFpvStream() - startVtxHeartbeat() - SpeakerManager.getInstance().initMegaphoneInfo() - - GimbalManager.getInstance().setmode() - - - //GimbalManager.getInstance().gimsetmode() - //PayloadWidgetManager.getInstance().initPayloadInfo(); - - - LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) - - - - - Handler(Looper.getMainLooper()).postDelayed({ - when (PreferenceUtils.getInstance().customStreamType) { - 1 -> StreamManager.getInstance().startLiveWithRTSP() - 2 -> StreamManager.getInstance().startLiveWithCustom() - else -> StreamManager.getInstance().startLiveWithCustom() - } - SimplePortScanner.getInstance().startScan() - - }, 5000) // 参数别改:5秒延迟确保相机就绪 - - LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType) + val shouldInit = when (cameraLocationType) { + 3 -> flightConnected + 4, 5 -> flightConnected || cameraConnected + else -> flightConnected || cameraConnected } - } else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) { - if ((isFlightControllerConnect == null || isFlightControllerConnect != true) && (isCameraConnect == null || isCameraConnect != true)) { - handler.postDelayed({ - initDJIManager() - }, 1000) - } else { - initTimes++ - cameraManager.setKeepAliveDecoding(true); - LogUtil.log(TAG, "云台初始化$initTimes") - FlightManager.getInstance().initFlightInfo() - BatteryManager.getInstance().initBatteryInfo() - StickManager.getInstance().initStickInfo() - GimbalManager.getInstance().initGimbalInfo() - AlternateLandingManager.getInstance().initAlterLandingInfo() - OSDManager.getInstance().initOsd() - FlightTaskProgressManager.getInstance().initFlightTaskProgress() - MediaManager.getInstance().init() - StreamManager.getInstance().initStreamManager() - LEDsSettingsManager.getInstance().initLEDsInfo() - RTKManager.getInstance().initRTKInfo() - //负载 - //PayloadWidgetManager.getInstance().initPayloadInfo() - //初始化上报 - LTEManager.getInstance().initLTEInfo() - WirelessLinkManager.getInstance().initWirelessLink() - CameraManager.getInstance().initCameraInfo() - // + if (shouldInit) break - - NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() - NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() - - //ApronArucoDetect.getInstance().init() - MissionV3Manager.getInstance().initMissionManager() - enableStream() - initMixedStream() - startVtxHeartbeat() - - SpeakerManager.getInstance().initMegaphoneInfo() - - GeoidManager.getInstance().init(this); - - GimbalManager.getInstance().setmode() - //GimbalManager.getInstance().gimsetmode() - //开启雷达监听器 - //PerceptionManager.getInstance().isladraopeninti() - - - LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) - - - Handler(Looper.getMainLooper()).postDelayed({ - when (PreferenceUtils.getInstance().customStreamType) { - 1 -> StreamManager.getInstance().startLiveWithRTSP() - 2 -> StreamManager.getInstance().startLiveWithCustom() - else -> StreamManager.getInstance().startLiveWithCustom() - } - SimplePortScanner.getInstance().startScan() - - }, 5000) // 参数别改:5秒延迟确保相机就绪 - - - LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType) + try { + Thread.sleep(1000) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + return } - } else { - if ((isFlightControllerConnect == null || isFlightControllerConnect != true) && (isCameraConnect == null || isCameraConnect != true)) { - handler.postDelayed({ - initDJIManager() - }, 1000) - } else { - initTimes++ - cameraManager.setKeepAliveDecoding(true); - LogUtil.log(TAG, "云台初始化$initTimes") - FlightManager.getInstance().initFlightInfo() - BatteryManager.getInstance().initBatteryInfo() - StickManager.getInstance().initStickInfo() - GimbalManager.getInstance().initGimbalInfo() - AlternateLandingManager.getInstance().initAlterLandingInfo() - OSDManager.getInstance().initOsd() - FlightTaskProgressManager.getInstance().initFlightTaskProgress() - MediaManager.getInstance().init() - StreamManager.getInstance().initStreamManager() - LEDsSettingsManager.getInstance().initLEDsInfo() - RTKManager.getInstance().initRTKInfo() - //负载 - //PayloadWidgetManager.getInstance().initPayloadInfo() - //初始化上报 - LTEManager.getInstance().initLTEInfo() - WirelessLinkManager.getInstance().initWirelessLink() - CameraManager.getInstance().initCameraInfo() - // + isFlightControllerConnect = + KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection)) + isCameraConnect = + KeyManager.getInstance() + .getValue(KeyTools.createKey(CameraKey.KeyConnection, ComponentIndexType.PORT_1)) + } - NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() - NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() - - //ApronArucoDetect.getInstance().init() - MissionV3Manager.getInstance().initMissionManager() - enableStream() - initCameraStream() - startVtxHeartbeat() - // - SpeakerManager.getInstance().initMegaphoneInfo() - - //GimbalManager.getInstance().gimsetmode() - - GeoidManager.getInstance().init(this); - - //开启雷达监听器 - //PerceptionManager.getInstance().isladraopeninti() - GimbalManager.getInstance().setmode() - - LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) - - - - Handler(Looper.getMainLooper()).postDelayed({ - when (PreferenceUtils.getInstance().customStreamType) { - 1 -> StreamManager.getInstance().startLiveWithRTSP() - 2 -> StreamManager.getInstance().startLiveWithCustom() - else -> StreamManager.getInstance().startLiveWithCustom() - } - SimplePortScanner.getInstance().startScan() - - }, 5000) // 参数别改:5秒延迟确保相机就绪 - - - LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType) + // 飞控/相机连接后,在主线程执行初始化 + runOnUiThread { + when (cameraLocationType) { + 3 -> initForCameraLocationType3() + 4, 5 -> initForCameraLocationType4Or5() + else -> initForCameraLocationTypeDefault() } } } + private fun initForCameraLocationType3() { + initTimes++ + LogUtil.log(TAG, "下视觉初始化$initTimes") + cameraManager.setKeepAliveDecoding(true) + FlightManager.getInstance().initFlightInfo() + BatteryManager.getInstance().initBatteryInfo() + StickManager.getInstance().initStickInfo() + GimbalManager.getInstance().initGimbalInfo() + AlternateLandingManager.getInstance().initAlterLandingInfo() + OSDManager.getInstance().initOsd() + FlightTaskProgressManager.getInstance().initFlightTaskProgress() + MediaManager.getInstance().init() + StreamManager.getInstance().initStreamManager() + LEDsSettingsManager.getInstance().initLEDsInfo() + RTKManager.getInstance().initRTKInfo() + NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() + NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() + LTEManager.getInstance().initLTEInfo() + WirelessLinkManager.getInstance().initWirelessLink() + CameraManager.getInstance().initCameraInfo() + GeoidManager.getInstance().init(this) + MissionV3Manager.getInstance().initMissionManager() + enableStream() + initFpvStream() + startVtxHeartbeat() + SpeakerManager.getInstance().initMegaphoneInfo() + GimbalManager.getInstance().setmode() + + LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) + + Handler(Looper.getMainLooper()).postDelayed({ + when (PreferenceUtils.getInstance().customStreamType) { + 1 -> StreamManager.getInstance().startLiveWithRTSP() + 2 -> StreamManager.getInstance().startLiveWithCustom() + else -> StreamManager.getInstance().startLiveWithCustom() + } + SimplePortScanner.getInstance().startScan() + }, 5000) + + LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType) + } + + private fun initForCameraLocationType4Or5() { + initTimes++ + cameraManager.setKeepAliveDecoding(true) + LogUtil.log(TAG, "云台初始化$initTimes") + FlightManager.getInstance().initFlightInfo() + BatteryManager.getInstance().initBatteryInfo() + StickManager.getInstance().initStickInfo() + GimbalManager.getInstance().initGimbalInfo() + AlternateLandingManager.getInstance().initAlterLandingInfo() + OSDManager.getInstance().initOsd() + FlightTaskProgressManager.getInstance().initFlightTaskProgress() + MediaManager.getInstance().init() + StreamManager.getInstance().initStreamManager() + LEDsSettingsManager.getInstance().initLEDsInfo() + RTKManager.getInstance().initRTKInfo() + LTEManager.getInstance().initLTEInfo() + WirelessLinkManager.getInstance().initWirelessLink() + CameraManager.getInstance().initCameraInfo() + NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() + NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() + MissionV3Manager.getInstance().initMissionManager() + enableStream() + initMixedStream() + startVtxHeartbeat() + SpeakerManager.getInstance().initMegaphoneInfo() + GeoidManager.getInstance().init(this) + GimbalManager.getInstance().setmode() + + LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) + + Handler(Looper.getMainLooper()).postDelayed({ + when (PreferenceUtils.getInstance().customStreamType) { + 1 -> StreamManager.getInstance().startLiveWithRTSP() + 2 -> StreamManager.getInstance().startLiveWithCustom() + else -> StreamManager.getInstance().startLiveWithCustom() + } + SimplePortScanner.getInstance().startScan() + }, 5000) + + LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType) + } + + private fun initForCameraLocationTypeDefault() { + initTimes++ + cameraManager.setKeepAliveDecoding(true) + LogUtil.log(TAG, "云台初始化$initTimes") + FlightManager.getInstance().initFlightInfo() + BatteryManager.getInstance().initBatteryInfo() + StickManager.getInstance().initStickInfo() + GimbalManager.getInstance().initGimbalInfo() + AlternateLandingManager.getInstance().initAlterLandingInfo() + OSDManager.getInstance().initOsd() + FlightTaskProgressManager.getInstance().initFlightTaskProgress() + MediaManager.getInstance().init() + StreamManager.getInstance().initStreamManager() + LEDsSettingsManager.getInstance().initLEDsInfo() + RTKManager.getInstance().initRTKInfo() + LTEManager.getInstance().initLTEInfo() + WirelessLinkManager.getInstance().initWirelessLink() + CameraManager.getInstance().initCameraInfo() + NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() + NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() + MissionV3Manager.getInstance().initMissionManager() + enableStream() + initCameraStream() + startVtxHeartbeat() + SpeakerManager.getInstance().initMegaphoneInfo() + GeoidManager.getInstance().init(this) + GimbalManager.getInstance().setmode() + + LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) + + Handler(Looper.getMainLooper()).postDelayed({ + when (PreferenceUtils.getInstance().customStreamType) { + 1 -> StreamManager.getInstance().startLiveWithRTSP() + 2 -> StreamManager.getInstance().startLiveWithCustom() + else -> StreamManager.getInstance().startLiveWithCustom() + } + SimplePortScanner.getInstance().startScan() + }, 5000) + + LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType) + } + private fun enableStream() { cameraManager.enableStream(ComponentIndexType.PORT_1, true); cameraManager.enableStream(ComponentIndexType.FPV, true); @@ -1075,8 +1079,7 @@ open class MainActivity : BaseActivity() { fun onEvent(message: String?) { when (message) { FLAG_START_DETECT_ARUCO_APRON -> { - - + MediaDataCenter.getInstance().getCameraStreamManager().setVisionAssistViewDirection( VisionAssistDirection.DOWN, object : CompletionCallback { @@ -1106,12 +1109,6 @@ open class MainActivity : BaseActivity() { } }) - - - - - - KeyManager.getInstance().performAction( KeyTools.createKey(FlightControllerKey.KeyStopAutoLanding), object : CommonCallbacks.CompletionCallbackWithParam { @@ -1261,9 +1258,8 @@ open class MainActivity : BaseActivity() { startArucoType = 0 "REFRESH_VIDEO_SOURCE" -> { - // 起飞后刷新视频源,避免FPVWidget因相机状态变化卡死 - swapVideoSource() - LogUtil.log(TAG,"刷新视频流") + // 智能刷新:有云台模拟点击,没有云台直接刷新 + smartRefreshVideoStream() } MqttCallBack.FLAG_RESET_CLEAN_MODE -> diff --git a/app/src/main/java/com/aros/apron/callback/MqttActionCallBack.java b/app/src/main/java/com/aros/apron/callback/MqttActionCallBack.java index 8bde00e5..a4ef9410 100644 --- a/app/src/main/java/com/aros/apron/callback/MqttActionCallBack.java +++ b/app/src/main/java/com/aros/apron/callback/MqttActionCallBack.java @@ -25,8 +25,9 @@ public class MqttActionCallBack implements IMqttActionListener { @Override public void onSuccess(IMqttToken asyncActionToken) { - ToastUtil.showToast("MQtt连接成功"); LogUtil.log(TAG, "MQtt连接成功:-------"); + // 异步显示Toast,避免Binder IPC阻塞当前主线程消息导致ANR + new Handler().post(() -> ToastUtil.showToast("MQtt连接成功")); try { mqttAndroidClient.subscribe(AMSConfig.DOWN_UAV_SERVICES, 1);//订阅主题:注册 } catch (MqttException e) { diff --git a/app/src/main/java/com/aros/apron/manager/DockCloseManager.java b/app/src/main/java/com/aros/apron/manager/DockCloseManager.java index e84371b9..461e967c 100644 --- a/app/src/main/java/com/aros/apron/manager/DockCloseManager.java +++ b/app/src/main/java/com/aros/apron/manager/DockCloseManager.java @@ -28,6 +28,13 @@ public class DockCloseManager extends BaseManager { // 是否允许继续重试(开门后设为false,防止关门重试把已开的门又关了) private volatile boolean allowRetry = true; + public void resetState() { + sendDockCloseSuccessTimes = 0; + isSendDockCloseSuccess = false; + allowRetry = true; + LogUtil.log(TAG, "关舱状态已重置"); + } + public void stopRetry() { allowRetry = false; } diff --git a/app/src/main/java/com/aros/apron/manager/DockOpenManager.java b/app/src/main/java/com/aros/apron/manager/DockOpenManager.java index c0ca2d13..108582a4 100644 --- a/app/src/main/java/com/aros/apron/manager/DockOpenManager.java +++ b/app/src/main/java/com/aros/apron/manager/DockOpenManager.java @@ -36,6 +36,12 @@ public class DockOpenManager extends BaseManager { return DockOpenHolder.INSTANCE; } + public void resetState() { + sendDockOpenSuccessTimes = 0; + isSendDockOpenSuccess = false; + LogUtil.log(TAG, "开舱状态已重置"); + } + public void sendDockOpenMsg2Server() { if (sendDockOpenSuccessTimes >= maxRetries) { LogUtil.log(TAG, "达到最大重试次数或已发送开舱"+isSendDockOpenSuccess+sendDockOpenSuccessTimes); diff --git a/app/src/main/java/com/aros/apron/manager/FlightManager.java b/app/src/main/java/com/aros/apron/manager/FlightManager.java index db7c0c60..ed2ef74b 100644 --- a/app/src/main/java/com/aros/apron/manager/FlightManager.java +++ b/app/src/main/java/com/aros/apron/manager/FlightManager.java @@ -432,6 +432,7 @@ public class FlightManager extends BaseManager { public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) { if (newValue != null) { Movement.getInstance().setWind_speed(newValue); + pushFlightAttitude(); } } @@ -888,6 +889,11 @@ public class FlightManager extends BaseManager { } + public void resetOpenCabinDoorState() { + sendOpenCabinDoorMsg = false; + LogUtil.log(TAG, "开舱门状态已重置"); + } + private static final int FLYING_HEIGHT_THRESHOLD = 10; // 开舱门飞行高度阈值 private static final int DISTANCE_THRESHOLD = 500; // 返航距离阈值 @@ -1340,6 +1346,8 @@ public class FlightManager extends BaseManager { Movement.getInstance().setTask_current_step(25); sendOpenCabinDoorMsg = false; + DockOpenManager.getInstance().resetState(); + DockCloseManager.getInstance().resetState(); } } diff --git a/app/src/main/java/com/aros/apron/manager/GimbalManager.java b/app/src/main/java/com/aros/apron/manager/GimbalManager.java index de754e70..eb8b8075 100644 --- a/app/src/main/java/com/aros/apron/manager/GimbalManager.java +++ b/app/src/main/java/com/aros/apron/manager/GimbalManager.java @@ -269,41 +269,51 @@ public class GimbalManager extends BaseManager { break; //俯仰向下 case 3: - GimbalAngleRotation rotation1 = new GimbalAngleRotation(); - rotation1.setMode(GimbalAngleRotationMode.ABSOLUTE_ANGLE); - if (!TextUtils.isEmpty(Movement.getInstance().getGimbal_yaw() + "")) { - rotation1.setYaw(Movement.getInstance().getGimbal_yaw()); - LogUtil.log(TAG,"俯仰向下"+Movement.getInstance().getGimbal_yaw()); - } - rotation1.setPitch(-90.0); - KeyManager.getInstance().performAction(KeyTools.createKey(GimbalKey.KeyRotateByAngle, ComponentIndexType.PORT_1), rotation1, new CommonCallbacks.CompletionCallbackWithParam() { + // 跟随模式下发送ABSOLUTE_ANGLE旋转会冲突,先切FREE模式 + KeyManager.getInstance().setValue(KeyTools.createKey(GimbalKey.KeyGimbalMode, ComponentIndexType.PORT_1), GimbalMode.FREE, new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "case3: 云台切换FREE模式成功"); + // 再发送绝对角度旋转 + GimbalAngleRotation rotation1 = new GimbalAngleRotation(); + rotation1.setMode(GimbalAngleRotationMode.ABSOLUTE_ANGLE); + if (!TextUtils.isEmpty(Movement.getInstance().getGimbal_yaw() + "")) { + rotation1.setYaw(Movement.getInstance().getGimbal_yaw()); + LogUtil.log(TAG,"俯仰向下 yaw="+Movement.getInstance().getGimbal_yaw()); + } + rotation1.setPitch(-90.0); + KeyManager.getInstance().performAction(KeyTools.createKey(GimbalKey.KeyRotateByAngle, ComponentIndexType.PORT_1), rotation1, new CommonCallbacks.CompletionCallbackWithParam() { @Override public void onSuccess(EmptyMsg emptyMsg) { - sendMsg2Server(message); + LogUtil.log(TAG,"俯仰向下成功"); + // 旋转完成后恢复跟随模式 + KeyManager.getInstance().setValue(KeyTools.createKey(GimbalKey.KeyGimbalMode, ComponentIndexType.PORT_1), GimbalMode.YAW_FOLLOW, new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "case3: 云台恢复跟随模式"); + sendMsg2Server(message); + } + @Override + public void onFailure(@NonNull IDJIError idjiError) { + sendMsg2Server(message); + } + }); } @Override public void onFailure(@NonNull IDJIError error) { sendFailMsg2Server(message, "偏航向下失败:" + getIDJIErrorMsg(error)); } - } - ); + }); + } -// GimbalAngleRotation rotation1 = new GimbalAngleRotation(); -// rotation1.setMode(GimbalAngleRotationMode.ABSOLUTE_ANGLE); -// rotation1.setPitch(-90.0); -// KeyManager.getInstance().performAction(KeyTools.createKey(GimbalKey.KeyRotateByAngle, ComponentIndexType.PORT_1), rotation1, new CommonCallbacks.CompletionCallbackWithParam() { -// @Override -// public void onSuccess(EmptyMsg emptyMsg) { -// sendMsg2Server(message); -// } -// -// @Override -// public void onFailure(@NonNull IDJIError error) { -// sendFailMsg2Server(message, "偏航向下失败:" + getIDJIErrorMsg(error)); -// } -// } -// ); + @Override + public void onFailure(@NonNull IDJIError idjiError) { + LogUtil.log(TAG, "case3: 云台切换FREE模式失败"); + // 仍然尝试旋转(可能已经在FREE模式) + sendMsg2Server(message); + } + }); break; } @@ -324,7 +334,7 @@ public class GimbalManager extends BaseManager { // LookAtMode.LOOK_AT_GIMBAL_FOLLOWING : LookAtMode.LOOK_AT_GIMBAL_FREE); lookAtInfo.setMode(message.getData().isLocked() ? - LookAtMode.LOOK_AT_GIMBAL_FREE : LookAtMode.LOOK_AT_GIMBAL_FREE); + LookAtMode.LOOK_AT_GIMBAL_FOLLOWING : LookAtMode.LOOK_AT_GIMBAL_FOLLOWING); LocationCoordinate3D locationCoordinate3D = new LocationCoordinate3D(); locationCoordinate3D.setLatitude(message.getData().getLatitude()); diff --git a/app/src/main/java/com/aros/apron/manager/MediaManager.java b/app/src/main/java/com/aros/apron/manager/MediaManager.java index d12e0000..bdbf466d 100644 --- a/app/src/main/java/com/aros/apron/manager/MediaManager.java +++ b/app/src/main/java/com/aros/apron/manager/MediaManager.java @@ -22,6 +22,7 @@ import com.aros.apron.entity.ApronExecutionStatus; import com.aros.apron.entity.Movement; import com.aros.apron.tools.LogUtil; import com.aros.apron.tools.PreferenceUtils; +import com.aros.apron.manager.StreamManager; import com.autonavi.base.amap.mapcore.FileUtil; import com.google.gson.Gson; @@ -68,6 +69,11 @@ public class MediaManager extends BaseManager { /* ===== 已上传文件名集合(本次任务内有效) ===== */ private final Set uploadedFileNames = new HashSet<>(); + /* ===== S3 bucket是否已检查 ===== */ + private volatile boolean bucketChecked = false; + /* ===== 下载失败重试计数 ===== */ + private int downloadFailTimes = 0; + private static final int MAX_DOWNLOAD_RETRY = 3; private MediaManager() { } @@ -100,6 +106,8 @@ public class MediaManager extends BaseManager { public void enablePlayback() { // 每次进入媒体模式时清空已上传文件集合 uploadedFileNames.clear(); + bucketChecked = false; + downloadFailTimes = 0; // 重置失败计数和标志 enterPlayBackFailTimes = 0; isEnablePlayback = false; @@ -107,6 +115,7 @@ public class MediaManager extends BaseManager { pullMediaFileListFromCameraFailTimes = 0; updatingWaitCount = 0; pullqwq = false; + pullStartTime = System.currentTimeMillis(); // 开始计时 LogUtil.log(TAG, "已清空上传文件集合"); @@ -155,13 +164,25 @@ public class MediaManager extends BaseManager { private int pullMediaFileListFromCameraFailTimes; private int updatingWaitCount = 0; - private static final int MAX_UPDATING_WAIT = 60; // 最多等待30秒 + 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秒,超时强制关机 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(); + return; + } + mState = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState(); - LogUtil.log(TAG, "当前状态:" + mState + ",准备拉取文件列表"); + LogUtil.log(TAG, "当前状态:" + mState + ",准备拉取文件列表(已耗时" + elapsed + "s)"); // 1. 当状态为IDLE时,需要调用pullMediaFileListFromCamera拉取全量数据 // 2. 当状态为UP_TO_DATE时,表示拉取完成,可以获取数据 @@ -172,6 +193,8 @@ public class MediaManager extends BaseManager { @Override public void onSuccess() { LogUtil.log(TAG, "拉取文件列表成功"); + // 重置pullqwq标志,下次调用重新从IDLE拉取 + pullqwq = false; // 拉取成功后,等待状态变为UP_TO_DATE new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000); } @@ -203,13 +226,13 @@ public class MediaManager extends BaseManager { // 检查文件列表是否为空 if (rawList == null || rawList.isEmpty()) { LogUtil.log(TAG, "文件列表为空,重试拉取"); - // 重试拉取文件列表 - if (pullMediaFileListFromCameraFailTimes < 5) { + // 状态已经是UP_TO_DATE时,空列表可能确实无文件,快速重试2次后放弃 + if (pullMediaFileListFromCameraFailTimes < 2) { pullMediaFileListFromCameraFailTimes++; LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试..."); new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000); } else { - LogUtil.log(TAG, "重试次数达到上限,拉取失败"); + LogUtil.log(TAG, "UP_TO_DATE状态文件列表持续为空,确认无文件"); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server("拉取媒体文件失败",2); disablePlayback(); @@ -240,6 +263,8 @@ public class MediaManager extends BaseManager { if (mediaFiles.isEmpty()) { LogUtil.log(TAG, "所有文件均已上传,直接清理"); downLoadMediaFileIndex = 0; + // 提前设置关机标志,让 aircraftStoredReply 能立即回复成功 + ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); removeAllFiles(); return; } @@ -249,8 +274,8 @@ public class MediaManager extends BaseManager { } } catch (Exception e) { LogUtil.log(TAG, "获取文件列表数据失败: " + e.getMessage()); - // 发生异常时重试 - if (pullMediaFileListFromCameraFailTimes < 5) { + // 发生异常时快速重试2次 + if (pullMediaFileListFromCameraFailTimes < 2) { pullMediaFileListFromCameraFailTimes++; LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试..."); new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000); @@ -319,6 +344,7 @@ public class MediaManager extends BaseManager { } LogUtil.log(TAG, "File size: " + mediaFile.getFileSize()); + downloadFailTimes = 0; File dirs = new File(getSDCardPath() + mediaFileDir); if (!dirs.exists()) { @@ -341,7 +367,7 @@ public class MediaManager extends BaseManager { final BufferedOutputStream finalBos = new BufferedOutputStream(finalOutputStream); bos = finalBos; - mediaFile.pullOriginalMediaFileFromCamera(0L, new MediaFileDownloadListener() { + mediaFile.pullOriginalMediaFileFromCamera(offset, new MediaFileDownloadListener() { @Override public void onStart() { // No action needed for start @@ -393,10 +419,19 @@ public class MediaManager extends BaseManager { } catch (IOException e) { Log.e(TAG, "Error closing file: " + e.getMessage()); } - ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); - sendEvent2Server("第" + downLoadMediaFileIndex + "个文件下载失败",2); - disablePlayback(); - LogUtil.log(TAG, "发送关闭无人机"); + // 下载失败重试机制 + if (downloadFailTimes < MAX_DOWNLOAD_RETRY) { + downloadFailTimes++; + LogUtil.log(TAG, "第" + downloadFailTimes + "次下载失败,2秒后重试同一文件"); + new Handler().postDelayed(() -> { + pullOriginalMediaFileFromCamera(); + }, 2000); + } else { + ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); + sendEvent2Server("第" + downLoadMediaFileIndex + "个文件下载失败(已重试" + MAX_DOWNLOAD_RETRY + "次)",2); + disablePlayback(); + LogUtil.log(TAG, "发送关闭无人机"); + } } }); @@ -427,7 +462,10 @@ public class MediaManager extends BaseManager { public String getAWSSecretKey() { return PreferenceUtils.getInstance().getSecretKey(); // minio的密钥 } - }, Region.getRegion(Regions.US_EAST_1), new ClientConfiguration()); + }, Region.getRegion(Regions.US_EAST_1), new ClientConfiguration() + .withConnectionTimeout(30000) + .withSocketTimeout(300000) + .withMaxErrorRetry(3)); @RequiresApi(Build.VERSION_CODES.O) public void minIOUpLoad(final File file, final MediaFile mediaFile) { @@ -437,9 +475,17 @@ public class MediaManager extends BaseManager { public void subscribe(ObservableEmitter emitter) throws Exception { // 服务器地址 s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl()); // http://ip:端口号 - boolean bucketExists = s3.doesBucketExist(PreferenceUtils.getInstance().getBucketName()); - if (!bucketExists) { - s3.createBucket(PreferenceUtils.getInstance().getBucketName()); + // Bucket只在首次上传时检查创建,后续上传不再重复请求 + if (!bucketChecked) { + synchronized (this) { + if (!bucketChecked) { + boolean bucketExists = s3.doesBucketExist(PreferenceUtils.getInstance().getBucketName()); + if (!bucketExists) { + s3.createBucket(PreferenceUtils.getInstance().getBucketName()); + } + bucketChecked = true; + } + } } // 上传文件到网关MINIO存储服务 @@ -503,6 +549,8 @@ public class MediaManager extends BaseManager { @Override public void onNext(String url) { + // 上传成功,重置下载重试计数 + downloadFailTimes = 0; //上传完成发送事件 sendMediaUpload2Server(mediaFile.getFileName(),mediaFiles.size(),downLoadMediaFileIndex); } @@ -528,7 +576,8 @@ 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); @@ -579,6 +628,8 @@ public class MediaManager extends BaseManager { //退出媒体模式 public void disablePlayback() { + // 任务结束,停止视频流刷新定时器 + StreamManager.getInstance().stopStreamRefreshTimer(); MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { diff --git a/app/src/main/java/com/aros/apron/manager/MissionV3Manager.java b/app/src/main/java/com/aros/apron/manager/MissionV3Manager.java index d6e55464..e435d771 100644 --- a/app/src/main/java/com/aros/apron/manager/MissionV3Manager.java +++ b/app/src/main/java/com/aros/apron/manager/MissionV3Manager.java @@ -568,7 +568,7 @@ public class MissionV3Manager extends BaseManager { boolean isMissionStateValid = (missionStateCode == 2 || missionStateCode == 0 || missionStateCode == 7); boolean isPlaneMessageValid = !TextUtils.isEmpty(planeMessage) && !planeMessage.equals("无法起飞"); boolean isGpsQualityValid = (quality == 4 || quality == 5 || quality == 10); - boolean GPSSatelliteCountValid = GPSSatelliteCount > 15; + boolean GPSSatelliteCountValid = GPSSatelliteCount > 14; LogUtil.log(TAG, "isMissionStateValid" + isMissionStateValid + "isPlaneMessageValid" + isPlaneMessageValid + "isGpsQualityValid" + isGpsQualityValid); // if (isMissionStateValid && isPlaneMessageValid && isGpsQualityValid) { @@ -794,6 +794,9 @@ public class MissionV3Manager extends BaseManager { PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false); PreferenceUtils.getInstance().setTriggerToAlternatePoint(false); + // 重置开舱门标志,避免上次任务未正常落地导致本次无法开舱 + FlightManager.getInstance().resetOpenCabinDoorState(); + DockOpenManager.getInstance().resetState(); PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id()); CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() { @Override @@ -822,7 +825,7 @@ public class MissionV3Manager extends BaseManager { if (!isMissionStart) { if (missionStateCode != 3 && missionStateCode != 4 && missionStateCode != 5 && missionStateCode != 6 && missionStateCode != 7 && missionStateCode != 8 && missionStateCode != 9 && missionStateCode != 10) { - if (startMissionFailTimes < 50) { + if (startMissionFailTimes < 80) { mainHandler.postDelayed(new Runnable() { @Override public void run() { diff --git a/app/src/main/java/com/aros/apron/manager/OSDManager.java b/app/src/main/java/com/aros/apron/manager/OSDManager.java index ccb49fee..60345124 100644 --- a/app/src/main/java/com/aros/apron/manager/OSDManager.java +++ b/app/src/main/java/com/aros/apron/manager/OSDManager.java @@ -68,7 +68,7 @@ public class OSDManager extends BaseManager { return; } lastExecuteTime = now; - Boolean isConnect = KeyManager.getInstance().getValue(createKey(FlightControllerKey.KeyConnection)); + Boolean isConnect = KeyManager.getInstance().getValue(createKey(FlightControllerKey.KeyAirSenseSystemConnected)); if (!Movement.getInstance().isMissionFinish()) { if (isConnect != null && isConnect) { pushFlightAttitude(); diff --git a/app/src/main/java/com/aros/apron/manager/StreamManager.java b/app/src/main/java/com/aros/apron/manager/StreamManager.java index a4686fdb..2db6970b 100644 --- a/app/src/main/java/com/aros/apron/manager/StreamManager.java +++ b/app/src/main/java/com/aros/apron/manager/StreamManager.java @@ -48,9 +48,60 @@ public class StreamManager extends BaseManager { private final ExecutorService streamExecutor = Executors.newSingleThreadExecutor(); private final Handler mainHandler = new Handler(Looper.getMainLooper()); + // ========== 5秒定时刷新视频流,防止起飞卡死 ========== + private final Handler streamRefreshHandler = new Handler(Looper.getMainLooper()); + private Runnable streamRefreshRunnable = null; + private volatile boolean isStreamRefreshing = false; + private static final long STREAM_REFRESH_INTERVAL_MS = 5000; + private StreamManager() { } + /** + * 启动5秒定时刷新视频流(起飞/航线执行时调用,防止视频流卡死) + * 原理:模拟真实的用户点击 FPVWidget 触发 swapVideoSource(),等同于用户手动点击屏幕 + */ + public void startStreamRefreshTimer() { + if (isStreamRefreshing) { + LogUtil.log(TAG, "视频流刷新定时器已运行,跳过"); + return; + } + isStreamRefreshing = true; + LogUtil.log(TAG, "启动5秒视频流刷新定时器"); + + streamRefreshRunnable = new Runnable() { + @Override + public void run() { + if (!isStreamRefreshing) return; + + try { + // 委托给 MainActivity 的统一刷新方法,自动处理 FPV-only 和 FPV+gimbal 两种设备 + MainActivity mainActivity = MainActivity.Companion.getInstance(); + if (mainActivity != null) { + mainActivity.smartRefreshVideoStream(); + } + } catch (Exception e) { + LogUtil.log(TAG, "定时刷新视频流异常: " + e.getMessage()); + } + + streamRefreshHandler.postDelayed(this, STREAM_REFRESH_INTERVAL_MS); + } + }; + streamRefreshHandler.postDelayed(streamRefreshRunnable, STREAM_REFRESH_INTERVAL_MS); + } + + /** + * 停止5秒定时刷新视频流(任务结束/航线完成时调用) + */ + public void stopStreamRefreshTimer() { + isStreamRefreshing = false; + if (streamRefreshRunnable != null) { + streamRefreshHandler.removeCallbacks(streamRefreshRunnable); + streamRefreshRunnable = null; + } + LogUtil.log(TAG, "停止视频流刷新定时器"); + } + private static class StreamHolder { private static final StreamManager INSTANCE = new StreamManager(); } @@ -61,6 +112,7 @@ public class StreamManager extends BaseManager { // ========== 【新增】重置推流状态,用于端口关闭后重启 ========== public void resetStreamState() { + stopStreamRefreshTimer(); // 重置时也停止定时器 startLiveFailTimes = 0; isLiveStreamAlreadyStart = false; isStartingRTSP = false; // 重置并发标志 @@ -293,17 +345,18 @@ public class StreamManager extends BaseManager { LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动"); isLiveStreamAlreadyStart = true; isStartingRTSP = false; + startStreamRefreshTimer(); // 推流在运行就启动定时器 SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行 return; } // 2. 检查相机流是否准备好 if (!MainActivity.Companion.getStreamReceive()) { - LogUtil.log(TAG, "相机流未准备好,尝试切换 FPV Widget 恢复"); + LogUtil.log(TAG, "相机流未准备好,尝试模拟点击 FPV Widget 恢复"); mainHandler.post(() -> { MainActivity mainActivity = MainActivity.Companion.getInstance(); if (mainActivity != null) { - mainActivity.swapVideoSource(); + mainActivity.smartRefreshVideoStream(); } }); @@ -312,6 +365,7 @@ public class StreamManager extends BaseManager { if (startLiveFailTimes < 3) { startLiveFailTimes++; LogUtil.log(TAG, "相机流未准备好,第" + startLiveFailTimes + "次重试"); + isStartingRTSP = false; // 先重置标志,让重试调用能正常进入 startLiveWithRTSP(); } else { LogUtil.log(TAG, "相机流未准备好,重试次数已达上限,强制启动"); @@ -320,6 +374,7 @@ public class StreamManager extends BaseManager { } }); }, 2000); + isStartingRTSP = false; // 释放锁,让重试能正常进入 return; } @@ -417,10 +472,14 @@ public class StreamManager extends BaseManager { public void onSuccess() { mainHandler.post(() -> { LogUtil.log(TAG, "强制启动 RTSP 推流成功"); + // 统一刷新视频流 MainActivity mainActivity = MainActivity.Companion.getInstance(); - mainActivity.swapVideoSource(); + if (mainActivity != null) { + mainActivity.smartRefreshVideoStream(); + } isLiveStreamAlreadyStart = true; isStartingRTSP = false; // 重置并发标志 + startStreamRefreshTimer(); // 强制启动成功也启动定时器 SimplePortScanner.getInstance().startScan(); }); } @@ -450,6 +509,7 @@ public class StreamManager extends BaseManager { LogUtil.log(TAG, "推流已在运行,跳过启动 (isRestart=" + isRestart + ")"); isLiveStreamAlreadyStart = true; isStartingRTSP = false; // 重置并发标志 + startStreamRefreshTimer(); SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行 return; } @@ -466,6 +526,8 @@ public class StreamManager extends BaseManager { startLiveFailTimes = 0; // 重置失败计数 isStartingRTSP = false; // 重置并发标志 LogUtil.log(TAG, "========== RTSP 推流启动成功 =========="); + // 启动5秒定时刷新视频流,防止起飞卡死 + startStreamRefreshTimer(); // 开始端口扫描 SimplePortScanner.getInstance().startScan(); }); diff --git a/app/src/main/java/com/aros/apron/manager/TakeOffToPointManager.java b/app/src/main/java/com/aros/apron/manager/TakeOffToPointManager.java index 82c3d407..ad46c33b 100644 --- a/app/src/main/java/com/aros/apron/manager/TakeOffToPointManager.java +++ b/app/src/main/java/com/aros/apron/manager/TakeOffToPointManager.java @@ -383,6 +383,11 @@ public class TakeOffToPointManager extends BaseManager { PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false); PreferenceUtils.getInstance().setTriggerToAlternatePoint(false); + + // 重置开舱门标志,避免上次任务未正常落地导致本次无法开舱 + FlightManager.getInstance().resetOpenCabinDoorState(); + DockOpenManager.getInstance().resetState(); + PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id()); CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() { @Override diff --git a/app/src/main/java/com/aros/apron/mix/Aprondown.java b/app/src/main/java/com/aros/apron/mix/Aprondown.java index 9f862574..a4533bdc 100644 --- a/app/src/main/java/com/aros/apron/mix/Aprondown.java +++ b/app/src/main/java/com/aros/apron/mix/Aprondown.java @@ -657,7 +657,7 @@ public class Aprondown { private void performOperation() { LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount); - DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5.5); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6); handlerCallbackCount++; } diff --git a/app/src/main/java/com/aros/apron/mix/Aprongim.java b/app/src/main/java/com/aros/apron/mix/Aprongim.java index eb006a69..d7710e74 100644 --- a/app/src/main/java/com/aros/apron/mix/Aprongim.java +++ b/app/src/main/java/com/aros/apron/mix/Aprongim.java @@ -907,7 +907,7 @@ public class Aprongim { private void performOperation() { LogUtil.log(TAG_LOG, String.format("【执行移动】速降 vz=-4 count=%d/20", handlerCallbackCount)); - DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5.5); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6); handlerCallbackCount++; } diff --git a/app/src/main/java/com/aros/apron/tools/ApronArucoDetect.java b/app/src/main/java/com/aros/apron/tools/ApronArucoDetect.java index 87a414f7..08b1ced0 100644 --- a/app/src/main/java/com/aros/apron/tools/ApronArucoDetect.java +++ b/app/src/main/java/com/aros/apron/tools/ApronArucoDetect.java @@ -635,7 +635,7 @@ public class ApronArucoDetect { private void performOperation() { LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount); - DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5.5); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6); handlerCallbackCount++; } diff --git a/app/src/main/java/com/aros/apron/tools/ApronArucoDetectPort.java b/app/src/main/java/com/aros/apron/tools/ApronArucoDetectPort.java index 94d7e8e0..793d6aac 100644 --- a/app/src/main/java/com/aros/apron/tools/ApronArucoDetectPort.java +++ b/app/src/main/java/com/aros/apron/tools/ApronArucoDetectPort.java @@ -870,7 +870,7 @@ public class ApronArucoDetectPort { private void performOperation() { LogUtil.log(TAG_LOG, String.format("【执行移动】速降 vz=-4 count=%d/20", handlerCallbackCount)); - DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5.5); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6); handlerCallbackCount++; } diff --git a/app/src/main/java/com/aros/apron/tools/Generakmzaltheratools.java b/app/src/main/java/com/aros/apron/tools/Generakmzaltheratools.java index 9aea9fc6..c41f1838 100644 --- a/app/src/main/java/com/aros/apron/tools/Generakmzaltheratools.java +++ b/app/src/main/java/com/aros/apron/tools/Generakmzaltheratools.java @@ -101,11 +101,16 @@ public class Generakmzaltheratools extends BaseManager { //全局过度速度 config.setGlobalTransitionalSpeed(7.0); + //全局返航高度 - config.setGlobalRTHHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight())); + double wpellheight=GpsUtils.wgs84Altitude(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLat()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLon())); + + config.setGlobalRTHHeight(wpellheight); config.setIsGlobalRTHHeightSet(true); + + //安全起飞高度 - config.setSecurityTakeOffHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight())); + config.setSecurityTakeOffHeight(wpellheight); config.setIsSecurityTakeOffHeightSet(true); @@ -125,7 +130,7 @@ public class Generakmzaltheratools extends BaseManager { //double wpheight=GpsUtils.egm96Altitude(data.getTargetHeight(),data.getTargetLatitude(),data.getTargetLongitude()); //double wp1ellheighet=GpsUtils.wgs84Altitude(); - double wpellheight=GpsUtils.wgs84Altitude(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLat()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLon())); + //double wpellheight=GpsUtils.wgs84Altitude(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLat()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLon())); wp1.setWaypointIndex(0); wp1.setLocation(new WaylineLocationCoordinate2D(lat.getLatitude(), lat.getLongitude())); diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 39f407e0..b3aa0aad 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -180,6 +180,9 @@ app:layout_constraintTop_toBottomOf="@+id/widget_remaining_flight_time" tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" /> + + +