liusuyi
2025-10-27 8a1be2c973eacb1fb661caf920321eb2c58ef87c
增加云台速度简易控制接口
1 files added
4 files modified
128 ■■■■■ changed files
ard-admin/src/main/java/com/ard/web/controller/work/UavGimbalController.java 14 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/websocket/handler/BaseSubscriptionManager.java 21 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java 14 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/domain/dto/control/SpeedControlDTO.java 5 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java 74 ●●●●● patch | view | raw | blame | history
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();
    }
}