| | |
| | | .in("wrk_sts", 7L, 8L) |
| | | .in("io_type", ioTypes) |
| | | .ne("id", task.getId()) // 排除当前任务本身 |
| | | .andNew("(is_deleted = 0)") |
| | | .eq("is_deleted", 0) // 排除已删除的任务 |
| | | ); |
| | | |
| | | int taskCount = allTasks != null ? allTasks.size() : 0; |
| | |
| | | .isNotNull("plc_str_time") // 只检查已收到AGV确认的任务(plc_str_time不为空) |
| | | .in("io_type", ioTypes) |
| | | .ne("id", task.getId()) // 排除当前任务本身 |
| | | .andNew("(is_deleted = 0)") |
| | | .eq("is_deleted", 0) // 排除已删除的任务 |
| | | ); |
| | | |
| | | // 检查并自动结束已完成工作档的AGV任务 |
| | |
| | | .build() |
| | | .doPost(); |
| | | // 打印返回参数 |
| | | // log.info("{}呼叫agv搬运 - 返回参数:{}", namespace, response); |
| | | log.info("{}呼叫agv搬运,请求参数「{}」 - 返回参数:{}", namespace,body, response); |
| | | |
| | | // 检查响应是否为空 |
| | | if (response == null || response.trim().isEmpty()) { |
| | |
| | | * @param taskTypeName 任务类型名称(用于日志) |
| | | * @return 仍然有效的正在搬运的任务列表(已完成的已被移除) |
| | | */ |
| | | private List<Task> checkAndCompleteFinishedTasks(List<Task> transportingTasks, String taskTypeName) { |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public List<Task> checkAndCompleteFinishedTasks(List<Task> transportingTasks, String taskTypeName) { |
| | | if (transportingTasks == null || transportingTasks.isEmpty()) { |
| | | return transportingTasks; |
| | | } |
| | |
| | | return validTasks; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 从memo字段中获取重试次数 |
| | | * memo格式:如果包含"retryCount:数字",则返回该数字,否则返回0 |
| | |
| | | /** |
| | | * 构造请求内容(仙工M4格式) |
| | | */ |
| | | private String getRequest(Task task, String nameSpace) { |
| | | public String getRequest(Task task, String nameSpace) { |
| | | JSONObject object = new JSONObject(); |
| | | // taskId使用工作号(wrk_no),格式:T + 工作号 |
| | | // 如果工作号为空,则使用任务ID作为备选 |
| | |
| | | return object.toJSONString(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 为任务分配站点(定时任务中调用) |
| | | * 注意:只会分配一个站点,找到第一个符合条件的站点就分配并退出 |
| | |
| | | * @return 如果无法分配站点,返回错误信息;如果分配成功,返回null并更新task的staNo |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String allocateSiteForTask(Task task) { |
| | | public String allocateSiteForTask(Task task) { |
| | | // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID |
| | | String displayTaskId = (task.getWrkNo() != null) ? String.valueOf(task.getWrkNo()) : String.valueOf(task.getId()); |
| | | log.debug("开始为任务ID:{}分配站点,任务类型:{},机器人组:{}", |
| | | log.debug("开始为任务ID:{}分配站点,任务类型:{},机器人组:{}", |
| | | displayTaskId, task.getIoType(), task.getInvWh()); |
| | | // 根据任务的invWh(机器人组)判断是东侧还是西侧 |
| | | String robotGroup = task.getInvWh(); |
| | | List<String> targetStations; |
| | | String groupKey; |
| | | |
| | | |
| | | if (robotGroup != null && robotGroup.equals(agvProperties.getRobotGroupEast())) { |
| | | // 东侧站点 |
| | | targetStations = agvProperties.getEastStations(); |
| | |
| | | groupKey = "east"; |
| | | log.warn("任务ID:{}的机器人组{}未识别,使用默认东侧站点", displayTaskId, robotGroup); |
| | | } |
| | | |
| | | |
| | | if (targetStations.isEmpty()) { |
| | | String errorMsg = "没有可用的目标站点配置"; |
| | | log.warn("任务ID:{},{}", displayTaskId, errorMsg); |
| | | return errorMsg; |
| | | } |
| | | |
| | | |
| | | // 将站点字符串列表转换为整数列表 |
| | | List<Integer> siteIntList = targetStations.stream() |
| | | .map(Integer::parseInt) |
| | | .collect(Collectors.toList()); |
| | | |
| | | log.info("任务ID:{},{}站点组配置的站点:{},共{}个站点", |
| | | |
| | | log.info("任务ID:{},{}站点组配置的站点:{},共{}个站点", |
| | | 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>() |
| | |
| | | .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'):{},将在{}个可用站点之间平均分配", |
| | | 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) |
| | | .eq("in_enable", "Y") |
| | | .eq("canining", "Y") |
| | | .eq("loading", "N") |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ); |
| | | |
| | | if (devListWithConfig.isEmpty()) { |
| | | // 站点配置不允许入库(canining != "Y"),暂不分配,等待配置开通(只在定时任务中记录日志) |
| | | // 记录每个站点的canining状态 |
| | | StringBuilder caniningStatusInfo = new StringBuilder(); |
| | | for (Integer siteNo : sites) { |
| | | BasDevp dev = allDevList.stream() |
| | | .filter(d -> d.getDevNo().equals(siteNo)) |
| | | .findFirst() |
| | | .orElse(null); |
| | | if (dev != null) { |
| | | if (caniningStatusInfo.length() > 0) { |
| | | caniningStatusInfo.append("; "); |
| | | } |
| | | caniningStatusInfo.append("站点").append(siteNo) |
| | | .append("(canining=").append(dev.getCanining()).append(")"); |
| | | } |
| | | } |
| | | log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通。能入站点列表:{},canining状态:{}", |
| | | displayTaskId, sites, caniningStatusInfo.toString()); |
| | | return null; // 返回null,表示暂不分配,等待配置开通 |
| | | |
| | | if (devListWithConfig==null || devListWithConfig.isEmpty()) { |
| | | log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通。能入站点列表:{}", |
| | | displayTaskId, sites); |
| | | return null; |
| | | } |
| | | |
| | | // 获取没有出库任务的站点(从已配置可入的站点中筛选) |
| | | List<Integer> configuredSites = devListWithConfig.stream() |
| | | .map(BasDevp::getDevNo) |
| | | .collect(Collectors.toList()); |
| | | log.info("任务ID:{},已配置可入站点列表:{}", displayTaskId, configuredSites); |
| | | List<Integer> canInSites = basDevpMapper.getCanInSites(configuredSites); |
| | | if (canInSites.isEmpty()) { |
| | | // 所有已配置可入的站点都有出库任务,暂不分配,等待下次定时任务再尝试(只在定时任务中记录日志) |
| | | log.warn("任务ID:{}没有可入站点(请等待出库完成),暂不分配站点,等待下次定时任务再尝试。已配置可入站点列表:{}", displayTaskId, configuredSites); |
| | | return null; // 返回null,表示暂不分配,等待下次定时任务再尝试 |
| | | } |
| | | log.info("任务ID:{},没有出库任务的站点列表:{}", displayTaskId, canInSites); |
| | | |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入),排除dev_no=0的无效站点 |
| | | List<BasDevp> devList = basDevpMapper.selectList(new EntityWrapper<BasDevp>() |
| | | .in("dev_no", canInSites) |
| | | .eq("in_enable", "Y") |
| | | .eq("canining", "Y") |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ).stream() |
| | | .filter(dev -> dev.getDevNo() != null && dev.getDevNo() != 0) // 再次过滤,确保不为null或0 |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (devList.isEmpty()) { |
| | | // 理论上不应该到这里,因为前面已经检查过了,但为了安全起见还是保留 |
| | | String errorMsg = "没有可入站点(in_enable='Y'且canining='Y')"; |
| | | log.warn("任务ID:{},{},可入站点列表:{}", displayTaskId, errorMsg, canInSites); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 记录每个站点的入库任务数 |
| | | StringBuilder siteInQtyInfo = new StringBuilder(); |
| | | for (BasDevp dev : devList) { |
| | | if (siteInQtyInfo.length() > 0) { |
| | | siteInQtyInfo.append("; "); |
| | | } |
| | | siteInQtyInfo.append("站点").append(dev.getDevNo()) |
| | | .append("(入库任务数=").append(dev.getInQty()).append(")"); |
| | | } |
| | | log.info("任务ID:{},可入站点及其入库任务数:{}", displayTaskId, siteInQtyInfo.toString()); |
| | | |
| | | // 先按规则排序(入库任务数排序) |
| | | devList.sort(Comparator.comparing(BasDevp::getInQty)); |
| | | |
| | | |
| | | // 先按规则排序(入库任务数排序) |
| | | devListWithConfig.sort(Comparator.comparing(BasDevp::getInQty)); |
| | | // 根据任务类型确定要检查的io_type列表 |
| | | Integer taskIoType = task.getIoType(); |
| | | List<Integer> checkIoTypes = null; |
| | |
| | | taskTypeName = "出库"; |
| | | } |
| | | } |
| | | |
| | | |
| | | // 先查询agv工作档中未被分配站点的站点 |
| | | // 查询agv工作档中所有已分配站点的任务(sta_no不为空、不为空字符串、不为0) |
| | | final List<String> allocatedSiteNos; |
| | |
| | | } else { |
| | | allocatedSiteNos = new ArrayList<>(); |
| | | } |
| | | |
| | | |
| | | // 从可用站点中筛选出未被分配的站点 |
| | | List<BasDevp> unallocatedSites = devList.stream() |
| | | List<BasDevp> unallocatedSites = devListWithConfig.stream() |
| | | .filter(dev -> { |
| | | String staNo = String.valueOf(dev.getDevNo()); |
| | | return !allocatedSiteNos.contains(staNo); |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | |
| | | List<BasDevp> unallocatedSites2= new ArrayList<>(); |
| | | for(int i=0;devListWithConfig.size()>i;i++){ |
| | | unallocatedSites2.add(devListWithConfig.get(i)); |
| | | } |
| | | // if(unallocatedSites==null || unallocatedSites.isEmpty()){ |
| | | // unallocatedSites=unallocatedSites2; |
| | | // |
| | | // } |
| | | // 只使用未分配站点 |
| | | if (unallocatedSites.isEmpty()) { |
| | | // 未分配站点为空:不分配站点 |
| | |
| | | } |
| | | allocatedSitesInfo.append("站点").append(staNo).append("已被分配"); |
| | | } |
| | | log.warn("任务ID:{},所有可用站点都已被分配,暂不分配站点,等待下次定时任务再尝试。已分配站点:{}", |
| | | log.warn("任务ID:{},所有可用站点都已被分配,暂不分配站点,等待下次定时任务再尝试。已分配站点:{}", |
| | | displayTaskId, allocatedSitesInfo.length() > 0 ? allocatedSitesInfo.toString() : "无详细信息"); |
| | | return null; // 返回null,表示暂不分配,等待下次定时任务再尝试 |
| | | } |
| | | |
| | | |
| | | // 存在未分配站点:根据配置的分配策略选择具体站点 |
| | | // 先按规则排序(入库任务数排序) |
| | | unallocatedSites.sort(Comparator.comparing(BasDevp::getInQty)); |
| | | |
| | | |
| | | // 根据配置选择分配策略,确定优先分配的站点顺序 |
| | | List<BasDevp> orderedSites = new ArrayList<>(); |
| | | String strategy = agvProperties.getSiteAllocation().getStrategy(); |
| | | boolean enableRoundRobin = agvProperties.getSiteAllocation().isEnableRoundRobin(); |
| | | |
| | | |
| | | // 记录是否使用轮询策略,以及轮询计数器(用于在成功分配站点后递增) |
| | | AtomicInteger roundRobinCounter = null; |
| | | int roundRobinStartIndex = 0; |
| | |
| | | // 将轮询选中的站点放在最前面 |
| | | orderedSites.addAll(unallocatedSites.subList(roundRobinStartIndex, unallocatedSites.size())); |
| | | orderedSites.addAll(unallocatedSites.subList(0, roundRobinStartIndex)); |
| | | log.info("任务ID:{},使用轮询分配策略,站点组:{},轮询起始索引:{},候选站点:{}(共{}个未分配站点)", |
| | | displayTaskId, groupKey, roundRobinStartIndex, |
| | | unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")), |
| | | log.info("任务ID:{},使用轮询分配策略,站点组:{},轮询起始索引:{},候选站点:{}(共{}个未分配站点)", |
| | | displayTaskId, groupKey, roundRobinStartIndex, |
| | | unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")), |
| | | unallocatedSites.size()); |
| | | } else if (unallocatedSites.size() > 1 && enableRoundRobin && "random".equals(strategy)) { |
| | | // 随机分配:先随机排序未分配站点 |
| | | List<BasDevp> shuffledSites = new ArrayList<>(unallocatedSites); |
| | | Collections.shuffle(shuffledSites); |
| | | orderedSites.addAll(shuffledSites); |
| | | log.info("任务ID:{},使用随机分配策略,候选站点:{}", |
| | | log.info("任务ID:{},使用随机分配策略,候选站点:{}", |
| | | displayTaskId, unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(","))); |
| | | } else { |
| | | // 默认:按入库任务数排序(已经排序好了) |
| | | orderedSites = unallocatedSites; |
| | | } |
| | | |
| | | |
| | | // 既然已经筛选出了未分配站点,直接根据分配策略选择第一个站点即可 |
| | | // 未分配站点在AGV工作档中都没有已分配的任务,可以直接分配 |
| | | BasDevp selectedSite = orderedSites.get(0); |
| | | Integer endSite = selectedSite.getDevNo(); |
| | | String staNo = String.valueOf(endSite); |
| | | |
| | | log.info("任务ID:{},从{}个未分配站点中选择站点{}(入库任务数:{}),候选站点:{}", |
| | | |
| | | log.info("任务ID:{},从{}个未分配站点中选择站点{}(入库任务数:{}),候选站点:{}", |
| | | displayTaskId, orderedSites.size(), staNo, selectedSite.getInQty(), |
| | | orderedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(","))); |
| | | |
| | | |
| | | // 如果使用轮询策略且成功分配站点,递增轮询计数器(确保下次从下一个站点开始) |
| | | if (roundRobinCounter != null && unallocatedSites.size() > 1) { |
| | | roundRobinCounter.getAndIncrement(); |
| | | log.debug("任务ID:{}成功分配到站点{},轮询计数器已递增,下次将从下一个站点开始轮询", displayTaskId, staNo); |
| | | } |
| | | |
| | | |
| | | // 检查站点是否有效(不能为0或null) |
| | | if (endSite == null || endSite == 0) { |
| | | String errorMsg = String.format("分配的站点无效(dev_no=%s)", endSite); |
| | | log.error("任务ID:{},{}", displayTaskId, errorMsg); |
| | | return errorMsg; |
| | | } |
| | | |
| | | |
| | | // 入库暂存+1 |
| | | basDevpMapper.incrementInQty(endSite); |
| | | |
| | | |
| | | // 更新任务的站点编号,并确保状态为7(待呼叫AGV) |
| | | task.setStaNo(String.valueOf(endSite)); |
| | | if (task.getWrkSts() == null || task.getWrkSts() != 7L) { |
| | |
| | | log.debug("任务ID:{}分配站点时,状态不是7,已更新为7(待呼叫AGV)", displayTaskId); |
| | | } |
| | | taskService.updateById(task); |
| | | |
| | | |
| | | log.info("任务ID:{}已分配站点:{},机器人组:{},任务类型:{}", displayTaskId, endSite, robotGroup, taskTypeName); |
| | | return null; // 分配成功,返回null |
| | | } |
| | | |
| | | /** |
| | | * 根据站点编号判断机器人组 |
| | | * @param staNo 站点编号 |
| | |
| | | return agvProperties.getRobotGroupEast(); // 默认使用东侧机器人组 |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 任务完成转历史 释放暂存点 |
| | |
| | | |
| | | log.info("agv任务档转历史成功:{}", taskIds); |
| | | } |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void moveTaskToHistory(Task agvTask) { |
| | | moveTaskToHistory(Collections.singletonList(agvTask)); |
| | | |
| | | } |
| | | /** |
| | | * 货物到达出库口,生成agv任务 |
| | | */ |