降落修改

This commit is contained in:
cxf 2026-05-20 10:15:41 +08:00
parent 66db69f894
commit 143ef4ffd5
28 changed files with 2138 additions and 548 deletions

View File

@ -171,6 +171,11 @@ open class MainActivity : BaseActivity() {
fun updateVtxHeartbeat() {
lastVtxFrameTime = System.currentTimeMillis()
}
@JvmStatic
fun setStartArucoType(type: Int) {
instance?.startArucoType = type
}
}
protected var primaryFpvWidget: FPVWidget? = null
@ -236,12 +241,13 @@ open class MainActivity : BaseActivity() {
private var cameraManager: ICameraStreamManager =
MediaDataCenter.getInstance().cameraStreamManager
private var startArucoType = 0 //1执行机库二维码识别 2执行备降点二维码识别
private var startArucoType = 0 //1执行机库二维码识别 2执行备降点二维码识别 3扫到任何码触发刹停
private var dictionary: Dictionary? = null
override fun useEventBus(): Boolean {
return true
}
override fun onResume() {
super.onResume()
@ -500,13 +506,13 @@ open class MainActivity : BaseActivity() {
secondaryFPVWidget?.post {
secondaryFPVWidget?.performClick()
}
LogUtil.log(TAG, "智能刷新:有云台,模拟点击 FPV Widget")
//LogUtil.log(TAG, "智能刷新:有云台,模拟点击 FPV Widget")
} else {
// 没有云台:直接刷新当前流
primaryFpvWidget?.post {
refreshVideoStream()
}
LogUtil.log(TAG, "智能刷新:无云台,直接刷新主视频流")
//LogUtil.log(TAG, "智能刷新:无云台,直接刷新主视频流")
}
}
@ -793,8 +799,9 @@ open class MainActivity : BaseActivity() {
enableStream()
initFpvStream()
startVtxHeartbeat()
SpeakerManager.getInstance().initMegaphoneInfo()
SpeakerManager.getInstance().addMegaphoneInfoListener()
GimbalManager.getInstance().setmode()
PayloadWidgetManager.getInstance().initPayloadInfo()
LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType)
@ -834,9 +841,10 @@ open class MainActivity : BaseActivity() {
enableStream()
initMixedStream()
startVtxHeartbeat()
SpeakerManager.getInstance().initMegaphoneInfo()
SpeakerManager.getInstance().addMegaphoneInfoListener()
GeoidManager.getInstance().init(this)
GimbalManager.getInstance().setmode()
PayloadWidgetManager.getInstance().initPayloadInfo()
LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType)
@ -876,9 +884,11 @@ open class MainActivity : BaseActivity() {
enableStream()
initCameraStream()
startVtxHeartbeat()
SpeakerManager.getInstance().initMegaphoneInfo()
SpeakerManager.getInstance().addMegaphoneInfoListener()
GeoidManager.getInstance().init(this)
GimbalManager.getInstance().setmode()
PayloadWidgetManager.getInstance().initPayloadInfo()
LogUtil.log(TAG, "自定义推流方式:" + PreferenceUtils.getInstance().customStreamType)
@ -905,6 +915,8 @@ open class MainActivity : BaseActivity() {
//val mixedLanding = MixedVisionLanding.getInstance()
// 为 PORT_1云台相机添加帧监听器
cameraManager.addFrameListener(
ComponentIndexType.PORT_1,
ICameraStreamManager.FrameFormat.YUV420_888
@ -933,6 +945,13 @@ open class MainActivity : BaseActivity() {
frameData,
dictionary
)
} else if (startArucoType == 3) {
Aprongim.getInstance()?.detectForceTriggerTags(
height,
width,
frameData,
dictionary
)
}
} finally {
Synchronizedstatus.setIsruningframe(false)
@ -972,6 +991,13 @@ open class MainActivity : BaseActivity() {
frameData,
dictionary
)
} else if (startArucoType == 3) {
Aprondown.getInstance()?.detectForceTriggerTags(
height,
width,
frameData,
dictionary
)
}
} finally {
Synchronizedstatus.setIsruningframe(false)
@ -1013,6 +1039,13 @@ open class MainActivity : BaseActivity() {
frameData,
dictionary
)
} else if (startArucoType == 3) {
ApronArucoDetect.getInstance()?.detectForceTriggerTags(
height,
width,
frameData,
dictionary
)
}
} finally {
Synchronizedstatus.setIsruningframe(false)
@ -1023,6 +1056,9 @@ open class MainActivity : BaseActivity() {
}
}
@SuppressLint("SuspiciousIndentation")
private fun initCameraStream() {
cameraManager.addFrameListener(
@ -1053,6 +1089,13 @@ open class MainActivity : BaseActivity() {
frameData,
dictionary
)
} else if (startArucoType == 3) {
ApronArucoDetectPort.getInstance()?.detectForceTriggerTags(
height,
width,
frameData,
dictionary
)
}
} finally {
Synchronizedstatus.setIsruningframe(false)

View File

@ -30,6 +30,7 @@ import com.google.gson.Gson;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
@ -201,6 +202,37 @@ public abstract class BaseManager {
}
}
/**
* 发送飞往备降点event事件
*/
public void sendFlyToAlternateLandPointEvent() {
try {
if (MqttManager.getInstance().mqttAndroidClient.isConnected()) {
MessageEvent messageEvent = new MessageEvent();
messageEvent.setBid(UUID.randomUUID().toString());
messageEvent.setTid(UUID.randomUUID().toString());
messageEvent.setTimestamp(System.currentTimeMillis());
messageEvent.setMethod(Constant.FLY_TO_ALTERNATE_LAND_POINT);
MessageEvent.Data data = new MessageEvent.Data();
if (TextUtils.isEmpty(PreferenceUtils.getInstance().getFlightId())) {
data.setFlight_id("null");
} else {
data.setFlight_id(PreferenceUtils.getInstance().getFlightId());
}
messageEvent.setData(data);
MqttMessage mqttMessage = new MqttMessage(new Gson().toJson(messageEvent).getBytes("UTF-8"));
mqttMessage.setQos(0);
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, mqttMessage);
LogUtil.log(TAG, "发送fly_to_alternate_land_point event成功:" + new Gson().toJson(messageEvent));
} else {
LogUtil.log(TAG, "发送fly_to_alternate_land_point event失败mqtt 未连接");
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.log(TAG, "发送fly_to_alternate_land_point event异常" + e.toString());
}
}
/**
* 发送媒体文件上传事件
*/

View File

