Commit cd00704b authored by wanghao's avatar wanghao

1 机械臂功能开发

parent fe607a03
package com.zehong.web.controller.equipment;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.zehong.common.core.controller.BaseController;
import com.zehong.common.core.domain.AjaxResult;
import com.zehong.system.domain.RobotArmCommand;
import com.zehong.system.service.IRobotArmCommandService;
import com.zehong.common.utils.poi.ExcelUtil;
import com.zehong.common.core.page.TableDataInfo;
/**
* 机械臂指令Controller
*
* @author zehong
* @date 2025-08-04
*/
@RestController
@RequestMapping("/robotArm/command")
public class RobotArmCommandController extends BaseController
{
@Autowired
private IRobotArmCommandService robotArmCommandService;
/**
* 查询机械臂指令列表
*/
@GetMapping("/list")
public TableDataInfo list(RobotArmCommand robotArmCommand)
{
startPage();
List<RobotArmCommand> list = robotArmCommandService.selectRobotArmCommandList(robotArmCommand);
return getDataTable(list);
}
/**
* 导出机械臂指令列表
*/
@GetMapping("/export")
public AjaxResult export(RobotArmCommand robotArmCommand)
{
List<RobotArmCommand> list = robotArmCommandService.selectRobotArmCommandList(robotArmCommand);
ExcelUtil<RobotArmCommand> util = new ExcelUtil<RobotArmCommand>(RobotArmCommand.class);
return util.exportExcel(list, "机械臂指令数据");
}
/**
* 获取机械臂指令详细信息
*/
@GetMapping(value = "/{robotArmCommandId}")
public AjaxResult getInfo(@PathVariable("robotArmCommandId") Long robotArmCommandId)
{
return AjaxResult.success(robotArmCommandService.selectRobotArmCommandById(robotArmCommandId));
}
/**
* 新增机械臂指令
*/
@PostMapping
public AjaxResult add(@RequestBody RobotArmCommand robotArmCommand)
{
return toAjax(robotArmCommandService.insertRobotArmCommand(robotArmCommand));
}
@PostMapping("/powerOn")
public AjaxResult powerOn(@RequestBody Map<String, Object> params) {
Long commandId = Long.parseLong(params.get("commandId").toString());
robotArmCommandService.powerOnCommand(commandId);
return AjaxResult.success("上电操作成功");
}
/**
* 修改机械臂指令
*/
@PutMapping
public AjaxResult edit(@RequestBody RobotArmCommand robotArmCommand)
{
return toAjax(robotArmCommandService.updateRobotArmCommand(robotArmCommand));
}
/**
* 删除机械臂指令
*/
@DeleteMapping("/{robotArmCommandIds}")
public AjaxResult remove(@PathVariable Long[] robotArmCommandIds)
{
return toAjax(robotArmCommandService.deleteRobotArmCommandByIds(robotArmCommandIds));
}
}
# 数据源配置
spring:
datasource:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
......@@ -56,7 +56,7 @@ spring:
config:
multi-statement-allow: true
# redis 配置
redis:
redis:
# 地址
host: localhost
# 端口,默认为6379
......@@ -102,4 +102,11 @@ netty:
boss-group-thread-count: 1 # 主线程数
worker-group-thread-count: 8 # 工作线程数
max-frame-length: 65535 # 最大帧长度
heartbeat-timeout: 60 # 心跳超时时间(秒)
\ No newline at end of file
heartbeat-timeout: 10 # 心跳超时时间(秒)
# 机械臂UDP配置
robot:
arm:
udp:
ip: 192.168.2.16
port: 6000
......@@ -102,4 +102,12 @@ netty:
boss-group-thread-count: 1 # 主线程数
worker-group-thread-count: 8 # 工作线程数
max-frame-length: 65535 # 最大帧长度
heartbeat-timeout: 60 # 心跳超时时间(秒)
\ No newline at end of file
heartbeat-timeout: 10 # 心跳超时时间(秒)
# 机械臂UDP配置
robot:
arm:
udp:
ip: 192.168.2.16
port: 6000
\ No newline at end of file
......@@ -99,4 +99,12 @@ netty:
boss-group-thread-count: 1 # 主线程数
worker-group-thread-count: 8 # 工作线程数
max-frame-length: 65535 # 最大帧长度
heartbeat-timeout: 60 # 心跳超时时间(秒)
\ No newline at end of file
heartbeat-timeout: 10 # 心跳超时时间(秒)
# 机械臂UDP配置
robot:
arm:
udp:
ip: 192.168.2.16
port: 6000
\ No newline at end of file
......@@ -130,6 +130,10 @@
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -35,7 +35,7 @@ public class NettyConfig {
/**
* 心跳检测超时时间(秒)
*/
private int heartbeatTimeout = 60;
private int heartbeatTimeout = 10;
// getter和setter方法
public int getPort() {
......
......@@ -105,6 +105,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
"/**/*.css",
"/**/*.js"
).permitAll()
// 对于 websocket 匿名访问
.antMatchers("/ws-robot-arm").permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
......
package com.zehong.framework.netty.handler;
import com.zehong.system.service.IRobotArmCommandService;
import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
......@@ -11,6 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.concurrent.locks.ReentrantLock;
......@@ -36,6 +39,12 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
// 线程安全锁,确保文件写入安全
private final ReentrantLock fileLock = new ReentrantLock();
@Resource
private RobotArmWebSocketHandler robotArmWebSocketHandler; // 注入WebSocket处理器
@Resource
private IRobotArmCommandService robotArmCommandService;
/**
* 接收UDP消息
*/
......@@ -68,18 +77,36 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
}
// 保存消息到文件
saveMessageToFile(packet.sender().toString(), correctMessage);
//saveMessageToFile(packet.sender().toString(), correctMessage);
// 处理消息逻辑
String response = "服务器已收到UDP消息:" + correctMessage;
String responseSave = "服务器已收到UDP消息:" + correctMessage;
// 处理机械臂完成消息
if (correctMessage.startsWith("COMPLETE,")) {
String[] parts = correctMessage.split(",");
if (parts.length >= 3) {
String trayCode = parts[1];
String storeyCode = parts[2];
// 更新指令状态为已完成
robotArmCommandService.completeCommand(trayCode, storeyCode);
// 发送成功响应
String response = "CMD_COMPLETE_ACK";
byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
ctx.writeAndFlush(new DatagramPacket(
io.netty.buffer.Unpooled.copiedBuffer(responseBytes),
packet.sender()));
}
}
// 回复客户端,明确使用UTF-8编码
byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
ctx.writeAndFlush(new DatagramPacket(
io.netty.buffer.Unpooled.copiedBuffer(responseBytes),
packet.sender()));
// 当收到消息时,更新状态为运行中
sendStatusToWebSocket("running");
} catch (Exception e) {
log.error("处理UDP消息异常", e);
// 出现异常时发送故障状态
sendStatusToWebSocket("error");
}
}
......@@ -164,9 +191,25 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
if (event.state() == IdleState.ALL_IDLE) {
log.info("UDP服务器超过规定时间未收到数据");
// UDP无连接,一般不关闭通道
// 通过WebSocket发送空闲状态给前端
sendStatusToWebSocket("idle");
// 处理空闲状态
robotArmCommandService.processPendingCommands();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
/**
* 发送状态到WebSocket
*/
private void sendStatusToWebSocket(String status) {
if (robotArmWebSocketHandler != null) {
robotArmWebSocketHandler.broadcastStatus(status);
} else {
log.warn("WebSocket处理器未初始化,无法发送状态");
}
}
}
package com.zehong.system.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.zehong.common.annotation.Excel;
import com.zehong.common.core.domain.BaseEntity;
/**
* 机械臂指令对象 t_robot_arm_command
*
* @author zehong
* @date 2025-08-04
*/
public class RobotArmCommand extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** id */
private Long robotArmCommandId;
/** 托盘编号 */
@Excel(name = "托盘编号")
private String trayCode;
/** 绑定层编号 */
@Excel(name = "绑定层编号")
private String storeyCode;
/** 类型:0-待上料;1-待下料 */
@Excel(name = "类型:0-待上料;1-待下料")
private String type;
/** 状态:0-待执行;1-执行中;2-执行结束(上料就是绑定托盘,下料就是解绑托盘) */
@Excel(name = "状态:0-待执行;1-执行中;2-未上电,3-执行结束(上料就是绑定托盘,下料就是解绑托盘)")
private String status;
/** 指令开始执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "指令开始执行时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date startExecutionTime;
/** 指令结束执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "指令结束执行时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date endExecutionTime;
/** 指令 */
private String command;
public void setRobotArmCommandId(Long robotArmCommandId)
{
this.robotArmCommandId = robotArmCommandId;
}
public Long getRobotArmCommandId()
{
return robotArmCommandId;
}
public void setTrayCode(String trayCode)
{
this.trayCode = trayCode;
}
public String getTrayCode()
{
return trayCode;
}
public void setStoreyCode(String storeyCode)
{
this.storeyCode = storeyCode;
}
public String getStoreyCode()
{
return storeyCode;
}
public void setType(String type)
{
this.type = type;
}
public String getType()
{
return type;
}
public void setStatus(String status)
{
this.status = status;
}
public String getStatus()
{
return status;
}
public void setStartExecutionTime(Date startExecutionTime)
{
this.startExecutionTime = startExecutionTime;
}
public Date getStartExecutionTime()
{
return startExecutionTime;
}
public void setEndExecutionTime(Date endExecutionTime)
{
this.endExecutionTime = endExecutionTime;
}
public Date getEndExecutionTime()
{
return endExecutionTime;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("robotArmCommandId", getRobotArmCommandId())
.append("trayCode", getTrayCode())
.append("storeyCode", getStoreyCode())
.append("type", getType())
.append("status", getStatus())
.append("startExecutionTime", getStartExecutionTime())
.append("endExecutionTime", getEndExecutionTime())
.append("createTime", getCreateTime())
.toString();
}
}
package com.zehong.system.mapper;
import java.util.List;
import com.zehong.system.domain.RobotArmCommand;
/**
* 机械臂指令Mapper接口
*
* @author zehong
* @date 2025-08-04
*/
public interface RobotArmCommandMapper
{
/**
* 查询机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 机械臂指令
*/
public RobotArmCommand selectRobotArmCommandById(Long robotArmCommandId);
public RobotArmCommand findExecutingCommand(String trayCode, String storeyCode);
/**
* 查询机械臂指令列表
*
* @param robotArmCommand 机械臂指令
* @return 机械臂指令集合
*/
public List<RobotArmCommand> selectRobotArmCommandList(RobotArmCommand robotArmCommand);
public List<RobotArmCommand> findByType(String type);
/**
* 新增机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public int insertRobotArmCommand(RobotArmCommand robotArmCommand);
/**
* 修改机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public int updateRobotArmCommand(RobotArmCommand robotArmCommand);
/**
* 更新执行中状态为完成状态
*/
int updateExecutingToCompleted();
/**
* 获取待执行的上料指令
*/
List<RobotArmCommand> selectPendingLoadingCommands();
/**
* 获取待执行的下料指令
*/
List<RobotArmCommand> selectPendingUnloadingCommands();
/**
* 删除机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 结果
*/
public int deleteRobotArmCommandById(Long robotArmCommandId);
/**
* 批量删除机械臂指令
*
* @param robotArmCommandIds 需要删除的数据ID
* @return 结果
*/
public int deleteRobotArmCommandByIds(Long[] robotArmCommandIds);
}
......@@ -27,6 +27,8 @@ public interface TStoreyInfoMapper
*/
public TStoreyInfo selectTStoreyInfoByCode(String fStoreyCode);
// 新增方法:查询离机械臂最近的空闲层
public TStoreyInfo selectNearestFreeStorey();
/**
* 查询老化层信息列表
*
......
package com.zehong.system.service;
import java.util.List;
import com.zehong.system.domain.RobotArmCommand;
/**
* 机械臂指令Service接口
*
* @author zehong
* @date 2025-08-04
*/
public interface IRobotArmCommandService
{
/**
* 查询机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 机械臂指令
*/
public RobotArmCommand selectRobotArmCommandById(Long robotArmCommandId);
/**
* 查询机械臂指令列表
*
* @param robotArmCommand 机械臂指令
* @return 机械臂指令集合
*/
public List<RobotArmCommand> selectRobotArmCommandList(RobotArmCommand robotArmCommand);
public List<RobotArmCommand> findByType(String type);
/**
* 新增机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public int insertRobotArmCommand(RobotArmCommand robotArmCommand);
public void powerOnCommand(Long commandId);
/**
* 修改机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public int updateRobotArmCommand(RobotArmCommand robotArmCommand);
/**
* 批量删除机械臂指令
*
* @param robotArmCommandIds 需要删除的机械臂指令ID
* @return 结果
*/
public int deleteRobotArmCommandByIds(Long[] robotArmCommandIds);
/**
* 删除机械臂指令信息
*
* @param robotArmCommandId 机械臂指令ID
* @return 结果
*/
public int deleteRobotArmCommandById(Long robotArmCommandId);
public void processIdleState();
public void processPendingCommands();
public void completeCommand(String trayCode,String storeyCode);
}
package com.zehong.system.service.impl;
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.service.websocket.RobotArmWebSocketHandler;
import com.zehong.system.udp.UdpCommandSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.zehong.system.mapper.RobotArmCommandMapper;
import com.zehong.system.domain.RobotArmCommand;
import com.zehong.system.service.IRobotArmCommandService;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 机械臂指令Service业务层处理
*
* @author zehong
* @date 2025-08-04
*/
@Service
public class RobotArmCommandServiceImpl implements IRobotArmCommandService
{
private static final Logger log = LoggerFactory.getLogger(RobotArmCommandServiceImpl.class);
@Resource
private RobotArmWebSocketHandler robotArmWebSocketHandler;
@Resource
private RobotArmCommandMapper robotArmCommandMapper;
@Resource
private TStoreyInfoMapper storeyInfoMapper;
@Resource
private UdpCommandSender udpCommandSender;
@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);
}
}
@Override
@Transactional
public void processPendingCommands() {
// 1. 只处理待执行指令(状态为0)
List<RobotArmCommand> loadingCommands = robotArmCommandMapper.selectPendingLoadingCommands();
if (!loadingCommands.isEmpty()) {
RobotArmCommand command = loadingCommands.get(0);
sendLoadingCommand(command);
return;
}
// 2. 处理待执行的下料指令
List<RobotArmCommand> unloadingCommands = robotArmCommandMapper.selectPendingUnloadingCommands();
if (!unloadingCommands.isEmpty()) {
RobotArmCommand command = unloadingCommands.get(0);
sendUnloadingCommand(command);
}
}
@Override
@Transactional
public void completeCommand(String trayCode, String storeyCode) {
// 1. 查找对应的执行中指令
RobotArmCommand command = robotArmCommandMapper.findExecutingCommand(trayCode, storeyCode);
if (command == null) {
log.warn("未找到对应的执行中指令: {} @ {}", trayCode, storeyCode);
return;
}
// 2. 更新状态为已完成
command.setStatus("3");
command.setEndExecutionTime(new Date());
robotArmCommandMapper.updateRobotArmCommand(command);
log.info("指令完成: {} @ {}", trayCode, storeyCode);
// 3. 广播指令更新
robotArmWebSocketHandler.broadcastCommandUpdate();
}
private void sendLoadingCommand(RobotArmCommand command) {
// 更新状态为执行中
command.setStatus("1");
command.setStartExecutionTime(new Date());
robotArmCommandMapper.updateRobotArmCommand(command);
notifyCommandsUpdate();
// 发送UDP指令
String udpMessage = String.format("LOAD,%s,%s", command.getTrayCode(), command.getStoreyCode());
udpCommandSender.sendCommand(udpMessage);
}
private void sendUnloadingCommand(RobotArmCommand command) {
// 更新状态为执行中
command.setStatus("1");
command.setStartExecutionTime(new Date());
robotArmCommandMapper.updateRobotArmCommand(command);
notifyCommandsUpdate();
// 发送UDP指令
String udpMessage = String.format("UNLOAD,%s,%s", command.getTrayCode(), command.getStoreyCode());
udpCommandSender.sendCommand(udpMessage);
}
/**
* 查询机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 机械臂指令
*/
@Override
public RobotArmCommand selectRobotArmCommandById(Long robotArmCommandId)
{
return robotArmCommandMapper.selectRobotArmCommandById(robotArmCommandId);
}
/**
* 查询机械臂指令列表
*
* @param robotArmCommand 机械臂指令
* @return 机械臂指令
*/
@Override
public List<RobotArmCommand> selectRobotArmCommandList(RobotArmCommand robotArmCommand)
{
return robotArmCommandMapper.selectRobotArmCommandList(robotArmCommand);
}
@Override
public List<RobotArmCommand> findByType(String type) {
return robotArmCommandMapper.findByType( type);
}
/**
* 新增机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
@Override
public int insertRobotArmCommand(RobotArmCommand robotArmCommand)
{
robotArmCommand.setCreateTime(DateUtils.getNowDate());
TStoreyInfo tStoreyInfo = storeyInfoMapper.selectNearestFreeStorey();
if(tStoreyInfo != null) {
robotArmCommand.setStoreyCode(tStoreyInfo.getfStoreyCode());
} else {
robotArmCommand.setStoreyCode("无空闲老化层");
}
int i = robotArmCommandMapper.insertRobotArmCommand(robotArmCommand);
notifyCommandsUpdate();
return i;
}
@Override
@Transactional
public void powerOnCommand(Long commandId) {
RobotArmCommand command = robotArmCommandMapper.selectRobotArmCommandById(commandId);
if (command == null) {
throw new RuntimeException("指令不存在");
}
if (!"2".equals(command.getStatus())) {
throw new RuntimeException("只有未上电状态的指令才能执行上电操作");
}
// 更新状态为执行中
command.setStatus("1");
robotArmCommandMapper.updateRobotArmCommand(command);
// 发送上电指令给机械臂
String udpMessage = String.format("POWER_ON,%s,%s", command.getTrayCode(), command.getStoreyCode());
udpCommandSender.sendCommand(udpMessage);
// 记录操作日志
log.info("执行上电操作: 指令ID={}, 托盘={}, 位置={}", commandId, command.getTrayCode(), command.getStoreyCode());
}
/**
* 修改机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
@Override
public int updateRobotArmCommand(RobotArmCommand robotArmCommand)
{
int i = robotArmCommandMapper.updateRobotArmCommand(robotArmCommand);
notifyCommandsUpdate();
return i;
}
/**
* 批量删除机械臂指令
*
* @param robotArmCommandIds 需要删除的机械臂指令ID
* @return 结果
*/
@Override
public int deleteRobotArmCommandByIds(Long[] robotArmCommandIds)
{
int i = robotArmCommandMapper.deleteRobotArmCommandByIds(robotArmCommandIds);
notifyCommandsUpdate();
return i;
}
/**
* 删除机械臂指令信息
*
* @param robotArmCommandId 机械臂指令ID
* @return 结果
*/
@Override
public int deleteRobotArmCommandById(Long robotArmCommandId)
{
int i = robotArmCommandMapper.deleteRobotArmCommandById(robotArmCommandId);
notifyCommandsUpdate();
return i;
}
private void notifyCommandsUpdate() {
robotArmWebSocketHandler.broadcastCommandUpdate();
}
}
package com.zehong.system.service.websocket;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zehong.system.domain.RobotArmCommand;
import com.zehong.system.service.IRobotArmCommandService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author lenovo
* @date 2025/8/4
* @description websocketHandler
*/
@Component
public class RobotArmWebSocketHandler extends TextWebSocketHandler {
private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
private static final Logger log = LoggerFactory.getLogger(RobotArmWebSocketHandler.class);
@Resource
private IRobotArmCommandService robotArmCommandService;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
sendInitialData(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
// 处理客户端请求
if ("{\"type\":\"request\",\"commands\":[\"loading\",\"unloading\"]}".equals(payload)) {
sendInitialData(session);
}
}
private void sendInitialData(WebSocketSession session) throws IOException {
// 发送待上料指令
List<RobotArmCommand> loadingCommands = robotArmCommandService.findByType("0");
session.sendMessage(new TextMessage(createMessage("loading", loadingCommands)));
// 发送待下料指令
List<RobotArmCommand> unloadingCommands = robotArmCommandService.findByType("1");
session.sendMessage(new TextMessage(createMessage("unloading", unloadingCommands)));
}
private String createMessage(String type, List<RobotArmCommand> commands) {
ObjectMapper mapper = new ObjectMapper();
try {
// 创建包含更多信息的DTO列表
List<Map<String, Object>> commandData = new ArrayList<>();
for (RobotArmCommand cmd : commands) {
Map<String, Object> cmdMap = new HashMap<>();
cmdMap.put("robotArmCommandId", cmd.getRobotArmCommandId());
cmdMap.put("trayCode", cmd.getTrayCode());
cmdMap.put("storeyCode", cmd.getStoreyCode());
cmdMap.put("status", cmd.getStatus());
commandData.add(cmdMap);
}
Map<String, Object> message = new HashMap<>();
message.put("type", type);
message.put("data", commandData);
return mapper.writeValueAsString(message);
} catch (JsonProcessingException e) {
return "{\"error\":\"Failed to serialize data\"}";
}
}
public void broadcastCommandUpdate() {
List<RobotArmCommand> loadingCommands = robotArmCommandService.findByType("0");
List<RobotArmCommand> unloadingCommands = robotArmCommandService.findByType("1");
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
try {
session.sendMessage(new TextMessage(createMessage("loading", loadingCommands)));
session.sendMessage(new TextMessage(createMessage("unloading", unloadingCommands)));
} catch (IOException e) {
// 处理异常
}
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
}
/**
* 广播状态消息给所有客户端
*/
public void broadcastStatus(String status) {
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> message = new HashMap<>();
message.put("type", "status");
message.put("data", status);
String jsonMessage = mapper.writeValueAsString(message);
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
try {
session.sendMessage(new TextMessage(jsonMessage));
} catch (IOException e) {
log.error("发送状态消息到WebSocket失败", e);
}
}
}
} catch (JsonProcessingException e) {
log.error("序列化状态消息失败", e);
}
}
}
package com.zehong.system.service.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* @author lenovo
* @date 2025/8/4
* @description TODO
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private RobotArmWebSocketHandler robotArmWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(robotArmWebSocketHandler, "/ws-robot-arm")
.setAllowedOrigins("*");
}
}
package com.zehong.system.udp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
/**
* @author lenovo
* @date 2025/8/4
* @description TODO
*/
@Component
public class UdpCommandSender {
private static final Logger log = LoggerFactory.getLogger(UdpCommandSender.class);
@Value("${robot.arm.udp.ip}")
private String robotArmIp;
@Value("${robot.arm.udp.port}")
private int robotArmPort;
private DatagramSocket socket;
private InetAddress address;
@PostConstruct
public void init() {
try {
socket = new DatagramSocket();
address = InetAddress.getByName(robotArmIp);
log.info("UDP命令发送器初始化成功,目标地址: {}:{}", robotArmIp, robotArmPort);
} catch (Exception e) {
log.error("UDP命令发送器初始化失败", e);
}
}
public void sendCommand(String message) {
if (socket == null || address == null) {
log.error("UDP命令发送器未初始化,无法发送消息");
return;
}
try {
byte[] buffer = message.getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, robotArmPort);
socket.send(packet);
log.info("已发送UDP指令: {}", message);
} catch (IOException e) {
log.error("发送UDP指令失败: {}", message, e);
}
}
@PreDestroy
public void cleanup() {
if (socket != null && !socket.isClosed()) {
socket.close();
log.info("UDP命令发送器已关闭");
}
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zehong.system.mapper.RobotArmCommandMapper">
<resultMap type="RobotArmCommand" id="RobotArmCommandResult">
<result property="robotArmCommandId" column="f_robot_arm_command_id" />
<result property="trayCode" column="f_tray_code" />
<result property="storeyCode" column="f_storey_code" />
<result property="type" column="f_type" />
<result property="status" column="f_status" />
<result property="startExecutionTime" column="f_start_execution_time" />
<result property="endExecutionTime" column="f_end_execution_time" />
<result property="createTime" column="f_create_time" />
</resultMap>
<sql id="selectRobotArmCommandVo">
select f_robot_arm_command_id, f_tray_code, f_storey_code, f_type, f_status, f_start_execution_time, f_end_execution_time, f_create_time, f_command from t_robot_arm_command
</sql>
<select id="findByType" parameterType="string" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/>
where f_type = #{type}
</select>
<select id="selectRobotArmCommandList" parameterType="RobotArmCommand" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/>
<where>
<if test="trayCode != null and trayCode != ''"> and f_tray_code = #{trayCode}</if>
<if test="storeyCode != null and storeyCode != ''"> and f_storey_code = #{storeyCode}</if>
<if test="type != null and type != ''"> and f_type = #{type}</if>
<if test="status != null and status != ''"> and f_status = #{status}</if>
<if test="startExecutionTime != null "> and f_start_execution_time = #{startExecutionTime}</if>
<if test="endExecutionTime != null "> and f_end_execution_time = #{endExecutionTime}</if>
<if test="createTime != null "> and f_create_time = #{createTime}</if>
</where>
</select>
<select id="selectRobotArmCommandById" parameterType="Long" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/>
where f_robot_arm_command_id = #{robotArmCommandId}
</select>
<!-- 查找执行中的指令 -->
<select id="findExecutingCommand" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/>
WHERE f_tray_code = #{trayCode}
AND f_storey_code = #{storeyCode}
AND f_status = '1' <!-- 状态为执行中 -->
LIMIT 1
</select>
<insert id="insertRobotArmCommand" parameterType="RobotArmCommand" useGeneratedKeys="true" keyProperty="robotArmCommandId">
insert into t_robot_arm_command
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="trayCode != null">f_tray_code,</if>
<if test="storeyCode != null">f_storey_code,</if>
<if test="type != null">f_type,</if>
<if test="status != null">f_status,</if>
<if test="startExecutionTime != null">f_start_execution_time,</if>
<if test="endExecutionTime != null">f_end_execution_time,</if>
<if test="createTime != null">f_create_time,</if>
<if test="command != null">f_command,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="trayCode != null">#{trayCode},</if>
<if test="storeyCode != null">#{storeyCode},</if>
<if test="type != null">#{type},</if>
<if test="status != null">#{status},</if>
<if test="startExecutionTime != null">#{startExecutionTime},</if>
<if test="endExecutionTime != null">#{endExecutionTime},</if>
<if test="createTime != null">#{createTime},</if>
<if test="command != null">#{command},</if>
</trim>
</insert>
<update id="updateRobotArmCommand" parameterType="RobotArmCommand">
update t_robot_arm_command
<trim prefix="SET" suffixOverrides=",">
<if test="trayCode != null">f_tray_code = #{trayCode},</if>
<if test="storeyCode != null">f_storey_code = #{storeyCode},</if>
<if test="type != null">f_type = #{type},</if>
<if test="status != null">f_status = #{status},</if>
<if test="startExecutionTime != null">f_start_execution_time = #{startExecutionTime},</if>
<if test="endExecutionTime != null">f_end_execution_time = #{endExecutionTime},</if>
<if test="createTime != null">f_create_time = #{createTime},</if>
<if test="command != null">f_command = #{command},</if>
</trim>
where f_robot_arm_command_id = #{robotArmCommandId}
</update>
<!-- 在 XML 中添加 -->
<!-- 更新执行中状态为完成状态 -->
<update id="updateExecutingToCompleted">
UPDATE t_robot_arm_command
SET f_status = '2'
WHERE f_status = '1' and f_end_execution_time IS NOT NULL
</update>
<!-- 获取待执行的上料指令 -->
<select id="selectPendingLoadingCommands" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/>
WHERE f_type = '0' AND f_status = '0'
ORDER BY f_create_time ASC
LIMIT 1
</select>
<!-- 获取待执行的下料指令 -->
<select id="selectPendingUnloadingCommands" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/>
WHERE f_type = '1' AND f_status = '0'
ORDER BY f_create_time ASC
LIMIT 1
</select>
<delete id="deleteRobotArmCommandById" parameterType="Long">
delete from t_robot_arm_command where f_robot_arm_command_id = #{robotArmCommandId}
</delete>
<delete id="deleteRobotArmCommandByIds" parameterType="String">
delete from t_robot_arm_command where f_robot_arm_command_id in
<foreach item="robotArmCommandId" collection="array" open="(" separator="," close=")">
#{robotArmCommandId}
</foreach>
</delete>
</mapper>
\ No newline at end of file
......@@ -48,7 +48,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectTStoreyInfoVo"/>
where f_storey_code = #{fStoreyCode}
</select>
<select id="selectNearestFreeStorey" resultMap="TStoreyInfoResult">
SELECT
f_storey_id, f_equipment_id, f_storey_code, f_ip, f_status, f_port,
f_aging_start_time, f_update_time, f_create_time, f_alarm_time
FROM (
SELECT
*,
CASE
WHEN cabinet_num BETWEEN 1 AND 18 THEN (cabinet_num - 1) * 10 + layer_num
WHEN cabinet_num BETWEEN 19 AND 36 THEN (cabinet_num - 19) * 10 + layer_num
END AS distance
FROM (
SELECT
*,
CAST(SUBSTRING_INDEX(f_storey_code, '-', 1) AS UNSIGNED) AS cabinet_num,
CAST(SUBSTRING_INDEX(f_storey_code, '-', -1) AS UNSIGNED) AS layer_num
FROM t_storey_info
WHERE f_status = '0' -- 空闲状态
) AS parsed
) AS calculated
ORDER BY distance ASC
LIMIT 1
</select>
<insert id="insertTStoreyInfo" parameterType="TStoreyInfo">
insert into t_storey_info
<trim prefix="(" suffix=")" suffixOverrides=",">
......
import request from '@/utils/request'
// 查询机械臂指令列表
export function listCommand(query) {
return request({
url: '/robotArm/command/list',
method: 'get',
params: query
})
}
// 查询机械臂指令详细
export function getCommand(robotArmCommandId) {
return request({
url: '/robotArm/command/' + robotArmCommandId,
method: 'get'
})
}
// 新增机械臂指令
export function addCommand(data) {
return request({
url: '/robotArm/command',
method: 'post',
data: data
})
}
// 执行上电操作
export function powerOnCommand(commandId) {
return request({
url: '/robotArm/command/powerOn',
method: 'post',
data: { commandId }
})
}
// 修改机械臂指令
export function updateCommand(data) {
return request({
url: '/robotArm/command',
method: 'put',
data: data
})
}
// 删除机械臂指令
export function delCommand(robotArmCommandId) {
return request({
url: '/robotArm/command/' + robotArmCommandId,
method: 'delete'
})
}
// 导出机械臂指令
export function exportCommand(query) {
return request({
url: '/robotArm/command/export',
method: 'get',
params: query
})
}
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