Commit 4198ca5c authored by wanghao's avatar wanghao

1 机械臂 可以 实时设置 有指令时是否执行的操作。

2 下料 时 柜子断电 导致 指令不执行 调整,把 断电的的柜子的 指令 设置成 断电失败的状态,等柜子修好后继续。
parent 81db12e6
...@@ -138,6 +138,27 @@ public class RobotArmCommandController extends BaseController ...@@ -138,6 +138,27 @@ public class RobotArmCommandController extends BaseController
return toAjax(robotArmCommandService.updateInstructionExecutionPriority(priority)); return toAjax(robotArmCommandService.updateInstructionExecutionPriority(priority));
} }
/**
* 控制机械臂
* @param action
* @return
*/
@GetMapping("/updateAnimationState/{action}")
public AjaxResult updateAnimationState(@PathVariable("action") String action) {
return toAjax(robotArmCommandService.updateAnimationState(action));
}
/**
* 短电已修复
* @param robotArmCommandId r
* @return r
*/
@GetMapping("/powerFailureResolved/{robotArmCommandId}")
public AjaxResult powerFailureResolved(@PathVariable("robotArmCommandId") Long robotArmCommandId) {
return robotArmCommandService.powerFailureResolved(robotArmCommandId);
}
/** /**
* 删除机械臂指令 * 删除机械臂指令
*/ */
......
...@@ -8,7 +8,7 @@ spring: ...@@ -8,7 +8,7 @@ spring:
master: master:
url: jdbc:mysql://localhost:3306/zh-mes-device-db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true url: jdbc:mysql://localhost:3306/zh-mes-device-db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
username: root username: root
password: zh@123456 password: Zh@123456
# 从库数据源 # 从库数据源
slave: slave:
# 从数据源开关/默认关闭 # 从数据源开关/默认关闭
...@@ -102,4 +102,4 @@ robot: ...@@ -102,4 +102,4 @@ robot:
arm: arm:
udp: udp:
ip: 127.0.0.1 ip: 127.0.0.1
port: 6000 port: 6000
\ No newline at end of file
...@@ -25,8 +25,8 @@ spring: ...@@ -25,8 +25,8 @@ spring:
messages: messages:
# 国际化资源文件路径 # 国际化资源文件路径
basename: i18n/messages basename: i18n/messages
profiles: profiles:
active: test active: dev
# 文件上传 # 文件上传
servlet: servlet:
multipart: multipart:
...@@ -48,7 +48,7 @@ token: ...@@ -48,7 +48,7 @@ token:
secret: abcdefghijklmnopqrstuvwxyz secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟) # 令牌有效期(默认30分钟)
expireTime: 3000000 expireTime: 3000000
# MyBatis配置 # MyBatis配置
mybatis: mybatis:
# 搜索指定包别名 # 搜索指定包别名
...@@ -59,11 +59,11 @@ mybatis: ...@@ -59,11 +59,11 @@ mybatis:
configLocation: classpath:mybatis/mybatis-config.xml configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件 # PageHelper分页插件
pagehelper: pagehelper:
helperDialect: mysql helperDialect: mysql
reasonable: true reasonable: true
supportMethodsArguments: true supportMethodsArguments: true
params: count=countSql params: count=countSql
# Swagger配置 # Swagger配置
swagger: swagger:
...@@ -73,7 +73,7 @@ swagger: ...@@ -73,7 +73,7 @@ swagger:
pathMapping: /dev-api pathMapping: /dev-api
# 防止XSS攻击 # 防止XSS攻击
xss: xss:
# 过滤开关 # 过滤开关
enabled: true enabled: true
# 排除链接(多个用逗号分隔) # 排除链接(多个用逗号分隔)
......
...@@ -36,7 +36,7 @@ public class RobotArmCommand extends BaseEntity ...@@ -36,7 +36,7 @@ public class RobotArmCommand extends BaseEntity
private String type; private String type;
/** 状态:0-待执行;1-执行中;2-执行结束(上料就是绑定托盘,下料就是解绑托盘) */ /** 状态:0-待执行;1-执行中;2-执行结束(上料就是绑定托盘,下料就是解绑托盘) */
@Excel(name = "状态:0-待分配位置,1-待执行;2-执行中;3-未上电;4-执行结束(上料就是绑定托盘,下料就是解绑托盘)") @Excel(name = "状态:0-待分配位置,1-待执行;2-执行中;3-未上电;4-执行结束(上料就是绑定托盘,下料就是解绑托盘): 5-断电失败")
private String status; private String status;
/** 指令开始执行时间 */ /** 指令开始执行时间 */
......
...@@ -43,6 +43,8 @@ public interface RobotArmCommandMapper ...@@ -43,6 +43,8 @@ public interface RobotArmCommandMapper
public List<RobotArmCommand> selectIsRunningCommandByTrayCode(String trayCode); public List<RobotArmCommand> selectIsRunningCommandByTrayCode(String trayCode);
public void updateStatusPowerOffFailed(String storeyCode);
/** /**
* 新增机械臂指令 * 新增机械臂指令
* *
......
...@@ -241,7 +241,9 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP ...@@ -241,7 +241,9 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
// 完成指令 // 完成指令
robotArmCommandService.completeCommand(commandIdToComplete); robotArmCommandService.completeCommand(commandIdToComplete);
log.info("指令完成: {}", commandIdToComplete); log.info("指令完成: {}", commandIdToComplete);
} catch (Exception e) {
log.error("指令完成失败: {}", commandIdToComplete, e);
} finally {
// 清空当前指令 // 清空当前指令
currentCommandId = null; currentCommandId = null;
...@@ -252,9 +254,6 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP ...@@ -252,9 +254,6 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
synchronized (this) { synchronized (this) {
handleFullyIdleState(); handleFullyIdleState();
} }
} catch (Exception e) {
log.error("指令完成失败: {}", commandIdToComplete, e);
commandState.set(CommandState.ERROR);
} }
} }
} }
......
...@@ -69,6 +69,10 @@ public interface IRobotArmCommandService ...@@ -69,6 +69,10 @@ public interface IRobotArmCommandService
public int updateInstructionExecutionPriority(String priority); public int updateInstructionExecutionPriority(String priority);
public int updateAnimationState(String action);
public AjaxResult powerFailureResolved(Long robotArmCommandId);
/** /**
* 批量删除机械臂指令 * 批量删除机械臂指令
* *
......
...@@ -105,52 +105,62 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService ...@@ -105,52 +105,62 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
if (cacheObject != null) { if (cacheObject != null) {
priority = (String) cacheObject; priority = (String) cacheObject;
} }
// 合并查询:同时获取上料、下料各1条待执行指令
List<RobotArmCommand> pendingCommands = robotArmCommandMapper.selectPendingLoadUnloadCommands(); Object cacheObject1 = redisCache.getCacheObject("sys_config:robotAction");
String action = "start";
String feedConveyorIpAndPort = iConveyorBeltIpMaintainService.selectConfigByKey(Constants.FEED_CONVEYOR_IP_AND_PORT); if (cacheObject1 != null) {
String feedIp ; action = (String) cacheObject1;
int feedPort ;
String outLetBeltIpAndPort = iConveyorBeltIpMaintainService.selectConfigByKey(Constants.OUT_LET_BELT_IP_AND_PORT);
String outLetBeltIp;
int outLetBeltPort;
// 区分上料、下料指令(根据f_type)
RobotArmCommand loadingCommand = null;
RobotArmCommand unloadingCommand = null;
for (RobotArmCommand command : pendingCommands) {
if ("0".equals(command.getType())) { // 上料指令(type=0)
loadingCommand = command;
} else if ("1".equals(command.getType())) { // 下料指令(type=1)
unloadingCommand = command;
}
} }
if(StringUtils.isNotBlank(feedConveyorIpAndPort) && StringUtils.isNotBlank(outLetBeltIpAndPort)) {
String[] splitFeed = feedConveyorIpAndPort.split(":"); if("start".equals(action)) {
feedIp = splitFeed[0];
feedPort = Integer.parseInt(splitFeed[1]); // 合并查询:同时获取上料、下料各1条待执行指令
List<RobotArmCommand> pendingCommands = robotArmCommandMapper.selectPendingLoadUnloadCommands();
String feedConveyorIpAndPort = iConveyorBeltIpMaintainService.selectConfigByKey(Constants.FEED_CONVEYOR_IP_AND_PORT);
String feedIp ;
int feedPort ;
String outLetBeltIpAndPort = iConveyorBeltIpMaintainService.selectConfigByKey(Constants.OUT_LET_BELT_IP_AND_PORT);
String outLetBeltIp;
int outLetBeltPort;
// 区分上料、下料指令(根据f_type)
RobotArmCommand loadingCommand = null;
RobotArmCommand unloadingCommand = null;
for (RobotArmCommand command : pendingCommands) {
if ("0".equals(command.getType())) { // 上料指令(type=0)
loadingCommand = command;
} else if ("1".equals(command.getType())) { // 下料指令(type=1)
unloadingCommand = command;
}
}
if(StringUtils.isNotBlank(feedConveyorIpAndPort) && StringUtils.isNotBlank(outLetBeltIpAndPort)) {
String[] splitOut = outLetBeltIpAndPort.split(":"); String[] splitFeed = feedConveyorIpAndPort.split(":");
outLetBeltIp = splitOut[0]; feedIp = splitFeed[0];
outLetBeltPort = Integer.parseInt(splitOut[1]); feedPort = Integer.parseInt(splitFeed[1]);
if((loadingCommand != null && "loading".equals(priority)) || String[] splitOut = outLetBeltIpAndPort.split(":");
(unloadingCommand == null && loadingCommand != null && "unloading".equals(priority))) { outLetBeltIp = splitOut[0];
// 传送带检测先去掉 outLetBeltPort = Integer.parseInt(splitOut[1]);
boolean[] roboticArmEntryConveyorData = Modbus4jUtils.getRoboticArmExitConveyorData(feedIp,feedPort);
log.info("机械臂入口 conveyor 0状态: " + roboticArmEntryConveyorData[0]); if((loadingCommand != null && "loading".equals(priority)) ||
log.info("机械臂入口 conveyor 1状态: " + roboticArmEntryConveyorData[1]); (unloadingCommand == null && loadingCommand != null && "unloading".equals(priority))) {
if(roboticArmEntryConveyorData[1]) { // 传送带检测先去掉
sendCommand(loadingCommand, "LOAD"); boolean[] roboticArmEntryConveyorData = Modbus4jUtils.getRoboticArmExitConveyorData(feedIp,feedPort);
}
} else if(unloadingCommand != null) { log.info("机械臂入口 conveyor 0状态: " + roboticArmEntryConveyorData[0]);
boolean[] roboticArmExitConveyorData = Modbus4jUtils.getRoboticArmExitConveyorData(outLetBeltIp,outLetBeltPort); log.info("机械臂入口 conveyor 1状态: " + roboticArmEntryConveyorData[1]);
log.info("机械臂出口 conveyor 0状态: " + roboticArmExitConveyorData[0]); if(roboticArmEntryConveyorData[1]) {
log.info("机械臂出口 conveyor 1状态: " + roboticArmExitConveyorData[1]); sendCommand(loadingCommand, "LOAD");
if(!roboticArmExitConveyorData[0]) { }
log.info("开始处理下料指令: {}", unloadingCommand); } else if(unloadingCommand != null) {
sendCommand(unloadingCommand, "UNLOAD"); boolean[] roboticArmExitConveyorData = Modbus4jUtils.getRoboticArmExitConveyorData(outLetBeltIp,outLetBeltPort);
log.info("机械臂出口 conveyor 0状态: " + roboticArmExitConveyorData[0]);
log.info("机械臂出口 conveyor 1状态: " + roboticArmExitConveyorData[1]);
if(!roboticArmExitConveyorData[0]) {
log.info("开始处理下料指令: {}", unloadingCommand);
sendCommand(unloadingCommand, "UNLOAD");
}
} }
} }
} }
...@@ -659,6 +669,41 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService ...@@ -659,6 +669,41 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
return 1; return 1;
} }
/**
* 修改动画状态
*
* @param action 动作
* @return 结果
*/
@Override
public int updateAnimationState(String action) {
redisCache.setCacheObject("sys_config:robotAction", action);
return 1;
}
/**
* 恢复电源故障
*
* @param robotArmCommandId r
* @return r
*/
@Override
public AjaxResult powerFailureResolved(Long robotArmCommandId) {
if(robotArmCommandId == null) {
return AjaxResult.error("参数错误");
}
RobotArmCommand robotArmCommand = new RobotArmCommand();
robotArmCommand.setRobotArmCommandId(robotArmCommandId);
robotArmCommand.setStatus("1");
robotArmCommandMapper.updateRobotArmCommand(robotArmCommand);
notifyCommandsUpdate();
return AjaxResult.success();
}
/** /**
* 批量删除机械臂指令 * 批量删除机械臂指令
* *
......
...@@ -9,13 +9,12 @@ import com.zehong.system.domain.SysConfig; ...@@ -9,13 +9,12 @@ import com.zehong.system.domain.SysConfig;
import com.zehong.system.domain.TEquipmentAlarmData; import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo; import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.domain.TTrayInfo; import com.zehong.system.domain.TTrayInfo;
import com.zehong.system.mapper.RobotArmCommandMapper;
import com.zehong.system.mapper.TStoreyInfoMapper; import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.mapper.TTrayInfoMapper; import com.zehong.system.mapper.TTrayInfoMapper;
import com.zehong.system.modbus.util.Modbus4jUtils; import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ISysConfigService; import com.zehong.system.service.*;
import com.zehong.system.service.ITEquipmentAlarmDataService; import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import com.zehong.system.service.ITStoreyInfoService;
import com.zehong.system.service.ITTrayInfoService;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -54,6 +53,11 @@ public class AllCommandHandler { ...@@ -54,6 +53,11 @@ public class AllCommandHandler {
@Autowired @Autowired
private ISysConfigService configService; private ISysConfigService configService;
@Resource
private RobotArmCommandMapper robotArmCommandMapper;
@Resource
private RobotArmWebSocketHandler robotArmWebSocketHandler;
// 使用常量定义索引,避免魔法数字 // 使用常量定义索引,避免魔法数字
private static final int AGING_STAGE_INDEX = 3; private static final int AGING_STAGE_INDEX = 3;
private static final int DEFAULT_HOURS = 72; private static final int DEFAULT_HOURS = 72;
...@@ -140,8 +144,8 @@ public class AllCommandHandler { ...@@ -140,8 +144,8 @@ public class AllCommandHandler {
@Async // 异步执行 @Async // 异步执行
@EventListener(PowerOffCommandEvent.class) @EventListener(PowerOffCommandEvent.class)
public void handlePowerOffCommand(PowerOffCommandEvent event) { public void handlePowerOffCommand(PowerOffCommandEvent event) {
String storeyCode = event.getDeviceCode() + "-" + event.getLayer();
try { try {
String storeyCode = event.getDeviceCode() + "-" + event.getLayer();
String ip = event.getIp(); String ip = event.getIp();
int port = event.getPort(); int port = event.getPort();
int registerOffset = event.getRegisterOffset(); int registerOffset = event.getRegisterOffset();
...@@ -169,6 +173,11 @@ public class AllCommandHandler { ...@@ -169,6 +173,11 @@ public class AllCommandHandler {
} catch (ModbusInitException | ModbusTransportException e) { } catch (ModbusInitException | ModbusTransportException e) {
log.error("断电指令执行失败 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer(), e); log.error("断电指令执行失败 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer(), e);
//把这个指令状态 改成 断电失败,不影响下面的指令执行
robotArmCommandMapper.updateStatusPowerOffFailed(storeyCode);
// 通过WebSocket广播更新
robotArmWebSocketHandler.broadcastCommandUpdate();
// 记录异常 // 记录异常
TEquipmentAlarmData alarmData = new TEquipmentAlarmData(); TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
alarmData.setfAlarmType("03"); // 老化层 alarmData.setfAlarmType("03"); // 老化层
......
...@@ -37,6 +37,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...@@ -37,6 +37,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectRobotArmCommandVo"/> <include refid="selectRobotArmCommandVo"/>
where f_tray_code = #{trayCode} and f_status != '4' where f_tray_code = #{trayCode} and f_status != '4'
</select> </select>
<update id="updateStatusPowerOffFailed" parameterType="string">
update t_robot_arm_command
set f_status = '5'
where f_storey_code = #{storeyCode} and f_status = '2'
</update>
<select id="selectRobotArmCommandList" parameterType="RobotArmCommand" resultMap="RobotArmCommandResult"> <select id="selectRobotArmCommandList" parameterType="RobotArmCommand" resultMap="RobotArmCommandResult">
<include refid="selectRobotArmCommandVo"/> <include refid="selectRobotArmCommandVo"/>
......
...@@ -112,6 +112,14 @@ export function delCommand(robotArmCommandId) { ...@@ -112,6 +112,14 @@ export function delCommand(robotArmCommandId) {
}) })
} }
export function powerFailureResolved(robotArmCommandId) {
return request({
url: '/robotArm/command/powerFailureResolved/' + robotArmCommandId,
method: 'get'
})
}
// 导出机械臂指令 // 导出机械臂指令
export function exportCommand(query) { export function exportCommand(query) {
return request({ return request({
...@@ -120,3 +128,10 @@ export function exportCommand(query) { ...@@ -120,3 +128,10 @@ export function exportCommand(query) {
params: query params: query
}) })
} }
export function updateAnimationState(action) {
return request({
url: '/robotArm/command/updateAnimationState/' + action,
method: 'get'
})
}
...@@ -2,21 +2,16 @@ ...@@ -2,21 +2,16 @@
<div class="robotic-arm-panel"> <div class="robotic-arm-panel">
<!-- 标题区域 --> <!-- 标题区域 -->
<div class="panel-title"> <div class="panel-title">
<!-- 左侧标题+状态指示灯组合 -->
<div class="title-with-status"> <div class="title-with-status">
<div class="title-left"> <div class="title-left">
<div class="title-text">机械臂</div> <div class="title-text">机械臂</div>
<div class="title-line"></div> <div class="title-line"></div>
</div> </div>
<!-- 状态指示灯:紧挨着标题文字右侧 -->
<div class="status-indicator"> <div class="status-indicator">
<div class="status-light" :class="statusClass"></div> <div class="status-light" :class="statusClass"></div>
<div class="status-text">{{ statusText }}</div> <div class="status-text">{{ statusText }}</div>
</div> </div>
</div> </div>
<!-- 上料按钮 -->
<div class="title-right"> <div class="title-right">
<button class="add-button" @click="openAddModeDialog"> <button class="add-button" @click="openAddModeDialog">
<i class="el-icon-plus"></i> 上料 <i class="el-icon-plus"></i> 上料
...@@ -38,16 +33,12 @@ ...@@ -38,16 +33,12 @@
<div class="dialog-content"> <div class="dialog-content">
<div class="mode-selection"> <div class="mode-selection">
<div class="mode-option" @click="selectMode('auto')"> <div class="mode-option" @click="selectMode('auto')">
<div class="mode-icon"> <div class="mode-icon"><i class="el-icon-video-play"></i></div>
<i class="el-icon-video-play"></i>
</div>
<div class="mode-title">自动模式</div> <div class="mode-title">自动模式</div>
<div class="mode-desc">系统自动分配位置</div> <div class="mode-desc">系统自动分配位置</div>
</div> </div>
<div class="mode-option" @click="selectMode('manual')"> <div class="mode-option" @click="selectMode('manual')">
<div class="mode-icon"> <div class="mode-icon"><i class="el-icon-s-operation"></i></div>
<i class="el-icon-s-operation"></i>
</div>
<div class="mode-title">手动模式</div> <div class="mode-title">手动模式</div>
<div class="mode-desc">手动指定层编号</div> <div class="mode-desc">手动指定层编号</div>
</div> </div>
...@@ -68,23 +59,13 @@ ...@@ -68,23 +59,13 @@
<div class="dialog-content"> <div class="dialog-content">
<div class="scan-prompt">请扫描托盘二维码</div> <div class="scan-prompt">请扫描托盘二维码</div>
<div class="mode-indicator">自动模式 - 系统自动分配位置</div> <div class="mode-indicator">自动模式 - 系统自动分配位置</div>
<div class="input-group"> <div class="input-group">
<label for="trayCode">标检单号:</label> <label for="trayCode">标检单号:</label>
<input <input type="text" v-model="productStandardInspectionNumber" placeholder="手动输入或扫码标检单号" ref="productStandardInspectionNumberInput">
type="text"
v-model="productStandardInspectionNumber"
placeholder="手动输入或扫码标检单号"
ref="productStandardInspectionNumberInput"
>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="trayCode">托盘编号:</label> <label for="trayCode">托盘编号:</label>
<input <input type="text" v-model="trayCode" placeholder="手动输入或扫码">
type="text"
v-model="trayCode"
placeholder="手动输入或扫码"
>
</div> </div>
</div> </div>
</div> </div>
...@@ -103,36 +84,18 @@ ...@@ -103,36 +84,18 @@
<div class="dialog-content"> <div class="dialog-content">
<div class="scan-prompt">请输入上料信息</div> <div class="scan-prompt">请输入上料信息</div>
<div class="mode-indicator">手动模式 - 需要指定层编号</div> <div class="mode-indicator">手动模式 - 需要指定层编号</div>
<div class="manual-inputs"> <div class="manual-inputs">
<div class="input-group"> <div class="input-group">
<label for="trayCode">标检单号:</label> <label for="trayCode">标检单号:</label>
<input <input type="text" v-model="productStandardInspectionNumber" placeholder="手动输入或扫码标检单号" ref="manualStandardInput">
type="text"
v-model="productStandardInspectionNumber"
placeholder="手动输入或扫码标检单号"
ref="manualStandardInput"
>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="trayCode">托盘编号:</label> <label for="trayCode">托盘编号:</label>
<input <input id="trayCode" type="text" v-model="manualTrayCode" placeholder="输入托盘编号">
id="trayCode"
type="text"
v-model="manualTrayCode"
placeholder="输入托盘编号"
>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="storeyCode">层编号:</label> <label for="storeyCode">层编号:</label>
<input <input id="storeyCode" type="text" v-model="storeyCode" placeholder="输入层编号(如: 1-1, 12-3)">
id="storeyCode"
type="text"
v-model="storeyCode"
placeholder="输入层编号(如: 1-1, 12-3)"
>
</div> </div>
</div> </div>
</div> </div>
...@@ -150,13 +113,7 @@ ...@@ -150,13 +113,7 @@
<div class="loading-command"> <div class="loading-command">
<div class="command-title">待上料指令</div> <div class="command-title">待上料指令</div>
<div class="command-list"> <div class="command-list">
<div <div v-for="(cmd, index) in loadingCommands" :key="index" class="command-item" :class="getCommandStatusClass(cmd.status)" @click="handleCommandClick(cmd)">
v-for="(cmd, index) in loadingCommands"
:key="index"
class="command-item"
:class="getCommandStatusClass(cmd.status)"
@click="handleCommandClick(cmd)"
>
<div class="cmd-info"> <div class="cmd-info">
<div class="cmd-tray">托盘: {{ cmd.trayCode }}</div> <div class="cmd-tray">托盘: {{ cmd.trayCode }}</div>
<div class="cmd-position">位置: {{ cmd.position }}</div> <div class="cmd-position">位置: {{ cmd.position }}</div>
...@@ -166,35 +123,23 @@ ...@@ -166,35 +123,23 @@
</div> </div>
</div> </div>
<!-- 中间:机械臂主体 --> <!-- 中间:机械臂主体(添加双击事件和暂停类) -->
<div class="arm-center-wrapper"> <div class="arm-center-wrapper">
<!-- 优先级控制区域 -->
<div class="priority-control"> <div class="priority-control">
<div class="priority-display">{{ priorityDisplayText }}</div> <div class="priority-display">{{ priorityDisplayText }}</div>
<button class="priority-toggle-btn" @click="togglePriority"> <button class="priority-toggle-btn" @click="togglePriority">点击切换</button>
点击切换
</button>
</div> </div>
<div class="robotic-arm-container" @dblclick="toggleAnimation" :class="{ 'paused-animation': !isAnimating }">
<div class="robotic-arm-container">
<div class="robotic-arm"> <div class="robotic-arm">
<!-- 机械臂各部件 --> <!-- 机械臂各部件 -->
<div class="arm-base"> <div class="arm-base">
<div class="base-top"></div> <div class="base-top"></div>
<div class="base-bottom"></div> <div class="base-bottom"></div>
</div> </div>
<div class="arm-joint joint-1"> <div class="arm-joint joint-1"><div class="joint-body"></div></div>
<div class="joint-body"></div> <div class="arm-segment segment-1"><div class="segment-body"></div></div>
</div> <div class="arm-joint joint-2"><div class="joint-body"></div></div>
<div class="arm-segment segment-1"> <div class="arm-segment segment-2"><div class="segment-body"></div></div>
<div class="segment-body"></div>
</div>
<div class="arm-joint joint-2">
<div class="joint-body"></div>
</div>
<div class="arm-segment segment-2">
<div class="segment-body"></div>
</div>
<div class="arm-gripper"> <div class="arm-gripper">
<div class="gripper-left"></div> <div class="gripper-left"></div>
<div class="gripper-right"></div> <div class="gripper-right"></div>
...@@ -207,13 +152,7 @@ ...@@ -207,13 +152,7 @@
<div class="unloading-command"> <div class="unloading-command">
<div class="command-title">待下料指令</div> <div class="command-title">待下料指令</div>
<div class="command-list"> <div class="command-list">
<div <div v-for="(cmd, index) in unloadingCommands" :key="index" class="command-item" :class="getCommandStatusClass(cmd.status)" @click="handleCommandClick(cmd)">
v-for="(cmd, index) in unloadingCommands"
:key="index"
class="command-item"
:class="getCommandStatusClass(cmd.status)"
@click="handleCommandClick(cmd)"
>
<div class="cmd-info"> <div class="cmd-info">
<div class="cmd-tray">托盘: {{ cmd.trayCode }}</div> <div class="cmd-tray">托盘: {{ cmd.trayCode }}</div>
<div class="cmd-position">位置: {{ cmd.position }}</div> <div class="cmd-position">位置: {{ cmd.position }}</div>
...@@ -245,7 +184,7 @@ ...@@ -245,7 +184,7 @@
</div> </div>
<!-- 确认执行完成对话框 --> <!-- 确认执行完成对话框 -->
<div class="dialog-mask" v-if="showSureCompleteDialog" @click.self="closePowerOnDialog"> <div class="dialog-mask" v-if="showSureCompleteDialog" @click.self="closeSureCompleteDialog">
<div class="dialog-container"> <div class="dialog-container">
<div class="dialog-header">确认执行完成</div> <div class="dialog-header">确认执行完成</div>
<div class="dialog-body"> <div class="dialog-body">
...@@ -263,11 +202,32 @@ ...@@ -263,11 +202,32 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 新增:断电失败修复对话框(状态5) -->
<div class="dialog-mask" v-if="showFixDialog" @click.self="closeFixDialog">
<div class="dialog-container">
<div class="dialog-header">断电失败</div>
<div class="dialog-body">
<div class="dialog-content">
<div class="scan-prompt">该指令断电失败,是否已修复?</div>
<div class="power-on-info">
<div><span class="label">托盘编号:</span> {{ selectedFailedCommand ? selectedFailedCommand.trayCode : '' }}</div>
<div><span class="label">位置:</span> {{ selectedFailedCommand ? selectedFailedCommand.position : '' }}</div>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="cancel-button" @click="closeFixDialog">取消</button>
<button class="confirm-button" @click="handleFixConfirm">是,已修复</button>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import {addCommand, powerOnCommand, sendHomeCommand, sendStopCommand, sureCompletedCommand, updateInstructionExecutionPriority, addManualCommand} from "@/api/robotArm/robotArmCommand" import {addCommand, powerOnCommand, sendHomeCommand, sendStopCommand, sureCompletedCommand,
updateInstructionExecutionPriority, addManualCommand,updateAnimationState,powerFailureResolved} from "@/api/robotArm/robotArmCommand"
export default { export default {
name: 'RoboticArm', name: 'RoboticArm',
...@@ -281,7 +241,6 @@ export default { ...@@ -281,7 +241,6 @@ export default {
// 自动模式数据 // 自动模式数据
trayCode: '', trayCode: '',
// 手动模式数据 // 手动模式数据
manualTrayCode: '', manualTrayCode: '',
storeyCode: '', storeyCode: '',
...@@ -297,6 +256,12 @@ export default { ...@@ -297,6 +256,12 @@ export default {
selectedCommand: null, selectedCommand: null,
priority: 'loading', // loading: 上料优先, unloading: 下料优先 priority: 'loading', // loading: 上料优先, unloading: 下料优先
initWebSocketIp: '', initWebSocketIp: '',
// 新增:动画控制
isAnimating: true, // 默认动画运行
// 新增:断电失败修复对话框
showFixDialog: false,
selectedFailedCommand: null,
}; };
}, },
computed: { computed: {
...@@ -362,34 +327,19 @@ export default { ...@@ -362,34 +327,19 @@ export default {
}, },
methods: { methods: {
// 打开上料模式选择对话框 // ==================== 原有方法保持不变 ====================
openAddModeDialog() { openAddModeDialog() { this.showModeDialog = true; },
this.showModeDialog = true; closeModeDialog() { this.showModeDialog = false; },
},
// 关闭模式选择对话框
closeModeDialog() {
this.showModeDialog = false;
},
// 选择模式
selectMode(mode) { selectMode(mode) {
this.showModeDialog = false; this.showModeDialog = false;
if (mode === 'auto') { if (mode === 'auto') this.showAutoAddDialog = true;
this.showAutoAddDialog = true; else this.showManualAddDialog = true;
} else {
this.showManualAddDialog = true;
}
}, },
// 关闭自动模式对话框
closeAutoDialog() { closeAutoDialog() {
this.showAutoAddDialog = false; this.showAutoAddDialog = false;
this.productStandardInspectionNumber = ''; this.productStandardInspectionNumber = '';
this.trayCode = ''; this.trayCode = '';
}, },
// 关闭手动模式对话框
closeManualDialog() { closeManualDialog() {
this.showManualAddDialog = false; this.showManualAddDialog = false;
this.manualTrayCode = ''; this.manualTrayCode = '';
...@@ -397,85 +347,44 @@ export default { ...@@ -397,85 +347,44 @@ export default {
this.trayType = '0'; this.trayType = '0';
this.productStandardInspectionNumber = ''; this.productStandardInspectionNumber = '';
}, },
// 确认自动模式上料
confirmAutoAdd() { confirmAutoAdd() {
if (!this.trayCode.trim()) { if (!this.trayCode.trim()) { this.$message.warning('请输入托盘编号'); return; }
this.$message.warning('请输入托盘编号'); if (!this.productStandardInspectionNumber.trim()) { this.$message.warning('请输入标检单编号'); return; }
return;
}
if (!this.productStandardInspectionNumber.trim()) {
this.$message.warning('请输入标检单编号');
return;
}
const robotArmCommand = { const robotArmCommand = {
trayCode: this.trayCode, trayCode: this.trayCode,
storeyCode: '待分配位置', storeyCode: '待分配位置',
type: '0', type: '0',
productStandardInspectionNumber: this.productStandardInspectionNumber productStandardInspectionNumber: this.productStandardInspectionNumber
}; };
addCommand(robotArmCommand).then(res => { addCommand(robotArmCommand).then(res => {
if(res.code === 200) { if(res.code === 200) { this.$message.success("添加成功"); this.closeAutoDialog(); }
this.$message.success("添加成功"); else { this.$message.error("添加失败"); }
this.closeAutoDialog();
} else {
this.$message.error("添加失败");
}
}) })
}, },
// 确认手动模式上料
confirmManualAdd() { confirmManualAdd() {
if (!this.productStandardInspectionNumber.trim()) { this.$message.warning('请输入标检单号'); return; }
if (!this.productStandardInspectionNumber.trim()) { if (!this.manualTrayCode.trim()) { this.$message.warning('请输入托盘编号'); return; }
this.$message.warning('请输入标检单号'); if (!this.storeyCode.trim()) { this.$message.warning('请输入层编号'); return; }
return;
}
if (!this.manualTrayCode.trim()) {
this.$message.warning('请输入托盘编号');
return;
}
if (!this.storeyCode.trim()) {
this.$message.warning('请输入层编号');
return;
}
const robotArmCommand = { const robotArmCommand = {
trayCode: this.manualTrayCode, trayCode: this.manualTrayCode,
storeyCode: this.storeyCode, storeyCode: this.storeyCode,
productStandardInspectionNumber: this.productStandardInspectionNumber, productStandardInspectionNumber: this.productStandardInspectionNumber,
}; };
addManualCommand(robotArmCommand).then(res => { addManualCommand(robotArmCommand).then(res => {
if(res.code === 200) { if(res.code === 200) { this.$message.success("手动添加成功"); this.closeManualDialog(); }
this.$message.success("手动添加成功");
this.closeManualDialog();
}
}) })
}, },
initWebSocket() { initWebSocket() {
// 从环境变量获取基础URL,默认使用Nginx代理地址
const backendUrl = process.env.VUE_APP_API_BASE_URL || this.initWebSocketIp; const backendUrl = process.env.VUE_APP_API_BASE_URL || this.initWebSocketIp;
// 根据需要切换不同的WebSocket端点 const wsPath = '/agecal/ws-robot-arm';
const wsPath = '/agecal/ws-robot-arm'; // 或 '/agecal/ws-aging-cabinet'
// 替换协议并添加完整路径
const wsUrl = backendUrl.replace('http', 'ws') + wsPath; const wsUrl = backendUrl.replace('http', 'ws') + wsPath;
try { try {
this.websocket = new WebSocket(wsUrl); this.websocket = new WebSocket(wsUrl);
this.websocket.onopen = () => { this.websocket.onopen = () => {
console.log('机械臂指令WebSocket连接成功'); console.log('机械臂指令WebSocket连接成功');
this.status = 'idle'; this.status = 'idle';
this.sendWebSocketMessage({ type: 'request', commands: ['loading', 'unloading'] }); this.sendWebSocketMessage({ type: 'request', commands: ['loading', 'unloading'] });
}; };
this.websocket.onmessage = (event) => { this.websocket.onmessage = (event) => {
try { try {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
...@@ -484,7 +393,7 @@ export default { ...@@ -484,7 +393,7 @@ export default {
robotArmCommandId: cmd.robotArmCommandId, robotArmCommandId: cmd.robotArmCommandId,
trayCode: cmd.trayCode, trayCode: cmd.trayCode,
position: cmd.storeyCode, position: cmd.storeyCode,
status: cmd.status || '0' // 默认待执行状态 status: cmd.status || '0'
})); }));
} }
else if (message.type === 'unloading') { else if (message.type === 'unloading') {
...@@ -492,41 +401,23 @@ export default { ...@@ -492,41 +401,23 @@ export default {
robotArmCommandId: cmd.robotArmCommandId, robotArmCommandId: cmd.robotArmCommandId,
trayCode: cmd.trayCode, trayCode: cmd.trayCode,
position: cmd.storeyCode, position: cmd.storeyCode,
status: cmd.status || '0' // 默认待执行状态 status: cmd.status || '0'
})); }));
} }
// 新增:处理状态更新消息
else if (message.type === 'status') { else if (message.type === 'status') {
this.status = message.data; // 更新机械臂状态 this.status = message.data;
} }
} catch (e) { } catch (e) { console.error('解析WebSocket消息失败:', e); }
console.error('解析WebSocket消息失败:', e);
}
};
this.websocket.onerror = (error) => {
console.error('WebSocket错误:', error);
this.status = 'error';
this.scheduleReconnect();
}; };
this.websocket.onerror = (error) => { console.error('WebSocket错误:', error); this.status = 'error'; this.scheduleReconnect(); };
this.websocket.onclose = () => { this.websocket.onclose = () => { console.log('WebSocket连接关闭'); this.scheduleReconnect(); };
console.log('WebSocket连接关闭'); } catch (e) { console.error('创建WebSocket失败:', e); this.scheduleReconnect(); }
this.scheduleReconnect();
};
} catch (e) {
console.error('创建WebSocket失败:', e);
this.scheduleReconnect();
}
}, },
sendWebSocketMessage(message) { sendWebSocketMessage(message) {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send(JSON.stringify(message)); this.websocket.send(JSON.stringify(message));
} }
}, },
scheduleReconnect() { scheduleReconnect() {
if (this.reconnectInterval) clearInterval(this.reconnectInterval); if (this.reconnectInterval) clearInterval(this.reconnectInterval);
this.reconnectInterval = setInterval(() => { this.reconnectInterval = setInterval(() => {
...@@ -537,132 +428,116 @@ export default { ...@@ -537,132 +428,116 @@ export default {
} }
}, 5000); }, 5000);
}, },
disconnectWebSocket() { disconnectWebSocket() {
if (this.websocket) { if (this.websocket) { try { this.websocket.close(); } catch (e) { console.error('关闭WebSocket时出错:', e); } }
try { if (this.reconnectInterval) { clearInterval(this.reconnectInterval); this.reconnectInterval = null; }
this.websocket.close();
} catch (e) {
console.error('关闭WebSocket时出错:', e);
}
}
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
}
this.websocket = null; this.websocket = null;
}, },
handleHome() { sendHomeCommand().then(response => { this.$message.success("已发送"); }) },
// 回零 handleStop() { sendStopCommand().then(response => { this.$message.success("已发送"); }) },
handleHome() {
sendHomeCommand().then( response => {
this.$message.success("已发送");
})
},
// 停止
handleStop() {
sendStopCommand().then( response => {
this.$message.success("已发送");
})
},
// 更新状态文本映射
getStatusText(status) { getStatusText(status) {
const statusMap = { const statusMap = { '0': '待分配位置', '1': '待执行', '2': '执行中', '3': '未上电', '4': '执行结束', '5': '断电失败' };
'0': '待分配位置',
'1': '待执行',
'2': '执行中',
'3': '未上电',
'4': '执行结束'
};
return statusMap[status] || '未知状态'; return statusMap[status] || '未知状态';
}, },
// 更新状态样式类映射
getCommandStatusClass(status) { getCommandStatusClass(status) {
return { return {
'status-0': status == '0', // 待分配位置 'status-0': status == '0',
'status-1': status == '1', // 待执行 'status-1': status == '1',
'status-2': status == '2', // 执行中 'status-2': status == '2',
'status-3': status == '3', // 未上电 'status-3': status == '3',
'status-4': status == '4' // 执行结束 'status-4': status == '4',
'status-5': status === '5'
}; };
}, },
// ==================== 修改:handleCommandClick 增加对 status === '5' 的处理 ====================
handleCommandClick(cmd) { handleCommandClick(cmd) {
// 只有未上电状态的指令才可点击
if (cmd.status === '3') { if (cmd.status === '3') {
this.selectedCommand = cmd; this.selectedCommand = cmd;
this.showPowerOnDialog = true; this.showPowerOnDialog = true;
} else if(cmd.status === '2') { } else if (cmd.status === '2') {
this.selectedCommand = cmd; this.selectedCommand = cmd;
this.showSureCompleteDialog = true; this.showSureCompleteDialog = true;
} else if (cmd.status === '5') { // 新增:断电失败
this.selectedFailedCommand = cmd;
this.showFixDialog = true;
} }
}, },
closeSureCompleteDialog() { this.showSureCompleteDialog = false; this.selectedCommand = null; },
closeSureCompleteDialog() { closePowerOnDialog() { this.showPowerOnDialog = false; this.selectedCommand = null; },
this.showSureCompleteDialog = false;
this.selectedCommand = null;
},
closePowerOnDialog() {
this.showPowerOnDialog = false;
this.selectedCommand = null;
},
confirmSureComplete() { confirmSureComplete() {
if (!this.selectedCommand) return; if (!this.selectedCommand) return;
sureCompletedCommand(this.selectedCommand.robotArmCommandId).then(res => { sureCompletedCommand(this.selectedCommand.robotArmCommandId).then(res => {
if(res.code === 200) { if(res.code === 200) { this.$message.success("确认完成成功"); this.closeSureCompleteDialog(); }
this.$message.success("确认完成成功"); else { this.$message.error("确认完成失败"); }
this.closeSureCompleteDialog(); }).catch(err => { this.$message.error("确认完成失败"); })
} else {
this.$message.error("确认完成失败");
}
}).catch(err => {
this.$message.error("确认完成失败");
})
}, },
confirmPowerOn() { confirmPowerOn() {
if (!this.selectedCommand) return; if (!this.selectedCommand) return;
powerOnCommand(this.selectedCommand.robotArmCommandId).then(res => { powerOnCommand(this.selectedCommand.robotArmCommandId).then(res => {
if(res.code === 200) { if(res.code === 200) { this.$message.success("上电操作成功"); this.closePowerOnDialog(); }
this.$message.success("上电操作成功"); else { this.$message.error("上电操作失败"); }
this.closePowerOnDialog(); }).catch(err => { this.$message.error("上电操作失败"); });
} else {
this.$message.error("上电操作失败");
}
}).catch(err => {
this.$message.error("上电操作失败");
});
}, },
// 切换优先级
togglePriority() { togglePriority() {
this.priority = this.priority === 'loading' ? 'unloading' : 'loading'; this.priority = this.priority === 'loading' ? 'unloading' : 'loading';
updateInstructionExecutionPriority(this.priority).then(res => { updateInstructionExecutionPriority(this.priority).then(res => {
if(res.code === 200) { if(res.code === 200) { this.$message.success("优先级设置成功"); }
this.$message.success("优先级设置成功"); else { this.$message.error("优先级设置失败"); }
} else {
this.$message.error("优先级设置失败");
}
}) })
// 这里可以添加发送优先级设置到后端的逻辑
console.log(`优先级已切换为: ${this.priorityDisplayText} : this.priority:${this.priority}` ); console.log(`优先级已切换为: ${this.priorityDisplayText} : this.priority:${this.priority}` );
}, },
msgSuccess(msg) { this.$message.success(msg); },
// 消息提示方法 msgError(msg) { this.$message.error(msg); },
msgSuccess(msg) {
this.$message.success(msg); // ==================== 新增:动画暂停/继续 ====================
toggleAnimation() {
const action = this.isAnimating ? '暂停' : '继续';
this.$confirm(`确定要${action}机械臂动作吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
// 用户点击确定:切换动画状态
this.isAnimating = !this.isAnimating;
const actionStr = this.isAnimating ? 'start' : 'stop';
updateAnimationState(actionStr).then(() => {
this.$message.success(`动画已${action}`);
}).catch(() => {
this.$message.error(`操作失败`);
// 如果失败,可恢复状态
this.isAnimating = !this.isAnimating;
});
}).catch(() => {
// 用户点击取消
this.$message.info(`已取消${action}操作`);
});
}, },
// ==================== 新增:断电失败修复对话框相关 ====================
msgError(msg) { closeFixDialog() {
this.$message.error(msg); this.showFixDialog = false;
this.selectedFailedCommand = null;
},
// 修复确认后的处理(您可在此完善具体逻辑)
handleFixConfirm() {
if (!this.selectedFailedCommand) return;
powerFailureResolved(this.selectedFailedCommand.robotArmCommandId).then(res => {
if(res.code === 200) { this.$message.success("断电修复成功"); }
else { this.$message.error("断电修复失败"); }
})
this.$message.success('修复操作已执行(待完善)');
this.closeFixDialog();
} }
} }
}; };
</script> </script>
<style scoped> <style scoped>
/* 新增:暂停动画类,作用于所有动画元素 */
.paused-animation .robotic-arm * {
animation-play-state: paused !important;
}
/* 标题区域样式 */ /* 标题区域样式 */
.panel-title { .panel-title {
position: absolute; position: absolute;
...@@ -1505,6 +1380,13 @@ export default { ...@@ -1505,6 +1380,13 @@ export default {
border: 1px solid rgba(180, 180, 180, 0.5); border: 1px solid rgba(180, 180, 180, 0.5);
} }
.command-item.status-5 {
/* 断电失败 */
background: rgba(80, 0, 0, 0.3);
border: 1px solid rgb(208, 24, 24);
cursor: pointer;
animation: blink-red 1s infinite; /* 保持原未上电动画 */
}
/* 状态文本颜色 */ /* 状态文本颜色 */
.status-0 .cmd-status { .status-0 .cmd-status {
color: #a0a0ff; /* 待分配位置文本色 */ color: #a0a0ff; /* 待分配位置文本色 */
...@@ -1526,6 +1408,9 @@ export default { ...@@ -1526,6 +1408,9 @@ export default {
color: #a0a0a0; /* 执行结束文本色 */ color: #a0a0a0; /* 执行结束文本色 */
} }
.status-5 .cmd-status {
color: #d01818; /* 未上电文本色 */
}
/* 状态文本样式 */ /* 状态文本样式 */
.cmd-status { .cmd-status {
font-size: 12px; font-size: 12px;
......
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