Commit 260dd38b authored by wanghao's avatar wanghao

1 机械臂功能开发

parent cd00704b
......@@ -104,9 +104,10 @@ netty:
max-frame-length: 65535 # 最大帧长度
heartbeat-timeout: 10 # 心跳超时时间(秒)
# 机械臂UDP配置
robot:
arm:
udp:
ip: 192.168.2.16
ip: 192.168.2.14
port: 6000
......@@ -22,7 +22,11 @@
<groupId>com.zehong</groupId>
<artifactId>zhmes-agecal-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.zehong.framework.config;
package com.zehong.system.netty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author lenovo
* @date 2025/7/31
* @description Netty配置类
*/
@Component
@ConfigurationProperties(prefix = "netty")
@Configuration
public class NettyConfig {
/**
* 服务端口
*/
private int port = 6001;
@Value("${netty.port}")
private int port;
/**
* 主线程数
*/
@Value("${netty.boss-group-thread-count}") // 注意配置文件中的横杠命名对应类中的驼峰命名
private int bossGroupThreadCount = 1;
/**
* 工作线程数
* 工作线程数(从配置文件读取,默认值为CPU核心数*2)
*/
private int workerGroupThreadCount = Runtime.getRuntime().availableProcessors() * 2;
@Value("${netty.worker-group-thread-count}") // 新增配置映射
private int workerGroupThreadCount;
/**
* 最大帧长度
*/
private int maxFrameLength = 65535;
@Value("${netty.max-frame-length}") // 配置文件中的横杠对应类中的驼峰
private int maxFrameLength;
/**
* 心跳检测超时时间(秒)
*/
private int heartbeatTimeout = 10;
@Value("${netty.heartbeat-timeout}") // 配置文件中的横杠对应类中的驼峰
private int heartbeatTimeout;
// getter和setter方法
public int getPort() {
......
package com.zehong.framework.netty;
package com.zehong.system.netty;
import com.zehong.framework.config.NettyConfig;
import com.zehong.framework.netty.handler.NettyUdpServerHandler;
import com.zehong.system.netty.handler.NettyUdpServerHandler;
import org.springframework.stereotype.Component;
import io.netty.bootstrap.Bootstrap;
......@@ -19,9 +18,9 @@ import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @author lenovo
......@@ -32,10 +31,10 @@ import java.util.concurrent.TimeUnit;
public class NettyUdpServer {
private static final Logger log = LoggerFactory.getLogger(NettyUdpServer.class);
@Autowired
@Resource
private NettyConfig nettyConfig;
@Autowired
@Resource
private NettyUdpServerHandler nettyUdpServerHandler;
private EventLoopGroup group;
......
package com.zehong.framework.netty.listener;
package com.zehong.system.netty.listener;
import com.zehong.framework.netty.NettyUdpServer;
import com.zehong.system.netty.NettyUdpServer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
......
......@@ -64,6 +64,9 @@ public interface IRobotArmCommandService
public void processIdleState();
void completeCommand(Long commandId);
void markCommandTimeout(Long commandId);
public void processPendingCommands();
public void completeCommand(String trayCode,String storeyCode);
......
package com.zehong.system.service.impl;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.List;
import com.zehong.common.utils.DateUtils;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.netty.handler.NettyUdpServerHandler;
import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import com.zehong.system.udp.UdpCommandSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.zehong.system.mapper.RobotArmCommandMapper;
import com.zehong.system.domain.RobotArmCommand;
......@@ -28,6 +34,9 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
{
private static final Logger log = LoggerFactory.getLogger(RobotArmCommandServiceImpl.class);
@Resource
private NettyUdpServerHandler nettyUdpServerHandler;
@Resource
private RobotArmWebSocketHandler robotArmWebSocketHandler;
......@@ -39,47 +48,93 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
@Resource
private UdpCommandSender udpCommandSender;
@Value("${robot.arm.udp.ip}")
private String robotArmAddress;
@Value("${robot.arm.udp.port}")
private int robotArmPort;
private SocketAddress getRobotAddress() {
try {
return new InetSocketAddress(
InetAddress.getByName(robotArmAddress), robotArmPort);
} catch (UnknownHostException e) {
throw new RuntimeException("无效的机械臂地址: " + robotArmAddress);
}
}
@Override
@Transactional
public void processIdleState() {
// 1. 更新执行中状态为完成状态
robotArmCommandMapper.updateExecutingToCompleted();
// 2. 优先处理上料指令
List<RobotArmCommand> loadingCommands = robotArmCommandMapper.selectPendingLoadingCommands();
public void processPendingCommands() {
// 1. 处理待执行的上料指令
List<RobotArmCommand> loadingCommands =
robotArmCommandMapper.selectPendingLoadingCommands();
if (!loadingCommands.isEmpty()) {
RobotArmCommand command = loadingCommands.get(0);
sendLoadingCommand(command);
sendCommand(loadingCommands.get(0), "LOAD");
return;
}
// 3. 处理下料指令
List<RobotArmCommand> unloadingCommands = robotArmCommandMapper.selectPendingUnloadingCommands();
// 2. 处理待执行的下料指令
List<RobotArmCommand> unloadingCommands =
robotArmCommandMapper.selectPendingUnloadingCommands();
if (!unloadingCommands.isEmpty()) {
RobotArmCommand command = unloadingCommands.get(0);
sendUnloadingCommand(command);
sendCommand(unloadingCommands.get(0), "UNLOAD");
}
}
private void sendCommand(RobotArmCommand command, String commandType) {
// 更新状态为执行中
command.setStatus("1");
command.setStartExecutionTime(new Date());
robotArmCommandMapper.updateRobotArmCommand(command);
// 通过WebSocket广播更新
robotArmWebSocketHandler.broadcastCommandUpdate();
// 发送UDP指令
SocketAddress address = getRobotAddress();
udpCommandSender.sendCommandWithId(
address,
command.getRobotArmCommandId(),
commandType,
command.getTrayCode(),
command.getStoreyCode()
);
// 注册指令跟踪
nettyUdpServerHandler.registerCommandExecution(address, command.getRobotArmCommandId());
}
@Override
@Transactional
public void processPendingCommands() {
// 1. 只处理待执行指令(状态为0)
List<RobotArmCommand> loadingCommands = robotArmCommandMapper.selectPendingLoadingCommands();
if (!loadingCommands.isEmpty()) {
RobotArmCommand command = loadingCommands.get(0);
sendLoadingCommand(command);
return;
public void completeCommand(Long commandId) {
RobotArmCommand command = robotArmCommandMapper.selectRobotArmCommandById(commandId);
if (command != null && "1".equals(command.getStatus())) {
command.setStatus("3");
command.setEndExecutionTime(new Date());
robotArmCommandMapper.updateRobotArmCommand(command);
// 通过WebSocket广播更新
robotArmWebSocketHandler.broadcastCommandUpdate();
}
}
// 2. 处理待执行的下料指令
List<RobotArmCommand> unloadingCommands = robotArmCommandMapper.selectPendingUnloadingCommands();
if (!unloadingCommands.isEmpty()) {
RobotArmCommand command = unloadingCommands.get(0);
sendUnloadingCommand(command);
@Override
@Transactional
public void markCommandTimeout(Long commandId) {
RobotArmCommand command = robotArmCommandMapper.selectRobotArmCommandById(commandId);
if (command != null && "1".equals(command.getStatus())) {
command.setStatus("3"); // 标记为未上电
robotArmCommandMapper.updateRobotArmCommand(command);
// 通过WebSocket广播更新
robotArmWebSocketHandler.broadcastCommandUpdate();
robotArmWebSocketHandler.broadcastStatus("idle");
}
}
@Override
@Transactional
public void completeCommand(String trayCode, String storeyCode) {
......@@ -245,6 +300,28 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
return i;
}
@Override
@Transactional
public void processIdleState() {
// 1. 更新执行中状态为完成状态
robotArmCommandMapper.updateExecutingToCompleted();
// 2. 优先处理上料指令
List<RobotArmCommand> loadingCommands = robotArmCommandMapper.selectPendingLoadingCommands();
if (!loadingCommands.isEmpty()) {
RobotArmCommand command = loadingCommands.get(0);
sendLoadingCommand(command);
return;
}
// 3. 处理下料指令
List<RobotArmCommand> unloadingCommands = robotArmCommandMapper.selectPendingUnloadingCommands();
if (!unloadingCommands.isEmpty()) {
RobotArmCommand command = unloadingCommands.get(0);
sendUnloadingCommand(command);
}
}
private void notifyCommandsUpdate() {
robotArmWebSocketHandler.broadcastCommandUpdate();
}
......
package com.zehong.system.udp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author lenovo
* @date 2025/8/5
* @description TODO
*/
public class RobotArmMessageParser {
private static final Pattern STATUS_PATTERN = Pattern.compile(
"PPP (\\w+) (\\S+)(?: (\\d+),(\\d+),(\\d+),(\\d+),)?"
);
public static RobotArmStatus parseMessage(String message) {
Matcher matcher = STATUS_PATTERN.matcher(message);
if (matcher.find()) {
String statusCode = matcher.group(1);
String statusText = matcher.group(2);
String x = matcher.group(3);
String y = matcher.group(4);
String z = matcher.group(5);
String r = matcher.group(6);
return new RobotArmStatus(statusCode, statusText,
parseCoord(x), parseCoord(y),
parseCoord(z), parseCoord(r));
}
return null;
}
private static Integer parseCoord(String coord) {
if (coord == null || coord.isEmpty()) return null;
try {
return Integer.parseInt(coord);
} catch (NumberFormatException e) {
return null;
}
}
public static class RobotArmStatus {
private final String code;
private final String text;
private final Integer x;
private final Integer y;
private final Integer z;
private final Integer r;
public RobotArmStatus(String code, String text,
Integer x, Integer y, Integer z, Integer r) {
this.code = code;
this.text = text;
this.x = x;
this.y = y;
this.z = z;
this.r = r;
}
public boolean isBusy() {
return "BUSY".equals(code);
}
public boolean isOk() {
return "OK".equals(code);
}
public boolean isComplete() {
return isOk() && "完成".equals(text);
}
public boolean isFullyIdle() {
return isComplete() && x == 0 && y == 0 && z == 0 && r == 0;
}
// Getters
public String getCode() { return code; }
public String getText() { return text; }
public Integer getX() { return x; }
public Integer getY() { return y; }
public Integer getZ() { return z; }
public Integer getR() { return r; }
}
}
......@@ -11,7 +11,9 @@ import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author lenovo
......@@ -31,6 +33,7 @@ public class UdpCommandSender {
private DatagramSocket socket;
private InetAddress address;
private final ReentrantLock lock = new ReentrantLock();
@PostConstruct
public void init() {
try {
......@@ -42,6 +45,38 @@ public class UdpCommandSender {
}
}
/**
* 发送指令到指定地址
*/
public void sendCommand(SocketAddress address, String message) {
if (socket == null || address == null) {
log.error("UDP命令发送器未初始化或地址无效");
return;
}
lock.lock();
try {
byte[] buffer = message.getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address);
socket.send(packet);
log.info("已发送UDP指令到 {}: {}", address, message);
} catch (IOException e) {
log.error("发送UDP指令失败: {}", message, e);
} finally {
lock.unlock();
}
}
/**
* 发送带指令ID的命令
*/
public void sendCommandWithId(SocketAddress address, Long commandId,
String commandType, String trayCode, String storeyCode) {
String message = String.format("CMD_ID:%d#%s#%s#%s",
commandId, commandType, trayCode, storeyCode);
sendCommand(address, message);
}
public void sendCommand(String message) {
if (socket == null || address == null) {
log.error("UDP命令发送器未初始化,无法发送消息");
......
......@@ -152,7 +152,7 @@ export default {
name: 'RoboticArm',
data() {
return {
status: 'idle', // idle, running, error
status: 'unknown', // idle, running, error
showAddDialog: false,
trayCode: '',
loadingCommands: [
......@@ -195,14 +195,16 @@ export default {
return {
'idle': this.status === 'idle',
'running': this.status === 'running',
'error': this.status === 'error'
'error': this.status === 'error',
'unknown': this.status === 'unknown'
};
},
statusText() {
return {
'idle': '空闲中',
'running': '运行中',
'error': '故障'
'error': '故障',
'unknown': '未知'
}[this.status];
}
},
......@@ -233,13 +235,14 @@ export default {
this.websocket.onopen = () => {
console.log('机械臂指令WebSocket连接成功');
this.status = 'running';
this.status = 'unknown';
this.sendWebSocketMessage({ type: 'request', commands: ['loading', 'unloading'] });
};
this.websocket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
console.log('收到WebSocket消息:', message);
if (message.type === 'loading') {
this.loadingCommands = message.data.map(cmd => ({
robotArmCommandId: cmd.robotArmCommandId,
......@@ -488,6 +491,17 @@ export default {
animation: blink 1s infinite;
}
/* 添加未知状态样式 */
.status-light.unknown {
background-color: #a0a0a0;
color: #a0a0a0;
animation: blink-gray 1s infinite;
}
@keyframes blink-gray {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.status-text {
font-size: 14px;
font-weight: bold;
......@@ -992,7 +1006,6 @@ export default {
min-width: auto;
}
}
/* 指令状态样式 */
.command-item.status-pending {
background: rgba(0, 40, 80, 0.3);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment