ard-admin/src/main/java/com/ard/web/controller/work/UavGimbalController.java
@@ -5,6 +5,7 @@ import com.ard.work.domain.dto.control.AngleControlDTO; import com.ard.work.domain.dto.control.SpeedControlDTO; import com.ard.work.service.ITyService; import com.ard.work.service.impl.GimbalSpeedService; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -26,6 +27,8 @@ @Resource private ITyService tyService; @Resource private GimbalSpeedService gimbalSpeedService; @ApiOperation(value = "云台转动(基于角度)", notes = "actionCode值:0-回中,1-上,2-右上,3-右,4-右下,5-下,6-左下,7-左,8-左上,254-停止 \r\n rotationAngle值:取值范围:0~255【云台实际转动角度= 转动角度/100+2.5 单位:度】") @@ -52,4 +55,15 @@ return AjaxResult.success(); } @ApiOperation(value = "云台速度控制(开始/停止统一接口)") @ApiOperationSupport(order = 4) @PostMapping("/speedControlSimple") public AjaxResult speedControlSimple(@RequestBody @Validated SpeedControlDTO speedControlDto) { if (speedControlDto.isStart()) { gimbalSpeedService.startControl(speedControlDto); } else { gimbalSpeedService.stopControl(speedControlDto.getBoxSn()); } return AjaxResult.success(); } } ard-work/src/main/java/com/ard/work/component/websocket/handler/BaseSubscriptionManager.java
@@ -24,8 +24,9 @@ * 订阅指定设备的数据 */ public void subscribe(String boxSn, Long userId, Integer period) { subscriptions.computeIfAbsent(boxSn, k -> new HashSet<>()).add(userId); subscriptions.computeIfAbsent(boxSn, k -> ConcurrentHashMap.newKeySet()).add(userId); if (subscriptions.get(boxSn).size() == 1) { log.debug("首次订阅,启动推送任务"); startPushTask(boxSn, period); } } @@ -34,15 +35,19 @@ * 取消订阅 */ public void unsubscribe(String boxSn, Long userId) { Set<Long> userIds = subscriptions.get(boxSn); if (userIds != null) { subscriptions.compute(boxSn, (key, userIds) -> { if (userIds == null) { return null; } userIds.remove(userId); if (userIds.isEmpty()) { stopPushTask(boxSn); log.debug("无人订阅设备 {},移除订阅记录", boxSn); return null; // 直接返回 null 等价于 remove(key) } } return userIds; }); } /** * 启动推送任务 */ @@ -52,7 +57,11 @@ return; } ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> { sendDataToSubscribers(boxSn); try { sendDataToSubscribers(boxSn); } catch (Exception e) { log.error("Error in push task for boxSn={}", boxSn, e); } }, 0, period, TimeUnit.SECONDS); pushTasks.put(boxSn, future); ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java
@@ -7,26 +7,30 @@ import com.ard.work.domain.dto.state.UavState; import com.ard.work.domain.dto.telemetry.TelemetryData; import com.ard.work.service.control.UavControlService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.HashMap; import java.util.Map; @Slf4j @Component public class UavAllManager extends BaseSubscriptionManager<Map<String,Object>> { public class UavAllManager extends BaseSubscriptionManager<Map<String, Object>> { @Resource private UavControlService uavControlService; @Override protected Map<String,Object> getData(String boxSn) { Map<String,Object> map=new HashMap<>(); protected Map<String, Object> getData(String boxSn) { log.debug("获取无人机所有数据"); Map<String, Object> map = new HashMap<>(); map.put("boxSn", boxSn); UavState uavState = UavStateCache.get(boxSn); if (uavState != null) { uavState.calculateFieldAngle();// 计算当前无人机的FieldAngle } map.put("boxSn", uavState.getBoxSn()); map.put("state", uavState); TelemetryData uavTelemetryData = UavTelemetryDataCache.get(boxSn); map.put("telemetry", uavTelemetryData); ard-work/src/main/java/com/ard/work/domain/dto/control/SpeedControlDTO.java
@@ -31,4 +31,9 @@ */ private float rollSpeed; /** * 指令类型:true=开始转动,false=停止转动 */ private boolean start; } ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java
New file @@ -0,0 +1,74 @@ package com.ard.work.service.impl; import com.ard.common.core.domain.model.LoginUser; import com.ard.common.utils.SecurityUtils; import com.ard.work.domain.cache.UavStateCache; import com.ard.work.domain.dto.control.SpeedControlDTO; import com.ard.work.domain.dto.state.UavState; import com.ard.work.service.ITyService; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Map; import java.util.concurrent.*; @Service @Slf4j public class GimbalSpeedService { private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final Map<String, ScheduledFuture<?>> tasks = new ConcurrentHashMap<>(); @Resource private ITyService tyService; public synchronized void startControl(SpeedControlDTO speed) { String boxSn = speed.getBoxSn(); if (tasks.containsKey(boxSn)) { return; } LoginUser loginUser = SecurityUtils.getLoginUser();// 主线程获取 ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> { double currentYaw = getCurrentYaw(boxSn); if ((speed.getYawSpeed() < 0 && currentYaw <= -180) || (speed.getYawSpeed() > 0 && currentYaw >= 180)) { stopControl(boxSn); return; } try { // 在定时任务线程设置用户上下文 // 手动设置 SecurityContext SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken( loginUser, null, loginUser.getAuthorities()) ); Boolean b = tyService.speedControl(speed); log.warn("speedControl result:{}", b); } catch (Exception e) { log.error(e.getMessage()); } }, 0, 200, TimeUnit.MILLISECONDS); tasks.put(boxSn, future); } public synchronized void stopControl(String boxSn) { ScheduledFuture<?> future = tasks.remove(boxSn); if (future != null) { future.cancel(false); } } private double getCurrentYaw(String boxSn) { UavState uavState = UavStateCache.get(boxSn); if(uavState==null) { return 0; } return uavState.getPtzState().getYaw(); } }