feat(inspection): 新增扬声器播放音频功能并优化机器人状态同步机制

- 在 ActionEnum 中添加 SPEAKER_PLAYAUDIO 操作类型
- 配置机器人状态检查任务的启用和时间间隔参数
- 移除应用配置中的废弃TTS外部接口配置
- 实现 EdgeSpeakerOperateService 中的音频播放功能和参数验证
- 重构 FlowActionExecutorService 以支持多种操作类型的服务分发
- 更新数据库表结构中机器人状态字段的含义和注释说明
- 修改 InspectionRobot 实体类中的状态定义和业务编号描述
- 扩展 InspectionRobotMapper 接口以支持运行时状态更新和候选查询
- 实现 InspectionRobotService 中的 AGV 请求构建和状态同步逻辑
- 添加 RobotRuntimeStateRefreshTask 定时任务来同步机器人运行状态
- 修复 InspectionTaskInstanceService 中的重复机器人ID设置问题
- 优化 RobotQuicStateService 中的设备状态判断逻辑
This commit is contained in:
lixiaolong 2026-08-07 16:17:12 +08:00
parent 86a8e02d85
commit 54df691ea6
14 changed files with 296 additions and 75 deletions

View File

@ -132,8 +132,3 @@ flowise:
abort: http://192.168.0.108:3000/api/v1/chatmessage/abort/f99329e9-b33d-437d-90ed-69eaa8a05418/ abort: http://192.168.0.108:3000/api/v1/chatmessage/abort/f99329e9-b33d-437d-90ed-69eaa8a05418/
query: http://192.168.0.108:3000/api/v1/executions/ query: http://192.168.0.108:3000/api/v1/executions/
api-key: pg61JW6W_GXyqmqSoURJ4mlSRrCMRzGLBJli_w3BFkg api-key: pg61JW6W_GXyqmqSoURJ4mlSRrCMRzGLBJli_w3BFkg
# TTS外部接口配置
tts:
external-api:
url: http://192.168.0.102:9003/generate_advanced_audio

View File

@ -124,6 +124,11 @@ xss:
# Integrated QUIC gateway and in-process node synchronization. # Integrated QUIC gateway and in-process node synchronization.
cmvr: cmvr:
inspection:
robot-state:
enabled: true
initial-delay-ms: 3000
refresh-ms: 1000
quic: quic:
enabled: true enabled: true
bind-host: 0.0.0.0 bind-host: 0.0.0.0

View File

@ -38,9 +38,9 @@ public class InspectionRobot extends BaseEntity
@ApiModelProperty("机器人永久唯一ID") @ApiModelProperty("机器人永久唯一ID")
private String robotId; private String robotId;
/** 机器人编码 */ /** Business display code. It is not an edge device ID. */
@Excel(name = "机器人编码") @Excel(name = "机器人业务编号")
@ApiModelProperty("机器人编码") @ApiModelProperty("机器人业务编号仅用于业务展示不作为设备ID")
private String robotCode; private String robotCode;
/** 机器人名称 */ /** 机器人名称 */
@ -75,7 +75,7 @@ public class InspectionRobot extends BaseEntity
/** 当前状态 */ /** 当前状态 */
@Excel(name = "当前状态") @Excel(name = "当前状态")
@ApiModelProperty("当前状态(0在线 1离线 2充电中 3巡检中)") @ApiModelProperty("AGV运行模式(0未知 1断开 2空闲 3手动 4自动 5充电 6暂停 7停止 8故障 9急停)")
private String status; private String status;
/** 电量百分比 */ /** 电量百分比 */

View File