@ -1,6 +1,7 @@
package com.aros.apron.callback;
import android.os.Handler;
import android.os.Looper;
import com.aros.apron.constant.AMSConfig;
import com.aros.apron.tools.LogUtil;
@ -17,6 +18,11 @@ public class MqttActionCallBack implements IMqttActionListener {
private final String TAG = "MqttActionCallBack";
private MqttAndroidClient mqttAndroidClient;
private MqttConnectOptions options;
private int reconnectAttempt = 0;
private static final int INITIAL_RETRY_INTERVAL = 3000; // 初始重试间隔 3
private static final int MAX_RETRY_INTERVAL = 30000; // 最大重试间隔 30
private static final Handler retryHandler = new Handler(Looper.getMainLooper());
private Runnable retryRunnable;
public MqttActionCallBack(MqttAndroidClient mqttAndroidClient, MqttConnectOptions options) {
this.mqttAndroidClient = mqttAndroidClient;
@ -26,10 +32,14 @@ public class MqttActionCallBack implements IMqttActionListener {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
LogUtil.log(TAG, "MQtt连接成功-------");
// 异步显示Toast避免Binder IPC阻塞当前主线程消息导致ANR
new Handler().post(() -> ToastUtil.showToast("MQtt连接成功"));
reconnectAttempt = 0;
if (retryRunnable != null) {
retryHandler.removeCallbacks(retryRunnable);
retryRunnable = null;
}
new Handler(Looper.getMainLooper()).post(() -> ToastUtil.showToast("MQtt连接成功"));
try {
mqttAndroidClient.subscribe(AMSConfig.DOWN_UAV_SERVICES, 1);//订阅主题:注册
mqttAndroidClient.subscribe(AMSConfig.DOWN_UAV_SERVICES, 1);
} catch (MqttException e) {
e.printStackTrace();
}
@ -39,19 +49,32 @@ public class MqttActionCallBack implements IMqttActionListener {
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
LogUtil.log(TAG, "MQtt连接失败:" + exception.toString());
try {
if (!mqttAndroidClient.isConnected()){
new Handler().postDelayed(new Runnable() {
if (!mqttAndroidClient.isConnected()) {
reconnectAttempt++;
// 指数退避3s 6s 12s 24s 30s(封顶)
long retryDelay = Math.min(INITIAL_RETRY_INTERVAL * (1L << Math.min(reconnectAttempt, 4)), MAX_RETRY_INTERVAL);
if (reconnectAttempt % 10 == 0 || reconnectAttempt <= 3) {
LogUtil.log(TAG, "" + reconnectAttempt + " 次重连,间隔 " + retryDelay + "ms");
}
if (retryRunnable != null) {
retryHandler.removeCallbacks(retryRunnable);
}
final long finalDelay = retryDelay;
retryRunnable = new Runnable() {
@Override
public void run() {
try {
mqttAndroidClient.connect(options, null, MqttActionCallBack.this); // 再次尝试连接
mqttAndroidClient.connect(options, null, MqttActionCallBack.this);
} catch (MqttException e) {
LogUtil.log(TAG,"mqtt重连异常:"+e.toString());
e.printStackTrace();
LogUtil.log(TAG, "mqtt重连异常:" + e.toString());
}
}
},1500);
};
retryHandler.postDelayed(retryRunnable, finalDelay);
}
} catch (Exception e) {
e.printStackTrace();

View File

@ -208,8 +208,10 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.FLIGHTTASK_RECOVERY:
LogUtil.log(TAG, "收到:航线继续" + jsonString);
clearVirtualStickHeartbeat();
MissionV3Manager.getInstance().resumeMission(message);
break;
case Constant.RETURN_HOME:
LogUtil.log(TAG, "收到:返航" + jsonString);
@ -428,12 +430,16 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.IR_METERING_MODE_SET:
LogUtil.log(TAG, "收到:负载控制—红外测温模式设置" + jsonString);
CameraManager.getInstance().setThermalTemperatureMeasureMode(message);
break;
case Constant.IR_METERING_POINT_SET:
LogUtil.log(TAG, "收到:负载控制—红外测温点设置" + jsonString);
CameraManager.getInstance().setThermalSpotMetersurePoint(message);
break;
case Constant.IR_METERING_AREA_SET:
LogUtil.log(TAG, "收到:负载控制—红外测温区域设置" + jsonString);
CameraManager.getInstance().setThermalRegionMetersureArea(message);
break;
case Constant.POI_MODE_ENTER:
LogUtil.log(TAG, "收到:飞行控制—进入 POI 环绕模式" + jsonString);
@ -456,8 +462,12 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.SPEAKER_AUDIO_PLAY_START:
LogUtil.log(TAG, "收到:喊话器-开始播放音频" + jsonString);
Movement.getInstance().setMd5(message.getData().getFile().getMd5());
Movement.getInstance().setPlay_file_md5(message.getData().getFile().getName());
Movement.getInstance().setStep_key("download");
SpeakerProgressReporter.getInstance().startAudioReport(2);
SpeakerManager.getInstance().setindex();
//SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerAudioPlayStart(message);
@ -470,8 +480,12 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.SPEAKER_TTS_PLAY_START:
LogUtil.log(TAG, "收到:喊话器-开始播放TTS文本" + jsonString);
Movement.getInstance().setMd5(message.getData().getTts().getMd5());
Movement.getInstance().setPlay_file_md5(message.getData().getTts().getMd5());
Movement.getInstance().setPlay_file_name(message.getData().getTts().getText());
SpeakerProgressReporter.getInstance().startTTSReport(2);
SpeakerManager.getInstance().setindex();
//SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerTTSPlayStart(message, 0);
@ -484,7 +498,7 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.SPEAKER_REPLAY:
LogUtil.log(TAG, "收到:喊话器-重新播放" + jsonString);
SpeakerManager.getInstance().setindex();
// SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerReply(message);
@ -497,7 +511,7 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.SPEAKER_PLAY_STOP:
LogUtil.log(TAG, "收到:喊话器-停止播放" + jsonString);
SpeakerManager.getInstance().setindex();
// SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerStop(message);
@ -511,7 +525,7 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.SPEAKER_PLAY_MODE_SET:
LogUtil.log(TAG, "收到:喊话器-设置播放模式" + jsonString);
SpeakerManager.getInstance().setindex();
// SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerPlayModeSet(message);
@ -524,7 +538,7 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.SPEAKER_PLAY_VOLUME_SET:
LogUtil.log(TAG, "收到:喊话器-设置音量" + jsonString);
SpeakerManager.getInstance().setindex();
//SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerPlayVolumeSet(message);
@ -537,7 +551,7 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.DRC_SPEAKER_TTS_SET:
LogUtil.log(TAG, "收到:喊话器-TTS喊话设置" + jsonString);
SpeakerManager.getInstance().setindex();
//SpeakerManager.getInstance().setindex();
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerTTSPlayStart(message, 1);

View File

@ -322,6 +322,11 @@ public class Constant {
*/
public static final String DRC_SPEAKER_TTS_SET="drc_speaker_tts_set";
/**
* 飞向备降点
*/
public static final String FLY_TO_ALTERNATE_LAND_POINT="fly_to_alternate_land_point";
/**
* 推流切换fpv
*/

View File

@ -1,5 +1,6 @@
package com.aros.apron.entity;
import java.security.PrivateKey;
import java.util.List;
public class MessageDown {
@ -111,6 +112,8 @@ public class MessageDown {
private float width;
private int psdk_index;
private int play_mode;
private int play_volume;
@ -132,6 +135,10 @@ public class MessageDown {
// saved: 是否保存 0- 1- (bool)
private boolean saved;
public int getGroup() {
return group;
}
@ -628,6 +635,33 @@ public class MessageDown {
public static class File {
private String fingerprint;
private String url;
private String md5;
private String name;
private String format;
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getFingerprint() {
return fingerprint;

View File

@ -3,10 +3,8 @@ package com.aros.apron.entity;
import java.util.List;
public class Movement {
private static class MovementHolder {
private static final Movement INSTANCE = new Movement();
}
@ -57,7 +55,7 @@ public class Movement {
private int landingPower;//降落电量所需百分比
private int lowBatteryRTHState;//智能低电量返航状态 0未触发智能低电量返航 1触发智能低电量返航飞行器正在倒计时 2执行智能低电量返航 3智能低电量返航被取消
private String missionName;//当前正在执行的航线名
private int currentWaypointIndex=0;//当前航点下标
private int currentWaypointIndex = 0;//当前航点下标
private boolean planeWing;//飞机是否在飞
private boolean isMotorsOn;//电机是否起转
@ -111,12 +109,12 @@ public class Movement {
private boolean resumeMissionStatus;//前端判断是否显示/隐藏继续航线
private boolean isVirtualStickQuitMission;//用户手动后退出航线
private boolean virtualcontrollget=false;//是否已经获得过控制权
private boolean virtualcontrollget = false;//是否已经获得过控制权
private int GPSSatelliteCount; //卫星个数
private boolean Alternate=false;
private boolean Alternate = false;
private boolean camera3stick=false;//3号相机模式释放控制权
private boolean camera3stick = false;//3号相机模式释放控制权
//适配上云格式参数拿到后再进行组装
@ -197,12 +195,12 @@ public class Movement {
private int wind_direction;
private int wind_speed;
private int camera_mode=0;
private int camera_mode = 0;
private int ir_metering_mode;
private int temperature;
private double x;
private double y;
private int ir_zoom_factor=2;
private int ir_zoom_factor = 2;
private double bottom;
private double left;
private double right;
@ -212,7 +210,7 @@ public class Movement {
private int record_time;
private int recording_state;
private int remain_photo_num;
private int remain_record_duration=3000;
private int remain_record_duration = 3000;
private boolean screen_split_enable;
private int wide_exposure_mode;
private int wide_exposure_value;
@ -222,7 +220,7 @@ public class Movement {
private int zoom_calibrate_nearest_focus_value;
private int zoom_exposure_mode;
private int zoom_exposure_value;
private double zoom_factor=2;
private double zoom_factor = 2;
private int zoom_focus_mode;
private int zoom_focus_state;
private int zoom_focus_value;
@ -278,9 +276,9 @@ public class Movement {
private String gnd_quality_4g;
private String sdr_freq_band;
private String freq_band_4g;
private boolean istakeoffex=false;
private boolean istakeoffex = false;
private boolean waylineinterpter=true;
private boolean waylineinterpter = true;
public boolean isWaylineinterpter() {
@ -318,12 +316,12 @@ public class Movement {
}
//航线上报状态
private int result=0;
private int result = 0;
// ========== 一键起飞相关字段 ==========
private boolean takeofftopointmission;
private volatile String takeoff_status="task_ready"; // task_ready/wayline_progress/wayline_ok/wayline_failed/wayline_cancel/task_finish
private volatile String takeoff_status = "task_ready"; // task_ready/wayline_progress/wayline_ok/wayline_failed/wayline_cancel/task_finish
private int takeoff_result; // 0成功非0错误
private float takeoff_remaining_distance; // 剩余距离
private float takeoff_remaining_time; // 剩余时间
@ -334,7 +332,7 @@ public class Movement {
private double takeofftargetlongitude;
private double takeofftargetheight;
private int mission_type;
private int mission_type;
public int getMission_type() {
return mission_type;
@ -355,6 +353,163 @@ public class Movement {
private String step_key;
private String md5;
//==================================psdk上报
private String psdk_index;
private String psdk_lib_version;
private String psdk_name="Speaker";
private String psdk_sn="8V2CP1X00A1ZPA";
private String psdk_type="5";
private String psdk_version="01.00.01.11";
private String play_file_md5;
private String play_file_name;
private String play_mode;
private String play_volume;
private String system_state;
private String work_mode;
private int[] values;
private String tts_language;
private String tts_speed;
private String tts_type;
private String tts_volume;
public String getTts_language() {
return tts_language;
}
public void setTts_language(String tts_language) {
this.tts_language = tts_language;
}
public String getTts_speed() {
return tts_speed;
}
public void setTts_speed(String tts_speed) {
this.tts_speed = tts_speed;
}
public String getTts_type() {
return tts_type;
}
public void setTts_type(String tts_type) {
this.tts_type = tts_type;
}
public String getTts_volume() {
return tts_volume;
}
public void setTts_volume(String tts_volume) {
this.tts_volume = tts_volume;
}
public String getPsdk_index() {
return psdk_index;
}
public void setPsdk_index(String 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 String getPsdk_type() {
return psdk_type;
}
public void setPsdk_type(String 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 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 String getPlay_mode() {
return play_mode;
}
public void setPlay_mode(String play_mode) {
this.play_mode = play_mode;
}
public String getPlay_volume() {
return play_volume;
}
public void setPlay_volume(String play_volume) {
this.play_volume = play_volume;
}
public String getSystem_state() {
return system_state;
}
public void setSystem_state(String system_state) {
this.system_state = system_state;
}
public String getWork_mode() {
return work_mode;
}
public void setWork_mode(String work_mode) {
this.work_mode = work_mode;
}
public int[] getValues() {
return values;
}
public void setValues(int[] values) {
this.values = values;
}
public boolean isCamera3stick() {
return camera3stick;
@ -413,7 +568,6 @@ public class Movement {
}
public String getTTS_status() {
return TTS_status;
}
@ -423,7 +577,6 @@ public class Movement {
}
public String getStep_key() {
return step_key;
}
@ -433,7 +586,6 @@ public class Movement {
}
public int getGPSSatelliteCount() {
return GPSSatelliteCount;
}
@ -445,6 +597,7 @@ public class Movement {
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
@ -508,7 +661,6 @@ public class Movement {
}
public float getTakeoff_remaining_distance() {
return takeoff_remaining_distance;
}
@ -550,9 +702,9 @@ public class Movement {
}
//用来判断是什么起飞type
private volatile int flightmode=0; //0代表没有操作 1代表航线飞行 2一键代表指令飞行 3flyto一键和flyto
private volatile int flightmode = 0; //0代表没有操作 1代表航线飞行 2一键代表指令飞行 3flyto一键和flyto
private boolean takeofftopoint;
private boolean opendrc=false; //true 是开启 false 是关闭
private boolean opendrc = false; //true 是开启 false 是关闭
public boolean isTakeofftopoint() {
return takeofftopoint;
@ -1743,7 +1895,6 @@ public class Movement {
}
public String getFirmware_version() {
return firmware_version;
}
@ -2182,7 +2333,6 @@ public class Movement {
}
public boolean isPlaneWing() {
return planeWing;
}
@ -2366,7 +2516,6 @@ public class Movement {
}
public String getFlightPathName() {
return flightPathName;
}

View File

@ -911,6 +911,7 @@ public class Osd {
private int camera_mode;
private int ir_metering_mode;
private IrMeteringPoint ir_metering_point;
private IrMeteringArea ir_metering_area;
private int ir_zoom_factor;
private LiveviewWorldRegion liveview_world_region;
private String payload_index;
@ -962,6 +963,14 @@ public class Osd {
this.ir_metering_point = ir_metering_point;
}
public IrMeteringArea getIr_metering_area() {
return ir_metering_area;
}
public void setIr_metering_area(IrMeteringArea ir_metering_area) {
this.ir_metering_area = ir_metering_area;
}
public int getIr_zoom_factor() {
return ir_zoom_factor;
}
@ -1200,6 +1209,102 @@ public class Osd {
}
}
public static class IrMeteringArea {
private double x;
private double y;
private double width;
private double height;
private double aver_temperature;
private IrTemperaturePoint min_temperature_point;
private IrTemperaturePoint max_temperature_point;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public double getAver_temperature() {
return aver_temperature;
}
public void setAver_temperature(double aver_temperature) {
this.aver_temperature = aver_temperature;
}
public IrTemperaturePoint getMin_temperature_point() {
return min_temperature_point;
}
public void setMin_temperature_point(IrTemperaturePoint min_temperature_point) {
this.min_temperature_point = min_temperature_point;
}
public IrTemperaturePoint getMax_temperature_point() {
return max_temperature_point;
}
public void setMax_temperature_point(IrTemperaturePoint max_temperature_point) {
this.max_temperature_point = max_temperature_point;
}
}
public static class IrTemperaturePoint {
private double x;
private double y;
private double temperature;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
}
public static class LiveviewWorldRegion {
private double bottom;
private double left;

View File

@ -13,6 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.aros.apron.base.BaseManager;
import com.aros.apron.constant.Constant;
import com.aros.apron.entity.FlightMission;
import com.aros.apron.entity.MessageDown;
import com.aros.apron.entity.MissionPoint;
@ -309,6 +310,13 @@ public class AlternateLandingManager extends BaseManager {
PreferenceUtils.getInstance().setNeedTriggerAlterArucoLand(false);
PreferenceUtils.getInstance().setNeedTriggerApronArucoLand(false);
sendEvent2Server( "开始飞往备降点",1);
sendFlyToAlternateLandPointEvent();
Movement.getInstance().setTakeoff_result(316052);
Movement.getInstance().setResult(316052);
//设置为未开始识别二维码状态
FlightManager.getInstance().setSendDetect(false);
EventBus.getDefault().post(FLAG_STOP_ARUCO);

View File

@ -3,15 +3,24 @@ package com.aros.apron.manager;
import static dji.sdk.keyvalue.key.KeyTools.createKey;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.aros.apron.base.BaseManager;
import com.aros.apron.entity.Movement;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.PreferenceUtils;
import dji.sdk.keyvalue.key.BatteryKey;
import dji.sdk.keyvalue.key.FlightControllerKey;
import dji.sdk.keyvalue.key.KeyTools;
import dji.sdk.keyvalue.value.battery.IndustryBatteryType;
import dji.sdk.keyvalue.value.common.EmptyMsg;
import dji.sdk.keyvalue.value.flightcontroller.LowBatteryRTHInfo;
import dji.v5.common.callback.CommonCallbacks;
import dji.v5.manager.KeyManager;
@ -33,10 +42,13 @@ public class BatteryManager extends BaseManager {
}
private boolean sendLowBatteryRTHPosition2Server;
//标识是否已触发强制返航避免重复触发
private boolean forcedRTHTriggered;
private static final long SECONDS_PER_DAY = 24 * 60 * 60; // 86400
public void initBatteryInfo() {
Boolean isConnect = KeyManager.getInstance().getValue(createKey(FlightControllerKey.KeyConnection));
if (isConnect != null && isConnect) {
@ -80,6 +92,7 @@ public class BatteryManager extends BaseManager {
if (newValue != null) {
Movement.getInstance().setBattery_a_capacity_percent(newValue);
Movement.getInstance().setCapacity_percent(newValue);
checkForcedBatteryRTH(newValue, Movement.getInstance().getBattery_b_capacity_percent());
}
}
});
@ -90,6 +103,7 @@ public class BatteryManager extends BaseManager {
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);
}
}
});
@ -273,6 +287,64 @@ public class BatteryManager extends BaseManager {
/**
* 检查双电池平均电量是否低于强制返航阈值低于则触发返航
*/
private void checkForcedBatteryRTH(int batteryA, int batteryB) {
if (forcedRTHTriggered) return;
if (!getGimbalAndCameraEnabled()) {
return;
}
// 过滤空值/无效电量0或负数不参与计算
if (batteryA <= 0 || batteryB <= 0) return;
String forcedBatteryStr = PreferenceUtils.getInstance().getForcedBattery();
if (TextUtils.isEmpty(forcedBatteryStr)) return;
int forcedBattery;
try {
forcedBattery = Integer.parseInt(forcedBatteryStr);
} catch (NumberFormatException e) {
return;
}
int avgBattery = (batteryA + batteryB) / 2;
if (avgBattery >= forcedBattery) return;
// 仅在飞行中触发
if (!Movement.getInstance().isPlaneWing()) return;
forcedRTHTriggered = true;
LogUtil.log(TAG, "平均电量" + avgBattery + "%低于强制返航阈值" + forcedBattery + "%,触发自动返航");
sendEvent2Server("触发低电量返航",1);
Movement.getInstance().setTakeoff_result(321773);
Movement.getInstance().setResult(321773);
new Handler(Looper.getMainLooper()).postDelayed(() -> {
dji.v5.manager.KeyManager.getInstance().performAction(
dji.sdk.keyvalue.key.KeyTools.createKey(dji.sdk.keyvalue.key.FlightControllerKey.KeyStartGoHome),
new CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>() {
@Override
public void onSuccess(EmptyMsg emptyMsg) {
LogUtil.log(TAG, "强制返航触发成功");
}
@Override
public void onFailure(@NonNull dji.v5.common.error.IDJIError error) {
LogUtil.log(TAG, "强制返航触发失败: " + error);
}
}
);
}, 1000);
}
public void releaseBatteryKey() {
KeyManager.getInstance().cancelListen(this);
}

View File

@ -52,6 +52,7 @@ import dji.sdk.keyvalue.value.camera.ZoomTargetPointInfo;
import dji.sdk.keyvalue.value.common.CameraLensType;
import dji.sdk.keyvalue.value.common.ComponentIndexType;
import dji.sdk.keyvalue.value.common.DoublePoint2D;
import dji.sdk.keyvalue.value.common.DoubleRect;
import dji.sdk.keyvalue.value.common.EmptyMsg;
import dji.sdk.keyvalue.value.product.ProductType;
import dji.v5.common.callback.CommonCallbacks;
@ -313,11 +314,13 @@ public class CameraManager extends BaseManager {
@Override
public void onValueChange(@Nullable ThermalTemperatureMeasureMode thermalTemperatureMeasureMode, @Nullable ThermalTemperatureMeasureMode t1) {
if (t1 != null && thermalTemperatureMeasureMode != null) {
Movement.getInstance().setIr_metering_mode(thermalTemperatureMeasureMode.value());
if(t1!=ThermalTemperatureMeasureMode.CENTRAL_POINT_METERING&&t1!=ThermalTemperatureMeasureMode.UNKNOWN){
Movement.getInstance().setIr_metering_mode(t1.value());
}
}
}
});
//当前测温的点的位置
KeyManager.getInstance().listen(KeyTools.createCameraKey(CameraKey.KeyThermalSpotMetersurePoint,
ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), this, new CommonCallbacks.KeyListener<DoublePoint2D>() {
@ -1791,85 +1794,85 @@ public class CameraManager extends BaseManager {
// }
// }
// //设置测温模式
// public void setThermalTemperatureMeasureMode(MQMessage message) {
// Boolean isConnect =KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey.
// KeyConnection,ComponentIndexType.PORT_1));
// if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) {
// KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalTemperatureMeasureMode,
// ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL),
// ThermalTemperatureMeasureMode.find(message.getThermalTemperatureMeasureMode()), new CommonCallbacks.CompletionCallback() {
// @Override
// public void onSuccess() {
// sendMsg2Server(message);
// }
//设置测温模式
public void setThermalTemperatureMeasureMode(MessageDown message) {
Boolean isConnect =KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey.
KeyConnection,ComponentIndexType.PORT_1));
if (isConnect != null && isConnect && getGimbalAndCameraEnabled()) {
KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalTemperatureMeasureMode,
ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL),
ThermalTemperatureMeasureMode.find(message.getData().getMode()), new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
sendMsg2Server(message);
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG,"设置测温模式:"+new Gson().toJson(error));
sendFailMsg2Server(message, "设置测温模式:" + getIDJIErrorMsg(error));
}
});
} else {
LogUtil.log(TAG, "设置测温模式失败:当前状态相机禁止操作");
}
}
//
// @Override
// public void onFailure(@NonNull IDJIError error) {
// LogUtil.log(TAG,"设置测温模式:"+new Gson().toJson(error));
// sendMsg2Server(message, "设置测温模式:" + getIDJIErrorMsg(error));
// }
// });
// } else {
// LogUtil.log(TAG, "设置测温模式失败:当前状态相机禁止操作");
// }
// }
//设置需要测温的点的位置
public void setThermalSpotMetersurePoint(MessageDown message) {
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()));
KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalSpotMetersurePoint,
ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), doublePoint2D, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
sendMsg2Server(message);
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG,"设置点测温失败:"+new Gson().toJson(error));
sendFailMsg2Server(message, "设置点测温失败:" + getIDJIErrorMsg(error));
}
});
} else {
LogUtil.log(TAG, "测温点设置失败:当前状态相机禁止操作");
}
}
//
// //设置需要测温的点的位置
// public void setThermalSpotMetersurePoint(MQMessage message) {
// 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.getMetersurePointX()));
// doublePoint2D.setY(Double.parseDouble(message.getMetersurePointY()));
// KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalSpotMetersurePoint,
// ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), doublePoint2D, new CommonCallbacks.CompletionCallback() {
// @Override
// public void onSuccess() {
// sendMsg2Server(message);
// }
//
// @Override
// public void onFailure(@NonNull IDJIError error) {
// LogUtil.log(TAG,"设置点测温失败:"+new Gson().toJson(error));
// sendMsg2Server(message, "设置点测温失败:" + getIDJIErrorMsg(error));
//
// }
// });
//
// } else {
// LogUtil.log(TAG, "测温点设置失败:当前状态相机禁止操作");
// }
// }
//
// //设置需要测温的区域位置
// public void setThermalRegionMetersureArea(MQMessage message) {
// Boolean isConnect =KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey.
// KeyConnection,ComponentIndexType.PORT_1));
// if (isConnect != null && isConnect) {
// DoubleRect doubleRect = new DoubleRect();
// doubleRect.setX(Double.parseDouble(message.getMetersureAreaX()));
// doubleRect.setY(Double.parseDouble(message.getMetersureAreaY()));
// doubleRect.setHeight(Double.parseDouble(message.getMetersureAreaHeight()));
// doubleRect.setWidth(Double.parseDouble(message.getMetersureAreaWidth()));
// KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalRegionMetersureArea,
// ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), doubleRect, new CommonCallbacks.CompletionCallback() {
// @Override
// public void onSuccess() {
// sendMsg2Server(message);
// }
//
// @Override
// public void onFailure(@NonNull IDJIError error) {
// LogUtil.log(TAG,"设置区域测温失败:"+new Gson().toJson(error));
// sendMsg2Server(message, "设置区域测温失败:" + getIDJIErrorMsg(error));
// }
// });
// } else {
// LogUtil.log(TAG, "测温区域设置失败:当前状态相机禁止操作");
// }
// }
//设置需要测温的区域位置
public void setThermalRegionMetersureArea(MessageDown message) {
Boolean isConnect =KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey.
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());
KeyManager.getInstance().setValue(KeyTools.createCameraKey(CameraKey.KeyThermalRegionMetersureArea,
ComponentIndexType.PORT_1, CameraLensType.CAMERA_LENS_THERMAL), doubleRect, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
sendMsg2Server(message);
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG,"设置区域测温失败:"+new Gson().toJson(error));
sendFailMsg2Server(message, "设置区域测温失败:" + getIDJIErrorMsg(error));
}
});
} else {
LogUtil.log(TAG, "测温区域设置失败:当前状态相机禁止操作");
}
}
}

View File

@ -16,6 +16,7 @@ import com.aros.apron.constant.AMSConfig;
import com.aros.apron.entity.ApronExecutionStatus;
import com.aros.apron.entity.MessageDown;
import com.aros.apron.entity.Movement;
import com.aros.apron.activity.MainActivity;
import com.aros.apron.mix.Aprondown;
import com.aros.apron.mix.Aprongim;
import com.aros.apron.tools.AlternateArucoDetect;
@ -26,6 +27,7 @@ import com.aros.apron.tools.LocationUtils;
import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.MqttManager;
import com.aros.apron.tools.PreferenceUtils;
import com.aros.apron.tools.PsdkWidgetScheduler;
import com.aros.apron.tools.TakeoffProgressScheduler;
import com.google.gson.Gson;
@ -36,6 +38,7 @@ import java.util.Objects;
import dji.sdk.keyvalue.key.AirLinkKey;
import dji.sdk.keyvalue.key.CameraKey;
import dji.sdk.keyvalue.key.FlightAssistantKey;
import dji.sdk.keyvalue.key.FlightControllerKey;
import dji.sdk.keyvalue.key.GimbalKey;
import dji.sdk.keyvalue.key.KeyTools;
@ -51,6 +54,7 @@ import dji.sdk.keyvalue.value.common.EmptyMsg;
import dji.sdk.keyvalue.value.common.LocationCoordinate2D;
import dji.sdk.keyvalue.value.common.LocationCoordinate3D;
import dji.sdk.keyvalue.value.common.Velocity3D;
import dji.sdk.keyvalue.value.flightassistant.AuxiliaryLightMode;
import dji.sdk.keyvalue.value.flightassistant.VisionAssistDirection;
import dji.sdk.keyvalue.value.flightcontroller.FailsafeAction;
import dji.sdk.keyvalue.value.flightcontroller.FlightMode;
@ -120,8 +124,12 @@ public class FlightManager extends BaseManager {
OSDManager.getInstance().pushFlightAttitude();
Movement.getInstance().setMissionFinish(true);
PsdkWidgetScheduler.getInstance().stop();
} else {
PsdkWidgetScheduler.getInstance().start();
PerceptionManager.getInstance().setPerceptionEnable(false);
PerceptionManager.getInstance().setObstacleAvoidanceupEnabled(false);
PerceptionManager.getInstance().setObstacleAvoidancedownEnabled(false);
@ -790,6 +798,8 @@ public class FlightManager extends BaseManager {
private int goHomeExecutionState;
//确保每次流程只发送触发降落一次降落完成后设置为false而不是最后开始landing时触发如果不再landing时设置为false那么在landing的途中也可能再次触发landing
private boolean isSendDetect;
// 强制触发ArUco检测标志无视高度条件与高度条件 || 关系
public static boolean forceTriggerDetection;
//飞机飞走后是否发送关舱门(确保只发送一次)
private boolean sendCloseCabinDoorMsg;
//飞机飞回后是否发送开舱门(确保只发送一次)
@ -904,16 +914,19 @@ public class FlightManager extends BaseManager {
double distance = Movement.getInstance().getHome_distance();
double flyingHeight = Movement.getInstance().getElevation();
boolean isDebugMode = PreferenceUtils.getInstance().getIsDebugMode();
boolean isDistanceAndHeightValid = distance < DISTANCE_THRESHOLD &&
flyingHeight > FLYING_HEIGHT_THRESHOLD && !sendOpenCabinDoorMsg;
if (!PreferenceUtils.getInstance().getTriggerToAlternatePoint() &&
isReturningHome && isDistanceAndHeightValid && !isDebugMode) {
LogUtil.log(TAG, "返航距离:" + distance + "---当前高度:" + flyingHeight);
sendOpenCabinDoorMsg = true;
DockCloseManager.getInstance().stopRetry();
DockOpenManager.getInstance().sendDockOpenMsg2Server();
}
}
@ -921,9 +934,9 @@ public class FlightManager extends BaseManager {
//降落时将云台朝下
private void gimbalDownwards() {
if (goHomeExecutionState == GoHomeState.LANDING.value()
&& Movement.getInstance().getElevation() > 11 && !isGimbalDownwards) {
&& Movement.getInstance().getElevation() > 5 && !isGimbalDownwards) {
// 切换为强制扫码模式扫到任何ArUco码就触发刹停
MainActivity.setStartArucoType(3);
MediaDataCenter.getInstance().getCameraStreamManager().setVisionAssistViewDirection(VisionAssistDirection.DOWN, new CommonCallbacks.CompletionCallback() {
@Override
@ -941,6 +954,7 @@ public class FlightManager extends BaseManager {
leDsSettings.setNavigationLEDsOn(false);
leDsSettings.setStatusIndicatorLEDsOn(true);
leDsSettings.setFrontLEDsOn(true);
KeyManager.getInstance().setValue(KeyTools.createKey(FlightControllerKey.KeyLEDsSettings), leDsSettings, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
@ -953,12 +967,28 @@ public class FlightManager extends BaseManager {
}
});
KeyManager.getInstance().setValue(KeyTools.createKey(FlightAssistantKey.KeyBottomAuxiliaryLightMode), AuxiliaryLightMode.OFF, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
LogUtil.log(TAG,"下视辅助灯开启成功");
}
DroneHelper.getInstance().setGimbalPitchDegree();
@Override
public void onFailure(@NonNull IDJIError idjiError) {
LogUtil.log(TAG,"夜航灯关闭失败");
}
});
//DroneHelper.getInstance().setGimbalPitchDegree();
//将镜头设置为自动对焦
DroneHelper.getInstance().setCameraFocusMode();
//DroneHelper.getInstance().setCameraFocusMode();
new android.os.Handler().postDelayed(() -> {
DroneHelper.getInstance().setGimbalPitchDegree();
}, 500);
new android.os.Handler().postDelayed(() -> {
DroneHelper.getInstance().setCameraFocusMode();
}, 500);
new android.os.Handler().postDelayed(() -> {
@ -967,7 +997,6 @@ public class FlightManager extends BaseManager {
new android.os.Handler().postDelayed(() -> {
setCameraExposureMode();
}, 500);
new android.os.Handler().postDelayed(() -> {
EventBus.getDefault().post("REFRESH_VIDEO_SOURCE");
}, 2000);
@ -986,13 +1015,21 @@ public class FlightManager extends BaseManager {
isGimbalDownwards = true;
//重复设置防止重复
new android.os.Handler().postDelayed(() -> {
DroneHelper.getInstance().setGimbalPitchDegree();
}, 1000);
PerceptionManager.getInstance().setPerceptionEnable(false);
PerceptionManager.getInstance().setObstacleAvoidanceupEnabled(false);
PerceptionManager.getInstance().setObstacleAvoidancedownEnabled(false);
PerceptionManager.getInstance().closeRadarManager(false);
if (Movement.getInstance().getIsRecording() == 1) {
CameraManager.getInstance().stopRecordVideo(null);
}
}
}
@ -1136,6 +1173,10 @@ public class FlightManager extends BaseManager {
private static final double FLYING_HEIGHT_THRESHOLD_MIN = -2;
private static final double FLYING_HEIGHT_THRESHOLD_MIN_ALTERNATE = 2.0;
// 打印相对高度的节流计数器(约每秒一次)
private int heightLogCounter = 0;
private static final int HEIGHT_LOG_INTERVAL = 30; // startVisionLanding大约每秒回调一次
private void startVisionLanding() {
boolean isDebugMode = PreferenceUtils.getInstance().getIsDebugMode();
@ -1143,29 +1184,36 @@ public class FlightManager extends BaseManager {
boolean needTriggerApronArucoLand = PreferenceUtils.getInstance().getNeedTriggerApronArucoLand();
boolean needTriggerAlterArucoLand = PreferenceUtils.getInstance().getNeedTriggerAlterArucoLand();
double thresholdMax = triggerToAlternatePoint ? FLYING_HEIGHT_THRESHOLD_MAX_ALTERNATE : FLYING_HEIGHT_THRESHOLD_MAX;
//LogUtil.log(TAG,"相对高度"+Movement.getInstance().getElevation()+"超声波高度"+Movement.getInstance().getUltrasonicHeight());
// 节流打印相对高度和超声波高度(约每秒1次)
if (++heightLogCounter >= HEIGHT_LOG_INTERVAL) {
LogUtil.log(TAG, "[高度监控] 相对高度=" + Movement.getInstance().getElevation() + "m, 超声波高度=" + Movement.getInstance().getUltrasonicHeight() + "dm");
heightLogCounter = 0;
}
if (isFlying && (Movement.getInstance().getElevation() < 15 && Movement.getInstance().getUltrasonicHeight() < 50) && !isSendDetect) {
// LogUtil.log(TAG,"相对高度"+Movement.getInstance().getElevation()+"超声波高度"+Movement.getInstance().getUltrasonicHeight());
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;
if (flyingHeight > thresholdMin) {
//LogUtil.log(TAG,"相对高度"+Movement.getInstance().getElevation()+"超声波高度"+Movement.getInstance().getUltrasonicHeight());
if (flyingHeight > thresholdMin || forceTriggerDetection) {
boolean shouldTriggerDetection;
if (isDebugMode) {
shouldTriggerDetection = goHomeExecutionState == 2;
} else {
shouldTriggerDetection = goHomeExecutionState == 2 || needTriggerApronArucoLand || needTriggerAlterArucoLand;
}
if (shouldTriggerDetection) {
if (shouldTriggerDetection || forceTriggerDetection) {
triggerArucoDetection();
}
}
}
@ -1174,6 +1222,8 @@ public class FlightManager extends BaseManager {
private void triggerArucoDetection() {
//当电池电量大于40时允许复降次数设置为10次
isSendDetect = true;
if (Movement.getInstance().getBattery_a_capacity_percent() > 40) {
AMSConfig.getInstance().setAlternateLandingTimes(10 + "");
}
@ -1192,7 +1242,7 @@ public class FlightManager extends BaseManager {
LogUtil.log(TAG, "开始识别机库二维码,椭球高度:" + Movement.getInstance().getElevation() + "" + "--超声波高度:" + Movement.getInstance().getUltrasonicHeight() + "分米");
sendEvent2Server("开始视觉降落", 1);
}
isSendDetect = true;
//PerceptionManager.getInstance().setPerceptionEnable(false);
}
@ -1242,6 +1292,8 @@ public class FlightManager extends BaseManager {
isGimbalReset = false;
isTriggerLanding = true;
isGimbalDownwards = false;
forceTriggerDetection = false;
MainActivity.setStartArucoType(0);
}
@Override
@ -1283,6 +1335,8 @@ public class FlightManager extends BaseManager {
isSendDetect = false;
isTriggerLanding = false;
sendCloseCabinDoorMsg = false;
forceTriggerDetection = false;
MainActivity.setStartArucoType(0);
ApronArucoDetect.getInstance().setCanLanding(false);
ApronArucoDetectPort.getInstance().setCanLanding(false);
Aprondown.getInstance().setCanLanding(false);
@ -1325,6 +1379,7 @@ public class FlightManager extends BaseManager {
}
// 上传媒体文件
SystemManager.getInstance().upLoadMedia(MqttManager.getInstance().mqttAndroidClient);
sendEvent2Server("开始上传日志文件", 1);
//上传日志文件
LogUploadManager.getInstance().uploadTodayLog();

View File

@ -102,27 +102,44 @@ public class MediaManager extends BaseManager {
private int enterPlayBackFailTimes;
private boolean isEnablePlayback;
private volatile boolean isPlaybackEnabling; // 防止并发调用
private Handler playbackTimeoutHandler = new Handler(android.os.Looper.getMainLooper());
private Runnable playbackTimeoutRunnable = null;
private static final int PLAYBACK_ENABLE_TIMEOUT_MS = 15000; // 进入媒体模式超时 15
public void enablePlayback() {
// 每次进入媒体模式时清空已上传文件集合
uploadedFileNames.clear();
bucketChecked = false;
downloadFailTimes = 0;
// 重置失败计数和标志
enterPlayBackFailTimes = 0;
isEnablePlayback = false;
// 重置拉取文件列表相关的计数器
pullMediaFileListFromCameraFailTimes = 0;
updatingWaitCount = 0;
pullqwq = false;
pullStartTime = System.currentTimeMillis(); // 开始计时
if (isPlaybackEnabling) {
LogUtil.log(TAG, "媒体模式已在启用中,跳过重复调用");
return;
}
boolean isFirstEntry = !isPlaybackEnabling;
isPlaybackEnabling = true;
LogUtil.log(TAG, "已清空上传文件集合");
// 停止端口扫描和 RTSP 刷新关闭 RTSP 推流确保媒体上传稳定
StreamManager.getInstance().stopStreamRefreshTimer();
com.aros.apron.tools.SimplePortScanner.getInstance().stopScan();
StreamManager.getInstance().stopstream();
// 仅首次调用时清理状态重试时保留计数器
if (isFirstEntry) {
uploadedFileNames.clear();
bucketChecked = false;
downloadFailTimes = 0;
enterPlayBackFailTimes = 0;
isEnablePlayback = false;
pullMediaFileListFromCameraFailTimes = 0;
updatingWaitCount = 0;
pullqwq = false;
pullStartTime = System.currentTimeMillis();
LogUtil.log(TAG, "已清空上传文件集合");
}
MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
LogUtil.log(TAG, "进入媒体模式成功");
isPlaybackEnabling = false; // 成功后释放锁
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
@ -149,8 +166,60 @@ public class MediaManager extends BaseManager {
public void run() {
if (enterPlayBackFailTimes < 10) {
enterPlayBackFailTimes++;
enablePlayback();
isPlaybackEnabling = false; // 释放锁
LogUtil.log(TAG, "" + enterPlayBackFailTimes + "次重试进入媒体模式");
retryEnablePlayback();
} else {
isPlaybackEnabling = false; // 失败放弃后释放锁
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
sendEvent2Server("媒体模式进入失败:关机",1);
}
}
}, 1500);
}
}
});
}
/** 重试进入媒体模式(不清理状态,保留计数器) */
private void retryEnablePlayback() {
isPlaybackEnabling = true;
MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
LogUtil.log(TAG, "进入媒体模式成功(重试)");
isPlaybackEnabling = false;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
MediaFileListDataSource source = new
MediaFileListDataSource.Builder().setIndexType(ComponentIndexType.PORT_1).build();
MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
pullMediaFileListFromCamera();
}
}, 3000);
}
}, 3000);
}
@Override
public void onFailure(@NonNull IDJIError idjiError) {
LogUtil.log(TAG, "" + enterPlayBackFailTimes + "次进入媒体模式失败:" + new Gson().toJson(idjiError));
if (!isEnablePlayback) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (enterPlayBackFailTimes < 10) {
enterPlayBackFailTimes++;
isPlaybackEnabling = false;
LogUtil.log(TAG, "" + enterPlayBackFailTimes + "次重试进入媒体模式");
retryEnablePlayback();
} else {
isPlaybackEnabling = false;
ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true);
sendEvent2Server("媒体模式进入失败:关机",1);
}

View File

@ -909,9 +909,15 @@ public class MissionV3Manager extends BaseManager {
Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.
KeyConnection));
if (isConnect != null && isConnect) {
if (isConnect == null || !isConnect || !getGimbalAndCameraEnabled()) {
LogUtil.log(TAG,"当前状态禁止恢复");
sendFailMsg2Server(message, "当前状态禁止恢复");
return;
}
if (Movement.getInstance().getFlightmode() == 1) {
Movement.getInstance().setMode_code(5);
}
IWaypointMissionManager missionManager = WaypointMissionManager.getInstance();
//如果航线是暂停状态,直接恢复航线否则查询断点信息
if (Movement.getInstance().getMissionStateCode() == 7) {

View File

@ -185,6 +185,23 @@ public class OSDManager extends BaseManager {
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);
Osd.Data.Cameras.IrTemperaturePoint irMinTemp = new Osd.Data.Cameras.IrTemperaturePoint();
irMinTemp.setX(0);
irMinTemp.setY(0);
irMinTemp.setTemperature(0);
irMeteringArea.setMin_temperature_point(irMinTemp);
Osd.Data.Cameras.IrTemperaturePoint irMaxTemp = new Osd.Data.Cameras.IrTemperaturePoint();
irMaxTemp.setX(0);
irMaxTemp.setY(0);
irMaxTemp.setTemperature(0);
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");
cameraA.setPhoto_state(Movement.getInstance().getPhoto_state());

View File

@ -2,9 +2,18 @@ package com.aros.apron.manager;
import androidx.annotation.NonNull;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import com.aros.apron.base.BaseManager;
import com.aros.apron.constant.AMSConfig;
import com.aros.apron.entity.Movement;
import com.aros.apron.entity.PayloadInfo;
import com.aros.apron.entity.PsdkWidgetValuesReport;
import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.MqttManager;
import com.aros.apron.xclog.XcFileLog;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -12,6 +21,7 @@ import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import dji.sdk.keyvalue.key.FlightControllerKey;
import dji.sdk.keyvalue.key.KeyTools;
@ -33,6 +43,16 @@ public class PayloadWidgetManager extends BaseManager {
private List<PayloadInfo> payloadInfos = new ArrayList<>();
private PayloadBasicInfo cachedBasicInfo;
private PayloadWidgetInfo cachedWidgetInfo;
private Handler psdkReportHandler;
private Runnable psdkReportRunnable;
private static final long PSDK_REPORT_INTERVAL = 5000; // 5秒
private StringBuffer payloadBuffer = new StringBuffer();
private volatile boolean hasSpeaker = false;
private volatile boolean speakerPortSet = false;
private PayloadWidgetManager() {
}
@ -58,7 +78,6 @@ public class PayloadWidgetManager extends BaseManager {
if (payloadManager != null) {
IPayloadManager iPayloadManager = payloadManager.get(PayloadIndexType.PORT_2);
if (iPayloadManager != null) {
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_2).pullWidgetInfoFromPayload(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
@ -66,16 +85,42 @@ public class PayloadWidgetManager extends BaseManager {
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_2).addPayloadBasicInfoListener(new PayloadBasicInfoListener() {
@Override
public void onPayloadBasicInfoUpdate(PayloadBasicInfo info) {
LogUtil.log(TAG, "左侧负载基础信息:" + info.toString());
XcFileLog.getInstace().w(TAG, "左侧负载基础信息:" + info.toString());
//LogUtil.log(TAG, "左侧负载基础信息:" + info.toString());
// XcFileLog.getInstace().w(TAG, "左侧负载基础信息:" + info.toString());
//cachedBasicInfo = info;
//startPsdkWidgetReport();
}
});
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_2).addPayloadDataListener(new PayloadDataListener() {
@Override
public void onDataFromPayloadUpdate(byte[] data) {
LogUtil.log(TAG, "左侧负载数据信息:" +new String(data, java.nio.charset.StandardCharsets.UTF_8).replaceAll("[^\\x20-\\x7E\\u4e00-\\u9fa5]", ""));
XcFileLog.getInstace().w(TAG, "左侧负载数据信息:" +new String(data, java.nio.charset.StandardCharsets.UTF_8).replaceAll("[^\\x20-\\x7E\\u4e00-\\u9fa5]", ""));
String cleanData = new String(data, java.nio.charset.StandardCharsets.UTF_8)
.replaceAll("[^\\x20-\\x7E\\u4e00-\\u9fa5]", "");
// LogUtil.log(TAG, "左侧负载数据信息:" + cleanData);
// XcFileLog.getInstace().w(TAG, "左侧负载数据信息:" + cleanData);
if (cleanData.contains("jwD")) {
payloadBuffer.setLength(0);
payloadBuffer.append(cleanData);
// LogUtil.log(TAG, "负载数据开始,清空缓存");
}
if (cleanData.contains("}")) {
if (payloadBuffer.length() > 0) {
payloadBuffer.append(cleanData);
String fullJson = payloadBuffer.toString();
payloadBuffer.setLength(0); // 清空准备下次
// LogUtil.log(TAG, "负载完整数据: " + fullJson);
sendFloatingWindowText(fullJson);
} else {
//LogUtil.log(TAG, "负载数据: 缓存为空,忽略孤立}");
}
}
}
});
@ -83,9 +128,12 @@ public class PayloadWidgetManager extends BaseManager {
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_2).addPayloadWidgetInfoListener(new PayloadWidgetInfoListener() {
@Override
public void onPayloadWidgetInfoUpdate(PayloadWidgetInfo info) {
LogUtil.log(TAG, "左侧负载控件信息:" + info.toString());
XcFileLog.getInstace().w(TAG, "左侧负载控件信息:" + info.toString());
//LogUtil.log(TAG, "左侧负载控件信息:" + info.toString());
//XcFileLog.getInstace().w(TAG, "左侧负载控件信息:" + info.toString());
//cachedWidgetInfo = info;
//如果负载为空
detectSpeaker(info, 2);
if (info.getConfigInterfaceWidgetList() == null || info.getMainInterfaceWidgetList() == null) {
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_2).pullWidgetInfoFromPayload(new CommonCallbacks.CompletionCallback() {
@Override
@ -112,13 +160,52 @@ public class PayloadWidgetManager extends BaseManager {
} else {
LogUtil.log(TAG, "监听LEFT_OR_MAIN PSDK数据失败:设备未连接");
}
IPayloadManager iPayloadManager3 = payloadManager.get(PayloadIndexType.PORT_3);
if (iPayloadManager3 != null) {
//基础信息
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_3).addPayloadBasicInfoListener(new PayloadBasicInfoListener() {
@Override
public void onPayloadBasicInfoUpdate(PayloadBasicInfo info) {
// LogUtil.log(TAG, "3号负载基础信息:" + info.toString());
}
});
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_3).addPayloadDataListener(new PayloadDataListener() {
@Override
public void onDataFromPayloadUpdate(byte[] data) {
// LogUtil.log(TAG, "3号Data负载基础信息:" + data);
}
});
//可以把负载设备控件打印
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_3).addPayloadWidgetInfoListener(new PayloadWidgetInfoListener() {
@Override
public void onPayloadWidgetInfoUpdate(PayloadWidgetInfo info) {
// LogUtil.log(TAG, "3号负载控件信息:" + info.toString());
detectSpeaker(info, 3);
if (info.getConfigInterfaceWidgetList() == null || info.getMainInterfaceWidgetList() == null) {
PayloadCenter.getInstance().getPayloadManager().get(PayloadIndexType.PORT_3).pullWidgetInfoFromPayload(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
// LogUtil.log(TAG, "负载重复拉取成功");
}
@Override
public void onFailure(@NonNull IDJIError idjiError) {
//LogUtil.log(TAG, "负载重复拉取失败");
}
});
}
}
});
} else {
LogUtil.log(TAG, "监听right_OR_MAIN PSDK数据失败:设备未连接");
}
} else {
LogUtil.log(TAG, "监听psdk数据失败:未检测到设备");
}
//负载基础信息
} else {
LogUtil.log(TAG, "设备未连接");
}
@ -425,6 +512,53 @@ public class PayloadWidgetManager extends BaseManager {
// }
/**
* 检测负载控件信息中是否有喊话器检测到后自动设置喊话器位置
* @param info 负载控件信息
* @param portIndex 端口编号 2或3
*/
private void detectSpeaker(PayloadWidgetInfo info, int portIndex) {
if (info == null || speakerPortSet) return;
try {
Object speakerWidget = info.getSpeakerWidget();
if (speakerWidget != null) {
String infoStr = info.toString();
// speakerWidget=SpeakerWidget{isTTSEnabled=true, isVoiceEnabled=true} 代表有喊话器
if (infoStr.contains("SpeakerWidget") && infoStr.contains("isTTSEnabled=true")) {
hasSpeaker = true;
LogUtil.log(TAG, "检测到喊话器负载(SpeakerWidget)在 PORT_" + portIndex);
// 自动设置喊话器位置设置成功后不再重复设置
dji.v5.manager.aircraft.megaphone.MegaphoneIndex targetIndex =
portIndex == 2 ? dji.v5.manager.aircraft.megaphone.MegaphoneIndex.PORT_2
: dji.v5.manager.aircraft.megaphone.MegaphoneIndex.PORT_3;
LogUtil.log(TAG, "自动设置喊话器位置到: " + targetIndex);
dji.v5.manager.aircraft.megaphone.MegaphoneManager.getInstance().setMegaphoneIndex(targetIndex,
new dji.v5.common.callback.CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
speakerPortSet = true;
LogUtil.log(TAG, "喊话器位置设置成功(从负载控件自动设置): " + targetIndex + ",不再重复设置");
// 同步到 SpeakerManager
com.aros.apron.manager.SpeakerManager.psdkindex = portIndex;
com.aros.apron.manager.SpeakerManager.getInstance().getMegaphoneIndex();
}
@Override
public void onFailure(@NonNull dji.v5.common.error.IDJIError error) {
LogUtil.log(TAG, "喊话器位置设置失败(从负载控件自动设置): " + error);
}
});
}
}
} catch (Exception e) {
LogUtil.log(TAG, "检测喊话器异常: " + e.getMessage());
}
}
public boolean hasSpeaker() {
return hasSpeaker;
}
/**
* 检查列表中是否已经存在指定payloadIndexType的PayloadInfo对象
*/
@ -436,4 +570,140 @@ public class PayloadWidgetManager extends BaseManager {
}
return false;
}
/**
* 启动5秒定时上报
*/
private void startPsdkWidgetReport() {
if (psdkReportHandler == null) {
psdkReportHandler = new Handler(Looper.getMainLooper());
}
if (psdkReportRunnable != null) {
return; // 已经在运行
}
psdkReportRunnable = new Runnable() {
@Override
public void run() {
psdkReportHandler.postDelayed(this, PSDK_REPORT_INTERVAL);
}
};
psdkReportHandler.post(psdkReportRunnable);
LogUtil.log(TAG, "启动PSDK负载控件5秒定时上报");
}
/**
* 停止定时上报
*/
private void stopPsdkWidgetReport() {
if (psdkReportHandler != null && psdkReportRunnable != null) {
psdkReportHandler.removeCallbacks(psdkReportRunnable);
psdkReportRunnable = null;
LogUtil.log(TAG, "停止PSDK负载控件定时上报");
}
}
/**
* 发送PSDK负载控件值到MQTT
*/
// private void sendPsdkWidgetValues() {
// try {
// if (MqttManager.getInstance().mqttAndroidClient == null || !MqttManager.getInstance().mqttAndroidClient.isConnected()) {
// return;
// }
// if (cachedBasicInfo == null && cachedWidgetInfo == null) {
// return;
// }
//
// PsdkWidgetValuesReport report = new PsdkWidgetValuesReport();
// report.setTid(UUID.randomUUID().toString());
// report.setBid(UUID.randomUUID().toString());
// report.setTimestamp(System.currentTimeMillis());
// report.setMethod("psdk_widget_values");
// report.setGateway(Movement.getInstance().getSerialNumber());
//
// PsdkWidgetValuesReport.Data data = new PsdkWidgetValuesReport.Data();
// List<PsdkWidgetValuesReport.PsdkWidgetValue> widgetList = new ArrayList<>();
//
// // 从cachedBasicInfo和cachedWidgetInfo组装数据
// if (cachedBasicInfo != null) {
// PsdkWidgetValuesReport.PsdkWidgetValue w = new PsdkWidgetValuesReport.PsdkWidgetValue();
// w.setPsdk_index(2);
// w.setPsdk_lib_version(cachedBasicInfo.getVersion() != null ? cachedBasicInfo.getVersion() : "");
// w.setPsdk_name(cachedBasicInfo.getProductName() != null ? cachedBasicInfo.getProductName() : "");
// w.setPsdk_sn(cachedBasicInfo.getDeviceSN() != null ? cachedBasicInfo.getDeviceSN() : "");
// w.setPsdk_type(5); // Speaker type
// w.setPsdk_version(cachedBasicInfo.getVersion() != null ? cachedBasicInfo.getVersion() : "");
//
// // Speaker数据
// PsdkWidgetValuesReport.SpeakerData speaker = new PsdkWidgetValuesReport.SpeakerData();
// speaker.setPlay_file_md5("");
// speaker.setPlay_file_name("");
// speaker.setPlay_mode(1);
// speaker.setPlay_volume(30);
// speaker.setSystem_state(0);
// speaker.setWork_mode(1);
//
// // 从widgetInfo中尝试获取实际的widget值
// if (cachedWidgetInfo != null) {
// if (cachedWidgetInfo.getMainInterfaceWidgetList() != null) {
// for (Object widget : cachedWidgetInfo.getMainInterfaceWidgetList()) {
// // 根据widget类型设置speaker字段
// }
// }
// }
// w.setSpeaker(speaker);
// w.setValues(new ArrayList<>());
// widgetList.add(w);
// }
//
// data.setPsdk_widget_values(widgetList);
// report.setData(data);
//
// String json = gson.toJson(report);
// LogUtil.log(TAG, "上报psdk_widget_values: " + json);
//
// MqttMessage msg = new MqttMessage(json.getBytes("UTF-8"));
// msg.setQos(0);
// MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, msg);
// } catch (Exception e) {
// LogUtil.log(TAG, "发送psdk_widget_values异常: " + e.getMessage());
// e.printStackTrace();
// }
// }
/**
* 发送负载浮窗文本到MQTT
*/
private void sendFloatingWindowText(String value) {
try {
if (MqttManager.getInstance().mqttAndroidClient == null || !MqttManager.getInstance().mqttAndroidClient.isConnected()) {
LogUtil.log(TAG, "MQTT未连接跳过浮窗文本上报");
return;
}
java.util.Map<String, Object> report = new java.util.LinkedHashMap<>();
report.put("bid", UUID.randomUUID().toString());
report.put("tid", UUID.randomUUID().toString());
report.put("timestamp", System.currentTimeMillis());
report.put("method", "psdk_floating_window_text_jdw");
report.put("gateway", Movement.getInstance().getSerialNumber());
java.util.Map<String, Object> data = new java.util.LinkedHashMap<>();
data.put("psdk_index", 2);
data.put("value", value);
report.put("data", data);
String json = new Gson().toJson(report);
LogUtil.log(TAG, "发送浮窗文本: " + json);
org.eclipse.paho.client.mqttv3.MqttMessage msg = new org.eclipse.paho.client.mqttv3.MqttMessage(json.getBytes("UTF-8"));
msg.setQos(1);
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, msg);
} catch (Exception e) {
LogUtil.log(TAG, "发送浮窗文本异常: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@ -37,9 +37,16 @@ import okhttp3.Request;
import okhttp3.Response;
public class SpeakerManager extends BaseManager {
private boolean issetok=false;
private volatile boolean issetok=false;
private android.os.Handler retryHandler;
private Runnable psdkIndexRetry;
public static int psdkindex=0;
private static final int MAX_MEGAPHONE_RETRIES = 10;
private int megaphoneRetryCount = 0;
private SpeakerManager() {
}
private static class SpeakerHolder {
@ -52,36 +59,215 @@ public class SpeakerManager extends BaseManager {
private XTtsPcmGenerator ttsGenerator;
public void initMegaphoneInfo() {
MegaphoneManager.getInstance().addMegaphoneInfoListener(new MegaphoneInfoListener() {
@Override
public void onUpdateMegaphoneInfo(MegaphoneInfo megaphoneInfo) {
if (megaphoneInfo != null) {
MegaphoneStatus megaphoneStatus = megaphoneInfo.getStatus();
//LogUtil.log(TAG,"喊话器"+megaphoneStatus);
// 当喊话器状态为"已连接"status=2再设置位置
if (megaphoneStatus == MegaphoneStatus.IDLE && issetok==false) {
MegaphoneManager.getInstance().setMegaphoneIndex(MegaphoneIndex.PORT_2, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
issetok=true;
LogUtil.log(TAG,"喊话器位置设置成功");
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG,"喊话器位置设置失败:"+getIDJIErrorMsg(error));
}
});
addMegaphoneInfoListener();
// 延迟10秒再触发查询等待喊话器硬件完全初始化
if (!issetok) {
if (retryHandler == null) {
retryHandler = new android.os.Handler(android.os.Looper.getMainLooper());
}
retryHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!issetok) {
startMegaphoneIndexQuery();
}
}
}, 10000);
}
}
/**
* 主动查询并设置喊话器位置失败后3秒重试成功后不再重试
*/
public void startMegaphoneIndexQuery() {
MegaphoneManager.getInstance().getMegaphoneIndex(new CommonCallbacks.CompletionCallbackWithParam<MegaphoneIndex>() {
@Override
public void onSuccess(MegaphoneIndex megaphoneIndex) {
LogUtil.log(TAG,"喊话器位置"+megaphoneIndex);
if (megaphoneIndex == MegaphoneIndex.UNKNOWN || megaphoneIndex == MegaphoneIndex.PORTSIDE) {
LogUtil.log(TAG, "喊话器位置查询返回不确定(" + megaphoneIndex + "),直接进入重试");
if (!issetok) {
retryMegaphoneIndex();
}
return;
}
MegaphoneIndex targetIndex = megaphoneIndex;
MegaphoneManager.getInstance().setMegaphoneIndex(targetIndex, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
issetok=true;
LogUtil.log(TAG,"喊话器位置设置成功");
getMegaphoneIndex();
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG,"喊话器位置设置失败:"+getIDJIErrorMsg(error));
if (!issetok) {
retryMegaphoneIndex();
}
}
});
}
@Override
public void onFailure(@NonNull IDJIError idjiError) {
LogUtil.log(TAG, "startMegaphoneIndexQuery失败: " + getIDJIErrorMsg(idjiError) + "3秒后重试");
if (!issetok) {
retryMegaphoneIndex();
}
}
});
}
private void retryMegaphoneIndex() {
if (megaphoneRetryCount >= MAX_MEGAPHONE_RETRIES) {
LogUtil.log(TAG, "喊话器位置设置已达到最大重试次数(" + MAX_MEGAPHONE_RETRIES + "),尝试盲设 PORT_2 / PORT_3");
blindSetMegaphoneIndex();
return;
}
if (retryHandler == null) {
retryHandler = new android.os.Handler(android.os.Looper.getMainLooper());
}
retryHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!issetok) {
megaphoneRetryCount++;
LogUtil.log(TAG, "喊话器位置设置重试第 " + megaphoneRetryCount + "");
startMegaphoneIndexQuery();
}
}
}, 3000);
}
private boolean isBlindSetting = false;
/**
* 10次查询重试都失败后依次盲试 PORT_2 PORT_3只要有一个成功即可
*/
private void blindSetMegaphoneIndex() {
if (issetok || isBlindSetting) return;
isBlindSetting = true;
MegaphoneIndex[] candidates = {MegaphoneIndex.PORT_2, MegaphoneIndex.PORT_3};
blindSetWithIndex(candidates, 0);
}
private void blindSetWithIndex(MegaphoneIndex[] candidates, int index) {
if (index >= candidates.length || issetok) {
if (!issetok) {
LogUtil.log(TAG, "盲设喊话器位置全部失败PORT_2 和 PORT_3 均不可用");
}
return;
}
final MegaphoneIndex target = candidates[index];
final int targetIdx = (target == MegaphoneIndex.PORT_2) ? 2 : 3;
LogUtil.log(TAG, "盲设喊话器位置: " + target);
MegaphoneManager.getInstance().setMegaphoneIndex(target, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
issetok = true;
psdkindex = targetIdx;
Movement.getInstance().setPsdk_index(String.valueOf(targetIdx));
LogUtil.log(TAG, "盲设喊话器位置成功: " + target + "psdk_index=" + targetIdx);
}
@Override
public void onFailure(@NonNull IDJIError error) {
psdkindex=0;
LogUtil.log(TAG, "盲设 " + target + " 失败,尝试下一个");
blindSetWithIndex(candidates, index + 1);
}
});
}
//喊话器位置
public void getMegaphoneIndex(){
MegaphoneManager.getInstance().getMegaphoneIndex(new CommonCallbacks.CompletionCallbackWithParam<MegaphoneIndex>() {
@Override
public void onSuccess(MegaphoneIndex megaphoneIndex) {
int index = switch (megaphoneIndex) {
case PORT_1 -> 1;
case PORT_2 -> 2;
case PORT_3 -> 3;
case PORTSIDE, UNKNOWN -> 0; // PORTSIDE和UNKNOWN都视为不确定进入盲设
default -> 0;
};
if (index != 0) {
Movement.getInstance().setPsdk_index(String.valueOf(index));
issetok = true;
LogUtil.log(TAG, "psdk_index已设置并保持: " + index);
psdkindex=index;
} else {
LogUtil.log(TAG, "getMegaphoneIndex回调位置不确定(" + megaphoneIndex + "),进入盲设流程");
blindSetMegaphoneIndex();
}
}
@Override
public void onFailure(@NonNull IDJIError idjiError) {
LogUtil.log(TAG, "getMegaphoneIndex失败: " + getIDJIErrorMsg(idjiError) + "10秒后重试");
if (retryHandler == null) {
retryHandler = new android.os.Handler(android.os.Looper.getMainLooper());
}
retryHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!issetok) {
getMegaphoneIndex();
}
}
}, 10000);
}
});
}
//喊话器基础信息
public void addMegaphoneInfoListener(){
MegaphoneManager.getInstance().addMegaphoneInfoListener(new MegaphoneInfoListener() {
@Override
public void onUpdateMegaphoneInfo(MegaphoneInfo megaphoneInfo) {
if (megaphoneInfo == null) return;
//工作模式 音量 状态 播放模式
Movement.getInstance().setPlay_mode(String.valueOf(megaphoneInfo.getPlayMode().value));
Movement.getInstance().setPlay_volume(String.valueOf(megaphoneInfo.getVolume()));
Movement.getInstance().setWork_mode(String.valueOf(megaphoneInfo.getWorkMode().co_a));
Movement.getInstance().setSystem_state(String.valueOf(megaphoneInfo.getStatus().co_a));
MegaphoneStatus status = megaphoneInfo.getStatus();
if (status == MegaphoneStatus.PLAYING) {
LogUtil.log(TAG, "喊话器状态: 正在播放中");
Movement.getInstance().setStep_key("play");
Movement.getInstance().setSpeakerpercent(100);
Movement.getInstance().setTTS_status("success");
} else if (status == MegaphoneStatus.IDLE) {
LogUtil.log(TAG, "喊话器状态: 空闲");
speakerStop();//暂停上传
} else if (status == MegaphoneStatus.IN_TRANSMISSION) {
LogUtil.log(TAG, "喊话器状态: 数据传输");
Movement.getInstance().setStep_key("upload");
Movement.getInstance().setTTS_status("in_progress");
} else if (status == MegaphoneStatus.TTS_IN_CONVERSION) {
LogUtil.log(TAG, "喊话器状态: tTTS正在转换成语音中");
Movement.getInstance().setTTS_status("in_progress");
}
}
});
}
//
public void speakerAudioPlayStart(MessageDown message) {
sendMsg2Server(message);
//暂停播放
stop();
stopspeaker();
if (TextUtils.isEmpty(message.getData().getFile().getUrl())) {
sendFailMsg2Server(message, "喊话文件下载地址为空");
@ -133,61 +319,61 @@ public class SpeakerManager extends BaseManager {
null);
MegaphoneManager.getInstance().startPushingFileToMegaphone(fileInfo,
new CommonCallbacks.CompletionCallbackWithProgress<Integer>() {
private boolean isCallbackExecuted = false; // 确保回调只执行一次
private boolean hasTriggeredPlay = false;
@Override
public void onProgressUpdate(Integer integer) {
Movement.getInstance().setSpeakerpercent(integer);
LogUtil.log(TAG, "喊话器内容上传进度:" + integer + "%");
if (integer >= 99 && !hasTriggeredPlay) {
hasTriggeredPlay = true;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
Movement.getInstance().setStep_key("play");
Movement.getInstance().setTTS_status("success");
sendMsg2Server(message);
SpeakerProgressReporter.getInstance().sendNowAudioProgress(psdkindex);
sendEvent2Server("喊话器播放音频成功", 1);
LogUtil.log(TAG,"喊话器播放音频成功");
Synchronizedstatus.setSpeakrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
sendFailMsg2Server(message, "喊话器播放音频失败:" + getIDJIErrorMsg(error));
sendEvent2Server("喊话器播放音频失败:" + getIDJIErrorMsg(error), 2);
Synchronizedstatus.setSpeakrunning(false);
LogUtil.log(TAG,"喊话器播放音频失败"+ getIDJIErrorMsg(error));
}
});
}
}, 1000);
}
}
@Override
public void onSuccess() {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
LogUtil.log(TAG, "喊话器内容上传成功");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
Movement.getInstance().setTTS_status("ok");
sendEvent2Server("喊话器播放音频成功", 1);
LogUtil.log(TAG,"喊话器播放音频成功");
Synchronizedstatus.setSpeakrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
sendEvent2Server("喊话器播放音频失败:" + getIDJIErrorMsg(error), 2);
Synchronizedstatus.setSpeakrunning(false);
LogUtil.log(TAG,"喊话器播放音频失败"+ getIDJIErrorMsg(error));
}
});
}
}, 1000);
Synchronizedstatus.setSpeakrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
sendFailMsg2Server(message, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
Synchronizedstatus.setSpeakrunning(false);
}
});
} catch (Exception e) {
sendEvent2Server("喊话.pcm文件下载失败:" + e.toString(), 2);
Synchronizedstatus.setSpeakrunning(false);
}
}
}
@ -213,6 +399,22 @@ public class SpeakerManager extends BaseManager {
}
public void stopspeaker(){
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
Synchronizedstatus.setSpeaksetrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
Synchronizedstatus.setSpeaksetrunning(false);
}
});
}
public void speakerStop(MessageDown message) {
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
@Override
@ -232,7 +434,8 @@ public class SpeakerManager extends BaseManager {
}
});
}
public void stop(){
public void speakerStop() {
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
@ -246,9 +449,10 @@ public class SpeakerManager extends BaseManager {
SpeakerProgressReporter.getInstance().stopTTSReport();
}
});
}
public void speakerPlayModeSet(MessageDown message) {
MegaphoneManager.getInstance().setPlayMode(message.getData().getPlay_mode() == 0 ?
PlayMode.SINGLE : PlayMode.LOOP, new CommonCallbacks.CompletionCallback() {
@ -285,8 +489,8 @@ public class SpeakerManager extends BaseManager {
private String text;
public void speakerTTSPlayStart(MessageDown message, int type) {
//暂停播放
stop();
stopspeaker();
LogUtil.log(TAG, "开始执行 speakerTTSPlayStart 方法type: " + type);
// 检查是否正在运行
@ -376,7 +580,7 @@ public class SpeakerManager extends BaseManager {
LogUtil.log(TAG, "开始上传文件到喊话器");
MegaphoneManager.getInstance().startPushingFileToMegaphone(fileInfo,
new CommonCallbacks.CompletionCallbackWithProgress<Integer>() {
private boolean isCallbackExecuted = false; // 确保回调只执行一次
@Override
public void onProgressUpdate(Integer integer) {
@ -388,11 +592,6 @@ public class SpeakerManager extends BaseManager {
@Override
public void onSuccess() {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
LogUtil.log(TAG, "喊话器内容上传成功");
new Handler().postDelayed(new Runnable() {
@ -403,8 +602,10 @@ public class SpeakerManager extends BaseManager {
@Override
public void onSuccess() {
sendMsg2Server(message);
Movement.getInstance().setTTS_status("ok");
Movement.getInstance().setTTS_status("success");
Movement.getInstance().setStep_key("play");
SpeakerProgressReporter.getInstance().sendNowTTSProgress(psdkindex);
sendEvent2Server("喊话器播放TTS音频成功", 1);
LogUtil.log(TAG,"喊话器播放TTS音频成功");
Synchronizedstatus.setSpeakTTSrunning(false);
@ -424,36 +625,37 @@ public class SpeakerManager extends BaseManager {
@Override
public void onFailure(@NonNull IDJIError error) {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
sendFailMsg2Server(message, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
LogUtil.log(TAG, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
Synchronizedstatus.setSpeakTTSrunning(false);
}
});
} else {
LogUtil.log(TAG, "合成失败PCM 文件为空");
sendFailMsg2Server(message, "合成失败PCM 文件为空");
Synchronizedstatus.setSpeakTTSrunning(false);
}
LogUtil.log(TAG, "设置 speakTTSrunning 为 false");
LogUtil.log(TAG, "speakerTTSPlayStart 方法执行完成");
Synchronizedstatus.setSpeakTTSrunning(false);
}
public void setindex(){
MegaphoneManager.getInstance().setMegaphoneIndex(MegaphoneIndex.PORT_2, new CommonCallbacks.CompletionCallback() {
MegaphoneIndex targetIndex = psdkindex == 2 ? MegaphoneIndex.PORT_2 :
psdkindex == 3 ? MegaphoneIndex.PORT_3 : MegaphoneIndex.PORT_2;
LogUtil.log(TAG, "setindex 根据 psdkindex=" + psdkindex + " 设置位置: " + targetIndex);
MegaphoneManager.getInstance().setMegaphoneIndex(targetIndex, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
issetok=true;
LogUtil.log(TAG,"喊话器位置设置成功");
issetok = true;
LogUtil.log(TAG, "喊话器位置设置成功");
getMegaphoneIndex();
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG,"喊话器位置设置失败:"+getIDJIErrorMsg(error));
LogUtil.log(TAG, "喊话器位置设置失败:" + getIDJIErrorMsg(error));
}
});
}

View File

@ -52,7 +52,7 @@ public class StreamManager extends BaseManager {
private final Handler streamRefreshHandler = new Handler(Looper.getMainLooper());
private Runnable streamRefreshRunnable = null;
private volatile boolean isStreamRefreshing = false;
private static final long STREAM_REFRESH_INTERVAL_MS = 5000;
private static final long STREAM_REFRESH_INTERVAL_MS = 8000;
private StreamManager() {
}
@ -112,10 +112,12 @@ public class StreamManager extends BaseManager {
// ========== 新增重置推流状态用于端口关闭后重启 ==========
public void resetStreamState() {
stopStreamRefreshTimer(); // 重置时也停止定时器
stopStreamRefreshTimer();
SimplePortScanner.getInstance().stopScan(); // 同时停止端口扫描
mainHandler.removeCallbacksAndMessages(null); // 清理所有待执行的回调
startLiveFailTimes = 0;
isLiveStreamAlreadyStart = false;
isStartingRTSP = false; // 重置并发标志
isStartingRTSP = false;
LogUtil.log(TAG, "推流状态已重置");
}
@ -201,9 +203,14 @@ public class StreamManager extends BaseManager {
}
private int startLiveFailTimes;
private boolean isLiveStreamAlreadyStart;
private boolean isStartingRTSP = false; // 防止并发调用
private volatile int startLiveFailTimes;
private volatile boolean isLiveStreamAlreadyStart;
private volatile boolean isStartingRTSP = false; // 防止并发调用
// 无限重试指数退避控制
private static final long RETRY_BASE_MS = 3000; // 初始 3
private static final long RETRY_MAX_MS = 30000; // 最大 30
private static final int LOG_INTERVAL = 10; // 10 次打一次日志
// 知眸测试
public void startLiveWithCustom() {
@ -247,16 +254,18 @@ public class StreamManager extends BaseManager {
@Override
public void onFailure(@NonNull IDJIError error) {
mainHandler.post(() -> {
LogUtil.log(TAG, "" + startLiveFailTimes + "开始推流失败:" + new Gson().toJson(error));
LogUtil.log(TAG, "" + startLiveFailTimes + "自定义推流失败:" + new Gson().toJson(error));
if (!isLiveStreamAlreadyStart) {
long retryDelay = Math.min(RETRY_BASE_MS * (1L << Math.min(startLiveFailTimes, 4)), RETRY_MAX_MS);
startLiveFailTimes++;
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
LogUtil.log(TAG, "自定义推流准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
}
isStartingRTSP = false;
isLiveStreamAlreadyStart = false;
mainHandler.postDelayed(() -> {
streamExecutor.execute(() -> {
if (startLiveFailTimes < 10) {
startLiveFailTimes++;
startLiveWithCustom();
}
});
}, 3000);
streamExecutor.execute(() -> startLiveWithCustom());
}, retryDelay);
}
});
}
@ -321,8 +330,9 @@ public class StreamManager extends BaseManager {
streamExecutor.execute(() -> {
isStartingRTSP = true;
// 重置失败计数每次新尝试时
// 每次新推流尝试清零所有状态标志修复 Bug 1, 2, 5
if (startLiveFailTimes == 0) {
isLiveStreamAlreadyStart = false;
LogUtil.log(TAG, "========== 开始 RTSP 推流流程 ==========");
}
@ -345,8 +355,6 @@ public class StreamManager extends BaseManager {
LogUtil.log(TAG, "RTSP 推流已在运行,无需重复启动");
isLiveStreamAlreadyStart = true;
isStartingRTSP = false;
startStreamRefreshTimer(); // 推流在运行就启动定时器
SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行
return;
}
@ -362,16 +370,11 @@ public class StreamManager extends BaseManager {
mainHandler.postDelayed(() -> {
streamExecutor.execute(() -> {
if (startLiveFailTimes < 3) {
startLiveFailTimes++;
startLiveFailTimes++;
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
LogUtil.log(TAG, "相机流未准备好,第" + startLiveFailTimes + "次重试");
isStartingRTSP = false; // 先重置标志让重试调用能正常进入
startLiveWithRTSP();
} else {
LogUtil.log(TAG, "相机流未准备好,重试次数已达上限,强制启动");
forceStartLive();
isStartingRTSP = false; // 重置并发标志
}
startLiveWithRTSP();
});
}, 2000);
isStartingRTSP = false; // 释放锁让重试能正常进入
@ -386,7 +389,7 @@ public class StreamManager extends BaseManager {
if (rtspUser == null || rtspPort == null || rtspPass == null ||
rtspUser.isEmpty() || rtspPort.isEmpty() || rtspPass.isEmpty()) {
LogUtil.log(TAG, "RTSP 配置参数有误user=" + rtspUser + ", port=" + rtspPort);
isStartingRTSP = false; // 重置并发标志
isStartingRTSP = false;
return;
}
@ -430,8 +433,7 @@ public class StreamManager extends BaseManager {
if (finalLiveStreamManager.isStreaming()) {
LogUtil.log(TAG, "推流已在运行,跳过启动");
isLiveStreamAlreadyStart = true;
isStartingRTSP = false; // 重置并发标志
SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行
isStartingRTSP = false;
return;
}
@ -445,63 +447,6 @@ public class StreamManager extends BaseManager {
private void forceStartLive() {
streamExecutor.execute(() -> {
if (PreferenceUtils.getInstance().getRtspUserName() != null &&
PreferenceUtils.getInstance().getRtspPort() != null &&
PreferenceUtils.getInstance().getRtspPassWord() != null) {
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
// 直接设置参数并启动不检查相机流状态
mainHandler.post(() -> {
LiveStreamSettings.Builder streamSettingBuilder = new LiveStreamSettings.Builder();
LiveStreamSettings streamSettings = streamSettingBuilder.setLiveStreamType(LiveStreamType.RTSP)
.setRtspSettings(new RtspSettings.Builder().setPassWord(PreferenceUtils.getInstance().getRtspPassWord()).
setPort(Integer.parseInt(PreferenceUtils.getInstance().getRtspPort())).
setUserName(PreferenceUtils.getInstance().getRtspUserName()).build()).build();
liveStreamManager.setLiveStreamSettings(streamSettings);
liveStreamManager.setCameraIndex(isliveindex == 1 ? ComponentIndexType.PORT_1 : ComponentIndexType.FPV);
liveStreamManager.setLiveStreamQuality(StreamQuality.FULL_HD);
liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
// 直接启动
liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
mainHandler.post(() -> {
LogUtil.log(TAG, "强制启动 RTSP 推流成功");
// 统一刷新视频流
MainActivity mainActivity = MainActivity.Companion.getInstance();
if (mainActivity != null) {
mainActivity.smartRefreshVideoStream();
}
isLiveStreamAlreadyStart = true;
isStartingRTSP = false; // 重置并发标志
startStreamRefreshTimer(); // 强制启动成功也启动定时器
SimplePortScanner.getInstance().startScan();
});
}
@Override
public void onFailure(@NonNull IDJIError error) {
mainHandler.post(() -> {
LogUtil.log(TAG, "强制启动 RTSP 推流失败:" + new Gson().toJson(error));
isStartingRTSP = false; // 重置并发标志
// 失败后也尝试重置状态并重启
resetStreamState();
});
}
});
});
} else {
LogUtil.log(TAG, "RTSP 配置参数有误,无法强制启动");
isStartingRTSP = false; // 重置并发标志
}
});
}
// ========== 新增实际启动流的私有方法确保在子线程执行 ==========
private void doStartLiveWithRTSP(ILiveStreamManager liveStreamManager, boolean isRestart) {
// 启动前再次检查状态
@ -509,8 +454,6 @@ public class StreamManager extends BaseManager {
LogUtil.log(TAG, "推流已在运行,跳过启动 (isRestart=" + isRestart + ")");
isLiveStreamAlreadyStart = true;
isStartingRTSP = false; // 重置并发标志
startStreamRefreshTimer();
SimplePortScanner.getInstance().startScan(); // 确保端口扫描在运行
return;
}
@ -540,34 +483,25 @@ public class StreamManager extends BaseManager {
LogUtil.log(TAG, detailedError);
LogUtil.log(TAG, "完整错误:" + new Gson().toJson(error));
// 错误类型判断
if (error.hint() != null) {
if (error.hint().contains("CAMERA")) {
LogUtil.log(TAG, "[诊断] 相机相关错误:可能是相机模式不对/相机被占用/SD 卡异常");
} else if (error.hint().contains("LIVE")) {
LogUtil.log(TAG, "[诊断] 推流相关错误:可能是推流配置问题/端口被占用");
} else if (error.hint().contains("PORT") || error.hint().contains("port")) {
LogUtil.log(TAG, "[诊断] 端口相关错误:请检查 RTSP 端口是否被占用");
}
}
if (!isLiveStreamAlreadyStart) {
// 延时 3 秒后重试重试也提交到线程池
// 计算指数退避间隔3s 6s 12s 24s 30s(封顶)
long retryDelay = Math.min(RETRY_BASE_MS * (1L << Math.min(startLiveFailTimes, 4)), RETRY_MAX_MS);
startLiveFailTimes++;
if (startLiveFailTimes <= 3 || startLiveFailTimes % LOG_INTERVAL == 0) {
LogUtil.log(TAG, "准备第" + startLiveFailTimes + "次重试,间隔 " + retryDelay + "ms");
}
// 重置所有状态让下次重试是干净的
isStartingRTSP = false;
isLiveStreamAlreadyStart = false;
stopstream(); // 先关再开避免端口占用
// 指数退避重试
mainHandler.postDelayed(() -> {
streamExecutor.execute(() -> {
if (startLiveFailTimes < 10) {
startLiveFailTimes++;
LogUtil.log(TAG, "准备第" + startLiveFailTimes + "次重试 RTSP 推流");
startLiveWithRTSP();
} else {
LogUtil.log(TAG, "RTSP 推流重试次数已达上限 (10 次),放弃");
LogUtil.log(TAG, "========== RTSP 推流失败 ==========");
isStartingRTSP = false; // 重置并发标志
}
});
}, 3000);
startLiveWithRTSP();
}, retryDelay);
} else {
isStartingRTSP = false; // 重置并发标志
isStartingRTSP = false;
}
});
}

View File

@ -385,8 +385,11 @@ public class TakeOffToPointManager extends BaseManager {
PreferenceUtils.getInstance().setTriggerToAlternatePoint(false);
// 重置开舱门标志避免上次任务未正常落地导致本次无法开舱
FlightManager.getInstance().resetOpenCabinDoorState();
DockOpenManager.getInstance().resetState();
// 只在首次尝试时重置避免重试时反复触发
if (startMissionFailTimes == 0) {
FlightManager.getInstance().resetOpenCabinDoorState();
DockOpenManager.getInstance().resetState();
}
PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id());
CommonCallbacks.CompletionCallback callback = new CommonCallbacks.CompletionCallback() {

View File

@ -4,7 +4,9 @@ import android.os.Looper;
import com.aros.apron.constant.AMSConfig;
import com.aros.apron.entity.ArucoMarker;
import com.aros.apron.entity.Movement;
import com.aros.apron.entity.Synchronizedstatus;
import com.aros.apron.manager.AlternateLandingManager;
import com.aros.apron.manager.FlightManager;
import com.aros.apron.tools.DroneHelper;
import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.PIDControl;
@ -66,6 +68,21 @@ public class Aprondown {
isStartAruco = startAruco;
}
/**
* 切换相机时停止检测
*/
public void stopDetection() {
if (lastFuture != null && !lastFuture.isDone()) {
lastFuture.cancel(true);
}
isStartAruco = false;
startFastStick = false;
arucoNotFoundTag = false;
isHeightStableMonitoring = false;
frameCounter = 0;
LogUtil.log(TAG_LOG, "【停止检测】下视相机模块已停止");
}
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Future<?> lastFuture = null;
private String TAG_LOG = getClass().getSimpleName();
@ -131,12 +148,50 @@ public class Aprondown {
pidControlX.reset();
pidControlY.reset();
}
public boolean startFastStick;
public boolean canLanding;
// ========== 核心修改主入口2帧一处理 + 预处理回退 ==========
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<Mat> corners = new ArrayList<>();
DetectorParameters parameters = new DetectorParameters();
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
detector.detectMarkers(grayImgMat, corners, ids);
if (!ids.empty()) {
int[] idArray = ids.toArray();
double relativeHeight = Movement.getInstance().getElevation();
boolean has6 = false;
for (int id : idArray) {
if (id == 6) {
has6 = true;
break;
}
}
if (has6 && relativeHeight < 20) {
LogUtil.log(TAG_LOG, "【强制刹停】高度=" + relativeHeight + "检测到6号满足条件");
FlightManager.forceTriggerDetection = true;
} else {
LogUtil.log(TAG_LOG, "【强制刹停】不满足条件:高度=" + relativeHeight + ", 6号=" + has6);
}
}
yuvMat.release();
grayImgMat.release();
ids.release();
}
public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) {
if (Synchronizedstatus.isAprongim()) return;
isTriggerSuccess = true;
Movement.getInstance().setVirtualStickEnableReason(2);
@ -149,12 +204,12 @@ public class Aprondown {
return;
}
// ========== 关键2帧一处理0处理-1跳过-2处理-3跳过 ==========
int currentFrame = frameCounter++;
boolean shouldProcess = (currentFrame % 2 == 0); // 偶数帧处理奇数帧跳过
if (!shouldProcess) {
// 关键跳过的帧啥也不干直接return让飞机自己飘
// 让飞机自己飘
LogUtil.log(TAG_LOG, "【跳过帧】" + currentFrame + " 让飞机自稳");
return;
}
@ -168,7 +223,7 @@ public class Aprondown {
lastFuture.cancel(true);
}
// 动态偏移原有
// 动态偏移
int ultHeight = Movement.getInstance().getUltrasonicHeight();
if (ultHeight >= 30) { LENS_OFFSET_X = 0; LENS_OFFSET_Y = 0; }
else if (ultHeight >= 20) { LENS_OFFSET_X = 0; LENS_OFFSET_Y = 0; }
@ -176,7 +231,7 @@ public class Aprondown {
else if (ultHeight >= 5) { LENS_OFFSET_X = 30; LENS_OFFSET_Y = 20; }
else { LENS_OFFSET_X = 55; LENS_OFFSET_Y = 35; }
// ========== 核心逻辑先预处理检测失败回退原图 ==========
lastFuture = executor.schedule(new Runnable() {
@Override
public void run() {
@ -195,12 +250,12 @@ public class Aprondown {
DetectorParameters parameters = new DetectorParameters();
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
// ========== 关键修改第1次预处理图检测 ==========
Mat processedMat = fixedPreprocess(grayImgMat);
detector.detectMarkers(processedMat, corners, ids);
LogUtil.log(TAG_LOG, "预处理图检测: " + (ids.empty() ? "未检出" : "成功,数量=" + corners.size()));
// ========== 关键修改第2次如果失败回退原图检测 ==========
if (ids.empty()) {
LogUtil.log(TAG_LOG, "预处理失败,回退原图检测");
corners.clear(); // 清空之前的结果
@ -208,17 +263,17 @@ public class Aprondown {
LogUtil.log(TAG_LOG, "原图检测: " + (ids.empty() ? "仍未检出" : "成功,数量=" + corners.size()));
}
// 释放预处理图必须避免内存泄漏
processedMat.release();
// 只保留6号码
ids = keepOnly6(ids, corners);
boolean marker6Found = false;
int ultrasonicHeight = Movement.getInstance().getUltrasonicHeight();
// ========== 新增高度稳定性监测 ==========
if (ultrasonicHeight > 0) {
if (!isHeightStableMonitoring) {
// 开始监测

View File

@ -6,7 +6,9 @@ import android.os.Looper;
import com.aros.apron.constant.AMSConfig;
import com.aros.apron.entity.ArucoMarker;
import com.aros.apron.entity.Movement;
import com.aros.apron.entity.Synchronizedstatus;
import com.aros.apron.manager.AlternateLandingManager;
import com.aros.apron.manager.FlightManager;
import com.aros.apron.tools.DroneHelper;
import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.PIDControl;
@ -115,6 +117,22 @@ public class Aprongim {
public void setStartAruco(boolean startAruco) {
isStartAruco = startAruco;
}
/**
* 切换相机时停止检测
*/
public void stopDetection() {
if (lastFuture != null && !lastFuture.isDone()) {
lastFuture.cancel(true);
}
isStartAruco = false;
startFastStick = false;
arucoNotFoundTag = false;
isHeightStableMonitoring = false;
frameCounter = 0;
LogUtil.log(TAG_LOG, "【停止检测】云台相机模块已停止");
}
public void setCanLanding(boolean v) {
startTime = 0;
endTime = 0;
@ -155,8 +173,50 @@ public class Aprongim {
return 0.0;
}
}
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<Mat> 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 count1to4 = 0;
for (int id : idArray) {
if (id == 6) {
has6 = true;
} else if (id >= 1 && id <= 4) {
count1to4++;
}
}
if (has6 && count1to4 >= 3 && ultHeight <= 60) {
LogUtil.log(TAG_LOG, "【强制刹停】满足条件6号 + 1-4号中" + count1to4 + "个 高度=" + ultHeight + "dm");
FlightManager.forceTriggerDetection = true;
} else {
LogUtil.log(TAG_LOG, "【强制刹停】不满足条件6号=" + has6 + ", 1-4号数量=" + count1to4 + ", 高度=" + ultHeight + "dm(阈值80)");
}
}
yuvMat.release();
grayImgMat.release();
ids.release();
}
// ========== 核心主入口3帧一处理 ==========
public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) {
if (!Synchronizedstatus.isAprongim()) return;
isTriggerSuccess = true;
Movement.getInstance().setVirtualStickEnableReason(2);
@ -250,7 +310,7 @@ public class Aprongim {
// 高度有明显变化重置计时器
lastHeightCheckTime = currentTime;
lastUltrasonicHeight = ultHeight;
// LogUtil.log(TAG_LOG, "高度变化: " + heightChange + " 分米,重置计时器");
// LogUtil.log(TAG_LOG, "高度变化: " + heightChange + " 分米,重置计时器");
} else if (currentTime - lastHeightCheckTime > HEIGHT_STABLE_THRESHOLD) {
// 45秒内高度没有变动执行拉高切换
LogUtil.log(TAG_LOG, "45秒内高度未变动执行拉高切换到下视觉");

View File

@ -33,9 +33,11 @@ public class Apronmixvalue {
public void switchthemmodefpv(){
Synchronizedstatus.setAprongim(false);
Aprongim.getInstance().stopDetection();
}
public void switchthemmodeGime(){
Synchronizedstatus.setAprongim(true);
Aprondown.getInstance().stopDetection();
}
public boolean isIsaglinetrue() {

View File

@ -1,14 +1,16 @@
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.calib3d.Calib3d;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
@ -17,11 +19,13 @@ 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;
@ -93,6 +97,19 @@ public class ApronArucoDetect {
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; }
@ -100,6 +117,8 @@ public class ApronArucoDetect {
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; }
@ -174,10 +193,12 @@ public class ApronArucoDetect {
try {
Mat yuvMat = new Mat(height + height / 2, width, CvType.CV_8UC1);
yuvMat.put(0, 0, data);
Mat rgbMat = new Mat();
Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2BGR_I420);
Mat grayImgMat = new Mat();
Imgproc.cvtColor(rgbMat, grayImgMat, Imgproc.COLOR_BGR2GRAY);
// ========== 关键修改直接用Y通道跳过RGB转换 ==========
// YUV420的Y通道就是灰度图质量比YUVBGRGRAY高很多
// BGR转换需要插值UV通道在低分辨率FPV上损失细节
Mat grayImgMat = yuvMat.submat(0, height, 0, width);
grayImgMat = grayImgMat.clone(); // 克隆避免和yuvMat共享数据
MatOfInt ids = new MatOfInt();
List<Mat> corners = new ArrayList<>();
@ -186,21 +207,67 @@ public class ApronArucoDetect {
DetectorParameters parameters = new DetectorParameters();
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
// ========== 关键修改第1次预处理图检测 ==========
Mat processedMat = fixedPreprocess(grayImgMat);
detector.detectMarkers(processedMat, corners, ids);
LogUtil.log(TAG_LOG, "预处理图检测: " + (ids.empty() ? "未检出" : "成功,数量=" + corners.size()));
// ========== 关键修改计算亮度决定预处理策略 ==========
Scalar meanValue = Core.mean(grayImgMat);
double currentBrightness = meanValue.val[0];
// ========== 关键修改第2次如果失败回退原图检测 ==========
if (ids.empty()) {
// ========== 关键修改第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(); // 清空之前的结果
corners.clear();
detector.detectMarkers(grayImgMat, corners, ids);
LogUtil.log(TAG_LOG, "原图检测: " + (ids.empty() ? "仍未检出" : "成功,数量=" + corners.size()));
}
// 释放预处理图必须避免内存泄漏
processedMat.release();
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号码
ids = keepOnly6(ids, corners);
@ -273,9 +340,9 @@ public class ApronArucoDetect {
double pixelWidth = calculateDistance(points[0], points[1]);
double centerX = (points[0].x + points[1].x + points[2].x + points[3].x) / 4.0 + LENS_OFFSET_X;
double errX = Math.abs(centerX - rgbMat.width() / 2.0);
double errX = Math.abs(centerX - width / 2.0);
double centerY = (points[0].y + points[1].y + points[2].y + points[3].y) / 4.0 + LENS_OFFSET_Y;
double errY = Math.abs(centerY - rgbMat.height() / 2.0);
double errY = Math.abs(centerY - height / 2.0);
LogUtil.log(TAG_LOG, "像素" + (int) pixelWidth +
" errX=" + (int) errX + " errY=" + (int) errY + " (含偏移" + LENS_OFFSET_X + ")");
@ -318,13 +385,19 @@ public class ApronArucoDetect {
// 原有修正逻辑
if (marker6Found) {
moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), rgbMat.width(), rgbMat.height());
moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), width, height);
}
dropTimesTag = true;
} else {
// 原有丢失处理
LogUtil.log(TAG_LOG, "找不到了二维码");
// 新增识别失败截图保存
if (saveFailScreenshot) {
saveFailScreenshot(grayImgMat, width, height);
}
if (!arucoNotFoundTag) {
startTime = System.currentTimeMillis();
arucoNotFoundTag = true;
@ -359,7 +432,6 @@ public class ApronArucoDetect {
// 释放资源
grayImgMat.release();
rgbMat.release();
yuvMat.release();
ids.release();
@ -395,13 +467,108 @@ public class ApronArucoDetect {
return new MatOfInt(newIds);
}
private Mat fixedPreprocess(Mat src) {
/**
* 识别失败时保存原图截图到外部存储
*/
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号 + 相对高度小于20才触发刹停
*/
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<Mat> corners = new ArrayList<>();
DetectorParameters parameters = new DetectorParameters();
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
detector.detectMarkers(grayImgMat, corners, ids);
if (!ids.empty()) {
int[] idArray = ids.toArray();
double relativeHeight = Movement.getInstance().getElevation();
boolean has6 = false;
for (int id : idArray) {
if (id == 6) {
has6 = true;
break;
}
}
if (has6 && relativeHeight < 20) {
LogUtil.log(TAG_LOG, "【强制刹停】高度=" + relativeHeight + "检测到6号满足条件");
FlightManager.forceTriggerDetection = true;
} else {
LogUtil.log(TAG_LOG, "【强制刹停】不满足条件:高度=" + relativeHeight + ", 6号=" + has6);
}
}
yuvMat.release();
grayImgMat.release();
ids.release();
}
private Mat fixedPreprocess(Mat src, double meanBrightness) {
Mat result = new Mat();
try {
// 新增判断亮度区分白天/夜景
Scalar meanValue = Core.mean(src);
double meanBrightness = meanValue.val[0];
if (meanBrightness < 80) { // 夜景模式FPV晚上通常<80
LogUtil.log(TAG_LOG, "【夜景预处理】亮度=" + (int)meanBrightness);
@ -442,34 +609,58 @@ public class ApronArucoDetect {
return result;
}
// 原逻辑白天模式亮度>=80
// ========== 关键修改白天模式分亮度区间处理 ==========
LogUtil.log(TAG_LOG, "【白天预处理】亮度=" + (int)meanBrightness);
// Step 1: 双边滤波保边缘降噪
// Step 1: 双边滤波保边缘降噪 所有白天场景通用
Mat filtered = new Mat();
Imgproc.bilateralFilter(src, filtered, 9, 75, 75);
// Step 2: CLAHE 局部对比度增强
Mat clahe = new Mat();
Imgproc.createCLAHE(4.0, new Size(8, 8)).apply(filtered, clahe);
Mat clahe;
double amount;
if (meanBrightness > 130) {
// 强光模式亮度>130大幅降低CLAHE避免眩光被增强成伪marker
LogUtil.log(TAG_LOG, "【强光处理】亮度过高降低CLAHE增益跳过锐化");
clahe = new Mat();
// clipLimit降到1.5默认4.0tileSize加大到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反锐化掩膜
Mat blurred = new Mat();
Imgproc.GaussianBlur(clahe, blurred, new Size(0, 0), 2.0);
// 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);
Mat detail = new Mat();
Core.subtract(clahe, blurred, detail);
double amount = 1.2; // 白天可以用1.2
Core.multiply(detail, new Scalar(amount), detail);
Core.add(clahe, detail, result);
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);
blurred.release();
detail.release();
clahe.release();
LogUtil.log(TAG_LOG, "白天处理完成:双边滤波+CLAHE+UnsharpMask(amount=" + amount + ")");
@ -497,6 +688,13 @@ public class ApronArucoDetect {
isHeightStableMonitoring = false;
lastHeightCheckTime = 0;
lastUltrasonicHeight = 0;
// 新增重置强光回退状态
undecodableFrameCount = 0;
forceSkipPreprocess = false;
skipPreprocessFrameCount = 0;
// 新增重置截图保存状态
saveFailScreenshot = false;
failScreenshotIndex = 0;
}

View File

@ -1,12 +1,15 @@
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.CvType;
import org.opencv.core.Mat;
@ -43,8 +46,6 @@ public class ApronArucoDetectPort {
private static final float SLOW_LAND_SPEED = -0.3f;
private static final float SLOW_SUPER_SPEED = -0.15f;
private static final int PIXEL_TRIGGER = 360;
private static final int TRIGGER_FRAME_THRESHOLD = 2;
// ========== 原有状态 ==========
private boolean isTriggerSuccess;
private boolean arucoNotFoundTag = false;
@ -64,7 +65,6 @@ public class ApronArucoDetectPort {
public PIDControl pidControlX = null;
public PIDControl pidControlY = null;
public int checkThrowingErrorsTimes;
private int consecutiveTriggerCount = 0;
private int frameCounter = 0;
// ========== 新增阶段控制标志 ==========
@ -73,9 +73,9 @@ public class ApronArucoDetectPort {
// 当前激活的降落模式0=旋转阶段(6号), 1=小码, 2=1-4大码, 3=6号纯下降
private int currentLandingMode = 0;
// ========== 新增连续降落条件判断 ==========
// 记录上一帧满足的降落条件类型0=, 1=单码精准降落(15/17号), 2=几何中心降落
private int lastLandingCondition = 0;
// ========== 新增6个独立分支计数器 ==========
private int[] landingCounters = new int[22]; // ID作为索引
private int counterRound = 0;
// ========== 新增高度稳定性监测 ==========
private long lastHeightCheckTime = 0;
@ -177,19 +177,19 @@ public class ApronArucoDetectPort {
if(isDoublePayload){
if (ultHeight <=5)
{
LENS_OFFSET_X=-130;
LENS_OFFSET_X=-180;
LENS_OFFSET_Y=120;
}else if(ultHeight<10){
LENS_OFFSET_X=-90;
LENS_OFFSET_X=-120;
LENS_OFFSET_Y=80;
} else if (ultHeight<20) {
LENS_OFFSET_X=-70;
LENS_OFFSET_X=-80;
LENS_OFFSET_Y=60;
}
}else{
if (ultHeight <=5)
{
LENS_OFFSET_X=120;
LENS_OFFSET_X=100;
LENS_OFFSET_Y = 100;
}else if(ultHeight<10){
LENS_OFFSET_X=80;
@ -207,10 +207,9 @@ public class ApronArucoDetectPort {
// ========== 图像预处理原有 ==========
Mat yuvMat = new Mat(height + height / 2, width, CvType.CV_8UC1);
yuvMat.put(0, 0, data);
Mat rgbMat = new Mat();
Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2BGR_I420);
Mat grayImgMat = new Mat();
Imgproc.cvtColor(rgbMat, grayImgMat, Imgproc.COLOR_BGR2GRAY);
// ========== 关键修改直接用Y通道跳过RGB转换 ==========
Mat grayImgMat = yuvMat.submat(0, height, 0, width);
grayImgMat = grayImgMat.clone();
MatOfInt ids = new MatOfInt();
List<Mat> corners = new ArrayList<>();
@ -317,24 +316,24 @@ public class ApronArucoDetectPort {
// 只要看到6号且还没对准就只用6号旋转不进入其他任何阶段
if (!bigMarker6.isEmpty() && !isYawAligned) {
currentLandingMode = 0;
processMarker6YawOnly(bigMarker6.get(0), rgbMat.width(), rgbMat.height(), ultHeight);
processMarker6YawOnly(bigMarker6.get(0), width, height, ultHeight);
}
// ========== 关键只有旋转完成后(isYawAligned=true)才识别其他码 ==========
else if (isYawAligned) {
// 优先级1小码模式高度<25dm且3个码
if (!smallMarkers.isEmpty() && ultHeight < 25) {
currentLandingMode = 1;
processSmallMarkers(smallMarkers, rgbMat.width(), rgbMat.height(), ultHeight);
processSmallMarkers(smallMarkers, width, height, ultHeight);
}
// 优先级21-4号几何中心下降
else if (!bigMarkers1_4.isEmpty()) {
currentLandingMode = 2;
processBigMarkersCenter(bigMarkers1_4, rgbMat.width(), rgbMat.height(), ultHeight);
processBigMarkersCenter(bigMarkers1_4, width, height, ultHeight);
}
// 优先级36号纯下降旋转完成后没看到1-4和小码时
else if (!bigMarker6.isEmpty()) {
currentLandingMode = 3;
processLandingOnly(bigMarker6.get(0), rgbMat.width(), rgbMat.height(), ultHeight);
processLandingOnly(bigMarker6.get(0), width, height, ultHeight);
}
// 旋转完成后丢失其他码但还有6号继续用6号下降
else {
@ -350,7 +349,7 @@ public class ApronArucoDetectPort {
handleLostMarker(ultHeight);
}
grayImgMat.release();
rgbMat.release();
grayImgMat.release();
yuvMat.release();
ids.release();
if (!corners.isEmpty()) {
@ -459,92 +458,209 @@ public class ApronArucoDetectPort {
double absY = Math.abs(offsetY);
double z = ultHeight / 10.0;
// 降落条件判断连续帧
// ========== 6个独立分支计数器降落判定 ==========
boolean isSpecialLanding = false;
double singleErrX = 0, singleErrY = 0;
// 单挂模式检查15号
if (!isDoublePayload && target15 != null && ultHeight <= 4) {
Mat corner = target15.getConner();
double cx = 0, cy = 0;
for (int i = 0; i < 4; i++) {
double[] p = corner.get(0, i);
cx += p[0];
cy += p[1];
if (ultHeight <= 4) {
// 当前帧可见码的Map
java.util.HashMap<Integer, ArucoMarker> markerMap = new java.util.HashMap<>();
for (ArucoMarker m : markers) {
markerMap.put(m.getId(), m);
}
cx /= 4.0; cy /= 4.0;
singleErrX = cx - imgWidth / 2.0 - LENS_OFFSET_X;
singleErrY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
if (Math.abs(singleErrX) < 60 && Math.abs(singleErrY) < 60) {
isSpecialLanding = true;
offsetX = singleErrX;
offsetY = singleErrY;
absX = Math.abs(singleErrX);
absY = Math.abs(singleErrY);
boolean foundLanding = false;
int landingId = 0;
// ===== 单挂模式13 15 17 14 16 18 =====
if (!isDoublePayload) {
// ---- 13号 ----
ArucoMarker marker13 = markerMap.get(13);
if (marker13 != null) {
Mat corner = marker13.getConner();
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 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); }
} else { landingCounters[13] = 0; }
// ---- 15号 ----
ArucoMarker marker15 = markerMap.get(15);
if (marker15 != null) {
Mat corner = marker15.getConner();
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 - 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 (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; }
// ---- 17号 ----
ArucoMarker marker17 = markerMap.get(17);
if (marker17 != null) {
Mat corner = marker17.getConner();
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 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); }
} else { landingCounters[17] = 0; }
// ---- 14号 ----
ArucoMarker marker14 = markerMap.get(14);
if (marker14 != null) {
Mat corner = marker14.getConner();
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 errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
if (Math.abs(errX) < 70 && 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; }
// ---- 16号 ----
ArucoMarker marker16 = markerMap.get(16);
if (marker16 != null) {
Mat corner = marker16.getConner();
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 - 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 (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; }
// ---- 18号 ----
ArucoMarker marker18 = markerMap.get(18);
if (marker18 != null) {
Mat corner = marker18.getConner();
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 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); }
} else { landingCounters[18] = 0; }
}
}
// ===== 双挂模式15 17 19 16 18 20 =====
else {
// ---- 15号 ----
ArucoMarker marker15d = markerMap.get(15);
if (marker15d != null) {
Mat corner = marker15d.getConner();
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 +220.0;
double errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
if (Math.abs(errX) < 55 && 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; }
// 双挂模式检查17号
if (isDoublePayload && target17 != null && ultHeight <= 4) {
Mat corner = target17.getConner();
double cx = 0, cy = 0;
for (int i = 0; i < 4; i++) {
double[] p = corner.get(0, i);
cx += p[0];
cy += p[1];
// ---- 17号 ----
ArucoMarker marker17d = markerMap.get(17);
if (marker17d != null) {
Mat corner = marker17d.getConner();
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 - 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 (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; }
// ---- 19号 ----
ArucoMarker marker19d = markerMap.get(19);
if (marker19d != null) {
Mat corner = marker19d.getConner();
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 errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[19]++; } else { landingCounters[19] = 0; }
if (landingCounters[19] >= 2 && !foundLanding) { foundLanding = true; landingId = 19; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); }
} else { landingCounters[19] = 0; }
// ---- 16号 ----
ArucoMarker marker16d = markerMap.get(16);
if (marker16d != null) {
Mat corner = marker16d.getConner();
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 +220.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 (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; }
// ---- 18号 ----
ArucoMarker marker18d = markerMap.get(18);
if (marker18d != null) {
Mat corner = marker18d.getConner();
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 - 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 (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; }
// ---- 20号 ----
ArucoMarker marker20d = markerMap.get(20);
if (marker20d != null) {
Mat corner = marker20d.getConner();
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 errY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
if (Math.abs(errX) < 55 && Math.abs(errY) < 70) { landingCounters[20]++; } else { landingCounters[20] = 0; }
if (landingCounters[20] >= 2 && !foundLanding) { foundLanding = true; landingId = 20; isSpecialLanding = true; singleErrX = errX; singleErrY = errY; offsetX = singleErrX; offsetY = singleErrY; absX = Math.abs(singleErrX); absY = Math.abs(singleErrY); }
} else { landingCounters[20] = 0; }
}
cx /= 4.0; cy /= 4.0;
singleErrX = cx - imgWidth / 2.0 - LENS_OFFSET_X;
singleErrY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
if (Math.abs(singleErrX) < 60 && Math.abs(singleErrY) < 60) {
isSpecialLanding = true;
offsetX = singleErrX;
offsetY = singleErrY;
absX = Math.abs(singleErrX);
absY = Math.abs(singleErrY);
}
}
LogUtil.log(TAG_LOG, "【计数器】13=" + landingCounters[13] + " 14=" + landingCounters[14] + " 15=" + landingCounters[15] + " 16=" + landingCounters[16] + " 17=" + landingCounters[17] + " 18=" + landingCounters[18]+ " 19=" + landingCounters[19]+ " 20=" + landingCounters[20]);
// 连续帧判断
int currentCondition = 0;
if (isSpecialLanding) {
currentCondition = 1;
} else if (ultHeight <= 4 && absX < 60 && absY < 60 && validCount >= 8) {
currentCondition = 2;
}
if (currentCondition == 0 || currentCondition != lastLandingCondition) {
if (consecutiveTriggerCount > 0) {
consecutiveTriggerCount = 0;
LogUtil.log(TAG_LOG, "【计数器清零】条件变化 last=" + lastLandingCondition + " curr=" + currentCondition);
}
lastLandingCondition = currentCondition;
}
if (currentCondition > 0) {
consecutiveTriggerCount++;
if (consecutiveTriggerCount >= TRIGGER_FRAME_THRESHOLD) {
// 触发降落
if (foundLanding) {
LogUtil.log(TAG_LOG, "【降落触发】ID=" + landingId + " 连续2帧满足条件");
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f);
startFastStick = true;
consecutiveTriggerCount = 0;
lastLandingCondition = 0;
LogUtil.log(TAG_LOG, currentCondition == 1 ?
"【降落触发】单双挂精准条件满足" :
"【降落触发】几何中心对准,码数量=" + validCount);
for (int i = 0; i < landingCounters.length; i++) { landingCounters[i] = 0; }
handler.postDelayed(() -> handler.post(runnable), 300);
return;
}
// 第二帧结束没有触发 重置
counterRound++;
if (counterRound >= 2) {
counterRound = 0;
for (int i = 0; i < landingCounters.length; i++) { landingCounters[i] = 0; }
LogUtil.log(TAG_LOG, "【计数器重置】2帧未触发重新从0开始");
}
}
double outX = 0, outY = 0, outZ = 0;
// 分段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;
@ -691,7 +807,7 @@ public class ApronArucoDetectPort {
outY = absY < 50 ? 0 : pidControlY.get_pid();
outX = Math.max(-0.20, Math.min(0.20, outX));
outY = Math.max(-0.20, Math.min(0.20, outY));
outZ = (absX < 100 && absY < 100) ? -0.4 : 0;
outZ = (absX < 100 && absY < 100) ? -0.8 : 0;
}else if(z>=4.0){
pidControlX.setInputFilterAll((float)offsetX/600);
pidControlY.setInputFilterAll(-(float)offsetY/600);
@ -748,7 +864,7 @@ public class ApronArucoDetectPort {
float vz;
if (ultHeight >= 50) {
vz = -0.7f;
vz = -0.8f;
} else if(ultHeight<=20){
vz = 0.0f;
}else{
@ -839,19 +955,59 @@ public class ApronArucoDetectPort {
return Math.sqrt(dx * dx + dy * dy);
}
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<Mat> 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 count1to4 = 0;
for (int id : idArray) {
if (id == 6) {
has6 = true;
} else if (id >= 1 && id <= 4) {
count1to4++;
}
}
if (has6 && count1to4 >= 3 && ultHeight <= 60) {
LogUtil.log(TAG_LOG, "【强制刹停】满足条件6号 + 1-4号中" + count1to4 + "个 高度=" + ultHeight + "dm");
FlightManager.forceTriggerDetection = true;
} else {
LogUtil.log(TAG_LOG, "【强制刹停】不满足条件6号=" + has6 + ", 1-4号数量=" + count1to4 + ", 高度=" + ultHeight + "dm(阈值80)");
}
}
yuvMat.release();
grayImgMat.release();
ids.release();
}
public void setDetectedBigMarkers() {
startFastStick = false;
isStartAruco = false;
consecutiveTriggerCount = 0;
frameCounter = 0;
// 关键重置旋转标志下次降落重新用6号旋转
isYawAligned = false;
currentLandingMode = 0;
lastLandingCondition = 0;
// 新增重置高度稳定性监测状态
isHeightStableMonitoring = false;
lastHeightCheckTime = 0;
lastUltrasonicHeight = 0;
// 重置分支计数器
counterRound = 0;
for (int i = 0; i < landingCounters.length; i++) {
landingCounters[i] = 0;
}
}
private int handlerCallbackCount = 0;

View File

@ -4,8 +4,12 @@ package com.aros.apron.tools;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import com.aros.apron.app.ApronApp;
import com.aros.apron.callback.MqttActionCallBack;
@ -59,48 +63,74 @@ public class MqttManager {
* 连接MQTT服务器
*/
private void doClientConnection() {
if (!mqttAndroidClient.isConnected() && isConnectIsNomarl()) {
try {
mqttAndroidClient.connect(mMqttConnectOptions, null, new MqttActionCallBack(mqttAndroidClient,mMqttConnectOptions));
} catch (MqttException e) {
LogUtil.log(TAG,"mqtt连接异常:"+e.toString());
e.printStackTrace();
}
if (mqttAndroidClient.isConnected()) {
return;
}
// 先检查网络状态仅用于日志和延迟策略不阻塞连接尝试
boolean networkAvailable = isConnectIsNomarl();
if (networkAvailable) {
tryConnect();
} else {
// 网络检查未通过时仍尝试连接防止网络误判导致永久断连
LogUtil.log(TAG, "网络检查未通过,但仍尝试连接");
tryConnect();
}
}
private void tryConnect() {
if (mqttAndroidClient.isConnected()) {
return;
}
try {
mqttAndroidClient.connect(mMqttConnectOptions, null, new MqttActionCallBack(mqttAndroidClient, mMqttConnectOptions));
} catch (MqttException e) {
LogUtil.log(TAG, "mqtt连接异常:" + e.toString());
e.printStackTrace();
}
}
/**
* 判断网络是否连接
* 判断网络是否连接仅做日志记录不阻塞连接尝试
*/
private boolean isConnectIsNomarl() {
ConnectivityManager connectivityManager = (ConnectivityManager) ApronApp.Companion.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
@SuppressLint("MissingPermission") NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
String name = info.getTypeName();
LogUtil.log(TAG, "当前网络名称:" + name);
return true;
} else {
LogUtil.log(TAG, "没有可用Mqtt网络,延迟三秒后重连");
/*没有可用网络的时候延迟3秒再尝试重连*/
doConnectionDelay();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
if (network != null) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
if (capabilities != null) {
String transportInfo;
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
transportInfo = "ETHERNET";
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
transportInfo = "WIFI";
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
transportInfo = "CELLULAR";
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
transportInfo = "VPN";
} else {
transportInfo = "OTHER";
}
LogUtil.log(TAG, "当前网络可用:" + transportInfo);
return true;
}
}
LogUtil.log(TAG, "没有可用Mqtt网络API 23+");
return false;
} else {
@SuppressLint("MissingPermission") NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
String name = info.getTypeName();
LogUtil.log(TAG, "当前网络名称:" + name);
return true;
} else {
LogUtil.log(TAG, "没有可用Mqtt网络");
return false;
}
}
}
/**
* 没有可用网络的时候延迟5秒再尝试重连
*/
private void doConnectionDelay() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
doClientConnection();
}
}, 3000);
}
private String generateRandomString(int length) {
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
StringBuilder builder = new StringBuilder();

View File

@ -27,6 +27,8 @@ public class SimplePortScanner {
private static final long SCAN_INTERVAL = 3000L;
private static final int CONNECT_TIMEOUT = 500;
private volatile boolean isScanning = false; // 防止重复启动扫描
private OnPortCheckListener checkListener;
// 主线程 Handler用于切换回调到 UI 线程和定时扫描
private final Handler mainHandler = new Handler(Looper.getMainLooper());
@ -51,11 +53,14 @@ public class SimplePortScanner {
closedCount++;
Log.i(TAG, "端口关闭次数:" + closedCount + "/" + MAX_CLOSED_COUNT);
if (closedCount >= MAX_CLOSED_COUNT) {
Log.i(TAG, "端口连续关闭 " + closedCount + " 次,尝试重启 RTSP 推流");
closedCount = 0; // 重置计数
// 先重置状态 startLiveWithRTSP 能真正执行
Log.i(TAG, "端口连续关闭 " + closedCount + " 次,停止扫描并重启推流");
closedCount = 0;
isScanning = false; // 停止自身扫描
mainHandler.removeCallbacks(scanRunnable);
StreamManager.getInstance().stopstream();
StreamManager.getInstance().resetStreamState();
StreamManager.getInstance().startLiveWithRTSP();
return; // 阻止 postDelayed 再次循环
}
if (checkListener != null) {
checkListener.onPortClosed();
@ -88,14 +93,24 @@ public class SimplePortScanner {
}
public void startScan() {
stopScan();
closedCount = 0; // 重置计数
if (isScanning) {
Log.i(TAG, "扫描已在运行,忽略重复调用");
return;
}
isScanning = true;
closedCount = 0;
Log.i(TAG, "开始端口扫描");
// 立即执行一次然后延迟 3 秒循环
mainHandler.post(scanRunnable);
}
public void stopScan() {
if (!isScanning) {
return;
}
isScanning = false;
mainHandler.removeCallbacks(scanRunnable);
Log.i(TAG, "停止端口扫描");
}
private boolean checkPort(String host, int port) {

View File

@ -75,6 +75,28 @@ public class SpeakerProgressReporter {
LogUtil.log(TAG, "停止音频进度上报");
}
/**
* 立即发送一次音频进度不启动定时循环
*/
public void sendNowAudioProgress(int psdkIndex) {
try {
sendAudioProgress(psdkIndex);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 立即发送一次TTS进度不启动定时循环
*/
public void sendNowTTSProgress(int psdkIndex) {
try {
sendTTSProgress(psdkIndex);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发送音频进度使用 SpeakerAudioPlayProgress 实体类
*/

View File

@ -1,5 +1,6 @@
package com.aros.apron.tools;
import com.aros.apron.entity.Movement;
import com.aros.apron.entity.XTTSParams;
import com.iflytek.aikit.core.AeeEvent;
import com.iflytek.aikit.core.AiHandle;
@ -67,11 +68,18 @@ public class XTtsPcmGenerator {
paramBuilder.param("speed", params.speed);
paramBuilder.param("volume", params.volume);
// 构建监听器
Movement.getInstance().setTts_speed(String.valueOf(params.speed));
Movement.getInstance().setTts_language("0");
Movement.getInstance().setTts_type("0");
Movement.getInstance().setTts_volume(String.valueOf(params.volume));
AiListener listener = buildListener();
LogUtil.log(TAG, "监听器创建成功");
// 注册监听器
try {
AiHelper.getInst().registerListener(ABILITY_ID, listener);
LogUtil.log(TAG, "监听器注册成功");
@ -82,7 +90,7 @@ public class XTtsPcmGenerator {
return null;
}
// 启动 TTS 引擎
aiHandle = AiHelper.getInst().start(ABILITY_ID, paramBuilder.build(), null);
if (aiHandle.getCode() != 0) {
LogUtil.log(TAG, "启动 TTS 引擎失败: " + aiHandle.getCode());
@ -91,7 +99,7 @@ public class XTtsPcmGenerator {
}
LogUtil.log(TAG, "TTS 引擎启动成功");
// 构建请求
AiText aiText = AiText.get("text").data(text).valid();
AiRequest request = AiRequest.builder().payload(aiText).build();