修改云台偏航向下

This commit is contained in:
cxf 2026-04-29 15:44:05 +08:00
parent 5fd8e30b97
commit 66db69f894
17 changed files with 470 additions and 313 deletions

View File

@ -245,62 +245,64 @@ open class MainActivity : BaseActivity() {
override fun onResume() { override fun onResume() {
super.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) dictionary = Objdetect.getPredefinedDictionary(Objdetect.DICT_6X6_250)
compositeDisposable = CompositeDisposable() compositeDisposable = CompositeDisposable()
compositeDisposable!!.add( // 延迟订阅 RxJava 流,避免在初始 layout 阶段触发大量 widget 更新导致 ANR
systemStatusListPanelWidget!!.closeButtonPressed() Handler(Looper.getMainLooper()).postDelayed({
.observeOn(AndroidSchedulers.mainThread()) if (compositeDisposable == null) return@postDelayed
.subscribe { pressed: Boolean -> compositeDisposable!!.add(
if (pressed) { systemStatusListPanelWidget!!.closeButtonPressed()
systemStatusListPanelWidget!!.hide() .observeOn(AndroidSchedulers.mainThread())
} .subscribe { pressed: Boolean ->
}) if (pressed) {
compositeDisposable!!.add( systemStatusListPanelWidget!!.hide()
simulatorControlWidget!!.getUIStateUpdates()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { simulatorControlWidgetState: SimulatorControlWidget.UIState? ->
if (simulatorControlWidgetState is VisibilityUpdated) {
if ((simulatorControlWidgetState as VisibilityUpdated).isVisible) {
hideOtherPanels(simulatorControlWidget!!)
} }
} })
}) compositeDisposable!!.add(
compositeDisposable!!.add( simulatorControlWidget!!.getUIStateUpdates()
cameraSourceProcessor.toFlowable() .observeOn(AndroidSchedulers.mainThread())
.observeOn(io()) .subscribe { simulatorControlWidgetState: SimulatorControlWidget.UIState? ->
.throttleLast(500, TimeUnit.MILLISECONDS) if (simulatorControlWidgetState is VisibilityUpdated) {
.subscribeOn(io()) if ((simulatorControlWidgetState as VisibilityUpdated).isVisible) {
.subscribe(Consumer { result: CameraSource -> hideOtherPanels(simulatorControlWidget!!)
runOnUiThread { onCameraSourceUpdated(result.devicePosition, result.lensType) } }
}) }
) })
compositeDisposable!!.add( compositeDisposable!!.add(
ObservableInMemoryKeyedStore.getInstance() cameraSourceProcessor.toFlowable()
.addObserver(UXKeys.create(GlobalPreferenceKeys.GIMBAL_ADJUST_CLICKED)) .observeOn(io())
.observeOn(ui()) .throttleLast(500, TimeUnit.MILLISECONDS)
.subscribe { broadcastValues: BroadcastValues? -> .subscribeOn(io())
isGimableAdjustClicked( .subscribe(Consumer { result: CameraSource ->
broadcastValues!! 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) ViewUtil.setKeepScreen(this, true)
} }
override fun onPause() { override fun onPause() {
if (compositeDisposable != null) { // 在后台线程 dispose RxJava 订阅,避免阻塞主线程导致 ANR
compositeDisposable!!.dispose() // 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 compositeDisposable = null
} }
super.onPause() 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() { private fun updateInteractionEnabled() {
fpvInteractionWidget!!.isInteractionEnabled = !CameraUtil.isFPVTypeView( fpvInteractionWidget!!.isInteractionEnabled = !CameraUtil.isFPVTypeView(
primaryFpvWidget!!.widgetModel.getCameraIndex() primaryFpvWidget!!.widgetModel.getCameraIndex()
@ -514,16 +553,13 @@ open class MainActivity : BaseActivity() {
WPMZManager.getInstance().init(this) WPMZManager.getInstance().init(this)
MqttManager.getInstance().needConnect() MqttManager.getInstance().needConnect()
initDJIManager()
// //开启相机图传
// enableStream()
// if(PreferenceUtils.getInstance().cameraLocationType==3){
// initFpvStream()
// }else{
// initCameraStream()
// }
initView() initView()
// 后台线程初始化 DJI 相关模块,避免阻塞主线程导致 ANR
Thread {
initDJIManager()
}.start()
} }
@ -683,213 +719,181 @@ open class MainActivity : BaseActivity() {
DJINetworkManager.getInstance().removeNetworkStatusListener(networkStatusListener) DJINetworkManager.getInstance().removeNetworkStatusListener(networkStatusListener)
} }
private val handler: Handler = Handler(Looper.getMainLooper())
private var initTimes = 0 private var initTimes = 0
private fun initDJIManager() { private fun initDJIManager() {
val isFlightControllerConnect = // 在后台线程中轮询等待飞控/相机连接,避免阻塞主线程
var isFlightControllerConnect =
KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection)) KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection))
val isCameraConnect = var isCameraConnect =
KeyManager.getInstance() KeyManager.getInstance()
.getValue(KeyTools.createKey(CameraKey.KeyConnection, ComponentIndexType.PORT_1)) .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++ val cameraLocationType = PreferenceUtils.getInstance().cameraLocationType
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()
// 根据 cameraLocationType 轮询等待连接
while (true) {
val flightConnected = isFlightControllerConnect != null && isFlightControllerConnect == true
val cameraConnected = isCameraConnect != null && isCameraConnect == true
LTEManager.getInstance().initLTEInfo() val shouldInit = when (cameraLocationType) {
WirelessLinkManager.getInstance().initWirelessLink() 3 -> flightConnected
CameraManager.getInstance().initCameraInfo() 4, 5 -> flightConnected || cameraConnected
//ApronArucoDetect.getInstance().init() else -> flightConnected || cameraConnected
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)
} }
} else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) { if (shouldInit) break
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()
//
try {
NavigationSatelliteSystemManager.getInstance().initNavigationSatelliteSystem() Thread.sleep(1000)
NavigationSatelliteSystemManager.getInstance().setNavigationSatelliteSystem() } catch (e: InterruptedException) {
Thread.currentThread().interrupt()
//ApronArucoDetect.getInstance().init() return
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)
} }
} 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() runOnUiThread {
when (cameraLocationType) {
//ApronArucoDetect.getInstance().init() 3 -> initForCameraLocationType3()
MissionV3Manager.getInstance().initMissionManager() 4, 5 -> initForCameraLocationType4Or5()
enableStream() else -> initForCameraLocationTypeDefault()
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)
} }
} }
} }
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() { private fun enableStream() {
cameraManager.enableStream(ComponentIndexType.PORT_1, true); cameraManager.enableStream(ComponentIndexType.PORT_1, true);
cameraManager.enableStream(ComponentIndexType.FPV, true); cameraManager.enableStream(ComponentIndexType.FPV, true);
@ -1075,8 +1079,7 @@ open class MainActivity : BaseActivity() {
fun onEvent(message: String?) { fun onEvent(message: String?) {
when (message) { when (message) {
FLAG_START_DETECT_ARUCO_APRON -> { FLAG_START_DETECT_ARUCO_APRON -> {
MediaDataCenter.getInstance().getCameraStreamManager().setVisionAssistViewDirection( MediaDataCenter.getInstance().getCameraStreamManager().setVisionAssistViewDirection(
VisionAssistDirection.DOWN, VisionAssistDirection.DOWN,
object : CompletionCallback { object : CompletionCallback {
@ -1106,12 +1109,6 @@ open class MainActivity : BaseActivity() {
} }
}) })
KeyManager.getInstance().performAction<EmptyMsg>( KeyManager.getInstance().performAction<EmptyMsg>(
KeyTools.createKey<EmptyMsg, EmptyMsg>(FlightControllerKey.KeyStopAutoLanding), KeyTools.createKey<EmptyMsg, EmptyMsg>(FlightControllerKey.KeyStopAutoLanding),
object : CommonCallbacks.CompletionCallbackWithParam<EmptyMsg?> { object : CommonCallbacks.CompletionCallbackWithParam<EmptyMsg?> {
@ -1261,9 +1258,8 @@ open class MainActivity : BaseActivity() {
startArucoType = 0 startArucoType = 0
"REFRESH_VIDEO_SOURCE" -> { "REFRESH_VIDEO_SOURCE" -> {
// 起飞后刷新视频源避免FPVWidget因相机状态变化卡死 // 智能刷新:有云台模拟点击,没有云台直接刷新
swapVideoSource() smartRefreshVideoStream()
LogUtil.log(TAG,"刷新视频流")
} }
MqttCallBack.FLAG_RESET_CLEAN_MODE -> MqttCallBack.FLAG_RESET_CLEAN_MODE ->

View File

@ -25,8 +25,9 @@ public class MqttActionCallBack implements IMqttActionListener {
@Override @Override
public void onSuccess(IMqttToken asyncActionToken) { public void onSuccess(IMqttToken asyncActionToken) {
ToastUtil.showToast("MQtt连接成功");
LogUtil.log(TAG, "MQtt连接成功-------"); LogUtil.log(TAG, "MQtt连接成功-------");
// 异步显示Toast避免Binder IPC阻塞当前主线程消息导致ANR
new Handler().post(() -> ToastUtil.showToast("MQtt连接成功"));
try { try {
mqttAndroidClient.subscribe(AMSConfig.DOWN_UAV_SERVICES, 1);//订阅主题:注册 mqttAndroidClient.subscribe(AMSConfig.DOWN_UAV_SERVICES, 1);//订阅主题:注册
} catch (MqttException e) { } catch (MqttException e) {

View File

@ -28,6 +28,13 @@ public class DockCloseManager extends BaseManager {
// 是否允许继续重试开门后设为false防止关门重试把已开的门又关了 // 是否允许继续重试开门后设为false防止关门重试把已开的门又关了
private volatile boolean allowRetry = true; private volatile boolean allowRetry = true;
public void resetState() {
sendDockCloseSuccessTimes = 0;
isSendDockCloseSuccess = false;
allowRetry = true;
LogUtil.log(TAG, "关舱状态已重置");
}
public void stopRetry() { public void stopRetry() {
allowRetry = false; allowRetry = false;
} }

View File

@ -36,6 +36,12 @@ public class DockOpenManager extends BaseManager {
return DockOpenHolder.INSTANCE; return DockOpenHolder.INSTANCE;
} }
public void resetState() {
sendDockOpenSuccessTimes = 0;
isSendDockOpenSuccess = false;
LogUtil.log(TAG, "开舱状态已重置");
}
public void sendDockOpenMsg2Server() { public void sendDockOpenMsg2Server() {
if (sendDockOpenSuccessTimes >= maxRetries) { if (sendDockOpenSuccessTimes >= maxRetries) {
LogUtil.log(TAG, "达到最大重试次数或已发送开舱"+isSendDockOpenSuccess+sendDockOpenSuccessTimes); LogUtil.log(TAG, "达到最大重试次数或已发送开舱"+isSendDockOpenSuccess+sendDockOpenSuccessTimes);

View File

@ -432,6 +432,7 @@ public class FlightManager extends BaseManager {
public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) { public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) {
if (newValue != null) { if (newValue != null) {
Movement.getInstance().setWind_speed(newValue); Movement.getInstance().setWind_speed(newValue);
pushFlightAttitude(); 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 FLYING_HEIGHT_THRESHOLD = 10; // 开舱门飞行高度阈值
private static final int DISTANCE_THRESHOLD = 500; // 返航距离阈值 private static final int DISTANCE_THRESHOLD = 500; // 返航距离阈值
@ -1340,6 +1346,8 @@ public class FlightManager extends BaseManager {
Movement.getInstance().setTask_current_step(25); Movement.getInstance().setTask_current_step(25);
sendOpenCabinDoorMsg = false; sendOpenCabinDoorMsg = false;
DockOpenManager.getInstance().resetState();
DockCloseManager.getInstance().resetState();
} }
} }

View File

@ -269,41 +269,51 @@ public class GimbalManager extends BaseManager {
break; break;
//俯仰向下 //俯仰向下
case 3: case 3:
GimbalAngleRotation rotation1 = new GimbalAngleRotation(); // 跟随模式下发送ABSOLUTE_ANGLE旋转会冲突先切FREE模式
rotation1.setMode(GimbalAngleRotationMode.ABSOLUTE_ANGLE); KeyManager.getInstance().setValue(KeyTools.createKey(GimbalKey.KeyGimbalMode, ComponentIndexType.PORT_1), GimbalMode.FREE, new CommonCallbacks.CompletionCallback() {
if (!TextUtils.isEmpty(Movement.getInstance().getGimbal_yaw() + "")) { @Override
rotation1.setYaw(Movement.getInstance().getGimbal_yaw()); public void onSuccess() {
LogUtil.log(TAG,"俯仰向下"+Movement.getInstance().getGimbal_yaw()); LogUtil.log(TAG, "case3: 云台切换FREE模式成功");
} // 再发送绝对角度旋转
rotation1.setPitch(-90.0); GimbalAngleRotation rotation1 = new GimbalAngleRotation();
KeyManager.getInstance().performAction(KeyTools.createKey(GimbalKey.KeyRotateByAngle, ComponentIndexType.PORT_1), rotation1, new CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>() { 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<EmptyMsg>() {
@Override @Override
public void onSuccess(EmptyMsg emptyMsg) { 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 @Override
public void onFailure(@NonNull IDJIError error) { public void onFailure(@NonNull IDJIError error) {
sendFailMsg2Server(message, "偏航向下失败:" + getIDJIErrorMsg(error)); sendFailMsg2Server(message, "偏航向下失败:" + getIDJIErrorMsg(error));
} }
} });
); }
// GimbalAngleRotation rotation1 = new GimbalAngleRotation(); @Override
// rotation1.setMode(GimbalAngleRotationMode.ABSOLUTE_ANGLE); public void onFailure(@NonNull IDJIError idjiError) {
// rotation1.setPitch(-90.0); LogUtil.log(TAG, "case3: 云台切换FREE模式失败");
// KeyManager.getInstance().performAction(KeyTools.createKey(GimbalKey.KeyRotateByAngle, ComponentIndexType.PORT_1), rotation1, new CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>() { // 仍然尝试旋转可能已经在FREE模式
// @Override sendMsg2Server(message);
// public void onSuccess(EmptyMsg emptyMsg) { }
// sendMsg2Server(message); });
// }
//
// @Override
// public void onFailure(@NonNull IDJIError error) {
// sendFailMsg2Server(message, "偏航向下失败:" + getIDJIErrorMsg(error));
// }
// }
// );
break; break;
} }
@ -324,7 +334,7 @@ public class GimbalManager extends BaseManager {
// LookAtMode.LOOK_AT_GIMBAL_FOLLOWING : LookAtMode.LOOK_AT_GIMBAL_FREE); // LookAtMode.LOOK_AT_GIMBAL_FOLLOWING : LookAtMode.LOOK_AT_GIMBAL_FREE);
lookAtInfo.setMode(message.getData().isLocked() ? 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 locationCoordinate3D = new LocationCoordinate3D();
locationCoordinate3D.setLatitude(message.getData().getLatitude()); locationCoordinate3D.setLatitude(message.getData().getLatitude());

View File

@ -22,6 +22,7 @@ import com.aros.apron.entity.ApronExecutionStatus;
import com.aros.apron.entity.Movement; import com.aros.apron.entity.Movement;
import com.aros.apron.tools.LogUtil; import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.PreferenceUtils; import com.aros.apron.tools.PreferenceUtils;
import com.aros.apron.manager.StreamManager;
import com.autonavi.base.amap.mapcore.FileUtil; import com.autonavi.base.amap.mapcore.FileUtil;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -68,6 +69,11 @@ public class MediaManager extends BaseManager {
/* ===== 已上传文件名集合(本次任务内有效) ===== */ /* ===== 已上传文件名集合(本次任务内有效) ===== */
private final Set<String> uploadedFileNames = new HashSet<>(); private final Set<String> uploadedFileNames = new HashSet<>();
/* ===== S3 bucket是否已检查 ===== */
private volatile boolean bucketChecked = false;
/* ===== 下载失败重试计数 ===== */
private int downloadFailTimes = 0;
private static final int MAX_DOWNLOAD_RETRY = 3;
private MediaManager() { private MediaManager() {
} }
@ -100,6 +106,8 @@ public class MediaManager extends BaseManager {
public void enablePlayback() { public void enablePlayback() {
// 每次进入媒体模式时清空已上传文件集合 // 每次进入媒体模式时清空已上传文件集合
uploadedFileNames.clear(); uploadedFileNames.clear();
bucketChecked = false;
downloadFailTimes = 0;
// 重置失败计数和标志 // 重置失败计数和标志
enterPlayBackFailTimes = 0; enterPlayBackFailTimes = 0;
isEnablePlayback = false; isEnablePlayback = false;
@ -107,6 +115,7 @@ public class MediaManager extends BaseManager {
pullMediaFileListFromCameraFailTimes = 0; pullMediaFileListFromCameraFailTimes = 0;
updatingWaitCount = 0; updatingWaitCount = 0;
pullqwq = false; pullqwq = false;
pullStartTime = System.currentTimeMillis(); // 开始计时
LogUtil.log(TAG, "已清空上传文件集合"); LogUtil.log(TAG, "已清空上传文件集合");
@ -155,13 +164,25 @@ public class MediaManager extends BaseManager {
private int pullMediaFileListFromCameraFailTimes; private int pullMediaFileListFromCameraFailTimes;
private int updatingWaitCount = 0; 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 pullqwq = false;
private boolean isPullMediaFileListFromCameraSuccess; private boolean isPullMediaFileListFromCameraSuccess;
private long pullStartTime = 0; // 记录整个拉取流程开始时间
private static final int MAX_PULL_DURATION = 25; // 整个拉取流程最多25秒超时强制关机
private void pullMediaFileListFromCamera() { 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(); mState = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState();
LogUtil.log(TAG, "当前状态:" + mState + ",准备拉取文件列表"); LogUtil.log(TAG, "当前状态:" + mState + ",准备拉取文件列表(已耗时" + elapsed + "s");
// 1. 当状态为IDLE时需要调用pullMediaFileListFromCamera拉取全量数据 // 1. 当状态为IDLE时需要调用pullMediaFileListFromCamera拉取全量数据
// 2. 当状态为UP_TO_DATE时表示拉取完成可以获取数据 // 2. 当状态为UP_TO_DATE时表示拉取完成可以获取数据
@ -172,6 +193,8 @@ public class MediaManager extends BaseManager {
@Override @Override
public void onSuccess() { public void onSuccess() {
LogUtil.log(TAG, "拉取文件列表成功"); LogUtil.log(TAG, "拉取文件列表成功");
// 重置pullqwq标志下次调用重新从IDLE拉取
pullqwq = false;
// 拉取成功后等待状态变为UP_TO_DATE // 拉取成功后等待状态变为UP_TO_DATE
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000); new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000);
} }
@ -203,13 +226,13 @@ public class MediaManager extends BaseManager {
// 检查文件列表是否为空 // 检查文件列表是否为空
if (rawList == null || rawList.isEmpty()) { if (rawList == null || rawList.isEmpty()) {
LogUtil.log(TAG, "文件列表为空,重试拉取"); LogUtil.log(TAG, "文件列表为空,重试拉取");
// 重试拉取文件列表 // 状态已经是UP_TO_DATE时空列表可能确实无文件快速重试2次后放弃
if (pullMediaFileListFromCameraFailTimes < 5) { if (pullMediaFileListFromCameraFailTimes < 2) {
pullMediaFileListFromCameraFailTimes++; pullMediaFileListFromCameraFailTimes++;
LogUtil.log(TAG, "" + pullMediaFileListFromCameraFailTimes + "次重试..."); LogUtil.log(TAG, "" + pullMediaFileListFromCameraFailTimes + "次重试...");
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000); new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000);
} else { } else {
LogUtil.log(TAG, "重试次数达到上限,拉取失败"); LogUtil.log(TAG, "UP_TO_DATE状态文件列表持续为空确认无文件");
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
sendEvent2Server("拉取媒体文件失败",2); sendEvent2Server("拉取媒体文件失败",2);
disablePlayback(); disablePlayback();
@ -240,6 +263,8 @@ public class MediaManager extends BaseManager {
if (mediaFiles.isEmpty()) { if (mediaFiles.isEmpty()) {
LogUtil.log(TAG, "所有文件均已上传,直接清理"); LogUtil.log(TAG, "所有文件均已上传,直接清理");
downLoadMediaFileIndex = 0; downLoadMediaFileIndex = 0;
// 提前设置关机标志 aircraftStoredReply 能立即回复成功
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
removeAllFiles(); removeAllFiles();
return; return;
} }
@ -249,8 +274,8 @@ public class MediaManager extends BaseManager {
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.log(TAG, "获取文件列表数据失败: " + e.getMessage()); LogUtil.log(TAG, "获取文件列表数据失败: " + e.getMessage());
// 发生异常时重试 // 发生异常时快速重试2次
if (pullMediaFileListFromCameraFailTimes < 5) { if (pullMediaFileListFromCameraFailTimes < 2) {
pullMediaFileListFromCameraFailTimes++; pullMediaFileListFromCameraFailTimes++;
LogUtil.log(TAG, "" + pullMediaFileListFromCameraFailTimes + "次重试..."); LogUtil.log(TAG, "" + pullMediaFileListFromCameraFailTimes + "次重试...");
new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000); new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000);
@ -319,6 +344,7 @@ public class MediaManager extends BaseManager {
} }
LogUtil.log(TAG, "File size: " + mediaFile.getFileSize()); LogUtil.log(TAG, "File size: " + mediaFile.getFileSize());
downloadFailTimes = 0;
File dirs = new File(getSDCardPath() + mediaFileDir); File dirs = new File(getSDCardPath() + mediaFileDir);
if (!dirs.exists()) { if (!dirs.exists()) {
@ -341,7 +367,7 @@ public class MediaManager extends BaseManager {
final BufferedOutputStream finalBos = new BufferedOutputStream(finalOutputStream); final BufferedOutputStream finalBos = new BufferedOutputStream(finalOutputStream);
bos = finalBos; bos = finalBos;
mediaFile.pullOriginalMediaFileFromCamera(0L, new MediaFileDownloadListener() { mediaFile.pullOriginalMediaFileFromCamera(offset, new MediaFileDownloadListener() {
@Override @Override
public void onStart() { public void onStart() {
// No action needed for start // No action needed for start
@ -393,10 +419,19 @@ public class MediaManager extends BaseManager {
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Error closing file: " + e.getMessage()); Log.e(TAG, "Error closing file: " + e.getMessage());
} }
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); // 下载失败重试机制
sendEvent2Server("" + downLoadMediaFileIndex + "个文件下载失败",2); if (downloadFailTimes < MAX_DOWNLOAD_RETRY) {
disablePlayback(); downloadFailTimes++;
LogUtil.log(TAG, "发送关闭无人机"); 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() { public String getAWSSecretKey() {
return PreferenceUtils.getInstance().getSecretKey(); // minio的密钥 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) @RequiresApi(Build.VERSION_CODES.O)
public void minIOUpLoad(final File file, final MediaFile mediaFile) { public void minIOUpLoad(final File file, final MediaFile mediaFile) {
@ -437,9 +475,17 @@ public class MediaManager extends BaseManager {
public void subscribe(ObservableEmitter<String> emitter) throws Exception { public void subscribe(ObservableEmitter<String> emitter) throws Exception {
// 服务器地址 // 服务器地址
s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl()); // http://ip:端口号 s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl()); // http://ip:端口号
boolean bucketExists = s3.doesBucketExist(PreferenceUtils.getInstance().getBucketName()); // Bucket只在首次上传时检查创建后续上传不再重复请求
if (!bucketExists) { if (!bucketChecked) {
s3.createBucket(PreferenceUtils.getInstance().getBucketName()); synchronized (this) {
if (!bucketChecked) {
boolean bucketExists = s3.doesBucketExist(PreferenceUtils.getInstance().getBucketName());
if (!bucketExists) {
s3.createBucket(PreferenceUtils.getInstance().getBucketName());
}
bucketChecked = true;
}
}
} }
// 上传文件到网关MINIO存储服务 // 上传文件到网关MINIO存储服务
@ -503,6 +549,8 @@ public class MediaManager extends BaseManager {
@Override @Override
public void onNext(String url) { public void onNext(String url) {
// 上传成功重置下载重试计数
downloadFailTimes = 0;
//上传完成发送事件 //上传完成发送事件
sendMediaUpload2Server(mediaFile.getFileName(),mediaFiles.size(),downLoadMediaFileIndex); sendMediaUpload2Server(mediaFile.getFileName(),mediaFiles.size(),downLoadMediaFileIndex);
} }
@ -528,7 +576,8 @@ public class MediaManager extends BaseManager {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@Override @Override
public void onComplete() { public void onComplete() {
// 每上传一张就清除缓存 // 每上传一张就清除缓存并记录已上传文件名
uploadedFileNames.add(mediaFile.getFileName());
FileUtil.deleteFile(file); FileUtil.deleteFile(file);
LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " uploaded successfully."); LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " uploaded successfully.");
sendEvent2Server( "" + downLoadMediaFileIndex + "个文件已上传",1); sendEvent2Server( "" + downLoadMediaFileIndex + "个文件已上传",1);
@ -579,6 +628,8 @@ public class MediaManager extends BaseManager {
//退出媒体模式 //退出媒体模式
public void disablePlayback() { public void disablePlayback() {
// 任务结束停止视频流刷新定时器
StreamManager.getInstance().stopStreamRefreshTimer();
MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() { MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() {
@Override @Override
public void onSuccess() { public void onSuccess() {

View File

@ -568,7 +568,7 @@ public class MissionV3Manager extends BaseManager {
boolean isMissionStateValid = (missionStateCode == 2 || missionStateCode == 0 || missionStateCode == 7); boolean isMissionStateValid = (missionStateCode == 2 || missionStateCode == 0 || missionStateCode == 7);
boolean isPlaneMessageValid = !TextUtils.isEmpty(planeMessage) && !planeMessage.equals("无法起飞"); boolean isPlaneMessageValid = !TextUtils.isEmpty(planeMessage) && !planeMessage.equals("无法起飞");
boolean isGpsQualityValid = (quality == 4 || quality == 5 || quality == 10); 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); LogUtil.log(TAG, "isMissionStateValid" + isMissionStateValid + "isPlaneMessageValid" + isPlaneMessageValid + "isGpsQualityValid" + isGpsQualityValid);
// if (isMissionStateValid && isPlaneMessageValid && isGpsQualityValid) { // if (isMissionStateValid && isPlaneMessageValid && isGpsQualityValid) {
@ -794,6 +794,9 @@ public class MissionV3Manager extends BaseManager {
PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false);
PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false);
PreferenceUtils.getInstance().setTriggerToAlternatePoint(false); PreferenceUtils.getInstance().setTriggerToAlternatePoint(false);
// 重置开舱门标志避免上次任务未正常落地导致本次无法开舱
FlightManager.getInstance().resetOpenCabinDoorState();
DockOpenManager.getInstance().resetState();
PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id()); PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id());
CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() { CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() {
@Override @Override
@ -822,7 +825,7 @@ public class MissionV3Manager extends BaseManager {
if (!isMissionStart) { if (!isMissionStart) {
if (missionStateCode != 3 && missionStateCode != 4 && missionStateCode != 5 && missionStateCode != 6 if (missionStateCode != 3 && missionStateCode != 4 && missionStateCode != 5 && missionStateCode != 6
&& missionStateCode != 7 && missionStateCode != 8 && missionStateCode != 9 && missionStateCode != 10) { && missionStateCode != 7 && missionStateCode != 8 && missionStateCode != 9 && missionStateCode != 10) {
if (startMissionFailTimes < 50) { if (startMissionFailTimes < 80) {
mainHandler.postDelayed(new Runnable() { mainHandler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@ -68,7 +68,7 @@ public class OSDManager extends BaseManager {
return; return;
} }
lastExecuteTime = now; lastExecuteTime = now;
Boolean isConnect = KeyManager.getInstance().getValue(createKey(FlightControllerKey.KeyConnection)); Boolean isConnect = KeyManager.getInstance().getValue(createKey(FlightControllerKey.KeyAirSenseSystemConnected));
if (!Movement.getInstance().isMissionFinish()) { if (!Movement.getInstance().isMissionFinish()) {
if (isConnect != null && isConnect) { if (isConnect != null && isConnect) {
pushFlightAttitude(); pushFlightAttitude();

View File

@ -48,9 +48,60 @@ public class StreamManager extends BaseManager {
private final ExecutorService streamExecutor = Executors.newSingleThreadExecutor(); private final ExecutorService streamExecutor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper()); 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() { 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 class StreamHolder {
private static final StreamManager INSTANCE = new StreamManager(); private static final StreamManager INSTANCE = new StreamManager();
} }
@ -61,6 +112,7 @@ public class StreamManager extends BaseManager {
// ========== 新增重置推流状态用于端口关闭后重启 ========== // ========== 新增重置推流状态用于端口关闭后重启 ==========
public void resetStreamState() { public void resetStreamState() {
stopStreamRefreshTimer(); // 重置时也停止定时器
startLiveFailTimes = 0; startLiveFailTimes = 0;
isLiveStreamAlreadyStart = false; isLiveStreamAlreadyStart = false;
isStartingRTSP = false; // 重置并发标志 isStartingRTSP = false; // 重置并发标志
@ -293,17 +345,18 @@ public class StreamManager extends BaseManager {
LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动"); LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动");
isLiveStreamAlreadyStart = true; isLiveStreamAlreadyStart = true;
isStartingRTSP = false; isStartingRTSP = false;
startStreamRefreshTimer(); // 推流在运行就启动定时器
SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行 SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行
return; return;
} }
// 2. 检查相机流是否准备好 // 2. 检查相机流是否准备好
if (!MainActivity.Companion.getStreamReceive()) { if (!MainActivity.Companion.getStreamReceive()) {
LogUtil.log(TAG, "相机流未准备好,尝试切换 FPV Widget 恢复"); LogUtil.log(TAG, "相机流未准备好,尝试模拟点击 FPV Widget 恢复");
mainHandler.post(() -> { mainHandler.post(() -> {
MainActivity mainActivity = MainActivity.Companion.getInstance(); MainActivity mainActivity = MainActivity.Companion.getInstance();
if (mainActivity != null) { if (mainActivity != null) {
mainActivity.swapVideoSource(); mainActivity.smartRefreshVideoStream();
} }
}); });
@ -312,6 +365,7 @@ public class StreamManager extends BaseManager {
if (startLiveFailTimes < 3) { if (startLiveFailTimes < 3) {
startLiveFailTimes++; startLiveFailTimes++;
LogUtil.log(TAG, "相机流未准备好,第" + startLiveFailTimes + "次重试"); LogUtil.log(TAG, "相机流未准备好,第" + startLiveFailTimes + "次重试");
isStartingRTSP = false; // 先重置标志让重试调用能正常进入
startLiveWithRTSP(); startLiveWithRTSP();
} else { } else {
LogUtil.log(TAG, "相机流未准备好,重试次数已达上限,强制启动"); LogUtil.log(TAG, "相机流未准备好,重试次数已达上限,强制启动");
@ -320,6 +374,7 @@ public class StreamManager extends BaseManager {
} }
}); });
}, 2000); }, 2000);
isStartingRTSP = false; // 释放锁让重试能正常进入
return; return;
} }
@ -417,10 +472,14 @@ public class StreamManager extends BaseManager {
public void onSuccess() { public void onSuccess() {
mainHandler.post(() -> { mainHandler.post(() -> {
LogUtil.log(TAG, "强制启动 RTSP 推流成功"); LogUtil.log(TAG, "强制启动 RTSP 推流成功");
// 统一刷新视频流
MainActivity mainActivity = MainActivity.Companion.getInstance(); MainActivity mainActivity = MainActivity.Companion.getInstance();
mainActivity.swapVideoSource(); if (mainActivity != null) {
mainActivity.smartRefreshVideoStream();
}
isLiveStreamAlreadyStart = true; isLiveStreamAlreadyStart = true;
isStartingRTSP = false; // 重置并发标志 isStartingRTSP = false; // 重置并发标志
startStreamRefreshTimer(); // 强制启动成功也启动定时器
SimplePortScanner.getInstance().startScan(); SimplePortScanner.getInstance().startScan();
}); });
} }
@ -450,6 +509,7 @@ public class StreamManager extends BaseManager {
LogUtil.log(TAG, "推流已在运行,跳过启动 (isRestart=" + isRestart + ")"); LogUtil.log(TAG, "推流已在运行,跳过启动 (isRestart=" + isRestart + ")");
isLiveStreamAlreadyStart = true; isLiveStreamAlreadyStart = true;
isStartingRTSP = false; // 重置并发标志 isStartingRTSP = false; // 重置并发标志
startStreamRefreshTimer();
SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行 SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行
return; return;
} }
@ -466,6 +526,8 @@ public class StreamManager extends BaseManager {
startLiveFailTimes = 0; // 重置失败计数 startLiveFailTimes = 0; // 重置失败计数
isStartingRTSP = false; // 重置并发标志 isStartingRTSP = false; // 重置并发标志
LogUtil.log(TAG, "========== RTSP 推流启动成功 =========="); LogUtil.log(TAG, "========== RTSP 推流启动成功 ==========");
// 启动5秒定时刷新视频流防止起飞卡死
startStreamRefreshTimer();
// 开始端口扫描 // 开始端口扫描
SimplePortScanner.getInstance().startScan(); SimplePortScanner.getInstance().startScan();
}); });

View File

@ -383,6 +383,11 @@ public class TakeOffToPointManager extends BaseManager {
PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false);
PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false);
PreferenceUtils.getInstance().setTriggerToAlternatePoint(false); PreferenceUtils.getInstance().setTriggerToAlternatePoint(false);
// 重置开舱门标志避免上次任务未正常落地导致本次无法开舱
FlightManager.getInstance().resetOpenCabinDoorState();
DockOpenManager.getInstance().resetState();
PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id()); PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id());
CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() { CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() {
@Override @Override

View File

@ -657,7 +657,7 @@ public class Aprondown {
private void performOperation() { private void performOperation() {
LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount); LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount);
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5.5); DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6);
handlerCallbackCount++; handlerCallbackCount++;
} }

View File

@ -907,7 +907,7 @@ public class Aprongim {
private void performOperation() { private void performOperation() {
LogUtil.log(TAG_LOG, String.format("【执行移动】速降 vz=-4 count=%d/20", handlerCallbackCount)); 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++; handlerCallbackCount++;
} }

View File

@ -635,7 +635,7 @@ public class ApronArucoDetect {
private void performOperation() { private void performOperation() {
LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount); LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount);
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5.5); DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6);
handlerCallbackCount++; handlerCallbackCount++;
} }

View File

@ -870,7 +870,7 @@ public class ApronArucoDetectPort {
private void performOperation() { private void performOperation() {
LogUtil.log(TAG_LOG, String.format("【执行移动】速降 vz=-4 count=%d/20", handlerCallbackCount)); 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++; handlerCallbackCount++;
} }

View File

@ -101,11 +101,16 @@ public class Generakmzaltheratools extends BaseManager {
//全局过度速度 //全局过度速度
config.setGlobalTransitionalSpeed(7.0); 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.setIsGlobalRTHHeightSet(true);
//安全起飞高度 //安全起飞高度
config.setSecurityTakeOffHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight())); config.setSecurityTakeOffHeight(wpellheight);
config.setIsSecurityTakeOffHeightSet(true); config.setIsSecurityTakeOffHeightSet(true);
@ -125,7 +130,7 @@ public class Generakmzaltheratools extends BaseManager {
//double wpheight=GpsUtils.egm96Altitude(data.getTargetHeight(),data.getTargetLatitude(),data.getTargetLongitude()); //double wpheight=GpsUtils.egm96Altitude(data.getTargetHeight(),data.getTargetLatitude(),data.getTargetLongitude());
//double wp1ellheighet=GpsUtils.wgs84Altitude(); //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.setWaypointIndex(0);
wp1.setLocation(new WaylineLocationCoordinate2D(lat.getLatitude(), lat.getLongitude())); wp1.setLocation(new WaylineLocationCoordinate2D(lat.getLatitude(), lat.getLongitude()));

View File

@ -180,6 +180,9 @@
app:layout_constraintTop_toBottomOf="@+id/widget_remaining_flight_time" app:layout_constraintTop_toBottomOf="@+id/widget_remaining_flight_time"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" /> tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
<dji.v5.ux.cameracore.widget.cameracontrols.CameraControlsWidget <dji.v5.ux.cameracore.widget.cameracontrols.CameraControlsWidget
android:id="@+id/widget_camera_controls" android:id="@+id/widget_camera_controls"
android:layout_width="0dp" android:layout_width="0dp"