@ -34,9 +34,9 @@ public class InspectionRobotVo extends BaseEntity
@ApiModelProperty("机器人永久唯一ID") @ApiModelProperty("机器人永久唯一ID")
private String robotId; private String robotId;
/** 机器人编码 */ /** Business display code. It is not an edge device ID. */
@Excel(name = "机器人编码") @Excel(name = "机器人业务编号")
@ApiModelProperty("机器人编码") @ApiModelProperty("机器人业务编号仅用于业务展示不作为设备ID")
private String robotCode; private String robotCode;
/** 机器人名称 */ /** 机器人名称 */
@ -81,7 +81,7 @@ public class InspectionRobotVo extends BaseEntity
/** 当前状态 */ /** 当前状态 */
@Excel(name = "当前状态") @Excel(name = "当前状态")
@ApiModelProperty("当前状态(0在线 1离线 2充电中 3巡检中)") @ApiModelProperty("AGV运行模式(0未知 1断开 2空闲 3手动 4自动 5充电 6暂停 7停止 8故障 9急停)")
private String status; private String status;
/** 电量百分比 */ /** 电量百分比 */

View File

@ -27,6 +27,16 @@ public interface InspectionRobotMapper extends MPJBaseMapper<InspectionRobot>
int updateQuicRuntimeState(InspectionRobot robot); int updateQuicRuntimeState(InspectionRobot robot);
/**
* Update the AGV runtime snapshot without changing business audit fields.
*/
int updateRuntimeStatus(InspectionRobot robot);
/**
* Select connected robots that currently report one online AGV device.
*/
List<InspectionRobot> selectRuntimeSyncCandidates();
int archiveInspectionRobotByIds(@Param("ids") String[] ids, @Param("updateBy") String updateBy); int archiveInspectionRobotByIds(@Param("ids") String[] ids, @Param("updateBy") String updateBy);
/** /**

View File

@ -28,6 +28,7 @@ import java.util.Objects;
public class RobotQuicStateService { public class RobotQuicStateService {
private static final Logger log = LoggerFactory.getLogger(RobotQuicStateService.class); private static final Logger log = LoggerFactory.getLogger(RobotQuicStateService.class);
private static final int AGV_DEVICE_KIND = 1;
private final InspectionRobotMapper robotMapper; private final InspectionRobotMapper robotMapper;
private final InspectionRobotDeviceMapper deviceMapper; private final InspectionRobotDeviceMapper deviceMapper;
@ -90,6 +91,9 @@ public class RobotQuicStateService {
robot.setSoftwareVersion(node.getSoftwareVersion()); robot.setSoftwareVersion(node.getSoftwareVersion());
robot.setLastHeartbeatTime(eventTime); robot.setLastHeartbeatTime(eventTime);
robot.setUpdateTime(DateUtils.getNowDate()); robot.setUpdateTime(DateUtils.getNowDate());
if (offline || !hasEnabledAgv(node)) {
robot.setStatus("1");
}
robotMapper.updateQuicRuntimeState(robot); robotMapper.updateQuicRuntimeState(robot);
if (offline) { if (offline) {
@ -162,6 +166,11 @@ public class RobotQuicStateService {
return device; return device;
} }
private boolean hasEnabledAgv(NodeSnapshot node) {
return node.getDeviceManager().getDevicesList().stream()
.anyMatch(device -> device.getKind() == AGV_DEVICE_KIND && device.getEnabled());
}
private String resolveGrpcHost(NodeSnapshot node) { private String resolveGrpcHost(NodeSnapshot node) {
String advertised = StringUtils.trimToNull(node.getGrpcEndpoint().getHost()); String advertised = StringUtils.trimToNull(node.getGrpcEndpoint().getHost());
String observed = StringUtils.trimToNull(node.getObservedSourceIp()); String observed = StringUtils.trimToNull(node.getObservedSourceIp());

View File

@ -2,9 +2,11 @@
package com.cmvr.inspection.service.impl; package com.cmvr.inspection.service.impl;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import cmvr.msgs.AgvUtils; import cmvr.msgs.AgvUtils;
import cmvr.quic_edge.v1.QuicEdge;
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl; import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
import com.cmvr.common.utils.SecurityUtils; import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.edge.client.model.EdgeCommonVO; import com.cmvr.edge.client.model.EdgeCommonVO;
@ -167,6 +169,50 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
return robotId == null ? null : robotId.trim(); return robotId == null ? null : robotId.trim();
} }
/**
* Build an AGV request from the device snapshot reported by this robot.
* robotCode is a business label and must never be used as an edge device ID.
*/
private EdgeCommonVO buildAgvRequest(InspectionRobot robot)
{
if (robot == null || StrUtil.isBlank(robot.getRobotId())) {
throw new GlobalException("机器人永久ID不能为空");
}
List<InspectionRobotDevice> agvDevices = inspectionRobotDeviceMapper.selectByRobotIdAndKind(
robot.getRobotId(), QuicEdge.DeviceKind.DEVICE_KIND_AGV_VALUE, true);
if (agvDevices.isEmpty()) {
List<InspectionRobotDevice> onlineDevices = inspectionRobotDeviceMapper.selectByRobotIdAndKind(
robot.getRobotId(), null, true);
if (onlineDevices.isEmpty()) {
throw new GlobalException("机器人当前没有在线设备:" + robot.getRobotId());
}
String deviceSummary = onlineDevices.stream()
.map(device -> device.getDeviceId() + "(" + deviceKindName(device.getDeviceKind()) + ")")
.collect(java.util.stream.Collectors.joining(""));
throw new GlobalException("机器人在线设备中没有AGV当前设备" + deviceSummary);
}
if (agvDevices.size() > 1) {
String deviceIds = agvDevices.stream()
.map(InspectionRobotDevice::getDeviceId)
.collect(java.util.stream.Collectors.joining(""));
throw new GlobalException("机器人存在多个在线AGV设备无法确定默认设备" + deviceIds);
}
EdgeCommonVO request = new EdgeCommonVO();
request.setRobotId(robot.getRobotId());
request.setDeviceId(agvDevices.get(0).getDeviceId());
return request;
}
private String deviceKindName(Integer deviceKind)
{
if (deviceKind == null) {
return "未知类型";
}
QuicEdge.DeviceKind kind = QuicEdge.DeviceKind.forNumber(deviceKind);
return kind == null ? "未知类型" + deviceKind : kind.name().replace("DEVICE_KIND_", "");
}
/** /**
* 获取机器人地图列表从AGV获取 * 获取机器人地图列表从AGV获取
* *
@ -180,15 +226,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
if (robot == null) { if (robot == null) {
throw new GlobalException("机器人不存在"); throw new GlobalException("机器人不存在");
} }
if (StrUtil.isBlank(robot.getIpAddress())) { return edgeAgvService.listMaps(buildAgvRequest(robot));
throw new GlobalException("机器人IP地址未配置");
}
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
return edgeAgvService.listMaps(edgeCommonVO);
} }
/** /**
@ -233,11 +271,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
throw new GlobalException("机器人不存在"); throw new GlobalException("机器人不存在");
} }
EdgeCommonVO edgeCommonVO = new EdgeCommonVO(); return edgeAgvService.downloadMap(buildAgvRequest(robot), mapName);
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
return edgeAgvService.downloadMap(edgeCommonVO, mapName);
} }
/** /**
@ -276,22 +310,16 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
} }
// 5. 从来源机器人下载地图JSON // 5. 从来源机器人下载地图JSON
EdgeCommonVO sourceEdgeVO = new EdgeCommonVO(); String mapContent = edgeAgvService.downloadMap(
sourceEdgeVO.setRobotId(sourceRobot.getRobotId()); buildAgvRequest(sourceRobot), map.getMapSourceName());
sourceEdgeVO.setDeviceId(sourceRobot.getRobotCode());
String mapContent = edgeAgvService.downloadMap(sourceEdgeVO, map.getMapSourceName());
if (StrUtil.isBlank(mapContent)) { if (StrUtil.isBlank(mapContent)) {
throw new GlobalException("下载的地图内容为空"); throw new GlobalException("下载的地图内容为空");
} }
// 6. 上传地图到目标机器人 // 6. 上传地图到目标机器人
EdgeCommonVO targetEdgeVO = new EdgeCommonVO(); edgeAgvService.uploadMap(
targetEdgeVO.setRobotId(targetRobot.getRobotId()); buildAgvRequest(targetRobot), map.getMapSourceName(), mapContent);
targetEdgeVO.setDeviceId(targetRobot.getRobotCode());
edgeAgvService.uploadMap(targetEdgeVO, map.getMapSourceName(), mapContent);
return "上传成功"; return "上传成功";
} }
@ -310,31 +338,37 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
throw new GlobalException("机器人不存在"); throw new GlobalException("机器人不存在");
} }
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
try { try {
// 获取运行时状态包含电池位置等信息 AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(buildAgvRequest(robot));
AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(edgeCommonVO); if (runtimeState == null) {
throw new GlobalException("AGV未返回运行状态");
}
// 设置电池电量 // 设置电池电量
if (runtimeState.hasBattery()) { if (runtimeState.hasBattery()) {
robot.setBatteryLevel((int)(runtimeState.getBattery().getPercentage() * 100)); int percentage = (int) Math.round(runtimeState.getBattery().getPercentage() * 100D);
robot.setBatteryLevel(Math.max(0, Math.min(100, percentage)));
} }
// 设置位置信息 // 设置位置信息
if (runtimeState.hasPose()) { if (runtimeState.hasPose()) {
AgvUtils.AgvPose2d pose = runtimeState.getPose(); AgvUtils.AgvPose2d pose = runtimeState.getPose();
robot.setCurrentPosition(pose.getX() + "," + pose.getY() + "," + pose.getTheta()); robot.setCurrentPosition(String.format(Locale.ROOT, "%.3f,%.3f,%.3f",
pose.getX(), pose.getY(), pose.getTheta()));
} }
robot.setUpdateTime(DateUtils.getNowDate()); // Keep the platform status identical to the edge AgvMode value.
inspectionRobotMapper.updateInspectionRobot(robot); robot.setStatus(String.valueOf(runtimeState.getMode()));
inspectionRobotMapper.updateRuntimeStatus(robot);
} catch (Exception e) { } catch (Exception e) {
// 记录日志但不抛出异常允许部分失败 if (e instanceof GlobalException) {
e.printStackTrace(); throw (GlobalException) e;
}
robot.setStatus("0");
inspectionRobotMapper.updateRuntimeStatus(robot);
throw new GlobalException("获取机器人运行状态失败:" +
(StrUtil.isBlank(e.getMessage()) ? e.getClass().getSimpleName() : e.getMessage()), e);
} }
return robot; return robot;
@ -354,11 +388,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
throw new GlobalException("机器人不存在"); throw new GlobalException("机器人不存在");
} }
EdgeCommonVO edgeCommonVO = new EdgeCommonVO(); AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(buildAgvRequest(robot));
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(edgeCommonVO);
return runtimeState.hasPose() ? runtimeState.getPose() : null; return runtimeState.hasPose() ? runtimeState.getPose() : null;
} }
@ -389,12 +419,8 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
.setTheta(theta != null ? theta : 0.0) .setTheta(theta != null ? theta : 0.0)
.build(); .build();
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
// 调用边缘端接口进行导航 // 调用边缘端接口进行导航
edgeAgvService.navigateToPose(edgeCommonVO, pose); edgeAgvService.navigateToPose(buildAgvRequest(robot), pose);
} }
/** /**
@ -414,12 +440,9 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
throw new GlobalException("机器人不存在"); throw new GlobalException("机器人不存在");
} }
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
AgvUtils.AgvMapDimension mapDimension = AgvUtils.AgvMapDimension.forNumber(dimension); AgvUtils.AgvMapDimension mapDimension = AgvUtils.AgvMapDimension.forNumber(dimension);
return edgeAgvService.startMapping(edgeCommonVO, mapDimension, mapName, realTime); return edgeAgvService.startMapping(
buildAgvRequest(robot), mapDimension, mapName, realTime);
} }
/** /**
@ -435,11 +458,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
throw new GlobalException("机器人不存在"); throw new GlobalException("机器人不存在");
} }
EdgeCommonVO edgeCommonVO = new EdgeCommonVO(); edgeAgvService.stopMapping(buildAgvRequest(robot));
edgeCommonVO.setRobotId(robot.getRobotId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
edgeAgvService.stopMapping(edgeCommonVO);
} }
} }

View File

@ -170,7 +170,6 @@ public class InspectionTaskInstanceServiceImpl extends ServiceImpl<InspectionTas
String insId = flowTaskRuntimeService.executeTask(TeTaskExecuteNormalVO.builder() String insId = flowTaskRuntimeService.executeTask(TeTaskExecuteNormalVO.builder()
.taskId(inspectionTask.getTaskConfigId()) .taskId(inspectionTask.getTaskConfigId())
.robotId(robot.getRobotId()) .robotId(robot.getRobotId())
.robotId(robot.getRobotId())
.moduleCode(FlowExecutionModuleCodes.INSPECTION) .moduleCode(FlowExecutionModuleCodes.INSPECTION)
.runParams(runParams) .runParams(runParams)
.build()); .build());

View File

@ -0,0 +1,64 @@
package com.cmvr.inspection.task;
import com.cmvr.inspection.domain.InspectionRobot;
import com.cmvr.inspection.mapper.InspectionRobotMapper;
import com.cmvr.inspection.service.IInspectionRobotService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Periodically persists AGV runtime snapshots for connected robots.
*/
@Component
@ConditionalOnProperty(prefix = "cmvr.inspection.robot-state", name = "enabled",
havingValue = "true", matchIfMissing = true)
public class RobotRuntimeStateRefreshTask
{
private static final Logger log = LoggerFactory.getLogger(RobotRuntimeStateRefreshTask.class);
private final InspectionRobotMapper inspectionRobotMapper;
private final IInspectionRobotService inspectionRobotService;
private final TaskExecutor taskExecutor;
private final Set<String> syncingRobotIds = ConcurrentHashMap.newKeySet();
public RobotRuntimeStateRefreshTask(InspectionRobotMapper inspectionRobotMapper,
IInspectionRobotService inspectionRobotService,
@Qualifier("threadPoolTaskExecutor") TaskExecutor taskExecutor)
{
this.inspectionRobotMapper = inspectionRobotMapper;
this.inspectionRobotService = inspectionRobotService;
this.taskExecutor = taskExecutor;
}
@Scheduled(initialDelayString = "${cmvr.inspection.robot-state.initial-delay-ms:3000}",
fixedDelayString = "${cmvr.inspection.robot-state.refresh-ms:5000}")
public void refreshConnectedRobots()
{
for (InspectionRobot robot : inspectionRobotMapper.selectRuntimeSyncCandidates()) {
if (robot.getId() == null || !syncingRobotIds.add(robot.getId())) {
continue;
}
taskExecutor.execute(() -> refreshRobot(robot));
}
}
private void refreshRobot(InspectionRobot robot)
{
try {
inspectionRobotService.syncRobotStatus(robot.getId());
} catch (Exception e) {
log.debug("Failed to refresh AGV runtime state, robotId={}, reason={}",
robot.getRobotId(), e.getMessage());
} finally {
syncingRobotIds.remove(robot.getId());
}
}
}

