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 fd0f8358..32c163c9 100644 --- a/app/src/main/java/com/aros/apron/activity/MainActivity.kt +++ b/app/src/main/java/com/aros/apron/activity/MainActivity.kt @@ -7,10 +7,12 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper +import android.util.Log import android.view.View import android.view.Window import android.view.WindowManager import android.widget.Button +import android.widget.Toast import android.widget.TextView import androidx.annotation.RequiresApi import androidx.constraintlayout.widget.ConstraintLayout @@ -49,6 +51,7 @@ import com.aros.apron.mix.Aprongim import com.aros.apron.tools.AlternateArucoDetect import com.aros.apron.tools.ApronArucoDetect import com.aros.apron.tools.ApronArucoDetectPort +import com.aros.apron.tools.ApronArucodownmany import com.aros.apron.tools.DroneHelper import com.aros.apron.tools.LogUtil import com.aros.apron.tools.MqttManager @@ -128,6 +131,8 @@ open class MainActivity : BaseActivity() { // 如果不需要改变 isAppStarted 的值,可以直接这样声明 var isAppStarted: Boolean = false var streamReceive: Boolean = false + + var isscousse: Boolean=false; private var instance: MainActivity? = null fun getInstance(): MainActivity? { @@ -136,6 +141,9 @@ open class MainActivity : BaseActivity() { private var vtxHeartbeatHandler: Handler? = null private var lastVtxFrameTime: Long = 0 + private var lastPortToastTime: Long = 0 + private var lastFpvToastTime: Long = 0 + private const val TOAST_INTERVAL_MS = 3000L private const val VTX_HEARTBEAT_TIMEOUT = 2000L // 3秒没有收到帧就认为图传异常 private var isVtxHeartbeatRunning = false @@ -242,7 +250,7 @@ open class MainActivity : BaseActivity() { private var cameraManager: ICameraStreamManager = MediaDataCenter.getInstance().cameraStreamManager - private var startArucoType = 0 //1执行机库二维码识别 2执行备降点二维码识别 3扫到任何码触发刹停 + private var startArucoType = 0 //1执行机库二维码识别 2执行备降点二维码识别 3扫到任何码触发刹停 private var dictionary: Dictionary? = null override fun useEventBus(): Boolean { return true @@ -282,7 +290,12 @@ open class MainActivity : BaseActivity() { .throttleLast(500, TimeUnit.MILLISECONDS) .subscribeOn(io()) .subscribe(Consumer { result: CameraSource -> - runOnUiThread { onCameraSourceUpdated(result.devicePosition, result.lensType) } + runOnUiThread { + onCameraSourceUpdated( + result.devicePosition, + result.lensType + ) + } }) ) compositeDisposable!!.add( @@ -513,7 +526,7 @@ open class MainActivity : BaseActivity() { primaryFpvWidget?.post { refreshVideoStream() } - //LogUtil.log(TAG, "智能刷新:无云台,直接刷新主视频流") + //LogUtil.log(TAG, "智能刷新:无云台,直接刷新主视频流") } } @@ -740,13 +753,14 @@ open class MainActivity : BaseActivity() { // 根据 cameraLocationType 轮询等待连接 while (true) { - val flightConnected = isFlightControllerConnect != null && isFlightControllerConnect == true + val flightConnected = + isFlightControllerConnect != null && isFlightControllerConnect == true val cameraConnected = isCameraConnect != null && isCameraConnect == true val shouldInit = when (cameraLocationType) { 3 -> flightConnected - 4, 5 -> flightConnected || cameraConnected - else -> flightConnected || cameraConnected + 1, 2, 4, 5 -> flightConnected && cameraConnected + else -> flightConnected && cameraConnected } if (shouldInit) break @@ -762,7 +776,12 @@ open class MainActivity : BaseActivity() { KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection)) isCameraConnect = KeyManager.getInstance() - .getValue(KeyTools.createKey(CameraKey.KeyConnection, ComponentIndexType.PORT_1)) + .getValue( + KeyTools.createKey( + CameraKey.KeyConnection, + ComponentIndexType.PORT_1 + ) + ) } // 飞控/相机连接后,在主线程执行初始化 @@ -804,16 +823,18 @@ open class MainActivity : BaseActivity() { GimbalManager.getInstance().setmode() PayloadWidgetManager.getInstance().initPayloadInfo() - if (PreferenceUtils.getInstance().lteEnable){ + if (PreferenceUtils.getInstance().lteEnable) { // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) MLTEManager.getInstance().initLTEManager() - Handler().postDelayed(Runnable { MLTEManager.getInstance().setLTEEnhancedTransmissionType()},3000) + Handler().postDelayed(Runnable { + MLTEManager.getInstance().setLTEEnhancedTransmissionType() + }, 3000) } - LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) + LogUtil.log("qwq", "lteEnable" + PreferenceUtils.getInstance().lteEnable) LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType) @@ -848,10 +869,12 @@ open class MainActivity : BaseActivity() { // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) - if (PreferenceUtils.getInstance().lteEnable){ - // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) + if (PreferenceUtils.getInstance().lteEnable) { + // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) MLTEManager.getInstance().initLTEManager() - Handler().postDelayed(Runnable { MLTEManager.getInstance().setLTEEnhancedTransmissionType()},3000) + Handler().postDelayed(Runnable { + MLTEManager.getInstance().setLTEEnhancedTransmissionType() + }, 3000) } @@ -914,12 +937,14 @@ open class MainActivity : BaseActivity() { PayloadWidgetManager.getInstance().initPayloadInfo() - // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) + // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) - if (PreferenceUtils.getInstance().lteEnable){ + if (PreferenceUtils.getInstance().lteEnable) { // LogUtil.log("qwq","lteEnable"+PreferenceUtils.getInstance().lteEnable) MLTEManager.getInstance().initLTEManager() - Handler().postDelayed(Runnable { MLTEManager.getInstance().setLTEEnhancedTransmissionType()},3000) + Handler().postDelayed(Runnable { + MLTEManager.getInstance().setLTEEnhancedTransmissionType() + }, 3000) } @@ -947,17 +972,24 @@ open class MainActivity : BaseActivity() { private fun initMixedStream() { // 初始化融合视觉降落识别器 //val mixedLanding = MixedVisionLanding.getInstance() - // 为 PORT_1(云台相机)添加帧监听器 - - cameraManager.addFrameListener( ComponentIndexType.PORT_1, ICameraStreamManager.FrameFormat.YUV420_888 ) { frameData, _, _, width, height, _ -> + // LogUtil.log(TAG,"port监听回调了addFrameListener") +// runOnUiThread { +// val now = System.currentTimeMillis() +// if (now - lastPortToastTime >= TOAST_INTERVAL_MS) { +// lastPortToastTime = now +// Toast.makeText(this, "port监听回调了addFrameListener", Toast.LENGTH_SHORT).show() +// } +// } + Movement.getInstance().isVtx = true updateVtxHeartbeat() streamReceive = true + // 使用融合视觉识别器处理云台相机帧 //mixedLanding.processGimbalFrame(height, width, frameData, dictionary) //使用云台 @@ -965,6 +997,12 @@ open class MainActivity : BaseActivity() { if (!Synchronizedstatus.isIsruningframe() && Synchronizedstatus.isAprongim() && Synchronizedstatus.isSwitchtime()) { try { Synchronizedstatus.setIsruningframe(true) + + if(!isscousse){ + isscousse=true; + LogUtil.log(TAG,"mix视频帧回调了") + } + if (startArucoType == 1) { Aprongim.getInstance()?.detectArucoTags( height, @@ -990,10 +1028,8 @@ open class MainActivity : BaseActivity() { } finally { Synchronizedstatus.setIsruningframe(false) } - } } - } // 为 FPV(下视相机)添加帧监听器 @@ -1001,6 +1037,15 @@ open class MainActivity : BaseActivity() { ComponentIndexType.FPV, ICameraStreamManager.FrameFormat.YUV420_888 ) { frameData, _, _, width, height, _ -> + + //LogUtil.log(TAG,"fpv监听回调了addFrameListener") +// runOnUiThread { +// val now = System.currentTimeMillis() +// if (now - lastFpvToastTime >= TOAST_INTERVAL_MS) { +// lastFpvToastTime = now +// Toast.makeText(this, "fpv监听回调了addFrameListener", Toast.LENGTH_SHORT).show() +// } +// } Movement.getInstance().isVtx = true updateVtxHeartbeat() streamReceive = true @@ -1011,6 +1056,9 @@ open class MainActivity : BaseActivity() { if (!Synchronizedstatus.isIsruningframe() && !Synchronizedstatus.isAprongim() && Synchronizedstatus.isSwitchtime()) { try { Synchronizedstatus.setIsruningframe(true) + + + if (startArucoType == 1) { Aprondown.getInstance()?.detectArucoTags( height, @@ -1042,7 +1090,6 @@ open class MainActivity : BaseActivity() { } } - @SuppressLint("SuspiciousIndentation") private fun initFpvStream() { cameraManager.addFrameListener( @@ -1059,6 +1106,12 @@ open class MainActivity : BaseActivity() { if (!Synchronizedstatus.isIsruningframe()) { try { Synchronizedstatus.setIsruningframe(true) + + if(!isscousse){ + isscousse=true; + LogUtil.log(TAG,"port视频帧回调了") + } + if (startArucoType == 1) { ApronArucoDetect.getInstance()?.detectArucoTags( height, @@ -1091,8 +1144,6 @@ open class MainActivity : BaseActivity() { } - - @SuppressLint("SuspiciousIndentation") private fun initCameraStream() { cameraManager.addFrameListener( @@ -1109,6 +1160,12 @@ open class MainActivity : BaseActivity() { if (!Synchronizedstatus.isIsruningframe()) { try { Synchronizedstatus.setIsruningframe(true) + + if(!isscousse){ + isscousse=true; + LogUtil.log(TAG,"fpv视频帧回调了") + } + if (startArucoType == 1) { ApronArucoDetectPort.getInstance()?.detectArucoTags( height, @@ -1156,7 +1213,6 @@ open class MainActivity : BaseActivity() { fun onEvent(message: String?) { when (message) { FLAG_START_DETECT_ARUCO_APRON -> { - MediaDataCenter.getInstance().getCameraStreamManager().setVisionAssistViewDirection( VisionAssistDirection.DOWN, object : CompletionCallback { @@ -1201,8 +1257,8 @@ open class MainActivity : BaseActivity() { }, 6000) } else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) { Handler().postDelayed(Runnable { - if (!Aprondown.getInstance().isTriggerSuccess) { - LogUtil.log(TAG, "图传异常:飞往备降点") + if (!Aprongim.getInstance().isTriggerSuccess) { + LogUtil.log(TAG, "图传异常:飞往备降点"+ Movement.getInstance().isVtx) //测试图传丢失 AlternateLandingManager.getInstance().startTaskProcess(null) } @@ -1251,6 +1307,7 @@ open class MainActivity : BaseActivity() { } }) } + FLAG_START_DETECT_ARUCO_ALTERNATE -> KeyManager.getInstance().performAction( KeyTools.createKey(FlightControllerKey.KeyStopAutoLanding), @@ -1268,11 +1325,11 @@ open class MainActivity : BaseActivity() { } else if (PreferenceUtils.getInstance().cameraLocationType == 4 || PreferenceUtils.getInstance().cameraLocationType == 5) { Handler().postDelayed(Runnable { if (!Aprongim.getInstance().isTriggerSuccess) { - LogUtil.log(TAG, "图传异常:飞往备降点") - //测试图传丢失 + LogUtil.log(TAG, "备降点图传异常:飞往备降点") AlternateLandingManager.getInstance().startTaskProcess(null) } }, 6000) + } else { Handler().postDelayed(Runnable { if (!ApronArucoDetectPort.getInstance().isTriggerSuccess) { diff --git a/app/src/main/java/com/aros/apron/base/BaseManager.java b/app/src/main/java/com/aros/apron/base/BaseManager.java index f6df38f1..cefc2252 100644 --- a/app/src/main/java/com/aros/apron/base/BaseManager.java +++ b/app/src/main/java/com/aros/apron/base/BaseManager.java @@ -86,26 +86,29 @@ public abstract class BaseManager { * @param entity */ public void sendMsg2Server(MessageDown entity) { - try { - if (MqttManager.getInstance().mqttAndroidClient.isConnected()) { - MessageReply messageReply = new MessageReply(); - messageReply.setBid(entity.getBid()); - messageReply.setTid(entity.getTid()); - messageReply.setTimestamp(entity.getTimestamp()); - messageReply.setMethod(entity.getMethod()); - MessageReply.Data data=new MessageReply.Data(); - data.setResult(0); - messageReply.setData(data); - MqttMessage mqttMessage = new MqttMessage(new Gson().toJson(messageReply).getBytes("UTF-8")); - mqttMessage.setQos(0); - MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_SERVICES_REPLY, mqttMessage); - } else { - LogUtil.log(TAG, "回复失败:mqtt 未连接"); + // 延迟 500ms 发送,避开媒体/日志上传等突发流量高峰 + mainHandler.postDelayed(() -> { + try { + if (MqttManager.getInstance().mqttAndroidClient.isConnected()) { + MessageReply messageReply = new MessageReply(); + messageReply.setBid(entity.getBid()); + messageReply.setTid(entity.getTid()); + messageReply.setTimestamp(entity.getTimestamp()); + messageReply.setMethod(entity.getMethod()); + MessageReply.Data data = new MessageReply.Data(); + data.setResult(0); + messageReply.setData(data); + MqttMessage mqttMessage = new MqttMessage(new Gson().toJson(messageReply).getBytes("UTF-8")); + mqttMessage.setQos(1); + MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_SERVICES_REPLY, mqttMessage); + } else { + LogUtil.log(TAG, "回复失败:mqtt 未连接"); + } + } catch (Exception e) { + e.printStackTrace(); + LogUtil.log(TAG, "回复异常:" + e.toString()); } - } catch (Exception e) { - e.printStackTrace(); - LogUtil.log(TAG, "回复异常:" + e.toString()); - } + }, 500); } final Handler mainHandler = new Handler(Looper.getMainLooper()); @@ -355,9 +358,23 @@ public abstract class BaseManager { breakPoint.setWayline_id(Movement.getInstance().getTask_wayline_id()); ext.setBreak_point(breakPoint); - output.setStatus("partially_done"); + + + //如果飞备降点了 + if(Movement.getInstance().isAlternate()){ + output.setStatus("failed"); + }else{ + output.setStatus("partially_done"); + } + } else { - output.setStatus("ok"); + //如果飞备降点了 + if(Movement.getInstance().isAlternate()){ + output.setStatus("failed"); + }else{ + output.setStatus("ok"); + } + LogUtil.log(TAG, "未查询到断点信息"); } @@ -391,7 +408,12 @@ public abstract class BaseManager { public void onFailure(@NonNull IDJIError idjiError) { // 查询失败也要发,status=ok FlightTaskProgress.Data.Output output = new FlightTaskProgress.Data.Output(); - output.setStatus("ok"); + //如果飞备降点了 + if(Movement.getInstance().isAlternate()){ + output.setStatus("failed"); + }else{ + output.setStatus("ok"); + } output.setExt(ext); output.setProgress(progress); @@ -406,6 +428,10 @@ public abstract class BaseManager { flightTaskProgress.setMethod(Constant.FLIGHT_TASK_PROGRESS); flightTaskProgress.setData(data); + + + + try { MqttMessage mqttMessage = new MqttMessage(new Gson().toJson(flightTaskProgress).getBytes("UTF-8")); mqttMessage.setQos(0); @@ -418,6 +444,7 @@ public abstract class BaseManager { } }); + } else { // 不是结束状态 → 立即发 FlightTaskProgress.Data.Output output = new FlightTaskProgress.Data.Output(); @@ -460,7 +487,7 @@ public abstract class BaseManager { if (breakPointInfo != null) { LogUtil.log(TAG, "查询断点成功:" + new Gson().toJson(breakPointInfo)); Movement.getInstance().setTask_attitude_head(Movement.getInstance().getAttitude_head()); - Movement.getInstance().setTask_break_reason(2); + //Movement.getInstance().setTask_break_reason(2); Movement.getInstance().setTask_index(Movement.getInstance().getTask_current_waypoint_index()); Movement.getInstance().setHeight(breakPointInfo.getLocation().getAltitude()); Movement.getInstance().setTask_latitude(breakPointInfo.getLocation().getLatitude()); diff --git a/app/src/main/java/com/aros/apron/callback/MqttCallBack.java b/app/src/main/java/com/aros/apron/callback/MqttCallBack.java index 1d202050..7b006a3b 100644 --- a/app/src/main/java/com/aros/apron/callback/MqttCallBack.java +++ b/app/src/main/java/com/aros/apron/callback/MqttCallBack.java @@ -1,6 +1,8 @@ package com.aros.apron.callback; +import static dji.sdk.keyvalue.key.KeyTools.createKey; + import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -191,14 +193,10 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { //存储最新消息并开始自检 MissionV3Manager.getInstance().taskExecute(message); Synchronizedstatus.setIsruning(true); - Synchronizedstatus.setFlighttaskExecuteStatus(true); - }else{ - sendFailMsg2Server(message,"已经收到航线"); } - } } break; @@ -223,7 +221,6 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { // }else{ // FlightManager.getInstance().startGoHome(message); // } - FlightManager.getInstance().startGoHome(message); } else { sendFailMsg2Server(message,"正在视觉或飞往备降不允许返航"); @@ -255,7 +252,6 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { // //1.检查图传是否连接 // MissionDataBean data = new Gson().fromJson(new Gson().toJson(message.getData()), MissionDataBean.class); // Boolean generateKmz = Generakmztools.getInstance().generateKmz(data); - synchronized (lock) { if (Synchronizedstatus.isIsruning()) { LogUtil.log(TAG, "自检正在运行"); @@ -269,7 +265,6 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { }); } else if (Synchronizedstatus.getInitStatus()) { if(!Synchronizedstatus.isTakeoff_to_point()){ - LogUtil.log(TAG, "收到:一键起飞" + jsonString); //设置modecode Movement.getInstance().setMode_code(1); @@ -289,7 +284,12 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { break; case Constant.FLY_TO_POINT: LogUtil.log(TAG, "收到:飞向目标点" + jsonString); - FlyToPointManager.getInstance().taskExecute(message); + if(!Synchronizedstatus.isFlyto()){ + FlyToPointManager.getInstance().taskExecute(message); + Synchronizedstatus.setFlyto(true); + }else{ + return; + } break; case Constant.FLY_TO_POINT_STOP: LogUtil.log(TAG, "收到:结束 flyto 飞向目标点任务" + jsonString); @@ -297,6 +297,11 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { break; case Constant.FLY_TO_POINT_STOP_UPDATE: LogUtil.log(TAG, "收到:更新 flyto 目标点" + jsonString); + if(KeyManager.getInstance().getValue(createKey(FlightControllerKey.KeyIsFlying))==true){ + FlyToPointManager.getInstance().updateTarget(message); + }else{ + sendEvent2Server("飞机没起飞不允许指点",2); + } break; case Constant.FLIGHT_AUTHORITY_GRAB: LogUtil.log(TAG, "收到:飞行控制权抢夺" + jsonString); @@ -434,7 +439,6 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { break; case Constant.IR_METERING_POINT_SET: LogUtil.log(TAG, "收到:负载控制—红外测温点设置" + jsonString); - CameraManager.getInstance().setThermalSpotMetersurePoint(message); break; case Constant.IR_METERING_AREA_SET: @@ -521,7 +525,6 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { return; } } - break; case Constant.SPEAKER_PLAY_MODE_SET: LogUtil.log(TAG, "收到:喊话器-设置播放模式" + jsonString); @@ -679,6 +682,8 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended { + + // //获取控制权 // case 60007: // LogUtil.log(TAG, "收到:获取控制权" + jsonString); diff --git a/app/src/main/java/com/aros/apron/entity/FlyToPointProgress.java b/app/src/main/java/com/aros/apron/entity/FlyToPointProgress.java new file mode 100644 index 00000000..27fddaec --- /dev/null +++ b/app/src/main/java/com/aros/apron/entity/FlyToPointProgress.java @@ -0,0 +1,162 @@ +package com.aros.apron.entity; + +import java.util.List; + +/** + * 飞向目标点进度上报事件 + * Method: fly_to_point_progress + * Topic: thing/product/{gateway_sn}/events (up) + */ +public class FlyToPointProgress { + + private String bid; + private String tid; + private Long timestamp; + private String method; + private Integer needReply; + private Data data; + + public String getBid() { + return bid; + } + + public void setBid(String bid) { + this.bid = bid; + } + + public String getTid() { + return tid; + } + + public void setTid(String tid) { + this.tid = tid; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public Integer getNeedReply() { + return needReply; + } + + public void setNeedReply(Integer needReply) { + this.needReply = needReply; + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public static class Data { + private String fly_to_id; + private String status; // wayline_cancel | wayline_failed | wayline_ok | wayline_progress + private int result; // 非0代表错误 + private int way_point_index; + private Float remaining_distance; // 米 + private Float remaining_time; // 秒 + private List planned_path_points; + + public String getFly_to_id() { + return fly_to_id; + } + + public void setFly_to_id(String fly_to_id) { + this.fly_to_id = fly_to_id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getResult() { + return result; + } + + public void setResult(int result) { + this.result = result; + } + + public int getWay_point_index() { + return way_point_index; + } + + public void setWay_point_index(int way_point_index) { + this.way_point_index = way_point_index; + } + + public Float getRemaining_distance() { + return remaining_distance; + } + + public void setRemaining_distance(Float remaining_distance) { + this.remaining_distance = remaining_distance; + } + + public Float getRemaining_time() { + return remaining_time; + } + + public void setRemaining_time(Float remaining_time) { + this.remaining_time = remaining_time; + } + + public List getPlanned_path_points() { + return planned_path_points; + } + + public void setPlanned_path_points(List planned_path_points) { + this.planned_path_points = planned_path_points; + } + } + + public static class PlannedPathPoint { + private double latitude; + private double longitude; + private float height; + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public float getHeight() { + return height; + } + + public void setHeight(float height) { + this.height = height; + } + } +} diff --git a/app/src/main/java/com/aros/apron/entity/HMS.java b/app/src/main/java/com/aros/apron/entity/HMS.java new file mode 100644 index 00000000..1e41d88f --- /dev/null +++ b/app/src/main/java/com/aros/apron/entity/HMS.java @@ -0,0 +1,151 @@ +package com.aros.apron.entity; + +import java.util.List; + +public class HMS { + + private String bid; + private Data data; + private String tid; + private long timestamp; + private String method; + + public String getBid() { + return bid; + } + + public void setBid(String bid) { + this.bid = bid; + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public String getTid() { + return tid; + } + + public void setTid(String tid) { + this.tid = tid; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public static class Data { + private List list; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public static class ListItem { + private Args args; + private String code; + private String device_type; + private int imminent; + private int in_the_sky; + private int level; + private int module; + + public Args getArgs() { + return args; + } + + public void setArgs(Args args) { + this.args = args; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getDevice_type() { + return device_type; + } + + public void setDevice_type(String device_type) { + this.device_type = device_type; + } + + public int getImminent() { + return imminent; + } + + public void setImminent(int imminent) { + this.imminent = imminent; + } + + public int getIn_the_sky() { + return in_the_sky; + } + + public void setIn_the_sky(int in_the_sky) { + this.in_the_sky = in_the_sky; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public int getModule() { + return module; + } + + public void setModule(int module) { + this.module = module; + } + + public static class Args { + private int component_index; + private int sensor_index; + + public int getComponent_index() { + return component_index; + } + + public void setComponent_index(int component_index) { + this.component_index = component_index; + } + + public int getSensor_index() { + return sensor_index; + } + + public void setSensor_index(int sensor_index) { + this.sensor_index = sensor_index; + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aros/apron/entity/MessageDown.java b/app/src/main/java/com/aros/apron/entity/MessageDown.java index 1d5af8e5..f947362a 100644 --- a/app/src/main/java/com/aros/apron/entity/MessageDown.java +++ b/app/src/main/java/com/aros/apron/entity/MessageDown.java @@ -76,13 +76,13 @@ public class MessageDown { private String seq; private String w; //摇杆和云台 - private String x; - private String y; + private double x; + private double y; private String camera_type; private String payload_index; - private float height; + private double height; private double latitude; private boolean locked; private double longitude; @@ -95,7 +95,7 @@ public class MessageDown { private double commander_flight_height; private int commander_mode_lost_action; - private String max_speed; + private int max_speed; private int rc_lost_action; private int rth_altitude; private String security_takeoff_height; @@ -110,7 +110,7 @@ public class MessageDown { private double yaw_speed; - private float width; + private double width; @@ -267,10 +267,6 @@ public class MessageDown { private int speed; - public void setHeight(float height) { - this.height = height; - } - public void setCommander_flight_height(double commander_flight_height) { this.commander_flight_height = commander_flight_height; } @@ -327,11 +323,11 @@ public class MessageDown { this.language = language; } - public float getWidth() { + public double getWidth() { return width; } - public void setWidth(float width) { + public void setWidth(double width) { this.width = width; } @@ -377,15 +373,15 @@ public class MessageDown { } public static class Points { - private int height; - private double latitude; - private double longitude; + private double height; // 目标点高度(椭球高),WGS84模型 + private double latitude; // 目标点纬度,南纬负,北纬正 + private double longitude; // 目标点经度,东经正,西经负 - public int getHeight() { + public double getHeight() { return height; } - public void setHeight(int height) { + public void setHeight(double height) { this.height = height; } @@ -421,11 +417,11 @@ public class MessageDown { this.commander_mode_lost_action = commander_mode_lost_action; } - public String getMax_speed() { + public int getMax_speed() { return max_speed; } - public void setMax_speed(String max_speed) { + public void setMax_speed(int max_speed) { this.max_speed = max_speed; } @@ -517,11 +513,11 @@ public class MessageDown { this.zoom_factor = zoom_factor; } - public float getHeight() { + public double getHeight() { return height; } - public void setHeight(int height) { + public void setHeight(double height) { this.height = height; } @@ -589,19 +585,19 @@ public class MessageDown { this.w = w; } - public String getX() { + public double getX() { return x; } - public void setX(String x) { + public void setX(double x) { this.x = x; } - public String getY() { + public double getY() { return y; } - public void setY(String y) { + public void setY(double y) { this.y = y; } diff --git a/app/src/main/java/com/aros/apron/entity/Movement.java b/app/src/main/java/com/aros/apron/entity/Movement.java index 6b436e7e..ab5c1f17 100644 --- a/app/src/main/java/com/aros/apron/entity/Movement.java +++ b/app/src/main/java/com/aros/apron/entity/Movement.java @@ -1,6 +1,7 @@ package com.aros.apron.entity; +import java.util.ArrayList; import java.util.List; @@ -79,6 +80,8 @@ public class Movement { private String LTELinkType;//图传类型 1cusync 图传 3LTE 增强图传 private String ltePhoneAreaCode = "";//LTE认证手机号区号 private String ltePhoneNumber = "";//LTE认证手机号 + private boolean ocuSyncLte;//增强图传是否开启 + private int ocuSyncLteTime;//增强图传剩余时间(天) private int goHomeHeight;//返航高度 private int failsafeAction;//失控动作 private int heightLimit;//限高 @@ -199,9 +202,21 @@ public class Movement { private int camera_mode = 0; private int ir_metering_mode; - private int temperature; + private int temperature=65535; private double x; private double y; + // 区域测温相关 + private double ir_region_x; + private double ir_region_y; + private double ir_region_width; + private double ir_region_height; + private double ir_region_aver_temperature=65535; + private double ir_region_min_temperature_x; + private double ir_region_min_temperature_y; + private double ir_region_min_temperature; + private double ir_region_max_temperature_x; + private double ir_region_max_temperature_y; + private double ir_region_max_temperature; private int ir_zoom_factor = 2; private double bottom; private double left; @@ -282,6 +297,16 @@ public class Movement { private boolean waylineinterpter = true; + private volatile boolean opentemperate=false; + + + public boolean isOpentemperate() { + return opentemperate; + } + + public void setOpentemperate(boolean opentemperate) { + this.opentemperate = opentemperate; + } public boolean isWaylineinterpter() { return waylineinterpter; @@ -334,6 +359,13 @@ public class Movement { private double takeofftargetlongitude; private double takeofftargetheight; + // 返航轨迹相关字段 + private List return_home_path_points = new ArrayList<>(); // 返航轨迹点列表 + private double rth_start_latitude; // 返航起点纬度 + private double rth_start_longitude; // 返航起点经度 + private double rth_start_altitude; // 返航起点高度 + private boolean is_rth_tracking; // 是否正在返航轨迹采集中 + private int mission_type; public int getMission_type() { @@ -707,6 +739,36 @@ public class Movement { private volatile int flightmode = 0; //0代表没有操作 1代表航线飞行 2一键代表指令飞行 3flyto(一键和flyto) private boolean takeofftopoint; private boolean opendrc = false; //true 是开启 false 是关闭 + private String fly_to_id; // 飞向目标点任务ID + + // flyto 飞向目标点进度 + private double flyto_target_latitude; // 目标点纬度 + private double flyto_target_longitude; // 目标点经度 + private float flyto_target_height; // 目标点高度 + private float flyto_remaining_distance; // 剩余距离(米) + private float flyto_remaining_time; // 剩余时间(秒) + private int flyto_max_speed = 10; // 最大速度(m/s) + private String fly_to_point_progress; + + + + + + + + + + + + + + public String getFly_to_point_progress() { + return fly_to_point_progress; + } + + public void setFly_to_point_progress(String fly_to_point_progress) { + this.fly_to_point_progress = fly_to_point_progress; + } public boolean isTakeofftopoint() { return takeofftopoint; @@ -724,6 +786,62 @@ public class Movement { this.opendrc = opendrc; } + public String getFly_to_id() { + return fly_to_id; + } + + public void setFly_to_id(String fly_to_id) { + this.fly_to_id = fly_to_id; + } + + public double getFlyto_target_latitude() { + return flyto_target_latitude; + } + + public void setFlyto_target_latitude(double flyto_target_latitude) { + this.flyto_target_latitude = flyto_target_latitude; + } + + public double getFlyto_target_longitude() { + return flyto_target_longitude; + } + + public void setFlyto_target_longitude(double flyto_target_longitude) { + this.flyto_target_longitude = flyto_target_longitude; + } + + public float getFlyto_target_height() { + return flyto_target_height; + } + + public void setFlyto_target_height(float flyto_target_height) { + this.flyto_target_height = flyto_target_height; + } + + public float getFlyto_remaining_distance() { + return flyto_remaining_distance; + } + + public void setFlyto_remaining_distance(float flyto_remaining_distance) { + this.flyto_remaining_distance = flyto_remaining_distance; + } + + public float getFlyto_remaining_time() { + return flyto_remaining_time; + } + + public void setFlyto_remaining_time(float flyto_remaining_time) { + this.flyto_remaining_time = flyto_remaining_time; + } + + public int getFlyto_max_speed() { + return flyto_max_speed; + } + + public void setFlyto_max_speed(int flyto_max_speed) { + this.flyto_max_speed = flyto_max_speed; + } + public int getFlightmode() { return flightmode; } @@ -1148,6 +1266,95 @@ public class Movement { this.ir_metering_mode = ir_metering_mode; } + // 区域测温 getter/setter + public double getIr_region_x() { + return ir_region_x; + } + + public void setIr_region_x(double ir_region_x) { + this.ir_region_x = ir_region_x; + } + + public double getIr_region_y() { + return ir_region_y; + } + + public void setIr_region_y(double ir_region_y) { + this.ir_region_y = ir_region_y; + } + + public double getIr_region_width() { + return ir_region_width; + } + + public void setIr_region_width(double ir_region_width) { + this.ir_region_width = ir_region_width; + } + + public double getIr_region_height() { + return ir_region_height; + } + + public void setIr_region_height(double ir_region_height) { + this.ir_region_height = ir_region_height; + } + + public double getIr_region_aver_temperature() { + return ir_region_aver_temperature; + } + + public void setIr_region_aver_temperature(double ir_region_aver_temperature) { + this.ir_region_aver_temperature = ir_region_aver_temperature; + } + + public double getIr_region_min_temperature_x() { + return ir_region_min_temperature_x; + } + + public void setIr_region_min_temperature_x(double ir_region_min_temperature_x) { + this.ir_region_min_temperature_x = ir_region_min_temperature_x; + } + + public double getIr_region_min_temperature_y() { + return ir_region_min_temperature_y; + } + + public void setIr_region_min_temperature_y(double ir_region_min_temperature_y) { + this.ir_region_min_temperature_y = ir_region_min_temperature_y; + } + + public double getIr_region_min_temperature() { + return ir_region_min_temperature; + } + + public void setIr_region_min_temperature(double ir_region_min_temperature) { + this.ir_region_min_temperature = ir_region_min_temperature; + } + + public double getIr_region_max_temperature_x() { + return ir_region_max_temperature_x; + } + + public void setIr_region_max_temperature_x(double ir_region_max_temperature_x) { + this.ir_region_max_temperature_x = ir_region_max_temperature_x; + } + + public double getIr_region_max_temperature_y() { + return ir_region_max_temperature_y; + } + + public void setIr_region_max_temperature_y(double ir_region_max_temperature_y) { + this.ir_region_max_temperature_y = ir_region_max_temperature_y; + } + + public double getIr_region_max_temperature() { + return ir_region_max_temperature; + } + + public void setIr_region_max_temperature(double ir_region_max_temperature) { + this.ir_region_max_temperature = ir_region_max_temperature; + } + public int getIr_zoom_factor() { return ir_zoom_factor; @@ -2188,6 +2395,22 @@ public class Movement { this.ltePhoneNumber = ltePhoneNumber; } + public boolean isOcuSyncLte() { + return ocuSyncLte; + } + + public void setOcuSyncLte(boolean ocuSyncLte) { + this.ocuSyncLte = ocuSyncLte; + } + + public int getOcuSyncLteTime() { + return ocuSyncLteTime; + } + + public void setOcuSyncLteTime(int ocuSyncLteTime) { + this.ocuSyncLteTime = ocuSyncLteTime; + } + public int getIsVirtualStickAdvancedModeEnabled() { return isVirtualStickAdvancedModeEnabled; } @@ -2618,5 +2841,45 @@ public class Movement { this.planeMessage = planeMessage; } + public List getReturn_home_path_points() { + return return_home_path_points; + } + + public void setReturn_home_path_points(List return_home_path_points) { + this.return_home_path_points = return_home_path_points; + } + + public double getRth_start_latitude() { + return rth_start_latitude; + } + + public void setRth_start_latitude(double rth_start_latitude) { + this.rth_start_latitude = rth_start_latitude; + } + + public double getRth_start_longitude() { + return rth_start_longitude; + } + + public void setRth_start_longitude(double rth_start_longitude) { + this.rth_start_longitude = rth_start_longitude; + } + + public double getRth_start_altitude() { + return rth_start_altitude; + } + + public void setRth_start_altitude(double rth_start_altitude) { + this.rth_start_altitude = rth_start_altitude; + } + + public boolean is_rth_tracking() { + return is_rth_tracking; + } + + public void set_rth_tracking(boolean rth_tracking) { + is_rth_tracking = rth_tracking; + } + } diff --git a/app/src/main/java/com/aros/apron/entity/Osd.java b/app/src/main/java/com/aros/apron/entity/Osd.java index 840bd0d7..7ef310ac 100644 --- a/app/src/main/java/com/aros/apron/entity/Osd.java +++ b/app/src/main/java/com/aros/apron/entity/Osd.java @@ -116,6 +116,8 @@ public class Osd { private double vertical_speed; private int wind_direction; private int wind_speed; + private boolean ocu_sync_lte; + private int ocu_sync_lte_time; public double getRtk_takeoff_altitude() { @@ -150,6 +152,22 @@ public class Osd { this._$5300 = _$5300; } + public boolean isOcu_sync_lte() { + return ocu_sync_lte; + } + + public void setOcu_sync_lte(boolean ocu_sync_lte) { + this.ocu_sync_lte = ocu_sync_lte; + } + + public int getOcu_sync_lte_time() { + return ocu_sync_lte_time; + } + + public void setOcu_sync_lte_time(int ocu_sync_lte_time) { + this.ocu_sync_lte_time = ocu_sync_lte_time; + } + public int getActivation_time() { return activation_time; } diff --git a/app/src/main/java/com/aros/apron/entity/PsdkWidgetValuesReport.java b/app/src/main/java/com/aros/apron/entity/PsdkWidgetValuesReport.java new file mode 100644 index 00000000..09ff1dc8 --- /dev/null +++ b/app/src/main/java/com/aros/apron/entity/PsdkWidgetValuesReport.java @@ -0,0 +1,121 @@ +package com.aros.apron.entity; + +import java.util.List; + +/** + * PSDK Widget Values 上报实体类 + */ +public class PsdkWidgetValuesReport { + private String tid; + private String bid; + private long timestamp; + private String method; + private String gateway; + private Data data; + + public String getTid() { return tid; } + public void setTid(String tid) { this.tid = tid; } + + public String getBid() { return bid; } + public void setBid(String bid) { this.bid = bid; } + + public long getTimestamp() { return timestamp; } + public void setTimestamp(long timestamp) { this.timestamp = timestamp; } + + public String getMethod() { return method; } + public void setMethod(String method) { this.method = method; } + + public String getGateway() { return gateway; } + public void setGateway(String gateway) { this.gateway = gateway; } + + public Data getData() { return data; } + public void setData(Data data) { this.data = data; } + + public static class Data { + private List psdk_widget_values; + + public List getPsdk_widget_values() { return psdk_widget_values; } + public void setPsdk_widget_values(List psdk_widget_values) { this.psdk_widget_values = psdk_widget_values; } + } + + public static class PsdkWidgetValue { + private int psdk_index; + private String psdk_lib_version; + private String psdk_name; + private String psdk_sn; + private int psdk_type; + private String psdk_version; + private SpeakerData speaker; + private List values; + + + + + public int getPsdk_index() { return psdk_index; } + public void setPsdk_index(int psdk_index) { this.psdk_index = psdk_index; } + + public String getPsdk_lib_version() { return psdk_lib_version; } + public void setPsdk_lib_version(String psdk_lib_version) { this.psdk_lib_version = psdk_lib_version; } + + public String getPsdk_name() { return psdk_name; } + public void setPsdk_name(String psdk_name) { this.psdk_name = psdk_name; } + + public String getPsdk_sn() { return psdk_sn; } + public void setPsdk_sn(String psdk_sn) { this.psdk_sn = psdk_sn; } + + public int getPsdk_type() { return psdk_type; } + public void setPsdk_type(int psdk_type) { this.psdk_type = psdk_type; } + + public String getPsdk_version() { return psdk_version; } + public void setPsdk_version(String psdk_version) { this.psdk_version = psdk_version; } + + public SpeakerData getSpeaker() { return speaker; } + public void setSpeaker(SpeakerData speaker) { this.speaker = speaker; } + + public List getValues() { return values; } + public void setValues(List values) { this.values = values; } + } + + public static class SpeakerData { + private String play_file_md5; + private String play_file_name; + private int play_mode; + private int play_volume; + private int system_state; + private int work_mode; + private int tts_language; + private int tts_speed; + private int tts_type; + private int tts_volume; + + public String getPlay_file_md5() { return play_file_md5; } + public void setPlay_file_md5(String play_file_md5) { this.play_file_md5 = play_file_md5; } + + public String getPlay_file_name() { return play_file_name; } + public void setPlay_file_name(String play_file_name) { this.play_file_name = play_file_name; } + + public int getPlay_mode() { return play_mode; } + public void setPlay_mode(int play_mode) { this.play_mode = play_mode; } + + public int getPlay_volume() { return play_volume; } + public void setPlay_volume(int play_volume) { this.play_volume = play_volume; } + + public int getSystem_state() { return system_state; } + public void setSystem_state(int system_state) { this.system_state = system_state; } + + public int getWork_mode() { return work_mode; } + public void setWork_mode(int work_mode) { this.work_mode = work_mode; } + + public int getTts_language() { return tts_language; } + public void setTts_language(int tts_language) { this.tts_language = tts_language; } + + public int getTts_speed() { return tts_speed; } + public void setTts_speed(int tts_speed) { this.tts_speed = tts_speed; } + + public int getTts_type() { return tts_type; } + public void setTts_type(int tts_type) { this.tts_type = tts_type; } + + public int getTts_volume() { return tts_volume; } + public void setTts_volume(int tts_volume) { this.tts_volume = tts_volume; } + } +} diff --git a/app/src/main/java/com/aros/apron/entity/ReturnHomeInfo.java b/app/src/main/java/com/aros/apron/entity/ReturnHomeInfo.java new file mode 100644 index 00000000..0540dfaf --- /dev/null +++ b/app/src/main/java/com/aros/apron/entity/ReturnHomeInfo.java @@ -0,0 +1,124 @@ +package com.aros.apron.entity; + +import java.util.ArrayList; +import java.util.List; + +/** + * 返航轨迹信息上报 + */ +public class ReturnHomeInfo { + private String tid; + private String bid; + private long timestamp; + private String method = "return_home_info"; + private Data data; + + public String getTid() { + return tid; + } + + public void setTid(String tid) { + this.tid = tid; + } + + public String getBid() { + return bid; + } + + public void setBid(String bid) { + this.bid = bid; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public static class Data { + private String flight_id; + private int last_point_type; + private List planned_path_points = new ArrayList<>(); + + public String getFlight_id() { + return flight_id; + } + + public void setFlight_id(String flight_id) { + this.flight_id = flight_id; + } + + public int getLast_point_type() { + return last_point_type; + } + + public void setLast_point_type(int last_point_type) { + this.last_point_type = last_point_type; + } + + public List getPlanned_path_points() { + return planned_path_points; + } + + public void setPlanned_path_points(List planned_path_points) { + this.planned_path_points = planned_path_points; + } + } + + public static class PathPoint { + private double latitude; + private double longitude; + private float height; + + public PathPoint() { + } + + public PathPoint(double latitude, double longitude, float height) { + this.latitude = latitude; + this.longitude = longitude; + this.height = height; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public float getHeight() { + return height; + } + + public void setHeight(float height) { + this.height = height; + } + } +} diff --git a/app/src/main/java/com/aros/apron/entity/Synchronizedstatus.java b/app/src/main/java/com/aros/apron/entity/Synchronizedstatus.java index ecf0d510..75283e75 100644 --- a/app/src/main/java/com/aros/apron/entity/Synchronizedstatus.java +++ b/app/src/main/java/com/aros/apron/entity/Synchronizedstatus.java @@ -40,6 +40,21 @@ public class Synchronizedstatus { private static volatile boolean takeoff_to_point=false; + + //flyto + private static volatile boolean flyto=false; + + + + + public static boolean isFlyto() { + return flyto; + } + + public static void setFlyto(boolean flyto) { + Synchronizedstatus.flyto = flyto; + } + public static boolean isTakeoff_to_point() { return takeoff_to_point; } diff --git a/app/src/main/java/com/aros/apron/manager/AlternateLandingManager.java b/app/src/main/java/com/aros/apron/manager/AlternateLandingManager.java index c2575fc3..55e9c542 100644 --- a/app/src/main/java/com/aros/apron/manager/AlternateLandingManager.java +++ b/app/src/main/java/com/aros/apron/manager/AlternateLandingManager.java @@ -295,7 +295,7 @@ public class AlternateLandingManager extends BaseManager { public void onSuccess() { sendEvent2Server( "备降点航线上传成功",1); if (PreferenceUtils.getInstance().getHaveRTK() && !(Movement.getInstance().getIs_fixed()==2)) { - RTKManager.getInstance().enableRtk(false); + //TKManager.getInstance().enableRtk(false); } new Handler().postDelayed(new Runnable() { @@ -311,8 +311,23 @@ public class AlternateLandingManager extends BaseManager { PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false); sendEvent2Server( "开始飞往备降点",1); + PerceptionManager.getInstance().setPerceptionEnable(false); - sendFlyToAlternateLandPointEvent(); + + // 发送3次,每次延迟500毫秒 + new Handler().post(new Runnable() { + int count = 0; + @Override + public void run() { + if (count < 3) { + sendFlyToAlternateLandPointEvent(); + count++; + if (count < 3) { + new Handler().postDelayed(this, 500); + } + } + } + }); Movement.getInstance().setTakeoff_result(316052); Movement.getInstance().setResult(316052); diff --git a/app/src/main/java/com/aros/apron/manager/BatteryManager.java b/app/src/main/java/com/aros/apron/manager/BatteryManager.java index fa6f1999..c3966352 100644 --- a/app/src/main/java/com/aros/apron/manager/BatteryManager.java +++ b/app/src/main/java/com/aros/apron/manager/BatteryManager.java @@ -85,28 +85,42 @@ public class BatteryManager extends BaseManager { /**************************************************************************************************************/ + KeyManager.getInstance().listen(KeyTools.createKey(BatteryKey. - KeyChargeRemainingInPercent, 0), this, new CommonCallbacks.KeyListener() { + KeyChargeRemainingInPercent), this, new CommonCallbacks.KeyListener() { @Override public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) { if (newValue != null) { Movement.getInstance().setBattery_a_capacity_percent(newValue); Movement.getInstance().setCapacity_percent(newValue); - checkForcedBatteryRTH(newValue, Movement.getInstance().getBattery_b_capacity_percent()); + LogUtil.log(TAG,"电池电量"+newValue); + checkForcedBatteryRTH(newValue); } } }); - KeyManager.getInstance().listen(KeyTools.createKey(BatteryKey. - KeyChargeRemainingInPercent, 1), this, new CommonCallbacks.KeyListener() { - @Override - public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) { - if (newValue != null) { - Movement.getInstance().setBattery_b_capacity_percent(newValue); - checkForcedBatteryRTH(Movement.getInstance().getBattery_a_capacity_percent(), newValue); - } - } - }); +// KeyManager.getInstance().listen(KeyTools.createKey(BatteryKey. +// KeyChargeRemainingInPercent, 0), this, new CommonCallbacks.KeyListener() { +// @Override +// public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) { +// if (newValue != null) { +// Movement.getInstance().setBattery_a_capacity_percent(newValue); +// Movement.getInstance().setCapacity_percent(newValue); +// // checkForcedBatteryRTH(newValue, Movement.getInstance().getBattery_b_capacity_percent()); +// } +// } +// }); +// +// KeyManager.getInstance().listen(KeyTools.createKey(BatteryKey. +// KeyChargeRemainingInPercent, 1), this, new CommonCallbacks.KeyListener() { +// @Override +// public void onValueChange(@Nullable Integer oldValue, @Nullable Integer newValue) { +// if (newValue != null) { +// Movement.getInstance().setBattery_b_capacity_percent(newValue); +// // checkForcedBatteryRTH(Movement.getInstance().getBattery_a_capacity_percent(), newValue); +// } +// } +// }); KeyManager.getInstance().listen(KeyTools.createKey(BatteryKey. KeyVoltage, 0), this, new CommonCallbacks.KeyListener() { @@ -290,18 +304,13 @@ public class BatteryManager extends BaseManager { /** * 检查双电池平均电量是否低于强制返航阈值,低于则触发返航 */ - private void checkForcedBatteryRTH(int batteryA, int batteryB) { - - + private void checkForcedBatteryRTH(int batteryA) { if (forcedRTHTriggered) return; - if (!getGimbalAndCameraEnabled()) { return; } - - // 过滤空值/无效电量(0或负数不参与计算) - if (batteryA <= 0 || batteryB <= 0) return; + if (batteryA <= 0 ) return; String forcedBatteryStr = PreferenceUtils.getInstance().getForcedBattery(); if (TextUtils.isEmpty(forcedBatteryStr)) return; @@ -313,14 +322,12 @@ public class BatteryManager extends BaseManager { return; } - int avgBattery = (batteryA + batteryB) / 2; - if (avgBattery >= forcedBattery) return; - + if (batteryA >= forcedBattery) return; // 仅在飞行中触发 if (!Movement.getInstance().isPlaneWing()) return; forcedRTHTriggered = true; - LogUtil.log(TAG, "平均电量" + avgBattery + "%低于强制返航阈值" + forcedBattery + "%,触发自动返航"); + LogUtil.log(TAG, "平均电量" + batteryA + "%低于强制返航阈值" + forcedBattery + "%,触发自动返航"); sendEvent2Server("触发低电量返航",1); Movement.getInstance().setTakeoff_result(321773); diff --git a/app/src/main/java/com/aros/apron/manager/CameraManager.java b/app/src/main/java/com/aros/apron/manager/CameraManager.java index fa9c581c..1179ebc3 100644 --- a/app/src/main/java/com/aros/apron/manager/CameraManager.java +++ b/app/src/main/java/com/aros/apron/manager/CameraManager.java @@ -44,6 +44,7 @@ import dji.sdk.keyvalue.value.camera.RecordingState; import dji.sdk.keyvalue.value.camera.TapZoomMode; import dji.sdk.keyvalue.value.camera.ThermalDigitalZoomFactor; import dji.sdk.keyvalue.value.camera.ThermalDisplayMode; +import dji.sdk.keyvalue.value.camera.ThermalAreaMetersureTemperature; import dji.sdk.keyvalue.value.camera.ThermalGainMode; import dji.sdk.keyvalue.value.camera.ThermalTemperatureMeasureMode; import dji.sdk.keyvalue.value.camera.VideoRecordingStatus; @@ -327,8 +328,9 @@ public class CameraManager extends BaseManager { @Override public void onValueChange(@Nullable DoublePoint2D doublePoint2D, @Nullable DoublePoint2D t1) { if (t1 != null) { - Movement.getInstance().setX(t1.getX()); - Movement.getInstance().setX(t1.getY()); +// Movement.getInstance().setX(t1.getX()); +// Movement.getInstance().setY(t1.getY()); +// Movement.getInstance().setIr_metering_mode(1); } } }); @@ -339,11 +341,60 @@ public class CameraManager extends BaseManager { @Override public void onValueChange(@Nullable Double aDouble, @Nullable Double t1) { if (t1 != null) { - Movement.getInstance().setTemperature(t1.intValue()); + if(Movement.getInstance().isOpentemperate()){ + Movement.getInstance().setTemperature(t1.intValue()); + } } } }); + //区域测温区域位置 + KeyManager.getInstance().listen(KeyTools.createCameraKey(CameraKey.KeyThermalRegionMetersureArea, + ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), this, + new CommonCallbacks.KeyListener() { + @Override + public void onValueChange(@Nullable DoubleRect oldValue, + @Nullable DoubleRect t1) { + if (t1 != null) { +// Movement.getInstance().setIr_region_x(t1.getX()); +// Movement.getInstance().setIr_region_y(t1.getY()); +// Movement.getInstance().setIr_region_width(t1.getWidth()); +// Movement.getInstance().setIr_region_height(t1.getHeight()); + } + } + }); + + //区域测温温度信息(平均温度、最低温度点、最高温度点) + KeyManager.getInstance().listen(KeyTools.createCameraKey(CameraKey.KeyThermalRegionMetersureTemperature, + ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), this, + new CommonCallbacks.KeyListener() { + @Override + public void onValueChange(@Nullable ThermalAreaMetersureTemperature oldValue, + @Nullable ThermalAreaMetersureTemperature t1) { + LogUtil.log(TAG,"ThermalAreaMetersureTemperature"+t1.toString()); + if (t1 != null) { + Movement.getInstance().setIr_metering_mode(2); + // 平均温度 + Movement.getInstance().setIr_region_aver_temperature(t1.getAverageAreaTemperature()); + // 最低温度点 + DoublePoint2D minPoint = t1.getMinTemperaturePoint(); + if (minPoint != null) { + Movement.getInstance().setIr_region_min_temperature_x(minPoint.getX()); + Movement.getInstance().setIr_region_min_temperature_y(minPoint.getY()); + } + Movement.getInstance().setIr_region_min_temperature(t1.getMinAreaTemperature()); + // 最高温度点 + DoublePoint2D maxPoint = t1.getMaxTemperaturePoint(); + if (maxPoint != null) { + Movement.getInstance().setIr_region_max_temperature_x(maxPoint.getX()); + Movement.getInstance().setIr_region_max_temperature_y(maxPoint.getY()); + } + Movement.getInstance().setIr_region_max_temperature(t1.getMaxAreaTemperature()); + } + } + }); + + //当前红外变焦倍率 KeyManager.getInstance().listen(KeyTools.createCameraKey(CameraKey.KeyThermalZoomRatios, ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), this, new CommonCallbacks.KeyListener() { @@ -1389,7 +1440,7 @@ public class CameraManager extends BaseManager { KeyConnection, ComponentIndexType.PORT_1)); if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) { if (message != null) { - int type = 1; + final int type; switch (message.getData().getVideo_type()) { case "ir": type = 3; @@ -1403,6 +1454,9 @@ public class CameraManager extends BaseManager { case "zoom": type = 2; break; + default: + type = 1; + break; } KeyManager.getInstance().setValue(KeyTools.createKey(CameraKey.KeyCameraVideoStreamSource, @@ -1411,7 +1465,27 @@ public class CameraManager extends BaseManager { new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { + // 切换到红外视频源时,自动设置调色盘为铁红色(6) + if (type == 3) { + KeyManager.getInstance().setValue( + KeyTools.createCameraKey(CameraKey.KeyThermalPalette, + ComponentIndexType.PORT_1, + CameraLensType.CAMERA_LENS_THERMAL), + CameraThermalPalette.find(6), + new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "红外切换成功,已设置调色盘为铁红"); + } + + @Override + public void onFailure(@NonNull IDJIError error) { + LogUtil.log(TAG, "设置红外调色盘失败:" + getIDJIErrorMsg(error)); + } + }); + } sendMsg2Server(message); + } @Override @@ -1804,6 +1878,7 @@ public class CameraManager extends BaseManager { ThermalTemperatureMeasureMode.find(message.getData().getMode()), new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { + Movement.getInstance().setIr_metering_mode(message.getData().getMode()); sendMsg2Server(message); } @@ -1820,22 +1895,26 @@ public class CameraManager extends BaseManager { // //设置需要测温的点的位置 public void setThermalSpotMetersurePoint(MessageDown message) { - Boolean isConnect =KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey. - KeyConnection,ComponentIndexType.PORT_1)); + Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey. + KeyConnection, ComponentIndexType.PORT_1)); if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) { DoublePoint2D doublePoint2D = new DoublePoint2D(); - doublePoint2D.setX(Double.parseDouble(message.getData().getX())); - doublePoint2D.setY(Double.parseDouble(message.getData().getY())); + doublePoint2D.setX(message.getData().getX()); + doublePoint2D.setY(message.getData().getY()); KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalSpotMetersurePoint, ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), doublePoint2D, new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { + Movement.getInstance().setOpentemperate(true); + Movement.getInstance().setIr_metering_mode(1); + Movement.getInstance().setX(message.getData().getX()); + Movement.getInstance().setY(message.getData().getY()); sendMsg2Server(message); } @Override public void onFailure(@NonNull IDJIError error) { - LogUtil.log(TAG,"设置点测温失败:"+new Gson().toJson(error)); + LogUtil.log(TAG, "设置点测温失败:" + new Gson().toJson(error)); sendFailMsg2Server(message, "设置点测温失败:" + getIDJIErrorMsg(error)); } @@ -1852,14 +1931,19 @@ public class CameraManager extends BaseManager { KeyConnection,ComponentIndexType.PORT_1)); if (isConnect != null && isConnect) { DoubleRect doubleRect = new DoubleRect(); - doubleRect.setX(Double.parseDouble(message.getData().getX())); - doubleRect.setY(Double.parseDouble(message.getData().getY())); - doubleRect.setHeight((double) message.getData().getHeight()); - doubleRect.setWidth((double) message.getData().getWidth()); + doubleRect.setX(message.getData().getX()); + doubleRect.setY(message.getData().getY()); + doubleRect.setHeight(message.getData().getHeight()); + doubleRect.setWidth(message.getData().getWidth()); KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalRegionMetersureArea, ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), doubleRect, new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { + Movement.getInstance().setIr_metering_mode(2); + Movement.getInstance().setIr_region_x(message.getData().getX()); + Movement.getInstance().setIr_region_y(message.getData().getY()); + Movement.getInstance().setIr_region_width(message.getData().getWidth()); + Movement.getInstance().setIr_region_height(message.getData().getHeight()); sendMsg2Server(message); } 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 461e967c..1086b3c3 100644 --- a/app/src/main/java/com/aros/apron/manager/DockCloseManager.java +++ b/app/src/main/java/com/aros/apron/manager/DockCloseManager.java @@ -81,7 +81,7 @@ public class DockCloseManager extends BaseManager { MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, mqttMessage, null, new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { - LogUtil.log(TAG, "关舱发送成功:"+sendDockCloseSuccessTimes+"clientId:"+MqttManager.getInstance().mqttAndroidClient.getClientId()); + LogUtil.log(TAG, "关舱发送成功:"+sendDockCloseSuccessTimes+"clientId:"+MqttManager.getInstance().mqttAndroidClient.getClientId()); sendEvent2Server("AMS通知机库关舱",1); isSendDockCloseSuccess = true; } 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 33d2fe2a..da8eab48 100644 --- a/app/src/main/java/com/aros/apron/manager/FlightManager.java +++ b/app/src/main/java/com/aros/apron/manager/FlightManager.java @@ -23,6 +23,8 @@ import com.aros.apron.tools.AlternateArucoDetect; import com.aros.apron.tools.ApronArucoDetect; import com.aros.apron.tools.ApronArucoDetectPort; import com.aros.apron.tools.DroneHelper; +import com.aros.apron.tools.FlyToPointProgressScheduler; +import com.aros.apron.tools.Gpsdistance; import com.aros.apron.tools.LocationUtils; import com.aros.apron.tools.LogUtil; import com.aros.apron.tools.MqttManager; @@ -33,6 +35,7 @@ import com.google.gson.Gson; import org.greenrobot.eventbus.EventBus; +import java.security.Key; import java.util.List; import java.util.Objects; @@ -74,6 +77,7 @@ import dji.v5.common.callback.CommonCallbacks; import dji.v5.common.error.IDJIError; import dji.v5.common.utils.GpsUtils; import dji.v5.manager.KeyManager; +import dji.v5.manager.SDKManager; import dji.v5.manager.aircraft.perception.data.PerceptionInfo; import dji.v5.manager.aircraft.perception.listener.PerceptionInformationListener; import dji.v5.manager.aircraft.virtualstick.VirtualStickManager; @@ -130,10 +134,10 @@ public class FlightManager extends BaseManager { PsdkWidgetScheduler.getInstance().start(); - PerceptionManager.getInstance().setPerceptionEnable(false); - PerceptionManager.getInstance().setObstacleAvoidanceupEnabled(false); - PerceptionManager.getInstance().setObstacleAvoidancedownEnabled(false); - PerceptionManager.getInstance().closeRadarManager(false); +// PerceptionManager.getInstance().setPerceptionEnable(false); +// PerceptionManager.getInstance().setObstacleAvoidanceupEnabled(false); +// PerceptionManager.getInstance().setObstacleAvoidancedownEnabled(false); +// PerceptionManager.getInstance().closeRadarManager(false); if (count == 0) { sendEvent2Server("开始上传日志文件", 1); @@ -247,8 +251,15 @@ public class FlightManager extends BaseManager { OSDManager.getInstance().pushFlightAttitude(); } - isFlying = newValue; + // 起飞时开启返航轨迹定时上报 + + if (!isFlying) { + RTHTrackerManager.getInstance().reset(); + }else{ + RTHTrackerManager.getInstance().startReporting(); + } + Movement.getInstance().setPlaneWing(newValue); pushFlightAttitude(); @@ -299,11 +310,15 @@ public class FlightManager extends BaseManager { @Override public void onValueChange(@Nullable String s, @Nullable String t1) { if (t1 != null) { - Movement.getInstance().setFirmware_version(t1); + + //Movement.getInstance().setFirmware_version(t1); + Movement.getInstance().setFirmware_version(SDKManager.getInstance().getSDKVersion()); pushFlightAttitude(); } } }); + + //RID状态 KeyManager.getInstance().listen(createKey(FlightControllerKey.KeyRidWorkingStatusPush), this, new CommonCallbacks.KeyListener() { @Override @@ -320,6 +335,7 @@ public class FlightManager extends BaseManager { @Override public void onValueChange(@Nullable RTKTakeoffAltitudeInfo rtkTakeoffAltitudeInfo, @Nullable RTKTakeoffAltitudeInfo t1) { if (t1 != null) { + //LogUtil.log(TAG,"KeyRTKTakeoffAltitudeInfo"+t1.getAltitude()); Movement.getInstance().setRtk_takeoff_altitude(t1.getAltitude()); pushFlightAttitude(); } @@ -342,14 +358,43 @@ public class FlightManager extends BaseManager { String.valueOf(newValue.getLatitude())); Movement.getInstance().setHome_distance(distance); + + Movement.getInstance().setHeight(GpsUtils.egm96Altitude((Movement.getInstance().getRtk_takeoff_altitude() + Movement.getInstance().getElevation()), newValue.getLatitude(), newValue.getLongitude())); + // Movement.getInstance().setHeight(Movement.getInstance().getRtk_takeoff_altitude() + Movement.getInstance().getElevation()); + Movement.getInstance().setLatitude(newValue.getLatitude()); Movement.getInstance().setLongitude(newValue.getLongitude()); pushFlightAttitude(); + // ========== 判断是否到达FlyTo目标点 ========== + double targetLat = Movement.getInstance().getFlyto_target_latitude(); + double targetLon = Movement.getInstance().getFlyto_target_longitude(); + if (targetLat != 0 && targetLon != 0) { + // 计算到目标点的距离 + double targetDistance = Gpsdistance.calculateDistance( + newValue.getLatitude(), newValue.getLongitude(), + targetLat, targetLon); + + double horizontalSpeed = Movement.getInstance().getHorizontal_speed(); + + // 距离目标点 < 3米 且 速度 < 0.2m/s 悬停 → 判定到达 + if (targetDistance < 3 && horizontalSpeed < 0.2) { + LogUtil.log(TAG, "【FlyTo到达目标点】距离=" + String.format("%.1f", targetDistance) + + "m 速度=" + String.format("%.2f", horizontalSpeed) + "m/s → 停止上报"); + + FlyToPointProgressScheduler.getInstance().markSuccess(); + + + // 清除目标点,避免重复触发 + Movement.getInstance().setFlyto_target_latitude(0); + Movement.getInstance().setFlyto_target_longitude(0); + } + } + } } @@ -553,6 +598,7 @@ public class FlightManager extends BaseManager { Movement.getInstance().setFlightmode(0); break; case FORCE_LANDING: + LogUtil.log(TAG,"强制降落触发"); Movement.getInstance().setMode_code(11); break; case POI: @@ -624,6 +670,7 @@ public class FlightManager extends BaseManager { triggerLandOrGoHome = true; Movement.getInstance().setVirtualStickQuitMission(false); } + pushFlightAttitude(); } } @@ -899,7 +946,6 @@ public class FlightManager extends BaseManager { } } - public void resetOpenCabinDoorState() { sendOpenCabinDoorMsg = false; LogUtil.log(TAG, "开舱门状态已重置"); @@ -1034,7 +1080,6 @@ public class FlightManager extends BaseManager { } } - private void setkuaim() { Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey. KeyConnection, ComponentIndexType.PORT_1)); @@ -1099,7 +1144,6 @@ public class FlightManager extends BaseManager { LogUtil.log(TAG, "相机未连接"); } } - //快门优先 private void setCameraExposureMode() { Boolean cameraConnect = KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey. @@ -1120,7 +1164,6 @@ public class FlightManager extends BaseManager { LogUtil.log(TAG, "相机未连接"); } } - //设置成 public void setCameraExposureModePROGRAM() { Boolean cameraConnect = KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey. @@ -1156,7 +1199,6 @@ public class FlightManager extends BaseManager { isGimbalReset = true; } } - // 检查是否满足开始视觉识别降落的条件 private void checkAndStartVisionLanding() { boolean shouldStartVisionLanding = (PreferenceUtils.getInstance().getLandType() == 2); @@ -1164,11 +1206,8 @@ public class FlightManager extends BaseManager { startVisionLanding(); // 检查是否满足降落条件 checkLandingConditions(); - } } - - private static final double FLYING_HEIGHT_THRESHOLD_MAX = 20; private static final double FLYING_HEIGHT_THRESHOLD_MAX_ALTERNATE = 10; private static final double FLYING_HEIGHT_THRESHOLD_MIN = -2; @@ -1176,7 +1215,7 @@ public class FlightManager extends BaseManager { // 打印相对高度的节流计数器(约每秒一次) private int heightLogCounter = 0; - private static final int HEIGHT_LOG_INTERVAL = 30; // startVisionLanding大约每秒回调一次 + private static final int HEIGHT_LOG_INTERVAL = 60; // startVisionLanding大约每秒回调一次 private void startVisionLanding() { @@ -1188,13 +1227,10 @@ public class FlightManager extends BaseManager { // 节流打印相对高度和超声波高度(约每秒1次) if (++heightLogCounter >= HEIGHT_LOG_INTERVAL) { - LogUtil.log(TAG, "[高度监控] 相对高度=" + Movement.getInstance().getElevation() + "m, 超声波高度=" + Movement.getInstance().getUltrasonicHeight() + "dm"); + LogUtil.log(TAG, "[高度监控] 相对高度=" + Movement.getInstance().getElevation() + "m, 超声波高度=" + Movement.getInstance().getUltrasonicHeight() + "dm"+"限高"+KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.KeyHeightLimitRange))); heightLogCounter = 0; } - - - if (isFlying && (Movement.getInstance().getElevation() < 15 && Movement.getInstance().getUltrasonicHeight() < 50 || forceTriggerDetection) && !isSendDetect) { double flyingHeight = Movement.getInstance().getElevation(); double thresholdMin = triggerToAlternatePoint ? FLYING_HEIGHT_THRESHOLD_MIN_ALTERNATE : FLYING_HEIGHT_THRESHOLD_MIN; @@ -1231,7 +1267,7 @@ public class FlightManager extends BaseManager { if (PreferenceUtils.getInstance().getTriggerToAlternatePoint()) { LogUtil.log(TAG, "识别AlterTag:" + PreferenceUtils.getInstance().getNeedTriggerAlterArucoLand()); //EventBus.getDefault().post(FLAG_START_DETECT_ARUCO_ALTERNATE); - PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(true); + PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false); PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false); LogUtil.log(TAG, "开始识别备降点二维码,椭球高度:" + Movement.getInstance().getElevation() + "米" + "--超声波高度:" + Movement.getInstance().getUltrasonicHeight() + "分米"); sendEvent2Server("开始备降点视觉降落", 1); @@ -1350,7 +1386,11 @@ public class FlightManager extends BaseManager { if (Movement.getInstance().isIstakeoffex() == true && !Objects.equals(Movement.getInstance().getTakeoff_status(), "wayline_cancel")) { - Movement.getInstance().setTakeoff_status("task_finish"); + if(Movement.getInstance().isAlternate()){ + Movement.getInstance().setTakeoff_status("wayline_failed"); + }else{ + Movement.getInstance().setTakeoff_status("task_finish"); + } new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { @@ -1359,7 +1399,11 @@ public class FlightManager extends BaseManager { }, 1000); sendEvent2Server("一键起飞已发送task_finish", 1); } else if (Movement.getInstance().isIstakeoffex() == true && Objects.equals(Movement.getInstance().getTakeoff_status(), "wayline_cancel")) { - Movement.getInstance().setTakeoff_status("wayline_failed"); + if(Movement.getInstance().isAlternate()){ + Movement.getInstance().setTakeoff_status("wayline_failed"); + }else{ + Movement.getInstance().setTakeoff_status("wayline_failed"); + } new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { @@ -1455,12 +1499,14 @@ public class FlightManager extends BaseManager { public void onSuccess(EmptyMsg emptyMsg) { //只有在取消返航时才能显示取消返航失败 Movement.getInstance().setMode_code(3); + isGimbalReset = false; sendMsg2Server(message); } @Override public void onFailure(@NonNull IDJIError error) { LogUtil.log(TAG, "取消返航执行失败" + error); + //isGimbalReset = false; sendFailMsg2Server(message, "取消返航执行失败:" + getIDJIErrorMsg(error)); } }); diff --git a/app/src/main/java/com/aros/apron/manager/FlyToPointManager.java b/app/src/main/java/com/aros/apron/manager/FlyToPointManager.java index 06f58ace..6c124b1a 100644 --- a/app/src/main/java/com/aros/apron/manager/FlyToPointManager.java +++ b/app/src/main/java/com/aros/apron/manager/FlyToPointManager.java @@ -1,41 +1,42 @@ package com.aros.apron.manager; -import static android.os.Environment.getExternalStoragePublicDirectory; - -import static com.aros.apron.tools.Utils.getIDJIErrorMsg; -import android.os.Environment; import android.os.Handler; -import android.os.Looper; + import androidx.annotation.NonNull; import com.aros.apron.base.BaseManager; -import com.aros.apron.entity.FlightMission; import com.aros.apron.entity.MessageDown; -import com.aros.apron.entity.MissionPoint; import com.aros.apron.entity.Movement; -import com.aros.apron.tools.DomParserKML; -import com.aros.apron.tools.DomParserWPML; +import com.aros.apron.entity.Synchronizedstatus; +import com.aros.apron.tools.FlyToPointProgressScheduler; import com.aros.apron.tools.LogUtil; import com.aros.apron.tools.PreferenceUtils; -import com.aros.apron.tools.Utils; -import com.aros.apron.tools.ZipUtil; -import com.google.gson.Gson; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; + import java.util.List; import dji.sdk.keyvalue.key.FlightControllerKey; import dji.sdk.keyvalue.key.KeyTools; +import dji.sdk.keyvalue.value.common.LocationCoordinate3D; +import dji.sdk.keyvalue.value.flightcontroller.FlyToMode; import dji.v5.common.callback.CommonCallbacks; import dji.v5.common.error.IDJIError; import dji.v5.manager.KeyManager; -import dji.v5.manager.aircraft.waypoint3.WaypointMissionManager; -import dji.v5.manager.interfaces.IWaypointMissionManager; +import dji.v5.manager.intelligent.IntelligentFlightManager; +import dji.v5.manager.intelligent.flyto.FlyToMissionManager; +import dji.v5.manager.intelligent.flyto.FlyToParam; +import dji.v5.manager.intelligent.flyto.FlyToTarget; +import dji.v5.manager.intelligent.flyto.IFlyToMissionManager; +/** + * 飞向目标点任务管理器 + * 使用 DJI MSDK 5.14+ 原生 IFlyToMissionManager API + * 对应 MQTT 协议: + * - fly_to_point 开始飞向目标点 + * - fly_to_point_stop 结束飞向目标点任务 + * - fly_to_point_update 更新目标点 + */ public class FlyToPointManager extends BaseManager { - final Handler mainHandler = new Handler(Looper.getMainLooper()); - + private static final String TAG = "FlyToPointManager"; private FlyToPointManager() { } @@ -48,202 +49,394 @@ public class FlyToPointManager extends BaseManager { return FlyToPointHolder.INSTANCE; } - public boolean isReceiverMission = false; + public volatile boolean isReceiverMission = false; - //收到飞往目标点航线 + public boolean isReceiverMission() { + return isReceiverMission; + } + + public void setReceiverMission(boolean receiverMission) { + isReceiverMission = receiverMission; + } + // ==================== MQTT 入口 ==================== + + /** + * 收到飞往目标点指令 (fly_to_point) + */ public void taskExecute(MessageDown message) { -// PreferenceUtils.getInstance().setMissionType(2); - PreferenceUtils.getInstance().setIsNewRoute(true); - //避免重复执行 - if (isReceiverMission == false) { - isReceiverMission = true; - } - if (message.getData().getPoints() != null && message.getData().getPoints().size() > 0) { - sendMsg2Server(message); - } else { - sendFailMsg2Server(message, "指点飞行经纬度有误"); + // PreferenceUtils.getInstance().setIsNewRoute(true); +// // 避免重复执行 +// if (!isReceiverMission) { +// isReceiverMission = true; +// } + + // 参数校验 + if (message.getData() == null || message.getData().getPoints() == null + || message.getData().getPoints().isEmpty()) { + sendFailMsg2Server(message, "指点飞行目标点为空"); + + Synchronizedstatus.setFlyto(false); + + return; } - toGenerateKMZFile(message); + + // 检查飞控连接 + Boolean isConnect = KeyManager.getInstance().getValue( + KeyTools.createKey(FlightControllerKey.KeyConnection)); + if (isConnect == null || !isConnect) { + sendFailMsg2Server(message, "设备未连接"); + Synchronizedstatus.setFlyto(false); + return; + } + + startMission(message); + } + // ==================== 开始任务 ==================== + /** - * 5.生成航线 - * - * @param message + * 开始飞向目标点 + * 协议: fly_to_point + * 参数: fly_to_id, max_speed, points[{latitude, longitude, height}] */ - public void toGenerateKMZFile(MessageDown message) { - Movement.getInstance().setTask_current_step(16); - // 创建第一个 MissionPoint 对象 - MissionPoint missionPoint = new MissionPoint(); - missionPoint.setLat(String.valueOf(Movement.getInstance().getLatitude())); - missionPoint.setLng(String.valueOf(Movement.getInstance().getLongitude())); - missionPoint.setSpeed(Double.parseDouble(message.getData().getMax_speed())); - missionPoint.setExecuteHeight(message.getData().getHeight()); - - // 创建第二个 MissionPoint 对象 - MissionPoint missionPoint1 = new MissionPoint(); - missionPoint1.setLat(message.getData().getPoints().get(0).getLatitude() + ""); - missionPoint1.setLng(message.getData().getPoints().get(0).getLongitude() + ""); - missionPoint1.setSpeed(Double.parseDouble(message.getData().getMax_speed())); - missionPoint1.setExecuteHeight(message.getData().getHeight()); - - // 创建一个 MissionPoint 列表 - List missionPoints = new ArrayList<>(); - missionPoints.add(missionPoint); - missionPoints.add(missionPoint1); - - // 创建 FlightMission 对象并设置其属性 - FlightMission flightMission = new FlightMission(); - flightMission.setPoints(missionPoints); - flightMission.setMissionId(2); - flightMission.setFinishAction("noAction"); - flightMission.setTakeOffSecurityHeight( - Float.parseFloat(Movement.getInstance().getElevation() + "")); - flightMission.setSpeed(Double.parseDouble(message.getData().getMax_speed())); - - LogUtil.log(TAG, "当前高度:" + Movement.getInstance().getElevation() - + "-指点飞行安全起飞高度:" + message.getData().getSecurity_takeoff_height()); - - sendEvent2Server("开始生成指点飞行航线", 1); - - // 生成xml文件 - File file1 = new File( - getExternalStoragePublicDirectory("KMZ").getAbsolutePath() + File.separator + "wpmz"); - if (!file1.exists()) { - if (file1.mkdirs()) { - sendEvent2Server("指点飞行航线文件生成成功", 1); - } else { - sendEvent2Server("指点飞行航线文件生成失败", 2); + private void startMission(MessageDown message) { + try { + MessageDown.Data data = message.getData(); + List points = data.getPoints(); + if (points == null || points.isEmpty()) { + sendFailMsg2Server(message, "目标点为空"); + return; } + + MessageDown.Data.Points target = points.get(0); + + // 当前椭球高(起飞点椭球高) + //double currentEllipsoid = Movement.getInstance().getRtk_takeoff_altitude(); + + // 目标相对高度 = 目标椭球高(WGS84) - 当前椭球高(全程 WGS84 椭球系) + double targetRelative = target.getHeight() - Movement.getInstance().getHeight()+Movement.getInstance().getElevation(); + + // 防止高度过低炸机,最低限制20米 + if (targetRelative < 20) { + LogUtil.log(TAG, "计算目标相对高=" + targetRelative + " < 20,限制为20米防炸机"); + targetRelative = 20; + } + + LogUtil.log(TAG, "目标椭球高=" + target.getHeight() + " 当前椭球高=" + Movement.getInstance().getHeight() + + " 当前相对高=" + Movement.getInstance().getElevation() + " 最终目标相对高=" + targetRelative); + + // 构建 FlyToTarget + FlyToTarget flyToTarget = new FlyToTarget(); + LocationCoordinate3D locationCoordinate3D=new LocationCoordinate3D(); + locationCoordinate3D.setLatitude(target.getLatitude()); + locationCoordinate3D.setLongitude(target.getLongitude()); + locationCoordinate3D.setAltitude(targetRelative); + flyToTarget.setTargetLocation(locationCoordinate3D); + + flyToTarget.setSecurityTakeoffHeight(20); + + if (data.getMax_speed() > 0) { + flyToTarget.setMaxSpeed(data.getMax_speed()); + } else { + flyToTarget.setMaxSpeed(10); // 默认 10m/s + } + + // 构建 FlyToParam + FlyToParam flyToParam = new FlyToParam(); + // max_speed 协议定义为 int(米/秒),范围 0-15 + flyToParam.setFlyToMode(FlyToMode.SET_HEIGHT); + + flyToParam.setHeight((int) targetRelative); + + + // 如果协议携带了 fly_to_id,记录下来 + if (data.getFly_to_id() != null) { + Movement.getInstance().setFly_to_id(data.getFly_to_id()); + LogUtil.log(TAG, "fly_to_id: " + data.getFly_to_id()); + } + + + int maxSpeed = data.getMax_speed() > 0 ? data.getMax_speed() : 10; + + sendEvent2Server("开始飞向目标点: lat=" + target.getLatitude() + + ", lng=" + target.getLongitude() + + ", height=" + target.getHeight()+"targetRelative"+targetRelative, 1); + + IFlyToMissionManager manager = IntelligentFlightManager.getInstance().getFlyToMissionManager(); + + + manager.startMission(flyToTarget, flyToParam, new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "飞向目标点任务开始成功"); + //Movement.getInstance().setTask_current_step(23); + Movement.getInstance().setMode_code(17); // 指令飞行模式 + + //开始上报 + FlyToPointProgressScheduler.getInstance().startReporting(); + + // 保存目标点信息到 Movement,供进度上报使用 + Movement.getInstance().setFlyto_target_latitude(target.getLatitude()); + Movement.getInstance().setFlyto_target_longitude(target.getLongitude()); + Movement.getInstance().setFlyto_target_height((float) target.getHeight()); + + Movement.getInstance().setFlyto_max_speed(maxSpeed); + + // 回复成功 + sendMsg2Server(message); + sendEvent2Server("飞向目标点任务已启动", 1); + Synchronizedstatus.setFlyto(false); + } + + @Override + public void onFailure(@NonNull IDJIError error) { + String errorMsg = "飞向目标点启动失败: " + error.description(); + LogUtil.log(TAG, errorMsg); + sendFailMsg2Server(message, errorMsg); + sendEvent2Server(errorMsg, 2); + + FlyToPointProgressScheduler.getInstance().markFailed(); + + Synchronizedstatus.setFlyto(false); + } + }); + } catch (Exception e) { + sendFailMsg2Server(message, "飞向目标点参数异常: " + e.getMessage()); + Synchronizedstatus.setFlyto(false); } - DomParserKML domParserKML = new DomParserKML(getExternalStoragePublicDirectory("KMZ").getAbsolutePath() + File.separator + "wpmz", - "/template.kml"); - domParserKML.createKml(flightMission); + } - DomParserWPML domParserWPML = new DomParserWPML(getExternalStoragePublicDirectory("KMZ").getAbsolutePath() + File.separator + "wpmz", - "/waylines.wpml"); - domParserWPML.createWpml(flightMission); + // ==================== 停止任务 ==================== - File kmzFile = new File(getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + "FlyTo.kmz"); - kmzFile.getParentFile().mkdirs(); + /** + * 结束飞向目标点任务 (fly_to_point_stop) + */ + public void stopMission(MessageDown message) { + Boolean isConnect = KeyManager.getInstance().getValue( + KeyTools.createKey(FlightControllerKey.KeyConnection)); + if (isConnect == null || !isConnect) { + sendFailMsg2Server(message, "设备未连接"); + return; + } + + IFlyToMissionManager manager = IntelligentFlightManager.getInstance().getFlyToMissionManager(); + manager.stopMission(new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "飞向目标点任务终止成功"); + sendMsg2Server(message); + + FlyToPointProgressScheduler.getInstance().markCancel(); + } + + @Override + public void onFailure(@NonNull IDJIError error) { + String errorMsg = "飞向目标点终止失败: " + error.description(); + LogUtil.log(TAG, errorMsg); + sendFailMsg2Server(message, errorMsg); + } + }); + } + + // ==================== 更新目标点 ==================== + + /** + * 更新飞向目标点 (fly_to_point_update) + * 可在「一键起飞」或「flyto 飞向目标点」执行过程中调用 + * 参数: max_speed, points[{latitude, longitude, height}] + */ + public void updateTarget(MessageDown message) { + Boolean isConnect = KeyManager.getInstance().getValue( + KeyTools.createKey(FlightControllerKey.KeyConnection)); + if (isConnect == null || !isConnect) { + sendFailMsg2Server(message, "设备未连接"); + return; + } try { - ZipUtil.zip(getExternalStoragePublicDirectory("KMZ").getAbsolutePath() + "/wpmz", - getExternalStoragePublicDirectory("KMZ").getAbsolutePath() + File.separator + "FlyTo.kmz"); - } catch (IOException e) { - sendEvent2Server("指点飞行任务生成异常", 2); - throw new RuntimeException(e); - } - pushKMZFileToAircraft(message); - } +// MessageDown.Data data = message.getData(); +// if (data == null || data.getPoints() == null || data.getPoints().isEmpty()) { +// sendFailMsg2Server(message, "更新目标点为空"); +// return; +// } +// +// MessageDown.Data.Points target = data.getPoints().get(0); +// +// // 当前椭球高(起飞点椭球高 + 相对起飞点高度) +// //double currentEllipsoid = Movement.getInstance().getRtk_takeoff_altitude(); +// +// // 目标相对高度 = 目标椭球高(WGS84) - 当前椭球高(全程 WGS84 椭球系) +// double targetRelative = target.getHeight() - Movement.getInstance().getHeight() + Movement.getInstance().getElevation(); +// +// // 防止高度过低炸机,最低限制20米 +// if (targetRelative < 20) { +// LogUtil.log(TAG, "计算目标相对高=" + targetRelative + " < 20,限制为20米防炸机"); +// targetRelative = 20; +// } +// +// // 构建 FlyToTarget +// FlyToTarget flyToTarget = new FlyToTarget(); +// LocationCoordinate3D locationCoordinate3D=new LocationCoordinate3D(); +// locationCoordinate3D.setLatitude(target.getLatitude()); +// locationCoordinate3D.setLongitude(target.getLongitude()); +// locationCoordinate3D.setAltitude(targetRelative); +// flyToTarget.setTargetLocation(locationCoordinate3D); +// +// +// +// flyToTarget.setSecurityTakeoffHeight(20); +// +// int maxspeed; +// if (data.getMax_speed() > 0) { +// flyToTarget.setMaxSpeed(data.getMax_speed()); +// maxspeed=data.getMax_speed(); +// } else { +// flyToTarget.setMaxSpeed(10); // 默认 10m/s +// maxspeed=10; +// } +// +// sendEvent2Server("更新目标点: lat=" + target.getLatitude() +// + ", lng=" + target.getLongitude() +// + ", height=" + target.getHeight()+"targetRelative"+targetRelative, 1); +// +// LogUtil.log(TAG, "updateTarget: 目标高=" + target.getHeight() +// + " getHeight=" + Movement.getInstance().getHeight() +// + " getElevation=" + Movement.getInstance().getElevation() +// + " targetRelative=" + targetRelative); + IFlyToMissionManager manager = IntelligentFlightManager.getInstance().getFlyToMissionManager(); - /** - * 6.上传指点航线 - * - * @param message - */ - private void pushKMZFileToAircraft(MessageDown message) { - Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey. - KeyConnection)); - if (isConnect != null && isConnect) { - - WaypointMissionManager.getInstance().pushKMZFileToAircraft(getExternalStoragePublicDirectory("KMZ").getAbsolutePath() + "/" + "FlyTo.kmz", new CommonCallbacks.CompletionCallbackWithProgress() { - @Override - public void onProgressUpdate(Double progress) { - sendEvent2Server("指点航线上传进度:" + progress, 1); - Movement.getInstance().setTask_current_step(17); - } - - @Override - public void onSuccess() { - sendEvent2Server("指点航线上传成功,准备执行任务", 1); - mainHandler.postDelayed(new Runnable() { - @Override - public void run() { - /** - * 7.开始任务 - */ - Movement.getInstance().setTask_current_step(22); - startMission(message); - } - }, 2000); - } - - @Override - public void onFailure(@NonNull IDJIError error) { - sendFailMsg2Server(message,"指点航线上传失败:"+ Utils.getIDJIErrorMsg(error)); - sendEvent2Server("指点航线上传失败:"+ Utils.getIDJIErrorMsg(error),2); - //待机 - Movement.getInstance().setMode_code(0); - - } - }); - } - } - - - /** - * 6.开始航线 - * - * @param message - */ - public void startMission(MessageDown message) { - Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey. - KeyConnection)); - if (isConnect != null && isConnect) { - CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() { - @Override - public void onSuccess() { - LogUtil.log(TAG, "指点航线开始成功"); - Movement.getInstance().setTask_status("paused"); - sendEvent2Server("任务开始执行", 1); - Movement.getInstance().setTask_current_step(23); -// Movement.getInstance().setMode_code(5); - //指令飞行( 指点前置不需要起飞准备这些因为没有指点起飞的航线) - Movement.getInstance().setMode_code(17); - - } - - @Override - public void onFailure(@NonNull IDJIError error) { - sendFailMsg2Server(message,"指点航线执行失败:" + new Gson().toJson(error)); - sendEvent2Server("指点航线执行失败:" + new Gson().toJson(error),2); - //待机 - Movement.getInstance().setMode_code(0); - - - } - }; - WaypointMissionManager.getInstance().startMission("FlyTo", callback); - } else { - sendEvent2Server("指点任务开始失败,设备未连接", 2); - } - } - - public void stopMission(MessageDown message) { - Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey. - KeyConnection)); - if (isConnect != null && isConnect) { - IWaypointMissionManager missionManager = WaypointMissionManager.getInstance(); - missionManager.stopMission("FlyTo", new CommonCallbacks.CompletionCallback() { + manager.stopMission(new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { + LogUtil.log(TAG, "目标点更新成功"); sendMsg2Server(message); - LogUtil.log(TAG, "指点任务终止成功"); - Movement.getInstance().setTask_status("paused"); + sendEvent2Server("目标点已更新", 1); + + + new Handler().postDelayed(()->{ + startMissionupdate(message); + },1000); + + } + + @Override + public void onFailure(@NonNull IDJIError idjiError) { + + String errorMsg = "目标点更新失败: " + idjiError; + LogUtil.log(TAG, errorMsg); + sendFailMsg2Server(message, errorMsg); + + } + }); + + + + } catch (Exception e) { + sendFailMsg2Server(message, "更新目标点参数异常: " + e.getMessage()); + } + } + + + private void startMissionupdate(MessageDown message) { + try { + MessageDown.Data data = message.getData(); + List points = data.getPoints(); + if (points == null || points.isEmpty()) { + sendFailMsg2Server(message, "目标点为空"); + return; + } + + MessageDown.Data.Points target = points.get(0); + + // 当前椭球高(起飞点椭球高) + //double currentEllipsoid = Movement.getInstance().getRtk_takeoff_altitude(); + + // 目标相对高度 = 目标椭球高(WGS84) - 当前椭球高(全程 WGS84 椭球系) + double targetRelative = target.getHeight() - Movement.getInstance().getHeight()+Movement.getInstance().getElevation(); + + // 防止高度过低炸机,最低限制20米 + if (targetRelative < 20) { + LogUtil.log(TAG, "计算目标相对高=" + targetRelative + " < 20,限制为20米防炸机"); + targetRelative = 20; + } + + LogUtil.log(TAG, "目标椭球高=" + target.getHeight() + " 当前椭球高=" + Movement.getInstance().getHeight() + + " 当前相对高=" + Movement.getInstance().getElevation() + " 最终目标相对高=" + targetRelative); + + // 构建 FlyToTarget + FlyToTarget flyToTarget = new FlyToTarget(); + LocationCoordinate3D locationCoordinate3D=new LocationCoordinate3D(); + locationCoordinate3D.setLatitude(target.getLatitude()); + locationCoordinate3D.setLongitude(target.getLongitude()); + locationCoordinate3D.setAltitude(targetRelative); + flyToTarget.setTargetLocation(locationCoordinate3D); + + flyToTarget.setSecurityTakeoffHeight(20); + + if (data.getMax_speed() > 0) { + flyToTarget.setMaxSpeed(data.getMax_speed()); + } else { + flyToTarget.setMaxSpeed(10); // 默认 10m/s + } + + // 构建 FlyToParam + FlyToParam flyToParam = new FlyToParam(); + // max_speed 协议定义为 int(米/秒),范围 0-15 + flyToParam.setFlyToMode(FlyToMode.SET_HEIGHT); + + flyToParam.setHeight((int) targetRelative); + + + // 如果协议携带了 fly_to_id,记录下来 + if (data.getFly_to_id() != null) { + Movement.getInstance().setFly_to_id(data.getFly_to_id()); + LogUtil.log(TAG, "fly_to_id: " + data.getFly_to_id()); + } + + + int maxSpeed = data.getMax_speed() > 0 ? data.getMax_speed() : 10; + + sendEvent2Server("开始飞向目标点: lat=" + target.getLatitude() + + ", lng=" + target.getLongitude() + + ", height=" + target.getHeight()+"targetRelative"+targetRelative, 1); + + IFlyToMissionManager manager = IntelligentFlightManager.getInstance().getFlyToMissionManager(); + + + manager.startMission(flyToTarget, flyToParam, new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "飞向目标点任务开始成功"); + //Movement.getInstance().setTask_current_step(23); + Movement.getInstance().setMode_code(17); // 指令飞行模式 + //开始上报 + FlyToPointProgressScheduler.getInstance().startReporting(); + + // 保存目标点信息到 Movement,供进度上报使用 + Movement.getInstance().setFlyto_target_latitude(target.getLatitude()); + Movement.getInstance().setFlyto_target_longitude(target.getLongitude()); + Movement.getInstance().setFlyto_target_height((float) target.getHeight()); + Movement.getInstance().setFlyto_max_speed(maxSpeed); + } @Override public void onFailure(@NonNull IDJIError error) { - sendFailMsg2Server(message, "指点任务终止失败:" + getIDJIErrorMsg(error)); + String errorMsg = "飞向目标点启动失败: " + error.description(); + LogUtil.log(TAG, errorMsg); + + FlyToPointProgressScheduler.getInstance().markFailed(); } }); - } else { - LogUtil.log(TAG, "指点任务终止失败:设备未连接"); + } catch (Exception e) { + sendFailMsg2Server(message, "飞向目标点参数异常: " + e.getMessage()); } } + + } diff --git a/app/src/main/java/com/aros/apron/manager/HatchDoorManager.java b/app/src/main/java/com/aros/apron/manager/HatchDoorManager.java new file mode 100644 index 00000000..c2d72387 --- /dev/null +++ b/app/src/main/java/com/aros/apron/manager/HatchDoorManager.java @@ -0,0 +1,304 @@ +package com.aros.apron.manager; + +import android.os.Handler; +import android.os.Looper; + +import com.aros.apron.base.BaseManager; +import com.aros.apron.constant.AMSConfig; +import com.aros.apron.constant.Constant; +import com.aros.apron.entity.ApronExecutionStatus; +import com.aros.apron.entity.MessageEvent; +import com.aros.apron.entity.Movement; +import com.aros.apron.tools.LogUtil; +import com.aros.apron.tools.MqttManager; +import com.aros.apron.tools.PreferenceUtils; +import com.google.gson.Gson; + +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +/** + * 舱门开关统一管理类 + * + * 核心规则: + * 1. 开舱优先级绝对大于关舱 —— 两者冲突时永远以开舱为准 + * 2. 同一时刻只允许存在一个有效指令(OPEN > NONE > CLOSE) + * 3. 收到服务端 MQTT 回复后才认为操作完成 + */ +public class HatchDoorManager extends BaseManager { + + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + + // 期望的舱门指令(优先级:OPEN(1) > NONE(0) > CLOSE(-1)) + private int desiredCommand = COMMAND_NONE; + private static final int COMMAND_OPEN = 1; + private static final int COMMAND_NONE = 0; + private static final int COMMAND_CLOSE = -1; + + // 当前执行状态 + private volatile State currentState = State.IDLE; + + // 重试计数 + private int retryCount = 0; + private static final int MAX_CLOSE_RETRIES = 2; + private static final int MAX_OPEN_RETRIES = 20; + private static final long RETRY_DELAY_MS = 2000; + + public enum State { + /** 空闲,无操作 */ + IDLE, + /** 正在发送开舱指令 */ + OPENING, + /** 已开舱(服务端已确认) */ + OPEN, + /** 正在发送关舱指令 */ + CLOSING, + /** 已关舱(服务端已确认) */ + CLOSED + } + + private HatchDoorManager() { + } + + private static class Holder { + private static final HatchDoorManager INSTANCE = new HatchDoorManager(); + } + + public static HatchDoorManager getInstance() { + return Holder.INSTANCE; + } + + // ==================== 公开 API ==================== + + /** + * 请求开舱(优先级最高,覆盖任何进行中的关舱操作) + */ + public synchronized void requestOpen() { + if (currentState == State.OPEN || currentState == State.OPENING) { + LogUtil.log(TAG, "舱门已处于开启/开启中,忽略重复请求"); + return; + } + + // 开舱覆盖关舱:取消进行中的关舱重试 + if (desiredCommand != COMMAND_OPEN) { + LogUtil.log(TAG, "开舱请求覆盖关舱请求(开舱优先级最高)"); + } + + desiredCommand = COMMAND_OPEN; + retryCount = 0; + sendOpenCommand(); + } + + /** + * 请求关舱 + */ + public synchronized void requestClose() { + // 如果当前期望开舱,关舱请求被忽略 + if (desiredCommand == COMMAND_OPEN) { + LogUtil.log(TAG, "开舱优先级高于关舱,忽略关舱请求"); + return; + } + if (currentState == State.CLOSED || currentState == State.CLOSING) { + LogUtil.log(TAG, "舱门已处于关闭/关闭中,忽略重复请求"); + return; + } + + desiredCommand = COMMAND_CLOSE; + retryCount = 0; + sendCloseCommand(); + } + + /** + * 供 MqttCallBack 回调调用:服务端确认开舱 + */ + public synchronized void onServerReplyOpen() { + LogUtil.log(TAG, "服务端确认开舱"); + ApronExecutionStatus.getInstance().setServerReplyDockOpen(true); + currentState = State.OPEN; + desiredCommand = COMMAND_NONE; + retryCount = 0; + } + + /** + * 供 MqttCallBack 回调调用:服务端确认关舱 + */ + public synchronized void onServerReplyClose() { + LogUtil.log(TAG, "服务端确认关舱"); + ApronExecutionStatus.getInstance().setServerReplyDockIn(true); + currentState = State.CLOSED; + desiredCommand = COMMAND_NONE; + retryCount = 0; + } + + /** + * 重置所有状态(每次新任务起飞前调用) + */ + public synchronized void reset() { + desiredCommand = COMMAND_NONE; + currentState = State.IDLE; + retryCount = 0; + ApronExecutionStatus.getInstance().setServerReplyDockOpen(false); + LogUtil.log(TAG, "舱门状态已重置"); + } + + /** + * 当前舱门状态(供外部查询) + */ + public synchronized State getState() { + return currentState; + } + + // ==================== 内部实现 ==================== + + private void sendOpenCommand() { + if (currentState == State.OPEN) { + return; + } + + int maxRetries = MAX_OPEN_RETRIES; + if (retryCount >= maxRetries) { + LogUtil.log(TAG, "开舱达到最大重试次数: " + retryCount); + return; + } + + if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) { + LogUtil.log(TAG, "开舱发送失败:mqtt 未连接,准备重试 " + (retryCount + 1)); + retryCount++; + mainHandler.postDelayed(this::sendOpenCommand, RETRY_DELAY_MS); + return; + } + + currentState = State.OPENING; + publishDoorMessage(Constant.OPEN_DOOR, true); + } + + private void sendCloseCommand() { + if (currentState == State.CLOSED) { + return; + } + + // 发关舱前再检查一次是否有开舱请求插队 + if (desiredCommand == COMMAND_OPEN) { + LogUtil.log(TAG, "开舱请求插入,中止关舱"); + return; + } + + int maxRetries = MAX_CLOSE_RETRIES; + if (retryCount >= maxRetries) { + LogUtil.log(TAG, "关舱达到最大重试次数: " + retryCount); + return; + } + + if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) { + LogUtil.log(TAG, "关舱发送失败:mqtt 未连接,准备重试 " + (retryCount + 1)); + retryCount++; + mainHandler.postDelayed(this::sendCloseCommand, RETRY_DELAY_MS); + return; + } + + currentState = State.CLOSING; + publishDoorMessage(Constant.CLOSE_DOOR, false); + } + + private void publishDoorMessage(String method, boolean isOpen) { + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setBid(UUID.randomUUID().toString()); + messageEvent.setTid(UUID.randomUUID().toString()); + messageEvent.setTimestamp(System.currentTimeMillis()); + messageEvent.setMethod(method); + + MqttMessage mqttMessage = new MqttMessage( + new Gson().toJson(messageEvent).getBytes(StandardCharsets.UTF_8)); + mqttMessage.setQos(1); + + try { + MqttManager.getInstance().mqttAndroidClient.publish( + AMSConfig.UP_UAV_EVENT, mqttMessage, null, + new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + String action = isOpen ? "开舱" : "关舱"; + LogUtil.log(TAG, action + "发送成功"); + sendEvent2Server("AMS通知机库" + action, 1); + + // 发送成功后等待服务端回复 + mainHandler.postDelayed(() -> { + synchronized (HatchDoorManager.this) { + if (isOpen) { + if (ApronExecutionStatus.getInstance().isServerReplyDockOpen()) { + currentState = State.OPEN; + desiredCommand = COMMAND_NONE; + retryCount = 0; + } else { + LogUtil.log(TAG, "未收到服务端开舱回复,重试"); + if (Movement.getInstance().isPlaneWing() + && Movement.getInstance().getElevation() > 40) { + retryOpen(); + } + } + } else { + if (ApronExecutionStatus.getInstance().isServerReplyDockIn()) { + currentState = State.CLOSED; + desiredCommand = COMMAND_NONE; + retryCount = 0; + } else { + LogUtil.log(TAG, "未收到服务端关舱回复,重试"); + retryClose(); + } + } + } + }, 2000); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + String action = isOpen ? "开舱" : "关舱"; + LogUtil.log(TAG, action + "发送回调失败:" + exception); + if (isOpen) { + retryOpen(); + } else { + retryClose(); + } + } + }); + } catch (Exception e) { + LogUtil.log(TAG, "舱门指令发送异常:" + e); + } + } + + private void retryOpen() { + // 开舱优先级最高,重试时不再检查冲突 + retryCount++; + if (retryCount < MAX_OPEN_RETRIES) { + LogUtil.log(TAG, "开舱重试 #" + retryCount); + mainHandler.postDelayed(() -> { + currentState = State.OPENING; + sendOpenCommand(); + }, RETRY_DELAY_MS); + } else { + LogUtil.log(TAG, "开舱达到最大重试次数: " + retryCount); + } + } + + private void retryClose() { + // 关舱重试前检查是否有开舱请求插入 + if (desiredCommand == COMMAND_OPEN) { + LogUtil.log(TAG, "开舱请求插入,停止关舱重试"); + return; + } + retryCount++; + if (retryCount < MAX_CLOSE_RETRIES) { + LogUtil.log(TAG, "关舱重试 #" + retryCount); + mainHandler.postDelayed(() -> { + currentState = State.CLOSING; + sendCloseCommand(); + }, RETRY_DELAY_MS); + } else { + LogUtil.log(TAG, "关舱达到最大重试次数: " + retryCount); + } + } +} diff --git a/app/src/main/java/com/aros/apron/manager/LDMManager.java b/app/src/main/java/com/aros/apron/manager/LDMManager.java new file mode 100644 index 00000000..c77904c0 --- /dev/null +++ b/app/src/main/java/com/aros/apron/manager/LDMManager.java @@ -0,0 +1,228 @@ +package com.aros.apron.manager; + +import android.content.Context; + +import com.aros.apron.tools.LogUtil; + +import java.util.EnumSet; +import java.util.Set; + +import dji.v5.common.callback.CommonCallbacks; +import dji.v5.common.error.IDJIError; +import dji.v5.common.ldm.LDMExemptModule; +import dji.v5.manager.interfaces.ILDMManager; +import dji.v5.manager.SDKManager; + +/** + * LDM(Local Data Mode / 本地数据模式)管理 + * + * 启用后 SDK 默认不再发起任何网络请求,用于隐私/合规/离线场景 + * 初始化、注册、获取 DJI License 始终允许联网,不受 LDM 限制 + */ +public class LDMManager { + + private static final String TAG = "LDMManager"; + + private Context context; + + private LDMManager() { + } + + private static class Holder { + private static final LDMManager INSTANCE = new LDMManager(); + } + + public static LDMManager getInstance() { + return Holder.INSTANCE; + } + + /** + * 初始化 Context(在 SDK 注册成功后调用一次即可) + */ + public void init(Context ctx) { + this.context = ctx.getApplicationContext(); + } + + // ==================== 获取 SDK 原生 ILDMManager ==================== + + private ILDMManager getLDMManager() { + return LDMManager.getInstance().getLDMManager(); + } + + // ==================== 核心方法 ==================== + + /** + * 启用 LDM(所有模块全部添加为例外,都可联网) + */ + public void enableLDM() { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + LogUtil.log(TAG, "enableLDM 失败:LDMManager 为 null"); + return; + } + if (context == null) { + LogUtil.log(TAG, "enableLDM 失败:context 未初始化,请先调用 init()"); + return; + } + try { + Set allModules = EnumSet.allOf(LDMExemptModule.class); + LogUtil.log(TAG, "启用 LDM,例外模块: " + allModules); + ldmManager.enableLDM(context, new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "LDM 启用成功"); + } + + @Override + public void onFailure(IDJIError error) { + LogUtil.log(TAG, "LDM 启用失败: " + (error != null ? error.description() : "unknown")); + } + }, allModules.toArray(new LDMExemptModule[0])); + } catch (Exception e) { + LogUtil.log(TAG, "LDM 启用异常: " + e); + } + } + + /** + * 启用 LDM,指定允许联网的例外模块 + * + * @param exemptModules 允许联网的模块集合,传 null/空集合表示全部禁止 + */ + public void enableLDMWithExempt(Set exemptModules) { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + LogUtil.log(TAG, "enableLDMWithExempt 失败:LDMManager 为 null"); + return; + } + if (context == null) { + LogUtil.log(TAG, "enableLDMWithExempt 失败:context 未初始化,请先调用 init()"); + return; + } + try { + Set modules = (exemptModules != null && !exemptModules.isEmpty()) + ? exemptModules : EnumSet.noneOf(LDMExemptModule.class); + LogUtil.log(TAG, "启用 LDM,例外模块: " + modules); + ldmManager.enableLDM(context, new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "LDM 启用成功"); + } + + @Override + public void onFailure(IDJIError error) { + LogUtil.log(TAG, "LDM 启用失败: " + (error != null ? error.description() : "unknown")); + } + }, modules.toArray(new LDMExemptModule[0])); + } catch (Exception e) { + LogUtil.log(TAG, "LDM 启用异常: " + e); + } + } + + /** + * 启用 LDM 并允许 RTK 模块联网 + * (适合使用网络 RTK 服务的场景,保证定位精度) + */ + public void enableLDMAllowRTK() { + Set exempt = EnumSet.of(LDMExemptModule.RTK); + enableLDMWithExempt(exempt); + } + + /** + * 关闭 LDM(恢复所有模块的网络访问) + */ + public void disableLDM() { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + LogUtil.log(TAG, "disableLDM 失败:LDMManager 为 null"); + return; + } + if (context == null) { + LogUtil.log(TAG, "disableLDM 失败:context 未初始化,请先调用 init()"); + return; + } + try { + LogUtil.log(TAG, "关闭 LDM"); + ldmManager.disableLDM(new CommonCallbacks.CompletionCallback() { + @Override + public void onSuccess() { + LogUtil.log(TAG, "LDM 关闭成功"); + } + + @Override + public void onFailure(IDJIError error) { + LogUtil.log(TAG, "LDM 关闭失败: " + (error != null ? error.description() : "unknown")); + } + }); + } catch (Exception e) { + LogUtil.log(TAG, "LDM 关闭异常: " + e); + } + } + + // ==================== 状态查询 ==================== + + /** + * 查询 LDM 是否已启用 + */ + public boolean isLDMEnabled() { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + return false; + } + try { + return ldmManager.isLDMEnabled(); + } catch (Exception e) { + LogUtil.log(TAG, "isLDMEnabled 异常: " + e); + return false; + } + } + + /** + * 查询 LDM License 是否已加载 + */ + public boolean isLDMLicenseLoaded() { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + return false; + } + try { + return ldmManager.isLDMLicenseLoaded(); + } catch (Exception e) { + LogUtil.log(TAG, "isLDMLicenseLoaded 异常: " + e); + return false; + } + } + + /** + * 查询指定模块是否在 LDM 模式下仍可访问网络 + */ + public boolean isNetworkEnabledForModule(LDMExemptModule module) { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + return false; + } + try { + return ldmManager.isNetworkServiceEnabledForModule(module); + } catch (Exception e) { + LogUtil.log(TAG, "isNetworkServiceEnabledForModule 异常: " + e); + return false; + } + } + + // ==================== License 相关 ==================== + + /** + * 获取本地 LDM License 文件路径(需要传入 context) + */ + public String getLocalLDMLicensePath() { + ILDMManager ldmManager = getLDMManager(); + if (ldmManager == null) { + return ""; + } + try { + return ldmManager.getLocalLDMLicensePath(context); + } catch (Exception e) { + LogUtil.log(TAG, "getLocalLDMLicensePath 异常: " + e); + return ""; + } + } +} diff --git a/app/src/main/java/com/aros/apron/manager/MLTEManager.java b/app/src/main/java/com/aros/apron/manager/MLTEManager.java index 0b8b390e..ea3688e3 100644 --- a/app/src/main/java/com/aros/apron/manager/MLTEManager.java +++ b/app/src/main/java/com/aros/apron/manager/MLTEManager.java @@ -88,8 +88,15 @@ public class MLTEManager extends BaseManager { if (!info.isLTEAuthenticationAvailable() || !info.isLTEAuthenticated()) { sendEvent2Server("⚠ LTE认证已过期或未认证,需要重新认证", 1); + Movement.getInstance().setOcuSyncLte(false); + Movement.getInstance().setOcuSyncLteTime(0); } else if (info.getLTEAuthenticatedRemainingTime() > 0 && info.getLTEAuthenticatedRemainingTime() < 3 * 24 * 3600) { sendEvent2Server("⚠ LTE认证剩余时间不足3天,请尽快重新认证", 1); + Movement.getInstance().setOcuSyncLte(true); + Movement.getInstance().setOcuSyncLteTime((int) (info.getLTEAuthenticatedRemainingTime() / (24 * 3600))); + } else { + Movement.getInstance().setOcuSyncLte(info.isLTEAuthenticated()); + Movement.getInstance().setOcuSyncLteTime((int) (info.getLTEAuthenticatedRemainingTime() / (24 * 3600))); } 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 166c0c5a..635ee012 100644 --- a/app/src/main/java/com/aros/apron/manager/MediaManager.java +++ b/app/src/main/java/com/aros/apron/manager/MediaManager.java @@ -250,34 +250,25 @@ public class MediaManager extends BaseManager { return; } - mState = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState(); - LogUtil.log(TAG, "当前状态:" + mState + ",准备拉取文件列表(已耗时" + elapsed + "s)"); + MediaFileListState currentState = MediaDataCenter.getInstance().getMediaManager().getMediaFileListState(); + LogUtil.log(TAG, "当前状态:" + currentState + ",准备拉取文件列表(已耗时" + elapsed + "s)"); - // 1. 当状态为IDLE时,需要调用pullMediaFileListFromCamera拉取全量数据 - // 2. 当状态为UP_TO_DATE时,表示拉取完成,可以获取数据 - if (mState == MediaFileListState.IDLE) { - // 状态为IDLE,开始拉取文件列表 + if (currentState == MediaFileListState.IDLE) { LogUtil.log(TAG, "状态为IDLE,开始拉取文件列表"); MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(new PullMediaFileListParam.Builder().count(-1).build(), new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { - LogUtil.log(TAG, "拉取文件列表成功"); - // 重置pullqwq标志,下次调用重新从IDLE拉取 - pullqwq = false; - // 拉取成功后,等待状态变为UP_TO_DATE - new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000); + LogUtil.log(TAG, "拉取请求已接受,等待状态变为UP_TO_DATE"); + pullMediaFileListFromCameraFailTimes = 0; } @Override public void onFailure(@NonNull IDJIError idjiError) { - LogUtil.log(TAG, "拉取媒体文件失败: " + new Gson().toJson(idjiError)); - // 失败后重试,最多重试3次 + LogUtil.log(TAG, "拉取请求失败: " + new Gson().toJson(idjiError)); if (pullMediaFileListFromCameraFailTimes < 5) { pullMediaFileListFromCameraFailTimes++; LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试..."); - new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000); } else { - LogUtil.log(TAG, "重试次数达到上限,拉取失败"); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server("拉取媒体文件失败",2); disablePlayback(); @@ -285,7 +276,9 @@ public class MediaManager extends BaseManager { } } }); - } else if (mState == MediaFileListState.UP_TO_DATE) { + // 不依赖回调续命,无条件调度下一轮轮询 + new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000); + } else if (currentState == MediaFileListState.UP_TO_DATE) { // 状态为UP_TO_DATE,获取文件列表数据 LogUtil.log(TAG, "状态为UP_TO_DATE,获取文件列表数据"); try { @@ -299,7 +292,7 @@ public class MediaManager extends BaseManager { if (pullMediaFileListFromCameraFailTimes < 2) { pullMediaFileListFromCameraFailTimes++; LogUtil.log(TAG, "第" + pullMediaFileListFromCameraFailTimes + "次重试..."); - new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 2000); + new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 3000); } else { LogUtil.log(TAG, "UP_TO_DATE状态文件列表持续为空,确认无文件"); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); @@ -357,8 +350,8 @@ public class MediaManager extends BaseManager { } } } else { - // 其他状态(如UPDATING),等待状态变化 - LogUtil.log(TAG, "状态为" + mState + ",等待状态变化... (count=" + updatingWaitCount + ")"); + // 其他状态(如UPDATING),等待状态变化,不要重复调用pullMediaFileListFromCamera + LogUtil.log(TAG, "状态为" + currentState + ",等待状态变化... (count=" + updatingWaitCount + ")"); updatingWaitCount++; // 增加超时处理,避免无限等待 @@ -369,24 +362,8 @@ public class MediaManager extends BaseManager { disablePlayback(); LogUtil.log(TAG, "发送关闭无人机"); return; - } else { - if (pullqwq == false) { - MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(new PullMediaFileListParam.Builder().count(-1).build(), new CommonCallbacks.CompletionCallback() { - @Override - public void onSuccess() { - LogUtil.log(TAG,"拉取成功"); - } - - @Override - public void onFailure(@NonNull IDJIError idjiError) { - LogUtil.log(TAG,"拉取失败"); - } - }); - pullqwq = true; - } } - new Handler().postDelayed(MediaManager.this::pullMediaFileListFromCamera, 1000); } } @@ -621,7 +598,7 @@ public class MediaManager extends BaseManager { // 上传成功,重置下载重试计数 downloadFailTimes = 0; //上传完成发送事件 - sendMediaUpload2Server(mediaFile.getFileName(),mediaFiles.size(),downLoadMediaFileIndex); + sendMediaUpload2Server(mediaFile.getFileName(), downLoadMediaFileIndex + 1, mediaFiles.size()); } @RequiresApi(Build.VERSION_CODES.O) 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 7459775d..dfc3b70c 100644 --- a/app/src/main/java/com/aros/apron/manager/MissionV3Manager.java +++ b/app/src/main/java/com/aros/apron/manager/MissionV3Manager.java @@ -187,12 +187,10 @@ public class MissionV3Manager extends BaseManager { Movement.getInstance().setVirtualStickQuitMission(false); - break; case PREPARING: - Movement.getInstance().setTask_status("in_progress"); // if (PreferenceUtils.getInstance().getMissionType() == 2 @@ -235,8 +233,6 @@ public class MissionV3Manager extends BaseManager { sendEvent2Server("任务状态:进入航线飞行,飞往指定航线的第一个航点", 1); - - //航线飞行 if (Movement.getInstance().getFlightmode() == 1) { Movement.getInstance().setMode_code(5); @@ -250,7 +246,7 @@ public class MissionV3Manager extends BaseManager { sendEvent2Server("任务状态:航线任务执行中", 1); Movement.getInstance().setTask_status("in_progress"); - + Movement.getInstance().setTask_break_reason(0); // if (PreferenceUtils.getInstance().getMissionType() == 2 // ) { // Movement.getInstance().setTask_status("paused"); @@ -273,7 +269,7 @@ public class MissionV3Manager extends BaseManager { sendEvent2Server("任务状态:航线任务执行中断", 1); - if(PreferenceUtils.getInstance().getMissionType()==0){ + if (PreferenceUtils.getInstance().getMissionType() == 0) { Movement.getInstance().setTask_status("paused"); sendFlightTaskProgress2Server(); } @@ -312,7 +308,7 @@ public class MissionV3Manager extends BaseManager { break; case FINISHED: - + FlyToPointManager.getInstance().setReceiverMission(false); //释放锁 Synchronizedstatus.setIsruning(false); Synchronizedstatus.setInitStatus(false); @@ -402,9 +398,7 @@ public class MissionV3Manager extends BaseManager { @Override public void onWaylineExecutingInfoUpdate(WaylineExecutingInfo excutingWaylineInfo) { if (excutingWaylineInfo != null) { - LogUtil.log(TAG,"航点"+excutingWaylineInfo.getCurrentWaypointIndex()); - - + LogUtil.log(TAG, "航点" + excutingWaylineInfo.getCurrentWaypointIndex()); //状态等执行完发送再更新(测试暂停时不改变index为0) if (excutingWaylineInfo.getCurrentWaypointIndex() != 0) { @@ -416,13 +410,115 @@ public class MissionV3Manager extends BaseManager { @Override public void onWaylineExecutingInterruptReasonUpdate(IDJIError error) { - LogUtil.log(TAG, "航线中断原因" + error.toString()); - sendEvent2Server("航线中断原因" + error.toString(), 2); + + LogUtil.log(TAG, "航线中断原因" + error); + sendEvent2Server("航线中断原因" + error,1); + + + if (error == null) { + LogUtil.log(TAG, "航线中断原因: error为null"); + return; + } + + String errorCode = error.errorCode(); + switch (errorCode != null ? errorCode : "") { + case "USER_BREAK": + // 用户手动停止 + Movement.getInstance().setTask_break_reason(1282); + LogUtil.log(TAG, "用户主动中断航线"); + sendEvent2Server("用户主动中断航线", 2); + break; + case "INTERRUPT_REASON_AVOID": + // 触发避障 + Movement.getInstance().setTask_break_reason(517); + LogUtil.log(TAG, "触发避障导致航线中断"); + sendEvent2Server("触发避障导致航线中断", 2); + break; + case "INTERRUPT_REASON_AVOID_EMERGENCY_BREAK": + // 触发避障而紧急刹停 + Movement.getInstance().setTask_break_reason(1565); + LogUtil.log(TAG, "触发避障紧急刹停"); + sendEvent2Server("触发避障紧急刹停", 2); + break; + case "INTERRUPT_REASON_AVOID_HEIGHT_LIMIT": + // 被限高或限低 + Movement.getInstance().setTask_break_reason(513); + LogUtil.log(TAG, "被限高或限低导致航线中断"); + sendEvent2Server("被限高或限低导致航线中断", 2); + break; + case "INTERRUPT_REASON_AVOID_RTK_UNHEALTHY": + // RTK信号异常 + Movement.getInstance().setTask_break_reason(518); + LogUtil.log(TAG, "RTK信号异常导致航线中断"); + sendEvent2Server("RTK信号异常导致航线中断", 2); + break; + case "INTERRUPT_REASON_AVOID_AIRPORT_LIMIT": + // 靠近机场(如民航机场) + Movement.getInstance().setTask_break_reason(521); + LogUtil.log(TAG, "靠近机场导致航线中断"); + sendEvent2Server("靠近机场导致航线中断", 2); + break; + case "BOUNDARY_LIMIT": + // 接近限飞区 + Movement.getInstance().setTask_break_reason(515); + LogUtil.log(TAG, "接近限飞区导致航线中断"); + sendEvent2Server("接近限飞区导致航线中断", 2); + break; + case "INTERRUPT_REASON_LOW_BATTERY": + // 低电量中断 + Movement.getInstance().setTask_break_reason(773); + LogUtil.log(TAG, "低电量触发航线中断"); + sendEvent2Server("低电量触发航线中断", 2); + break; + case "INTERRUPT_REASON_RC_SIGNAL_LOST": + // 遥控器信号丢失 + Movement.getInstance().setTask_break_reason(775); + LogUtil.log(TAG, "遥控器信号丢失,航线中断"); + sendEvent2Server("遥控器信号丢失,航线中断", 2); + break; + case "INTERRUPT_REASON_GPS_INVALID": + // GPS 信号无效 + Movement.getInstance().setTask_break_reason(769); + LogUtil.log(TAG, "GPS信号无效,航线中断"); + sendEvent2Server("GPS信号无效,航线中断", 2); + break; + default: + LogUtil.log(TAG, "未知航线中断原因: " + errorCode); + Movement.getInstance().setTask_break_reason(65535); + sendEvent2Server("未知航线中断原因", 2); + break; + } + + //LogUtil.log(TAG, "航线中断: ---" + new Gson().toJson(error)); + // sendEvent2Server("航线中断原因: " + errorCode, 2); +// LogUtil.log(TAG, "航线中断原因" + error); + + if (error.errorCode().equals("USER_BREAK") + || error.errorCode().equals("INTERRUPT_REASON_AVOID_USER_REQ_BREAK")) + {//如果是手动暂停航线,则不会触发返航或拉高 + return; + } else { + if (PreferenceUtils.getInstance().getMissionInterruptAction() == 2) { + if (error.errorCode().equals("INTERRUPT_REASON_AVOID") || + error.errorCode().equals("INTERRUPT_REASON_AVOID_HEIGHT_LIMIT")) { + mainHandler.postDelayed(new Runnable() { + @Override + public void run() { + resumeMission(null); + } + }, 1000); + } else { + WayLineExecutingInterruptManager.getInstance().onExecutingInterruptToDo(); + } + } else if (PreferenceUtils.getInstance().getMissionInterruptAction() == 3) { + WayLineExecutingInterruptManager.getInstance().onExecutingInterruptToDo(); + return; + } + } } }); - waypointMissionManager.addWaypointActionListener(new WaypointActionListener() { @Override public void onExecutionStart(int actionId) { @@ -478,7 +574,6 @@ public class MissionV3Manager extends BaseManager { Movement.getInstance().setTask_current_step(5); - //1.检查图传是否连接 //避免重复执行 if (isReceiverMission == false) { @@ -495,7 +590,7 @@ public class MissionV3Manager extends BaseManager { verifyGpsAndMissionState(message); } - // verifyGpsAndMissionState(message); + // verifyGpsAndMissionState(message); } @@ -575,14 +670,14 @@ 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 ||quality == 3); + boolean isGpsQualityValid = (quality == 4 || quality == 5 || quality == 10 || quality == 3); boolean GPSSatelliteCountValid = GPSSatelliteCount > 12; LogUtil.log(TAG, "isMissionStateValid" + isMissionStateValid + "isPlaneMessageValid" + isPlaneMessageValid + "isGpsQualityValid" + isGpsQualityValid); // if (isMissionStateValid && isPlaneMessageValid && isGpsQualityValid) { sendEvent2Server("卫星数量" + GPSSatelliteCount + "gps是否ok" + GPSSatelliteCountValid, 1); - if (isGpsQualityValid || GPSSatelliteCountValid ) { + if (isGpsQualityValid || GPSSatelliteCountValid) { //5.下载航线 downLoadKMZFile(message); sendEvent2Server("执行下载航线成功", 1); @@ -596,6 +691,13 @@ public class MissionV3Manager extends BaseManager { public void run() { verifyGpsAndMissionStateTimes++; verifyGpsAndMissionState(message); + + LogUtil.log(TAG, "航线状态第" + verifyGpsAndMissionStateTimes + "次检索失败:" + + WaypointMissionExecuteState.find(missionStateCode).name() + + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + + Movement.getInstance().getPlaneMessage() + + "-GPS信号等级:" + Movement.getInstance().getQuality()); + sendEvent2Server("航线状态第" + verifyGpsAndMissionStateTimes + "次检索失败:" + WaypointMissionExecuteState.find(missionStateCode).name() + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + @@ -605,6 +707,11 @@ public class MissionV3Manager extends BaseManager { }, 2000); } else { + LogUtil.log(TAG, "飞行器自检异常:" + WaypointMissionExecuteState.find(missionStateCode).name() + + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + + Movement.getInstance().getPlaneMessage() + + "-GPS信号等级:" + Movement.getInstance().getQuality()); + sendEvent2Server("飞行器自检异常:" + WaypointMissionExecuteState.find(missionStateCode).name() + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + Movement.getInstance().getPlaneMessage() + @@ -630,7 +737,7 @@ public class MissionV3Manager extends BaseManager { @Override public void onFailure(Call call, IOException e) { sendEvent2Server("任务下载失败:" + e.toString(), 2); - // LogUtil.log(TAG, "任务下载失败"); + LogUtil.log(TAG, "任务下载失败"); TaskFailManager.getInstance().sendTaskFailMsg2Server(-1); } @@ -655,6 +762,7 @@ public class MissionV3Manager extends BaseManager { } fos.flush(); sendEvent2Server("航线下载成功 ", 1); + LogUtil.log(TAG, "航线下载成功2"); //LogUtil.log(TAG, "航线下载成功"); mainHandler.post(new Runnable() { @Override @@ -664,7 +772,7 @@ public class MissionV3Manager extends BaseManager { }); } catch (Exception e) { sendEvent2Server("任务下载失败,网络异常:" + e.toString(), 2); - //LogUtil.log(TAG, "任务下载失败,网络异常"); + LogUtil.log(TAG, "任务下载失败,网络异常"); TaskFailManager.getInstance().sendTaskFailMsg2Server(-1); } } @@ -798,7 +906,7 @@ public class MissionV3Manager extends BaseManager { * @param message */ public void startMission(MessageDown message) { - Boolean isConnect =true; + Boolean isConnect = true; if (isConnect != null && isConnect) { int missionStateCode = Movement.getInstance().getMissionStateCode(); //每次航线开始时,重置是否需要识别二维码状态,避免刚起飞就识别二维码/并确保不是飞向备降点的航线 @@ -892,13 +1000,12 @@ public class MissionV3Manager extends BaseManager { public void onSuccess() { sendMsg2Server(message); - if( PreferenceUtils.getInstance().getMissionType()==0){ + if (PreferenceUtils.getInstance().getMissionType() == 0) { Movement.getInstance().setTask_status("paused"); sendFlightTaskProgress2Server(); } - //暂停成功就是手动飞行 Movement.getInstance().setMode_code(3); @@ -920,7 +1027,7 @@ public class MissionV3Manager extends BaseManager { KeyConnection)); if (isConnect != null && isConnect) { if (isConnect == null || !isConnect || !getGimbalAndCameraEnabled()) { - LogUtil.log(TAG,"当前状态禁止恢复"); + LogUtil.log(TAG, "当前状态禁止恢复"); sendFailMsg2Server(message, "当前状态禁止恢复"); return; } @@ -937,14 +1044,14 @@ public class MissionV3Manager extends BaseManager { sendMsg2Server(message); Movement.getInstance().setTask_status("in_progress"); - if( PreferenceUtils.getInstance().getMissionType()==0){ + if (PreferenceUtils.getInstance().getMissionType() == 0) { Movement.getInstance().setCameraMode(5); - }else if( PreferenceUtils.getInstance().getMissionType()==1){ + } else if (PreferenceUtils.getInstance().getMissionType() == 1) { Movement.getInstance().setCameraMode(17); } - if(PreferenceUtils.getInstance().getMissionType()==0){ + if (PreferenceUtils.getInstance().getMissionType() == 0) { sendFlightTaskProgress2Server(); } @@ -972,15 +1079,15 @@ public class MissionV3Manager extends BaseManager { sendMsg2Server(message); PreferenceUtils.getInstance().setIsNewRoute(false); - if( PreferenceUtils.getInstance().getMissionType()==0){ + if (PreferenceUtils.getInstance().getMissionType() == 0) { Movement.getInstance().setCameraMode(5); - }else if( PreferenceUtils.getInstance().getMissionType()==1){ + } else if (PreferenceUtils.getInstance().getMissionType() == 1) { Movement.getInstance().setCameraMode(17); } LogUtil.log(TAG, "恢复断点航线成功"); Movement.getInstance().setTask_status("in_progress"); - if(PreferenceUtils.getInstance().getMissionType()==0){ + if (PreferenceUtils.getInstance().getMissionType() == 0) { sendFlightTaskProgress2Server(); } @@ -1034,7 +1141,7 @@ public class MissionV3Manager extends BaseManager { LogUtil.log(TAG, "图传仍未恢复,重启 AMS 第 " + (times + 1) + " 次"); mainHandler.postDelayed(() -> RestartAPPTool.INSTANCE.restartApp(ApronApp.Companion.getContext()), 1000); - }else{ + } else { sendEvent2Server("达到重启最大次数还没有获得图传", 2); TaskFailManager.getInstance().sendTaskFailMsg2Server(-1); } 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 d2db938f..e5a6c00b 100644 --- a/app/src/main/java/com/aros/apron/manager/OSDManager.java +++ b/app/src/main/java/com/aros/apron/manager/OSDManager.java @@ -181,26 +181,31 @@ public class OSDManager extends BaseManager { cameraA.setCamera_mode(Movement.getInstance().getCamera_mode()); cameraA.setIr_metering_mode(Movement.getInstance().getIr_metering_mode()); cameraA.setCamera_mode(Movement.getInstance().getCamera_mode()); + irMeteringPoint.setTemperature(Movement.getInstance().getTemperature()); irMeteringPoint.setX(Movement.getInstance().getX()); irMeteringPoint.setY(Movement.getInstance().getY()); + + cameraA.setIr_metering_point(irMeteringPoint); Osd.Data.Cameras.IrMeteringArea irMeteringArea = new Osd.Data.Cameras.IrMeteringArea(); - irMeteringArea.setX(0); - irMeteringArea.setY(0); - irMeteringArea.setWidth(0); - irMeteringArea.setHeight(0); - irMeteringArea.setAver_temperature(0); + irMeteringArea.setX(Movement.getInstance().getIr_region_x()); + irMeteringArea.setY(Movement.getInstance().getIr_region_y()); + irMeteringArea.setWidth(Movement.getInstance().getIr_region_width()); + irMeteringArea.setHeight(Movement.getInstance().getIr_region_height()); + irMeteringArea.setAver_temperature(Movement.getInstance().getIr_region_aver_temperature()); Osd.Data.Cameras.IrTemperaturePoint irMinTemp = new Osd.Data.Cameras.IrTemperaturePoint(); - irMinTemp.setX(0); - irMinTemp.setY(0); - irMinTemp.setTemperature(0); + irMinTemp.setX(Movement.getInstance().getIr_region_min_temperature_x()); + irMinTemp.setY(Movement.getInstance().getIr_region_min_temperature_y()); + irMinTemp.setTemperature(Movement.getInstance().getIr_region_min_temperature()); irMeteringArea.setMin_temperature_point(irMinTemp); Osd.Data.Cameras.IrTemperaturePoint irMaxTemp = new Osd.Data.Cameras.IrTemperaturePoint(); - irMaxTemp.setX(0); - irMaxTemp.setY(0); - irMaxTemp.setTemperature(0); + irMaxTemp.setX(Movement.getInstance().getIr_region_max_temperature_x()); + irMaxTemp.setY(Movement.getInstance().getIr_region_max_temperature_y()); + irMaxTemp.setTemperature(Movement.getInstance().getIr_region_max_temperature()); irMeteringArea.setMax_temperature_point(irMaxTemp); + + cameraA.setIr_metering_area(irMeteringArea); cameraA.setIr_zoom_factor(Movement.getInstance().getIr_zoom_factor()); cameraA.setPayload_index("53-0-0"); @@ -276,6 +281,8 @@ public class OSDManager extends BaseManager { data.setVertical_speed(Movement.getInstance().getVertical_speed()); data.setWind_direction(Movement.getInstance().getWind_direction()); data.setWind_speed(Movement.getInstance().getWind_speed()); + data.setOcu_sync_lte(Movement.getInstance().isOcuSyncLte()); + data.setOcu_sync_lte_time(Movement.getInstance().getOcuSyncLteTime()); Osd.Data.MaintainStatus maintainStatus = new Osd.Data.MaintainStatus(); Osd.Data.MaintainStatus.MaintainStatusArray maintainItem = new Osd.Data.MaintainStatus.MaintainStatusArray(); diff --git a/app/src/main/java/com/aros/apron/manager/RTHTrackerManager.java b/app/src/main/java/com/aros/apron/manager/RTHTrackerManager.java new file mode 100644 index 00000000..42afceb7 --- /dev/null +++ b/app/src/main/java/com/aros/apron/manager/RTHTrackerManager.java @@ -0,0 +1,158 @@ +package com.aros.apron.manager; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import com.aros.apron.base.BaseManager; +import com.aros.apron.constant.AMSConfig; +import com.aros.apron.entity.Movement; +import com.aros.apron.entity.ReturnHomeInfo; +import com.aros.apron.entity.ReturnHomeInfo.PathPoint; +import com.aros.apron.tools.LogUtil; +import com.aros.apron.tools.PreferenceUtils; +import com.google.gson.Gson; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import dji.sdk.keyvalue.key.FlightControllerKey; +import dji.sdk.keyvalue.key.KeyTools; +import dji.sdk.keyvalue.value.common.LocationCoordinate3D; +import dji.v5.manager.KeyManager; +import com.aros.apron.tools.MqttManager; + +/** + * 返航轨迹记录管理器 + * 每2s上报3个点: + * index 0: 飞机当前位置(实时) + * index 1: 飞机与返航点之间的中点(实时计算) + * index 2: 返航点(固定) + */ +public class RTHTrackerManager extends BaseManager { + + private static final String TAG = "RTHTrackerManager"; + private static final long REPORT_INTERVAL_MS = 2000; + + private RTHTrackerManager() { + } + + private static class RTHTrackerHolder { + private static final RTHTrackerManager INSTANCE = new RTHTrackerManager(); + } + + public static RTHTrackerManager getInstance() { + return RTHTrackerHolder.INSTANCE; + } + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private boolean isRunning = false; + + private final Runnable reportRunnable = new Runnable() { + @Override + public void run() { + sendReturnHomeInfo(); + if (isRunning) { + mHandler.postDelayed(this, REPORT_INTERVAL_MS); + } + } + }; + + /** + * 开始定时上报返航轨迹(每2s一次) + */ + public void startReporting() { + if (isRunning) { + LogUtil.log(TAG, "返航轨迹上报已在运行中"); + return; + } + isRunning = true; + mHandler.post(reportRunnable); + LogUtil.log(TAG, "开启返航轨迹定时上报,间隔: " + REPORT_INTERVAL_MS + "ms"); + } + + /** + * 停止定时上报 + */ + public void stopReporting() { + isRunning = false; + mHandler.removeCallbacks(reportRunnable); + LogUtil.log(TAG, "关闭返航轨迹定时上报"); + } + + /** + * 发送返航轨迹信息到服务器 + * 3个点:当前位置 -> 中间点 -> 返航点 + */ + private void sendReturnHomeInfo() { + try { + if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) { + return; + } + + // 获取飞机当前位置 + LocationCoordinate3D currentLocation = + KeyManager.getInstance().getValue( + KeyTools.createKey(FlightControllerKey.KeyAircraftLocation3D)); + + if (currentLocation == null) { + return; + } + + double currentLat = currentLocation.getLatitude(); + double currentLng = currentLocation.getLongitude(); + Double altDouble = currentLocation.getAltitude(); + double currentAlt = altDouble != null ? altDouble : 0; + + // 获取返航点 + double homeLat = Movement.getInstance().getHomepoint_latitude(); + double homeLng = Movement.getInstance().getHomepoint_longitude(); + + // 计算中点 + double midLat = (currentLat + homeLat) / 2.0; + double midLng = (currentLng + homeLng) / 2.0; + double midAlt = currentAlt; + + // 构建3个点 + List pathPoints = new ArrayList<>(3); + pathPoints.add(new PathPoint(currentLat, currentLng, (float) currentAlt)); // index 0: 飞机当前位置 + pathPoints.add(new PathPoint(midLat, midLng, (float) midAlt)); // index 1: 中间点 + pathPoints.add(new PathPoint(homeLat, homeLng, (float) currentAlt)); // index 2: 返航点 + + ReturnHomeInfo returnHomeInfo = new ReturnHomeInfo(); + returnHomeInfo.setBid(UUID.randomUUID().toString()); + returnHomeInfo.setTid(UUID.randomUUID().toString()); + returnHomeInfo.setTimestamp(System.currentTimeMillis()); + returnHomeInfo.setMethod("return_home_info"); + + ReturnHomeInfo.Data data = new ReturnHomeInfo.Data(); + String flightId = PreferenceUtils.getInstance().getFlightId(); + data.setFlight_id(TextUtils.isEmpty(flightId) ? "null" : flightId); + data.setLast_point_type(0); + data.setPlanned_path_points(pathPoints); + + returnHomeInfo.setData(data); + + String json = new Gson().toJson(returnHomeInfo); + MqttMessage mqttMessage = new MqttMessage(json.getBytes("UTF-8")); + mqttMessage.setQos(0); + + MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, mqttMessage); + // LogUtil.log(TAG, String.format("发送返航轨迹 - 飞机:[%.6f,%.6f,%.1f] 中点:[%.6f,%.6f,%.1f] 返航:[%.6f,%.6f,%.1f]", + // currentLat, currentLng, currentAlt, midLat, midLng, midAlt, homeLat, homeLng, currentAlt)); + + } catch (Exception e) { + LogUtil.log(TAG, "发送return_home_info异常: " + e.getMessage()); + } + } + + /** + * 重置状态 + */ + public void reset() { + stopReporting(); + } +} diff --git a/app/src/main/java/com/aros/apron/manager/StickManager.java b/app/src/main/java/com/aros/apron/manager/StickManager.java index 235834ee..905a2ca1 100644 --- a/app/src/main/java/com/aros/apron/manager/StickManager.java +++ b/app/src/main/java/com/aros/apron/manager/StickManager.java @@ -307,13 +307,9 @@ public class StickManager extends BaseManager { param.setVerticalControlMode(VerticalControlMode.VELOCITY); param.setRollPitchCoordinateSystem(FlightCoordinateSystem.BODY); - if (!TextUtils.isEmpty(message.getData().getY())) { - param.setPitch(Double.valueOf(message.getData().getY()));//左右(速度模式-10m/s-10m/s) - } - if (!TextUtils.isEmpty(message.getData().getX())) { - param.setRoll(Double.valueOf(message.getData().getX()));//前后(速度模式-10m/s-10m/s) + param.setPitch(message.getData().getY());//左右(速度模式-10m/s-10m/s) + param.setRoll(message.getData().getX());//前后(速度模式-10m/s-10m/s) - } if (!TextUtils.isEmpty(message.getData().getW())) { param.setYaw(Double.valueOf(message.getData().getW()));//旋转(角速度模式-100-100) 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 2c15d180..fb1ddc9e 100644 --- a/app/src/main/java/com/aros/apron/manager/StreamManager.java +++ b/app/src/main/java/com/aros/apron/manager/StreamManager.java @@ -276,6 +276,9 @@ public class StreamManager extends BaseManager { private int isliveindex = 1; //1 代表 port 2 代表 fpv public void switchptspfpv(ComponentIndexType ComponentIndex, MessageDown message) { + if(isliveindex==2){ + return; + } isliveindex = 2; sendMsg2Server(message); ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager(); @@ -298,6 +301,9 @@ public class StreamManager extends BaseManager { } public void switchptspport(ComponentIndexType ComponentIndex, MessageDown message) { + if(isliveindex==1){ + return; + } isliveindex = 1; sendMsg2Server(message); ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager(); @@ -330,7 +336,6 @@ public class StreamManager extends BaseManager { streamExecutor.execute(() -> { isStartingRTSP = true; - // 每次新推流尝试:清零所有状态标志(修复 Bug 1, 2, 5) if (startLiveFailTimes == 0) { isLiveStreamAlreadyStart = false; LogUtil.log(TAG, "========== 开始 RTSP 推流流程 =========="); @@ -350,7 +355,6 @@ public class StreamManager extends BaseManager { return; } - // 1. 检查是否已经在推流,避免重复启动 if (liveStreamManager.isStreaming()) { LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动"); isLiveStreamAlreadyStart = true; @@ -358,7 +362,6 @@ public class StreamManager extends BaseManager { return; } - // 2. 检查相机流是否准备好 if (!MainActivity.Companion.getStreamReceive()) { LogUtil.log(TAG, "相机流未准备好,尝试模拟点击 FPV Widget 恢复"); mainHandler.post(() -> { @@ -381,7 +384,7 @@ public class StreamManager extends BaseManager { return; } - // 3. 检查 RTSP 配置 + String rtspUser = PreferenceUtils.getInstance().getRtspUserName(); String rtspPort = PreferenceUtils.getInstance().getRtspPort(); String rtspPass = PreferenceUtils.getInstance().getRtspPassWord(); @@ -395,14 +398,14 @@ public class StreamManager extends BaseManager { LogUtil.log(TAG, "RTSP 配置检查通过:user=" + rtspUser + ", port=" + rtspPort + ", camera=" + (isliveindex == 1 ? "PORT_1" : "FPV")); - // 4. 等待相机模式稳定(避免刚切换模式就推流) + //等待相机模式稳定(避免刚切换模式就推流) try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - // 5. 设置 RTSP 参数(在主线程执行) + //设置 RTSP 参数 final ILiveStreamManager finalLiveStreamManager = liveStreamManager; mainHandler.post(() -> { LiveStreamSettings.Builder streamSettingBuilder = new LiveStreamSettings.Builder(); @@ -416,17 +419,15 @@ public class StreamManager extends BaseManager { finalLiveStreamManager.setLiveStreamSettings(streamSettings); - // 6. 设置相机源 + // 设置相机源 ComponentIndexType cameraIndex = (isliveindex == 1) ? ComponentIndexType.PORT_1 : ComponentIndexType.FPV; finalLiveStreamManager.setCameraIndex(cameraIndex); - // 7. 设置推流质量 finalLiveStreamManager.setLiveStreamQuality(StreamQuality.FULL_HD); finalLiveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO); LogUtil.log(TAG, "RTSP 参数设置完成,等待 500ms 后启动推流"); - // 8. 等待设置生效 mainHandler.postDelayed(() -> { streamExecutor.execute(() -> { // 9. 启动推流前再次检查 @@ -449,7 +450,6 @@ public class StreamManager extends BaseManager { // ========== 【新增】实际启动流的私有方法,确保在子线程执行 ========== private void doStartLiveWithRTSP(ILiveStreamManager liveStreamManager, boolean isRestart) { - // 启动前再次检查状态 if (liveStreamManager.isStreaming()) { LogUtil.log(TAG, "推流已在运行,跳过启动 (isRestart=" + isRestart + ")"); isLiveStreamAlreadyStart = true; 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 5818d1b2..9da9be58 100644 --- a/app/src/main/java/com/aros/apron/manager/TakeOffToPointManager.java +++ b/app/src/main/java/com/aros/apron/manager/TakeOffToPointManager.java @@ -202,6 +202,13 @@ public class TakeOffToPointManager extends BaseManager { public void run() { verifyGpsAndMissionStateTimes++; verifyGpsAndMissionState(message); + + LogUtil.log(TAG,"航线状态第" + verifyGpsAndMissionStateTimes + "次检索失败:" + + WaypointMissionExecuteState.find(missionStateCode).name() + + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + + Movement.getInstance().getPlaneMessage() + + "-GPS信号等级:" + Movement.getInstance().getQuality()); + sendEvent2Server("航线状态第" + verifyGpsAndMissionStateTimes + "次检索失败:" + WaypointMissionExecuteState.find(missionStateCode).name() + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + @@ -212,6 +219,12 @@ public class TakeOffToPointManager extends BaseManager { } else { ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); + + LogUtil.log(TAG,"飞行器自检异常:" + WaypointMissionExecuteState.find(missionStateCode).name() + + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + + Movement.getInstance().getPlaneMessage() + + "-GPS信号等级:" + Movement.getInstance().getQuality()); + sendEvent2Server("飞行器自检异常:" + WaypointMissionExecuteState.find(missionStateCode).name() + "-RTK:" + Movement.getInstance().getIs_fixed() + "-飞行器状态:" + Movement.getInstance().getPlaneMessage() + diff --git a/app/src/main/java/com/aros/apron/manager/UpdateManager.java b/app/src/main/java/com/aros/apron/manager/UpdateManager.java new file mode 100644 index 00000000..3c0757ae --- /dev/null +++ b/app/src/main/java/com/aros/apron/manager/UpdateManager.java @@ -0,0 +1,145 @@ +package com.aros.apron.manager; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; + +import androidx.core.app.NotificationCompat; +import androidx.core.content.FileProvider; + +import com.aros.apron.R; +import com.aros.apron.tools.LogUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class UpdateManager { + + private static final String TAG = "UpdateManager"; + private static final String CHANNEL_ID = "app_update_channel"; + private static final int NOTIFICATION_ID = 1001; + + private static UpdateManager instance; + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + + private UpdateManager() {} + + public static synchronized UpdateManager getInstance() { + if (instance == null) { + instance = new UpdateManager(); + } + return instance; + } + + /** + * 开始下载并安装APK + * @param context 上下文 + * @param apkUrl APK下载链接 + * @param replyCallback 下载完成后回复服务器的回调 + */ + public void startUpdate(Context context, String apkUrl, Runnable replyCallback) { + LogUtil.log(TAG, "开始远程更新: " + apkUrl); + showNotification(context, 0); + + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder().url(apkUrl).build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + LogUtil.log(TAG, "APK下载失败: " + e.toString()); + mainHandler.post(() -> { + if (replyCallback != null) replyCallback.run(); + }); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response == null || !response.isSuccessful()) { + LogUtil.log(TAG, "APK下载失败, 状态码: " + (response != null ? response.code() : "null")); + mainHandler.post(() -> { + if (replyCallback != null) replyCallback.run(); + }); + return; + } + + long totalSize = response.body().contentLength(); + long downloaded = 0; + File apkFile = new File(context.getExternalFilesDir(null), "app_update.apk"); + + try (InputStream is = response.body().byteStream(); + OutputStream os = new FileOutputStream(apkFile)) { + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + downloaded += len; + final int progress = (int) ((downloaded * 100) / totalSize); + mainHandler.post(() -> showNotification(context, progress)); + } + os.flush(); + + LogUtil.log(TAG, "APK下载完成: " + apkFile.getAbsolutePath()); + mainHandler.post(() -> { + showNotification(context, 100); + installApk(context, apkFile); + if (replyCallback != null) replyCallback.run(); + }); + } catch (Exception e) { + LogUtil.log(TAG, "APK写入失败: " + e.toString()); + mainHandler.post(() -> { + if (replyCallback != null) replyCallback.run(); + }); + } + } + }); + } + + private void showNotification(Context context, int progress) { + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "应用更新", NotificationManager.IMPORTANCE_LOW); + manager.createNotificationChannel(channel); + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle("正在更新应用") + .setSmallIcon(R.mipmap.ic_launcher) + .setProgress(100, progress, false) + .setContentText(progress + "%") + .setOngoing(true); + + manager.notify(NOTIFICATION_ID, builder.build()); + } + + private void installApk(Context context, File apkFile) { + Uri apkUri; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile); + } else { + apkUri = Uri.fromFile(apkFile); + } + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } +} 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 3c671df7..0dbe6cc7 100644 --- a/app/src/main/java/com/aros/apron/mix/Aprondown.java +++ b/app/src/main/java/com/aros/apron/mix/Aprondown.java @@ -702,7 +702,7 @@ public class Aprondown { @Override public void run() { performOperation(); - if (handlerCallbackCount < 20) { + if (handlerCallbackCount < 15) { handler.postDelayed(this, 50); } else { performNextStep(); 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 ed98f9ea..39073310 100644 --- a/app/src/main/java/com/aros/apron/mix/Aprongim.java +++ b/app/src/main/java/com/aros/apron/mix/Aprongim.java @@ -269,7 +269,7 @@ public class Aprongim { }else{ if (ultHeight <=5) { - LENS_OFFSET_X=100; + LENS_OFFSET_X=120; LENS_OFFSET_Y = 100; }else if(ultHeight<10){ LENS_OFFSET_X=80; @@ -562,7 +562,7 @@ public class Aprongim { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 + 200.0; + double errX = cx - imgWidth / 2.0 + 120.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[13]++; } else { landingCounters[13] = 0; } if (landingCounters[13] >= 2 && !foundLanding) { foundLanding = true; landingId = 13; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } @@ -577,7 +577,7 @@ public class Aprongim { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[15]++; } else { landingCounters[15] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[15]++; } else { landingCounters[15] = 0; } if (landingCounters[15] >= 2 && !foundLanding) { foundLanding = true; landingId = 15; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[15] = 0; } @@ -588,7 +588,7 @@ public class Aprongim { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 - 200.0; + double errX = cx - imgWidth / 2.0 - 150.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[17]++; } else { landingCounters[17] = 0; } if (landingCounters[17] >= 2 && !foundLanding) { foundLanding = true; landingId = 17; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } @@ -601,9 +601,9 @@ public class Aprongim { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 + 200.0; + double errX = cx - imgWidth / 2.0 + 120.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[14]++; } else { landingCounters[14] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[14]++; } else { landingCounters[14] = 0; } if (landingCounters[14] >= 2 && !foundLanding) { foundLanding = true; landingId = 14; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[14] = 0; } @@ -616,7 +616,7 @@ public class Aprongim { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } if (landingCounters[16] >= 2 && !foundLanding) { foundLanding = true; landingId = 16; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[16] = 0; } @@ -627,7 +627,7 @@ public class Aprongim { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 - 200.0; + double errX = cx - imgWidth / 2.0 - 150.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[18]++; } else { landingCounters[18] = 0; } if (landingCounters[18] >= 2 && !foundLanding) { foundLanding = true; landingId = 18; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } @@ -657,7 +657,7 @@ public class Aprongim { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[17]++; } else { landingCounters[17] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[17]++; } else { landingCounters[17] = 0; } if (landingCounters[17] >= 2 && !foundLanding) { foundLanding = true; landingId = 17; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[17] = 0; } @@ -683,7 +683,7 @@ public class Aprongim { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 +240.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } if (landingCounters[16] >= 2 && !foundLanding) { foundLanding = true; landingId = 16; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[16] = 0; } @@ -696,7 +696,7 @@ public class Aprongim { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[18]++; } else { landingCounters[18] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[18]++; } else { landingCounters[18] = 0; } if (landingCounters[18] >= 2 && !foundLanding) { foundLanding = true; landingId = 18; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[18] = 0; } @@ -740,8 +740,8 @@ public class Aprongim { // 分段PID控制(原有逻辑保持不变) if(z <= 0.4){ - pidControlX.setInputFilterAll((float)offsetX/1750); - pidControlY.setInputFilterAll(-(float)offsetY/1750); + pidControlX.setInputFilterAll((float)offsetX/1800); + pidControlY.setInputFilterAll(-(float)offsetY/1800); if (pidControlX.get_pid()<0){ if (pidControlX.get_pid()<-0.125){ outX=absX<120?0:-0.125; @@ -1085,7 +1085,7 @@ public class Aprongim { @Override public void run() { performOperation(); - if (handlerCallbackCount < 20) { + if (handlerCallbackCount < 15) { handler.postDelayed(this, 50); } else { performNextStep(); 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 93017d33..0166a2e8 100644 --- a/app/src/main/java/com/aros/apron/tools/ApronArucoDetect.java +++ b/app/src/main/java/com/aros/apron/tools/ApronArucoDetect.java @@ -823,7 +823,7 @@ public class ApronArucoDetect { @Override public void run() { performOperation(); - if (handlerCallbackCount < 20) { + if (handlerCallbackCount < 15) { handler.postDelayed(this, 50); } else { performNextStep(); 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 0e4355f9..e7dbd038 100644 --- a/app/src/main/java/com/aros/apron/tools/ApronArucoDetectPort.java +++ b/app/src/main/java/com/aros/apron/tools/ApronArucoDetectPort.java @@ -192,8 +192,8 @@ public class ApronArucoDetectPort { }else{ if (ultHeight <=5) { - LENS_OFFSET_X=100; - LENS_OFFSET_Y = 100; + LENS_OFFSET_X=120; + LENS_OFFSET_Y = 120; }else if(ultHeight<10){ LENS_OFFSET_X=80; LENS_OFFSET_Y = 80; @@ -484,7 +484,7 @@ public class ApronArucoDetectPort { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 + 200.0; + double errX = cx - imgWidth / 2.0 + 150.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[13]++; } else { landingCounters[13] = 0; } if (landingCounters[13] >= 2 && !foundLanding) { foundLanding = true; landingId = 13; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } @@ -499,7 +499,7 @@ public class ApronArucoDetectPort { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[15]++; } else { landingCounters[15] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[15]++; } else { landingCounters[15] = 0; } if (landingCounters[15] >= 2 && !foundLanding) { foundLanding = true; landingId = 15; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[15] = 0; } @@ -510,7 +510,7 @@ public class ApronArucoDetectPort { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 - 200.0; + double errX = cx - imgWidth / 2.0 - 150.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[17]++; } else { landingCounters[17] = 0; } if (landingCounters[17] >= 2 && !foundLanding) { foundLanding = true; landingId = 17; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } @@ -523,9 +523,9 @@ public class ApronArucoDetectPort { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 + 200.0; + double errX = cx - imgWidth / 2.0 + 150.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[14]++; } else { landingCounters[14] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[14]++; } else { landingCounters[14] = 0; } if (landingCounters[14] >= 2 && !foundLanding) { foundLanding = true; landingId = 14; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[14] = 0; } @@ -538,7 +538,7 @@ public class ApronArucoDetectPort { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } if (landingCounters[16] >= 2 && !foundLanding) { foundLanding = true; landingId = 16; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[16] = 0; } @@ -549,7 +549,7 @@ public class ApronArucoDetectPort { double cx = 0, cy = 0; for (int i = 0; i < 4; i++) { double[] p = corner.get(0, i); cx += p[0]; cy += p[1]; } cx /= 4.0; cy /= 4.0; - double errX = cx - imgWidth / 2.0 - 200.0; + double errX = cx - imgWidth / 2.0 - 150.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[18]++; } else { landingCounters[18] = 0; } if (landingCounters[18] >= 2 && !foundLanding) { foundLanding = true; landingId = 18; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } @@ -579,7 +579,7 @@ public class ApronArucoDetectPort { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[17]++; } else { landingCounters[17] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[17]++; } else { landingCounters[17] = 0; } if (landingCounters[17] >= 2 && !foundLanding) { foundLanding = true; landingId = 17; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[17] = 0; } @@ -605,7 +605,7 @@ public class ApronArucoDetectPort { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 +240.0; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[16]++; } else { landingCounters[16] = 0; } if (landingCounters[16] >= 2 && !foundLanding) { foundLanding = true; landingId = 16; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[16] = 0; } @@ -618,7 +618,7 @@ public class ApronArucoDetectPort { cx /= 4.0; cy /= 4.0; double errX = cx - imgWidth / 2.0 - LENS_OFFSET_X; double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y; - if (Math.abs(errX) < 70 && Math.abs(errY) < 70) { landingCounters[18]++; } else { landingCounters[18] = 0; } + if (Math.abs(errX) < 60 && Math.abs(errY) < 70) { landingCounters[18]++; } else { landingCounters[18] = 0; } if (landingCounters[18] >= 2 && !foundLanding) { foundLanding = true; landingId = 18; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); } } else { landingCounters[18] = 0; } @@ -662,8 +662,8 @@ public class ApronArucoDetectPort { // 分段PID控制(原有逻辑保持不变) if(z <= 0.4){ - pidControlX.setInputFilterAll((float)offsetX/1750); - pidControlY.setInputFilterAll(-(float)offsetY/1750); + pidControlX.setInputFilterAll((float)offsetX/1800); + pidControlY.setInputFilterAll(-(float)offsetY/1800); if (pidControlX.get_pid()<0){ if (pidControlX.get_pid()<-0.125){ outX=absX<120?0:-0.125; @@ -1019,7 +1019,7 @@ public class ApronArucoDetectPort { @Override public void run() { performOperation(); - if (handlerCallbackCount < 20) { + if (handlerCallbackCount < 15) { handler.postDelayed(this, 50); } else { performNextStep(); diff --git a/app/src/main/java/com/aros/apron/tools/ApronArucodownmany.java b/app/src/main/java/com/aros/apron/tools/ApronArucodownmany.java new file mode 100644 index 00000000..ec6d9737 --- /dev/null +++ b/app/src/main/java/com/aros/apron/tools/ApronArucodownmany.java @@ -0,0 +1,1054 @@ +package com.aros.apron.tools; + +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import com.aros.apron.constant.AMSConfig; +import com.aros.apron.entity.ArucoMarker; +import com.aros.apron.entity.Movement; +import com.aros.apron.manager.AlternateLandingManager; +import com.aros.apron.manager.FlightManager; + +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.MatOfInt; +import org.opencv.core.MatOfPoint2f; +import org.opencv.core.Point; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.opencv.objdetect.ArucoDetector; +import org.opencv.objdetect.DetectorParameters; +import org.opencv.objdetect.Dictionary; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import dji.sdk.keyvalue.key.FlightControllerKey; +import dji.sdk.keyvalue.key.KeyTools; +import dji.v5.common.callback.CommonCallbacks; +import dji.v5.common.error.IDJIError; +import dji.v5.manager.KeyManager; + +/** + * 2帧一处理:0处理-1跳过-2处理-3跳过 + * 先预处理检测,失败再回退原图检测 + */ +public class ApronArucodownmany { + + private static final String TAG = "ApronArucoDetect"; + + // ========== 原有参数 ========== + private static double LENS_OFFSET_X = 0; + private static double LENS_OFFSET_Y = 0; + private static final int CENTER_ERR_MAX = 30; + private static final float SLOW_LAND_SPEED = -0.2f; + private static final float SLOW_SUPER_SPEED = -0.1f; + private static final int PIXEL_TRIGGER = 360; + private static final int TRIGGER_FRAME_THRESHOLD = 2; + + // ========== 【新增】7-8-9-10号锁定机制 ========== + private int lockedMarkerId = 0; // 0=未锁定, 7/8/9/10=已锁定 + private int[] landingCounters = new int[11]; // 索引对应ID + private int counterRound = 0; + private int lockedMarkerLostCounter = 0; // 锁定的码丢失帧计数 + private static final int LOCKED_MARKER_LOST_THRESHOLD = 5; // 丢失5帧后允许重新锁定 + + // ========== 【新增】每个小码的独立降落偏移阈值 ========== + // 格式:[id][0]=errX阈值, [id][1]=errY阈值 + // 可根据实际摆放位置调整 + private static final int[][] MARKER_LAND_THRESHOLDS = { + {0, 0}, // 0-6 不用 + {0, 0}, // 7 + {0, 0}, // 8 + {0, 0}, // 9 + {0, 0}, // 10 + }; + + /** + * 获取指定码的降落偏移(errX方向) + * 根据实际摆放位置调整:正值表示码在画面右侧,负值表示左侧 + */ + private double getMarkerOffsetX(int id) { + switch (id) { + case 7: return 110; // 往右110像素 + case 8: return 0; // 不动 + case 9: return 110; // 往右110像素 + case 10: return 0; // 不动 + default: return 0; + } + } + + /** + * 获取指定码的降落偏移(errY方向) + * 根据实际摆放位置调整:正值表示码在画面下方,负值表示上方 + */ + private double getMarkerOffsetY(int id) { + switch (id) { + case 7: return 0; // 不动 + case 8: return 0; // 不动 + case 9: return -110; // 往上110像素 + case 10: return -110; // 往上110像素 + default: return 0; + } + } + + /** + * 获取指定码的降落errX阈值 + */ + private int getMarkerLandErrXThreshold(int id) { + switch (id) { + case 7: return 30; + case 8: return 30; + case 9: return 30; + case 10: return 30; + default: return 30; + } + } + + /** + * 获取指定码的降落errY阈值 + */ + private int getMarkerLandErrYThreshold(int id) { + switch (id) { + case 7: return 30; + case 8: return 30; + case 9: return 30; + case 10: return 30; + default: return 30; + } + } + + // ========== 原有状态 ========== + private boolean isTriggerSuccess; + private boolean arucoNotFoundTag = false; + private boolean isStartAruco = false; + + public boolean isStartAruco() { + return isStartAruco; + } + + public void setStartAruco(boolean startAruco) { + isStartAruco = startAruco; + } + + ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + Future lastFuture = null; + private String TAG_LOG = getClass().getSimpleName(); + Double resultYaw = 0.0; + private boolean triggerToAlternateLandingPoint; + long startTime; + long endTime; + private int sigleMarkerDetectFailsTimes; + private boolean dropTimesTag; + private int dropTimes = 0; + private boolean isDoublePayload; + private int trycount = 0; + + public PIDControl pidControlX = null; + public PIDControl pidControlY = null; + public int checkThrowingErrorsTimes; + private int consecutiveTriggerCount = 0; + + // ========== 【新增】2帧一处理计数器 ========== + private int frameCounter = 0; + + // ========== 【新增】高度稳定性监测 ========== + private long lastHeightCheckTime = 0; + private double lastUltrasonicHeight = 0; + private static final long HEIGHT_STABLE_THRESHOLD = 45000; // 45秒 + private static final double HEIGHT_CHANGE_THRESHOLD =1; // 10厘米 + private boolean isHeightStableMonitoring = false; + + // ========== 【新增】强光预处理动态回退 ========== + // 连续"检测到轮廓但无法解码"的次数 + private int undecodableFrameCount = 0; + private static final int UNDECODABLE_THRESHOLD = 5; // 连续5帧解码失败则跳过预处理 + private boolean forceSkipPreprocess = false; // 强制跳过预处理标志 + private int skipPreprocessFrameCount = 0; // 强制跳过预处理的持续帧数 + private static final int SKIP_PREPROCESS_DURATION = 30; // 强制跳过30帧后恢复尝试预处理 + + // ========== 【新增】识别失败截图保存 ========== + private boolean saveFailScreenshot = false; // 是否开启识别失败截图保存 + private int failScreenshotIndex = 0; // 截图文件序号 + private static final int MAX_SCREENSHOTS = 50; // 最多保存50张截图 + + // ========== 原有方法 ========== + public int getCheckThrowingErrorsTimes() { return checkThrowingErrorsTimes; } + public void setCheckThrowingErrorsTimes(int v) { checkThrowingErrorsTimes = v; } + public boolean isTriggerSuccess() { return isTriggerSuccess; } + public void setTriggerSuccess(boolean v) { isTriggerSuccess = v; } + public boolean isDoublePayload() { return isDoublePayload; } + public void setDoublePayload(boolean v) { isDoublePayload = v; } + public boolean isSaveFailScreenshot() { return saveFailScreenshot; } + public void setSaveFailScreenshot(boolean v) { saveFailScreenshot = v; } + public boolean isStartFastStick() { return startFastStick; } + public void setStartFastStick(boolean v) { startFastStick = v; } + public boolean isCanLanding() { return canLanding; } + public void setCanLanding(boolean v) { + startTime = 0; + endTime = 0; + canLanding = v; + } + + private ApronArucodownmany() {} + private static class OpenCVHelperHolder { + private static final ApronArucodownmany INSTANCE = new ApronArucodownmany(); + static { INSTANCE.init(); } + } + public static ApronArucodownmany getInstance() { return OpenCVHelperHolder.INSTANCE; } + + public void init() { + pidControlX = new PIDControl(0.65f, 0.02f, 0.2f, 0.05f, 2.5f, 0.1f); + pidControlY = new PIDControl(0.65f, 0.02f, 0.2f, 0.05f, 2.5f, 0.1f); + pidControlX.reset(); + pidControlY.reset(); + } + + public boolean startFastStick; + public boolean canLanding; + + // ========== 【核心修改】主入口:2帧一处理 + 预处理回退 ========== + public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) { + isTriggerSuccess = true; + Movement.getInstance().setVirtualStickEnableReason(2); + + // 原有过滤 + if (isStartAruco || startFastStick) { + LogUtil.log(TAG_LOG, "过滤:" + isStartAruco + startFastStick); + if (!isStartAruco && startFastStick) { + checkThrowingErrorsTimes++; + } + return; + } + + // ========== 【关键】2帧一处理:0处理-1跳过-2处理-3跳过 ========== + int currentFrame = frameCounter++; + boolean shouldProcess = (currentFrame % 2 == 0); // 偶数帧处理,奇数帧跳过 + + if (!shouldProcess) { + // 【关键】跳过的帧:啥也不干,直接return,让飞机自己飘 + LogUtil.log(TAG_LOG, "【跳过帧】" + currentFrame + " 让飞机自稳"); + return; + } + + LogUtil.log(TAG_LOG, "【处理帧】" + currentFrame + " 执行修正"); + + isStartAruco = true; + + if (lastFuture != null && !lastFuture.isDone()) { + LogUtil.log(TAG_LOG, "break---"); + lastFuture.cancel(true); + } + + // ========== 【核心逻辑】先预处理检测,失败回退原图 ========== + lastFuture = executor.schedule(new Runnable() { + @Override + public void run() { + try { + Mat yuvMat = new Mat(height + height / 2, width, CvType.CV_8UC1); + yuvMat.put(0, 0, data); + + // ========== 【关键修改】直接用Y通道,跳过RGB转换 ========== + // YUV420的Y通道就是灰度图,质量比YUV→BGR→GRAY高很多 + // BGR转换需要插值UV通道,在低分辨率FPV上损失细节 + Mat grayImgMat = yuvMat.submat(0, height, 0, width); + grayImgMat = grayImgMat.clone(); // 克隆,避免和yuvMat共享数据 + + MatOfInt ids = new MatOfInt(); + List corners = new ArrayList<>(); + Mat corner6 = new Mat(); + + DetectorParameters parameters = new DetectorParameters(); + ArucoDetector detector = new ArucoDetector(dictionary, parameters); + + // ========== 【关键修改】计算亮度,决定预处理策略 ========== + Scalar meanValue = Core.mean(grayImgMat); + double currentBrightness = meanValue.val[0]; + + // ========== 【关键修改】第1次:预处理图检测 ========== + boolean shouldUsePreprocess = !forceSkipPreprocess; + Mat processedMat; + boolean preprocessed = false; + + if (shouldUsePreprocess) { + processedMat = fixedPreprocess(grayImgMat, currentBrightness); + preprocessed = true; + } else { + processedMat = grayImgMat; + LogUtil.log(TAG_LOG, "【强光回退】连续解码失败,跳过预处理,使用原图(" + (int)currentBrightness + ")"); + } + + detector.detectMarkers(processedMat, corners, ids); + LogUtil.log(TAG_LOG, (preprocessed ? "预处理图检测" : "原图检测(回退)") + + ": " + (ids.empty() ? "未检出" : "成功,数量=" + corners.size())); + + // ========== 【关键修改】第2次:如果预处理失败,回退原图检测 ========== + if (ids.empty() && preprocessed) { + LogUtil.log(TAG_LOG, "预处理失败,回退原图检测"); + corners.clear(); + detector.detectMarkers(grayImgMat, corners, ids); + LogUtil.log(TAG_LOG, "原图检测: " + (ids.empty() ? "仍未检出" : "成功,数量=" + corners.size())); + } + + // 释放预处理图(必须!避免内存泄漏) + if (preprocessed && processedMat != null) { + processedMat.release(); + } + + // 【新增】追踪连续解码失败帧 + if (ids.empty()) { + undecodableFrameCount++; + if (undecodableFrameCount >= UNDECODABLE_THRESHOLD && !forceSkipPreprocess) { + forceSkipPreprocess = true; + skipPreprocessFrameCount = 0; + LogUtil.log(TAG_LOG, "【强光检测】连续" + UNDECODABLE_THRESHOLD + "帧解码失败,强制跳过预处理" + + "(当前亮度=" + (int)currentBrightness + ")"); + } + } else { + undecodableFrameCount = 0; + } + + // 【新增】强制跳过预处理模式:持续一定帧数后恢复尝试 + if (forceSkipPreprocess) { + skipPreprocessFrameCount++; + if (skipPreprocessFrameCount >= SKIP_PREPROCESS_DURATION) { + forceSkipPreprocess = false; + undecodableFrameCount = 0; + LogUtil.log(TAG_LOG, "【强光检测】恢复预处理尝试,观察效果"); + } + } + + // 【新增】高亮度曝光补偿提示 + if (currentBrightness > 130) { + LogUtil.log(TAG_LOG, "【曝光提示】亮度过高(" + (int)currentBrightness + "),建议降低EV或锁定AE"); + } + + // 保留6、7、8、9、10号码 + ids = keepOnlyMarkers(ids, corners, new int[]{6, 7, 8, 9, 10}); + + int ultHeight = Movement.getInstance().getUltrasonicHeight(); + + // ========== 【新增】高度稳定性监测 ========== + if (ultHeight > 0) { + if (!isHeightStableMonitoring) { + // 开始监测 + isHeightStableMonitoring = true; + lastHeightCheckTime = System.currentTimeMillis(); + lastUltrasonicHeight = ultHeight; + LogUtil.log(TAG_LOG, "开始监测高度稳定性,基准高度: " + ultHeight + " 分米"); + } else { + // 检查高度变化 + long currentTime = System.currentTimeMillis(); + double heightChange = Math.abs(ultHeight - lastUltrasonicHeight); + + if (heightChange > HEIGHT_CHANGE_THRESHOLD) { + // 高度有明显变化,重置计时器 + lastHeightCheckTime = currentTime; + lastUltrasonicHeight = ultHeight; + LogUtil.log(TAG_LOG, "高度变化: " + heightChange + " 分米,重置计时器"); + } else if (currentTime - lastHeightCheckTime > HEIGHT_STABLE_THRESHOLD) { + // 45秒内高度没有变动,执行复降 + LogUtil.log(TAG_LOG, "45秒内高度未变动,执行复降"); + + if (ultHeight <= 20) { + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f); + if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) { + LogUtil.log(TAG_LOG, "超过复降限制,去备降点"); + AlternateLandingManager.getInstance().startTaskProcess(null); + Movement.getInstance().setAlternate(true); + return; + } + if (dropTimesTag) { + dropTimesTag = false; + dropTimes++; + LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次"); + } + } else { + LogUtil.log(TAG_LOG, "执行位移"); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f); + } + + isHeightStableMonitoring = false; + isStartAruco = false; + return; + } + } + } + + // ========== 分类检测到的码 ========== + List markers7to10 = new ArrayList<>(); + corner6 = null; + + if (!ids.empty()) { + trycount = 0; + arucoNotFoundTag = false; + int[] idArray = ids.toArray(); + int ultrasonicHeight = Movement.getInstance().getUltrasonicHeight(); + + for (int i = 0; i < idArray.length; i++) { + int id = idArray[i]; + if (id == 6) { + corner6 = corners.get(i); + } else if (id >= 7 && id <= 10) { + Mat corner = corners.get(i); + Point[] pts = new Point[4]; + for (int j = 0; j < 4; j++) { + double[] p = corner.get(0, j); + pts[j] = new Point(p[0], p[1]); + } + double pixelW = calculateDistance(pts[0], pts[1]); + markers7to10.add(new ArucoMarker(id, corner, 0.24f)); + LogUtil.log(TAG_LOG, "检测到降落码: ID=" + id + " pixel=" + (int) pixelW); + } + } + + // ========== 阶段判断:7-10优先级更高,有就用7-10,否则用6号 ========== + if (!markers7to10.isEmpty()) { + // ===== 7-10降落阶段(不管高度多少,有码就用) ===== + processMarkers7to10(markers7to10, width, height, ultrasonicHeight); + dropTimesTag = true; + + } else if (corner6 != null) { + // ===== 6号下降阶段(7-10没出现时才用6号) ===== + moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), width, height); + dropTimesTag = true; + + } else { + LogUtil.log(TAG_LOG, "阶段1未找到6号码,7-10也无"); + } + + } else { + // 原有丢失处理 + LogUtil.log(TAG_LOG, "找不到了二维码"); + +// // 【新增】识别失败截图保存 +// if (saveFailScreenshot) { +// saveFailScreenshot(grayImgMat, width, height); +// +// } + + if (!arucoNotFoundTag) { + startTime = System.currentTimeMillis(); + arucoNotFoundTag = true; + } + endTime = System.currentTimeMillis(); + long lostDuration = endTime - startTime; + + if (lostDuration > 1000 && lostDuration <= 12000) { + if (Movement.getInstance().getUltrasonicHeight() <= 20) { + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f); + if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) { + LogUtil.log(TAG_LOG, "超过复降限制,去备降点"); + AlternateLandingManager.getInstance().startTaskProcess(null); + Movement.getInstance().setAlternate(true); + return; + } + if (dropTimesTag) { + dropTimesTag = false; + dropTimes++; + LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次"); + } + } else { + LogUtil.log(TAG_LOG, "执行位移"); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f); + } + } else if (lostDuration > 8000) { + LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点"); + AlternateLandingManager.getInstance().startTaskProcess(null); + Movement.getInstance().setAlternate(true); + } + } + + // 释放资源 + grayImgMat.release(); + yuvMat.release(); + ids.release(); + + if (!corners.isEmpty()) { + for (Mat c : corners) { + if (c != null) c.release(); + } + } + isStartAruco = false; + + } catch (Exception e) { + LogUtil.log(TAG_LOG, "Exception" + e); + isStartAruco = false; + } + } + }, 0, TimeUnit.MILLISECONDS); + } + // ========== 以下全部原有方法,一点不改 ========== + // ========== 保留指定的多个号码 ========== + private static MatOfInt keepOnlyMarkers(MatOfInt ids, List corners, int[] keepIds) { + if (ids.empty()) return ids; + int[] idArr = ids.toArray(); + java.util.Set keepSet = new java.util.HashSet<>(); + for (int id : keepIds) keepSet.add(id); + + List keepIdx = new ArrayList<>(); + for (int i = 0; i < idArr.length; i++) { + if (keepSet.contains(idArr[i])) keepIdx.add(i); + } + + List tmpCorners = new ArrayList<>(keepIdx.size()); + for (int i : keepIdx) tmpCorners.add(corners.get(i)); + + corners.clear(); + ids.release(); + int[] newIds = new int[keepIdx.size()]; + for (int j = 0; j < keepIdx.size(); j++) newIds[j] = idArr[keepIdx.get(j)]; + corners.addAll(tmpCorners); + return new MatOfInt(newIds); + } + + /** + * 阶段2:<20dm 降落阶段 + * 【锁定机制】第一次扫到7/8/9/10就锁定,后面只认锁定的这个 + * 降落条件:只有锁定的码连续2帧满足条件才速降 + */ + private void processMarkers7to10(List markers, int imgWidth, int imgHeight, int ultHeight) { + java.util.HashMap markerMap = new java.util.HashMap<>(); + for (ArucoMarker m : markers) { + markerMap.put(m.getId(), m); + } + + // ===== 锁定逻辑:未锁定时,尝试锁定第一个检测到的码 ===== + if (lockedMarkerId == 0) { + if (markers.isEmpty()) { + LogUtil.log(TAG_LOG, "【阶段2】未锁定,7-10无码,不动"); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f); + return; + } + // 取第一个检测到的码锁定(7>8>9>10 优先级) + for (int id = 7; id <= 10; id++) { + if (markerMap.containsKey(id)) { + lockedMarkerId = id; + lockedMarkerLostCounter = 0; // 重置丢失计数器 + LogUtil.log(TAG_LOG, "【锁定】锁定ID=" + lockedMarkerId + " 后续只认这个"); + break; + } + } + // 锁定后悬停,不急着下降 + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f); + return; + } + + // ===== 已锁定:只处理锁定的码,其他全部无视 ===== + ArucoMarker lockedMarker = markerMap.get(lockedMarkerId); + + if (lockedMarker == null) { + // 锁定的码丢失,悬停 + 计时 + lockedMarkerLostCounter++; + LogUtil.log(TAG_LOG, "【阶段2】锁定的ID=" + lockedMarkerId + " 丢失,悬停等待 丢失帧=" + lockedMarkerLostCounter); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f); + + // 超过阈值 → 解除锁定,允许重新锁定其他码 + if (lockedMarkerLostCounter >= LOCKED_MARKER_LOST_THRESHOLD) { + LogUtil.log(TAG_LOG, "【阶段2】锁定的ID=" + lockedMarkerId + " 丢失超过" + LOCKED_MARKER_LOST_THRESHOLD + "帧,解除锁定,准备重新锁定"); + lockedMarkerId = 0; + lockedMarkerLostCounter = 0; + landingCounters[lockedMarkerId] = 0; + counterRound = 0; + } + return; + } + + // 锁定的码回来了 → 重置丢失计数器 + if (lockedMarkerLostCounter > 0) { + LogUtil.log(TAG_LOG, "【阶段2】锁定的ID=" + lockedMarkerId + " 回来了,重置丢失计数"); + lockedMarkerLostCounter = 0; + } + + // 计算锁定的码的中心 + 应用该码的专属偏移 + double[] center = getMarkerCenter(lockedMarker.getConner()); + double markerOffsetX = getMarkerOffsetX(lockedMarkerId); + double markerOffsetY = getMarkerOffsetY(lockedMarkerId); + double errX = center[0] - imgWidth / 2.0 + markerOffsetX; + double errY = center[1] - imgHeight / 2.0 + markerOffsetY; + double absX = Math.abs(errX); + double absY = Math.abs(errY); + + LogUtil.log(TAG_LOG, "【阶段2锁定ID=" + lockedMarkerId + "】偏移=" + markerOffsetX + "/" + markerOffsetY + + " errX=" + (int) errX + " errY=" + (int) errY + " ult=" + ultHeight); + + // ===== 降落判定:只看锁定的码,用该码自己的偏移阈值 + 像素>=200 ===== + if (ultHeight <= 4 && !startFastStick) { + int thresholdX = getMarkerLandErrXThreshold(lockedMarkerId); + int thresholdY = getMarkerLandErrYThreshold(lockedMarkerId); + + // 计算锁定的码的像素宽度 + Point[] markerPts = new Point[4]; + Mat lockedCorner = lockedMarker.getConner(); + for (int i = 0; i < 4; i++) { + double[] p = lockedCorner.get(0, i); + markerPts[i] = new Point(p[0], p[1]); + } + double pixelWidth = calculateDistance(markerPts[0], markerPts[1]); + + if (absX < thresholdX && absY < thresholdY && pixelWidth >= 200) { + landingCounters[lockedMarkerId]++; + } else { + landingCounters[lockedMarkerId] = 0; + } + LogUtil.log(TAG_LOG, "【降落计数器】锁定的ID=" + lockedMarkerId + " 连续=" + landingCounters[lockedMarkerId] + + " errX=" + (int) errX + " errY=" + (int) errY + " 阈值=" + thresholdX + "/" + thresholdY + + " pixel=" + (int) pixelWidth); + + if (landingCounters[lockedMarkerId] >= TRIGGER_FRAME_THRESHOLD) { + LogUtil.log(TAG_LOG, "【降落触发】锁定的ID=" + lockedMarkerId + " 连续" + TRIGGER_FRAME_THRESHOLD + "帧满足"); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f); + startFastStick = true; + landingCounters[lockedMarkerId] = 0; + handler.postDelayed(() -> handler.post(runnable), 300); + return; + } + + counterRound++; + if (counterRound >= 2) { + counterRound = 0; + landingCounters[lockedMarkerId] = 0; + LogUtil.log(TAG_LOG, "【计数器重置】2帧未触发,重新从0开始"); + } + } + + // ===== 位移修正:只用锁定的码 ===== + if (ultHeight <= 4) { + // 低于4dm:温柔对准,不下降 + float vx = 0f, vy = 0f; + + if (absX > 120) vx = errX > 0 ? 0.135f : -0.135f; + else if (absX > 80) vx = errX > 0 ? 0.09f : -0.09f; + else if (absX > 60) vx = errX > 0 ? 0.07f : -0.07f; + else if (absX > 30) vx = errX > 0 ? 0.05f : -0.05f; + + if (absY > 120) vy = errY > 0 ? 0.135f : -0.135f; + else if (absY > 80) vy = errY > 0 ? 0.09f : -0.09f; + else if (absY > 60) vy = errY > 0 ? 0.07f : -0.07f; + else if (absY > 30) vy = errY > 0 ? 0.05f : -0.05f; + + DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, 0f, 0f); + LogUtil.log(TAG_LOG, "【执行移动】锁定ID=" + lockedMarkerId + " vx=" + vx + " vy=" + vy); + return; + } + + // 4-20dm:PID修正 + 慢降 + double z = ultHeight / 10.0; + + pidControlX.setInputFilterAll((float) (errX / 700.0)); + pidControlY.setInputFilterAll((float) (-errY / 700.0)); + + float outX = (float) Math.max(-0.2, Math.min(0.2, pidControlX.get_pid())); + float outY = (float) Math.max(-0.2, Math.min(0.2, pidControlY.get_pid())); + float outZ = (absX < 150 && absY < 150) ? -0.2f : 0f; + + DroneHelper.getInstance().moveVxVyYawrateHeight(outX, outY, 0f, outZ); + LogUtil.log(TAG_LOG, "【执行移动】锁定ID=" + lockedMarkerId + " vx=" + outX + " vy=" + outY + " vz=" + outZ); + } + + /** + * 获取二维码中心坐标 + */ + private double[] getMarkerCenter(Mat corner) { + double sumX = 0, sumY = 0; + for (int i = 0; i < 4; i++) { + double[] p = corner.get(0, i); + sumX += p[0]; + sumY += p[1]; + } + return new double[]{sumX / 4.0, sumY / 4.0}; + } + + /** + * 识别失败时保存原图截图到外部存储 + */ + private void saveFailScreenshot(Mat grayMat, int width, int height) { + if (failScreenshotIndex >= MAX_SCREENSHOTS) { + LogUtil.log(TAG_LOG, "【截图保存】已达到最大数量 " + MAX_SCREENSHOTS + ",停止保存"); + return; + } + + try { + String dirPath = getScreenshotDirPath(); + if (dirPath == null) { + LogUtil.log(TAG_LOG, "【截图保存】无法获取存储目录"); + return; + } + + String fileName = String.format("aruco_fail_%03d_%d.png", + failScreenshotIndex, System.currentTimeMillis()); + String fullPath = dirPath + "/" + fileName; + + boolean success = Imgcodecs.imwrite(fullPath, grayMat); + if (success) { + failScreenshotIndex++; + LogUtil.log(TAG_LOG, "【截图保存】已保存: " + fileName + + " (亮度=" + (int) Core.mean(grayMat).val[0] + ")"); + } else { + LogUtil.log(TAG_LOG, "【截图保存】保存失败: " + fullPath); + } + } catch (Exception e) { + LogUtil.log(TAG_LOG, "【截图保存】异常: " + e.getMessage()); + } + } + + /** + * 获取截图保存目录 + */ + private String getScreenshotDirPath() { + File storageDir; + if (checkSDCard()) { + storageDir = new File(Environment.getExternalStorageDirectory(), "apronPic/aruco_fail_screenshots"); + } else { + storageDir = new File(Environment.getExternalStorageDirectory().getParentFile(), "apronPic/aruco_fail_screenshots"); + } + + if (!storageDir.exists()) { + boolean created = storageDir.mkdirs(); + if (!created) { + LogUtil.log(TAG_LOG, "【截图保存】创建目录失败: " + storageDir.getAbsolutePath()); + return null; + } + } + return storageDir.getAbsolutePath(); + } + + private boolean checkSDCard() { + return TextUtils.equals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); + } + + /** + * startArucoType == 3 专用:扫到6号 + 7-10号中≥3个才触发刹停 + */ + public void detectForceTriggerTags(int height, int width, byte[] data, Dictionary dictionary) { + Mat yuvMat = new Mat(height + height / 2, width, CvType.CV_8UC1); + yuvMat.put(0, 0, data); + Mat grayImgMat = yuvMat.submat(0, height, 0, width); + grayImgMat = grayImgMat.clone(); + + MatOfInt ids = new MatOfInt(); + List corners = new ArrayList<>(); + DetectorParameters parameters = new DetectorParameters(); + ArucoDetector detector = new ArucoDetector(dictionary, parameters); + + detector.detectMarkers(grayImgMat, corners, ids); + + if (!ids.empty()) { + int ultHeight = Movement.getInstance().getUltrasonicHeight(); + int[] idArray = ids.toArray(); + boolean has6 = false; + int count7to10 = 0; + + for (int id : idArray) { + if (id == 6) { + has6 = true; + } else if (id >= 7 && id <= 10) { + count7to10++; + } + } + + if (has6 && count7to10 >= 3 && ultHeight <= 60) { + LogUtil.log(TAG_LOG, "【强制刹停】满足条件:6号 + 7-10号中" + count7to10 + "个 高度=" + ultHeight + "dm"); + FlightManager.forceTriggerDetection = true; + } else { + LogUtil.log(TAG_LOG, "【强制刹停】不满足条件:6号=" + has6 + ", 7-10号数量=" + count7to10 + ", 高度=" + ultHeight + "dm"); + } + } + + yuvMat.release(); + grayImgMat.release(); + ids.release(); + } + + private Mat fixedPreprocess(Mat src, double meanBrightness) { + Mat result = new Mat(); + try { + if (meanBrightness < 80) { // 夜景模式(FPV晚上通常<80) + LogUtil.log(TAG_LOG, "【夜景预处理】亮度=" + (int)meanBrightness); + + // Step 1: 中值滤波(去椒盐噪点,比双边滤波更适合夜景) + Mat denoised = new Mat(); + Imgproc.medianBlur(src, denoised, 5); // 5x5,去噪且保边 + + // Step 2: 直方图均衡化(全局对比度拉伸,把黑灰白拉开) + Mat equalized = new Mat(); + Imgproc.equalizeHist(denoised, equalized); + + // Step 3: 轻度CLAHE(局部微调,避免过曝) + Mat clahe = new Mat(); + Imgproc.createCLAHE(2.0, new Size(8, 8)).apply(equalized, clahe); // 2.0比白天4.0保守 + + // Step 4: 夜景下降低锐化强度(避免放大残余噪点) + Mat blurred = new Mat(); + Imgproc.GaussianBlur(clahe, blurred, new Size(0, 0), 2.0); + Mat detail = new Mat(); + Core.subtract(clahe, blurred, detail); + + double amount = 0.6; // 【关键】夜景降到0.6,白天1.2会放大噪点 + Core.multiply(detail, new Scalar(amount), detail); + Core.add(clahe, detail, result); + + // 限制范围 + Core.min(result, new Scalar(255), result); + Core.max(result, new Scalar(0), result); + + // 释放 + denoised.release(); + equalized.release(); + blurred.release(); + detail.release(); + clahe.release(); + + LogUtil.log(TAG_LOG, "夜景处理完成:中值滤波+均衡化+弱锐化(amount=" + amount + ")"); + return result; + } + + // ========== 【关键修改】白天模式:分亮度区间处理 ========== + LogUtil.log(TAG_LOG, "【白天预处理】亮度=" + (int)meanBrightness); + + // Step 1: 双边滤波(保边缘降噪)— 所有白天场景通用 + Mat filtered = new Mat(); + Imgproc.bilateralFilter(src, filtered, 9, 75, 75); + + Mat clahe; + double amount; + + if (meanBrightness > 130) { + // 【强光模式】亮度>130:大幅降低CLAHE,避免眩光被增强成伪marker + LogUtil.log(TAG_LOG, "【强光处理】亮度过高,降低CLAHE增益,跳过锐化"); + clahe = new Mat(); + // clipLimit降到1.5(默认4.0),tileSize加大到16x16(更平滑) + Imgproc.createCLAHE(1.5, new Size(16, 16)).apply(filtered, clahe); + amount = 0; // 强光下跳过锐化,避免放大眩光边缘 + } else if (meanBrightness > 100) { + // 【中亮模式】亮度100-130:适度CLAHE,轻度锐化 + LogUtil.log(TAG_LOG, "【中亮处理】适度CLAHE+轻度锐化"); + clahe = new Mat(); + Imgproc.createCLAHE(2.5, new Size(8, 8)).apply(filtered, clahe); + amount = 0.8; + } else { + // 【正常白天】亮度80-100:原逻辑 + clahe = new Mat(); + Imgproc.createCLAHE(4.0, new Size(8, 8)).apply(filtered, clahe); + amount = 1.2; + } + + filtered.release(); + + // Step 3: Unsharp Mask(反锐化掩膜)— 强光模式跳过 + if (amount > 0) { + Mat blurred = new Mat(); + Imgproc.GaussianBlur(clahe, blurred, new Size(0, 0), 2.0); + + Mat detail = new Mat(); + Core.subtract(clahe, blurred, detail); + + Core.multiply(detail, new Scalar(amount), detail); + Core.add(clahe, detail, result); + + blurred.release(); + detail.release(); + } else { + clahe.copyTo(result); + } + + Core.min(result, new Scalar(255), result); + Core.max(result, new Scalar(0), result); + + clahe.release(); + + LogUtil.log(TAG_LOG, "白天处理完成:双边滤波+CLAHE+UnsharpMask(amount=" + amount + ")"); + return result; + + } catch (Exception e) { + LogUtil.log(TAG_LOG, "预处理失败: " + e.getMessage()); + src.copyTo(result); + return result; + } + } + + private double calculateDistance(Point p1, Point p2) { + double dx = p2.x - p1.x; + double dy = p2.y - p1.y; + return Math.sqrt(dx * dx + dy * dy); + } + + public void setDetectedBigMarkers() { + startFastStick = false; + isStartAruco = false; + consecutiveTriggerCount = 0; + frameCounter = 0; + isHeightStableMonitoring = false; + lastHeightCheckTime = 0; + lastUltrasonicHeight = 0; + undecodableFrameCount = 0; + forceSkipPreprocess = false; + skipPreprocessFrameCount = 0; + saveFailScreenshot = false; + failScreenshotIndex = 0; + // 重置7-8-9-10锁定和计数器 + lockedMarkerId = 0; + lockedMarkerLostCounter = 0; + counterRound = 0; + for (int i = 0; i < landingCounters.length; i++) { + landingCounters[i] = 0; + } + } + + + private double calculateYawErrorFromCorners(Point[] pts) { + double dxTop = pts[1].x - pts[0].x; + double dyTop = pts[1].y - pts[0].y; + double angleTop = Math.toDegrees(Math.atan2(dyTop, dxTop)); + double dxBottom = pts[2].x - pts[3].x; + double dyBottom = pts[2].y - pts[3].y; + double angleBottom = Math.toDegrees(Math.atan2(dyBottom, dxBottom)); + double yawError = (angleTop + angleBottom) / 2.0; + while (yawError > 180) yawError -= 360; + while (yawError < -180) yawError += 360; + return yawError; + } + + private void moveOnArucoDetected(ArucoMarker marker, int imageWidth, int imageHeight) { + Mat corner6 = marker.getConner(); + Point[] pts = new Point[4]; + for (int i = 0; i < 4; i++) { + double[] p = corner6.get(0, i); + pts[i] = new Point(p[0], p[1]); + } + + double cx = (pts[0].x + pts[1].x + pts[2].x + pts[3].x) / 4.0; + double cy = (pts[0].y + pts[1].y + pts[2].y + pts[3].y) / 4.0; + Point screenCenter = new Point(imageWidth / 2.0, imageHeight / 2.0); + + double errX = (cx - screenCenter.x); + double errY = (cy - screenCenter.y); + int currentHeight = Movement.getInstance().getUltrasonicHeight(); + + double scaleFactor; + if (currentHeight >= 50) { + scaleFactor = 300.0; // 50m: 像素3, 大力修正 + } else if (currentHeight >= 30) { + scaleFactor = 500.0; // 30m: 像素5 + } else if (currentHeight >= 20) { + scaleFactor = 600.0; // 20m: 像素8 + } else if (currentHeight >= 10) { + scaleFactor = 700.0; // 10m: 像素16 + } else { + scaleFactor = 900.0; // 1m: 像素159, 温柔修正 + } + + pidControlX.setInputFilterAll((float) (errX / scaleFactor)); + pidControlY.setInputFilterAll((float) (-errY / scaleFactor)); + + float rawVx = pidControlX.get_pid(); + float rawVy = pidControlY.get_pid(); + float yawRate = 0f; + + float vx = (float) Math.max(-0.2, Math.min(0.2, rawVx)); + float vy = (float) Math.max(-0.2, Math.min(0.2, rawVy)); + + double pixelWidth = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) + + Math.pow(pts[1].y - pts[0].y, 2)); + + if (currentHeight >= 10 && currentHeight <= 55) { + double yawError = calculateYawErrorFromCorners(pts); + double absError = Math.abs(yawError); + + if (absError > 10.0) { + float targetRate; + if (absError > 100.0) targetRate = 50.0f; + else if (absError > 50.0) targetRate = 30.0f; + else if (absError > 30.0) targetRate = 20.0f; + else targetRate = 10.0f; + yawRate = yawError > 0 ? targetRate : -targetRate; + + String speedLabel = absError > 100 ? "高速50" : + absError > 50 ? "中速30" : + absError > 30 ? "低速20" : "微调10"; + LogUtil.log(TAG_LOG, "机头矫正:误差=" + (int)yawError + "° 转速=" + yawRate + "°/s [" + speedLabel + "]"); + } else { + yawRate = 0.0f; + LogUtil.log(TAG_LOG, "机头已对准:偏航误差=" + (int)yawError + "°"); + } + } else { + yawRate = 0.0f; + } + + float vz; + if (currentHeight <= 4) { + vz = 0.0f; + if (Math.abs(errX) > 120) { + vx = rawVx > 0 ? 0.135f : -0.135f; + } else if (Math.abs(errX) > 80) { + vx = rawVx > 0 ? 0.09f : -0.09f; + } else if (Math.abs(errX) > 60) { + vx = rawVx > 0 ? 0.07f : -0.07f; + } else if (Math.abs(errX) > 30) { + vx = rawVx > 0 ? 0.05f : -0.05f; + } else { + vx = 0f; + } + + + if (Math.abs(errY) > 120) { + vy = rawVy > 0 ? 0.135f : -0.135f; + } else if (Math.abs(errY) > 80) { + vy = rawVy > 0 ? 0.09f : -0.09f; + } else if (Math.abs(errY) > 60) { + vy = rawVy > 0 ? 0.07f : -0.07f; + } else if (Math.abs(errY) > 30) { + vy = rawVy > 0 ? 0.05f : -0.05f; + } else { + vy = 0f; // 【修正】 + } + + } else if (currentHeight <= 8) { + vz = SLOW_SUPER_SPEED; + } else { + vz = SLOW_LAND_SPEED; + } + + DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, yawRate, vz); + + LogUtil.log(TAG_LOG, "vx" + vx + "vy" + vy + " errX=" + (int) errX + " errY=" + (int) errY + + " pixelW=" + (int) pixelWidth + " vz=" + vz + " ult=" + currentHeight + " yaw=" + yawRate); + } + + private int handlerCallbackCount = 0; + private Handler handler = new Handler(Looper.getMainLooper()); + private Runnable runnable = new Runnable() { + @Override + public void run() { + performOperation(); + if (handlerCallbackCount < 18) { + handler.postDelayed(this, 50); + } else { + performNextStep(); + } + } + }; + + private void performOperation() { + LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount); + DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -6); + handlerCallbackCount++; + } + + private void performNextStep() { + canLanding = true; + handlerCallbackCount = 0; + dropTimes = 0; + handler.removeCallbacks(runnable); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aros/apron/tools/DroneHelper.java b/app/src/main/java/com/aros/apron/tools/DroneHelper.java index 1c10cf53..3b1d27d0 100644 --- a/app/src/main/java/com/aros/apron/tools/DroneHelper.java +++ b/app/src/main/java/com/aros/apron/tools/DroneHelper.java @@ -239,6 +239,28 @@ public class DroneHelper { } ); } else { + GimbalAngleRotation rotation = new GimbalAngleRotation(); + rotation.setMode(GimbalAngleRotationMode.ABSOLUTE_ANGLE); + rotation.setYaw(0.0); + rotation.setRoll(0.0); + rotation.setPitch(-90.0); + KeyManager.getInstance().performAction(KeyTools.createKey(GimbalKey.KeyRotateByAngle, ComponentIndexType.PORT_1), rotation, new CommonCallbacks.CompletionCallbackWithParam() { + @Override + public void onSuccess(EmptyMsg emptyMsg) { + LogUtil.log(TAG, "云台朝下"); + isGimbalPitchDegree = true; + setGimbalPitchDegreeFailTimes = 0; + } + + @Override + public void onFailure(@NonNull IDJIError error) { + LogUtil.log(TAG, "fail:" + error.toString()); + retryGimbalPitchDegree(); + + } + } + ); + LogUtil.log(TAG, "云台未连接"); retryGimbalPitchDegree(); } diff --git a/app/src/main/java/com/aros/apron/tools/FlyToPointProgressScheduler.java b/app/src/main/java/com/aros/apron/tools/FlyToPointProgressScheduler.java new file mode 100644 index 00000000..117b7cdb --- /dev/null +++ b/app/src/main/java/com/aros/apron/tools/FlyToPointProgressScheduler.java @@ -0,0 +1,259 @@ +package com.aros.apron.tools; + +import android.os.Handler; +import android.os.Looper; + +import com.aros.apron.constant.AMSConfig; +import com.aros.apron.entity.FlyToPointProgress; +import com.aros.apron.entity.Movement; +import com.google.gson.Gson; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import dji.sdk.keyvalue.key.FlightControllerKey; +import dji.sdk.keyvalue.key.KeyTools; +import dji.sdk.keyvalue.value.common.LocationCoordinate3D; +import dji.v5.manager.KeyManager; + +/** + * 飞向目标点进度定时上报器 + * 协议: fly_to_point_progress + */ +public class FlyToPointProgressScheduler { + + private static final String TAG = "FlyToPointProgressScheduler"; + private static volatile FlyToPointProgressScheduler instance; + + private Handler reportHandler; + private Runnable reportRunnable; + + private volatile boolean isRunning = false; + + // 轨迹点缓存 + private List pathPoints; + + // 采样间隔 1秒 + private static final long REPORT_INTERVAL = 1000; + + // 当前状态 + private String currentStatus = "wayline_progress"; + private int currentResult = 0; + + private FlyToPointProgressScheduler() { + reportHandler = new Handler(Looper.getMainLooper()); + pathPoints = new ArrayList<>(); + } + + public static FlyToPointProgressScheduler getInstance() { + if (instance == null) { + synchronized (FlyToPointProgressScheduler.class) { + if (instance == null) { + instance = new FlyToPointProgressScheduler(); + } + } + } + return instance; + } + + /** + * 开始定时上报 + */ + public void startReporting() { + if (isRunning) { + LogUtil.log(TAG, "定时上报已在运行中"); + return; + } + + isRunning = true; + currentStatus = "wayline_progress"; + currentResult = 0; + pathPoints.clear(); + + LogUtil.log(TAG, "启动飞向目标点进度定时上报"); + + reportRunnable = new Runnable() { + @Override + public void run() { + if (!isRunning) return; + + try { + // 1. 采样飞机位置 + new Thread(() -> sampleCurrentLocation()).start(); + + // 2. 发送上报 + sendProgressReport(); + + } catch (Exception e) { + e.printStackTrace(); + LogUtil.log(TAG, "定时任务异常:" + e.getMessage()); + } + + if (isRunning) { + reportHandler.postDelayed(this, REPORT_INTERVAL); + } + } + }; + + // 立即执行第一次 + reportHandler.post(reportRunnable); + } + + /** + * 停止定时上报 + */ + public void stopReporting() { + if (!isRunning) return; + + isRunning = false; + + // 最后发送一次 + sendProgressReport(); + + reportHandler.removeCallbacks(reportRunnable); + pathPoints.clear(); + + LogUtil.log(TAG, "停止飞向目标点进度定时上报"); + } + + /** + * 标记任务成功 + */ + public void markSuccess() { + currentStatus = "wayline_ok"; + currentResult = 0; + stopReporting(); + } + + /** + * 标记任务失败 + */ + public void markFailed() { + currentStatus = "wayline_failed"; + currentResult = -1; + stopReporting(); + } + + /** + * 标记任务取消 + */ + public void markCancel() { + currentStatus = "wayline_cancel"; + currentResult = 0; + stopReporting(); + } + + /** + * 采样当前飞机位置 + */ + private void sampleCurrentLocation() { + try { + LocationCoordinate3D location = KeyManager.getInstance().getValue( + KeyTools.createKey(FlightControllerKey.KeyAircraftLocation3D)); + + if (location == null) return; + + double lat = location.getLatitude(); + double lng = location.getLongitude(); + double alt = location.getAltitude(); + + // 过滤无效坐标 + if (Math.abs(lat) < 0.0001 && Math.abs(lng) < 0.0001) return; + + FlyToPointProgress.PlannedPathPoint point = new FlyToPointProgress.PlannedPathPoint(); + point.setLatitude(lat); + point.setLongitude(lng); + point.setHeight((float) alt); + + // 计算剩余距离和时间 + double remaining_distance = Gpsdistance.calculate3DDistance( + lat, lng, alt, + Movement.getInstance().getFlyto_target_latitude(), + Movement.getInstance().getFlyto_target_longitude(), + Movement.getInstance().getFlyto_target_height()); + + int speed = Movement.getInstance().getFlyto_max_speed(); + double remaining_time = speed > 0 ? remaining_distance / speed : 0; + + Movement.getInstance().setFlyto_remaining_distance((float) remaining_distance); + Movement.getInstance().setFlyto_remaining_time((float) remaining_time); + + // 刷新规划路径点(3个:飞机当前位置 → 中间点 → 目标点) + pathPoints.clear(); + pathPoints.add(point); + + // 中间点 = 当前位置与目标点连线的中点 + FlyToPointProgress.PlannedPathPoint midPoint = new FlyToPointProgress.PlannedPathPoint(); + midPoint.setLatitude((lat + Movement.getInstance().getFlyto_target_latitude()) / 2.0); + midPoint.setLongitude((lng + Movement.getInstance().getFlyto_target_longitude()) / 2.0); + midPoint.setHeight((float) ((alt + Movement.getInstance().getFlyto_target_height()) / 2.0)); + pathPoints.add(midPoint); + + // 目标点 + FlyToPointProgress.PlannedPathPoint targetPoint = new FlyToPointProgress.PlannedPathPoint(); + targetPoint.setLatitude(Movement.getInstance().getFlyto_target_latitude()); + targetPoint.setLongitude(Movement.getInstance().getFlyto_target_longitude()); + + + targetPoint.setHeight((float) Movement.getInstance().getFlyto_target_height()); + + + pathPoints.add(targetPoint); + + } catch (Exception e) { + LogUtil.log(TAG, "采样位置失败:" + e.getMessage()); + } + } + + /** + * 发送进度上报 + */ + public void sendProgressReport() { + + try { + if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) { + LogUtil.log(TAG, "MQTT未连接,跳过上报"); + return; + } + + FlyToPointProgress.Data data = new FlyToPointProgress.Data(); + data.setFly_to_id(Movement.getInstance().getFly_to_id()); + data.setStatus(currentStatus); + data.setResult(currentResult); + data.setWay_point_index(1); + data.setRemaining_distance(Movement.getInstance().getFlyto_remaining_distance()); + data.setRemaining_time(Movement.getInstance().getFlyto_remaining_time()); + data.setPlanned_path_points(new ArrayList<>(pathPoints)); + + FlyToPointProgress progress = new FlyToPointProgress(); + progress.setBid(UUID.randomUUID().toString()); + progress.setTid(UUID.randomUUID().toString()); + progress.setTimestamp(System.currentTimeMillis()); + progress.setMethod("fly_to_point_progress"); + progress.setNeedReply(1); + progress.setData(data); + + String json = new Gson().toJson(progress); + MqttMessage mqttMessage = new MqttMessage(json.getBytes("UTF-8")); + mqttMessage.setQos(0); + MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, mqttMessage); + + LogUtil.log(TAG, "上报进度,status=" + currentStatus + ",轨迹点:" + pathPoints.size() + " 个"); + + } catch (Exception e) { + e.printStackTrace(); + LogUtil.log(TAG, "上报异常:" + e.getMessage()); + } + } + + /** + * 释放资源 + */ + public void release() { + stopReporting(); + instance = null; + } +} 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 c41f1838..30561198 100644 --- a/app/src/main/java/com/aros/apron/tools/Generakmzaltheratools.java +++ b/app/src/main/java/com/aros/apron/tools/Generakmzaltheratools.java @@ -104,13 +104,18 @@ public class Generakmzaltheratools extends BaseManager { //全局返航高度 double wpellheight=GpsUtils.wgs84Altitude(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLat()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLon())); + LogUtil.log(TAG,"全局返航高度wpellheight"+wpellheight); config.setGlobalRTHHeight(wpellheight); config.setIsGlobalRTHHeightSet(true); //安全起飞高度 - config.setSecurityTakeOffHeight(wpellheight); + if(wpellheight<100.0){ + config.setSecurityTakeOffHeight(wpellheight); + }else{ + config.setSecurityTakeOffHeight(100.0); + } config.setIsSecurityTakeOffHeightSet(true); diff --git a/app/src/main/java/com/aros/apron/tools/MqttManager.java b/app/src/main/java/com/aros/apron/tools/MqttManager.java index 226ae428..1af7bf30 100644 --- a/app/src/main/java/com/aros/apron/tools/MqttManager.java +++ b/app/src/main/java/com/aros/apron/tools/MqttManager.java @@ -59,9 +59,6 @@ public class MqttManager { - /** - * 连接MQTT服务器 - */ private void doClientConnection() { if (mqttAndroidClient.isConnected()) { return; @@ -89,9 +86,7 @@ public class MqttManager { } } - /** - * 判断网络是否连接(仅做日志记录,不阻塞连接尝试) - */ + private boolean isConnectIsNomarl() { ConnectivityManager connectivityManager = (ConnectivityManager) ApronApp.Companion.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { diff --git a/app/src/main/java/com/aros/apron/tools/PsdkWidgetScheduler.java b/app/src/main/java/com/aros/apron/tools/PsdkWidgetScheduler.java new file mode 100644 index 00000000..af421353 --- /dev/null +++ b/app/src/main/java/com/aros/apron/tools/PsdkWidgetScheduler.java @@ -0,0 +1,182 @@ +package com.aros.apron.tools; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import com.aros.apron.constant.AMSConfig; +import com.aros.apron.entity.Movement; +import com.aros.apron.entity.PsdkWidgetValuesReport; +import com.google.gson.Gson; + +import org.eclipse.paho.android.service.MqttAndroidClient; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * PSDK widget 值定时上报器 + */ +public class PsdkWidgetScheduler { + + private static final String TAG = "PsdkWidgetScheduler"; + private static volatile PsdkWidgetScheduler instance; + + private Handler handler; + private Runnable runnable; + + private volatile boolean isRunning = false; + + private static final long INTERVAL = 5000; + + private PsdkWidgetScheduler() { + handler = new Handler(Looper.getMainLooper()); + } + + public static PsdkWidgetScheduler getInstance() { + if (instance == null) { + synchronized (PsdkWidgetScheduler.class) { + if (instance == null) { + instance = new PsdkWidgetScheduler(); + } + } + } + return instance; + } + + + public void start() { + if (isRunning) { + LogUtil.log(TAG, "PSDK定时上报已在运行中"); + return; + } + + isRunning = true; + LogUtil.log(TAG, "启动PSDK widget定时上报,间隔5s"); + + runnable = new Runnable() { + @Override + public void run() { + if (!isRunning) return; + + try { + sendPsdkWidgetValues(); + } catch (Exception e) { + e.printStackTrace(); + LogUtil.log(TAG, "PSDK定时任务异常:" + e.getMessage()); + } + + if (isRunning) { + handler.postDelayed(this, INTERVAL); + } + } + }; + + handler.post(runnable); + } + + + public void stop() { + if (!isRunning) return; + + isRunning = false; + handler.removeCallbacks(runnable); + LogUtil.log(TAG, "停止PSDK widget定时上报"); + } + + public boolean isRunning() { + return isRunning; + } + + + public void release() { + stop(); + instance = null; + } + + private void sendPsdkWidgetValues() { + try { + MqttAndroidClient client = MqttManager.getInstance().mqttAndroidClient; + if (client == null || !client.isConnected()) { + LogUtil.log(TAG, "MQTT未连接,跳过PSDK widget上报"); + return; + } + + String psdkIndex = Movement.getInstance().getPsdk_index(); + if (TextUtils.isEmpty(psdkIndex) || psdkIndex.equals("0")) { + LogUtil.log(TAG, "psdk_index为空或为0(" + psdkIndex + "),跳过上报"); + return; + } + + PsdkWidgetValuesReport report = new PsdkWidgetValuesReport(); + report.setBid(UUID.randomUUID().toString()); + report.setTid(UUID.randomUUID().toString()); + report.setTimestamp(System.currentTimeMillis()); + report.setMethod("psdk_widget_values"); + report.setGateway("gateway_sn"); + + PsdkWidgetValuesReport.Data data = new PsdkWidgetValuesReport.Data(); + PsdkWidgetValuesReport.PsdkWidgetValue widget = new PsdkWidgetValuesReport.PsdkWidgetValue(); + widget.setPsdk_index(safeParseInt(psdkIndex, 0)); + widget.setPsdk_lib_version(safeString(Movement.getInstance().getPsdk_lib_version(), "")); + widget.setPsdk_name(safeString(Movement.getInstance().getPsdk_name(), "Speaker")); + widget.setPsdk_sn(safeString(Movement.getInstance().getPsdk_sn(), "")); + widget.setPsdk_type(safeParseInt(Movement.getInstance().getPsdk_type(), 5)); + widget.setPsdk_version(safeString(Movement.getInstance().getPsdk_version(), "01.00.01.11")); + + + PsdkWidgetValuesReport.SpeakerData speaker = new PsdkWidgetValuesReport.SpeakerData(); + speaker.setPlay_file_md5(safeString(Movement.getInstance().getPlay_file_md5(), "")); + speaker.setPlay_file_name(safeString(Movement.getInstance().getPlay_file_name(), "")); + speaker.setPlay_mode(safeParseInt(Movement.getInstance().getPlay_mode(), 0)); + speaker.setPlay_volume(safeParseInt(Movement.getInstance().getPlay_volume(), 0)); + speaker.setSystem_state(safeParseInt(Movement.getInstance().getSystem_state(), 0)); + speaker.setWork_mode(safeParseInt(Movement.getInstance().getWork_mode(), 0)); + speaker.setTts_language(safeParseInt(Movement.getInstance().getTts_language(), 0)); + speaker.setTts_speed(safeParseInt(Movement.getInstance().getTts_speed(), 0)); + speaker.setTts_type(safeParseInt(Movement.getInstance().getTts_type(), 0)); + speaker.setTts_volume(safeParseInt(Movement.getInstance().getTts_volume(), 0)); + + widget.setSpeaker(speaker); + + int[] valuesArray = Movement.getInstance().getValues(); + if (valuesArray != null && valuesArray.length > 0) { + List valuesList = new ArrayList<>(); + for (int v : valuesArray) { + valuesList.add(v); + } + widget.setValues(valuesList); + } + + List widgets = new ArrayList<>(); + widgets.add(widget); + data.setPsdk_widget_values(widgets); + report.setData(data); + + String jsonPayload = new Gson().toJson(report); + LogUtil.log(TAG, "PSDK widget JSON payload: " + jsonPayload); + MqttMessage mqttMessage = new MqttMessage(jsonPayload.getBytes("UTF-8")); + mqttMessage.setQos(1); + client.publish(AMSConfig.UP_UAV_EVENT, mqttMessage); + LogUtil.log(TAG, "上传PSDK widget值成功"); + } catch (Exception e) { + e.printStackTrace(); + LogUtil.log(TAG, "PSDK widget上传异常:" + e.toString()); + } + } + + private int safeParseInt(String value, int defaultVal) { + if (TextUtils.isEmpty(value)) return defaultVal; + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultVal; + } + } + + private String safeString(String value, String defaultVal) { + return TextUtils.isEmpty(value) ? defaultVal : value; + } +}