1.修改Q20飞机协议,删除部分无用协议,修改请求参数
This commit is contained in:
parent
4ce9541397
commit
442cd50d21
|
|
@ -332,13 +332,13 @@ public class Q20CommandController {
|
|||
return new Result<>().ok(q20CommandService.logData(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== OTA ====================
|
||||
// ==================== OTA(暂时停用,先注释) ====================
|
||||
|
||||
@PostMapping("/otaUpgrade/{deviceSn}")
|
||||
@LogOperation("Q20 OTA升级")
|
||||
@Operation(summary = "固件OTA升级")
|
||||
@RequiresPermissions("bus:q20:ota")
|
||||
public Result<Object> otaUpgrade(@PathVariable String deviceSn, @RequestBody Q20OtaUpgradeDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.otaUpgrade(deviceSn, dto));
|
||||
}
|
||||
// @PostMapping("/otaUpgrade/{deviceSn}")
|
||||
// @LogOperation("Q20 OTA升级")
|
||||
// @Operation(summary = "固件OTA升级")
|
||||
// @RequiresPermissions("bus:q20:ota")
|
||||
// public Result<Object> otaUpgrade(@PathVariable String deviceSn, @RequestBody Q20OtaUpgradeDTO dto) {
|
||||
// return new Result<>().ok(q20CommandService.otaUpgrade(deviceSn, dto));
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,25 +80,25 @@ public class Q20RouteController {
|
|||
return new Result<>().ok(q20RouteService.breakLoopHover(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 获取航线信息 ====================
|
||||
// ==================== 获取航线信息(暂时停用,先注释) ====================
|
||||
|
||||
@GetMapping("/info/{deviceSn}")
|
||||
@Operation(summary = "获取航线信息(route_info)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeInfo(@PathVariable String deviceSn,
|
||||
@RequestParam int mode,
|
||||
@RequestParam(required = false) String value) {
|
||||
return new Result<>().ok(q20RouteService.routeInfo(deviceSn, mode, value));
|
||||
}
|
||||
// @GetMapping("/info/{deviceSn}")
|
||||
// @Operation(summary = "获取航线信息(route_info)")
|
||||
// @RequiresPermissions("bus:q20:route")
|
||||
// public Result<Object> routeInfo(@PathVariable String deviceSn,
|
||||
// @RequestParam int mode,
|
||||
// @RequestParam(required = false) String value) {
|
||||
// return new Result<>().ok(q20RouteService.routeInfo(deviceSn, mode, value));
|
||||
// }
|
||||
|
||||
// ==================== 获取执行进度 ====================
|
||||
// ==================== 获取执行进度(暂时停用,先注释) ====================
|
||||
|
||||
@GetMapping("/progress/{deviceSn}")
|
||||
@Operation(summary = "获取航线执行进度(route_progress)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeProgress(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20RouteService.routeProgress(deviceSn));
|
||||
}
|
||||
// @GetMapping("/progress/{deviceSn}")
|
||||
// @Operation(summary = "获取航线执行进度(route_progress)")
|
||||
// @RequiresPermissions("bus:q20:route")
|
||||
// public Result<Object> routeProgress(@PathVariable String deviceSn) {
|
||||
// return new Result<>().ok(q20RouteService.routeProgress(deviceSn));
|
||||
// }
|
||||
|
||||
// ==================== 暂停航线 ====================
|
||||
|
||||
|
|
@ -120,33 +120,33 @@ public class Q20RouteController {
|
|||
return new Result<>().ok(q20RouteService.routeResume(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 退出航线 ====================
|
||||
// ==================== 退出航线(暂时停用,先注释) ====================
|
||||
|
||||
@PostMapping("/finish/{deviceSn}")
|
||||
@LogOperation("退出航线")
|
||||
@Operation(summary = "退出航线(route_finish)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeFinish(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20RouteService.routeFinish(deviceSn));
|
||||
}
|
||||
// @PostMapping("/finish/{deviceSn}")
|
||||
// @LogOperation("退出航线")
|
||||
// @Operation(summary = "退出航线(route_finish)")
|
||||
// @RequiresPermissions("bus:q20:route")
|
||||
// public Result<Object> routeFinish(@PathVariable String deviceSn) {
|
||||
// return new Result<>().ok(q20RouteService.routeFinish(deviceSn));
|
||||
// }
|
||||
|
||||
// ==================== 获取围栏信息 ====================
|
||||
// ==================== 获取围栏信息(暂时停用,先注释) ====================
|
||||
|
||||
@GetMapping("/geofenceInfo/{deviceSn}")
|
||||
@Operation(summary = "获取围栏信息(geofence_info)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> geofenceInfo(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20RouteService.geofenceInfo(deviceSn));
|
||||
}
|
||||
// @GetMapping("/geofenceInfo/{deviceSn}")
|
||||
// @Operation(summary = "获取围栏信息(geofence_info)")
|
||||
// @RequiresPermissions("bus:q20:route")
|
||||
// public Result<Object> geofenceInfo(@PathVariable String deviceSn) {
|
||||
// return new Result<>().ok(q20RouteService.geofenceInfo(deviceSn));
|
||||
// }
|
||||
|
||||
// ==================== 上传围栏 ====================
|
||||
// ==================== 上传围栏(暂时停用,先注释) ====================
|
||||
|
||||
@PostMapping("/geofenceUpload/{deviceSn}")
|
||||
@LogOperation("上传围栏")
|
||||
@Operation(summary = "上传围栏(geofence_upload)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> geofenceUpload(@PathVariable String deviceSn,
|
||||
@RequestBody Q20GeofenceUploadDTO dto) {
|
||||
return new Result<>().ok(q20RouteService.geofenceUpload(deviceSn, dto));
|
||||
}
|
||||
// @PostMapping("/geofenceUpload/{deviceSn}")
|
||||
// @LogOperation("上传围栏")
|
||||
// @Operation(summary = "上传围栏(geofence_upload)")
|
||||
// @RequiresPermissions("bus:q20:route")
|
||||
// public Result<Object> geofenceUpload(@PathVariable String deviceSn,
|
||||
// @RequestBody Q20GeofenceUploadDTO dto) {
|
||||
// return new Result<>().ok(q20RouteService.geofenceUpload(deviceSn, dto));
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class Q20RouteActionDTO {
|
|||
@JsonProperty("action_id")
|
||||
private int actionId;
|
||||
|
||||
@Schema(description = "动作执行函数:takePhoto/startRecord/stopRecord/focus/zoom/customDirName/gimbalRotate/rotateYaw/hover/gimbalEvenlyRotate/orientedShoot/panoShot/recordPointCloud")
|
||||
@Schema(description = "动作执行函数:takePhoto/startRecord/stopRecord/zoom/gimbalRotate/rotateYaw/hover")
|
||||
@JsonProperty("action_actuator_func")
|
||||
private String actionActuatorFunc;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
|
@ -16,22 +15,6 @@ import java.util.List;
|
|||
@Schema(description = "航点动作组")
|
||||
public class Q20RouteActionGroupDTO {
|
||||
|
||||
@Schema(description = "动作组ID,范围[0,65535]")
|
||||
@JsonProperty("action_group_id")
|
||||
private int actionGroupId;
|
||||
|
||||
@Schema(description = "动作组起始航点索引")
|
||||
@JsonProperty("action_group_start_index")
|
||||
private Integer actionGroupStartIndex;
|
||||
|
||||
@Schema(description = "动作组结束航点索引")
|
||||
@JsonProperty("action_group_end_index")
|
||||
private Integer actionGroupEndIndex;
|
||||
|
||||
@Schema(description = "动作组执行模式")
|
||||
@JsonProperty("action_group_mode")
|
||||
private String actionGroupMode;
|
||||
|
||||
@Schema(description = "动作列表")
|
||||
private List<Q20RouteActionDTO> action;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 无人机信息DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "无人机信息")
|
||||
public class Q20RouteDroneInfoDTO {
|
||||
|
||||
@Schema(description = "无人机枚举值,20=Q20")
|
||||
@JsonProperty("drone_enum_value")
|
||||
private int droneEnumValue;
|
||||
|
||||
@Schema(description = "无人机子枚举值,0=Q20, 1=Q20Y, 2=Q20N")
|
||||
@JsonProperty("drone_sub_enum_value")
|
||||
private int droneSubEnumValue;
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||
import com.fasterxml.jackson.annotation.Nulls;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -19,15 +22,17 @@ public class Q20RouteExecuteDTO {
|
|||
@Schema(description = "执行模式:1=执行最近一次上传航线,2=执行指定route_id的航线")
|
||||
private int mode;
|
||||
|
||||
@Schema(description = "航线ID,mode=2时有效")
|
||||
@Schema(description = "航线ID,mode=2时有效;mode=1时为空字符串")
|
||||
@JsonProperty("route_id")
|
||||
private String routeId;
|
||||
@JsonSetter(nulls = Nulls.AS_EMPTY)
|
||||
private String routeId = "";
|
||||
|
||||
@Schema(description = "是否指定机库:0=未设置,1=已设置")
|
||||
@JsonProperty("specific_dock")
|
||||
private int specificDock;
|
||||
|
||||
@Schema(description = "机库信息列表")
|
||||
@Schema(description = "机库信息列表,空时为空数组[]")
|
||||
@JsonProperty("dock_info")
|
||||
private List<Q20DockInfoItemDTO> dockInfo;
|
||||
@JsonSetter(nulls = Nulls.AS_EMPTY)
|
||||
private List<Q20DockInfoItemDTO> dockInfo = new ArrayList<>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,11 @@ import java.util.List;
|
|||
@Schema(description = "航线文件夹,包含所有航点信息")
|
||||
public class Q20RouteFolderDTO {
|
||||
|
||||
@Schema(description = "模板ID,默认0")
|
||||
@JsonProperty("template_id")
|
||||
private Integer templateId;
|
||||
|
||||
@Schema(description = "航线ID,默认0")
|
||||
@JsonProperty("wayline_id")
|
||||
private Integer waylineId;
|
||||
|
||||
@Schema(description = "全局飞行速度(m/s),范围[1,15]")
|
||||
@JsonProperty("auto_flight_speed")
|
||||
private float autoFlightSpeed;
|
||||
|
||||
@Schema(description = "执行高度模式:WGS84 / relativeToStartPoint / realTimeFollowSurface")
|
||||
@Schema(description = "执行高度模式:relativeToStartPoint(相对起飞点高度)/ WGS84(椭球高)")
|
||||
@JsonProperty("execute_height_mode")
|
||||
private String executeHeightMode;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,6 @@ import lombok.Data;
|
|||
@Schema(description = "任务配置信息")
|
||||
public class Q20RouteMissionConfigDTO {
|
||||
|
||||
@Schema(description = "飞向航线起点模式:safely=安全模式 / pointToPoint=斜线直达模式")
|
||||
@JsonProperty("fly_to_wayline_mode")
|
||||
private String flyToWaylineMode;
|
||||
|
||||
@Schema(description = "完成动作:goHome / noAction / autoLand / preLand / gotoFirstWaypoint")
|
||||
@JsonProperty("finish_action")
|
||||
private String finishAction;
|
||||
|
|
@ -38,27 +34,11 @@ public class Q20RouteMissionConfigDTO {
|
|||
@JsonProperty("execute_rc_lost_action")
|
||||
private String executeRcLostAction;
|
||||
|
||||
@Schema(description = "安全起飞高度(m),范围[2,1500]")
|
||||
@JsonProperty("takeoff_security_height")
|
||||
private Float takeoffSecurityHeight;
|
||||
|
||||
@Schema(description = "全局过渡速度(m/s)")
|
||||
@JsonProperty("global_transitional_speed")
|
||||
private Float globalTransitionalSpeed;
|
||||
|
||||
@Schema(description = "全局返航高度(m)")
|
||||
@JsonProperty("global_RTH_height")
|
||||
private Float globalRthHeight;
|
||||
|
||||
@Schema(description = "降落点海拔高度(m)")
|
||||
@Schema(description = "降落点海拔高度(m),默认5")
|
||||
@JsonProperty("land_height")
|
||||
private Float landHeight;
|
||||
|
||||
@Schema(description = "无人机信息")
|
||||
@JsonProperty("drone_info")
|
||||
private Q20RouteDroneInfoDTO droneInfo;
|
||||
|
||||
@Schema(description = "挂载信息")
|
||||
@JsonProperty("payload_info")
|
||||
private Q20RoutePayloadInfoDTO payloadInfo;
|
||||
private Float landHeight = 5f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 挂载信息DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "挂载信息")
|
||||
public class Q20RoutePayloadInfoDTO {
|
||||
|
||||
@Schema(description = "挂载枚举值,30=ZT30, 31=ZR30, 40=ZT40")
|
||||
@JsonProperty("payload_enum_value")
|
||||
private int payloadEnumValue;
|
||||
|
||||
@Schema(description = "挂载位置索引,0=1号挂载位置")
|
||||
@JsonProperty("payload_position_index")
|
||||
private int payloadPositionIndex;
|
||||
}
|
||||
|
|
@ -17,10 +17,6 @@ public class Q20RoutePlacemarkDTO {
|
|||
@Schema(description = "航点序号,从0开始")
|
||||
private int index;
|
||||
|
||||
@Schema(description = "是否危险点:0=安全,1=危险")
|
||||
@JsonProperty("is_risky")
|
||||
private Integer isRisky;
|
||||
|
||||
@Schema(description = "航点坐标")
|
||||
private Q20RoutePointDTO point;
|
||||
|
||||
|
|
@ -36,18 +32,6 @@ public class Q20RoutePlacemarkDTO {
|
|||
@JsonProperty("waypoint_speed")
|
||||
private Float waypointSpeed;
|
||||
|
||||
@Schema(description = "偏航角参数")
|
||||
@JsonProperty("waypoint_heading_param")
|
||||
private Q20RouteWaypointHeadingDTO waypointHeadingParam;
|
||||
|
||||
@Schema(description = "转弯参数")
|
||||
@JsonProperty("waypoint_turn_param")
|
||||
private Q20RouteWaypointTurnDTO waypointTurnParam;
|
||||
|
||||
@Schema(description = "是否飞直线:0=曲线,1=直线")
|
||||
@JsonProperty("use_straight_line")
|
||||
private Integer useStraightLine;
|
||||
|
||||
@Schema(description = "动作组")
|
||||
@JsonProperty("action_group")
|
||||
private Q20RouteActionGroupDTO actionGroup;
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 航点偏航角参数DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "航点偏航角参数")
|
||||
public class Q20RouteWaypointHeadingDTO {
|
||||
|
||||
@Schema(description = "偏航角模式:followWayline/manually/fixed/smoothTransition/towardPOI")
|
||||
@JsonProperty("waypoint_heading_mode")
|
||||
private String waypointHeadingMode;
|
||||
|
||||
@Schema(description = "偏航角(deg)")
|
||||
@JsonProperty("waypoint_heading_angle")
|
||||
private Float waypointHeadingAngle;
|
||||
|
||||
@Schema(description = "POI点坐标 \"[lng,lat,alt]\",towardPOI模式时有效")
|
||||
@JsonProperty("waypoint_poi_point")
|
||||
private String waypointPoiPoint;
|
||||
|
||||
@Schema(description = "偏航角路径模式:clockwise(顺时针)/ counterClockwise(逆时针)")
|
||||
@JsonProperty("waypoint_heading_path_mode")
|
||||
private String waypointHeadingPathMode;
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 航点转弯参数DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "航点转弯参数")
|
||||
public class Q20RouteWaypointTurnDTO {
|
||||
|
||||
@Schema(description = "转弯模式:coordinateTurn / toPointAndStopWithDiscontinuityCurvature / toPointAndStopWithContinuityCurvature / toPointAndPassWithContinuityCurvature")
|
||||
@JsonProperty("waypoint_turn_mode")
|
||||
private String waypointTurnMode;
|
||||
|
||||
@Schema(description = "转弯截距(m)")
|
||||
@JsonProperty("waypoint_turn_damping_dist")
|
||||
private Float waypointTurnDampingDist;
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ public class Q20CommandServiceImpl implements Q20CommandService {
|
|||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload(method, data);
|
||||
payload.set("gateway", deviceSn);
|
||||
payload.set("need_reply", 1);
|
||||
return djiBaseService.sendWaitReplyJudgeResult(topic, payload);
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ public class Q20CommandServiceImpl implements Q20CommandService {
|
|||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload(method, data);
|
||||
payload.set("gateway", deviceSn);
|
||||
payload.set("need_reply", 1);
|
||||
JSONObject reply = djiBaseService.sendWaitReply(topic, payload);
|
||||
if (reply == null) {
|
||||
throw new RenException("设备未响应,请检查设备是否在线");
|
||||
|
|
@ -71,6 +73,12 @@ public class Q20CommandServiceImpl implements Q20CommandService {
|
|||
|
||||
@Override
|
||||
public String goHome(String deviceSn, Q20GoHomeDTO dto) {
|
||||
if (dto.getMode() == 1 && dto.getSafeAltitude() == null) {
|
||||
throw new RenException("返航模式为安全高度返回(mode=1)时,必须填写安全返航高度(safe_altitude)");
|
||||
}
|
||||
if (dto.getSpecificHome() != null && dto.getSpecificHome() == 1 && dto.getHomePoint() == null) {
|
||||
throw new RenException("指定返航点(specific_home=1)时,必须填写返航点坐标(home_point)");
|
||||
}
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("mode", dto.getMode());
|
||||
if (dto.getSafeAltitude() != null) {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
return djiBaseService.sendWaitReplyJudgeResult(topic, djiBaseService.getPayload(method, data));
|
||||
}
|
||||
|
||||
/** 发送指令并等待回复(携带 gateway 与 need_reply=1),返回结果描述字符串 */
|
||||
private String execCmdWithGateway(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload(method, data);
|
||||
payload.set("gateway", deviceSn);
|
||||
payload.set("need_reply", 1);
|
||||
return djiBaseService.sendWaitReplyJudgeResult(topic, payload);
|
||||
}
|
||||
|
||||
/** 发送指令并等待回复,返回完整回复JSONObject;超时抛出RenException */
|
||||
private JSONObject execCmdGetReply(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
|
|
@ -77,6 +86,19 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
return reply;
|
||||
}
|
||||
|
||||
/** 发送指令并等待回复(携带 gateway 与 need_reply=1),返回完整回复JSONObject;超时抛出RenException */
|
||||
private JSONObject execCmdGetReplyWithGateway(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload(method, data);
|
||||
payload.set("gateway", deviceSn);
|
||||
payload.set("need_reply", 1);
|
||||
JSONObject reply = djiBaseService.sendWaitReply(topic, payload);
|
||||
if (reply == null) {
|
||||
throw new RenException("设备未响应,请检查设备是否在线");
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
/** 利用Jackson @JsonProperty注解将DTO序列化为snake_case键的JSONObject */
|
||||
private JSONObject dtoToJson(Object dto) {
|
||||
try {
|
||||
|
|
@ -145,9 +167,10 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public String routeUpload(String deviceSn, Q20RouteUploadDTO dto) {
|
||||
validateWaypoints(dto);
|
||||
validateMissionConfig(dto);
|
||||
checkRouteNotExists(dto.getWayline(), dto.getRouteName());
|
||||
JSONObject data = dtoToJson(dto);
|
||||
String result = execCmd(deviceSn, "route_upload", data);
|
||||
String result = execCmdWithGateway(deviceSn, "route_upload", data);
|
||||
saveRouteToLocal(deviceSn, dto);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -164,6 +187,34 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
if (valid < 2) {
|
||||
throw new RenException("航线至少需要两个有效航点(含经纬度)");
|
||||
}
|
||||
// 不使用全局速度(use_global_speed=0)的航点必须填写航点飞行速度(waypoint_speed)
|
||||
for (Q20RoutePlacemarkDTO p : placemarks) {
|
||||
if (p != null && p.getUseGlobalSpeed() != null && p.getUseGlobalSpeed() == 0
|
||||
&& p.getWaypointSpeed() == null) {
|
||||
throw new RenException("航点" + p.getIndex() + "未使用全局速度时,必须填写航点飞行速度(waypoint_speed)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务配置条件必填校验:
|
||||
* preLand 时 action_value 必填;goHome 时 go_home_type 必填;
|
||||
* exit_on_rc_lost 为 executeLostAction 时 execute_rc_lost_action 必填
|
||||
*/
|
||||
private void validateMissionConfig(Q20RouteUploadDTO dto) {
|
||||
Q20RouteMissionConfigDTO mc = dto != null ? dto.getMissionConfig() : null;
|
||||
if (mc == null) {
|
||||
return;
|
||||
}
|
||||
if ("preLand".equals(mc.getFinishAction()) && StrUtil.isBlank(mc.getActionValue())) {
|
||||
throw new RenException("结束动作为精准降落(preLand)时,必须填写精准降落二维码值(action_value)");
|
||||
}
|
||||
if ("goHome".equals(mc.getFinishAction()) && StrUtil.isBlank(mc.getGoHomeType())) {
|
||||
throw new RenException("结束动作为返航(goHome)时,必须填写返航类型(go_home_type)");
|
||||
}
|
||||
if ("executeLostAction".equals(mc.getExitOnRcLost()) && StrUtil.isBlank(mc.getExecuteRcLostAction())) {
|
||||
throw new RenException("失联退出策略为执行失控动作(executeLostAction)时,必须填写失联执行动作(execute_rc_lost_action)");
|
||||
}
|
||||
}
|
||||
|
||||
/** 上传前校验:航线ID和航线名称均不能与库中已有记录重复 */
|
||||
|
|
@ -189,7 +240,7 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
@Override
|
||||
public String routeExecute(String deviceSn, Q20RouteExecuteDTO dto) {
|
||||
JSONObject data = dtoToJson(dto);
|
||||
String result = execCmd(deviceSn, "route_execute", data);
|
||||
String result = execCmdWithGateway(deviceSn, "route_execute", data);
|
||||
saveFlightTask(deviceSn, dto.getRouteId());
|
||||
return result;
|
||||
}
|
||||
|
|
@ -198,13 +249,14 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public String routeAuto(String deviceSn, Q20RouteAutoDTO dto) {
|
||||
validateWaypoints(dto.getRouteInfo());
|
||||
validateMissionConfig(dto.getRouteInfo());
|
||||
Q20RouteUploadDTO routeInfo = dto.getRouteInfo();
|
||||
checkRouteNotExists(routeInfo.getWayline(), routeInfo.getRouteName());
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("route_info", dtoToJson(dto.getRouteInfo()));
|
||||
data.set("execute_info", dtoToJson(dto.getExecuteInfo()));
|
||||
data.set("vehicle_config", dtoToJson(dto.getVehicleConfig()));
|
||||
String result = execCmd(deviceSn, "route_auto", data);
|
||||
String result = execCmdWithGateway(deviceSn, "route_auto", data);
|
||||
if (dto.getRouteInfo() != null) {
|
||||
saveRouteToLocal(deviceSn, dto.getRouteInfo());
|
||||
}
|
||||
|
|
@ -217,7 +269,7 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
public String breakLoopHover(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "break_loop_hover", data);
|
||||
return execCmdWithGateway(deviceSn, "break_loop_hover", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -232,28 +284,28 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
public JSONObject routeProgress(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmdGetReply(deviceSn, "route_progress", data);
|
||||
return execCmdGetReplyWithGateway(deviceSn, "route_progress", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routePause(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "route_pause", data);
|
||||
return execCmdWithGateway(deviceSn, "route_pause", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routeResume(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "route_resume", data);
|
||||
return execCmdWithGateway(deviceSn, "route_resume", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routeFinish(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "route_finish", data);
|
||||
return execCmdWithGateway(deviceSn, "route_finish", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -306,19 +358,12 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
|
||||
Q20RouteMissionConfigDTO mc = dto.getMissionConfig();
|
||||
if (mc != null) {
|
||||
route.setFlyToWaylineMode(mc.getFlyToWaylineMode());
|
||||
route.setFinishAction(mc.getFinishAction());
|
||||
route.setExitOnRcLost(mc.getExitOnRcLost());
|
||||
route.setExecuteRcLostAction(mc.getExecuteRcLostAction());
|
||||
if (mc.getGlobalTransitionalSpeed() != null) {
|
||||
route.setGlobalTransitionalSpeed(mc.getGlobalTransitionalSpeed().doubleValue());
|
||||
}
|
||||
if (mc.getGlobalRthHeight() != null) {
|
||||
route.setGlobalRthHeight(mc.getGlobalRthHeight().doubleValue());
|
||||
}
|
||||
if (mc.getTakeoffSecurityHeight() != null) {
|
||||
route.setTakeoffSecurityHeight(mc.getTakeoffSecurityHeight().doubleValue());
|
||||
}
|
||||
}
|
||||
|
||||
Q20RouteFolderDTO folder = dto.getFolder();
|
||||
|
|
@ -439,26 +484,10 @@ public class Q20RouteServiceImpl implements Q20RouteService {
|
|||
? (double) globalSpeed
|
||||
: (p.getWaypointSpeed() != null ? p.getWaypointSpeed().doubleValue() : (double) globalSpeed));
|
||||
|
||||
Q20RouteWaypointHeadingDTO heading = p.getWaypointHeadingParam();
|
||||
if (heading != null) {
|
||||
wp.setWaypointHeadingMode(heading.getWaypointHeadingMode());
|
||||
if (heading.getWaypointHeadingAngle() != null) {
|
||||
wp.setWaypointHeadingAngle(String.valueOf(heading.getWaypointHeadingAngle()));
|
||||
}
|
||||
wp.setWaypointHeadingPathMode(heading.getWaypointHeadingPathMode());
|
||||
wp.setWaypointPoiPoint(heading.getWaypointPoiPoint());
|
||||
}
|
||||
if (wp.getWaypointHeadingMode() == null) wp.setWaypointHeadingMode("followWayline");
|
||||
if (wp.getWaypointHeadingPathMode() == null) wp.setWaypointHeadingPathMode("followBadArc");
|
||||
|
||||
Q20RouteWaypointTurnDTO turn = p.getWaypointTurnParam();
|
||||
if (turn != null) {
|
||||
wp.setWaypointTruningMode(turn.getWaypointTurnMode());
|
||||
if (turn.getWaypointTurnDampingDist() != null) {
|
||||
wp.setWaypointTurnDampingDist(String.valueOf(turn.getWaypointTurnDampingDist()));
|
||||
}
|
||||
}
|
||||
if (wp.getWaypointTruningMode() == null) wp.setWaypointTruningMode("toPointAndStopWithDiscontinuityCurvature");
|
||||
// 偏航/转弯模式不再由航点配置,统一使用默认值入库
|
||||
wp.setWaypointHeadingMode("followWayline");
|
||||
wp.setWaypointHeadingPathMode("followBadArc");
|
||||
wp.setWaypointTruningMode("toPointAndStopWithDiscontinuityCurvature");
|
||||
|
||||
return wp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-safety">安全/降落伞</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-status">状态信息</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-log">日志</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-ota">OTA</a></li>
|
||||
<!-- OTA 暂时停用:<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-ota">OTA</a></li> -->
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-route">航线任务</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
@ -193,12 +193,12 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-pause-circle me-1"></i>紧急悬停</div>
|
||||
<div class="card-header"><i class="bi bi-pause-circle me-1"></i>停止动作</div>
|
||||
<div class="card-body">
|
||||
<h6 class="cmd-title">stop — 立即悬停 (value 固定=1)</h6>
|
||||
<p class="text-muted small">无需参数,发送后飞机立即悬停</p>
|
||||
<h6 class="cmd-title">stop — 停止动作 (value 固定=1)</h6>
|
||||
<p class="text-muted small">无需参数,发送后飞机停止当前动作</p>
|
||||
<button class="btn btn-danger w-100" onclick="cmd('stop','POST',null,'stop_resp')">
|
||||
<i class="bi bi-stop-fill me-1"></i>紧急悬停
|
||||
<i class="bi bi-stop-fill me-1"></i>停止动作
|
||||
</button>
|
||||
<div id="stop_resp" class="resp-box" style="display:none"></div>
|
||||
</div>
|
||||
|
|
@ -213,7 +213,6 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<label class="form-label">返航模式</label>
|
||||
<select id="gh_mode" class="form-select">
|
||||
<option value="1">1 安全高度返回</option>
|
||||
<option value="2">2 直线飞行</option>
|
||||
<option value="3">3 原路返回</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -841,7 +840,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== OTA ===== -->
|
||||
<!-- ===== OTA(暂时停用,前端先注释) =====
|
||||
<div class="tab-pane fade" id="tab-ota">
|
||||
<div class="section-grid">
|
||||
<div class="card" style="max-width:600px">
|
||||
|
|
@ -863,6 +862,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- ===== 航线任务 ===== -->
|
||||
<div class="tab-pane fade" id="tab-route">
|
||||
|
|
@ -875,9 +875,9 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button class="btn btn-warning" onclick="route('pause','POST',null,'rq_resp')"><i class="bi bi-pause-fill me-1"></i>暂停</button>
|
||||
<button class="btn btn-success" onclick="route('resume','POST',null,'rq_resp')"><i class="bi bi-play-fill me-1"></i>继续</button>
|
||||
<button class="btn btn-danger" onclick="route('finish','POST',null,'rq_resp')"><i class="bi bi-stop-fill me-1"></i>退出航线</button>
|
||||
<!-- 退出航线暂时停用:<button class="btn btn-danger" onclick="route('finish','POST',null,'rq_resp')"><i class="bi bi-stop-fill me-1"></i>退出航线</button> -->
|
||||
<button class="btn btn-secondary" onclick="route('breakHover','POST',null,'rq_resp')"><i class="bi bi-arrow-repeat me-1"></i>取消悬停</button>
|
||||
<button class="btn btn-info" onclick="routeGet('progress','rq_resp')"><i class="bi bi-bar-chart me-1"></i>获取进度</button>
|
||||
<!-- 获取进度暂时停用:<button class="btn btn-info" onclick="routeGet('progress','rq_resp')"><i class="bi bi-bar-chart me-1"></i>获取进度</button> -->
|
||||
</div>
|
||||
<div id="rq_resp" class="resp-box" style="display:none"></div>
|
||||
</div>
|
||||
|
|
@ -900,13 +900,6 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<button class="btn btn-outline-secondary" type="button" onclick="fillNextWayline('ru_wayline','route')" title="按库中最新上传航线ID自动生成"><i class="bi bi-arrow-repeat"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label">起点飞行模式</label>
|
||||
<select id="ru_fly_mode" class="form-select">
|
||||
<option value="safely">safely 安全</option>
|
||||
<option value="pointToPoint">pointToPoint 直达</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label">完成动作</label>
|
||||
<select id="ru_finish" class="form-select" onchange="document.getElementById('ru_action_value_row').style.display=this.value==='preLand'?'':'none'">
|
||||
|
|
@ -920,21 +913,12 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<label class="form-label">精准降落标识 (actionValue) <small class="text-muted">{hangar_sn}-{dock_code}</small></label>
|
||||
<input id="ru_action_value" class="form-control" placeholder="例: dock001-1">
|
||||
</div>
|
||||
<div class="col-6"><label class="form-label">安全起飞高 (m)</label><input id="ru_takeoff_h" class="form-control" type="number" value="30"></div>
|
||||
<div class="col-6"><label class="form-label">全局返航高 (m)</label><input id="ru_rth_h" class="form-control" type="number" value="50"></div>
|
||||
<div class="col-6"><label class="form-label">全局飞行速 (m/s)</label><input id="ru_speed" class="form-control" type="number" value="10" step="0.5"></div>
|
||||
<div class="col-6"><label class="form-label">高度模式</label>
|
||||
<select id="ru_height_mode" class="form-select">
|
||||
<option value="relativeToStartPoint">相对起点</option>
|
||||
<option value="WGS84">WGS84</option>
|
||||
<option value="realTimeFollowSurface">实时仿地</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label">返航类型</label>
|
||||
<select id="ru_go_home_type" class="form-select">
|
||||
<option value="directReturn">directReturn 直接</option>
|
||||
<option value="originalReturn">originalReturn 原路</option>
|
||||
<option value="relativeToStartPoint">relativeToStartPoint 相对起点</option>
|
||||
<option value="WGS84">WGS84 椭球高</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
|
|
@ -952,18 +936,6 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<option value="hover">hover 悬停</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label">低电动作</label>
|
||||
<select id="ru_low_battery" class="form-select">
|
||||
<option value="goBackCritAndLandEmerg">紧急返航降落</option>
|
||||
<option value="landing">landing 降落</option>
|
||||
<option value="warning">warning 警告</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6"><label class="form-label">无人机枚举 (20=Q20)</label><input id="ru_drone_enum" class="form-control" type="number" value="20"></div>
|
||||
<div class="col-6"><label class="form-label">子枚举 (0=Q20)</label><input id="ru_drone_sub" class="form-control" type="number" value="0"></div>
|
||||
<div class="col-6"><label class="form-label">挂载枚举</label><input id="ru_payload_enum" class="form-control" type="number" value="30" placeholder="30=ZT30"></div>
|
||||
<div class="col-6"><label class="form-label">挂载位置索引</label><input id="ru_payload_pos" class="form-control" type="number" value="0"></div>
|
||||
<div class="col-6"><label class="form-label">全局飞行高 (m)</label><input id="ru_flight_height" class="form-control" type="number" value="100" step="1"></div>
|
||||
<div class="col-6 d-flex align-items-end pb-1">
|
||||
<div class="form-check">
|
||||
|
|
@ -972,6 +944,26 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="cmd-title mt-3">无人机设置 (vehicle_config)</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<label class="form-label">返航类型</label>
|
||||
<select id="ru_go_home_type" class="form-select">
|
||||
<option value="directReturn">directReturn 直接</option>
|
||||
<option value="originalReturn">originalReturn 原路</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6"><label class="form-label">返航高度 (m)</label><input id="ru_vc_rth_h" class="form-control" type="number" value="100"></div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">低电动作</label>
|
||||
<select id="ru_low_battery" class="form-select">
|
||||
<option value="goBackCritAndLandEmerg">紧急返航降落</option>
|
||||
<option value="landing">landing 降落</option>
|
||||
<option value="warning">warning 警告</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 航点列表 -->
|
||||
<div class="col-lg-8">
|
||||
|
|
@ -986,6 +978,18 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<div id="ru_waypoints">
|
||||
<!-- 航点行由 JS 动态生成 -->
|
||||
</div>
|
||||
<!-- 备降点列表 -->
|
||||
<div class="d-flex align-items-center justify-content-between mb-1 mt-3">
|
||||
<h6 class="cmd-title mb-0">备降点列表 (alternate_points) <small class="text-muted">选填</small></h6>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm px-3" onclick="openMapModal('upload','alternate')"><i class="bi bi-map me-1"></i>地图选点</button>
|
||||
<button class="btn btn-success btn-sm px-3" onclick="ruAddAlternate()"><i class="bi bi-plus-circle me-1"></i>添加备降点</button>
|
||||
<button class="btn btn-danger btn-sm px-3" onclick="ruRemoveLastAlternate()"><i class="bi bi-dash-circle me-1"></i>删除末点</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ru_alternates">
|
||||
<!-- 备降点行由 JS 动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
|
|
@ -1002,7 +1006,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
|
||||
<div class="section-grid">
|
||||
|
||||
<!-- 获取航线信息 -->
|
||||
<!-- 获取航线信息(暂时停用,前后端接口先注释)
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-info-circle me-1"></i>获取航线信息</div>
|
||||
<div class="card-body">
|
||||
|
|
@ -1026,6 +1030,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<div id="ri_resp" class="resp-box" style="display:none"></div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- 执行航线 -->
|
||||
<div class="card">
|
||||
|
|
@ -1054,7 +1059,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
</div>
|
||||
<div id="re_dock_fields" style="display:none">
|
||||
<label class="form-label mt-2">机库信息 (JSON数组)</label>
|
||||
<textarea id="re_dock_info" class="form-control" rows="4" style="font-family:monospace;font-size:11px">[{"index":0,"type":"takeoff","sn":"DOCK_SN","code":"","longitude":113.0,"latitude":23.0,"altitude":0,"heading":0,"alternateSet":0}]</textarea>
|
||||
<textarea id="re_dock_info" class="form-control" rows="4" style="font-family:monospace;font-size:11px">[{"index":0,"type":"takeoff","sn":"DOCK_SN","code":"","longitude":113.0,"latitude":23.0,"altitude":0,"heading":0,"alternate_set":0}]</textarea>
|
||||
</div>
|
||||
<button class="btn btn-success w-100 mt-3" onclick="routeExecuteCmd()">
|
||||
<i class="bi bi-send me-1"></i>执行航线
|
||||
|
|
@ -1063,7 +1068,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 围栏信息 -->
|
||||
<!-- 围栏管理(暂时停用,前端先注释)
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-hexagon me-1"></i>围栏管理</div>
|
||||
<div class="card-body">
|
||||
|
|
@ -1079,6 +1084,7 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<div id="gf_resp" class="resp-box" style="display:none"></div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -1099,13 +1105,6 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<button class="btn btn-outline-secondary" type="button" onclick="fillNextWayline('fa_wayline','auto')" title="按库中最新一键航线ID自动生成"><i class="bi bi-arrow-repeat"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label">起点飞行模式</label>
|
||||
<select id="fa_fly_mode" class="form-select">
|
||||
<option value="safely">safely 安全</option>
|
||||
<option value="pointToPoint">pointToPoint 直达</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label">完成动作</label>
|
||||
<select id="fa_finish" class="form-select" onchange="document.getElementById('fa_action_value_row').style.display=this.value==='preLand'?'':'none'">
|
||||
|
|
@ -1119,14 +1118,12 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<label class="form-label">精准降落标识 (actionValue) <small class="text-muted">{hangar_sn}-{dock_code}</small></label>
|
||||
<input id="fa_action_value" class="form-control" placeholder="例: dock001-1">
|
||||
</div>
|
||||
<div class="col-6"><label class="form-label">安全起飞高 (m)</label><input id="fa_takeoff_h" class="form-control" type="number" value="30"></div>
|
||||
<div class="col-6"><label class="form-label">全局返航高 (m)</label><input id="fa_rth_h" class="form-control" type="number" value="50"></div>
|
||||
<div class="col-6"><label class="form-label">全局过渡速 (m/s)</label><input id="fa_speed" class="form-control" type="number" value="10" step="0.5"></div>
|
||||
<div class="col-6"><label class="form-label">高度模式</label>
|
||||
<select id="fa_height_mode" class="form-select">
|
||||
<option value="relativeToStartPoint">相对起点</option>
|
||||
<option value="WGS84">WGS84</option>
|
||||
<option value="realTimeFollowSurface">实时仿地</option>
|
||||
<option value="relativeToStartPoint">relativeToStartPoint 相对起点</option>
|
||||
<option value="WGS84">WGS84 椭球高</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
|
|
@ -1144,10 +1141,6 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<option value="hover">hover 悬停</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6"><label class="form-label">无人机枚举 (20=Q20)</label><input id="fa_drone_enum" class="form-control" type="number" value="20"></div>
|
||||
<div class="col-6"><label class="form-label">子枚举 (0=Q20)</label><input id="fa_drone_sub" class="form-control" type="number" value="0"></div>
|
||||
<div class="col-6"><label class="form-label">挂载枚举</label><input id="fa_payload_enum" class="form-control" type="number" value="30" placeholder="30=ZT30"></div>
|
||||
<div class="col-6"><label class="form-label">挂载位置索引</label><input id="fa_payload_pos" class="form-control" type="number" value="0"></div>
|
||||
<div class="col-6"><label class="form-label">全局飞行高 (m)</label><input id="fa_flight_height" class="form-control" type="number" value="100" step="1"></div>
|
||||
<div class="col-6 d-flex align-items-end pb-1">
|
||||
<div class="form-check">
|
||||
|
|
@ -1212,6 +1205,18 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
|||
<div id="fa_waypoints">
|
||||
<!-- 航点行由 JS 动态生成 -->
|
||||
</div>
|
||||
<!-- 备降点列表 -->
|
||||
<div class="d-flex align-items-center justify-content-between mb-1 mt-3">
|
||||
<h6 class="cmd-title mb-0">备降点列表 (alternate_points) <small class="text-muted">选填</small></h6>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm px-3" onclick="openMapModal('auto','alternate')"><i class="bi bi-map me-1"></i>地图选点</button>
|
||||
<button class="btn btn-success btn-sm px-3" onclick="faAddAlternate()"><i class="bi bi-plus-circle me-1"></i>添加备降点</button>
|
||||
<button class="btn btn-danger btn-sm px-3" onclick="faRemoveLastAlternate()"><i class="bi bi-dash-circle me-1"></i>删除末点</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="fa_alternates">
|
||||
<!-- 备降点行由 JS 动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
|
|
@ -1726,8 +1731,18 @@ async function cmdGetAndShow(action, displayId, respId) {
|
|||
|
||||
// ==================== 飞行控制 ====================
|
||||
function goHomeCmd() {
|
||||
const body = { mode: +v('gh_mode'), safeAltitude: +v('gh_safe_alt'), speed: +v('gh_speed') };
|
||||
if (document.getElementById('gh_specific_home').checked) {
|
||||
const mode = +v('gh_mode');
|
||||
const safeAltRaw = (v('gh_safe_alt') || '').trim();
|
||||
// mode=1(安全高度返回)时安全高度必填
|
||||
if (mode === 1 && !safeAltRaw) { toast('返航模式为安全高度返回(mode=1)时,必须填写安全高度', true); return; }
|
||||
const specific = document.getElementById('gh_specific_home').checked;
|
||||
// 指定返航点时返航点经纬度必填
|
||||
if (specific && (!(v('gh_hp_lng') || '').trim() || !(v('gh_hp_lat') || '').trim())) {
|
||||
toast('指定返航点时,必须填写返航点经纬度', true); return;
|
||||
}
|
||||
const body = { mode, speed: +v('gh_speed') };
|
||||
if (safeAltRaw) body.safeAltitude = +safeAltRaw;
|
||||
if (specific) {
|
||||
body.specificHome = 1;
|
||||
body.homePoint = {
|
||||
longitude: +v('gh_hp_lng'),
|
||||
|
|
@ -1788,13 +1803,13 @@ function sendDrcManual() {
|
|||
sendDrc({ vx: +v('drc_vx'), vy: +v('drc_vy'), vz: +v('drc_vz'), vyaw: +v('drc_vyaw') });
|
||||
}
|
||||
|
||||
// ==================== OTA ====================
|
||||
function otaCmd() {
|
||||
cmd('otaUpgrade', 'POST', {
|
||||
sn: v('ota_sn') || sn(), model: v('ota_model'), url: v('ota_url'),
|
||||
md5: v('ota_md5'), packageName: v('ota_pkg_name'), version: v('ota_version'), amend: v('ota_amend')
|
||||
}, 'ota_resp');
|
||||
}
|
||||
// ==================== OTA(暂时停用,前端先注释) ====================
|
||||
// function otaCmd() {
|
||||
// cmd('otaUpgrade', 'POST', {
|
||||
// sn: v('ota_sn') || sn(), model: v('ota_model'), url: v('ota_url'),
|
||||
// md5: v('ota_md5'), packageName: v('ota_pkg_name'), version: v('ota_version'), amend: v('ota_amend')
|
||||
// }, 'ota_resp');
|
||||
// }
|
||||
|
||||
// ==================== 航线任务 ====================
|
||||
// 当前时间戳 yyyyMMddHHmmss
|
||||
|
|
@ -1846,25 +1861,26 @@ async function routeJsonCmd(action, textareaId, respId) {
|
|||
request('POST', API_BASE + '/business/q20/route/' + action + '/' + s, body, respId);
|
||||
}
|
||||
|
||||
function routeInfo() {
|
||||
const s = sn();
|
||||
const mode = v('ri_mode'), val = v('ri_value').trim();
|
||||
let url = API_BASE + '/business/q20/route/info/' + s + '?mode=' + mode;
|
||||
if (val) url += '&value=' + encodeURIComponent(val);
|
||||
request('GET', url, null, 'ri_resp');
|
||||
}
|
||||
// 获取航线信息暂时停用(前后端接口先注释)
|
||||
// function routeInfo() {
|
||||
// const s = sn();
|
||||
// const mode = v('ri_mode'), val = v('ri_value').trim();
|
||||
// let url = API_BASE + '/business/q20/route/info/' + s + '?mode=' + mode;
|
||||
// if (val) url += '&value=' + encodeURIComponent(val);
|
||||
// request('GET', url, null, 'ri_resp');
|
||||
// }
|
||||
|
||||
async function routeExecuteCmd() {
|
||||
if (!await cfm()) return;
|
||||
const s = sn();
|
||||
const body = {
|
||||
mode: +v('re_mode'),
|
||||
routeId: v('re_route_id') || undefined,
|
||||
specificDock: +v('re_specific_dock'),
|
||||
dockInfo: []
|
||||
route_id: v('re_route_id') || '',
|
||||
specific_dock: +v('re_specific_dock'),
|
||||
dock_info: []
|
||||
};
|
||||
if (body.specificDock === 1) {
|
||||
try { body.dockInfo = JSON.parse(document.getElementById('re_dock_info').value); }
|
||||
if (body.specific_dock === 1) {
|
||||
try { body.dock_info = JSON.parse(document.getElementById('re_dock_info').value); }
|
||||
catch (e) { show('re_resp', '机库JSON解析失败: ' + e.message, true); return; }
|
||||
}
|
||||
request('POST', API_BASE + '/business/q20/route/execute/' + s, body, 're_resp');
|
||||
|
|
@ -1881,43 +1897,106 @@ function buildWaypointRow(prefix, idx) {
|
|||
<div class="col"><label class="form-label">经度<span class="req-star">*</span></label><input id="${prefix}_lng_${idx}" class="form-control form-control-sm" type="number" step="0.0000001" placeholder="113.000000"></div>
|
||||
<div class="col"><label class="form-label">纬度<span class="req-star">*</span></label><input id="${prefix}_lat_${idx}" class="form-control form-control-sm" type="number" step="0.0000001" placeholder="23.000000"></div>
|
||||
<div class="col-2"><label class="form-label">高度(m)<span class="req-star">*</span></label><input id="${prefix}_alt_${idx}" class="form-control form-control-sm" type="number" value="50"></div>
|
||||
<div class="col-2"><label class="form-label">速度(m/s)</label><input id="${prefix}_speed_${idx}" class="form-control form-control-sm" type="number" value="10" step="0.5"></div>
|
||||
<div class="col-2"><label class="form-label">速度(m/s)<span class="req-star" id="${prefix}_speedstar_${idx}" style="display:none">*</span></label><input id="${prefix}_speed_${idx}" class="form-control form-control-sm" type="number" value="10" step="0.5"></div>
|
||||
<div class="col-auto"><label class="form-label">自定义速</label>
|
||||
<select id="${prefix}_finish_${idx}" class="form-select form-select-sm">
|
||||
<select id="${prefix}_finish_${idx}" class="form-select form-select-sm" onchange="toggleWaypointSpeedReq('${prefix}', ${idx})">
|
||||
<option value="">全局</option><option value="custom">自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-1 align-items-end">
|
||||
<div class="col-auto" style="width:28px"></div>
|
||||
<div class="col"><label class="form-label">偏航角模式</label>
|
||||
<select id="${prefix}_heading_mode_${idx}" class="form-select form-select-sm">
|
||||
<option value="followWayline">followWayline 跟随航线</option>
|
||||
<option value="manually">manually 手动</option>
|
||||
<option value="fixed">fixed 固定</option>
|
||||
<option value="smoothTransition">smoothTransition 平滑过渡</option>
|
||||
<option value="towardPOI">towardPOI 朝向POI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col"><label class="form-label">偏航转向路径</label>
|
||||
<select id="${prefix}_heading_path_mode_${idx}" class="form-select form-select-sm">
|
||||
<option value="followBadArc">followBadArc 最短弧</option>
|
||||
<option value="clockwise">clockwise 顺时针</option>
|
||||
<option value="counterClockwise">counterClockwise 逆时针</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col"><label class="form-label">转弯模式</label>
|
||||
<select id="${prefix}_turn_mode_${idx}" class="form-select form-select-sm">
|
||||
<option value="toPointAndStopWithDiscontinuityCurvature">到点停止(非连续)</option>
|
||||
<option value="toPointAndStopWithContinuityCurvature">到点停止(连续)</option>
|
||||
<option value="toPointAndPassWithContinuityCurvature">飞越(连续)</option>
|
||||
<option value="coordinateTurn">协调转弯</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between mt-1">
|
||||
<small class="text-muted">动作组 (action_group)</small>
|
||||
<button class="btn btn-outline-success btn-sm py-0" onclick="addAction('${prefix}', ${idx})"><i class="bi bi-plus-circle me-1"></i>添加动作</button>
|
||||
</div>
|
||||
<div id="${prefix}_actions_${idx}" class="mt-1"></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// 自定义速时速度为必填:切换红色*标记,并清除可能的错误高亮
|
||||
function toggleWaypointSpeedReq(prefix, idx) {
|
||||
const custom = !!document.getElementById(prefix + '_finish_' + idx)?.value;
|
||||
const star = document.getElementById(prefix + '_speedstar_' + idx);
|
||||
if (star) star.style.display = custom ? '' : 'none';
|
||||
const speed = document.getElementById(prefix + '_speed_' + idx);
|
||||
if (speed && (!custom || (speed.value || '').trim())) speed.classList.remove('field-missing');
|
||||
}
|
||||
|
||||
// ---- 航点动作 (action_group.action) ----
|
||||
// 动作类型列表(依据 Q20航线任务.docx 动作类型)
|
||||
const ACTION_FUNCS = [
|
||||
['takePhoto', '单拍'], ['startRecord', '开始录像'], ['stopRecord', '停止录像'],
|
||||
['zoom', '变焦'], ['gimbalRotate', '旋转云台'], ['rotateYaw', '飞行器偏航'],
|
||||
['hover', '悬停等待'],
|
||||
];
|
||||
|
||||
// 各动作类型的参数字段定义(依据 Q20航线任务.docx 动作类型参数)
|
||||
// k=字段名 l=中文标签 t=类型(number/text/select) d=默认值 o=select选项
|
||||
const ACTION_PARAM_SCHEMA = {
|
||||
takePhoto: [{k:'payload_position_index',l:'挂载位置',t:'number',d:0},{k:'file_suffix',l:'文件后缀',t:'text',d:''}],
|
||||
startRecord: [{k:'payload_position_index',l:'挂载位置',t:'number',d:0},{k:'file_suffix',l:'文件后缀',t:'text',d:''}],
|
||||
stopRecord: [{k:'payload_position_index',l:'挂载位置',t:'number',d:0}],
|
||||
zoom: [{k:'payload_position_index',l:'挂载位置',t:'number',d:0},{k:'focal_length',l:'变焦焦距(mm)',t:'number',d:24}],
|
||||
gimbalRotate:[{k:'payload_position_index',l:'挂载位置',t:'number',d:0},{k:'gimbal_heading_yaw_base',l:'偏航坐标系',t:'select',o:['north'],d:'north'},{k:'gimbal_rotate_mode',l:'转动模式',t:'select',o:['absoluteAngle'],d:'absoluteAngle'},{k:'gimbal_pitch_rotate_enable',l:'使能Pitch',t:'number',d:1},{k:'gimbal_pitch_rotate_angle',l:'Pitch角度',t:'number',d:0},{k:'gimbal_roll_rotate_enable',l:'使能Roll',t:'number',d:0},{k:'gimbal_roll_rotate_angle',l:'Roll角度',t:'number',d:0},{k:'gimbal_yaw_rotate_enable',l:'使能Yaw',t:'number',d:0},{k:'gimbal_yaw_rotate_angle',l:'Yaw角度',t:'number',d:0},{k:'gimbal_rotate_time_enable',l:'使能转动时间',t:'number',d:0},{k:'gimbal_rotate_time',l:'转动用时(s)',t:'number',d:0}],
|
||||
rotateYaw: [{k:'aircraft_heading',l:'目标偏航角[-180,180]',t:'number',d:0},{k:'aircraft_path_mode',l:'转动方向',t:'select',o:['clockwise','counterClockwise'],d:'clockwise'}],
|
||||
hover: [{k:'hover_time',l:'悬停时间(s,-1恒稳)',t:'number',d:1}],
|
||||
};
|
||||
|
||||
// 每个航点的动作计数器,key = `${prefix}_${wpIdx}`,仅用于生成唯一动作行下标
|
||||
const actionCounters = {};
|
||||
|
||||
function addAction(prefix, wpIdx) {
|
||||
const key = prefix + '_' + wpIdx;
|
||||
const aIdx = (actionCounters[key] = (actionCounters[key] || 0) + 1) - 1;
|
||||
const opts = ACTION_FUNCS.map(([val, lab]) => `<option value="${val}">${val} ${lab}</option>`).join('');
|
||||
const html = `<div class="border rounded p-2 mb-1 bg-light" id="${prefix}_act_${wpIdx}_${aIdx}">
|
||||
<div class="d-flex align-items-end gap-2">
|
||||
<div class="flex-grow-1"><label class="form-label small mb-0">动作类型</label>
|
||||
<select id="${prefix}_actfunc_${wpIdx}_${aIdx}" class="form-select form-select-sm" onchange="renderActionParams('${prefix}', ${wpIdx}, ${aIdx})">${opts}</select>
|
||||
</div>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="removeAction('${prefix}', ${wpIdx}, ${aIdx})"><i class="bi bi-trash"></i></button>
|
||||
</div>
|
||||
<div class="row g-1 mt-1" id="${prefix}_actparams_${wpIdx}_${aIdx}"></div>
|
||||
</div>`;
|
||||
document.getElementById(`${prefix}_actions_${wpIdx}`).insertAdjacentHTML('beforeend', html);
|
||||
renderActionParams(prefix, wpIdx, aIdx);
|
||||
}
|
||||
|
||||
function removeAction(prefix, wpIdx, aIdx) {
|
||||
const row = document.getElementById(`${prefix}_act_${wpIdx}_${aIdx}`);
|
||||
if (row) row.remove();
|
||||
}
|
||||
|
||||
// 按动作类型动态渲染参数输入框
|
||||
function renderActionParams(prefix, wpIdx, aIdx) {
|
||||
const func = document.getElementById(`${prefix}_actfunc_${wpIdx}_${aIdx}`).value;
|
||||
const schema = ACTION_PARAM_SCHEMA[func] || [];
|
||||
document.getElementById(`${prefix}_actparams_${wpIdx}_${aIdx}`).innerHTML = schema.map(f => {
|
||||
const id = `${prefix}_actp_${wpIdx}_${aIdx}_${f.k}`;
|
||||
const input = f.t === 'select'
|
||||
? `<select id="${id}" class="form-select form-select-sm">${f.o.map(o => `<option value="${o}"${o === f.d ? ' selected' : ''}>${o}</option>`).join('')}</select>`
|
||||
: `<input id="${id}" class="form-control form-control-sm" type="${f.t}" value="${f.d}">`;
|
||||
return `<div class="col-4"><label class="form-label small mb-0">${f.l}</label>${input}</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 收集某航点下的动作列表(按 DOM 顺序)
|
||||
function collectActions(prefix, wpIdx) {
|
||||
const container = document.getElementById(`${prefix}_actions_${wpIdx}`);
|
||||
if (!container) return [];
|
||||
const actions = [];
|
||||
container.querySelectorAll(`select[id^="${prefix}_actfunc_${wpIdx}_"]`).forEach(sel => {
|
||||
const func = sel.value;
|
||||
const aIdx = sel.id.split('_').pop();
|
||||
const param = {};
|
||||
(ACTION_PARAM_SCHEMA[func] || []).forEach(f => {
|
||||
const el = document.getElementById(`${prefix}_actp_${wpIdx}_${aIdx}_${f.k}`);
|
||||
if (el) param[f.k] = f.t === 'number' ? +el.value : el.value;
|
||||
});
|
||||
actions.push({ action_id: actions.length, action_actuator_func: func, action_actuator_func_param: param });
|
||||
});
|
||||
return actions;
|
||||
}
|
||||
|
||||
function addWaypoint() {
|
||||
const container = document.getElementById('ru_waypoints');
|
||||
container.insertAdjacentHTML('beforeend', buildWaypointRow('wp', waypointCount++));
|
||||
|
|
@ -1938,62 +2017,125 @@ function collectPlacemarks(prefix, count) {
|
|||
const lat = document.getElementById(prefix + '_lat_' + i)?.value;
|
||||
if (!lng || !lat) continue;
|
||||
const useCustomSpeed = !!document.getElementById(prefix + '_finish_' + i).value;
|
||||
const headingMode = document.getElementById(prefix + '_heading_mode_' + i)?.value || 'followWayline';
|
||||
const headingPathMode = document.getElementById(prefix + '_heading_path_mode_' + i)?.value || 'followBadArc';
|
||||
const turnMode = document.getElementById(prefix + '_turn_mode_' + i)?.value || 'toPointAndStopWithDiscontinuityCurvature';
|
||||
const pm = {
|
||||
index: placemarks.length,
|
||||
point: { coordinates: lng + ',' + lat },
|
||||
execute_height: +document.getElementById(prefix + '_alt_' + i).value,
|
||||
use_global_speed: useCustomSpeed ? 0 : 1,
|
||||
waypoint_heading_param: { waypoint_heading_mode: headingMode, waypoint_heading_path_mode: headingPathMode },
|
||||
waypoint_turn_param: { waypoint_turn_mode: turnMode },
|
||||
use_straight_line: 1
|
||||
use_global_speed: useCustomSpeed ? 0 : 1
|
||||
};
|
||||
if (useCustomSpeed) {
|
||||
pm.waypoint_speed = +document.getElementById(prefix + '_speed_' + i).value;
|
||||
}
|
||||
const actions = collectActions(prefix, i);
|
||||
if (actions.length) {
|
||||
pm.action_group = { action: actions };
|
||||
}
|
||||
placemarks.push(pm);
|
||||
}
|
||||
return placemarks;
|
||||
}
|
||||
|
||||
// 校验:未使用全局速度(自定义速)的航点必须填写航点速度
|
||||
function validateWaypointSpeed(prefix, count) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const lng = document.getElementById(prefix + '_lng_' + i)?.value;
|
||||
const lat = document.getElementById(prefix + '_lat_' + i)?.value;
|
||||
if (!lng || !lat) continue;
|
||||
const custom = !!document.getElementById(prefix + '_finish_' + i)?.value;
|
||||
const speedEl = document.getElementById(prefix + '_speed_' + i);
|
||||
if (custom && !((speedEl && speedEl.value || '').trim())) {
|
||||
toast('航点' + i + '为自定义速时,必须填写速度参数', true);
|
||||
if (speedEl) {
|
||||
speedEl.classList.add('field-missing');
|
||||
speedEl.addEventListener('input', function clr() { speedEl.classList.remove('field-missing'); speedEl.removeEventListener('input', clr); });
|
||||
speedEl.focus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 备降点 (alternate_points) ----
|
||||
let ruAltCount = 0;
|
||||
let faAltCount = 0;
|
||||
|
||||
// 通用备降点行模板:prefix 区分上传航线('rualt')与一键飞行('faalt')
|
||||
function buildAlternateRow(prefix, idx) {
|
||||
return `<div class="row g-1 align-items-end mb-1" id="${prefix}_row_${idx}">
|
||||
<div class="col-auto" style="width:28px"><span class="form-label text-center d-block">${idx}</span></div>
|
||||
<div class="col"><label class="form-label">经度</label><input id="${prefix}_lng_${idx}" class="form-control form-control-sm" type="number" step="0.0000001" placeholder="114.000000"></div>
|
||||
<div class="col"><label class="form-label">纬度</label><input id="${prefix}_lat_${idx}" class="form-control form-control-sm" type="number" step="0.0000001" placeholder="22.000000"></div>
|
||||
<div class="col-3"><label class="form-label">备降高度(m)</label><input id="${prefix}_alt_${idx}" class="form-control form-control-sm" type="number" value="74"></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function ruAddAlternate() {
|
||||
document.getElementById('ru_alternates').insertAdjacentHTML('beforeend', buildAlternateRow('rualt', ruAltCount++));
|
||||
}
|
||||
function ruRemoveLastAlternate() {
|
||||
if (ruAltCount === 0) return;
|
||||
ruAltCount--;
|
||||
const row = document.getElementById('rualt_row_' + ruAltCount);
|
||||
if (row) row.remove();
|
||||
}
|
||||
function faAddAlternate() {
|
||||
document.getElementById('fa_alternates').insertAdjacentHTML('beforeend', buildAlternateRow('faalt', faAltCount++));
|
||||
}
|
||||
function faRemoveLastAlternate() {
|
||||
if (faAltCount === 0) return;
|
||||
faAltCount--;
|
||||
const row = document.getElementById('faalt_row_' + faAltCount);
|
||||
if (row) row.remove();
|
||||
}
|
||||
|
||||
// 从指定前缀的备降点表单读取 alternate_points 数组(上传/一键飞行共用)
|
||||
function collectAlternatePoints(prefix, count) {
|
||||
const points = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const lng = document.getElementById(prefix + '_lng_' + i)?.value;
|
||||
const lat = document.getElementById(prefix + '_lat_' + i)?.value;
|
||||
if (!lng || !lat) continue;
|
||||
points.push({
|
||||
point: { coordinates: lng + ',' + lat },
|
||||
alternate_height: +document.getElementById(prefix + '_alt_' + i).value
|
||||
});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function buildUploadDto() {
|
||||
const placemarks = collectPlacemarks('wp', waypointCount);
|
||||
const finishAction = v('ru_finish');
|
||||
const mc = {
|
||||
fly_to_wayline_mode: v('ru_fly_mode'),
|
||||
finish_action: finishAction,
|
||||
go_home_type: v('ru_go_home_type'),
|
||||
exit_on_rc_lost: v('ru_exit_rc_lost'),
|
||||
execute_rc_lost_action: v('ru_rc_lost_action'),
|
||||
takeoff_security_height: +v('ru_takeoff_h'),
|
||||
global_transitional_speed: +v('ru_speed'),
|
||||
global_RTH_height: +v('ru_rth_h'),
|
||||
land_height: 0,
|
||||
drone_info: { drone_enum_value: +v('ru_drone_enum'), drone_sub_enum_value: +v('ru_drone_sub') },
|
||||
payload_info: { payload_enum_value: +v('ru_payload_enum'), payload_position_index: +v('ru_payload_pos') }
|
||||
land_height: 5
|
||||
};
|
||||
if (finishAction === 'preLand') {
|
||||
const av = v('ru_action_value');
|
||||
if (av) mc.action_value = av;
|
||||
}
|
||||
const folder = {
|
||||
auto_flight_speed: +v('ru_speed'),
|
||||
execute_height_mode: v('ru_height_mode'),
|
||||
placemark: placemarks
|
||||
};
|
||||
const altPoints = collectAlternatePoints('rualt', ruAltCount);
|
||||
if (altPoints.length) folder.alternate_points = altPoints;
|
||||
return {
|
||||
routeName: v('ru_name'),
|
||||
wayline: v('ru_wayline'),
|
||||
flight_height: +v('ru_flight_height'),
|
||||
accurate_import: document.getElementById('ru_accurate_import').checked,
|
||||
mission_config: mc,
|
||||
folder: {
|
||||
template_id: 0,
|
||||
wayline_id: 0,
|
||||
auto_flight_speed: +v('ru_speed'),
|
||||
execute_height_mode: v('ru_height_mode'),
|
||||
placemark: placemarks
|
||||
},
|
||||
folder: folder,
|
||||
vehicle_config: {
|
||||
go_home_type: v('ru_go_home_type'),
|
||||
global_RTH_height: +v('ru_rth_h'),
|
||||
global_RTH_height: +v('ru_vc_rth_h'),
|
||||
low_battery_action: v('ru_low_battery')
|
||||
}
|
||||
};
|
||||
|
|
@ -2001,6 +2143,7 @@ function buildUploadDto() {
|
|||
|
||||
async function routeUploadCmd() {
|
||||
if (collectPlacemarks('wp', waypointCount).length < 2) { toast('请至少填写两个有效航点(含经纬度)', true); return; }
|
||||
if (!validateWaypointSpeed('wp', waypointCount)) return;
|
||||
if (!await cfm()) return;
|
||||
const s = sn();
|
||||
request('POST', API_BASE + '/business/q20/route/upload/' + s, buildUploadDto(), 'ru_resp');
|
||||
|
|
@ -2031,35 +2174,31 @@ function buildAutoRouteInfo() {
|
|||
const placemarks = collectPlacemarks('fawp', faWaypointCount);
|
||||
const finishAction = v('fa_finish');
|
||||
const mc = {
|
||||
fly_to_wayline_mode: v('fa_fly_mode'),
|
||||
finish_action: finishAction,
|
||||
go_home_type: v('fa_go_home_type'),
|
||||
exit_on_rc_lost: v('fa_exit_rc_lost'),
|
||||
execute_rc_lost_action: v('fa_rc_lost_action'),
|
||||
takeoff_security_height: +v('fa_takeoff_h'),
|
||||
global_transitional_speed: +v('fa_speed'),
|
||||
global_RTH_height: +v('fa_rth_h'),
|
||||
land_height: 0,
|
||||
drone_info: { drone_enum_value: +v('fa_drone_enum'), drone_sub_enum_value: +v('fa_drone_sub') },
|
||||
payload_info: { payload_enum_value: +v('fa_payload_enum'), payload_position_index: +v('fa_payload_pos') }
|
||||
land_height: 5
|
||||
};
|
||||
if (finishAction === 'preLand') {
|
||||
const av = v('fa_action_value');
|
||||
if (av) mc.action_value = av;
|
||||
}
|
||||
const folder = {
|
||||
auto_flight_speed: +v('fa_speed'),
|
||||
execute_height_mode: v('fa_height_mode'),
|
||||
placemark: placemarks
|
||||
};
|
||||
const altPoints = collectAlternatePoints('faalt', faAltCount);
|
||||
if (altPoints.length) folder.alternate_points = altPoints;
|
||||
return {
|
||||
routeName: v('fa_name'),
|
||||
wayline: v('fa_wayline'),
|
||||
flight_height: +v('fa_flight_height'),
|
||||
accurate_import: document.getElementById('fa_accurate_import').checked,
|
||||
mission_config: mc,
|
||||
folder: {
|
||||
template_id: 0,
|
||||
wayline_id: 0,
|
||||
auto_flight_speed: +v('fa_speed'),
|
||||
execute_height_mode: v('fa_height_mode'),
|
||||
placemark: placemarks
|
||||
},
|
||||
folder: folder,
|
||||
vehicle_config: {
|
||||
go_home_type: v('fa_go_home_type'),
|
||||
global_RTH_height: +v('fa_vc_rth_h'),
|
||||
|
|
@ -2092,6 +2231,7 @@ function buildAutoDto() {
|
|||
|
||||
async function routeAutoCmd() {
|
||||
if (collectPlacemarks('fawp', faWaypointCount).length < 2) { toast('请至少填写两个有效航点(含经纬度)', true); return; }
|
||||
if (!validateWaypointSpeed('fawp', faWaypointCount)) return;
|
||||
if (!await cfm()) return;
|
||||
let body;
|
||||
try { body = buildAutoDto(); }
|
||||
|
|
@ -2117,14 +2257,20 @@ let amapInitializing = false;
|
|||
let mapMarkers = [];
|
||||
let mapPolyline = null;
|
||||
let mapTarget = 'upload'; // 'upload' = 上传航线, 'auto' = 一键飞行
|
||||
let mapMode = 'waypoint'; // 'waypoint' = 航点, 'alternate' = 备降点
|
||||
|
||||
function openMapModal(target) {
|
||||
function openMapModal(target, mode) {
|
||||
mapTarget = target === 'auto' ? 'auto' : 'upload';
|
||||
mapMode = mode === 'alternate' ? 'alternate' : 'waypoint';
|
||||
const modal = document.getElementById('mapModal');
|
||||
modal.style.display = 'flex';
|
||||
document.getElementById('map_api_key').value = AMAP_KEY;
|
||||
const altSrc = mapTarget === 'auto' ? 'fa_flight_height' : 'ru_flight_height';
|
||||
document.getElementById('map_default_alt').value = document.getElementById(altSrc).value || 100;
|
||||
if (mapMode === 'alternate') {
|
||||
document.getElementById('map_default_alt').value = 74;
|
||||
} else {
|
||||
const altSrc = mapTarget === 'auto' ? 'fa_flight_height' : 'ru_flight_height';
|
||||
document.getElementById('map_default_alt').value = document.getElementById(altSrc).value || 100;
|
||||
}
|
||||
if (!amapScriptLoaded && !amapInitializing) { loadAmapWithKey(); return; }
|
||||
if (amapScriptLoaded && !amapInstance) { initAmap(); return; }
|
||||
if (amapInstance) setTimeout(() => amapInstance.resize(), 150);
|
||||
|
|
@ -2371,23 +2517,41 @@ function gcj02ToWgs84(lng, lat) {
|
|||
}
|
||||
|
||||
function confirmMapPoints() {
|
||||
if (mapMarkers.length === 0) { toast('请先在地图上选择至少一个航点', true); return; }
|
||||
const isAlt = mapMode === 'alternate';
|
||||
if (mapMarkers.length === 0) { toast(isAlt ? '请先在地图上选择至少一个备降点' : '请先在地图上选择至少一个航点', true); return; }
|
||||
const doConvert = document.getElementById('map_wgs84_convert').checked;
|
||||
const defaultAlt = +document.getElementById('map_default_alt').value || 100;
|
||||
const defaultAlt = +document.getElementById('map_default_alt').value || (isAlt ? 74 : 100);
|
||||
const isAuto = mapTarget === 'auto';
|
||||
const prefix = isAuto ? 'fawp' : 'wp';
|
||||
const addFn = isAuto ? faAddWaypoint : addWaypoint;
|
||||
document.getElementById(isAuto ? 'fa_waypoints' : 'ru_waypoints').innerHTML = '';
|
||||
if (isAuto) faWaypointCount = 0; else waypointCount = 0;
|
||||
mapMarkers.forEach(mp => {
|
||||
addFn();
|
||||
const i = (isAuto ? faWaypointCount : waypointCount) - 1;
|
||||
let lng = mp.lng, lat = mp.lat;
|
||||
if (doConvert) [lng, lat] = gcj02ToWgs84(lng, lat);
|
||||
document.getElementById(prefix + '_lng_' + i).value = lng.toFixed(7);
|
||||
document.getElementById(prefix + '_lat_' + i).value = lat.toFixed(7);
|
||||
document.getElementById(prefix + '_alt_' + i).value = defaultAlt;
|
||||
});
|
||||
if (isAlt) {
|
||||
const prefix = isAuto ? 'faalt' : 'rualt';
|
||||
const addFn = isAuto ? faAddAlternate : ruAddAlternate;
|
||||
document.getElementById(isAuto ? 'fa_alternates' : 'ru_alternates').innerHTML = '';
|
||||
if (isAuto) faAltCount = 0; else ruAltCount = 0;
|
||||
mapMarkers.forEach(mp => {
|
||||
addFn();
|
||||
const i = (isAuto ? faAltCount : ruAltCount) - 1;
|
||||
let lng = mp.lng, lat = mp.lat;
|
||||
if (doConvert) [lng, lat] = gcj02ToWgs84(lng, lat);
|
||||
document.getElementById(prefix + '_lng_' + i).value = lng.toFixed(7);
|
||||
document.getElementById(prefix + '_lat_' + i).value = lat.toFixed(7);
|
||||
document.getElementById(prefix + '_alt_' + i).value = defaultAlt;
|
||||
});
|
||||
} else {
|
||||
const prefix = isAuto ? 'fawp' : 'wp';
|
||||
const addFn = isAuto ? faAddWaypoint : addWaypoint;
|
||||
document.getElementById(isAuto ? 'fa_waypoints' : 'ru_waypoints').innerHTML = '';
|
||||
if (isAuto) faWaypointCount = 0; else waypointCount = 0;
|
||||
Object.keys(actionCounters).forEach(k => { if (k.startsWith(prefix + '_')) delete actionCounters[k]; });
|
||||
mapMarkers.forEach(mp => {
|
||||
addFn();
|
||||
const i = (isAuto ? faWaypointCount : waypointCount) - 1;
|
||||
let lng = mp.lng, lat = mp.lat;
|
||||
if (doConvert) [lng, lat] = gcj02ToWgs84(lng, lat);
|
||||
document.getElementById(prefix + '_lng_' + i).value = lng.toFixed(7);
|
||||
document.getElementById(prefix + '_lat_' + i).value = lat.toFixed(7);
|
||||
document.getElementById(prefix + '_alt_' + i).value = defaultAlt;
|
||||
});
|
||||
}
|
||||
closeMapModal();
|
||||
}
|
||||
|
||||
|
|
@ -2427,19 +2591,19 @@ const FIELD_REQ = {
|
|||
// —— OTA ——
|
||||
ota_sn: 1, ota_model: 0, ota_url: 1, ota_md5: 1, ota_pkg_name: 0, ota_version: 1, ota_amend: 0,
|
||||
// —— 航线:上传航线 ——
|
||||
ru_name: 1, ru_wayline: 1, ru_fly_mode: 0, ru_finish: 1, ru_action_value: 0,
|
||||
ru_takeoff_h: 0, ru_rth_h: 0, ru_speed: 1, ru_height_mode: 1,
|
||||
ru_go_home_type: 0, ru_exit_rc_lost: 0, ru_rc_lost_action: 0, ru_low_battery: 0,
|
||||
ru_drone_enum: 0, ru_drone_sub: 0, ru_payload_enum: 0, ru_payload_pos: 0, ru_flight_height: 0, ru_accurate_import: 0,
|
||||
ru_name: 1, ru_wayline: 1, ru_finish: 1, ru_action_value: 0,
|
||||
ru_rth_h: 0, ru_speed: 1, ru_height_mode: 1,
|
||||
ru_go_home_type: 0, ru_exit_rc_lost: 0, ru_rc_lost_action: 0, ru_low_battery: 0, ru_vc_rth_h: 0,
|
||||
ru_flight_height: 0, ru_accurate_import: 0,
|
||||
// —— 航线:获取/执行/围栏 ——
|
||||
ri_mode: 1, ri_value: 0,
|
||||
re_mode: 1, re_route_id: 0, re_specific_dock: 0, re_dock_info: 0,
|
||||
gf_upload_json: 1,
|
||||
// —— 一键飞行 ——
|
||||
fa_name: 1, fa_wayline: 1, fa_fly_mode: 0, fa_finish: 1, fa_action_value: 0,
|
||||
fa_takeoff_h: 0, fa_rth_h: 0, fa_speed: 1, fa_height_mode: 1,
|
||||
fa_name: 1, fa_wayline: 1, fa_finish: 1, fa_action_value: 0,
|
||||
fa_rth_h: 0, fa_speed: 1, fa_height_mode: 1,
|
||||
fa_exit_rc_lost: 0, fa_rc_lost_action: 0,
|
||||
fa_drone_enum: 0, fa_drone_sub: 0, fa_payload_enum: 0, fa_payload_pos: 0, fa_flight_height: 0, fa_accurate_import: 0,
|
||||
fa_flight_height: 0, fa_accurate_import: 0,
|
||||
fa_go_home_type: 0, fa_vc_rth_h: 0, fa_low_battery: 0,
|
||||
fa_exec_mode: 1, fa_specific_dock: 0, fa_dock_info: 0,
|
||||
};
|
||||
|
|
@ -2478,6 +2642,19 @@ function markField(id, required) {
|
|||
const CONDITIONAL_REQ = {
|
||||
re_route_id: () => v('re_mode') === '2',
|
||||
ri_value: () => v('ri_mode') === '2',
|
||||
// 结束动作为精准降落(preLand)时,必须填写精准降落二维码值(action_value)
|
||||
ru_action_value: () => v('ru_finish') === 'preLand',
|
||||
fa_action_value: () => v('fa_finish') === 'preLand',
|
||||
// 结束动作为返航(goHome)时,必须填写返航类型(go_home_type)
|
||||
ru_go_home_type: () => v('ru_finish') === 'goHome',
|
||||
fa_go_home_type: () => v('fa_finish') === 'goHome',
|
||||
// 失联退出策略为执行失控动作(executeLostAction)时,必须填写失联执行动作(execute_rc_lost_action)
|
||||
ru_rc_lost_action: () => v('ru_exit_rc_lost') === 'executeLostAction',
|
||||
fa_rc_lost_action: () => v('fa_exit_rc_lost') === 'executeLostAction',
|
||||
// 返航:mode=1(安全高度返回)时安全高度必填;指定返航点时返航点经纬度必填
|
||||
gh_safe_alt: () => v('gh_mode') === '1',
|
||||
gh_hp_lng: () => !!document.getElementById('gh_specific_home')?.checked,
|
||||
gh_hp_lat: () => !!document.getElementById('gh_specific_home')?.checked,
|
||||
};
|
||||
// 字段当前是否必填(含条件必填)
|
||||
function isRequiredNow(id) {
|
||||
|
|
@ -2544,13 +2721,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
const savedSn = localStorage.getItem('q20_device_sn');
|
||||
if (savedSn) {
|
||||
document.getElementById('deviceSn').value = savedSn;
|
||||
document.getElementById('ota_sn').value = savedSn;
|
||||
const otaSnEl = document.getElementById('ota_sn');
|
||||
if (otaSnEl) otaSnEl.value = savedSn;
|
||||
}
|
||||
|
||||
document.getElementById('deviceSn').addEventListener('input', function () {
|
||||
const val = this.value.trim();
|
||||
refreshSnState();
|
||||
document.getElementById('ota_sn').value = val;
|
||||
const otaSnEl = document.getElementById('ota_sn');
|
||||
if (otaSnEl) otaSnEl.value = val;
|
||||
// 记住本次输入,下次进入页面自动回填
|
||||
if (val) localStorage.setItem('q20_device_sn', val);
|
||||
else localStorage.removeItem('q20_device_sn');
|
||||
|
|
@ -2572,8 +2751,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
document.getElementById('fa_dock_fields').style.display = this.value === '1' ? '' : 'none';
|
||||
});
|
||||
|
||||
// 执行模式联动:选 2(指定 route_id) 时 route_id 变为必填,实时刷新标记
|
||||
['re_mode', 'ri_mode'].forEach(id => {
|
||||
// 条件必填联动:route_id / action_value / go_home_type / execute_rc_lost_action / 返航参数 随相关选择实时刷新标记
|
||||
['re_mode', 'ri_mode', 'ru_finish', 'fa_finish', 'ru_exit_rc_lost', 'fa_exit_rc_lost', 'gh_mode', 'gh_specific_home'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.addEventListener('change', refreshConditionalMarkers);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue