1.新增Q20飞机参数指令11个接口
2.新增Q20飞机控制指令12个接口 3.新增Q20飞机航线任务13个接口 4.新增Q20飞机设备拓扑状态2个接口 5.新增Q20飞机切面日志
This commit is contained in:
parent
0aa306b8a9
commit
e23e95c511
|
|
@ -0,0 +1,51 @@
|
|||
package com.multictrl.common.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Q20 service 调用日志切面
|
||||
* 在每个 Q20 service 方法执行前后打印开始/结束及耗时,异常时打印错误信息
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class Q20ServiceLogAspect {
|
||||
|
||||
@Pointcut("execution(public * com.multictrl.modules.business.q20.service.impl.*.*(..))")
|
||||
public void q20ServicePointcut() {
|
||||
}
|
||||
|
||||
@Around("q20ServicePointcut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
MethodSignature sig = (MethodSignature) point.getSignature();
|
||||
String className = sig.getDeclaringType().getSimpleName();
|
||||
String methodName = sig.getName();
|
||||
String tag = className + "." + methodName;
|
||||
|
||||
// 第一个参数通常是 deviceSn
|
||||
Object[] args = point.getArgs();
|
||||
String deviceSn = (args != null && args.length > 0 && args[0] instanceof String)
|
||||
? (String) args[0] : "-";
|
||||
|
||||
log.info("Q20 [{}] 开始 deviceSn={}", tag, deviceSn);
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
Object result = point.proceed();
|
||||
log.info("Q20 [{}] 结束 deviceSn={} 耗时={}ms", tag, deviceSn, System.currentTimeMillis() - start);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("Q20 [{}] 异常 deviceSn={} 耗时={}ms error={}",
|
||||
tag, deviceSn, System.currentTimeMillis() - start, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.multictrl.common.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 注册 Jackson 2.x ObjectMapper bean。
|
||||
* Spring Boot 4.x 默认只注册 tools.jackson.databind.ObjectMapper(Jackson 3.x),
|
||||
* 项目中使用 com.fasterxml.jackson.annotation.JsonProperty 的代码需要此 bean。
|
||||
*/
|
||||
@Configuration
|
||||
public class Jackson2ObjectMapperConfig {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
|
|
@ -104,6 +104,10 @@ public class RouteDTO implements Serializable {
|
|||
@Schema(description = "kmz地址")
|
||||
private String kmzUrl;
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@Schema(description = "Q20航线ID(Q20设备上报的wayline ID)")
|
||||
private String q20RouteId;
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@Schema(description = "航点个数")
|
||||
private Integer waypointNum;
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ public class RouteEntity extends BaseEntity {
|
|||
* kmz地址
|
||||
*/
|
||||
private String kmzUrl;
|
||||
/**
|
||||
* Q20航线ID(Q20设备上报的wayline ID)
|
||||
*/
|
||||
private String q20RouteId;
|
||||
/**
|
||||
* 航点个数
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.multictrl.common.constant.BusinessConstant;
|
|||
import com.multictrl.modules.business.q20.handler.Q20EventsHandler;
|
||||
import com.multictrl.modules.business.q20.handler.Q20OsdTopicHandler;
|
||||
import com.multictrl.modules.business.q20.handler.Q20StateTopicHandler;
|
||||
import com.multictrl.modules.business.q20.handler.Q20StatusHandler;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -32,10 +33,12 @@ public class TopicDistributor {
|
|||
private final Q20OsdTopicHandler q20OsdTopicHandler;
|
||||
private final Q20EventsHandler q20EventsHandler;
|
||||
private final Q20StateTopicHandler q20StateTopicHandler;
|
||||
private final Q20StatusHandler q20StatusHandler;
|
||||
private final Map<String, MessageHandler> handlerMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, MessageHandler> q20HandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static final String Q20_TOPIC_PREFIX = "thing/device/";
|
||||
private static final String Q20_THING_PREFIX = "thing/device/";
|
||||
private static final String Q20_SYS_PREFIX = "sys/device/";
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
|
@ -49,13 +52,16 @@ public class TopicDistributor {
|
|||
q20HandlerMap.put(BusinessConstant.OSD, q20OsdTopicHandler);
|
||||
q20HandlerMap.put(BusinessConstant.EVENTS, q20EventsHandler);
|
||||
q20HandlerMap.put(BusinessConstant.STATE, q20StateTopicHandler);
|
||||
q20HandlerMap.put(BusinessConstant.STATUS, q20StatusHandler);
|
||||
q20HandlerMap.put(BusinessConstant.SERVICES_REPLY, servicesReplyHandler);
|
||||
}
|
||||
|
||||
public void route(String topic, String payload) {
|
||||
String method = StrUtil.subAfter(topic, "/", true);
|
||||
String gateway = StrUtil.subAfter(StrUtil.subBefore(topic, "/", true),
|
||||
"/", true);
|
||||
Map<String, MessageHandler> map = topic.startsWith(Q20_TOPIC_PREFIX) ? q20HandlerMap : handlerMap;
|
||||
boolean isQ20 = topic.startsWith(Q20_THING_PREFIX) || topic.startsWith(Q20_SYS_PREFIX);
|
||||
Map<String, MessageHandler> map = isQ20 ? q20HandlerMap : handlerMap;
|
||||
MessageHandler handler = map.get(method);
|
||||
if (handler != null) {
|
||||
handler.handleMessage(topic, payload, gateway);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,344 @@
|
|||
package com.multictrl.modules.business.q20.controller;
|
||||
|
||||
import com.multictrl.common.annotation.ApiOrder;
|
||||
import com.multictrl.common.annotation.LogOperation;
|
||||
import com.multictrl.common.utils.Result;
|
||||
import com.multictrl.modules.business.q20.dto.*;
|
||||
import com.multictrl.modules.business.q20.service.Q20CommandService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Q20控制指令接口
|
||||
* topic: thing/device/{device_sn}/services (下行)
|
||||
* 回复: thing/device/{device_sn}/services_reply (上行)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("business/q20/cmd")
|
||||
@Tag(name = "Q20控制指令")
|
||||
@ApiOrder(12)
|
||||
@RequiredArgsConstructor
|
||||
public class Q20CommandController {
|
||||
|
||||
private final Q20CommandService q20CommandService;
|
||||
|
||||
// ==================== 飞行控制 ====================
|
||||
|
||||
@PostMapping("/takeoff/{deviceSn}")
|
||||
@LogOperation("Q20起飞")
|
||||
@Operation(summary = "起飞: mode=0绝对高度/1相对高度")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> takeoff(@PathVariable String deviceSn, @RequestBody Q20TakeoffDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.takeoff(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/land/{deviceSn}")
|
||||
@LogOperation("Q20降落")
|
||||
@Operation(summary = "降落: mode=0直接降落/1精准降落")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> land(@PathVariable String deviceSn, @RequestBody Q20LandDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.land(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/stop/{deviceSn}")
|
||||
@LogOperation("Q20紧急悬停")
|
||||
@Operation(summary = "紧急悬停(value固定为1)")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> stop(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20CommandService.stop(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/goHome/{deviceSn}")
|
||||
@LogOperation("Q20返航")
|
||||
@Operation(summary = "返航: mode=1安全高度/2直线飞行/3原路返回")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> goHome(@PathVariable String deviceSn, @RequestBody Q20GoHomeDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.goHome(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/setHome/{deviceSn}")
|
||||
@LogOperation("Q20设置返航点")
|
||||
@Operation(summary = "设置返航点坐标")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> setHome(@PathVariable String deviceSn, @RequestBody Q20SetHomeDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.setHome(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/navigate/{deviceSn}")
|
||||
@LogOperation("Q20航点飞行")
|
||||
@Operation(summary = "飞往指定坐标: action=noAction/land/precland")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> navigate(@PathVariable String deviceSn, @RequestBody Q20NavigateDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.navigate(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 云台相机 ====================
|
||||
|
||||
@PostMapping("/gimbalControl/{deviceSn}")
|
||||
@LogOperation("Q20云台控制")
|
||||
@Operation(summary = "云台控制: mode=0速度模式/1绝对值模式")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> gimbalControl(@PathVariable String deviceSn, @RequestBody Q20GimbalControlDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.gimbalControl(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/mountTda/{deviceSn}")
|
||||
@LogOperation("Q20挂载TDA数据")
|
||||
@Operation(summary = "挂载TDA数据(仅支持deviceID=68的负载)")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> mountTda(@PathVariable String deviceSn, @RequestBody Q20MountTdaDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.mountTda(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/cameraPhotoTake/{deviceSn}")
|
||||
@LogOperation("Q20拍照")
|
||||
@Operation(summary = "相机拍照")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> cameraPhotoTake(@PathVariable String deviceSn, @RequestBody Q20CameraDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.cameraPhotoTake(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/cameraRecordingStart/{deviceSn}")
|
||||
@LogOperation("Q20开始录像")
|
||||
@Operation(summary = "开始录像")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> cameraRecordingStart(@PathVariable String deviceSn, @RequestBody Q20CameraDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.cameraRecordingStart(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/cameraRecordingStop/{deviceSn}")
|
||||
@LogOperation("Q20停止录像")
|
||||
@Operation(summary = "停止录像")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> cameraRecordingStop(@PathVariable String deviceSn, @RequestBody Q20CameraDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.cameraRecordingStop(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/cameraFocalLengthSet/{deviceSn}")
|
||||
@LogOperation("Q20设置焦距")
|
||||
@Operation(summary = "设置相机焦距: zoom_type=0绝对值/1连续, zoom_mode=0数字/1光学")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> cameraFocalLengthSet(@PathVariable String deviceSn, @RequestBody Q20CameraFocalLengthDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.cameraFocalLengthSet(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 直播 ====================
|
||||
|
||||
@PostMapping("/liveStartPush/{deviceSn}")
|
||||
@LogOperation("Q20开始直播")
|
||||
@Operation(summary = "开始直播推流: url_type=0Agora/1RTMP/2RTSP/3GB28181/4WebRTC")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> liveStartPush(@PathVariable String deviceSn, @RequestBody Q20LiveStartDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.liveStartPush(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/liveStopPush/{deviceSn}")
|
||||
@LogOperation("Q20停止直播")
|
||||
@Operation(summary = "停止直播推流")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> liveStopPush(@PathVariable String deviceSn,
|
||||
@RequestParam int payloadIndex,
|
||||
@RequestParam int video) {
|
||||
return new Result<>().ok(q20CommandService.liveStopPush(deviceSn, payloadIndex, video));
|
||||
}
|
||||
|
||||
@PostMapping("/imageSwitch/{deviceSn}")
|
||||
@LogOperation("Q20图像切换")
|
||||
@Operation(summary = "图像切换(红外/可见光/激光)")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> imageSwitch(@PathVariable String deviceSn, @RequestBody Q20ImageSwitchDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.imageSwitch(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 设备控制 ====================
|
||||
|
||||
@PostMapping("/deviceCharge/{deviceSn}")
|
||||
@LogOperation("Q20充电控制")
|
||||
@Operation(summary = "充电控制: version=1(1.0版本)/2(2.0版本)")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> deviceCharge(@PathVariable String deviceSn,
|
||||
@RequestParam int value,
|
||||
@RequestParam int version) {
|
||||
return new Result<>().ok(q20CommandService.deviceCharge(deviceSn, value, version));
|
||||
}
|
||||
|
||||
@PostMapping("/deviceBoot/{deviceSn}")
|
||||
@LogOperation("Q20重启/关机")
|
||||
@Operation(summary = "设备控制: value=0关机/1重启/2硬重启/3锁定")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> deviceBoot(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20CommandService.deviceBoot(deviceSn, value));
|
||||
}
|
||||
|
||||
@PostMapping("/safetySwitch/{deviceSn}")
|
||||
@LogOperation("Q20安全开关")
|
||||
@Operation(summary = "安全开关控制")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> safetySwitch(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20CommandService.safetySwitch(deviceSn, value));
|
||||
}
|
||||
|
||||
// ==================== 负载 ====================
|
||||
|
||||
@PostMapping("/throwingLock/{deviceSn}")
|
||||
@LogOperation("Q20投掷锁控制")
|
||||
@Operation(summary = "投掷锁/解锁: value=0解锁/1锁定")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> throwingLock(@PathVariable String deviceSn, @RequestBody Q20ThrowingDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.throwingLock(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/throwingExecution/{deviceSn}")
|
||||
@LogOperation("Q20执行投掷")
|
||||
@Operation(summary = "执行投掷动作")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> throwingExecution(@PathVariable String deviceSn, @RequestParam long deviceId) {
|
||||
return new Result<>().ok(q20CommandService.throwingExecution(deviceSn, deviceId));
|
||||
}
|
||||
|
||||
@PostMapping("/halyarding/{deviceSn}")
|
||||
@LogOperation("Q20绞车控制")
|
||||
@Operation(summary = "绞车控制")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> halyarding(@PathVariable String deviceSn, @RequestBody Q20HalyardingDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.halyarding(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/logistics/{deviceSn}")
|
||||
@LogOperation("Q20物流控制")
|
||||
@Operation(summary = "物流控制")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> logistics(@PathVariable String deviceSn,
|
||||
@RequestParam int value,
|
||||
@RequestParam boolean force) {
|
||||
return new Result<>().ok(q20CommandService.logistics(deviceSn, value, force));
|
||||
}
|
||||
|
||||
// ==================== 虚拟摇杆 ====================
|
||||
|
||||
@PostMapping("/drc/{deviceSn}")
|
||||
@Operation(summary = "虚拟摇杆控制(高频指令,不等回复)")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public void drc(@PathVariable String deviceSn, @RequestBody Q20DrcDTO dto) {
|
||||
q20CommandService.drc(deviceSn, dto);
|
||||
}
|
||||
|
||||
// ==================== 安全/降落伞 ====================
|
||||
|
||||
@PostMapping("/setAutoParachute/{deviceSn}")
|
||||
@LogOperation("Q20设置自动开伞")
|
||||
@Operation(summary = "设置自动开伞开关")
|
||||
@RequiresPermissions("bus:q20:parachute")
|
||||
public Result<Object> setAutoParachute(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20CommandService.setAutoParachute(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getEmergencyStopOtp/{deviceSn}")
|
||||
@Operation(summary = "获取紧急停止OTP验证码")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> getEmergencyStopOtp(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20CommandService.getEmergencyStopOtp(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/emergencyStop/{deviceSn}")
|
||||
@LogOperation("Q20紧急停止")
|
||||
@Operation(summary = "紧急停止(需先获取OTP)")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> emergencyStop(@PathVariable String deviceSn, @RequestBody Q20EmergencyStopDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.emergencyStop(deviceSn, dto));
|
||||
}
|
||||
|
||||
@GetMapping("/getParachuteOtp/{deviceSn}")
|
||||
@Operation(summary = "获取开伞OTP验证码")
|
||||
@RequiresPermissions("bus:q20:parachute")
|
||||
public Result<Object> getParachuteOtp(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20CommandService.getParachuteOtp(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/openParachute/{deviceSn}")
|
||||
@LogOperation("Q20开伞")
|
||||
@Operation(summary = "开伞(otp=99时为强制开伞)")
|
||||
@RequiresPermissions("bus:q20:parachute")
|
||||
public Result<Object> openParachute(@PathVariable String deviceSn, @RequestBody Q20OpenParachuteDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.openParachute(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/confirmParachuteSafety/{deviceSn}")
|
||||
@LogOperation("Q20确认开伞安全")
|
||||
@Operation(summary = "确认开伞安全")
|
||||
@RequiresPermissions("bus:q20:parachute")
|
||||
public Result<Object> confirmParachuteSafety(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20CommandService.confirmParachuteSafety(deviceSn, value));
|
||||
}
|
||||
|
||||
// ==================== 状态/信息 ====================
|
||||
|
||||
@GetMapping("/queryStatus/{deviceSn}")
|
||||
@Operation(summary = "查询飞行状态: query_value=liftoff/arrival等")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> queryStatus(@PathVariable String deviceSn, @RequestBody Q20QueryStatusDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.queryStatus(deviceSn, dto));
|
||||
}
|
||||
|
||||
@GetMapping("/config/{deviceSn}")
|
||||
@Operation(summary = "获取设备配置")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> getConfig(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20CommandService.getConfig(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/stereoImage/{deviceSn}")
|
||||
@LogOperation("Q20立体图像开关")
|
||||
@Operation(summary = "立体图像开关: 0=开启/1=关闭")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> stereoImage(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20CommandService.stereoImage(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/version/{deviceSn}")
|
||||
@Operation(summary = "获取设备固件版本")
|
||||
@RequiresPermissions("bus:q20:cmd")
|
||||
public Result<Object> version(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20CommandService.version(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 日志 ====================
|
||||
|
||||
@GetMapping("/logCount/{deviceSn}")
|
||||
@Operation(summary = "获取日志数量")
|
||||
@RequiresPermissions("bus:q20:log")
|
||||
public Result<Object> logCount(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20CommandService.logCount(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/logList/{deviceSn}")
|
||||
@Operation(summary = "获取日志列表")
|
||||
@RequiresPermissions("bus:q20:log")
|
||||
public Result<Object> logList(@PathVariable String deviceSn, @RequestBody Q20LogListDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.logList(deviceSn, dto));
|
||||
}
|
||||
|
||||
@PostMapping("/logData/{deviceSn}")
|
||||
@LogOperation("Q20上传日志")
|
||||
@Operation(summary = "上传日志到OSS")
|
||||
@RequiresPermissions("bus:q20:log")
|
||||
public Result<Object> logData(@PathVariable String deviceSn, @RequestBody Q20LogDataDTO dto) {
|
||||
return new Result<>().ok(q20CommandService.logData(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.multictrl.modules.business.q20.controller;
|
||||
|
||||
import com.multictrl.common.annotation.ApiOrder;
|
||||
import com.multictrl.common.utils.Result;
|
||||
import com.multictrl.modules.business.q20.service.Q20DeviceService;
|
||||
import com.multictrl.modules.business.q20.vo.Q20DeviceStatusVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20飞机管理接口
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("business/q20")
|
||||
@Tag(name = "Q20飞机管理")
|
||||
@ApiOrder(10)
|
||||
@RequiredArgsConstructor
|
||||
public class Q20Controller {
|
||||
|
||||
private final Q20DeviceService q20DeviceService;
|
||||
|
||||
@GetMapping("/online/{deviceSn}")
|
||||
@Operation(summary = "查询飞机在线状态,在线时同时返回最新OSD数据")
|
||||
@RequiresPermissions("bus:q20:online")
|
||||
public Result<Q20DeviceStatusVO> getOnlineStatus(@PathVariable String deviceSn) {
|
||||
return new Result<Q20DeviceStatusVO>().ok(q20DeviceService.getOnlineStatus(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/onlineBatch")
|
||||
@Operation(summary = "批量查询飞机在线状态")
|
||||
@RequiresPermissions("bus:q20:online")
|
||||
public Result<List<Q20DeviceStatusVO>> getOnlineStatusBatch(@RequestBody List<String> deviceSnList) {
|
||||
return new Result<List<Q20DeviceStatusVO>>().ok(q20DeviceService.getOnlineStatusBatch(deviceSnList));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.multictrl.modules.business.q20.controller;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.common.exception.RenException;
|
||||
import com.multictrl.common.utils.Result;
|
||||
import com.multictrl.modules.business.service.DJIBaseService;
|
||||
import com.multictrl.modules.business.service.MqttPushService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Q20 MQTT模拟接口(测试/调试用)
|
||||
* - /reply : 模拟设备对 services 指令的回复(注入 CacheUtils)
|
||||
* - /aircraft: 模拟飞机主动上报消息(发布到对应 MQTT topic,经过正常 handler 处理)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("business/q20/mock")
|
||||
@Tag(name = "Q20 MQTT模拟(测试)")
|
||||
@RequiredArgsConstructor
|
||||
public class Q20MockController {
|
||||
|
||||
private final DJIBaseService djiBaseService;
|
||||
private final MqttPushService mqttPushService;
|
||||
|
||||
// ────────── 指令模拟回复 ──────────
|
||||
|
||||
@GetMapping("/pending/{deviceSn}")
|
||||
@Operation(summary = "获取当前待回复命令信息")
|
||||
public Result<Object> getPending(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(djiBaseService.getPendingCmd(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/reply/{deviceSn}")
|
||||
@Operation(summary = "注入模拟MQTT回复(直接写入缓存,body为完整回复JSON)")
|
||||
public Result<Object> injectReply(
|
||||
@PathVariable String deviceSn,
|
||||
@RequestBody JSONObject replyData) {
|
||||
djiBaseService.injectMockReply(deviceSn, replyData);
|
||||
return new Result<>().ok("模拟回复已注入");
|
||||
}
|
||||
|
||||
// ────────── 飞机主动上报模拟 ──────────
|
||||
|
||||
/**
|
||||
* 模拟飞机发布 MQTT 消息到指定 topic
|
||||
* topicType: status | osd | events | services_reply
|
||||
* 消息由前端构造完整 payload,后端直接转发到对应 topic
|
||||
*/
|
||||
@PostMapping("/aircraft/{deviceSn}")
|
||||
@Operation(summary = "模拟飞机上报消息(发布到MQTT topic)")
|
||||
public Result<Object> aircraftPublish(
|
||||
@PathVariable String deviceSn,
|
||||
@RequestParam String topicType,
|
||||
@RequestBody JSONObject payload) {
|
||||
String topic = buildTopic(deviceSn, topicType);
|
||||
// 补充缺失的公共字段
|
||||
if (!payload.containsKey("tid")) payload.set("tid", IdUtil.fastUUID());
|
||||
if (!payload.containsKey("bid")) payload.set("bid", IdUtil.fastUUID());
|
||||
if (!payload.containsKey("timestamp")) payload.set("timestamp", System.currentTimeMillis());
|
||||
if (!payload.containsKey("gateway")) payload.set("gateway", deviceSn);
|
||||
mqttPushService.pushMessageByClient1(topic, payload.toString());
|
||||
return new Result<>().ok("已发布 → " + topic);
|
||||
}
|
||||
|
||||
private String buildTopic(String deviceSn, String topicType) {
|
||||
return switch (topicType) {
|
||||
case "status" -> "sys/device/" + deviceSn + "/status";
|
||||
case "osd" -> "thing/device/" + deviceSn + "/osd";
|
||||
case "events" -> "thing/device/" + deviceSn + "/events";
|
||||
case "services_reply" -> "thing/device/" + deviceSn + "/services_reply";
|
||||
default -> throw new RenException("未知 topicType: " + topicType);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
package com.multictrl.modules.business.q20.controller;
|
||||
|
||||
import com.multictrl.common.annotation.ApiOrder;
|
||||
import com.multictrl.common.annotation.LogOperation;
|
||||
import com.multictrl.common.utils.Result;
|
||||
import com.multictrl.modules.business.q20.dto.Q20ParamsDTO;
|
||||
import com.multictrl.modules.business.q20.service.Q20ParamService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Q20参数指令接口
|
||||
* topic: thing/device/{device_sn}/services (下行)
|
||||
* 回复: thing/device/{device_sn}/services_reply (上行)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("business/q20/param")
|
||||
@Tag(name = "Q20参数指令")
|
||||
@ApiOrder(11)
|
||||
@RequiredArgsConstructor
|
||||
public class Q20ParamController {
|
||||
|
||||
private final Q20ParamService q20ParamService;
|
||||
|
||||
// ==================== 高度限制 ====================
|
||||
|
||||
@PostMapping("/setHeightLimit/{deviceSn}")
|
||||
@LogOperation("设置高度限制")
|
||||
@Operation(summary = "设置高度限制(m),0=无限制")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setHeightLimit(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setHeightLimit(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getHeightLimit/{deviceSn}")
|
||||
@Operation(summary = "获取当前高度限制(m)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getHeightLimit(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getHeightLimit(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 距离限制 ====================
|
||||
|
||||
@PostMapping("/setDistanceLimit/{deviceSn}")
|
||||
@LogOperation("设置距离限制")
|
||||
@Operation(summary = "设置距离限制(m),0=无限制")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setDistanceLimit(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setDistanceLimit(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getDistanceLimit/{deviceSn}")
|
||||
@Operation(summary = "获取当前距离限制(m)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getDistanceLimit(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getDistanceLimit(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 围栏动作 ====================
|
||||
|
||||
@PostMapping("/setGeofenceAction/{deviceSn}")
|
||||
@LogOperation("设置围栏动作")
|
||||
@Operation(summary = "设置围栏动作: goContinue/warning/hover/goBack/terminate/landing")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setGeofenceAction(@PathVariable String deviceSn, @RequestParam String value) {
|
||||
return new Result<>().ok(q20ParamService.setGeofenceAction(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getGeofenceAction/{deviceSn}")
|
||||
@Operation(summary = "获取当前围栏动作")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getGeofenceAction(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getGeofenceAction(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 返航高度 ====================
|
||||
|
||||
@PostMapping("/setReturnHeight/{deviceSn}")
|
||||
@LogOperation("设置返航高度")
|
||||
@Operation(summary = "设置返航高度(m),相对home点高度")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setReturnHeight(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setReturnHeight(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getReturnHeight/{deviceSn}")
|
||||
@Operation(summary = "获取当前返航高度(m)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getReturnHeight(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getReturnHeight(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 返航类型 ====================
|
||||
|
||||
@PostMapping("/setReturnType/{deviceSn}")
|
||||
@LogOperation("设置返航类型")
|
||||
@Operation(summary = "设置返航类型: directReturn(直接返航) / originalReturn(原路返航)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setReturnType(@PathVariable String deviceSn, @RequestParam String value) {
|
||||
return new Result<>().ok(q20ParamService.setReturnType(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getReturnType/{deviceSn}")
|
||||
@Operation(summary = "获取当前返航类型")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getReturnType(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getReturnType(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 电池低电阈值 ====================
|
||||
|
||||
@PostMapping("/setBatteryLowThreshold/{deviceSn}")
|
||||
@LogOperation("设置低电阈值")
|
||||
@Operation(summary = "设置低电阈值(比例,如0.3=30%)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setBatteryLowThreshold(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setBatteryLowThreshold(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getBatteryLowThreshold/{deviceSn}")
|
||||
@Operation(summary = "获取当前低电阈值")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getBatteryLowThreshold(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getBatteryLowThreshold(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 严重低电阈值 ====================
|
||||
|
||||
@PostMapping("/setBatteryCriticalThreshold/{deviceSn}")
|
||||
@LogOperation("设置严重低电阈值")
|
||||
@Operation(summary = "设置严重低电阈值(比例)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setBatteryCriticalThreshold(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setBatteryCriticalThreshold(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getBatteryCriticalThreshold/{deviceSn}")
|
||||
@Operation(summary = "获取严重低电阈值")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getBatteryCriticalThreshold(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getBatteryCriticalThreshold(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 紧急低电阈值 ====================
|
||||
|
||||
@PostMapping("/setBatteryEmergencyThreshold/{deviceSn}")
|
||||
@LogOperation("设置紧急低电阈值")
|
||||
@Operation(summary = "设置紧急低电阈值(比例)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setBatteryEmergencyThreshold(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setBatteryEmergencyThreshold(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getBatteryEmergencyThreshold/{deviceSn}")
|
||||
@Operation(summary = "获取紧急低电阈值")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getBatteryEmergencyThreshold(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getBatteryEmergencyThreshold(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 低电动作 ====================
|
||||
|
||||
@PostMapping("/setLowBatteryAction/{deviceSn}")
|
||||
@LogOperation("设置低电动作")
|
||||
@Operation(summary = "设置低电动作: warning(警告) / landing(降落) / goBackCritAndLandEmerg(低电返航·紧急降落)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setLowBatteryAction(@PathVariable String deviceSn, @RequestParam String value) {
|
||||
return new Result<>().ok(q20ParamService.setLowBatteryAction(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getLowBatteryAction/{deviceSn}")
|
||||
@Operation(summary = "获取当前低电动作")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getLowBatteryAction(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getLowBatteryAction(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 精准降落 ====================
|
||||
|
||||
@PostMapping("/setAprilTagId/{deviceSn}")
|
||||
@LogOperation("设置精准降落二维码标识")
|
||||
@Operation(summary = "设置精准降落二维码标识")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setAprilTagId(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20ParamService.setAprilTagId(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getAprilTagId/{deviceSn}")
|
||||
@Operation(summary = "获取精准降落二维码标识")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getAprilTagId(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getAprilTagId(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/setPrecisionLandMode/{deviceSn}")
|
||||
@LogOperation("设置精准降落模式")
|
||||
@Operation(summary = "设置精准降落模式: noPrecisionLanding / opportunisticPrecisionLanding / requiredPrecisionLanding")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setPrecisionLandMode(@PathVariable String deviceSn, @RequestParam String value) {
|
||||
return new Result<>().ok(q20ParamService.setPrecisionLandMode(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getPrecisionLandMode/{deviceSn}")
|
||||
@Operation(summary = "获取精准降落模式")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getPrecisionLandMode(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getPrecisionLandMode(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 避障 ====================
|
||||
|
||||
@PostMapping("/setObstacleAvoidanceEnable/{deviceSn}")
|
||||
@LogOperation("设置避障开关")
|
||||
@Operation(summary = "设置避障开关: 0=关闭 1=开启")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setObstacleAvoidanceEnable(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20ParamService.setObstacleAvoidanceEnable(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getObstacleAvoidanceEnable/{deviceSn}")
|
||||
@Operation(summary = "获取避障开关状态")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getObstacleAvoidanceEnable(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getObstacleAvoidanceEnable(deviceSn));
|
||||
}
|
||||
|
||||
@PostMapping("/setObstacleAvoidanceDistance/{deviceSn}")
|
||||
@LogOperation("设置避障距离")
|
||||
@Operation(summary = "设置避障距离(m)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setObstacleAvoidanceDistance(@PathVariable String deviceSn, @RequestParam float value) {
|
||||
return new Result<>().ok(q20ParamService.setObstacleAvoidanceDistance(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getObstacleAvoidanceDistance/{deviceSn}")
|
||||
@Operation(summary = "获取避障距离(m)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getObstacleAvoidanceDistance(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getObstacleAvoidanceDistance(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 视觉传感器 ====================
|
||||
|
||||
@PostMapping("/setVisualSensorEnable/{deviceSn}")
|
||||
@LogOperation("设置视觉传感器开关")
|
||||
@Operation(summary = "设置视觉传感器开关: 0=关闭 1=开启")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setVisualSensorEnable(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20ParamService.setVisualSensorEnable(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getVisualSensorEnable/{deviceSn}")
|
||||
@Operation(summary = "获取视觉传感器开关状态")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getVisualSensorEnable(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getVisualSensorEnable(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== RTK ====================
|
||||
|
||||
@PostMapping("/setRtkEnable/{deviceSn}")
|
||||
@LogOperation("设置RTK开关")
|
||||
@Operation(summary = "设置RTK开关: 0=关闭 1=开启(重启后生效)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setRtkEnable(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20ParamService.setRtkEnable(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getRtkEnable/{deviceSn}")
|
||||
@Operation(summary = "获取RTK开关状态")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getRtkEnable(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getRtkEnable(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 多载控制 ====================
|
||||
|
||||
@PostMapping("/setUatMultCtrlEnable/{deviceSn}")
|
||||
@LogOperation("设置多载控制开关")
|
||||
@Operation(summary = "设置多载控制开关: 0=关闭 1=开启(重启后生效)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setUatMultCtrlEnable(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20ParamService.setUatMultCtrlEnable(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getUatMultCtrlEnable/{deviceSn}")
|
||||
@Operation(summary = "获取多载控制开关状态")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getUatMultCtrlEnable(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getUatMultCtrlEnable(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 降落伞 ====================
|
||||
|
||||
@PostMapping("/setParachuteSafetyEnable/{deviceSn}")
|
||||
@LogOperation("设置降落伞安全开关")
|
||||
@Operation(summary = "设置降落伞安全开关: 0=关闭 1=开启")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setParachuteSafetyEnable(@PathVariable String deviceSn, @RequestParam int value) {
|
||||
return new Result<>().ok(q20ParamService.setParachuteSafetyEnable(deviceSn, value));
|
||||
}
|
||||
|
||||
@GetMapping("/getParachuteSafetyEnable/{deviceSn}")
|
||||
@Operation(summary = "获取降落伞安全开关状态")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getParachuteSafetyEnable(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getParachuteSafetyEnable(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 批量 ====================
|
||||
|
||||
@PostMapping("/setParams/{deviceSn}")
|
||||
@LogOperation("批量设置参数")
|
||||
@Operation(summary = "批量设置参数(null字段不下发)")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> setParams(@PathVariable String deviceSn, @RequestBody Q20ParamsDTO dto) {
|
||||
return new Result<>().ok(q20ParamService.setParams(deviceSn, dto));
|
||||
}
|
||||
|
||||
@GetMapping("/getParams/{deviceSn}")
|
||||
@Operation(summary = "批量获取所有参数")
|
||||
@RequiresPermissions("bus:q20:param")
|
||||
public Result<Object> getParams(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20ParamService.getParams(deviceSn));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
package com.multictrl.modules.business.q20.controller;
|
||||
|
||||
import com.multictrl.common.annotation.ApiOrder;
|
||||
import com.multictrl.common.annotation.LogOperation;
|
||||
import com.multictrl.common.utils.Result;
|
||||
import com.multictrl.modules.business.q20.dto.*;
|
||||
import com.multictrl.modules.business.q20.service.Q20RouteService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Q20航线任务接口
|
||||
* topic: thing/device/{device_sn}/services (下行)
|
||||
* 回复: thing/device/{device_sn}/services_reply (上行)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("business/q20/route")
|
||||
@Tag(name = "Q20航线任务")
|
||||
@ApiOrder(13)
|
||||
@RequiredArgsConstructor
|
||||
public class Q20RouteController {
|
||||
|
||||
private final Q20RouteService q20RouteService;
|
||||
|
||||
// ==================== 下一个航线ID ====================
|
||||
|
||||
@GetMapping("/nextWayline")
|
||||
@Operation(summary = "获取下一个航线ID(根据库中最新航线ID自动递增)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<String> nextWayline(@RequestParam(defaultValue = "route") String type) {
|
||||
return new Result<String>().ok(q20RouteService.nextWayline(type));
|
||||
}
|
||||
|
||||
// ==================== 上传航线 ====================
|
||||
|
||||
@PostMapping("/upload/{deviceSn}")
|
||||
@LogOperation("上传航线")
|
||||
@Operation(summary = "上传航线(route_upload)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeUpload(@PathVariable String deviceSn,
|
||||
@RequestBody Q20RouteUploadDTO dto) {
|
||||
return new Result<>().ok(q20RouteService.routeUpload(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 执行航线 ====================
|
||||
|
||||
@PostMapping("/execute/{deviceSn}")
|
||||
@LogOperation("执行航线")
|
||||
@Operation(summary = "执行航线(route_execute)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeExecute(@PathVariable String deviceSn,
|
||||
@RequestBody Q20RouteExecuteDTO dto) {
|
||||
return new Result<>().ok(q20RouteService.routeExecute(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 一键飞行 ====================
|
||||
|
||||
@PostMapping("/auto/{deviceSn}")
|
||||
@LogOperation("一键飞行(上传+执行)")
|
||||
@Operation(summary = "一键飞行,上传并执行航线(route_auto)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeAuto(@PathVariable String deviceSn,
|
||||
@RequestBody Q20RouteAutoDTO dto) {
|
||||
return new Result<>().ok(q20RouteService.routeAuto(deviceSn, dto));
|
||||
}
|
||||
|
||||
// ==================== 取消悬停 ====================
|
||||
|
||||
@PostMapping("/breakHover/{deviceSn}")
|
||||
@LogOperation("取消悬停")
|
||||
@Operation(summary = "取消悬停(break_loop_hover)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> breakLoopHover(@PathVariable String deviceSn) {
|
||||
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("/progress/{deviceSn}")
|
||||
@Operation(summary = "获取航线执行进度(route_progress)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeProgress(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20RouteService.routeProgress(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 暂停航线 ====================
|
||||
|
||||
@PostMapping("/pause/{deviceSn}")
|
||||
@LogOperation("暂停航线")
|
||||
@Operation(summary = "暂停航线(route_pause)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routePause(@PathVariable String deviceSn) {
|
||||
return new Result<>().ok(q20RouteService.routePause(deviceSn));
|
||||
}
|
||||
|
||||
// ==================== 继续航线 ====================
|
||||
|
||||
@PostMapping("/resume/{deviceSn}")
|
||||
@LogOperation("继续航线")
|
||||
@Operation(summary = "继续航线(route_resume)")
|
||||
@RequiresPermissions("bus:q20:route")
|
||||
public Result<Object> routeResume(@PathVariable String deviceSn) {
|
||||
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));
|
||||
}
|
||||
|
||||
// ==================== 获取围栏信息 ====================
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20相机操作参数")
|
||||
public class Q20CameraDTO {
|
||||
|
||||
@Schema(description = "设备id, JSON key: device_id")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "负载索引: 0=前向, 1=前向, 2=下向, JSON key: payload_index")
|
||||
private int payloadIndex;
|
||||
|
||||
@Schema(description = "相机类型: ir=红外, wide=广角, zoom=变焦, JSON key: camera_type")
|
||||
private String cameraType;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20相机焦距参数")
|
||||
public class Q20CameraFocalLengthDTO {
|
||||
|
||||
@Schema(description = "负载索引, JSON key: payload_index")
|
||||
private int payloadIndex;
|
||||
|
||||
@Schema(description = "设备id, JSON key: device_id")
|
||||
private long deviceId;
|
||||
|
||||
@Schema(description = "相机类型: ir/wide/zoom/visible, JSON key: camera_type")
|
||||
private String cameraType;
|
||||
|
||||
@Schema(description = "变焦倍数: 可见光2-200, 红外2-20; 连续变焦时范围-10~10, JSON key: zoom_factor")
|
||||
private Double zoomFactor;
|
||||
|
||||
@Schema(description = "变焦类型: 0=绝对值, 1=连续, JSON key: zoom_type")
|
||||
private int zoomType;
|
||||
|
||||
@Schema(description = "变焦模式: 0=数字(默认), 1=光学, JSON key: zoom_mode")
|
||||
private int zoomMode;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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 Q20DockAlternatePointDTO {
|
||||
|
||||
@Schema(description = "经度")
|
||||
private float longitude;
|
||||
|
||||
@Schema(description = "纬度")
|
||||
private float latitude;
|
||||
|
||||
@Schema(description = "高度(m)")
|
||||
private float altitude;
|
||||
|
||||
@Schema(description = "安全转移高度(m)")
|
||||
@JsonProperty("safe_height")
|
||||
private float safeHeight;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
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(用于route_execute)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "机库信息条目")
|
||||
public class Q20DockInfoItemDTO {
|
||||
|
||||
@Schema(description = "机库序号")
|
||||
private int index;
|
||||
|
||||
@Schema(description = "机库类型:takeoff=起飞点,landing=降落点")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "机库/客户端设备序列号")
|
||||
private String sn;
|
||||
|
||||
@Schema(description = "降落平台识别码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "经度,精确到7位小数")
|
||||
private float longitude;
|
||||
|
||||
@Schema(description = "纬度,精确到7位小数")
|
||||
private float latitude;
|
||||
|
||||
@Schema(description = "椭球高度(EGM96)(m)")
|
||||
private float altitude;
|
||||
|
||||
@Schema(description = "朝向角(deg)")
|
||||
private float heading;
|
||||
|
||||
@Schema(description = "是否已设置备降点:0=未设置,1=已设置")
|
||||
@JsonProperty("alternate_set")
|
||||
private int alternateSet;
|
||||
|
||||
@Schema(description = "备降点信息")
|
||||
@JsonProperty("alternate_point")
|
||||
private Q20DockAlternatePointDTO alternatePoint;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20虚拟摇杆参数")
|
||||
public class Q20DrcDTO {
|
||||
|
||||
@Schema(description = "X轴速度(m/s)")
|
||||
private float vx;
|
||||
|
||||
@Schema(description = "Y轴速度(m/s)")
|
||||
private float vy;
|
||||
|
||||
@Schema(description = "Z轴速度(m/s)")
|
||||
private float vz;
|
||||
|
||||
@Schema(description = "偏航角速度(deg/s)")
|
||||
private float vyaw;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20紧急停止参数")
|
||||
public class Q20EmergencyStopDTO {
|
||||
|
||||
@Schema(description = "OTP验证码,执行紧急停止前通过接口获取")
|
||||
private String otp;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 围栏条目DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "围栏条目信息")
|
||||
public class Q20GeofenceItemDTO {
|
||||
|
||||
@Schema(description = "围栏类型:polygon=多边形围栏,circle=圆形围栏")
|
||||
@JsonProperty("geofence_type")
|
||||
private String geofenceType;
|
||||
|
||||
@Schema(description = "围栏方向:0=围栏内,1=围栏外")
|
||||
private int inclusion;
|
||||
|
||||
@Schema(description = "多边形围栏顶点经纬度列表,每项格式:\"经度,纬度\"")
|
||||
@JsonProperty("vertex_coordinates")
|
||||
private List<String> vertexCoordinates;
|
||||
|
||||
@Schema(description = "圆形围栏半径(m)")
|
||||
@JsonProperty("circle_radius")
|
||||
private Integer circleRadius;
|
||||
|
||||
@Schema(description = "圆形围栏中心坐标,格式:\"经度,纬度\"")
|
||||
@JsonProperty("circle_coordinate")
|
||||
private String circleCoordinate;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 上传围栏DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "上传围栏请求体")
|
||||
public class Q20GeofenceUploadDTO {
|
||||
|
||||
@Schema(description = "围栏信息列表")
|
||||
@JsonProperty("geofence_info")
|
||||
private List<Q20GeofenceItemDTO> geofenceInfo;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20云台控制参数")
|
||||
public class Q20GimbalControlDTO {
|
||||
|
||||
@Schema(description = "设备id, JSON key: device_id")
|
||||
private long deviceId;
|
||||
|
||||
@Schema(description = "控制模式: 0=速度模式, 1=绝对值模式")
|
||||
private int mode;
|
||||
|
||||
@Schema(description = "横滚角(deg)")
|
||||
private float roll;
|
||||
|
||||
@Schema(description = "偏航角(deg), 默认0, 顺时针为正")
|
||||
private float yaw;
|
||||
|
||||
@Schema(description = "俯仰角(deg), 默认0, 向上为正向下为负")
|
||||
private float pitch;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20返航参数")
|
||||
public class Q20GoHomeDTO {
|
||||
|
||||
@Schema(description = "返航模式: 1=安全高度返回, 2=直线飞行, 3=原路返回")
|
||||
private int mode;
|
||||
|
||||
@Schema(description = "安全返航高度(m), mode=1时有效, JSON key: safe_altitude")
|
||||
private Float safeAltitude;
|
||||
|
||||
@Schema(description = "返航速度(m/s), mode=1时有效")
|
||||
private Float speed;
|
||||
|
||||
@Schema(description = "是否指定返回点: 1=是, 0=否, JSON key: specific_home")
|
||||
private Integer specificHome;
|
||||
|
||||
@Schema(description = "指定返回点坐标, specific_home=1时有效, JSON key: home_point")
|
||||
private HomePoint homePoint;
|
||||
|
||||
@Data
|
||||
@Schema(name = "返航目标点")
|
||||
public static class HomePoint {
|
||||
|
||||
@Schema(description = "经度(7位小数)")
|
||||
private float longitude;
|
||||
|
||||
@Schema(description = "纬度(7位小数)")
|
||||
private float latitude;
|
||||
|
||||
@Schema(description = "椭球高度EGM96(m)")
|
||||
private float altitude;
|
||||
|
||||
@Schema(description = "航向角(deg)")
|
||||
private float heading;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20绞车控制参数")
|
||||
public class Q20HalyardingDTO {
|
||||
|
||||
@Schema(description = "设备id, JSON key: device_id")
|
||||
private long deviceId;
|
||||
|
||||
@Schema(description = "绞车动作")
|
||||
private int action;
|
||||
|
||||
@Schema(description = "绳子位置, JSON key: rope_position")
|
||||
private int ropePosition;
|
||||
|
||||
@Schema(description = "绳子状态, JSON key: rope_status")
|
||||
private int ropeStatus;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20图像切换参数")
|
||||
public class Q20ImageSwitchDTO {
|
||||
|
||||
@Schema(description = "设备id, JSON key: device_id")
|
||||
private long deviceId;
|
||||
|
||||
@Schema(description = "激光开关: 0=关, 1=开, JSON key: laser_enable")
|
||||
private int laserEnable;
|
||||
|
||||
@Schema(description = "可见光开关: 0=关, 1=开, JSON key: visible_enable")
|
||||
private int visibleEnable;
|
||||
|
||||
@Schema(description = "红外开关: 0=关, 1=开, JSON key: ir_enable")
|
||||
private int irEnable;
|
||||
|
||||
@Schema(description = "红外类型: 0=白热, 1=黑热, 2=彩色, 3=辐射野, 4=辉煌红, JSON key: ir_type")
|
||||
private int irType;
|
||||
|
||||
@Schema(description = "流类型: 0=VISIBLE_ONLY, 1=IR_ONLY, 2=IR_IN_VISIBLE, 3=VISIBLE_IN_IR, 4=VISIBLE_WITH_IR, 5=NIGHT_ONLY, JSON key: stream_type")
|
||||
private int streamType;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20降落参数")
|
||||
public class Q20LandDTO {
|
||||
|
||||
@Schema(description = "降落模式: 0=直接降落, 1=精准降落")
|
||||
private int mode;
|
||||
|
||||
@Schema(description = "精准降落二维码号(mode=1时有效), JSON key: qr_code")
|
||||
private int qrCode;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20直播开始参数")
|
||||
public class Q20LiveStartDTO {
|
||||
|
||||
@Schema(description = "负载索引: fpv=-1, 前向=0, 前向=1, 下向=2, JSON key: payload_index")
|
||||
private int payloadIndex;
|
||||
|
||||
@Schema(description = "推流协议类型: 0=Agora, 1=RTMP, 2=RTSP, 3=GB28181, 4=WebRTC, JSON key: url_type")
|
||||
private int urlType;
|
||||
|
||||
@Schema(description = "直播URL地址")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "码流类型: 0=主码流, 1=辅码流")
|
||||
private int video;
|
||||
|
||||
@Schema(description = "视频ID, 格式: {sn}/{camera_index}/{video_index}, JSON key: video_id")
|
||||
private String videoId;
|
||||
|
||||
@Schema(description = "视频类型: ir/wide/zoom, JSON key: video_type")
|
||||
private String videoType;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20日志数据参数")
|
||||
public class Q20LogDataDTO {
|
||||
|
||||
@Schema(description = "日志ID")
|
||||
private int id;
|
||||
|
||||
@Schema(description = "OSS上传URL")
|
||||
private String url;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20日志列表参数")
|
||||
public class Q20LogListDTO {
|
||||
|
||||
@Schema(description = "日志起始位置")
|
||||
private int start;
|
||||
|
||||
@Schema(description = "日志结束位置")
|
||||
private int end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20挂载TDA数据DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "Q20挂载TDA参数")
|
||||
public class Q20MountTdaDTO {
|
||||
|
||||
@Schema(description = "负载索引(仅支持deviceID为68的负载), JSON key: payload_index")
|
||||
private int payloadIndex;
|
||||
|
||||
@Schema(description = "TDA数据长度, JSON key: tda_length")
|
||||
private int tdaLength;
|
||||
|
||||
@Schema(description = "TDA数据内容(int8[])")
|
||||
private List<Integer> tda;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20航点飞行参数")
|
||||
public class Q20NavigateDTO {
|
||||
|
||||
@Schema(description = "目标经度")
|
||||
private float longitude;
|
||||
|
||||
@Schema(description = "目标纬度")
|
||||
private float latitude;
|
||||
|
||||
@Schema(description = "目标高度(m)")
|
||||
private float altitude;
|
||||
|
||||
@Schema(description = "飞行速度(m/s)")
|
||||
private float speed;
|
||||
|
||||
@Schema(description = "到达后动作: noAction(悬停,默认)/land(降落)/precland(精准降落)")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "精准降落二维码号, JSON key: qr_code")
|
||||
private Integer qrCode;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20开伞参数")
|
||||
public class Q20OpenParachuteDTO {
|
||||
|
||||
@Schema(description = "OTP验证码,\"99\"时系统视为强制开伞")
|
||||
private String otp;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20 OTA升级DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "Q20 OTA升级参数")
|
||||
public class Q20OtaUpgradeDTO {
|
||||
|
||||
@Schema(description = "设备序列号")
|
||||
private String sn;
|
||||
|
||||
@Schema(description = "设备型号, 如 Q20")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "固件包URL")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "固件包MD5")
|
||||
private String md5;
|
||||
|
||||
@Schema(description = "固件包名称, JSON key: package_name")
|
||||
private String packageName;
|
||||
|
||||
@Schema(description = "固件版本号")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "版本说明")
|
||||
private String amend;
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20批量参数设置(set_params)
|
||||
* 所有字段均可选,null字段不发送给设备
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "Q20批量参数")
|
||||
public class Q20ParamsDTO {
|
||||
|
||||
@Schema(description = "低电阈值(比例,如0.3=30%)")
|
||||
private Float batteryLowThreshold;
|
||||
|
||||
@Schema(description = "严重低电阈值(比例)")
|
||||
private Float batteryCriticalThreshold;
|
||||
|
||||
@Schema(description = "紧急低电阈值(比例)")
|
||||
private Float batteryEmergencyThreshold;
|
||||
|
||||
@Schema(description = "低电动作: warning(警告) / landing(降落) / goBackCritAndLandEmerg(低电返航·紧急降落)")
|
||||
private String lowBatteryAction;
|
||||
|
||||
@Schema(description = "高度限制(m),0=无限制")
|
||||
private Float heightLimit;
|
||||
|
||||
@Schema(description = "距离限制(m),0=无限制")
|
||||
private Float distanceLimit;
|
||||
|
||||
@Schema(description = "围栏动作: goContinue/warning/hover/goBack/terminate/landing")
|
||||
private String geofenceAction;
|
||||
|
||||
@Schema(description = "返航高度(m),相对home点高度")
|
||||
private Float returnHeight;
|
||||
|
||||
@Schema(description = "返航速度(m/s)")
|
||||
private Float returnSpeed;
|
||||
|
||||
@Schema(description = "返航类型: directReturn(直接返航) / originalReturn(原路返航)")
|
||||
private String returnType;
|
||||
|
||||
@Schema(description = "避障开关: 0=关闭 1=开启")
|
||||
private Integer obstacleAvoidanceEnable;
|
||||
|
||||
@Schema(description = "避障距离(m)")
|
||||
private Float obstacleAvoidanceDistance;
|
||||
|
||||
@Schema(description = "视觉传感器开关: 0=关闭 1=开启")
|
||||
private Integer visualSensorEnable;
|
||||
|
||||
@Schema(description = "精准降落二维码标识")
|
||||
private Integer aprilTagId;
|
||||
|
||||
@Schema(description = "精准降落模式: noPrecisionLanding / opportunisticPrecisionLanding / requiredPrecisionLanding")
|
||||
private String precisionLandMode;
|
||||
|
||||
@Schema(description = "数据链路丢失动作: goContinue/hover/goBack/landing/terminate/disarm")
|
||||
private String dataLinkLossAction;
|
||||
|
||||
@Schema(description = "RC信号丢失动作: goContinue/hover/goBack/landing/terminate/disarm")
|
||||
private String rcLossAction;
|
||||
|
||||
@Schema(description = "降落伞安全开关: 0=关闭 1=开启")
|
||||
private Integer parachuteSafetyEnable;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20查询状态参数")
|
||||
public class Q20QueryStatusDTO {
|
||||
|
||||
@Schema(description = "查询类型: liftoff/arrival等, JSON key: query_value")
|
||||
private String queryValue;
|
||||
|
||||
@Schema(description = "业务ID")
|
||||
private String bid;
|
||||
|
||||
@Schema(description = "类型: waypoint/command等")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "航线标识")
|
||||
private String wayline;
|
||||
|
||||
@Schema(description = "状态码")
|
||||
private String code;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 动作DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "航点动作")
|
||||
public class Q20RouteActionDTO {
|
||||
|
||||
@Schema(description = "动作ID,范围[0,65535]")
|
||||
@JsonProperty("action_id")
|
||||
private int actionId;
|
||||
|
||||
@Schema(description = "动作执行函数:takePhoto/startRecord/stopRecord/focus/zoom/customDirName/gimbalRotate/rotateYaw/hover/gimbalEvenlyRotate/orientedShoot/panoShot/recordPointCloud")
|
||||
@JsonProperty("action_actuator_func")
|
||||
private String actionActuatorFunc;
|
||||
|
||||
@Schema(description = "动作执行函数参数,参数结构因动作类型而异")
|
||||
@JsonProperty("action_actuator_func_param")
|
||||
private Map<String, Object> actionActuatorFuncParam;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 动作组DTO
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@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;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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 Q20RouteAlternatePointDTO {
|
||||
|
||||
@Schema(description = "备降点坐标")
|
||||
private Q20RoutePointDTO point;
|
||||
|
||||
@Schema(description = "备降点高度(m)")
|
||||
@JsonProperty("alternate_height")
|
||||
private Float alternateHeight;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
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(route_auto = 上传 + 执行)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "一键飞行请求体(route_auto = 上传 + 执行)")
|
||||
public class Q20RouteAutoDTO {
|
||||
|
||||
@Schema(description = "航线信息(上传参数)")
|
||||
@JsonProperty("route_info")
|
||||
private Q20RouteUploadDTO routeInfo;
|
||||
|
||||
@Schema(description = "执行信息(执行参数)")
|
||||
@JsonProperty("execute_info")
|
||||
private Q20RouteExecuteDTO executeInfo;
|
||||
|
||||
@Schema(description = "飞机配置信息")
|
||||
@JsonProperty("vehicle_config")
|
||||
private Q20RouteVehicleConfigDTO vehicleConfig;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 执行航线DTO(route_execute)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "执行航线请求体(route_execute)")
|
||||
public class Q20RouteExecuteDTO {
|
||||
|
||||
@Schema(description = "执行模式:1=执行最近一次上传航线,2=执行指定route_id的航线")
|
||||
private int mode;
|
||||
|
||||
@Schema(description = "航线ID,mode=2时有效")
|
||||
@JsonProperty("route_id")
|
||||
private String routeId;
|
||||
|
||||
@Schema(description = "是否指定机库:0=未设置,1=已设置")
|
||||
@JsonProperty("specific_dock")
|
||||
private int specificDock;
|
||||
|
||||
@Schema(description = "机库信息列表")
|
||||
@JsonProperty("dock_info")
|
||||
private List<Q20DockInfoItemDTO> dockInfo;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 航线文件夹DTO(包含所有航点)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@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")
|
||||
@JsonProperty("execute_height_mode")
|
||||
private String executeHeightMode;
|
||||
|
||||
@Schema(description = "航点列表")
|
||||
private List<Q20RoutePlacemarkDTO> placemark;
|
||||
|
||||
@Schema(description = "备降点列表")
|
||||
@JsonProperty("alternate_points")
|
||||
private List<Q20RouteAlternatePointDTO> alternatePoints;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
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 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;
|
||||
|
||||
@Schema(description = "完成动作附加值,finishAction=preLand时为精准降落标识,格式:{hangar_sn}-{dock_code}")
|
||||
@JsonProperty("action_value")
|
||||
private String actionValue;
|
||||
|
||||
@Schema(description = "返航类型:directReturn / originalReturn")
|
||||
@JsonProperty("go_home_type")
|
||||
private String goHomeType;
|
||||
|
||||
@Schema(description = "失联时退出航线:goContinue / executeLostAction")
|
||||
@JsonProperty("exit_on_rc_lost")
|
||||
private String exitOnRcLost;
|
||||
|
||||
@Schema(description = "失联执行动作:goBack / landing / hover")
|
||||
@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)")
|
||||
@JsonProperty("land_height")
|
||||
private Float landHeight;
|
||||
|
||||
@Schema(description = "无人机信息")
|
||||
@JsonProperty("drone_info")
|
||||
private Q20RouteDroneInfoDTO droneInfo;
|
||||
|
||||
@Schema(description = "挂载信息")
|
||||
@JsonProperty("payload_info")
|
||||
private Q20RoutePayloadInfoDTO payloadInfo;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
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 Q20RoutePlacemarkDTO {
|
||||
|
||||
@Schema(description = "航点序号,从0开始")
|
||||
private int index;
|
||||
|
||||
@Schema(description = "是否危险点:0=安全,1=危险")
|
||||
@JsonProperty("is_risky")
|
||||
private Integer isRisky;
|
||||
|
||||
@Schema(description = "航点坐标")
|
||||
private Q20RoutePointDTO point;
|
||||
|
||||
@Schema(description = "执行高度(m)")
|
||||
@JsonProperty("execute_height")
|
||||
private float executeHeight;
|
||||
|
||||
@Schema(description = "是否使用全局速度:0=不使用全局速度,1=使用全局速度")
|
||||
@JsonProperty("use_global_speed")
|
||||
private Integer useGlobalSpeed;
|
||||
|
||||
@Schema(description = "航点飞行速度(m/s),useGlobalSpeed=0时有效")
|
||||
@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;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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 Q20RoutePointDTO {
|
||||
|
||||
@Schema(description = "坐标,格式:\"经度,纬度\",如 \"113.123,23.456\"")
|
||||
private String coordinates;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import static com.fasterxml.jackson.annotation.JsonProperty.Access.WRITE_ONLY;
|
||||
|
||||
/**
|
||||
* Q20航线任务 - 上传航线顶层DTO(route_upload)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "上传航线请求体(route_upload)")
|
||||
public class Q20RouteUploadDTO {
|
||||
|
||||
@Schema(description = "航线名称(仅本地存储用,不下发给设备)")
|
||||
@JsonIgnore
|
||||
private String routeName;
|
||||
|
||||
@Schema(description = "全局飞行高度(m),仅本地存储用,不下发给设备")
|
||||
@JsonProperty(value = "flight_height", access = WRITE_ONLY)
|
||||
private Float flightHeight;
|
||||
|
||||
@Schema(description = "是否精准导入,仅本地存储用,不下发给设备")
|
||||
@JsonProperty(value = "accurate_import", access = WRITE_ONLY)
|
||||
private Boolean accurateImport;
|
||||
|
||||
@Schema(description = "航线ID(存储用)")
|
||||
private String wayline;
|
||||
|
||||
@Schema(description = "任务配置信息")
|
||||
@JsonProperty("mission_config")
|
||||
private Q20RouteMissionConfigDTO missionConfig;
|
||||
|
||||
@Schema(description = "航线文件夹,包含所有航点信息")
|
||||
private Q20RouteFolderDTO folder;
|
||||
|
||||
@Schema(description = "飞机配置信息")
|
||||
@JsonProperty("vehicle_config")
|
||||
private Q20RouteVehicleConfigDTO vehicleConfig;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
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 Q20RouteVehicleConfigDTO {
|
||||
|
||||
@Schema(description = "返航类型:directReturn / originalReturn")
|
||||
@JsonProperty("go_home_type")
|
||||
private String goHomeType;
|
||||
|
||||
@Schema(description = "全局返航高度(m)")
|
||||
@JsonProperty("global_RTH_height")
|
||||
private Float globalRthHeight;
|
||||
|
||||
@Schema(description = "低电动作:warning / landing / goBackCritAndLandEmerg")
|
||||
@JsonProperty("low_battery_action")
|
||||
private String lowBatteryAction;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20设置返航点参数")
|
||||
public class Q20SetHomeDTO {
|
||||
|
||||
@Schema(description = "经度")
|
||||
private float longitude;
|
||||
|
||||
@Schema(description = "纬度")
|
||||
private float latitude;
|
||||
|
||||
@Schema(description = "椭球高度(m)")
|
||||
private float altitude;
|
||||
|
||||
@Schema(description = "航向角(deg)")
|
||||
private float heading;
|
||||
|
||||
@Schema(description = "true=使用当前GPS位置为home, JSON key: current_gps")
|
||||
private Boolean currentGps;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20起飞参数")
|
||||
public class Q20TakeoffDTO {
|
||||
|
||||
@Schema(description = "高度模式: 0=绝对高度, 1=相对高度(默认)")
|
||||
private int mode;
|
||||
|
||||
@Schema(description = "起飞高度(m)")
|
||||
private float altitude;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.multictrl.modules.business.q20.dto;
|
||||
|
||||
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(name = "Q20投掷控制参数")
|
||||
public class Q20ThrowingDTO {
|
||||
|
||||
@Schema(description = "设备id, JSON key: device_id")
|
||||
private long deviceId;
|
||||
|
||||
@Schema(description = "操作值: 0=解锁, 1=锁定")
|
||||
private int value;
|
||||
}
|
||||
|
|
@ -15,6 +15,9 @@ public interface Q20Constant {
|
|||
String METHOD_MOUNT = "mount";
|
||||
String METHOD_BATTERY = "battery";
|
||||
String METHOD_OBS = "obs";
|
||||
// Q20 status topic method字段值(sys/device/{sn}/status)
|
||||
String METHOD_ONLINE = "online";
|
||||
String METHOD_HEARTBEAT = "heartbeat";
|
||||
// Q20 state topic method字段值
|
||||
String METHOD_VERSION = "version";
|
||||
String METHOD_NOTIFY = "notify";
|
||||
|
|
@ -44,4 +47,10 @@ public interface Q20Constant {
|
|||
String Q20_ARRIVAL = "q20_arrival_";
|
||||
String Q20_ROUTE_EXECUTE = "q20_route_execute_";
|
||||
String Q20_ROUTE_AUTO = "q20_route_auto_";
|
||||
String Q20_ONLINE = "q20_online_";
|
||||
|
||||
// sys/device/ status topic前缀
|
||||
String Q20_SYS_TOPIC_PREFIX = "sys/device/";
|
||||
String STATUS_REPLY_SUFFIX = "_reply";
|
||||
long ONLINE_TTL_MS = 35_000L;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package com.multictrl.modules.business.q20.handler;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.common.constant.BusinessConstant;
|
||||
import com.multictrl.common.utils.CacheUtils;
|
||||
import com.multictrl.common.utils.JsonUtils;
|
||||
import com.multictrl.modules.business.handler.MessageHandler;
|
||||
import com.multictrl.modules.business.service.MqttPushService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Q20设备拓扑状态处理(topic: sys/device/{device_sn}/status)
|
||||
* method: online(设备上线) | heartbeat(心跳维持在线)
|
||||
* 两种消息均需回复 sys/device/{device_sn}/status_reply
|
||||
* 35s内无消息则缓存过期,视为设备离线
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class Q20StatusHandler implements MessageHandler {
|
||||
|
||||
private final MqttPushService mqttPushService;
|
||||
|
||||
@Override
|
||||
public void handleMessage(String topic, String payload, String gateway) {
|
||||
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
|
||||
if (message == null) {
|
||||
log.debug("Q20 status --> payload解析失败,解析后为null");
|
||||
return;
|
||||
}
|
||||
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
|
||||
String method = message.getStr(BusinessConstant.METHOD);
|
||||
if (method == null) {
|
||||
log.debug("Q20 status --> method字段缺失, topic: {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case Q20Constant.METHOD_ONLINE:
|
||||
handleOnline(topic, message, deviceSn);
|
||||
break;
|
||||
case Q20Constant.METHOD_HEARTBEAT:
|
||||
handleHeartbeat(topic, message, deviceSn);
|
||||
break;
|
||||
default:
|
||||
log.debug("Q20 status --> 未知method: {}, deviceSn: {}", method, deviceSn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备上线:刷新在线缓存,回复 status_reply(output为空对象)
|
||||
*/
|
||||
private void handleOnline(String topic, JSONObject message, String deviceSn) {
|
||||
CacheUtils.set(Q20Constant.Q20_ONLINE + deviceSn, true, Q20Constant.ONLINE_TTL_MS);
|
||||
log.info("Q20 status --> 设备上线, deviceSn: {}", deviceSn);
|
||||
Integer needReply = message.getInt(BusinessConstant.NEED_REPLY);
|
||||
if (needReply != null && needReply == 1) {
|
||||
sendReply(topic, message, new JSONObject());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳:刷新在线缓存TTL,回复 status_reply(output含uplink上行延迟ms)
|
||||
*/
|
||||
private void handleHeartbeat(String topic, JSONObject message, String deviceSn) {
|
||||
CacheUtils.set(Q20Constant.Q20_ONLINE + deviceSn, true, Q20Constant.ONLINE_TTL_MS);
|
||||
log.debug("Q20 status --> 心跳, deviceSn: {}", deviceSn);
|
||||
Integer needReply = message.getInt(BusinessConstant.NEED_REPLY);
|
||||
if (needReply != null && needReply == 1) {
|
||||
long uplink = 0;
|
||||
Long timestamp = message.getLong("timestamp");
|
||||
if (timestamp != null && timestamp > 0) {
|
||||
uplink = Math.max(0, System.currentTimeMillis() - timestamp);
|
||||
}
|
||||
JSONObject output = new JSONObject();
|
||||
output.set("uplink", uplink);
|
||||
sendReply(topic, message, output);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendReply(String topic, JSONObject message, JSONObject output) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("result", 0);
|
||||
data.set("output", output);
|
||||
message.set(BusinessConstant.DATA, data);
|
||||
message.set("timestamp", System.currentTimeMillis());
|
||||
message.remove(BusinessConstant.NEED_REPLY);
|
||||
String replyTopic = topic + Q20Constant.STATUS_REPLY_SUFFIX;
|
||||
mqttPushService.pushMessageByClient1(replyTopic, message.toString());
|
||||
log.debug("Q20 status --> 已回复, topic: {}", replyTopic);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.multictrl.modules.business.q20.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.modules.business.q20.dto.*;
|
||||
|
||||
/**
|
||||
* Q20控制指令服务接口
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
public interface Q20CommandService {
|
||||
|
||||
// ==================== 飞行控制 ====================
|
||||
|
||||
String takeoff(String deviceSn, Q20TakeoffDTO dto);
|
||||
|
||||
String land(String deviceSn, Q20LandDTO dto);
|
||||
|
||||
String stop(String deviceSn);
|
||||
|
||||
String goHome(String deviceSn, Q20GoHomeDTO dto);
|
||||
|
||||
String setHome(String deviceSn, Q20SetHomeDTO dto);
|
||||
|
||||
String navigate(String deviceSn, Q20NavigateDTO dto);
|
||||
|
||||
// ==================== 云台相机 ====================
|
||||
|
||||
String gimbalControl(String deviceSn, Q20GimbalControlDTO dto);
|
||||
|
||||
String mountTda(String deviceSn, Q20MountTdaDTO dto);
|
||||
|
||||
String cameraPhotoTake(String deviceSn, Q20CameraDTO dto);
|
||||
|
||||
String cameraRecordingStart(String deviceSn, Q20CameraDTO dto);
|
||||
|
||||
String cameraRecordingStop(String deviceSn, Q20CameraDTO dto);
|
||||
|
||||
String cameraFocalLengthSet(String deviceSn, Q20CameraFocalLengthDTO dto);
|
||||
|
||||
// ==================== 直播 ====================
|
||||
|
||||
String liveStartPush(String deviceSn, Q20LiveStartDTO dto);
|
||||
|
||||
String liveStopPush(String deviceSn, int payloadIndex, int video);
|
||||
|
||||
String imageSwitch(String deviceSn, Q20ImageSwitchDTO dto);
|
||||
|
||||
// ==================== 设备控制 ====================
|
||||
|
||||
String deviceCharge(String deviceSn, int value, int version);
|
||||
|
||||
String deviceBoot(String deviceSn, int value);
|
||||
|
||||
String safetySwitch(String deviceSn, int value);
|
||||
|
||||
// ==================== 负载 ====================
|
||||
|
||||
String throwingLock(String deviceSn, Q20ThrowingDTO dto);
|
||||
|
||||
String throwingExecution(String deviceSn, long deviceId);
|
||||
|
||||
String halyarding(String deviceSn, Q20HalyardingDTO dto);
|
||||
|
||||
String logistics(String deviceSn, int value, boolean force);
|
||||
|
||||
// ==================== 虚拟摇杆(高频,不等回复) ====================
|
||||
|
||||
void drc(String deviceSn, Q20DrcDTO dto);
|
||||
|
||||
// ==================== 安全/降落伞 ====================
|
||||
|
||||
String setAutoParachute(String deviceSn, int value);
|
||||
|
||||
JSONObject getEmergencyStopOtp(String deviceSn);
|
||||
|
||||
String emergencyStop(String deviceSn, Q20EmergencyStopDTO dto);
|
||||
|
||||
JSONObject getParachuteOtp(String deviceSn);
|
||||
|
||||
String openParachute(String deviceSn, Q20OpenParachuteDTO dto);
|
||||
|
||||
String confirmParachuteSafety(String deviceSn, int value);
|
||||
|
||||
// ==================== 状态/信息 ====================
|
||||
|
||||
JSONObject queryStatus(String deviceSn, Q20QueryStatusDTO dto);
|
||||
|
||||
JSONObject getConfig(String deviceSn);
|
||||
|
||||
String stereoImage(String deviceSn, int value);
|
||||
|
||||
JSONObject version(String deviceSn);
|
||||
|
||||
// ==================== 日志 ====================
|
||||
|
||||
JSONObject logCount(String deviceSn);
|
||||
|
||||
JSONObject logList(String deviceSn, Q20LogListDTO dto);
|
||||
|
||||
String logData(String deviceSn, Q20LogDataDTO dto);
|
||||
|
||||
// ==================== OTA ====================
|
||||
|
||||
String otaUpgrade(String deviceSn, Q20OtaUpgradeDTO dto);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.multictrl.modules.business.q20.service;
|
||||
|
||||
import com.multictrl.modules.business.q20.vo.Q20DeviceStatusVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Q20飞机设备服务
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
public interface Q20DeviceService {
|
||||
|
||||
Q20DeviceStatusVO getOnlineStatus(String deviceSn);
|
||||
|
||||
List<Q20DeviceStatusVO> getOnlineStatusBatch(List<String> deviceSnList);
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.multictrl.modules.business.q20.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.modules.business.q20.dto.Q20ParamsDTO;
|
||||
|
||||
/**
|
||||
* Q20参数指令服务
|
||||
* 通过 thing/device/{sn}/services 主题下发,设备回复 services_reply
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
public interface Q20ParamService {
|
||||
|
||||
// ==================== 高度/距离限制 ====================
|
||||
|
||||
String setHeightLimit(String deviceSn, float value);
|
||||
Object getHeightLimit(String deviceSn);
|
||||
|
||||
String setDistanceLimit(String deviceSn, float value);
|
||||
Object getDistanceLimit(String deviceSn);
|
||||
|
||||
// ==================== 围栏 ====================
|
||||
|
||||
String setGeofenceAction(String deviceSn, String value);
|
||||
Object getGeofenceAction(String deviceSn);
|
||||
|
||||
// ==================== 返航 ====================
|
||||
|
||||
String setReturnHeight(String deviceSn, float value);
|
||||
Object getReturnHeight(String deviceSn);
|
||||
|
||||
String setReturnType(String deviceSn, String value);
|
||||
Object getReturnType(String deviceSn);
|
||||
|
||||
// ==================== 电池阈值 ====================
|
||||
|
||||
String setBatteryLowThreshold(String deviceSn, float value);
|
||||
Object getBatteryLowThreshold(String deviceSn);
|
||||
|
||||
String setBatteryCriticalThreshold(String deviceSn, float value);
|
||||
Object getBatteryCriticalThreshold(String deviceSn);
|
||||
|
||||
String setBatteryEmergencyThreshold(String deviceSn, float value);
|
||||
Object getBatteryEmergencyThreshold(String deviceSn);
|
||||
|
||||
String setLowBatteryAction(String deviceSn, String value);
|
||||
Object getLowBatteryAction(String deviceSn);
|
||||
|
||||
// ==================== 降落 ====================
|
||||
|
||||
String setAprilTagId(String deviceSn, int value);
|
||||
Object getAprilTagId(String deviceSn);
|
||||
|
||||
String setPrecisionLandMode(String deviceSn, String value);
|
||||
Object getPrecisionLandMode(String deviceSn);
|
||||
|
||||
// ==================== 避障/传感器 ====================
|
||||
|
||||
String setObstacleAvoidanceEnable(String deviceSn, int value);
|
||||
Object getObstacleAvoidanceEnable(String deviceSn);
|
||||
|
||||
String setObstacleAvoidanceDistance(String deviceSn, float value);
|
||||
Object getObstacleAvoidanceDistance(String deviceSn);
|
||||
|
||||
String setVisualSensorEnable(String deviceSn, int value);
|
||||
Object getVisualSensorEnable(String deviceSn);
|
||||
|
||||
// ==================== 其他开关 ====================
|
||||
|
||||
String setRtkEnable(String deviceSn, int value);
|
||||
Object getRtkEnable(String deviceSn);
|
||||
|
||||
String setUatMultCtrlEnable(String deviceSn, int value);
|
||||
Object getUatMultCtrlEnable(String deviceSn);
|
||||
|
||||
String setParachuteSafetyEnable(String deviceSn, int value);
|
||||
Object getParachuteSafetyEnable(String deviceSn);
|
||||
|
||||
// ==================== 批量 ====================
|
||||
|
||||
String setParams(String deviceSn, Q20ParamsDTO dto);
|
||||
JSONObject getParams(String deviceSn);
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package com.multictrl.modules.business.q20.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.modules.business.q20.dto.*;
|
||||
|
||||
/**
|
||||
* Q20航线任务服务接口
|
||||
* topic: thing/device/{device_sn}/services (下行)
|
||||
* 回复: thing/device/{device_sn}/services_reply (上行)
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
public interface Q20RouteService {
|
||||
|
||||
/**
|
||||
* 获取下一个航线ID(根据库中最新Q20航线ID自动递增,无记录则返回默认ID)
|
||||
*
|
||||
* @param type 航线类型:route=上传航线(q20_route_),auto=一键航线(q20_auto_)
|
||||
* @return 下一个可用的航线ID
|
||||
*/
|
||||
String nextWayline(String type);
|
||||
|
||||
/**
|
||||
* 上传航线(route_upload)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @param dto 上传航线数据
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String routeUpload(String deviceSn, Q20RouteUploadDTO dto);
|
||||
|
||||
/**
|
||||
* 执行航线(route_execute)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @param dto 执行航线数据
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String routeExecute(String deviceSn, Q20RouteExecuteDTO dto);
|
||||
|
||||
/**
|
||||
* 一键飞行,上传并执行航线(route_auto)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @param dto 一键飞行数据
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String routeAuto(String deviceSn, Q20RouteAutoDTO dto);
|
||||
|
||||
/**
|
||||
* 取消悬停(break_loop_hover)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String breakLoopHover(String deviceSn);
|
||||
|
||||
/**
|
||||
* 获取航线信息(route_info)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @param mode 模式(1=最近一次,2=指定route_id)
|
||||
* @param value mode=2时为route_id,否则可传null
|
||||
* @return 设备返回的航线信息
|
||||
*/
|
||||
JSONObject routeInfo(String deviceSn, int mode, String value);
|
||||
|
||||
/**
|
||||
* 获取航线执行进度(route_progress)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @return 设备返回的执行进度信息
|
||||
*/
|
||||
JSONObject routeProgress(String deviceSn);
|
||||
|
||||
/**
|
||||
* 暂停航线(route_pause)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String routePause(String deviceSn);
|
||||
|
||||
/**
|
||||
* 继续航线(route_resume)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String routeResume(String deviceSn);
|
||||
|
||||
/**
|
||||
* 退出航线(route_finish)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String routeFinish(String deviceSn);
|
||||
|
||||
/**
|
||||
* 获取围栏信息(geofence_info)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @return 设备返回的围栏信息
|
||||
*/
|
||||
JSONObject geofenceInfo(String deviceSn);
|
||||
|
||||
/**
|
||||
* 上传围栏(geofence_upload)
|
||||
*
|
||||
* @param deviceSn 设备序列号
|
||||
* @param dto 围栏上传数据
|
||||
* @return 执行结果描述
|
||||
*/
|
||||
String geofenceUpload(String deviceSn, Q20GeofenceUploadDTO dto);
|
||||
}
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
package com.multictrl.modules.business.q20.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.common.exception.RenException;
|
||||
import com.multictrl.modules.business.q20.dto.*;
|
||||
import com.multictrl.modules.business.q20.service.Q20CommandService;
|
||||
import com.multictrl.modules.business.service.DJIBaseService;
|
||||
import com.multictrl.modules.business.service.MqttPushService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Q20控制指令服务实现
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class Q20CommandServiceImpl implements Q20CommandService {
|
||||
|
||||
private final DJIBaseService djiBaseService;
|
||||
private final MqttPushService mqttPushService;
|
||||
|
||||
private static final String SERVICES_TOPIC = "thing/device/%s/services";
|
||||
|
||||
private String execCmd(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload(method, data);
|
||||
payload.set("gateway", deviceSn);
|
||||
return djiBaseService.sendWaitReplyJudgeResult(topic, payload);
|
||||
}
|
||||
|
||||
private JSONObject execCmdGetReply(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload(method, data);
|
||||
payload.set("gateway", deviceSn);
|
||||
JSONObject reply = djiBaseService.sendWaitReply(topic, payload);
|
||||
if (reply == null) {
|
||||
throw new RenException("设备未响应,请检查设备是否在线");
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
// ==================== 飞行控制 ====================
|
||||
|
||||
@Override
|
||||
public String takeoff(String deviceSn, Q20TakeoffDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("mode", dto.getMode());
|
||||
data.set("altitude", dto.getAltitude());
|
||||
return execCmd(deviceSn, "takeoff", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String land(String deviceSn, Q20LandDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("mode", dto.getMode());
|
||||
data.set("qr_code", dto.getQrCode());
|
||||
return execCmd(deviceSn, "land", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String stop(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "stop", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String goHome(String deviceSn, Q20GoHomeDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("mode", dto.getMode());
|
||||
if (dto.getSafeAltitude() != null) {
|
||||
data.set("safe_altitude", dto.getSafeAltitude());
|
||||
}
|
||||
if (dto.getSpeed() != null) {
|
||||
data.set("speed", dto.getSpeed());
|
||||
}
|
||||
if (dto.getSpecificHome() != null) {
|
||||
data.set("specific_home", dto.getSpecificHome());
|
||||
}
|
||||
if (dto.getHomePoint() != null) {
|
||||
Q20GoHomeDTO.HomePoint hp = dto.getHomePoint();
|
||||
JSONObject homePoint = new JSONObject();
|
||||
homePoint.set("longitude", hp.getLongitude());
|
||||
homePoint.set("latitude", hp.getLatitude());
|
||||
homePoint.set("altitude", hp.getAltitude());
|
||||
homePoint.set("heading", hp.getHeading());
|
||||
data.set("home_point", homePoint);
|
||||
}
|
||||
return execCmd(deviceSn, "go_home", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setHome(String deviceSn, Q20SetHomeDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("longitude", dto.getLongitude());
|
||||
data.set("latitude", dto.getLatitude());
|
||||
data.set("altitude", dto.getAltitude());
|
||||
data.set("heading", dto.getHeading());
|
||||
if (dto.getCurrentGps() != null) {
|
||||
data.set("current_gps", dto.getCurrentGps());
|
||||
}
|
||||
return execCmd(deviceSn, "set_home", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String navigate(String deviceSn, Q20NavigateDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("longitude", dto.getLongitude());
|
||||
data.set("latitude", dto.getLatitude());
|
||||
data.set("altitude", dto.getAltitude());
|
||||
data.set("speed", dto.getSpeed());
|
||||
data.set("action", dto.getAction());
|
||||
if (dto.getQrCode() != null) {
|
||||
data.set("qr_code", dto.getQrCode());
|
||||
}
|
||||
return execCmd(deviceSn, "navigate", data);
|
||||
}
|
||||
|
||||
// ==================== 云台相机 ====================
|
||||
|
||||
@Override
|
||||
public String gimbalControl(String deviceSn, Q20GimbalControlDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("device_id", dto.getDeviceId());
|
||||
data.set("mode", dto.getMode());
|
||||
data.set("roll", dto.getRoll());
|
||||
data.set("yaw", dto.getYaw());
|
||||
data.set("pitch", dto.getPitch());
|
||||
return execCmd(deviceSn, "gimbal_control", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mountTda(String deviceSn, Q20MountTdaDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("payload_index", dto.getPayloadIndex());
|
||||
data.set("tda_length", dto.getTdaLength());
|
||||
data.set("tda", dto.getTda());
|
||||
return execCmd(deviceSn, "mount_tda", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cameraPhotoTake(String deviceSn, Q20CameraDTO dto) {
|
||||
JSONObject data = buildCameraData(dto);
|
||||
return execCmd(deviceSn, "camera_photo_take", data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String cameraRecordingStart(String deviceSn, Q20CameraDTO dto) {
|
||||
JSONObject data = buildCameraData(dto);
|
||||
return execCmd(deviceSn, "camera_recording_start", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cameraRecordingStop(String deviceSn, Q20CameraDTO dto) {
|
||||
JSONObject data = buildCameraData(dto);
|
||||
return execCmd(deviceSn, "camera_recording_stop", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cameraFocalLengthSet(String deviceSn, Q20CameraFocalLengthDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("payload_index", dto.getPayloadIndex());
|
||||
data.set("device_id", dto.getDeviceId());
|
||||
data.set("camera_type", dto.getCameraType());
|
||||
data.set("zoom_factor", dto.getZoomFactor());
|
||||
data.set("zoom_type", dto.getZoomType());
|
||||
data.set("zoom_mode", dto.getZoomMode());
|
||||
return execCmd(deviceSn, "camera_focal_length_set", data);
|
||||
}
|
||||
|
||||
private JSONObject buildCameraData(Q20CameraDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
if (dto.getDeviceId() != null) {
|
||||
data.set("device_id", dto.getDeviceId());
|
||||
}
|
||||
data.set("payload_index", dto.getPayloadIndex());
|
||||
data.set("camera_type", dto.getCameraType());
|
||||
return data;
|
||||
}
|
||||
|
||||
// ==================== 直播 ====================
|
||||
|
||||
@Override
|
||||
public String liveStartPush(String deviceSn, Q20LiveStartDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("payload_index", dto.getPayloadIndex());
|
||||
data.set("url_type", dto.getUrlType());
|
||||
data.set("url", dto.getUrl());
|
||||
data.set("video", dto.getVideo());
|
||||
data.set("video_id", dto.getVideoId());
|
||||
data.set("video_type", dto.getVideoType());
|
||||
return execCmd(deviceSn, "live_start_push", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String liveStopPush(String deviceSn, int payloadIndex, int video) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("payload_index", payloadIndex);
|
||||
data.set("video", video);
|
||||
return execCmd(deviceSn, "live_stop_push", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String imageSwitch(String deviceSn, Q20ImageSwitchDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("device_id", dto.getDeviceId());
|
||||
data.set("laser_enable", dto.getLaserEnable());
|
||||
data.set("visible_enable", dto.getVisibleEnable());
|
||||
data.set("ir_enable", dto.getIrEnable());
|
||||
data.set("ir_type", dto.getIrType());
|
||||
data.set("stream_type", dto.getStreamType());
|
||||
return execCmd(deviceSn, "image_switch", data);
|
||||
}
|
||||
|
||||
// ==================== 设备控制 ====================
|
||||
|
||||
@Override
|
||||
public String deviceCharge(String deviceSn, int value, int version) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
data.set("version", version);
|
||||
return execCmd(deviceSn, "device_charge", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deviceBoot(String deviceSn, int value) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
return execCmd(deviceSn, "device_boot", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String safetySwitch(String deviceSn, int value) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
return execCmd(deviceSn, "safety_switch", data);
|
||||
}
|
||||
|
||||
// ==================== 负载 ====================
|
||||
|
||||
@Override
|
||||
public String throwingLock(String deviceSn, Q20ThrowingDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("device_id", dto.getDeviceId());
|
||||
data.set("value", dto.getValue());
|
||||
return execCmd(deviceSn, "throwing_lock", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String throwingExecution(String deviceSn, long deviceId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("device_id", deviceId);
|
||||
return execCmd(deviceSn, "throwing_execution", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String halyarding(String deviceSn, Q20HalyardingDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("device_id", dto.getDeviceId());
|
||||
data.set("action", dto.getAction());
|
||||
data.set("rope_position", dto.getRopePosition());
|
||||
data.set("rope_status", dto.getRopeStatus());
|
||||
return execCmd(deviceSn, "halyarding", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logistics(String deviceSn, int value, boolean force) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
data.set("force", force);
|
||||
return execCmd(deviceSn, "logistics", data);
|
||||
}
|
||||
|
||||
// ==================== 虚拟摇杆(高频,不等回复) ====================
|
||||
|
||||
@Override
|
||||
public void drc(String deviceSn, Q20DrcDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("vx", dto.getVx());
|
||||
data.set("vy", dto.getVy());
|
||||
data.set("vz", dto.getVz());
|
||||
data.set("vyaw", dto.getVyaw());
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject payload = djiBaseService.getPayload("drc", data);
|
||||
payload.set("gateway", deviceSn);
|
||||
mqttPushService.pushMessageByClient1(topic, payload.toString());
|
||||
}
|
||||
|
||||
// ==================== 安全/降落伞 ====================
|
||||
|
||||
@Override
|
||||
public String setAutoParachute(String deviceSn, int value) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
return execCmd(deviceSn, "set_auto_parachute", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getEmergencyStopOtp(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 0);
|
||||
return execCmdGetReply(deviceSn, "get_emergency_stop_otp", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String emergencyStop(String deviceSn, Q20EmergencyStopDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("otp", dto.getOtp());
|
||||
return execCmd(deviceSn, "emergency_stop", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getParachuteOtp(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 0);
|
||||
return execCmdGetReply(deviceSn, "get_parachute_otp", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String openParachute(String deviceSn, Q20OpenParachuteDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("otp", dto.getOtp());
|
||||
return execCmd(deviceSn, "open_parachute", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String confirmParachuteSafety(String deviceSn, int value) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
return execCmd(deviceSn, "confirm_parachute_safety", data);
|
||||
}
|
||||
|
||||
// ==================== 状态/信息 ====================
|
||||
|
||||
@Override
|
||||
public JSONObject queryStatus(String deviceSn, Q20QueryStatusDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("query_value", dto.getQueryValue());
|
||||
if (dto.getBid() != null) {
|
||||
data.set("bid", dto.getBid());
|
||||
}
|
||||
if (dto.getType() != null) {
|
||||
data.set("type", dto.getType());
|
||||
}
|
||||
if (dto.getWayline() != null) {
|
||||
data.set("wayline", dto.getWayline());
|
||||
}
|
||||
if (dto.getCode() != null) {
|
||||
data.set("code", dto.getCode());
|
||||
}
|
||||
return execCmdGetReply(deviceSn, "query_status", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getConfig(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmdGetReply(deviceSn, "get_config", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String stereoImage(String deviceSn, int value) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", value);
|
||||
return execCmd(deviceSn, "stereo_image", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject version(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 0);
|
||||
return execCmdGetReply(deviceSn, "version", data);
|
||||
}
|
||||
|
||||
// ==================== 日志 ====================
|
||||
|
||||
@Override
|
||||
public JSONObject logCount(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 0);
|
||||
return execCmdGetReply(deviceSn, "log_count", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject logList(String deviceSn, Q20LogListDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("start", dto.getStart());
|
||||
data.set("end", dto.getEnd());
|
||||
return execCmdGetReply(deviceSn, "log_list", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logData(String deviceSn, Q20LogDataDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("id", dto.getId());
|
||||
data.set("url", dto.getUrl());
|
||||
return execCmd(deviceSn, "log_data", data);
|
||||
}
|
||||
|
||||
// ==================== OTA ====================
|
||||
|
||||
@Override
|
||||
public String otaUpgrade(String deviceSn, Q20OtaUpgradeDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("sn", dto.getSn());
|
||||
data.set("model", dto.getModel());
|
||||
data.set("url", dto.getUrl());
|
||||
data.set("md5", dto.getMd5());
|
||||
data.set("package_name", dto.getPackageName());
|
||||
data.set("version", dto.getVersion());
|
||||
data.set("amend", dto.getAmend());
|
||||
return execCmd(deviceSn, "ota_upgrade", data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.multictrl.modules.business.q20.service.impl;
|
||||
|
||||
import com.multictrl.common.utils.CacheUtils;
|
||||
import com.multictrl.modules.business.q20.handler.Q20Constant;
|
||||
import com.multictrl.modules.business.q20.service.Q20DeviceService;
|
||||
import com.multictrl.modules.business.q20.vo.Q20DeviceStatusVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Q20飞机设备服务实现
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class Q20DeviceServiceImpl implements Q20DeviceService {
|
||||
|
||||
@Override
|
||||
public Q20DeviceStatusVO getOnlineStatus(String deviceSn) {
|
||||
Q20DeviceStatusVO vo = new Q20DeviceStatusVO();
|
||||
vo.setDeviceSn(deviceSn);
|
||||
boolean online = CacheUtils.get(Q20Constant.Q20_ONLINE + deviceSn) != null;
|
||||
vo.setOnline(online);
|
||||
if (online) {
|
||||
vo.setOsd(CacheUtils.get(Q20Constant.Q20_OSD + deviceSn));
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Q20DeviceStatusVO> getOnlineStatusBatch(List<String> deviceSnList) {
|
||||
if (CollectionUtils.isEmpty(deviceSnList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return deviceSnList.stream().map(this::getOnlineStatus).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
package com.multictrl.modules.business.q20.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.multictrl.common.exception.RenException;
|
||||
import com.multictrl.modules.business.q20.dto.Q20ParamsDTO;
|
||||
import com.multictrl.modules.business.q20.service.Q20ParamService;
|
||||
import com.multictrl.modules.business.service.DJIBaseService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Q20参数指令服务实现
|
||||
* 复用 DJIBaseService 的 send/wait 机制,仅替换 topic 为 thing/device/ 前缀
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class Q20ParamServiceImpl implements Q20ParamService {
|
||||
|
||||
private final DJIBaseService djiBaseService;
|
||||
|
||||
private static final String SERVICES_TOPIC = "thing/device/%s/services";
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 内部通用方法
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
/** set类指令:发送并等待回复,返回结果描述 */
|
||||
private String execSet(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
return djiBaseService.sendWaitReplyJudgeResult(topic, djiBaseService.getPayload(method, data));
|
||||
}
|
||||
|
||||
/** get类指令:发送并等待回复,返回 param_value 字段值 */
|
||||
private Object execGet(String deviceSn, String method) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 0);
|
||||
JSONObject reply = djiBaseService.sendWaitReply(topic, djiBaseService.getPayload(method, data));
|
||||
if (reply == null) {
|
||||
throw new RenException("设备未响应,请检查设备是否在线");
|
||||
}
|
||||
if (reply.getInt("result", -1) != 0) {
|
||||
throw new RenException("设备返回错误: " + reply.getStr("message", "未知错误"));
|
||||
}
|
||||
return reply.get("param_value");
|
||||
}
|
||||
|
||||
private JSONObject floatData(float value) {
|
||||
JSONObject d = new JSONObject();
|
||||
d.set("value", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
private JSONObject intData(int value) {
|
||||
JSONObject d = new JSONObject();
|
||||
d.set("value", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
private JSONObject strData(String value) {
|
||||
JSONObject d = new JSONObject();
|
||||
d.set("value", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 高度/距离限制
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setHeightLimit(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_height_limit", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHeightLimit(String deviceSn) {
|
||||
return execGet(deviceSn, "get_height_limit");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setDistanceLimit(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_distance_limit", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDistanceLimit(String deviceSn) {
|
||||
return execGet(deviceSn, "get_distance_limit");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 围栏
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setGeofenceAction(String deviceSn, String value) {
|
||||
return execSet(deviceSn, "set_geofence_action", strData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeofenceAction(String deviceSn) {
|
||||
return execGet(deviceSn, "get_geofence_action");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 返航
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setReturnHeight(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_return_height", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getReturnHeight(String deviceSn) {
|
||||
return execGet(deviceSn, "get_return_height");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setReturnType(String deviceSn, String value) {
|
||||
return execSet(deviceSn, "set_return_type", strData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getReturnType(String deviceSn) {
|
||||
return execGet(deviceSn, "get_return_type");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 电池阈值
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setBatteryLowThreshold(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_battery_low_threshold", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBatteryLowThreshold(String deviceSn) {
|
||||
return execGet(deviceSn, "get_battery_low_threshold");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setBatteryCriticalThreshold(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_battery_critical_threshold", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBatteryCriticalThreshold(String deviceSn) {
|
||||
return execGet(deviceSn, "get_battery_critical_threshold");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setBatteryEmergencyThreshold(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_battery_emergency_threshold", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBatteryEmergencyThreshold(String deviceSn) {
|
||||
return execGet(deviceSn, "get_battery_emergency_threshold");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setLowBatteryAction(String deviceSn, String value) {
|
||||
return execSet(deviceSn, "set_low_battery_action", strData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getLowBatteryAction(String deviceSn) {
|
||||
return execGet(deviceSn, "get_low_battery_action");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 降落
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setAprilTagId(String deviceSn, int value) {
|
||||
return execSet(deviceSn, "set_april_tag_id", intData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAprilTagId(String deviceSn) {
|
||||
return execGet(deviceSn, "get_april_tag_id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setPrecisionLandMode(String deviceSn, String value) {
|
||||
return execSet(deviceSn, "set_precision_land_mode", strData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrecisionLandMode(String deviceSn) {
|
||||
return execGet(deviceSn, "get_precision_land_mode");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 避障/传感器
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setObstacleAvoidanceEnable(String deviceSn, int value) {
|
||||
return execSet(deviceSn, "set_obstacle_avoidance_enable", intData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObstacleAvoidanceEnable(String deviceSn) {
|
||||
return execGet(deviceSn, "get_obstacle_avoidance_enable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setObstacleAvoidanceDistance(String deviceSn, float value) {
|
||||
return execSet(deviceSn, "set_obstacle_avoidance_distance", floatData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObstacleAvoidanceDistance(String deviceSn) {
|
||||
return execGet(deviceSn, "get_obstacle_avoidance_distance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setVisualSensorEnable(String deviceSn, int value) {
|
||||
return execSet(deviceSn, "set_visual_sensor_enable", intData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getVisualSensorEnable(String deviceSn) {
|
||||
return execGet(deviceSn, "get_visual_sensor_enable");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 其他开关
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setRtkEnable(String deviceSn, int value) {
|
||||
return execSet(deviceSn, "set_rtk_enable", intData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getRtkEnable(String deviceSn) {
|
||||
return execGet(deviceSn, "get_rtk_enable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setUatMultCtrlEnable(String deviceSn, int value) {
|
||||
return execSet(deviceSn, "set_uat_mult_ctrl_enable", intData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUatMultCtrlEnable(String deviceSn) {
|
||||
return execGet(deviceSn, "get_uat_mult_ctrl_enable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setParachuteSafetyEnable(String deviceSn, int value) {
|
||||
return execSet(deviceSn, "set_parachute_safety_enable", intData(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParachuteSafetyEnable(String deviceSn) {
|
||||
return execGet(deviceSn, "get_parachute_safety_enable");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 批量
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String setParams(String deviceSn, Q20ParamsDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
if (dto.getBatteryLowThreshold() != null) data.set("battery_low_threshold", dto.getBatteryLowThreshold());
|
||||
if (dto.getBatteryCriticalThreshold() != null) data.set("battery_critical_threshold", dto.getBatteryCriticalThreshold());
|
||||
if (dto.getBatteryEmergencyThreshold() != null) data.set("battery_emergency_threshold", dto.getBatteryEmergencyThreshold());
|
||||
if (dto.getLowBatteryAction() != null) data.set("low_battery_action", dto.getLowBatteryAction());
|
||||
if (dto.getHeightLimit() != null) data.set("height_limit", dto.getHeightLimit());
|
||||
if (dto.getDistanceLimit() != null) data.set("distance_limit", dto.getDistanceLimit());
|
||||
if (dto.getGeofenceAction() != null) data.set("geofence_action", dto.getGeofenceAction());
|
||||
if (dto.getReturnHeight() != null) data.set("return_height", dto.getReturnHeight());
|
||||
if (dto.getReturnSpeed() != null) data.set("return_speed", dto.getReturnSpeed());
|
||||
if (dto.getReturnType() != null) data.set("return_type", dto.getReturnType());
|
||||
if (dto.getObstacleAvoidanceEnable() != null) data.set("obstacle_avoidance_enable", dto.getObstacleAvoidanceEnable());
|
||||
if (dto.getObstacleAvoidanceDistance() != null) data.set("obstacle_avoidance_distance", dto.getObstacleAvoidanceDistance());
|
||||
if (dto.getVisualSensorEnable() != null) data.set("visual_sensor_enable", dto.getVisualSensorEnable());
|
||||
if (dto.getAprilTagId() != null) data.set("april_tag_id", dto.getAprilTagId());
|
||||
if (dto.getPrecisionLandMode() != null) data.set("precision_land_mode", dto.getPrecisionLandMode());
|
||||
if (dto.getDataLinkLossAction() != null) data.set("data_link_loss_action", dto.getDataLinkLossAction());
|
||||
if (dto.getRcLossAction() != null) data.set("rc_loss_action", dto.getRcLossAction());
|
||||
if (dto.getParachuteSafetyEnable() != null) data.set("parachute_safety_enable", dto.getParachuteSafetyEnable());
|
||||
return execSet(deviceSn, "set_params", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getParams(String deviceSn) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 0);
|
||||
JSONObject reply = djiBaseService.sendWaitReply(topic, djiBaseService.getPayload("get_params", data));
|
||||
if (reply == null) {
|
||||
throw new RenException("设备未响应,请检查设备是否在线");
|
||||
}
|
||||
if (reply.getInt("result", -1) != 0) {
|
||||
throw new RenException("设备返回错误: " + reply.getStr("message", "未知错误"));
|
||||
}
|
||||
return reply.getJSONObject("params");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,465 @@
|
|||
package com.multictrl.modules.business.q20.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.multictrl.common.exception.RenException;
|
||||
import com.multictrl.modules.business.dao.RouteDao;
|
||||
import com.multictrl.modules.business.dao.RouteWaypointDao;
|
||||
import com.multictrl.modules.business.dao.WaypointActionDao;
|
||||
import com.multictrl.modules.business.entity.RouteEntity;
|
||||
import com.multictrl.modules.business.entity.RouteWaypointEntity;
|
||||
import com.multictrl.modules.business.entity.WaypointActionEntity;
|
||||
import com.multictrl.modules.business.q20.dto.*;
|
||||
import com.multictrl.modules.business.q20.service.Q20RouteService;
|
||||
import com.multictrl.modules.business.service.DJIBaseService;
|
||||
import com.multictrl.modules.business.service.FlightTaskService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Q20航线任务服务实现
|
||||
* 复用 DJIBaseService 的 send/wait 机制,topic 为 thing/device/{sn}/services
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class Q20RouteServiceImpl implements Q20RouteService {
|
||||
|
||||
private final DJIBaseService djiBaseService;
|
||||
private final FlightTaskService flightTaskService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RouteDao routeDao;
|
||||
private final RouteWaypointDao routeWaypointDao;
|
||||
private final WaypointActionDao waypointActionDao;
|
||||
|
||||
private static final String SERVICES_TOPIC = "thing/device/%s/services";
|
||||
private static final String TEMPLATE_TYPE_Q20 = "q20";
|
||||
/** 上传航线的航线ID前缀 */
|
||||
private static final String ROUTE_WAYLINE_PREFIX = "q20_route_";
|
||||
/** 一键航线的航线ID前缀 */
|
||||
private static final String AUTO_WAYLINE_PREFIX = "q20_auto_";
|
||||
/** 拆分航线ID的「前缀 + 数字后缀」 */
|
||||
private static final Pattern WAYLINE_TAIL = Pattern.compile("^(.*?)(\\d+)$");
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 内部通用方法
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
/** 发送指令并等待回复,返回结果描述字符串 */
|
||||
private String execCmd(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
return djiBaseService.sendWaitReplyJudgeResult(topic, djiBaseService.getPayload(method, data));
|
||||
}
|
||||
|
||||
/** 发送指令并等待回复,返回完整回复JSONObject;超时抛出RenException */
|
||||
private JSONObject execCmdGetReply(String deviceSn, String method, JSONObject data) {
|
||||
String topic = String.format(SERVICES_TOPIC, deviceSn);
|
||||
JSONObject reply = djiBaseService.sendWaitReply(topic, djiBaseService.getPayload(method, data));
|
||||
if (reply == null) {
|
||||
throw new RenException("设备未响应,请检查设备是否在线");
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
/** 利用Jackson @JsonProperty注解将DTO序列化为snake_case键的JSONObject */
|
||||
private JSONObject dtoToJson(Object dto) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(dto);
|
||||
return JSONUtil.parseObj(json);
|
||||
} catch (Exception e) {
|
||||
throw new RenException("参数序列化失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 接口实现
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String nextWayline(String type) {
|
||||
// 按类型区分前缀:一键航线用 q20_auto_,上传航线用 q20_route_
|
||||
String prefix = AUTO_WAYLINE_PREFIX.equals(type) || "auto".equalsIgnoreCase(type)
|
||||
? AUTO_WAYLINE_PREFIX : ROUTE_WAYLINE_PREFIX;
|
||||
String defaultId = prefix + "001";
|
||||
// 取库中该前缀下最新一条Q20航线的航线ID,按其数字后缀递增
|
||||
RouteEntity latest = routeDao.selectOne(new QueryWrapper<RouteEntity>()
|
||||
.eq("template_type", TEMPLATE_TYPE_Q20)
|
||||
.likeRight("q20_route_id", prefix)
|
||||
.orderByDesc("create_date")
|
||||
.last("LIMIT 1"));
|
||||
String next = latest != null ? incrementWayline(latest.getQ20RouteId()) : defaultId;
|
||||
// 兜底:防止递增结果与库中已有ID冲突
|
||||
int guard = 0;
|
||||
while (waylineExists(next) && guard++ < 1000) {
|
||||
next = incrementWayline(next);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/** 航线ID递增:保留前缀与位宽,对数字后缀+1;无数字后缀时追加_001 */
|
||||
private String incrementWayline(String base) {
|
||||
if (StrUtil.isBlank(base)) {
|
||||
return ROUTE_WAYLINE_PREFIX + "001";
|
||||
}
|
||||
Matcher m = WAYLINE_TAIL.matcher(base.trim());
|
||||
if (!m.matches()) {
|
||||
return base.trim() + "_001";
|
||||
}
|
||||
String prefix = m.group(1);
|
||||
String num = m.group(2);
|
||||
String inc = new BigInteger(num).add(BigInteger.ONE).toString();
|
||||
if (inc.length() < num.length()) {
|
||||
inc = StrUtil.padPre(inc, num.length(), '0');
|
||||
}
|
||||
return prefix + inc;
|
||||
}
|
||||
|
||||
/** 判断航线ID是否已存在于Q20航线库 */
|
||||
private boolean waylineExists(String wayline) {
|
||||
if (StrUtil.isBlank(wayline)) {
|
||||
return false;
|
||||
}
|
||||
Long cnt = routeDao.selectCount(new QueryWrapper<RouteEntity>()
|
||||
.eq("q20_route_id", wayline)
|
||||
.eq("template_type", TEMPLATE_TYPE_Q20));
|
||||
return cnt != null && cnt > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String routeUpload(String deviceSn, Q20RouteUploadDTO dto) {
|
||||
validateWaypoints(dto);
|
||||
checkRouteNotExists(dto.getWayline(), dto.getRouteName());
|
||||
JSONObject data = dtoToJson(dto);
|
||||
String result = execCmd(deviceSn, "route_upload", data);
|
||||
saveRouteToLocal(deviceSn, dto);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 航点校验:航线至少需要两个有效航点(含经纬度坐标) */
|
||||
private void validateWaypoints(Q20RouteUploadDTO dto) {
|
||||
List<Q20RoutePlacemarkDTO> placemarks = (dto != null && dto.getFolder() != null)
|
||||
? dto.getFolder().getPlacemark() : null;
|
||||
long valid = placemarks == null ? 0 : placemarks.stream()
|
||||
.filter(p -> p != null && p.getPoint() != null
|
||||
&& StrUtil.isNotBlank(p.getPoint().getCoordinates())
|
||||
&& p.getPoint().getCoordinates().split(",").length >= 2)
|
||||
.count();
|
||||
if (valid < 2) {
|
||||
throw new RenException("航线至少需要两个有效航点(含经纬度)");
|
||||
}
|
||||
}
|
||||
|
||||
/** 上传前校验:航线ID和航线名称均不能与库中已有记录重复 */
|
||||
private void checkRouteNotExists(String wayline, String routeName) {
|
||||
if (StrUtil.isNotBlank(wayline)) {
|
||||
Long cnt = routeDao.selectCount(new QueryWrapper<RouteEntity>()
|
||||
.eq("q20_route_id", wayline)
|
||||
.eq("template_type", TEMPLATE_TYPE_Q20));
|
||||
if (cnt != null && cnt > 0) {
|
||||
throw new RenException("航线ID「" + wayline + "」已存在,请勿重复上传");
|
||||
}
|
||||
}
|
||||
// if (StrUtil.isNotBlank(routeName)) {
|
||||
// Long cnt = routeDao.selectCount(new QueryWrapper<RouteEntity>()
|
||||
// .eq("route_name", routeName)
|
||||
// .eq("template_type", TEMPLATE_TYPE_Q20));
|
||||
// if (cnt != null && cnt > 0) {
|
||||
// throw new RenException("航线名称「" + routeName + "」已存在,请勿重复上传");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routeExecute(String deviceSn, Q20RouteExecuteDTO dto) {
|
||||
JSONObject data = dtoToJson(dto);
|
||||
String result = execCmd(deviceSn, "route_execute", data);
|
||||
saveFlightTask(deviceSn, dto.getRouteId());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String routeAuto(String deviceSn, Q20RouteAutoDTO dto) {
|
||||
validateWaypoints(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);
|
||||
if (dto.getRouteInfo() != null) {
|
||||
saveRouteToLocal(deviceSn, dto.getRouteInfo());
|
||||
}
|
||||
String waylineId = dto.getRouteInfo() != null ? dto.getRouteInfo().getWayline() : null;
|
||||
saveFlightTask(deviceSn, waylineId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String breakLoopHover(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "break_loop_hover", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject routeInfo(String deviceSn, int mode, String value) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("mode", mode);
|
||||
data.set("value", value);
|
||||
return execCmdGetReply(deviceSn, "route_info", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject routeProgress(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmdGetReply(deviceSn, "route_progress", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routePause(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "route_pause", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routeResume(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "route_resume", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String routeFinish(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmd(deviceSn, "route_finish", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject geofenceInfo(String deviceSn) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("value", 1);
|
||||
return execCmdGetReply(deviceSn, "geofence_info", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String geofenceUpload(String deviceSn, Q20GeofenceUploadDTO dto) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.set("geofence", dtoToJson(dto));
|
||||
return execCmd(deviceSn, "geofence_upload", data);
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 本地航线库存储
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 上传成功后将航线数据持久化到本地航线库
|
||||
* 同设备同 wayline ID 已存在时覆盖旧记录(删除旧航点/动作后重新插入)
|
||||
*/
|
||||
private void saveRouteToLocal(String deviceSn, Q20RouteUploadDTO dto) {
|
||||
String wayline = dto.getWayline();
|
||||
String routeName = StrUtil.isNotBlank(dto.getRouteName())
|
||||
? dto.getRouteName()
|
||||
: (StrUtil.isNotBlank(wayline) ? wayline : deviceSn + "_" + System.currentTimeMillis());
|
||||
|
||||
// 同设备相同 wayline 已存在则清除旧记录
|
||||
RouteEntity existing = routeDao.selectOne(new QueryWrapper<RouteEntity>()
|
||||
.eq("dock_sn", deviceSn)
|
||||
.eq(StrUtil.isNotBlank(wayline), "q20_route_id", wayline)
|
||||
.eq("template_type", TEMPLATE_TYPE_Q20));
|
||||
if (existing != null) {
|
||||
Long oldId = existing.getId();
|
||||
routeWaypointDao.delete(new QueryWrapper<RouteWaypointEntity>().eq("route_id", oldId));
|
||||
waypointActionDao.delete(new QueryWrapper<WaypointActionEntity>().eq("route_id", oldId));
|
||||
routeDao.deleteById(oldId);
|
||||
log.debug("Q20 routeUpload -> 覆盖旧航线记录 id={}, wayline={}", oldId, wayline);
|
||||
}
|
||||
|
||||
// 构建 RouteEntity
|
||||
RouteEntity route = new RouteEntity();
|
||||
route.setRouteName(routeName);
|
||||
route.setDockSn(deviceSn);
|
||||
route.setTemplateType(TEMPLATE_TYPE_Q20);
|
||||
route.setQ20RouteId(wayline);
|
||||
|
||||
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();
|
||||
if (folder != null) {
|
||||
route.setHeightModel(folder.getExecuteHeightMode() != null ? folder.getExecuteHeightMode() : "relativeToStartPoint");
|
||||
route.setFlightSpeed((double) folder.getAutoFlightSpeed());
|
||||
List<Q20RoutePlacemarkDTO> placemarks = folder.getPlacemark();
|
||||
route.setWaypointNum(placemarks != null ? placemarks.size() : 0);
|
||||
}
|
||||
|
||||
// vehicleConfig 中的 globalRthHeight 优先级低于 missionConfig
|
||||
Q20RouteVehicleConfigDTO vc = dto.getVehicleConfig();
|
||||
if (vc != null && route.getGlobalRthHeight() == null && vc.getGlobalRthHeight() != null) {
|
||||
route.setGlobalRthHeight(vc.getGlobalRthHeight().doubleValue());
|
||||
}
|
||||
|
||||
// 前端传入的可选覆盖字段
|
||||
if (dto.getFlightHeight() != null) route.setFlightHeight(dto.getFlightHeight().doubleValue());
|
||||
if (dto.getAccurateImport() != null) route.setIsAccurateImport(dto.getAccurateImport());
|
||||
|
||||
// 补全 NOT NULL 字段默认值
|
||||
if (route.getFlightHeight() == null) route.setFlightHeight(100.0);
|
||||
if (route.getGlobalRthHeight() == null) route.setGlobalRthHeight(100.0);
|
||||
if (route.getTakeoffSecurityHeight() == null) route.setTakeoffSecurityHeight(100.0);
|
||||
if (route.getGlobalTransitionalSpeed() == null) route.setGlobalTransitionalSpeed(5.0);
|
||||
if (route.getFinishAction() == null) route.setFinishAction("goHome");
|
||||
if (route.getExitOnRcLost() == null) route.setExitOnRcLost("executeLostAction");
|
||||
if (route.getFlyToWaylineMode() == null) route.setFlyToWaylineMode("safely");
|
||||
if (route.getTotalDistance() == null) route.setTotalDistance(0.0);
|
||||
if (route.getExpectFlightTime() == null) route.setExpectFlightTime("0");
|
||||
if (route.getIsAccurateImport() == null) route.setIsAccurateImport(false);
|
||||
|
||||
routeDao.insert(route);
|
||||
Long routeId = route.getId();
|
||||
|
||||
// 保存航点及动作
|
||||
if (folder != null && folder.getPlacemark() != null) {
|
||||
for (Q20RoutePlacemarkDTO p : folder.getPlacemark()) {
|
||||
RouteWaypointEntity wp = buildWaypoint(routeId, p, folder.getAutoFlightSpeed());
|
||||
routeWaypointDao.insert(wp);
|
||||
|
||||
if (p.getActionGroup() != null && p.getActionGroup().getAction() != null) {
|
||||
List<Q20RouteActionDTO> actions = p.getActionGroup().getAction();
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
Q20RouteActionDTO action = actions.get(i);
|
||||
WaypointActionEntity wa = new WaypointActionEntity();
|
||||
wa.setRouteId(routeId);
|
||||
wa.setWaypointId(wp.getId());
|
||||
wa.setActionType(action.getActionActuatorFunc());
|
||||
wa.setActionSort(i);
|
||||
if (action.getActionActuatorFuncParam() != null) {
|
||||
try {
|
||||
wa.setActionValue(objectMapper.writeValueAsString(action.getActionActuatorFuncParam()));
|
||||
} catch (Exception e) {
|
||||
log.warn("Q20 routeUpload -> 动作参数序列化失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
waypointActionDao.insert(wa);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Q20 routeUpload -> 航线已入库 routeId={}, routeName={}, wayline={}, deviceSn={}",
|
||||
routeId, routeName, wayline, deviceSn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行航线后创建飞行任务记录
|
||||
* 优先按 waylineId 匹配,找不到则取该设备最近一条 Q20 航线
|
||||
*/
|
||||
private void saveFlightTask(String deviceSn, String waylineId) {
|
||||
RouteEntity route = null;
|
||||
if (StrUtil.isNotBlank(waylineId)) {
|
||||
route = routeDao.selectOne(new QueryWrapper<RouteEntity>()
|
||||
.eq("q20_route_id", waylineId)
|
||||
.eq("template_type", TEMPLATE_TYPE_Q20));
|
||||
}
|
||||
if (route == null) {
|
||||
route = routeDao.selectOne(new QueryWrapper<RouteEntity>()
|
||||
.eq("dock_sn", deviceSn)
|
||||
.eq("template_type", TEMPLATE_TYPE_Q20)
|
||||
.orderByDesc("create_date")
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
if (route == null) {
|
||||
log.warn("Q20 saveFlightTask -> 未找到航线记录,跳过任务创建 deviceSn={}, waylineId={}", deviceSn, waylineId);
|
||||
return;
|
||||
}
|
||||
String taskId = IdUtil.fastSimpleUUID();
|
||||
flightTaskService.addRouteTask(taskId, deviceSn, route.getId());
|
||||
log.info("Q20 saveFlightTask -> 任务已创建 taskId={}, routeId={}, deviceSn={}", taskId, route.getId(), deviceSn);
|
||||
}
|
||||
|
||||
private RouteWaypointEntity buildWaypoint(Long routeId, Q20RoutePlacemarkDTO p, float globalSpeed) {
|
||||
RouteWaypointEntity wp = new RouteWaypointEntity();
|
||||
wp.setRouteId(routeId);
|
||||
wp.setWaypointSort(p.getIndex());
|
||||
wp.setFlightHeight((double) p.getExecuteHeight());
|
||||
|
||||
// 解析坐标 "经度,纬度"
|
||||
if (p.getPoint() != null && StrUtil.isNotBlank(p.getPoint().getCoordinates())) {
|
||||
String[] parts = p.getPoint().getCoordinates().split(",");
|
||||
if (parts.length >= 2) {
|
||||
try {
|
||||
wp.setLongitude(Double.parseDouble(parts[0].trim()));
|
||||
wp.setLatitude(Double.parseDouble(parts[1].trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Q20 routeUpload -> 坐标解析失败: {}", p.getPoint().getCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean useGlobal = p.getUseGlobalSpeed() == null || p.getUseGlobalSpeed() == 1;
|
||||
wp.setFollowRouteSpeed(useGlobal);
|
||||
wp.setFollowRouteHeight(false);
|
||||
wp.setFlightSpeed(useGlobal
|
||||
? (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");
|
||||
|
||||
return wp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.multictrl.modules.business.q20.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Q20飞机在线状态响应
|
||||
*
|
||||
* @author 938693313@qq.com
|
||||
* @since 1.0.0 2026/5/20
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "Q20飞机在线状态")
|
||||
public class Q20DeviceStatusVO {
|
||||
|
||||
@Schema(description = "设备序列号")
|
||||
private String deviceSn;
|
||||
|
||||
@Schema(description = "是否在线(35s内有心跳则为true)")
|
||||
private boolean online;
|
||||
|
||||
@Schema(description = "最新OSD遥测数据,仅在线时有值")
|
||||
private Object osd;
|
||||
}
|
||||
|
|
@ -49,4 +49,10 @@ public interface DJIBaseService {
|
|||
|
||||
//机场是否在线
|
||||
Boolean isDockOnline(String dockSn);
|
||||
|
||||
// 获取当前待回复命令信息(测试/模拟用)
|
||||
JSONObject getPendingCmd(String deviceSn);
|
||||
|
||||
// 注入模拟MQTT回复(测试/模拟用),replyData 为完整回复 JSON(含 result/output 等字段)
|
||||
void injectMockReply(String deviceSn, JSONObject replyData);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 大疆基础服务
|
||||
|
|
@ -28,6 +29,9 @@ import java.util.Optional;
|
|||
public class DJIBaseServiceImpl implements DJIBaseService {
|
||||
private final MqttPushService mqttPushService;
|
||||
|
||||
/** 设备SN → 当前正在等待回复的命令信息(供模拟回复使用) */
|
||||
private final ConcurrentHashMap<String, JSONObject> pendingCmds = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public JSONObject getPayload(String method, JSONObject data) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
|
|
@ -46,16 +50,57 @@ public class DJIBaseServiceImpl implements DJIBaseService {
|
|||
mqttPushService.pushMessageByClient1(topic, payload.toString());
|
||||
String bid = payload.getStr("bid");
|
||||
String tid = payload.getStr("tid");
|
||||
int tryCount = 100;
|
||||
while (tryCount-- > 0) {
|
||||
Object object = CacheUtils.get(bid + "_" + tid);
|
||||
if (object != null) {
|
||||
return (JSONObject) object;
|
||||
}
|
||||
Utils.sleep(100);
|
||||
String cacheKey = bid + "_" + tid;
|
||||
|
||||
// 记录当前等待回复的命令,供前端模拟回复使用
|
||||
String deviceSn = extractDeviceSn(topic);
|
||||
if (deviceSn != null) {
|
||||
JSONObject pending = new JSONObject();
|
||||
pending.set("bid", bid);
|
||||
pending.set("tid", tid);
|
||||
pending.set("method", payload.getStr("method"));
|
||||
pending.set("cacheKey", cacheKey);
|
||||
pending.set("timestamp", System.currentTimeMillis());
|
||||
pendingCmds.put(deviceSn, pending);
|
||||
}
|
||||
|
||||
return null;
|
||||
try {
|
||||
int tryCount = 100;
|
||||
while (tryCount-- > 0) {
|
||||
Object object = CacheUtils.get(cacheKey);
|
||||
if (object != null) {
|
||||
return (JSONObject) object;
|
||||
}
|
||||
Utils.sleep(100);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (deviceSn != null) pendingCmds.remove(deviceSn);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractDeviceSn(String topic) {
|
||||
// topic格式: thing/device/{sn}/services 或 thing/product/{sn}/services
|
||||
if (topic == null) return null;
|
||||
String[] parts = topic.split("/");
|
||||
return parts.length >= 3 ? parts[2] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getPendingCmd(String deviceSn) {
|
||||
return pendingCmds.get(deviceSn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectMockReply(String deviceSn, JSONObject replyData) {
|
||||
JSONObject pending = pendingCmds.get(deviceSn);
|
||||
if (pending == null) {
|
||||
throw new RenException("当前设备没有待回复的命令,可能已超时或尚未发送");
|
||||
}
|
||||
String cacheKey = pending.getStr("cacheKey");
|
||||
JSONObject reply = replyData != null ? replyData : new JSONObject();
|
||||
CacheUtils.set(cacheKey, reply);
|
||||
log.debug("injectMockReply -> deviceSn={}, method={}, replyData={}", deviceSn, pending.getStr("method"), reply);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ public class ShiroConfig {
|
|||
filterMap.put("/swagger-resources/**", "anon");
|
||||
filterMap.put("/captcha", "anon");
|
||||
filterMap.put("/", "anon");
|
||||
filterMap.put("/q20-login.html", "anon");
|
||||
filterMap.put("/q20-ctrl.html", "anon");
|
||||
filterMap.put("/srs/**", "anon");
|
||||
filterMap.put("/mqtt/auth", "anon");
|
||||
filterMap.put("/**", "oauth2");
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ mqtt:
|
|||
username: dock
|
||||
password: Dock@2023
|
||||
subClientId: dj-one-sub${random.int(10)}
|
||||
subTopic: thing/product/#,sys/product/#,thing/device/#
|
||||
subTopic: thing/product/#,sys/product/#,thing/device/#,sys/device/#
|
||||
pubClientId: dj-one-pub${random.int(10)}
|
||||
client2:
|
||||
url: tcp://${host.ip}:61637
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,126 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Q20 登录</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bs-body-bg: #0d1117;
|
||||
--bs-body-color: #c9d1d9;
|
||||
--card-bg: #161b22;
|
||||
--border-color: #30363d;
|
||||
--accent: #388bfd;
|
||||
--danger: #f85149;
|
||||
--muted: #8b949e;
|
||||
}
|
||||
body { background: var(--bs-body-bg); color: var(--bs-body-color); font-size: 13px; }
|
||||
.card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 8px; max-width: 400px; width: 100%; }
|
||||
.form-control { background: #0d1117; border-color: var(--border-color); color: var(--bs-body-color); font-size: 12px; }
|
||||
.form-control:focus { background: #0d1117; border-color: var(--accent); color: var(--bs-body-color); box-shadow: 0 0 0 2px rgba(56,139,253,.25); }
|
||||
.form-label { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
|
||||
.btn-primary { background: var(--accent); border-color: var(--accent); font-size: 12px; }
|
||||
#captchaImg { cursor: pointer; border: 1px solid var(--border-color); border-radius: 4px; height: 38px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex justify-content-center align-items-center" style="min-height:100vh">
|
||||
|
||||
<div class="card p-4">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-send-fill" style="font-size:2rem;color:var(--accent)"></i>
|
||||
<h5 class="mt-2 mb-0" style="color:#e6edf3">Q20 飞行控制台</h5>
|
||||
<small class="text-muted">Drone Control Panel</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label">服务地址</label>
|
||||
<input id="apiBase" class="form-control" value="/multictrl" placeholder="如 http://192.168.1.1:61620/api">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">用户名</label>
|
||||
<input id="loginUser" class="form-control" placeholder="admin">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">密码</label>
|
||||
<input id="loginPass" class="form-control" type="password" placeholder="••••••••">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">验证码</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input id="loginCaptcha" class="form-control" placeholder="点击图片刷新" onkeydown="if(event.key==='Enter')doLogin()">
|
||||
<img id="captchaImg" src="" alt="captcha" onclick="refreshCaptcha()" title="点击刷新">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary w-100" onclick="doLogin()">
|
||||
<i class="bi bi-box-arrow-in-right me-1"></i>登 录
|
||||
</button>
|
||||
<div id="loginErr" class="mt-2 text-danger small" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let loginUuid = '';
|
||||
|
||||
function genUuid() {
|
||||
return crypto.randomUUID ? crypto.randomUUID()
|
||||
: Math.random().toString(36).slice(2) + Date.now().toString(36);
|
||||
}
|
||||
|
||||
function baseUrl() {
|
||||
return (document.getElementById('apiBase').value || '/multictrl').replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function refreshCaptcha() {
|
||||
loginUuid = genUuid();
|
||||
document.getElementById('captchaImg').src =
|
||||
baseUrl() + '/captcha?uuid=' + loginUuid + '&t=' + Date.now();
|
||||
}
|
||||
|
||||
async function doLogin() {
|
||||
const err = document.getElementById('loginErr');
|
||||
err.style.display = 'none';
|
||||
const username = document.getElementById('loginUser').value.trim();
|
||||
const password = document.getElementById('loginPass').value;
|
||||
const captcha = document.getElementById('loginCaptcha').value.trim();
|
||||
if (!username || !password || !captcha) {
|
||||
err.textContent = '请填写完整信息';
|
||||
err.style.display = '';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(baseUrl() + '/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password, captcha, uuid: loginUuid })
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.code !== 0) {
|
||||
err.textContent = json.msg || '登录失败';
|
||||
err.style.display = '';
|
||||
refreshCaptcha();
|
||||
document.getElementById('loginCaptcha').value = '';
|
||||
return;
|
||||
}
|
||||
sessionStorage.setItem('q20_token', json.data.token);
|
||||
sessionStorage.setItem('q20_api_base', baseUrl());
|
||||
sessionStorage.setItem('q20_username', username);
|
||||
window.location.href = 'q20-ctrl.html';
|
||||
} catch (e) {
|
||||
err.textContent = '网络错误: ' + e.message;
|
||||
err.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 已登录则直接跳控制台
|
||||
if (sessionStorage.getItem('q20_token')) {
|
||||
window.location.href = 'q20-ctrl.html';
|
||||
return;
|
||||
}
|
||||
refreshCaptcha();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -2071,4 +2071,10 @@ ON COLUMN "public"."bus_geo_mark"."create_date" IS '创建时间';
|
|||
COMMENT
|
||||
ON COLUMN "public"."bus_geo_mark"."creator" IS '创建人';
|
||||
COMMENT
|
||||
ON TABLE "public"."bus_geo_mark" IS '地图标注';
|
||||
ON TABLE "public"."bus_geo_mark" IS '地图标注';
|
||||
|
||||
-- 20260526 航线表新增飞机类型和航线id字段
|
||||
ALTER TABLE public.bus_route
|
||||
ADD COLUMN q20_route_id varchar(50);
|
||||
COMMENT
|
||||
ON COLUMN "public"."bus_route"."q20_route_id" IS 'q20航线id';
|
||||
Loading…
Reference in New Issue