From 8a1be2c973eacb1fb661caf920321eb2c58ef87c Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Mon, 27 Oct 2025 17:15:18 +0800
Subject: [PATCH] 增加云台速度简易控制接口

---
 ard-admin/src/main/java/com/ard/web/controller/work/UavGimbalController.java                 |   14 ++++
 ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java                     |   74 ++++++++++++++++++++++++
 ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java           |   14 +++-
 ard-work/src/main/java/com/ard/work/domain/dto/control/SpeedControlDTO.java                  |    5 +
 ard-work/src/main/java/com/ard/work/component/websocket/handler/BaseSubscriptionManager.java |   21 +++++--
 5 files changed, 117 insertions(+), 11 deletions(-)

diff --git a/ard-admin/src/main/java/com/ard/web/controller/work/UavGimbalController.java b/ard-admin/src/main/java/com/ard/web/controller/work/UavGimbalController.java
index aad82b9..d943bcc 100644
--- a/ard-admin/src/main/java/com/ard/web/controller/work/UavGimbalController.java
+++ b/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();
+    }
 }
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/work/component/websocket/handler/BaseSubscriptionManager.java b/ard-work/src/main/java/com/ard/work/component/websocket/handler/BaseSubscriptionManager.java
index 5569e22..8d0db3e 100644
--- a/ard-work/src/main/java/com/ard/work/component/websocket/handler/BaseSubscriptionManager.java
+++ b/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);
diff --git a/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java b/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java
index 1e1e469..e0ac46a 100644
--- a/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java
+++ b/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);
diff --git a/ard-work/src/main/java/com/ard/work/domain/dto/control/SpeedControlDTO.java b/ard-work/src/main/java/com/ard/work/domain/dto/control/SpeedControlDTO.java
index 5144dd2..c3ede6f 100644
--- a/ard-work/src/main/java/com/ard/work/domain/dto/control/SpeedControlDTO.java
+++ b/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;
+
 }
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java b/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java
new file mode 100644
index 0000000..062969d
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java
@@ -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();
+    }
+}

--
Gitblit v1.9.3