| | |
| | | |
| | | /** |
| | | * 呼叫AGV |
| | | * |
| | | * <p> |
| | | * 重要:此方法只能从 AgvScheduler.callAgv() 定时任务中调用! |
| | | * 所有AGV呼叫请求必须通过定时任务统一处理,确保: |
| | | * 1. 任务按顺序处理,避免并发冲突 |
| | |
| | | return true; // 已达到最大重试次数,不再重试,返回true表示已处理(虽然失败) |
| | | } |
| | | |
| | | // 打印请求信息(包含重试次数) |
| | | // if (currentRetryCount > 0) { |
| | | // log.info("{}呼叫agv搬运(第{}次重试) - 请求地址:{}", namespace, currentRetryCount + 1, url); |
| | | // } else { |
| | | // log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url); |
| | | // } |
| | | // log.info("{}呼叫agv搬运 - 请求参数:{}", namespace, body); |
| | | |
| | | boolean result = false; // 默认返回false,表示未成功处理 |
| | | try { |
| | | // 使用仙工M4接口 |
| | |
| | | .build() |
| | | .doPost(); |
| | | // 打印返回参数 |
| | | log.info("{}呼叫agv搬运,请求参数「{}」 - 返回参数:{}", namespace,body, response); |
| | | log.info("仙工-{}呼叫agv搬运,请求参数「{}」 - 返回参数:{}", namespace, body, response); |
| | | |
| | | // 检查响应是否为空 |
| | | if (response == null || response.trim().isEmpty()) { |
| | | String errorMsg = "AGV接口返回为空"; |
| | | log.warn("定时任务:{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, displayTaskId, errorMsg); |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | // 如果达到最大重试次数,返回true表示已处理(虽然失败) |
| | | // 否则返回false,让定时任务重新尝试 |
| | | if (retryEnabled && currentRetryCount >= maxRetryCount) { |
| | | result = true; // 已达到最大重试次数,返回true表示已处理(虽然失败) |
| | | log.info("定时任务:任务ID:{},AGV呼叫失败且已达到最大重试次数({}次),标记为已处理,不再重试", |
| | | displayTaskId, maxRetryCount); |
| | | } else { |
| | | result = false; // 返回false,让定时任务重新尝试 |
| | | log.info("定时任务:任务ID:{},下次将重新尝试发送AGV命令", displayTaskId); |
| | | } |
| | | task.setWrkSts(10L); |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(String.format("AGV接口返回为空")); |
| | | taskService.updateById(task); |
| | | } else { |
| | | // 尝试解析JSON响应,捕获JSON解析异常 |
| | | JSONObject jsonObject = null; |
| | |
| | | // JSON解析失败,响应可能不是有效的JSON格式(如"Server Error"等) |
| | | String errorMsg = String.format("AGV接口返回非JSON格式响应,响应内容:%s,解析错误:%s", response, e.getMessage()); |
| | | log.error("定时任务:{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, displayTaskId, errorMsg); |
| | | |
| | | // 服务器错误时,标记站点为不可用,清空站点分配,不再为当前任务分配站点 |
| | | try { |
| | | Integer siteNo = Integer.parseInt(staNo); |
| | | // 查询站点信息 |
| | | List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo)); |
| | | if (basDevpList != null && !basDevpList.isEmpty()) { |
| | | BasDevp basDevp = basDevpList.get(0); |
| | | // 标记站点为不可用(设置canining='N') |
| | | basDevp.setCanining("N"); |
| | | basDevpMapper.updateById(basDevp); |
| | | log.warn("定时任务:任务ID:{},AGV接口返回服务器错误,已标记站点{}为不可用(canining='N')", |
| | | displayTaskId, siteNo); |
| | | |
| | | // 减少站点的入库任务数(之前分配站点时已经增加了in_qty) |
| | | basDevpMapper.decrementInQty(siteNo); |
| | | log.debug("定时任务:任务ID:{},站点{}的in_qty已减少", displayTaskId, siteNo); |
| | | } |
| | | } catch (Exception ex) { |
| | | log.error("定时任务:任务ID:{},标记站点{}为不可用时发生异常:{}", displayTaskId, staNo, ex.getMessage()); |
| | | } |
| | | |
| | | // 清空当前任务的站点分配 |
| | | log.warn("定时任务:任务ID:{},AGV接口返回服务器错误,清空站点分配:{},不再为当前任务分配站点", |
| | | displayTaskId, staNo); |
| | | task.setStaNo(null); |
| | | taskService.updateById(task); |
| | | |
| | | // 标记任务为失败,不再尝试分配 |
| | | task.setWrkSts(10L); |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(String.format("AGV接口返回服务器错误,站点已标记为不可用:%s", errorMsg)); |
| | | task.setErrorMemo(errorMsg); |
| | | taskService.updateById(task); |
| | | |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | // 服务器错误时,不再尝试分配,直接标记为已处理 |
| | | result = true; // 返回true表示已处理(虽然失败),不再尝试分配 |
| | | log.info("定时任务:任务ID:{},AGV呼叫失败(服务器错误),站点已标记为不可用,任务已标记为失败,不再尝试分配", |
| | | displayTaskId); |
| | | } |
| | | |
| | | // 如果JSON解析成功,继续处理 |
| | |
| | | Long currentStatus = task.getWrkSts(); |
| | | if (currentStatus == null || currentStatus != 8L) { |
| | | task.setWrkSts(8L); |
| | | |
| | | log.info("定时任务:{}呼叫agv搬运成功 - 任务ID:{},状态从{}更新为8", namespace, displayTaskId, currentStatus); |
| | | } else { |
| | | log.info("定时任务:{}呼叫agv搬运成功(重试) - 任务ID:{},状态保持为8", namespace, displayTaskId); |
| | | } |
| | | task.setLocNo(task.getStaNo()); |
| | | task.setMemo(clearRetryInfo(task.getMemo())); // 清除重试信息 |
| | | task.setErrorTime(null); |
| | | task.setErrorMemo(null); |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(jsonObject.toJSONString()); |
| | | taskService.updateById(task); |
| | | log.info("定时任务:{}呼叫agv搬运成功 - 任务ID:{}", namespace, displayTaskId); |
| | | result = true; // 返回true,表示成功处理 |
| | |
| | | String errorMsg = String.format("错误码:%s,错误信息:%s", code, message); |
| | | log.warn("定时任务:{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, displayTaskId, errorMsg); |
| | | |
| | | // 检查是否是站点或库位相关的错误,如果是,清空站点分配,让定时任务重新分配 |
| | | boolean shouldReallocateSite = false; |
| | | if (message != null) { |
| | | String lowerMessage = message.toLowerCase(); |
| | | // 库位不存在、站点不存在等错误,应该重新分配站点 |
| | | if (lowerMessage.contains("库位不存在") || |
| | | lowerMessage.contains("站点不存在") || |
| | | lowerMessage.contains("位置不存在") || |
| | | lowerMessage.contains("库位无效") || |
| | | lowerMessage.contains("站点无效")) { |
| | | shouldReallocateSite = true; |
| | | } |
| | | } |
| | | |
| | | if (shouldReallocateSite) { |
| | | // 清空站点分配,让定时任务重新分配站点 |
| | | log.warn("定时任务:任务ID:{},AGV呼叫失败({}),清空站点分配:{},下次将重新分配站点", |
| | | displayTaskId, errorMsg, staNo); |
| | | task.setStaNo(null); |
| | | task.setWrkSts(10L); |
| | | task.setLocNo(task.getStaNo()); |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(jsonObject.toJSONString()); |
| | | taskService.updateById(task); |
| | | } |
| | | |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | |
| | | // 如果达到最大重试次数,返回true表示已处理(虽然失败) |
| | | // 否则返回false,让定时任务重新尝试(如果站点被清空,会重新分配站点;如果站点未清空,会重新发送AGV) |
| | | if (retryEnabled && currentRetryCount >= maxRetryCount) { |
| | | result = true; // 已达到最大重试次数,返回true表示已处理(虽然失败) |
| | | log.info("定时任务:任务ID:{},AGV呼叫失败且已达到最大重试次数({}次),标记为已处理,不再重试", |
| | | displayTaskId, maxRetryCount); |
| | | } else { |
| | | result = false; // 返回false,让定时任务重新尝试(重新分配站点或重新发送AGV) |
| | | if (shouldReallocateSite) { |
| | | log.info("定时任务:任务ID:{},站点已清空,下次将重新分配站点", displayTaskId); |
| | | } else { |
| | | log.info("定时任务:任务ID:{},下次将重新尝试发送AGV命令", displayTaskId); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | String errorMsg = "异常信息:" + e.getMessage(); |
| | | log.error("定时任务:{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}", |
| | | namespace, displayTaskId, url, body, errorMsg, e); |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | // 如果达到最大重试次数,返回true表示已处理(虽然失败) |
| | | // 否则返回false,让定时任务重新尝试 |
| | | if (retryEnabled && currentRetryCount >= maxRetryCount) { |
| | | result = true; // 已达到最大重试次数,返回true表示已处理(虽然失败) |
| | | log.info("定时任务:任务ID:{},AGV呼叫异常且已达到最大重试次数({}次),标记为已处理,不再重试", |
| | | displayTaskId, maxRetryCount); |
| | | if (errorMsg.contains("connect timed out")) { |
| | | log.error("定时任务:{}呼叫agv搬运异常 - 任务ID:{},网络请求超时", |
| | | namespace, displayTaskId); |
| | | } else { |
| | | result = false; // 返回false,让定时任务重新尝试 |
| | | log.info("定时任务:任务ID:{},下次将重新尝试发送AGV命令", displayTaskId); |
| | | task.setWrkSts(10L); |
| | | task.setLocNo(task.getStaNo()); |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(errorMsg); |
| | | taskService.updateById(task); |
| | | } |
| | | } finally { |
| | | try { |
| | |
| | | |
| | | /** |
| | | * 处理AGV呼叫失败的情况 |
| | | * |
| | | * @param task 任务对象 |
| | | * @param namespace 命名空间(入库/出库/转移) |
| | | * @param errorMsg 错误信息 |
| | |
| | | /** |
| | | * 检查并自动结束已完成工作档的AGV任务 |
| | | * 如果任务对应的工作档已经完成(入库成功),则自动结束该AGV任务 |
| | | * |
| | | * @param transportingTasks 正在搬运的任务列表 |
| | | * @param taskTypeName 任务类型名称(用于日志) |
| | | * @return 仍然有效的正在搬运的任务列表(已完成的已被移除) |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 从memo字段中获取重试次数 |
| | | * memo格式:如果包含"retryCount:数字",则返回该数字,否则返回0 |
| | | * |
| | | * @param task 任务对象 |
| | | * @return 重试次数 |
| | | */ |
| | |
| | | |
| | | /** |
| | | * 更新memo字段中的重试次数 |
| | | * |
| | | * @param memo 原始memo内容 |
| | | * @param retryCount 新的重试次数 |
| | | * @return 更新后的memo内容 |
| | |
| | | |
| | | /** |
| | | * 清除memo字段中的重试信息 |
| | | * |
| | | * @param memo 原始memo内容 |
| | | * @return 清除后的memo内容 |
| | | */ |
| | |
| | | /** |
| | | * 为任务分配站点(定时任务中调用) |
| | | * 注意:只会分配一个站点,找到第一个符合条件的站点就分配并退出 |
| | | * |
| | | * @param task 任务对象 |
| | | * @return 如果无法分配站点,返回错误信息;如果分配成功,返回null并更新task的staNo |
| | | */ |
| | |
| | | displayTaskId, groupKey.equals("east") ? agvProperties.getEastDisplayName() : agvProperties.getWestDisplayName(), |
| | | targetStations, targetStations.size()); |
| | | |
| | | // 判断能入站点(in_enable="Y"表示能入),排除dev_no=0的无效站点 |
| | | List<BasDevp> allDevList = basDevpMapper.selectList( |
| | | new EntityWrapper<BasDevp>() |
| | | .in("dev_no", siteIntList) |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ); |
| | | // 记录所有站点的状态信息 |
| | | StringBuilder siteStatusInfo = new StringBuilder(); |
| | | for (BasDevp dev : allDevList) { |
| | | if (siteStatusInfo.length() > 0) { |
| | | siteStatusInfo.append("; "); |
| | | } |
| | | siteStatusInfo.append("站点").append(dev.getDevNo()) |
| | | .append("(in_enable=").append(dev.getInEnable()) |
| | | .append(",canining=").append(dev.getCanining()).append(")"); |
| | | } |
| | | log.info("任务ID:{},候选站点状态:{}", displayTaskId, siteStatusInfo.toString()); |
| | | |
| | | List<Integer> sites = allDevList.stream() |
| | | .filter(dev -> "Y".equals(dev.getInEnable())) |
| | | .map(BasDevp::getDevNo) |
| | | .filter(devNo -> devNo != null && devNo != 0) // 再次过滤,确保不为null或0 |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 检查是否有站点不可用,如果有,说明需要在可用的站点之间平均分配 |
| | | List<Integer> unavailableSites = new ArrayList<>(siteIntList); |
| | | unavailableSites.removeAll(sites); |
| | | if (!unavailableSites.isEmpty()) { |
| | | log.info("任务ID:{},{}站点组中有{}个站点不可用(in_enable!='Y'):{},将在{}个可用站点之间平均分配", |
| | | displayTaskId, groupKey.equals("east") ? agvProperties.getEastDisplayName() : agvProperties.getWestDisplayName(), |
| | | unavailableSites.size(), unavailableSites, sites.size()); |
| | | } |
| | | |
| | | if (sites.isEmpty()) { |
| | | String errorMsg = "没有能入站点(in_enable='Y')"; |
| | | log.warn("任务ID:{},{},候选站点列表:{},站点状态:{}", displayTaskId, errorMsg, targetStations, siteStatusInfo.toString()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 先检查站点配置(canining="Y"可入),排除dev_no=0的无效站点 |
| | | List<BasDevp> devListWithConfig = basDevpMapper.selectList(new EntityWrapper<BasDevp>() |
| | | .in("dev_no", sites) |
| | | .in("dev_no", siteIntList) |
| | | .eq("in_enable", "Y") |
| | | .eq("canining", "Y") |
| | | .eq("loading", "N") |
| | |
| | | ); |
| | | |
| | | if (devListWithConfig==null || devListWithConfig.isEmpty()) { |
| | | log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通。能入站点列表:{}", |
| | | displayTaskId, sites); |
| | | log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通。站点列表:{}", |
| | | displayTaskId, targetStations); |
| | | return null; |
| | | } |
| | | |
| | |
| | | log.info("任务ID:{}已分配站点:{},机器人组:{},任务类型:{}", displayTaskId, endSite, robotGroup, taskTypeName); |
| | | return null; // 分配成功,返回null |
| | | } |
| | | |
| | | /** |
| | | * 根据站点编号判断机器人组 |
| | | * |
| | | * @param staNo 站点编号 |
| | | * @return 机器人组名称 |
| | | */ |
| | |
| | | |
| | | log.info("agv任务档转历史成功:{}", taskIds); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void moveTaskToHistory(Task agvTask) { |
| | | moveTaskToHistory(Collections.singletonList(agvTask)); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 货物到达出库口,生成agv任务 |
| | | */ |
| | |
| | | |
| | | /** |
| | | * 取消AGV任务(仙工M4接口) |
| | | * |
| | | * @param task 任务对象 |
| | | * @return 是否成功 |
| | | */ |