View File

@ -169,6 +169,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateQuicRuntimeState" parameterType="InspectionRobot"> <update id="updateQuicRuntimeState" parameterType="InspectionRobot">
update inspection_robot update inspection_robot
set ip_address = #{ipAddress}, port = #{port}, connect_status = #{connectStatus}, set ip_address = #{ipAddress}, port = #{port}, connect_status = #{connectStatus},
status = coalesce(#{status}, status),
quic_node_id = #{quicNodeId}, quic_session_id = #{quicSessionId}, quic_node_id = #{quicNodeId}, quic_session_id = #{quicSessionId},
quic_boot_id = #{quicBootId}, observed_ip = #{observedIp}, quic_boot_id = #{quicBootId}, observed_ip = #{observedIp},
software_version = #{softwareVersion}, last_heartbeat_time = #{lastHeartbeatTime}, software_version = #{softwareVersion}, last_heartbeat_time = #{lastHeartbeatTime},
@ -176,6 +177,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where id = #{id} where id = #{id}
</update> </update>
<update id="updateRuntimeStatus" parameterType="InspectionRobot">
update inspection_robot
set status = coalesce(#{status}, status),
battery_level = coalesce(#{batteryLevel}, battery_level),
current_position = coalesce(#{currentPosition}, current_position)
where id = #{id}
</update>
<select id="selectRuntimeSyncCandidates" resultMap="InspectionRobotResult">
select r.id, r.robot_id, r.robot_code, r.robot_name, r.robot_model, r.robot_type,
r.ip_address, r.port, r.current_map_id, r.status, r.battery_level,
r.current_position, r.connect_status, r.archived_status
from inspection_robot r
where r.connect_status = '1'
and coalesce(r.archived_status, '0') = '0'
and (select count(*)
from inspection_robot_device d
where d.robot_id = r.robot_id
and d.device_kind = 1
and d.enabled = '1'
and d.online_status = '1') = 1
</select>
<update id="archiveInspectionRobotByIds" parameterType="String"> <update id="archiveInspectionRobotByIds" parameterType="String">
update inspection_robot set archived_status = '1', connect_status = '0', update inspection_robot set archived_status = '1', connect_status = '0',
update_time = sysdate(), update_by = #{updateBy} update_time = sysdate(), update_by = #{updateBy}

View File

@ -11,6 +11,8 @@ import java.util.Map;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum ActionEnum { public enum ActionEnum {
SPEAKER_PLAYAUDIO("EDGE", "SPEAKER_PLAYAUDIO", "Play audio"),
// 平台控制类 // 平台控制类
STOPPED("NONE", "STOP", "强制终止"), STOPPED("NONE", "STOP", "强制终止"),
PAUSED("NONE", "PAUSED", "暂停"), PAUSED("NONE", "PAUSED", "暂停"),

View File

@ -1,5 +1,6 @@
package com.cmvr.test.flow.runtime.operator.edge; package com.cmvr.test.flow.runtime.operator.edge;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.exception.GlobalException; import com.cmvr.common.exception.GlobalException;
import com.cmvr.edge.client.service.EdgeSpeakerService; import com.cmvr.edge.client.service.EdgeSpeakerService;
@ -24,6 +25,17 @@ public class EdgeSpeakerOperateService implements EdgeOperateService {
@Override @Override
public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) { public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) {
JSONObject inputParams = message.getInputParams();
String deviceId = inputParams.getString("deviceId");
String audioPath = inputParams.getString("audioPath");
String robotId = message.getRobotId();
if (StrUtil.hasBlank(robotId, deviceId, audioPath)) {
throw new GlobalException("robotId, deviceId and audioPath are required");
}
String result = edgeSpeakerService.playAudio(robotId, deviceId, audioPath);
if (result != null) {
return TaskNodeExecuteResult.success(JSONObject.of("result", result));
}
// ActionEnum action = message.getAction(); // ActionEnum action = message.getAction();
// JSONObject inputParams = message.getInputParams(); // JSONObject inputParams = message.getInputParams();
// String deviceId = inputParams.getString("deviceId"); // String deviceId = inputParams.getString("deviceId");
@ -64,6 +76,6 @@ public class EdgeSpeakerOperateService implements EdgeOperateService {
// throw new GlobalException("不支持的扬声器操作类型: " + action); // throw new GlobalException("不支持的扬声器操作类型: " + action);
// } // }
// return TaskNodeExecuteResult.success(); // return TaskNodeExecuteResult.success();
return null; return TaskNodeExecuteResult.success();
} }
} }

View File

@ -17,16 +17,25 @@ import com.cmvr.edge.client.service.EdgeAgvService;
import com.cmvr.device.service.InspectionAlertListenService; import com.cmvr.device.service.InspectionAlertListenService;
import com.cmvr.llm.service.LLMAiAgentPlatformService; import com.cmvr.llm.service.LLMAiAgentPlatformService;
import com.cmvr.test.enums.ActionEnum; import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.flow.runtime.dispatcher.FlowCodeNodeHandler;
import com.cmvr.test.flow.runtime.dispatcher.FlowHttpNodeHandler;
import com.cmvr.test.flow.runtime.dispatcher.FlowSleepNodeHandler;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage; import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import com.cmvr.test.flow.runtime.operator.ae.AEOperateService;
import com.cmvr.test.flow.runtime.operator.edge.EdgeManualInspectionOperateService; import com.cmvr.test.flow.runtime.operator.edge.EdgeManualInspectionOperateService;
import com.cmvr.test.flow.runtime.operator.edge.EdgeDeviceCommandOperateService; import com.cmvr.test.flow.runtime.operator.edge.EdgeDeviceCommandOperateService;
import com.cmvr.test.flow.runtime.operator.edge.EdgeArmOperateService; import com.cmvr.test.flow.runtime.operator.edge.EdgeArmOperateService;
import com.cmvr.test.flow.runtime.operator.edge.EdgeOperateService;
import com.cmvr.test.flow.runtime.operator.llm.InspectionMeterRecognizeOperateService; import com.cmvr.test.flow.runtime.operator.llm.InspectionMeterRecognizeOperateService;
import com.cmvr.test.flow.runtime.operator.llm.LLMOperateService;
import com.cmvr.test.model.vo.FlowActionRequestVO; import com.cmvr.test.model.vo.FlowActionRequestVO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -43,6 +52,12 @@ public class FlowActionExecutorService {
private final EdgeManualInspectionOperateService edgeManualInspectionOperateService; private final EdgeManualInspectionOperateService edgeManualInspectionOperateService;
private final EdgeDeviceCommandOperateService edgeDeviceCommandOperateService; private final EdgeDeviceCommandOperateService edgeDeviceCommandOperateService;
private final InspectionMeterRecognizeOperateService inspectionMeterRecognizeOperateService; private final InspectionMeterRecognizeOperateService inspectionMeterRecognizeOperateService;
private final List<EdgeOperateService> edgeOperateServices;
private final List<LLMOperateService> llmOperateServices;
private final List<AEOperateService> aeOperateServices;
private final FlowSleepNodeHandler flowSleepNodeHandler;
private final FlowHttpNodeHandler flowHttpNodeHandler;
private final FlowCodeNodeHandler flowCodeNodeHandler;
public String actionExecute(FlowActionRequestVO req) { public String actionExecute(FlowActionRequestVO req) {
@ -54,16 +69,77 @@ public class FlowActionExecutorService {
switch (action.getOperate()) { switch (action.getOperate()) {
case "EDGE": case "EDGE":
return executeEdgeAction(action, req); return executeEdgeOperateAction(action, req);
case "LLM": case "LLM":
return executeLLMAction(action, req); return executeLLMOperateAction(action, req);
case "AE":
return executeAEOperateAction(action, req);
case "NONE":
return executeStandaloneAction(action, req);
default: default:
throw new GlobalException("仅设备和大模型 Action 可调用!"); throw new GlobalException("仅设备和大模型 Action 可调用!");
} }
} }
private String executeEdgeOperateAction(ActionEnum action, FlowActionRequestVO req) {
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
for (EdgeOperateService service : edgeOperateServices) {
if (service.supports(action)) {
return resultToString(service.execute(message));
}
}
throw new GlobalException("Unsupported EDGE action: " + action.getAction());
}
private String executeLLMOperateAction(ActionEnum action, FlowActionRequestVO req) {
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
for (LLMOperateService service : llmOperateServices) {
if (service.supports(action)) {
return resultToString(service.execute(message));
}
}
throw new GlobalException("Unsupported LLM action: " + action.getAction());
}
private String executeAEOperateAction(ActionEnum action, FlowActionRequestVO req) {
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
for (AEOperateService service : aeOperateServices) {
if (service.supports(action)) {
return resultToString(service.execute(message));
}
}
throw new GlobalException("Unsupported AE action: " + action.getAction());
}
private String executeStandaloneAction(ActionEnum action, FlowActionRequestVO req) {
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
switch (action) {
case SLEEP:
return resultToString(flowSleepNodeHandler.handle(message));
case HTTP:
return resultToString(flowHttpNodeHandler.handle(message));
case CODE:
return resultToString(flowCodeNodeHandler.handle(message));
default:
throw new GlobalException("This action requires flow context: " + action.getAction());
}
}
private String resultToString(TaskNodeExecuteResult result) {
if (result == null) {
throw new GlobalException("Single-node execution returned no result");
}
if (!result.isSuccess()) {
throw new GlobalException(StrUtil.isBlank(result.getErrorMsg())
? "Single-node execution failed" : result.getErrorMsg());
}
return result.getOutputParams() == null ? "{}" : result.getOutputParams().toJSONString();
}
private String executeEdgeAction(ActionEnum action, FlowActionRequestVO req) { private String executeEdgeAction(ActionEnum action, FlowActionRequestVO req) {
JSONObject payload = req.getPayload() == null ? new JSONObject() : req.getPayload(); JSONObject payload = req.getPayload() == null ? new JSONObject() : req.getPayload();
String robotId = payload.getString("robotId"); String robotId = payload.getString("robotId");
@ -253,9 +329,15 @@ public class FlowActionExecutorService {
} }
private TaskNodeExecuteMessage buildSingleNodeMessage(ActionEnum action, JSONObject payload) { private TaskNodeExecuteMessage buildSingleNodeMessage(ActionEnum action, JSONObject payload) {
JSONObject inputParams = payload == null ? new JSONObject() : payload;
TaskNodeExecuteMessage message = new TaskNodeExecuteMessage(); TaskNodeExecuteMessage message = new TaskNodeExecuteMessage();
message.setAction(action); message.setAction(action);
message.setInputParams(payload == null ? new JSONObject() : payload); message.setNodeId("single-node");
message.setRobotId(inputParams.getString("robotId"));
message.setIterations(java.util.Arrays.asList(1, 1));
message.setLoopNum(1);
message.setLoopArray(inputParams.get("array"));
message.setInputParams(inputParams);
return message; return message;
} }
} }

View File

@ -19,7 +19,7 @@ CREATE TABLE `inspection_robot` (
`ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址', `ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址',
`port` int(11) DEFAULT NULL COMMENT '端口号', `port` int(11) DEFAULT NULL COMMENT '端口号',
`current_map_id` varchar(64) DEFAULT NULL COMMENT '当前地图ID', `current_map_id` varchar(64) DEFAULT NULL COMMENT '当前地图ID',
`status` char(1) DEFAULT '0' COMMENT '状态(0在线 1离线 2充电中 3巡检中)', `status` char(1) DEFAULT '0' COMMENT 'AGV运行模式(0未知 1断开 2空闲 3手动 4自动 5充电 6暂停 7停止 8故障 9急停)',
`battery_level` int(3) DEFAULT NULL COMMENT '电量百分比', `battery_level` int(3) DEFAULT NULL COMMENT '电量百分比',
`current_position` varchar(200) DEFAULT NULL COMMENT '当前位置', `current_position` varchar(200) DEFAULT NULL COMMENT '当前位置',
`connect_status` char(1) NOT NULL DEFAULT '0' COMMENT '连接状态(0离线 1在线)', `connect_status` char(1) NOT NULL DEFAULT '0' COMMENT '连接状态(0离线 1在线)',