1、阿罗斯机库蛙跳

This commit is contained in:
zhangchuang 2026-06-03 17:53:20 +08:00
parent 92553d35be
commit 5c0549e8d0
26 changed files with 778 additions and 72 deletions

View File

@ -44,6 +44,8 @@ public interface BusinessConstant {
String AIRPORT_ORGANIZATION_GET = "airport_organization_get";
String AIRPORT_ORGANIZATION_BIND = "airport_organization_bind";
String FLIGHT_TASK_RESOURCE_GET = "flighttask_resource_get";
//私有协议为了确认跳飞场景下妙算pskd设备是否开启成功
String PRIVATE_GET_MULTI_MIAOSUAN_PSDK_STATUS = "private_get_multi_miaosuan_psdk_status";
//********************************* dj events topic method *********************************//
String FLIGHTTASK_PROGRESS = "flighttask_progress";
String TAKEOFF_TO_POINT_PROGRESS = "takeoff_to_point_progress";

View File

@ -25,13 +25,14 @@ import java.util.List;
@RestController
@RequestMapping("business/command")
@Tag(name = "航线飞行", description = """
航线管理是无人机自主作业的重要功能
可以实现行业领域的批量化智能化作业
上云 API 提供了相关的接口实现了航线任务在云端的共享查看下发执行取消以及进度上报等功能
用户需要遵照航线文件格式规范WPML编写航线文件定义航线任务一个航线任务中可以定义多条航线""")
航线管理是无人机自主作业的重要功能
可以实现行业领域的批量化智能化作业
上云 API 提供了相关的接口实现了航线任务在云端的共享查看下发执行取消以及进度上报等功能
用户需要遵照航线文件格式规范WPML编写航线文件定义航线任务一个航线任务中可以定义多条航线""")
@ApiOrder(5)
@RequiredArgsConstructor
public class RouteFlightController {
private final DJIBaseService djiBaseService;
private final RouteFlightService routeFlightService;
@ -76,7 +77,7 @@ public class RouteFlightController {
@Operation(summary = "取消任务")
@RequiresPermissions("bus:route:flight:flightTaskUndo")
public Result<Object> flightTaskUndo(@PathVariable String dockSn,
@RequestBody List<String> taskIds) {
@RequestBody List<String> taskIds) {
if (taskIds.isEmpty()) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
@ -89,7 +90,7 @@ public class RouteFlightController {
@Operation(summary = "执行航线")
@RequiresPermissions("bus:route:flight:flightExecute")
public Result<Object> flightExecute(@PathVariable String dockSn,
@RequestBody FlightExecute flightExecute) {
@RequestBody FlightExecute flightExecute) {
return new Result<>().ok(routeFlightService.flightExecute(dockSn, flightExecute));
}
@ -99,8 +100,8 @@ public class RouteFlightController {
@Operation(summary = "空中下发航线")
@RequiresPermissions("bus:route:flight:inFlightWaylineDeliver")
public Result<Object> inFlightWaylineDeliver(@PathVariable String dockSn,
@Parameter(name = "routeId", description = "航线标识")
@RequestParam Long routeId) {
@Parameter(name = "routeId", description = "航线标识")
@RequestParam Long routeId) {
return new Result<>().ok(routeFlightService.inFlightWaylineDeliver(dockSn, routeId));
}

View File

@ -0,0 +1,16 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.MultiGroupEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 蛙跳组信息
*
* @author 张闯
* @since 2026-05-28
*/
@Mapper
public interface MultiGroupDao extends BaseDao<MultiGroupEntity> {
}

View File

@ -0,0 +1,16 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.MultiGroupDeviceEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 蛙跳组绑定的设备信息
*
* @author 张闯
* @since 2026-05-28
*/
@Mapper
public interface MultiGroupDeviceDao extends BaseDao<MultiGroupDeviceEntity> {
}

View File

@ -51,6 +51,10 @@ public class DockDTO implements Serializable {
@Schema(description = "机库型号")
private String dockModel;
@NotBlank(message = "{dock.model.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "使用场景")
private String scenario;
@NotNull(message = "{dept.id.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "所属部门")

View File

@ -1,7 +1,9 @@
package com.multictrl.modules.business.dto.flight;
import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.Data;
/**
@ -20,4 +22,9 @@ public class FlightExecute {
@Schema(description = "模拟器执行")
private FlightTaskPrepare.SimulateMission simulateMission;
@Schema(hidden = true)
List<PrivateMultiFlightBindDockInfo> privateMultiFlightBindDockInfos;
@Schema(hidden = true)
private String taskId;
}

View File

@ -1,6 +1,8 @@
package com.multictrl.modules.business.dto.flight;
import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -19,6 +21,7 @@ import lombok.NoArgsConstructor;
*/
@Data
public class FlightTaskPrepare {
//计划 ID
private String flight_id;
//开始执行时间
@ -53,10 +56,7 @@ public class FlightTaskPrepare {
private int rth_altitude;
//返航高度模式
//{"0":"智能高度","1":"设定高度"}
/// 智能返航模式下飞行器将自动规划最佳返航高度
/// 大疆机场当前不支持设置返航高度模式只能选择'设定高度'模式
/// 当环境光线不满足视觉系统要求时譬如傍晚阳光直射夜间弱光无光
/// 飞行器将使用您设定的返航高度进行直线返航
/// 智能返航模式下飞行器将自动规划最佳返航高度 大疆机场当前不支持设置返航高度模式只能选择'设定高度'模式 当环境光线不满足视觉系统要求时譬如傍晚阳光直射夜间弱光无光 飞行器将使用您设定的返航高度进行直线返航
private int rth_mode;
//遥控器失控动作
//{"0":"返航","1":"悬停","2":"降落"}
@ -86,10 +86,16 @@ public class FlightTaskPrepare {
// 如果不一致则拉取文件更新如果一致则不处理
private int flight_safety_advance_check;
// aros 机场蛙跳参数
// 把所有参与到本地任务的机场经纬度高度/备降点经纬度高度 信息都给到psdk
// psdk根据到每个机库的距离自动计算出需要返航的机场位置
private List<PrivateMultiFlightBindDockInfo> private_multi_flight_bind_dock_info;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class BreakPoint {
//断点序号
@Schema(description = "断点序号")
private int index;
@ -110,6 +116,7 @@ public class FlightTaskPrepare {
@NoArgsConstructor
@AllArgsConstructor
public static class ExecutableConditions {
//存储容量
//可执行任务的机场或飞行器最低存储容量机场或飞行器存储容量不满足 storage_capacity 任务执行失败
private int storage_capacity;
@ -119,6 +126,7 @@ public class FlightTaskPrepare {
@NoArgsConstructor
@AllArgsConstructor
public static class FileInfo {
//文件 URL
private String url;
//文件签名 文件内容 MD5 签名
@ -130,6 +138,7 @@ public class FlightTaskPrepare {
@NoArgsConstructor
@AllArgsConstructor
public static class ReadyConditions {
//电池容量
//可执行任务的飞行器电池电量百分比阈值任务开始执行时的飞行器电量必须大于 battery_capacity
private int battery_capacity;
@ -145,6 +154,7 @@ public class FlightTaskPrepare {
@NoArgsConstructor
@AllArgsConstructor
public static class SimulateMission {
//是否开启模拟器任务
//{"0":"不开启","1":"开启"}
//当次任务打开或关闭模拟器

View File

@ -0,0 +1,53 @@
package com.multictrl.modules.business.dto.multi;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author 张闯
* @since 2026-05-28
*/
@Schema(name = "蛙跳组绑定设备列表")
@Data
public class MultiDockDTO {
@Schema(description = "主键")
private Long id;
@JsonProperty(required = true)
@Schema(description = "机库名称")
private String dockName;
@JsonProperty(required = true)
@Schema(description = "SN码")
private String dockSn;
@JsonProperty(required = true)
@Schema(description = "机库类型")
private String dockType;
@JsonProperty(required = true)
@Schema(description = "机库型号")
private String dockModel;
@Schema(description = "使用场景")
private String scenario;
@JsonProperty(required = true)
@Schema(description = "所属部门")
private Long deptId;
@JsonProperty(required = true)
@Schema(description = "机场经度")
private double longitude;
@JsonProperty(required = true)
@Schema(description = "机场纬度")
private double latitude;
@JsonProperty(required = true)
@Schema(description = "机场椭球高度")
private double height;
}

View File

@ -0,0 +1,36 @@
package com.multictrl.modules.business.dto.multi;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
/**
* @author 张闯
* @since 2026-05-28
*/
@Data
@Schema(name = "蛙跳组信息")
public class MultiGroupDTO {
/**
* 蛙跳组名称
*/
@NotBlank(message = "{group.name.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "蛙跳组名称")
private String groupName;
/**
* 蛙跳组备注说明
*/
@NotBlank(message = "{group.mark.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "蛙跳组备注说明")
private String groupMark;
}

View File

@ -0,0 +1,34 @@
package com.multictrl.modules.business.dto.multi;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
import lombok.Data;
/**
* @author 张闯
* @since 2026-05-28
*/
@Data
@Schema(name = "蛙跳组绑定设备列表")
public class MultiGroupDeviceDTO {
/**
* 蛙跳组id
*/
@NotBlank(message = "{group.id.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "蛙跳组id")
private Long groudId;
/**
* 网关sn
*/
@NotBlank(message = "{gateway.sns.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "设备网关sn列表")
private List<String> gatewaySns;
}

View File

@ -0,0 +1,25 @@
package com.multictrl.modules.business.dto.multi;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Data;
/**
* @author 张闯
* @since 2026-05-31
*/
@Data
@Schema(name = "蛙跳任务类")
public class MultiTaskDTO {
List<MultiDockDTO> docks;
MultiDockDTO miaoSuan;
MultiDockDTO miaoSuanLocatedDock;
String taskId;
Long routeId;
}

View File

@ -0,0 +1,29 @@
package com.multictrl.modules.business.dto.multi;
import lombok.Data;
/**
* aros 机场蛙跳参数
* @author 张闯
* @since 2026-05-31
*/
@Data
public class PrivateMultiFlightBindDockInfo {
private String dock_sn;
private double longitude;
private double latitude;
private double height;
private double alternate_land_point_longitude;
private double alternate_land_point_latitude;
private double alternate_land_point_height;
private double alternate_land_point_safe_land_height;
}

View File

@ -31,8 +31,19 @@ public class DockEntity extends BaseEntity {
private String dockSn;
/**
* 机库类型
*
* dock
* pilot
* miao_suan
*/
private String dockType;
/**
* 使用场景
* null或者 inspection 巡检
* multi 蛙跳
*/
private String scenario;
/**
* 机库型号
*/

View File

@ -0,0 +1,33 @@
package com.multictrl.modules.business.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.multictrl.common.entity.BaseEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 张闯
* @since 2026-05-28
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("bus_multi_group_device")
public class MultiGroupDeviceEntity extends BaseEntity {
/**
* 蛙跳组id
*/
private Long groudId;
/**
* 网关sn
*/
private String gatewaySn;
/**
* 更新者
*/
private Long updater;
/**
* 更新时间
*/
private Date updateDate;
}

View File

@ -0,0 +1,33 @@
package com.multictrl.modules.business.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.multictrl.common.entity.BaseEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 张闯
* @since 2026-05-28
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("bus_multi_group")
public class MultiGroupEntity extends BaseEntity {
/**
* 蛙跳组名称
*/
private String groupName;
/**
* 蛙跳组标识
*/
private String groupMark;
/**
* 组织编号
*/
private Long deptId;
private Long updater;
private Date updateDate;
}

View File

@ -28,6 +28,7 @@ import java.util.Optional;
@Component
@RequiredArgsConstructor
public class OsdHandler implements MessageHandler {
private static final String GPS_ZERO = "0.000000";
private final InfluxService influxService;
private final FlightTaskService flightTaskService;
@ -41,7 +42,7 @@ public class OsdHandler implements MessageHandler {
if (gateway.equals(dockSn)) {
// log.debug("dock osd --> topic: {}, payload: {}", topic, payload);
if (data.containsKey("network_state")) {//网络状态机库osd分3次上报信息只有网络状态对程序有用其他前端展示即可
CacheUtils.set(BusinessConstant.DOCK_OSD + dockSn, data);
CacheUtils.set(BusinessConstant.DOCK_OSD + dockSn, data, 5000);
//判断飞行结束任务中切到空闲
if (data.containsKey("mode_code")) {
int modeCode = data.getInt("mode_code", -1);
@ -54,13 +55,14 @@ public class OsdHandler implements MessageHandler {
CacheUtils.set(BusinessConstant.DOCK_IN_WORK + dockSn, true);
}
//刚从任务中切到空闲说明架次结束入库了
if (modeCode == DockMode.IDLE.getCode() && CacheUtils.get(BusinessConstant.DOCK_IN_WORK + dockSn) != null) {
if (modeCode == DockMode.IDLE.getCode()
&& CacheUtils.get(BusinessConstant.DOCK_IN_WORK + dockSn) != null) {
CacheUtils.delete(BusinessConstant.DOCK_IN_WORK + dockSn);
log.debug("{}机库架次飞行完成取消DRC心跳", dockSn);
List<String> list = Optional.ofNullable(CacheUtils.get(BusinessConstant.DRC_HEART_BEAT_SN))
.filter(obj -> obj instanceof List)
.map(obj -> (List<String>) obj)
.orElse(Collections.emptyList());
.filter(obj -> obj instanceof List)
.map(obj -> (List<String>) obj)
.orElse(Collections.emptyList());
if (CollUtil.isNotEmpty(list)) {
list.remove(dockSn);
}
@ -76,9 +78,10 @@ public class OsdHandler implements MessageHandler {
//保存osd到influxdb
String latitude = data.getStr("latitude");
String longitude = data.getStr("longitude");
if (latitude != null && longitude != null && !GPS_ZERO.equals(latitude) && !GPS_ZERO.equals(longitude)) {
if (latitude != null && longitude != null && !GPS_ZERO.equals(latitude) && !GPS_ZERO.equals(
longitude)) {
String payload_index = data.getJSONArray("cameras") != null ?
data.getJSONArray("cameras").getJSONObject(0).getStr("payload_index") : null;
data.getJSONArray("cameras").getJSONObject(0).getStr("payload_index") : null;
//获取电池电量如果单电池则把 a赋值给b
int capacity_percent_a;
int capacity_percent_b;
@ -91,44 +94,48 @@ public class OsdHandler implements MessageHandler {
}
UavReport uavReport = UavReport.builder()
.dockSn(dockSn)
.obstacle_avoidance_horizon(data.getJSONObject("obstacle_avoidance").getInt("horizon"))
.obstacle_avoidance_upside(data.getJSONObject("obstacle_avoidance").getInt("upside"))
.obstacle_avoidance_downside(data.getJSONObject("obstacle_avoidance").getInt("downside"))
.is_near_area_limit(data.getInt("is_near_area_limit"))
.is_near_height_limit(data.getInt("is_near_height_limit"))
.payload_index(payload_index)
.gimbal_pitch(payload_index != null ? data.getJSONObject(payload_index).getDouble("gimbal_pitch") : null)
.gimbal_roll(payload_index != null ? data.getJSONObject(payload_index).getDouble("gimbal_roll") : null)
.gimbal_yaw(payload_index != null ? data.getJSONObject(payload_index).getDouble("gimbal_yaw") : null)
.zoom_factor(payload_index != null ? data.getJSONObject(payload_index).getDouble("zoom_factor") : null)
.track_id(data.getStr("track_id"))
.position_state_is_fixed(data.getJSONObject("position_state").getInt("is_fixed"))
.position_state_is_quality(data.getJSONObject("position_state").getInt("quality"))
.position_state_is_gps_number(data.getJSONObject("position_state").getInt("gps_number"))
.position_state_is_rtk_number(data.getJSONObject("position_state").getInt("rtk_number"))
.battery_capacity_percent(data.getJSONObject("battery").getInt("capacity_percent"))
.battery_remain_flight_time(data.getJSONObject("battery").getInt("remain_flight_time"))
.battery_return_home_power(data.getJSONObject("battery").getInt("return_home_power"))
.battery_landing_power(data.getJSONObject("battery").getInt("landing_power"))
.battery_capacity_percent_a(capacity_percent_a)
.battery_capacity_percent_b(capacity_percent_b)
.wind_direction(data.getInt("wind_direction"))
.wind_speed(data.getDouble("wind_speed"))
.home_distance(data.getDouble("home_distance"))
//state上报
.dockSn(dockSn)
.obstacle_avoidance_horizon(data.getJSONObject("obstacle_avoidance").getInt("horizon"))
.obstacle_avoidance_upside(data.getJSONObject("obstacle_avoidance").getInt("upside"))
.obstacle_avoidance_downside(data.getJSONObject("obstacle_avoidance").getInt("downside"))
.is_near_area_limit(data.getInt("is_near_area_limit"))
.is_near_height_limit(data.getInt("is_near_height_limit"))
.payload_index(payload_index)
.gimbal_pitch(
payload_index != null ? data.getJSONObject(payload_index).getDouble("gimbal_pitch") : null)
.gimbal_roll(
payload_index != null ? data.getJSONObject(payload_index).getDouble("gimbal_roll") : null)
.gimbal_yaw(
payload_index != null ? data.getJSONObject(payload_index).getDouble("gimbal_yaw") : null)
.zoom_factor(
payload_index != null ? data.getJSONObject(payload_index).getDouble("zoom_factor") : null)
.track_id(data.getStr("track_id"))
.position_state_is_fixed(data.getJSONObject("position_state").getInt("is_fixed"))
.position_state_is_quality(data.getJSONObject("position_state").getInt("quality"))
.position_state_is_gps_number(data.getJSONObject("position_state").getInt("gps_number"))
.position_state_is_rtk_number(data.getJSONObject("position_state").getInt("rtk_number"))
.battery_capacity_percent(data.getJSONObject("battery").getInt("capacity_percent"))
.battery_remain_flight_time(data.getJSONObject("battery").getInt("remain_flight_time"))
.battery_return_home_power(data.getJSONObject("battery").getInt("return_home_power"))
.battery_landing_power(data.getJSONObject("battery").getInt("landing_power"))
.battery_capacity_percent_a(capacity_percent_a)
.battery_capacity_percent_b(capacity_percent_b)
.wind_direction(data.getInt("wind_direction"))
.wind_speed(data.getDouble("wind_speed"))
.home_distance(data.getDouble("home_distance"))
//state上报
// .home_latitude(data.getDouble("home_latitude"))
// .home_longitude(data.getDouble("home_longitude"))
.attitude_head(data.getInt("attitude_head"))
.attitude_roll(data.getDouble("attitude_roll"))
.attitude_pitch(data.getDouble("attitude_pitch"))
.elevation(data.getDouble("elevation"))
.height(data.getDouble("height"))
.latitude(data.getDouble("latitude"))
.longitude(data.getDouble("longitude"))
.vertical_speed(data.getDouble("vertical_speed"))
.horizontal_speed(data.getDouble("horizontal_speed"))
.build();
.attitude_head(data.getInt("attitude_head"))
.attitude_roll(data.getDouble("attitude_roll"))
.attitude_pitch(data.getDouble("attitude_pitch"))
.elevation(data.getDouble("elevation"))
.height(data.getDouble("height"))
.latitude(data.getDouble("latitude"))
.longitude(data.getDouble("longitude"))
.vertical_speed(data.getDouble("vertical_speed"))
.horizontal_speed(data.getDouble("horizontal_speed"))
.build();
influxService.addRecord(uavReport);
}
}

View File

@ -36,6 +36,7 @@ import java.util.List;
@Component
@RequiredArgsConstructor
public class RequestsHandler implements MessageHandler {
private final DJIConfig djiConfig;
private final MqttPushService mqttPushService;
private final DockDeviceDao dockDeviceDao;
@ -74,7 +75,8 @@ public class RequestsHandler implements MessageHandler {
return;
}
String sn = deviceJson.getStr("sn");
DockDeviceEntity dockDevice = dockDeviceDao.selectOne(new QueryWrapper<DockDeviceEntity>().eq("sn", sn));
DockDeviceEntity dockDevice = dockDeviceDao.selectOne(
new QueryWrapper<DockDeviceEntity>().eq("sn", sn));
if (dockDevice == null) {
data.set("result", 1);
mqttPushService.pushMessageByClient1(topic + BusinessConstant._REPLY, message.toString());
@ -100,7 +102,8 @@ public class RequestsHandler implements MessageHandler {
} else if (BusinessConstant.AIRPORT_ORGANIZATION_GET.equals(method)) {//第三步 查询设备绑定对应的组织信息
String deviceBindingCode = data.getStr("device_binding_code");
String organizationId = data.getStr("organization_id");
if (!djiConfig.getDeviceBindCode().equals(deviceBindingCode) || !djiConfig.getOrganizationId().equals(organizationId)) {
if (!djiConfig.getDeviceBindCode().equals(deviceBindingCode) || !djiConfig.getOrganizationId()
.equals(organizationId)) {
data.set("result", 2);
mqttPushService.pushMessageByClient1(topic + BusinessConstant._REPLY, message.toString());
log.debug("requests step3--> 查询设备绑定对应的组织信息失败设备绑定码或组织id不匹配");
@ -143,9 +146,9 @@ public class RequestsHandler implements MessageHandler {
device.setDeviceType(Integer.parseInt(split[1]));
device.setSubType(Integer.parseInt(split[2]));
DeviceDicEntity deviceDicEntity = deviceDicDao.selectOne(new QueryWrapper<DeviceDicEntity>()
.eq("domain", device.getDomain())
.eq("device_type", device.getDeviceType())
.eq("sub_type", device.getSubType()));
.eq("domain", device.getDomain())
.eq("device_type", device.getDeviceType())
.eq("sub_type", device.getSubType()));
device.setDeviceName(deviceDicEntity != null ? deviceDicEntity.getDeviceName() : "unknown");
bindDeviceList.add(device);
}
@ -156,7 +159,7 @@ public class RequestsHandler implements MessageHandler {
return;
}
dockDeviceDao.delete(new QueryWrapper<DockDeviceEntity>().eq("sn", gateway).or()
.eq("parent_sn", gateway));
.eq("parent_sn", gateway));
dockDeviceDao.insert(bindDeviceList);
data.set("result", 0);
mqttPushService.pushMessageByClient1(topic + BusinessConstant._REPLY, message.toString());
@ -208,6 +211,19 @@ public class RequestsHandler implements MessageHandler {
message.remove("gateway");
mqttPushService.pushMessageByClient1(topic + BusinessConstant._REPLY, message.toString());
log.debug("requests storage_config_get--> 存储配置获取成功");
} else if (BusinessConstant.PRIVATE_GET_MULTI_MIAOSUAN_PSDK_STATUS.equals(method)) {
String psdkSn = message.getStr("psdk_sn");
int status = CacheUtils.get(BusinessConstant.DOCK_OSD + psdkSn) != null ? 1 : 0;
data.clear();
data.set("result", 0);
JSONObject output = new JSONObject();
output.set("status", status);
data.set("output", output);
message.set("timestamp", System.currentTimeMillis());
message.remove("need_reply");
mqttPushService.pushMessageByClient1(topic + BusinessConstant._REPLY, message.toString());
log.debug("requests private_get_multi_miaosuan_psdk_status--> 检查妙算设备是否上线:{}-> status: {}",
psdkSn, status);
}
} else {
log.debug("requests --> payload解析失败解析后为null");

View File

@ -15,6 +15,7 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqttPushService {
@Resource(name = "mqttOutboundChannel1")
private MessageChannel mqttOutboundHandler1;
@ -22,7 +23,7 @@ public class MqttPushService {
private MessageChannel mqttOutboundHandler2;
/**
* 客户端1推送消息
* 客户端1推送消息 该连接处理正常的协议交互
*
* @param topic 主题
* @param msg 消息
@ -30,11 +31,11 @@ public class MqttPushService {
public void pushMessageByClient1(String topic, String msg) {
log.debug("client1 down-> topic:{},msg:{}", topic, msg);
mqttOutboundHandler1.send(MessageBuilder.withPayload(msg).
setHeader("mqtt_topic", topic).build());
setHeader("mqtt_topic", topic).build());
}
/**
* 客户端2推送消息
* 客户端2推送消息 该连接处理drc控制的协议交互
*
* @param topic 主题
* @param msg 消息
@ -42,6 +43,6 @@ public class MqttPushService {
public void pushMessageByClient2(String topic, String msg) {
log.debug("client2 down-> topic:{},msg:{}", topic, msg);
mqttOutboundHandler2.send(MessageBuilder.withPayload(msg).
setHeader("mqtt_topic", topic).build());
setHeader("mqtt_topic", topic).build());
}
}

View File

@ -0,0 +1,35 @@
package com.multictrl.modules.business.service;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.dto.multi.MultiDockDTO;
import com.multictrl.modules.business.dto.multi.MultiGroupDTO;
import com.multictrl.modules.business.dto.multi.MultiGroupDeviceDTO;
import com.multictrl.modules.business.dto.multi.MultiTaskDTO;
import com.multictrl.modules.business.entity.DockEntity;
import java.util.List;
/**
* 阿罗斯机场 蛙跳 充电机库 Ars550 换电机库 Ars600
*
* @author 张闯
* @since 2026-05-28
*/
public interface MultiService {
List<MultiDockDTO> getDockList();
void saveMultiGroup(MultiGroupDTO multiGroupDTO);
void saveMultiGroupDevice(MultiGroupDeviceDTO multiGroupDeviceDTO);
void updateMultiGroup(MultiGroupDTO multiGroupDTO);
void updateMultiGroupDevice(MultiGroupDeviceDTO multiGroupDeviceDTO);
String privateMultiDockTaskFlightTaskCheck(MultiTaskDTO multiTaskDTO);
String privateMultiDockFlightTaskOutbound(MultiTaskDTO multiTaskDTO);
String privateMultiDockFlightTaskInbound(MultiTaskDTO multiTaskDTO);
}

View File

@ -2,6 +2,7 @@ package com.multictrl.modules.business.service;
import com.multictrl.modules.business.dto.flight.FlightExecute;
import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo;
import java.util.List;
/**

View File

@ -93,6 +93,7 @@ public class DJIBaseServiceImpl implements DJIBaseService {
return message;
}
@Override
public String executeAndReturnResult(String dockSn, String method) {

View File

@ -0,0 +1,231 @@
package com.multictrl.modules.business.service.impl;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.MessageUtils;
import com.multictrl.modules.business.dao.MultiGroupDao;
import com.multictrl.modules.business.dao.MultiGroupDeviceDao;
import com.multictrl.modules.business.dto.flight.FlightExecute;
import com.multictrl.modules.business.dto.multi.MultiDockDTO;
import com.multictrl.modules.business.dto.multi.MultiGroupDTO;
import com.multictrl.modules.business.dto.multi.MultiGroupDeviceDTO;
import com.multictrl.modules.business.dto.multi.MultiTaskDTO;
import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo;
import com.multictrl.modules.business.service.DJIBaseService;
import com.multictrl.modules.business.service.MultiService;
import com.multictrl.modules.business.service.RouteFlightService;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 阿罗斯机场 蛙跳
*
* @author 张闯
* @since 2026-05-28
*/
@Slf4j
@Service
public class MultiServiceImpl implements MultiService {
@Resource
MultiGroupDao multiGroupDao;
@Resource
MultiGroupDeviceDao multiGroupDeviceDao;
@Resource
DJIBaseService djiBaseService;
@Resource
RouteFlightService routeFlightService;
private static final String MIAO_SUAN = "miao_suan";
@Override
public List<MultiDockDTO> getDockList() {
return List.of();
}
@Override
public void saveMultiGroup(MultiGroupDTO multiGroupDTO) {
}
@Override
public void saveMultiGroupDevice(MultiGroupDeviceDTO multiGroupDeviceDTO) {
}
@Override
public void updateMultiGroup(MultiGroupDTO multiGroupDTO) {
}
@Override
public void updateMultiGroupDevice(MultiGroupDeviceDTO multiGroupDeviceDTO) {
}
@Override
public String privateMultiDockTaskFlightTaskCheck(MultiTaskDTO multiTaskDTO) {
for (MultiDockDTO multiDockDTO : multiTaskDTO.getDocks()) {
if (multiDockDTO.getDockType().equals(MIAO_SUAN)) {
continue;
}
String result = privateMultiDockTaskFlightTaskCheck(multiDockDTO.getDockSn());
log.info("privateMultiDockTaskFlightTaskCheck-> dockSn:{},result:{}", multiDockDTO.getDockSn(), result);
}
return MessageUtils.getMessage(ErrorCode.DOCK_EXEC_SUCCESS);
}
@Override
public String privateMultiDockFlightTaskOutbound(MultiTaskDTO multiTaskDTO) {
//第一步调用所有机场出库
for (MultiDockDTO multiDockDTO : multiTaskDTO.getDocks()) {
if (multiDockDTO.getDockType().equals(MIAO_SUAN)) {
continue;
}
boolean need_drone_open = multiDockDTO.getDockSn()
.equals(multiTaskDTO.getMiaoSuanLocatedDock().getDockSn());
String result = privateMultiDockFlightTaskOutbound(multiDockDTO.getDockSn(),
need_drone_open, multiTaskDTO.getMiaoSuan().getDockSn(), multiTaskDTO.getTaskId());
log.info("privateMultiDockFlightTaskOutbound-> 调用所有机场出库 dockSn:{},result:{}",
multiDockDTO.getDockSn(),
result);
}
//第二步检查机库各传感器状态是否已经出库完成,180秒内
int tryCount = 180;
while (tryCount-- > 0) {
boolean allFinished = true;
for (MultiDockDTO multiDockDTO : multiTaskDTO.getDocks()) {
if (multiDockDTO.getDockType().equals(MIAO_SUAN)) {
continue;
}
Object o;
if ((o = CacheUtils.get(BusinessConstant.DOCK_OSD + multiDockDTO.getDockSn())) == null) {
continue;
}
JSONObject jsonObject = (JSONObject) o;
//cover_state 舱盖状态 {"0":"关闭","1":"打开","2":"半开","3":"舱盖状态异常"}
int cover_state = jsonObject.getInt("cover_state");
//putter_state 推杆状态 {"0":"关闭","1":"打开","2":"半开","3":"推杆状态异常"}
int putter_state = jsonObject.getInt("putter_state");
log.info(
"privateMultiDockFlightTaskOutbound-> 检查机库各传感器状态,是否已经出库完成 dockSn:{},cover_state:{},putter_state:{}"
, multiDockDTO.getDockSn(), cover_state, putter_state);
allFinished = allFinished && cover_state == 1 && putter_state == 1;
}
if (allFinished) {
break;
}
}
if (tryCount <= 0) {
return "出库超时";
}
//第三步下发航线给psdk
FlightExecute flightExecute = new FlightExecute();
flightExecute.setTaskId(multiTaskDTO.getTaskId());
flightExecute.setRouteId(multiTaskDTO.getRouteId());
//获取每个机库的 经纬度/高度/备降点经纬度/备降点高度
List<PrivateMultiFlightBindDockInfo> privateMultiFlightBindDockInfos = new ArrayList<>();
for (MultiDockDTO multiDockDTO : multiTaskDTO.getDocks()) {
if (multiDockDTO.getDockType().equals(MIAO_SUAN)) {
continue;
}
Object o;
if ((o = CacheUtils.get(BusinessConstant.DOCK_OSD + multiDockDTO.getDockSn())) == null) {
throw new RenException("机库: " + multiDockDTO.getDockSn() + "信息获取失败");
}
JSONObject jsonObject = (JSONObject) o;
PrivateMultiFlightBindDockInfo privateMultiFlightBindDockInfo = new PrivateMultiFlightBindDockInfo();
privateMultiFlightBindDockInfo.setDock_sn(multiDockDTO.getDockSn());
privateMultiFlightBindDockInfo.setLongitude(jsonObject.getDouble("longitude"));
privateMultiFlightBindDockInfo.setLatitude(jsonObject.getDouble("latitude"));
privateMultiFlightBindDockInfo.setHeight(jsonObject.getDouble("height"));
JSONObject alternate_land_point = jsonObject.getJSONObject("alternate_land_point");
privateMultiFlightBindDockInfo.setAlternate_land_point_longitude(
alternate_land_point.getDouble("longitude"));
privateMultiFlightBindDockInfo.setAlternate_land_point_latitude(alternate_land_point.getDouble("latitude"));
privateMultiFlightBindDockInfo.setAlternate_land_point_height(alternate_land_point.getDouble("height"));
privateMultiFlightBindDockInfo.setAlternate_land_point_safe_land_height(
alternate_land_point.getDouble("safe_land_height"));
privateMultiFlightBindDockInfos.add(privateMultiFlightBindDockInfo);
}
flightExecute.setPrivateMultiFlightBindDockInfos(privateMultiFlightBindDockInfos);
log.info("privateMultiDockFlightTaskOutbound-> 下发航线给妙算 flightExecute:{}", flightExecute);
return routeFlightService.flightExecute(multiTaskDTO.getMiaoSuan().getDockSn(), flightExecute);
}
@Override
public String privateMultiDockFlightTaskInbound(MultiTaskDTO multiTaskDTO) {
//第一步调用所有机场入库
for (MultiDockDTO multiDockDTO : multiTaskDTO.getDocks()) {
if (multiDockDTO.getDockType().equals(MIAO_SUAN)) {
continue;
}
boolean need_drone_close = multiDockDTO.getDockSn()
.equals(multiTaskDTO.getMiaoSuanLocatedDock().getDockSn());
String result = privateMultiDockFlightTaskInbound(multiDockDTO.getDockSn(),
need_drone_close, multiTaskDTO.getMiaoSuan().getDockSn(), multiTaskDTO.getTaskId());
log.info("privateMultiDockFlightTaskInbound-> dockSn:{},result:{}", multiDockDTO.getDockSn(),
result);
}
//第二步判断所有机场的门都关闭了
int tryCount = 180;
while (tryCount-- > 0) {
boolean allFinished = true;
for (MultiDockDTO multiDockDTO : multiTaskDTO.getDocks()) {
if (multiDockDTO.getDockType().equals(MIAO_SUAN)) {
continue;
}
Object o;
if ((o = CacheUtils.get(BusinessConstant.DOCK_OSD + multiDockDTO.getDockSn())) == null) {
continue;
}
JSONObject jsonObject = (JSONObject) o;
//cover_state 舱盖状态 {"0":"关闭","1":"打开","2":"半开","3":"舱盖状态异常"}
int cover_state = jsonObject.getInt("cover_state");
log.info(
"privateMultiDockFlightTaskInbound-> 检查机库各传感器状态,是否已经入库完成 dockSn:{},cover_state:{}"
, multiDockDTO.getDockSn(), cover_state);
allFinished = allFinished && cover_state == 0;
}
if (allFinished) {
break;
}
}
if (tryCount <= 0) {
return "入库超时";
}
return "流程结束";
}
private String privateMultiDockTaskFlightTaskCheck(String dockSn) {
return djiBaseService.executeAndReturnResult(dockSn, "private_multi_dock_task_flighttask_check");
}
private String privateMultiDockFlightTaskOutbound(String dockSn, boolean need_drone_open, String psdk_sn,
String task_id) {
JSONObject jsonObject = new JSONObject();
jsonObject.set("need_drone_open", need_drone_open);
jsonObject.set("psdk_sn", psdk_sn);
jsonObject.set("task_id", task_id);
return djiBaseService.executeAndReturnResult(dockSn, "private_multi_doc_dask_flighttask_outbound", jsonObject);
}
private String privateMultiDockFlightTaskInbound(String dockSn, boolean need_drone_close, String psdk_sn,
String task_id) {
JSONObject jsonObject = new JSONObject();
jsonObject.set("need_drone_close", need_drone_close);
jsonObject.set("psdk_sn", psdk_sn);
jsonObject.set("task_id", task_id);
return djiBaseService.executeAndReturnResult(dockSn, "private_multi_doc_dask_flighttask_inbound", jsonObject);
}
}

View File

@ -15,6 +15,7 @@ import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.flight.FlightExecute;
import com.multictrl.modules.business.dto.flight.FlightTaskPrepare;
import com.multictrl.modules.business.dto.flight.InFlightWaylineDeliver;
import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo;
import com.multictrl.modules.business.service.DJIBaseService;
import com.multictrl.modules.business.service.FlightTaskService;
import com.multictrl.modules.business.service.RouteFlightService;
@ -40,6 +41,7 @@ import java.util.concurrent.locks.ReentrantLock;
@Service
@RequiredArgsConstructor
public class RouteFlightServiceImpl implements RouteFlightService {
private final DJIBaseService djiBaseService;
private final RouteService routeService;
private final MinioConfig minioConfig;
@ -53,6 +55,9 @@ public class RouteFlightServiceImpl implements RouteFlightService {
return djiBaseService.executeAndReturnResult(dockSn, "flighttask_undo", data);
}
/*
* multiFlightBindDockInfos aros机场蛙跳模式参数把N个参与到蛙跳的的机场信息给到妙算返航时自动计算离哪个机库位置近就降落到哪个机库
*/
@Override
public String flightExecute(String dockSn, FlightExecute flightExecute) {
ReentrantLock lock = cacheLock.computeIfAbsent(dockSn, k -> new ReentrantLock());
@ -60,7 +65,7 @@ public class RouteFlightServiceImpl implements RouteFlightService {
try {
Boolean dockOnline = djiBaseService.isDockOnline(dockSn);
if (!dockOnline) {
throw new RenException(ErrorCode.DOCK_OSD_NOT_EXIST, "无法操作");
throw new RenException(ErrorCode.DOCK_OSD_NOT_EXIST, "机场不在线,无法操作");
}
Integer dockModeCode = djiBaseService.getDockModeCode(dockSn);
if (!Objects.equals(DockMode.IDLE.getCode(), dockModeCode)) {
@ -73,6 +78,9 @@ public class RouteFlightServiceImpl implements RouteFlightService {
}
//任务标识
String taskId = IdUtil.fastSimpleUUID();
if (StrUtil.isNotEmpty(flightExecute.getTaskId())) {
taskId = flightExecute.getTaskId();
}
CacheUtils.set(BusinessConstant.WORKING_TASK_ID + dockSn, taskId);
//设置默认相机模式需要定频推送前端
CacheUtils.set(BusinessConstant.UAV_VIDEO_TYPE + dockSn, "wide");
@ -110,12 +118,17 @@ public class RouteFlightServiceImpl implements RouteFlightService {
if (flightExecute.getSimulateMission() != null) {
flightTaskPrepare.setSimulate_mission(flightExecute.getSimulateMission());
}
if (flightExecute.getPrivateMultiFlightBindDockInfos() != null) {
flightTaskPrepare.setPrivate_multi_flight_bind_dock_info(
flightExecute.getPrivateMultiFlightBindDockInfos());
}
JSONObject data = JsonUtils.parseObject(JsonUtils.toJsonString(flightTaskPrepare), JSONObject.class);
String result = djiBaseService.executeAndReturnResult(dockSn, "flighttask_prepare", data);
log.debug("flight_task_prepare --> result:{}", result);
//执行任务
Utils.sleep(1000);
String resultMsg = djiBaseService.executeAndReturnResult(dockSn, "flighttask_execute", new JSONObject().set("flight_id", taskId));
String resultMsg = djiBaseService.executeAndReturnResult(dockSn, "flighttask_execute",
new JSONObject().set("flight_id", taskId));
flightTaskService.addRouteTask(taskId, dockSn, route.getId());
return resultMsg;
} finally {
@ -176,7 +189,8 @@ public class RouteFlightServiceImpl implements RouteFlightService {
if (flightId == null) {
throw new RenException(ErrorCode.ROUTE_TASK_NOT_EXIST);
}
return djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_stop", new JSONObject().set("in_flight_wayline_id", flightId));
return djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_stop",
new JSONObject().set("in_flight_wayline_id", flightId));
}
@Override
@ -185,6 +199,7 @@ public class RouteFlightServiceImpl implements RouteFlightService {
if (flightId == null) {
throw new RenException(ErrorCode.ROUTE_TASK_NOT_EXIST);
}
return djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_recover", new JSONObject().set("in_flight_wayline_id", flightId));
return djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_recover",
new JSONObject().set("in_flight_wayline_id", flightId));
}
}

View File

@ -78,6 +78,6 @@ influx:
org: dj
host:
ip: 223.108.157.174
ip: 192.168.1.193
port: 61620
is-ssl: false

19
pom.xml
View File

@ -8,8 +8,20 @@
<name>Dock-MultiCtrl</name>
<description>大疆机场多版本控制</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
<parent>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.5</version>
@ -106,6 +118,11 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.62</version>
</dependency>
</dependencies>
<dependencyManagement>

View File

@ -2027,3 +2027,75 @@ INSERT INTO "public"."bus_device_dic"
VALUES (41, 1, 99, 0, 'M4TD Camera', NULL);
INSERT INTO "public"."bus_device_dic"
VALUES (42, 3, 3, 0, 'Dock3', NULL);
ALTER TABLE public.bus_dock
ADD scenario varchar(128) NULL;
COMMENT
ON COLUMN public.bus_dock.scenario IS '使用场景';
CREATE TABLE public.bus_multi_group
(
id int8 NOT NULL,
group_name varchar(128) NOT NULL,
group_mark varchar(256) NULL,
dept_id int8 NULL,
creator int8 NULL,
create_date timestamp NULL,
updater int8 NULL,
update_date timestamp NULL
);
COMMENT
ON TABLE public.bus_multi_group IS '跳飞设备组信息';
-- Column comments
COMMENT
ON COLUMN public.bus_multi_group.id IS '组id';
COMMENT
ON COLUMN public.bus_multi_group.group_name IS '组名称';
COMMENT
ON COLUMN public.bus_multi_group.group_mark IS '组描述';
COMMENT
ON COLUMN public.bus_multi_group.dept_id IS '所属组织';
COMMENT
ON COLUMN public.bus_multi_group.creator IS '创建人';
COMMENT
ON COLUMN public.bus_multi_group.create_date IS '创建时间';
COMMENT
ON COLUMN public.bus_multi_group.updater IS '更新人';
COMMENT
ON COLUMN public.bus_multi_group.update_date IS '更新时间';
CREATE TABLE public.bus_multi_group_device
(
id int8 NOT NULL,
groud_id int8 NOT NULL,
gateway_sn varchar(64) NOT NULL,
creator int8 NULL,
create_date timestamp NULL,
updater int8 NULL,
update_date timestamp NULL
);
COMMENT
ON TABLE public.bus_multi_group_device IS '蛙跳任务组绑定的设备编号';
-- Column comments
COMMENT
ON COLUMN public.bus_multi_group_device.id IS '编号';
COMMENT
ON COLUMN public.bus_multi_group_device.groud_id IS '蛙跳组';
COMMENT
ON COLUMN public.bus_multi_group_device.gateway_sn IS '设备编号sn';
COMMENT
ON COLUMN public.bus_multi_group_device.creator IS '创建人';
COMMENT
ON COLUMN public.bus_multi_group_device.create_date IS '创建时间';
COMMENT
ON COLUMN public.bus_multi_group_device.updater IS '更新人';
COMMENT
ON COLUMN public.bus_multi_group_device.update_date IS '更新时间';