From e58b6ce7c4611c1418a3594339fb92facf28a95c Mon Sep 17 00:00:00 2001 From: liuhanqing Date: Fri, 21 Nov 2025 14:25:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E8=BD=A6=E8=B0=83=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/common/domain/SendData2.java | 37 + .../com/ruoyi/device/ddj/domain/MsgDdj.java | 4 + .../schedule/plcRead/ddj/PlcDdjRead.java | 13 +- .../schedule/plcRead/srgv/PlcSrgvRead.java | 22 +- .../srgv/roadUtil/MapImageGenerator.java | 92 ++ .../plcRead/srgv/roadUtil/MatrixModifier.java | 94 ++ .../plcRead/srgv/roadUtil/PathFinder.java | 901 ++++++++++++++++++ .../schedule/plcRead/srgv/roadUtil/Site.java | 41 + .../scheduleTask/srgv/BaseSRgvThread.java | 306 +++++- 9 files changed, 1484 insertions(+), 26 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/common/domain/SendData2.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MapImageGenerator.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MatrixModifier.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/PathFinder.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/Site.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/common/domain/SendData2.java b/ruoyi-admin/src/main/java/com/ruoyi/common/domain/SendData2.java new file mode 100644 index 0000000..9f2ca36 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/common/domain/SendData2.java @@ -0,0 +1,37 @@ +package com.ruoyi.common.domain; + + + +public class SendData2 { + + + private String address; + + private String data ; + + private int type;//1单字 2双字 3字符串 + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/device/ddj/domain/MsgDdj.java b/ruoyi-admin/src/main/java/com/ruoyi/device/ddj/domain/MsgDdj.java index 7241b93..cf45ba6 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/device/ddj/domain/MsgDdj.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/device/ddj/domain/MsgDdj.java @@ -102,6 +102,10 @@ public class MsgDdj extends BaseEntity private String plcDbReadAddress; + private String ip; + + private String port; + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/ddj/PlcDdjRead.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/ddj/PlcDdjRead.java index 6da78b8..e0efa7f 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/ddj/PlcDdjRead.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/ddj/PlcDdjRead.java @@ -20,15 +20,15 @@ import java.util.concurrent.ConcurrentHashMap; public class PlcDdjRead implements Runnable { public static ConcurrentHashMap deviceDdjMap; public volatile boolean isRun = true; - + public MsgDdj msgDdj; public SiemensS7Net siemensS7Net = null; public static IReadWriteNet readWriteNet = null; private final String ip; private final int port; private boolean success = false; - public PlcDdjRead(ConcurrentHashMap deviceDdjMap, PlcConnectInfo plcConnectInfo) { - PlcDdjRead.deviceDdjMap = deviceDdjMap; + public PlcDdjRead(MsgDdj msgDdj, PlcConnectInfo plcConnectInfo) { + this.msgDdj = msgDdj; this.ip = plcConnectInfo.getIp(); this.port = plcConnectInfo.getPort(); } @@ -46,12 +46,7 @@ public class PlcDdjRead implements Runnable { while (isRun) { try { if (IsConnect()) { - for (String key : deviceDdjMap.keySet()) { - MsgDdj msgDdj = deviceDdjMap.get(key); - if (msgDdj != null) { - readPlcDdj(msgDdj); - } - } + readPlcDdj(msgDdj); Thread.sleep(1000); } else { reConnect(); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java index e68aa56..9614276 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java @@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentHashMap; public class PlcSrgvRead implements Runnable { public static ConcurrentHashMap deviceSrgvMap; - private ModbusTcpNet modbusTcpNet; + private static ModbusTcpNet modbusTcpNet; public static IReadWriteNet readWriteNet = null; public volatile boolean isRun = true; private final String ip; @@ -36,12 +36,18 @@ public class PlcSrgvRead implements Runnable { this.station = (byte) 1; } - public static int WriteToSSJ(String address, byte[] data) { - int a = 0; - if (readWriteNet != null) { - a = HslRead.WriteResultRender(readWriteNet.Write(address, data), address); - } - return a; + public static boolean Write(String address, int[] value){ + + OperateResult write = modbusTcpNet.Write(address, value); + return write.IsSuccess; + + } + + public static boolean Write(String address, short[] value){ + + OperateResult write = modbusTcpNet.Write(address, value); + return write.IsSuccess; + } @@ -191,4 +197,6 @@ public class PlcSrgvRead implements Runnable { } setIsConnect(false); } + + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MapImageGenerator.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MapImageGenerator.java new file mode 100644 index 0000000..8b45f9a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MapImageGenerator.java @@ -0,0 +1,92 @@ +package com.ruoyi.schedule.plcRead.srgv.roadUtil; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class MapImageGenerator { + + // 定义颜色常量 + private static final Color PASSABLE_COLOR = Color.GREEN; + private static final Color PUBLIC_PATH_COLOR = Color.YELLOW; + private static final Color OBSTACLE_COLOR = Color.BLACK; + private static final Color PATH_COLOR = Color.RED; + private static final Color INFLECTION_POINT_COLOR = Color.BLUE; + + // 每个单元格的大小(像素) + private static final int CELL_SIZE = 10; + + /** + * 根据二维数组生成地图图片,并标记路径和拐点 + * + * @param grid 二维数组表示的地图 + * @param path 路径坐标列表 + * @param inflections 拐点坐标列表 + * @param outputFilePath 输出图片文件路径 + */ + public static void generateMapImage(int[][] grid, List path, List inflections, String outputFilePath) { + int rows = grid.length; + int cols = grid[0].length; + + int width = cols * CELL_SIZE; + int height = rows * CELL_SIZE; + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + + // 填充背景 + g.setColor(Color.WHITE); + g.fillRect(0, 0, width, height); + + // 绘制地图 + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + int value = grid[i][j]; + Color color; + + if (value == 1) { + color = PASSABLE_COLOR; + } else if (value == 2) { + color = PUBLIC_PATH_COLOR; + } else { + color = OBSTACLE_COLOR; + } + + g.setColor(color); + g.fillRect(j * CELL_SIZE, i * CELL_SIZE, CELL_SIZE, CELL_SIZE); + } + } + + // 绘制路径 + if (path != null && !path.isEmpty()) { + g.setColor(PATH_COLOR); + for (int[] coord : path) { + int x = coord[1]; + int y = coord[0]; + g.fillRect(x * CELL_SIZE + 2, y * CELL_SIZE + 2, CELL_SIZE - 4, CELL_SIZE - 4); + } + } + + // 绘制拐点 + if (inflections != null && !inflections.isEmpty()) { + g.setColor(INFLECTION_POINT_COLOR); + for (int[] coord : inflections) { + int x = coord[1]; + int y = coord[0]; + g.fillOval(x * CELL_SIZE + 2, y * CELL_SIZE + 2, CELL_SIZE - 4, CELL_SIZE - 4); + } + } + + // 保存图片 + try { + ImageIO.write(image, "png", new File(outputFilePath)); + } catch (IOException e) { + e.printStackTrace(); + } finally { + g.dispose(); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MatrixModifier.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MatrixModifier.java new file mode 100644 index 0000000..197e676 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/MatrixModifier.java @@ -0,0 +1,94 @@ +package com.ruoyi.schedule.plcRead.srgv.roadUtil; + +import java.util.List; + +public class MatrixModifier { + + /** + * 返回一个修改后的新二维数组,不对原数组进行修改 + * + * @param matrix 原始二维数组 + * @param row 指定行索引 + * @param col 指定列索引 + * @return 新的修改后的二维数组 + */ + public static int[][] modifyColumnNeighbors(int[][] matrix, int row, int col) { + return modifySinglePoint(matrix, row, col); + } + + /** + * 私有方法:对单个点执行上下列的修改(返回新数组) + */ + private static int[][] modifySinglePoint(int[][] matrix, int row, int col) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return new int[0][0]; + } + + // 深拷贝原数组 + int rows = matrix.length; + int cols = matrix[0].length; + int[][] newMatrix = new int[rows][cols]; + for (int i = 0; i < rows; i++) { + System.arraycopy(matrix[i], 0, newMatrix[i], 0, cols); + } + + // 向上修改 + for (int i = row - 1; i >= 0; i--) { + if (newMatrix[i][col] == 1) { + newMatrix[i][col] = 2; + } else { + break; + } + } + + // 向下修改 + for (int i = row + 1; i < rows; i++) { + if (newMatrix[i][col] == 1) { + newMatrix[i][col] = 2; + } else { + break; + } + } + + newMatrix[row][col] = 2; + + return newMatrix; + } + + /** + * 新增方法:对两个点分别执行列方向上的修改 + */ + public static int[][] modifyColumnNeighbors(int[][] matrix, int startX, int startY, int endX, int endY) { + // 先对第一个点修改 + int[][] modified = modifySinglePoint(matrix, startX, startY); + // 再对第二个点修改 + return modifySinglePoint(modified, endX, endY); + } + + //对多个站点执行更新 + public static void modifyColumnNeighbors(int[][] matrix, List points,int value) { + + if(points == null){ + return; + } + + for (int[] point : points) { + matrix[point[0]][point[1]] = value; + } + + } + + //拷贝原数组 + public static int[][] copyMatrix(int[][] matrix) { + //拷贝原数组 + int rows = matrix.length; + int cols = matrix[0].length; + int[][] newMatrix = new int[rows][cols]; + return newMatrix; + } + + + +} + + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/PathFinder.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/PathFinder.java new file mode 100644 index 0000000..cf59420 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/PathFinder.java @@ -0,0 +1,901 @@ +package com.ruoyi.schedule.plcRead.srgv.roadUtil; + +import java.util.*; +import java.util.stream.Collectors; + +public class PathFinder { + + private static class PathInfo { + List path; + int inflectionCount; + + PathInfo(List path, int inflectionCount) { + this.path = path; + this.inflectionCount = inflectionCount; + } + } + + private static class Node implements Comparable { + int x, y, distance; + + Node(int x, int y, int distance) { + this.x = x; + this.y = y; + this.distance = distance; + } + + @Override + public int compareTo(Node other) { + return Integer.compare(this.distance, other.distance); + } + } + + /** + * 使用 Dijkstra 算法查找从起点到终点的最短路径。 + * + * @param grid 二维数组表示的网格地图 + * @param startX 起点 X 坐标 (用户视角从1开始) + * @param startY 起点 Y 坐标 (用户视角从1开始) + * @param endX 终点 X 坐标 (用户视角从1开始) + * @param endY 终点 Y 坐标 (用户视角从1开始) + * @return 包含路径坐标的 int 数组列表,若无路径则返回 null + */ + public static List findShortestPath2(int[][] grid, int startX, int startY, int endX, int endY) { + int rows = grid.length; + int cols = grid[0].length; + int[][] distances = new int[rows][cols]; + int[][][] prev = new int[rows][cols][2]; + int[][][] arr = new int[rows][cols][2]; + PriorityQueue pq = new PriorityQueue<>(); + + for (int i = 0; i < rows; i++) { + Arrays.fill(distances[i], Integer.MAX_VALUE); + } + distances[startX][startY] = 0; + pq.add(new Node(startX, startY, 0)); + + int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + + while (!pq.isEmpty()) { + Node current = pq.poll(); + int x = current.x; + int y = current.y; + + if (x == endX && y == endY) break; + if (current.distance > distances[x][y]) continue; + + for (int[] dir : dirs) { + int nx = x + dir[0]; + int ny = y + dir[1]; + + if (nx >= 0 && nx < rows && ny >= 0 && ny < cols) { + int cellValue = grid[nx][ny]; + if (cellValue == 1 || cellValue == 2) { + int newDist = current.distance + 1; + if (newDist < distances[nx][ny]) { + distances[nx][ny] = newDist; + prev[nx][ny] = new int[]{x, y}; + pq.add(new Node(nx, ny, newDist)); + } + } + + + } + } + } + + List path = new ArrayList<>(); + int currX = endX, currY = endY; + while (currX != startX || currY != startY) { + path.add(new int[]{currX, currY}); + int[] p = prev[currX][currY]; + if (p[0] == 0 && p[1] == 0 && (currX != startX || currY != startY)) { + return null; + } + currX = p[0]; + currY = p[1]; + } + path.add(new int[]{startX, startY}); + Collections.reverse(path); + + + + + return path; + } + + + + + + + + /** + * 使用 A* 算法查找从起点到终点的最短路径。 + * + * @param grid 二维数组表示的网格地图 + * @param startX 起点 X 坐标 + * @param startY 起点 Y 坐标 + * @param endX 终点 X 坐标 + * @param endY 终点 Y 坐标 + * @return 包含路径坐标的 int 数组列表,若无路径则返回 null + */ + public static List findShortestPath(int[][] grid, int startX, int startY, int endX, int endY) { + int rows = grid.length; + int cols = grid[0].length; + + // gCost: 从起点到当前节点的实际成本 + int[][] gCost = new int[rows][cols]; + // fCost: gCost + 启发式估计成本(从当前节点到终点) + int[][] fCost = new int[rows][cols]; + // prev: 记录路径回溯信息 + int[][][] prev = new int[rows][cols][2]; + // closedSet: 记录已访问的节点 + boolean[][] closedSet = new boolean[rows][cols]; + + // 初始化成本数组 + for (int i = 0; i < rows; i++) { + Arrays.fill(gCost[i], Integer.MAX_VALUE); + Arrays.fill(fCost[i], Integer.MAX_VALUE); + } + + // 优先队列,按fCost排序 + PriorityQueue openSet = new PriorityQueue<>(); + + // 初始化起点 + gCost[startX][startY] = 0; + fCost[startX][startY] = heuristic(startX, startY, endX, endY); + openSet.add(new AStarNode(startX, startY, gCost[startX][startY], fCost[startX][startY])); + + // 四个方向:上、下、左、右 + int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + + while (!openSet.isEmpty()) { + // 取出fCost最小的节点 + AStarNode current = openSet.poll(); + int x = current.x; + int y = current.y; + + // 如果到达终点,结束搜索 + if (x == endX && y == endY) { + break; + } + + // 如果已经处理过该节点,跳过 + if (closedSet[x][y]) { + continue; + } + + // 标记为已处理 + closedSet[x][y] = true; + + // 检查四个方向的邻居 + for (int[] dir : dirs) { + int nx = x + dir[0]; + int ny = y + dir[1]; + + // 检查边界 + if (nx >= 0 && nx < rows && ny >= 0 && ny < cols) { + // 检查是否可通行(值为1或2)且未处理过 + int cellValue = grid[nx][ny]; + if ((cellValue == 1 || cellValue == 2) && !closedSet[nx][ny]) { + // 计算新的gCost(从起点到邻居节点的成本) + int tentativeGCost = gCost[x][y] + 1; + + // 如果新的路径更优 + if (tentativeGCost < gCost[nx][ny]) { + // 更新路径信息 + prev[nx][ny] = new int[]{x, y}; + gCost[nx][ny] = tentativeGCost; + fCost[nx][ny] = tentativeGCost + heuristic(nx, ny, endX, endY); + + // 添加到开放集合 + openSet.add(new AStarNode(nx, ny, gCost[nx][ny], fCost[nx][ny])); + } + } + } + } + } + + // 如果终点不可达 + if (gCost[endX][endY] == Integer.MAX_VALUE) { + return null; + } + + // 回溯路径 + List path = new ArrayList<>(); + int currX = endX, currY = endY; + + // 从终点回溯到起点 + while (currX != startX || currY != startY) { + path.add(new int[]{currX, currY}); + int[] p = prev[currX][currY]; + + // 检查路径有效性 + if (p[0] == 0 && p[1] == 0 && (currX != startX || currY != startY)) { + return null; + } + + currX = p[0]; + currY = p[1]; + } + + // 添加起点 + path.add(new int[]{startX, startY}); + + // 反转路径,使其从起点到终点 + Collections.reverse(path); + + return path; + } + + /** + * A*算法节点类 + */ + private static class AStarNode implements Comparable { + int x, y; // 节点坐标 + int gCost; // 从起点到当前节点的实际成本 + int fCost; // 总成本(fCost = gCost + hCost) + + AStarNode(int x, int y, int gCost, int fCost) { + this.x = x; + this.y = y; + this.gCost = gCost; + this.fCost = fCost; + } + + @Override + public int compareTo(AStarNode other) { + // 首先按fCost排序,如果fCost相同则按gCost排序 + if (this.fCost != other.fCost) { + return Integer.compare(this.fCost, other.fCost); + } + return Integer.compare(other.gCost, this.gCost); // gCost较大的优先(更接近终点) + } + } + + /** + * 启发式函数:使用曼哈顿距离 + * + * @param x1 起点x坐标 + * @param y1 起点y坐标 + * @param x2 终点x坐标 + * @param y2 终点y坐标 + * @return 估计距离 + */ + private static int heuristic(int x1, int y1, int x2, int y2) { + return Math.abs(x1 - x2) + Math.abs(y1 - y2); + } + + + + + + + + + + + /** + * 提取路径中的拐点(1→2 或 2→1 的边界点) + * + * @param path 路径坐标列表(逻辑坐标) + * @param grid 网格数据 + * @return 拐点坐标列表(逻辑坐标) + */ + public static List extractInflectionPoints(List path, int[][] grid) { + + + // + + // 识别并输出拐点坐标 + List inflectionPoints = new ArrayList<>(); + for (int i = 0; i < path.size() - 1; i++) { + int[] current = path.get(i); + int[] next = path.get(i + 1); + int currentValue = grid[current[0]][current[1]]; + int nextValue = grid[next[0]][next[1]]; + + // 检测1→2或2→1的转换,记录2所在的坐标 + if ((currentValue == 1 && nextValue == 2) || (currentValue == 2 && nextValue == 1)) { + if (currentValue == 2) { + inflectionPoints.add(current); + } else { + inflectionPoints.add(next); + } + } + } + + + //inflectionPoints去重 + List collect = inflectionPoints.stream().distinct().collect(Collectors.toList()); + + + + return collect; + + + + } + + + + /** + * 使用 BFS 查找从起点开始,路径上所有点的值为 2,直到终点(值为 1 或 2)的最短路径。 + * + * @param grid 二维数组表示的网格地图 + * @param startX 起点 X 坐标 (用户视角从1开始) + * @param startY 起点 Y 坐标 (用户视角从1开始) + * @param endX 终点 X 坐标 (用户视角从1开始) + * @param endY 终点 Y 坐标 (用户视角从1开始) + * @return 包含路径坐标的 int 数组列表,若无路径则返回 null + */ + public static List findShortestPathFrom2To2Or1(int[][] grid, int startX, int startY, int endX, int endY) { + + grid = MatrixModifier.modifyColumnNeighbors(grid, startX, startY, endX, endY); + + + int rows = grid.length; + int cols = grid[0].length; + + if (grid[startX][startY] != 2) { + return null; // 起点不是 2,直接失败 + } + + boolean[][] visited = new boolean[rows][cols]; + int[][] prev = new int[rows * cols][2]; // 记录路径回溯信息 + + Queue queue = new LinkedList<>(); + queue.offer(new int[]{startX, startY}); + visited[startX][startY] = true; + + int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上下左右方向 + + while (!queue.isEmpty()) { + int[] current = queue.poll(); + int x = current[0]; + int y = current[1]; + + if (x == endX && y == endY) { + break; // 找到终点 + } + + for (int[] dir : dirs) { + int nx = x + dir[0]; + int ny = y + dir[1]; + + if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && !visited[nx][ny]) { + if (grid[nx][ny] == 2 || (nx == endX && ny == endY && (grid[nx][ny] == 1 || grid[nx][ny] == 2))) { + visited[nx][ny] = true; + prev[nx * cols + ny] = new int[]{x, y}; + queue.offer(new int[]{nx, ny}); + } + } + } + } + + if (!visited[endX][endY]) { + return null; // 终点不可达 + } + + // 回溯路径 + List path = new ArrayList<>(); + int currX = endX, currY = endY; + while (currX != startX || currY != startY) { + path.add(new int[]{currX, currY}); + int[] parent = prev[currX * cols + currY]; + currX = parent[0]; + currY = parent[1]; + } + path.add(new int[]{startX, startY}); + Collections.reverse(path); + + return path; + } + + + + + + /** + * 提取路径中的拐点: + * - 区域类型转换点(1 ↔ 2) + * - 方向变化点(水平转垂直 / 垂直转水平) + * + * @param path 路径坐标列表(逻辑坐标) + * @param grid 网格数据 + * @return 拐点坐标列表(逻辑坐标) + */ + public static List extractInflectionPoints2(List path, int[][] grid) { + List inflectionPoints = new ArrayList<>(); + + // 记录类型转换点和方向变化点 + Set seen = new HashSet<>(); + + // 检测类型转换点(1 ↔ 2) + /*for (int i = 0; i < path.size() - 1; i++) { + int[] current = path.get(i); + int[] next = path.get(i + 1); + int currentValue = grid[current[0]][current[1]]; + int nextValue = grid[next[0]][next[1]]; + + if ((currentValue == 1 && nextValue == 2) || (currentValue == 2 && nextValue == 1)) { + int[] point = currentValue == 2 ? current : next; + String key = point[0] + "," + point[1]; + if (!seen.contains(key)) { + seen.add(key); + inflectionPoints.add(point); + } + } + }*/ + + // 检测方向变化点(连续三点的方向变化) + for (int i = 1; i < path.size() - 1; i++) { + int[] prev = path.get(i - 1); + int[] curr = path.get(i); + int[] next = path.get(i + 1); + + boolean horizontal1 = prev[0] == curr[0]; // 前一段是否水平 + boolean horizontal2 = curr[0] == next[0]; // 后一段是否水平 + + if (horizontal1 != horizontal2) { // 方向变化 + String key = curr[0] + "," + curr[1]; + if (!seen.contains(key)) { + seen.add(key); + inflectionPoints.add(curr); + } + } + } + + // 排序后返回(可选) + return inflectionPoints; /*.stream() + .sorted(Comparator.comparingInt(a -> a[0] * 1000 + a[1])) + .collect(Collectors.toList());*/ + } + + /* + * + * 同层多车选择最优 + * 排除车辆障碍点 + * + * 空车运行 + * + * + * */ + public static Site findOptimalCarWithObstacles( + int[][] grid, int endX, int endY, List cars) { + // 创建包含车辆位置的障碍物地图 + int[][] obstacleGrid = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new); + cars.forEach(car -> { + int[] pos = car.getStartPoint(); + if (pos != null) obstacleGrid[pos[0]][pos[1]] = 0; // 标记为障碍 + }); + + // 复用现有路径查找逻辑 + return findOptimalCarForDestination(obstacleGrid, endX, endY, cars); + } + + + /* + * + * 同层多车选择最优 + * 排除车辆障碍点 + * + * 载货运行 + * + * + * */ + public static Site findOptimalCarWithObstaclesByFull( + int[][] grid, int endX, int endY, List cars) { + // 创建包含车辆位置的障碍物地图 + int[][] obstacleGrid = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new); + cars.forEach(car -> { + int[] pos = car.getStartPoint(); + if (pos != null) obstacleGrid[pos[0]][pos[1]] = 0; // 标记为障碍 + }); + + // 复用现有路径查找逻辑 + return findOptimalCarForDestinationByFull(obstacleGrid, endX, endY, cars); + } + + + /* + * 此方法使用情景 + * 一个原点(目标点)、多个目标点(原点),多个目标点(原点)互相影响, + * 按算法查找多个目标点(原点)的最优路径 + * + * */ + public static Site findOptimalCarWithObstacles(int[][] grid, Site srcSite, List tarSites){ + //所有车先都设置为障碍 + tarSites.forEach(site -> { + int[] pos = site.getStartPoint(); + if (pos != null) grid[pos[0]][pos[1]] = 0; // 标记为障碍 + }); + + // 并行计算所有车辆的路径 + Map carPathInfo = tarSites.parallelStream() + .filter(Objects::nonNull) // 过滤掉null元素 + .collect(Collectors.toMap( + car -> car, + car -> { + int[] startPoint = car.getStartPoint(); + if (startPoint == null || startPoint.length < 2) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); + } + + + + // 计算路径 + List path = findShortestPath(grid, startPoint[0], startPoint[1], srcSite.getStartPoint()[0], srcSite.getStartPoint()[1]); + if (path == null) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); + } + + + + // 检查路径冲突 + boolean blocked = tarSites.stream() + .filter(other -> other != car) + .map(Site::getStartPoint) + .filter(Objects::nonNull) + .anyMatch(pos -> path.stream().anyMatch(p -> p[0] == pos[0] && p[1] == pos[1])); + + if (blocked) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); + } + + // 计算拐点 + List inflectionPoints = extractInflectionPoints2(path, grid); + return new PathInfo(path, inflectionPoints.size()); + } + )); + + // 过滤无效路径 + Map availableCars = carPathInfo.entrySet().stream() + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // 选择最优车辆(路径最短且拐点最少) + Site selectedCar = availableCars.entrySet().stream() + .min(Comparator.comparingInt(e -> e.getValue().path.size() * 100 + e.getValue().inflectionCount)) + .map(Map.Entry::getKey) + .orElse(null); + + return selectedCar; + + } + + /* + * 载货查找最佳,使用情景 + * + * */ + public static Site findOptimalWithCargo(int[][] grid, Site srcSite, List tarSites){ + + // 并行计算所有车辆的路径 + Map carPathInfo = tarSites.parallelStream() + .collect(Collectors.toMap( + car -> car, + car -> { + int[] startPoint = car.getStartPoint(); + if (startPoint == null || startPoint.length < 2) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); + } + + + // 计算路径 + List path = findShortestPathFrom2To2Or1(grid, startPoint[0], startPoint[1], srcSite.getStartPoint()[0], srcSite.getStartPoint()[1]); + if (path == null) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); + } + + + + // 检查路径冲突 + boolean blocked = tarSites.stream() + .filter(other -> other != car) + .map(Site::getStartPoint) + .filter(Objects::nonNull) + .anyMatch(pos -> path.stream().anyMatch(p -> p[0] == pos[0] && p[1] == pos[1])); + + if (blocked) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); + } + + // 计算拐点 + List inflectionPoints = extractInflectionPoints2(path, grid); + return new PathInfo(path, inflectionPoints.size()); + } + )); + + // 过滤无效路径 + Map availableCars = carPathInfo.entrySet().stream() + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // 选择最优车辆(路径最短且拐点最少) + Site selectedCar = availableCars.entrySet().stream() + .min(Comparator.comparingInt(e -> e.getValue().path.size() * 100 + e.getValue().inflectionCount)) + .map(Map.Entry::getKey) + .orElse(null); + + return selectedCar; + } + + + + /* + * 空载查找最佳,使用情景 + * 小车不载货,一个原点(目标点),多个目标点(原点) + * + * */ + public static Site findOptimalWithNoCargo(int[][] grid, Site srcSite, List tarSites){ + + + // 并行计算所有车辆的路径 + Map carPathInfo = tarSites.parallelStream() + .collect(Collectors.toMap( + car -> car, + car -> { + int[] startPoint = car.getStartPoint(); + if (startPoint == null || startPoint.length < 2) { + return null; + } + + + + // 计算路径 + List path = findShortestPath(grid, startPoint[0], startPoint[1], srcSite.getStartPoint()[0], srcSite.getStartPoint()[1]); + if (path == null) { + + return null; + } + + + + // 检查路径冲突 + boolean blocked = tarSites.stream() + .filter(other -> other != car) + .map(Site::getStartPoint) + .filter(Objects::nonNull) + .anyMatch(pos -> path.stream().anyMatch(p -> p[0] == pos[0] && p[1] == pos[1])); + + if (blocked) { + return null; + } + + // 计算拐点 + List inflectionPoints = extractInflectionPoints2(path, grid); + return new PathInfo(path, inflectionPoints.size()); + } + )); + + // 过滤无效路径 + Map availableCars = carPathInfo.entrySet().stream() + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // 选择最优车辆(路径最短且拐点最少) + Site selectedCar = availableCars.entrySet().stream() + .min(Comparator.comparingInt(e -> e.getValue().path.size() * 100 + e.getValue().inflectionCount)) + .map(Map.Entry::getKey) + .orElse(null); + + return selectedCar; + + + } + + + + /* + * + * 同层多车选择最优 + * */ + /* + * + * 同层多车选择最优 + * */ + /* + * + * 同层多车选择最优 + * */ + public static Site findOptimalCarForDestination( + int[][] grid, int endX, int endY, List cars) { + // 创建网格副本 + int[][] tempGrid = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new); + + // 并行计算所有车辆的路径 + Map carPathInfo = cars.parallelStream() + .filter(car -> { + int[] startPoint = car.getStartPoint(); + return startPoint != null && startPoint.length >= 2; + }) // 过滤掉无效的车辆 + .collect(Collectors.toMap( + car -> car, + car -> { + int[] startPoint = car.getStartPoint(); + // 注意:这里可以移除重复的 null 检查,因为已经在 filter 中处理过了 + + + // 计算路径 + List path = findShortestPath(tempGrid, startPoint[0], startPoint[1], endX, endY); + if (path == null) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); // 返回特殊标记值而不是null + } + + + // 检查路径冲突 + boolean blocked = cars.stream() + .filter(other -> other != car) + .map(Site::getStartPoint) + .filter(Objects::nonNull) + .anyMatch(pos -> path.stream().anyMatch(p -> p[0] == pos[0] && p[1] == pos[1])); + + if (blocked) { + return new PathInfo(new ArrayList<>(), Integer.MAX_VALUE); // 返回特殊标记值而不是null + } + + // 计算拐点 + List inflectionPoints = extractInflectionPoints2(path, tempGrid); + return new PathInfo(path, inflectionPoints.size()); + }, + (existing, replacement) -> existing // 合并函数:如果键冲突,保留现有的值 + )); + + // 过滤无效路径(检查特殊标记值) + Map availableCars = carPathInfo.entrySet().stream() + .filter(entry -> entry.getValue() != null && + entry.getValue().inflectionCount != Integer.MAX_VALUE) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // 选择最优车辆(路径最短且拐点最少) + Site selectedCar = availableCars.entrySet().stream() + .min(Comparator.comparingInt(e -> e.getValue().path.size() * 100 + e.getValue().inflectionCount)) + .map(Map.Entry::getKey) + .orElse(null); + + // 为所有车辆生成路径图片 + + return selectedCar; + } + + + + + + public static Site findOptimalCarForDestinationByFull( + int[][] grid, int endX, int endY, List cars) { + // 创建网格副本 + int[][] tempGrid = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new); + + // 并行计算所有车辆的路径 + Map carPathInfo = cars.parallelStream() + .collect(Collectors.toMap( + car -> car, + car -> { + int[] startPoint = car.getStartPoint(); + if (startPoint == null || startPoint.length < 2) { + return null; + } + + // 计算路径 + List path = findShortestPathFrom2To2Or1(tempGrid, startPoint[0], startPoint[1], endX, endY); + if (path == null) { + return null; + } + + + // 检查路径冲突 + boolean blocked = cars.stream() + .filter(other -> other != car) + .map(Site::getStartPoint) + .filter(Objects::nonNull) + .anyMatch(pos -> path.stream().anyMatch(p -> p[0] == pos[0] && p[1] == pos[1])); + + if (blocked) { + return null; + } + + // 计算拐点 + List inflectionPoints = extractInflectionPoints2(path, tempGrid); + return new PathInfo(path, inflectionPoints.size()); + } + )); + + // 过滤无效路径 + Map availableCars = carPathInfo.entrySet().stream() + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // 选择最优车辆(路径最短且拐点最少) + Site selectedCar = availableCars.entrySet().stream() + .min(Comparator.comparingInt(e -> e.getValue().path.size() * 100 + e.getValue().inflectionCount)) + .map(Map.Entry::getKey) + .orElse(null); + + // 为所有车辆生成路径图片 + + return selectedCar; + } + + + /** + * 自动避让路径规划:当B车阻挡A车路径时,自动为B车规划新路径 + */ + public static Site findOptimalCarWithAutoAvoidance( + int[][] grid, int endX, int endY, List cars) { + + // 1. 首先尝试常规路径规划 + Site selectedCar = findOptimalCarForDestination(grid, endX, endY, cars); + if (selectedCar != null) { + return selectedCar; // 如果有车可以直接到达,直接返回 + } + + // 2. 检查是否有车辆被其他车辆阻挡 + for (Site car : cars) { + int[] start = car.getStartPoint(); + List path = findShortestPath(grid, start[0], start[1], endX, endY); + + if (path != null) { + // 检查路径是否被其他车辆阻挡 + for (Site otherCar : cars) { + if (otherCar != car) { + int[] otherPos = otherCar.getStartPoint(); + if (otherPos != null && path.stream().anyMatch(p -> p[0] == otherPos[0] && p[1] == otherPos[1])) { + // 3. 为阻挡车辆寻找最近可用位置 + int[] newPos = findNearestAvailablePosition(grid, otherCar.getStartPoint()); + if (newPos != null) { + // 4. 更新阻挡车辆位置并重新尝试 + otherCar.setStartPoint(newPos); + return findOptimalCarForDestination(grid, endX, endY, cars); + } + } + } + } + } + } + + // 5. 如果所有尝试都失败,返回null + return null; + } + + /** + * 为被阻挡车辆寻找最近的可用位置 + */ + private static int[] findNearestAvailablePosition(int[][] grid, int[] currentPos) { + if (currentPos == null) return null; + + int rows = grid.length; + int cols = grid[0].length; + boolean[][] visited = new boolean[rows][cols]; + Queue queue = new LinkedList<>(); + queue.add(currentPos); + visited[currentPos[0]][currentPos[1]] = true; + + int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + + while (!queue.isEmpty()) { + int[] pos = queue.poll(); + + for (int[] dir : dirs) { + int nx = pos[0] + dir[0]; + int ny = pos[1] + dir[1]; + + if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && !visited[nx][ny]) { + if (grid[nx][ny] == 1 || grid[nx][ny] == 2) { + return new int[]{nx, ny}; // 找到第一个可用位置 + } + visited[nx][ny] = true; + queue.add(new int[]{nx, ny}); + } + } + } + + return null; // 没有找到可用位置 + } + + + + + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/Site.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/Site.java new file mode 100644 index 0000000..a04a0bb --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/roadUtil/Site.java @@ -0,0 +1,41 @@ +package com.ruoyi.schedule.plcRead.srgv.roadUtil; + +public class Site { + + private String carId; + + private int[] startPoint;//站点坐标 + + private String site;//当前站点 + + public Site(String carId, int[] startPoint) { + this.carId = carId; + this.startPoint = startPoint; + } + + public Site(String carId, int[] startPoint, String site) { + this.carId = carId; + this.startPoint = startPoint; + this.site = site; + } + + public String getSite() { + return site; + } + + public String getCarId() { + return carId; + } + + public void setCarId(String carId) { + this.carId = carId; + } + + public int[] getStartPoint() { + return startPoint; + } + + public void setStartPoint(int[] startPoint) { + this.startPoint = startPoint; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java index da50dba..a97b8b3 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java @@ -1,20 +1,25 @@ package com.ruoyi.schedule.scheduleTask.srgv; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; import com.ruoyi.bas.port.domain.WcsPort; -import com.ruoyi.bas.port.mapper.WcsPortMapper; import com.ruoyi.bas.port.service.IWcsPortService; +import com.ruoyi.common.domain.SendData2; import com.ruoyi.device.srgv.domain.DeviceSrgv; import com.ruoyi.queue.srgv.domain.WcsSrgvQueue; +import com.ruoyi.queue.srgv.domain.WcsSrgvQueueFinish; import com.ruoyi.queue.srgv.service.IWcsSrgvQueueFinishService; import com.ruoyi.queue.srgv.service.IWcsSrgvQueueService; +import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead; +import com.ruoyi.schedule.plcRead.srgv.roadUtil.PathFinder; import lombok.extern.slf4j.Slf4j; -import java.util.HashMap; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; @Slf4j public class BaseSRgvThread implements Runnable, SRgvThreadInterface { @@ -46,7 +51,7 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { if (ObjUtil.isNotNull(deviceSrgv)) { //循环所有楼层 for (int i = 1; i <= 8; i++) { - sRgvTask(deviceSrgv); + sRgvTask(deviceSrgv, i); } } else { log.error("堆垛机设备为空"); @@ -76,33 +81,299 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { completeCarTask(deviceSrgv,floor); //小车发送任务 - sendTask(deviceSrgv); + sendTask(deviceSrgv, floor); //小车充电任务 - inCharging(deviceSrgv); + inCharging(deviceSrgv, floor); //小车离开充电桩 - outCharging(deviceSrgv); + outCharging(deviceSrgv, floor); } /** * 小车离开充电桩 */ - private void outCharging(DeviceSrgv deviceSrgv) { + private void outCharging(DeviceSrgv deviceSrgv, int floor) { } /** * 小车充电任务 */ - private void inCharging(DeviceSrgv deviceSrgv) { + private void inCharging(DeviceSrgv deviceSrgv, int floor) { } /** * 小车发送任务 */ - private void sendTask(DeviceSrgv deviceSrgv) { + private void sendTask(DeviceSrgv deviceSrgv, int floor) { + WcsSrgvQueue param = new WcsSrgvQueue(); + param.setIssued("0"); + param.setTheFloor(Convert.toStr(floor)); + List wcsSrgvQueues = wcsSagvQueueMapper.selectWcsSrgvQueueList(param); + if (CollUtil.isEmpty(wcsSrgvQueues)) { + return; + } + for (WcsSrgvQueue wcsSrgvQueue : wcsSrgvQueues) { + processPendingTask(wcsSrgvQueue, deviceSrgv, floor); + } } + /** + * 处理队列下发 + */ + private void processPendingTask(WcsSrgvQueue wcsSrgvQueue, DeviceSrgv deviceSrgv, int floor) { + String agvId = wcsSrgvQueue.getAgvId(); + if (agvId.equals(Convert.toStr(deviceSrgv.getDeviceId())) && deviceSrgv.getWorkMode() == 5 && deviceSrgv.getAllowSend() == 1) { + String startSite = wcsSrgvQueue.getStartSite(); + String endSite = wcsSrgvQueue.getEndSite(); + String currSite = deviceSrgv.getCurrSite(); + + if (isSameFloor(startSite, endSite)) { + if (StrUtil.isNotEmpty(endSite)) { + handleUnspecifiedDestination(wcsSrgvQueue, startSite, floor, deviceSrgv); + } + } + + + //判断是否可以下发任务 + if ("0".equals(wcsSrgvQueue.getIssued()) && deviceSrgv.getStatus() == 1) { + handleUnissuedTask(deviceSrgv, wcsSrgvQueue, currSite, startSite, endSite, floor); + } + } + + + } + + /** + * 处理未下发的任务 + */ + private void handleUnissuedTask(DeviceSrgv deviceSrgv, WcsSrgvQueue wcsSrgvQueue, String currSite, String startSite, String endSite, int floor) { + + //判断小车是否在起始点 + if (currSite.equals(startSite)) { + //小车在起始点 + sendMainTask(deviceSrgv, wcsSrgvQueue, currSite, endSite, floor); + } else { + //小车不在起始点 + sendPointToPointTask(deviceSrgv, wcsSrgvQueue, currSite, startSite, floor); + } + } + + /** + * 小车发送点对点任务 + */ + private void sendPointToPointTask(DeviceSrgv deviceSrgv, WcsSrgvQueue wcsSrgvQueue, String currSite, String startSite, int floor) { + } + + /** + * 小车发送点对点主任务 + */ + private void sendMainTask(DeviceSrgv deviceSrgv, WcsSrgvQueue wcsSrgvQueue, String currSite, String endSite, int floor) { + String taskType = wcsSrgvQueue.getTaskType(); + boolean sendFlag = false; + switch (taskType) { + case "1": + // 空车任务(充电、换层、手动点到点、除尘等) + sendFlag = sendEmptyCarTask(deviceSrgv, wcsSrgvQueue, currSite, endSite, floor); + break; + case "2": + // 载货任务(出库、入库、移库等) + sendFlag = sendFullCarTask(deviceSrgv, wcsSrgvQueue, currSite, endSite, floor); + break; + } + + if (sendFlag) { + //处理监控信息 + } + } + + /** + * 小车发送载货任务 + */ + private boolean sendFullCarTask(DeviceSrgv deviceSrgv, WcsSrgvQueue wcsSrgvQueue, String currSite, String endSite) { + return false; + } + + /** + * 小车发送空车任务 + */ + private boolean sendEmptyCarTask(DeviceSrgv deviceSrgv, WcsSrgvQueue wcsSrgvQueue, String startSite, String endSite, int floor) { + //获取当前地图 + Map arrByRoadSite = roadCacheService.getArrByRoadSite(floor); + //获取空载地图 + int[][] emptyMap = roadCacheService.findEmptyMap(floor, arrByRoadSite.get(startSite)[0], arrByRoadSite.get(startSite)[1], arrByRoadSite.get(endSite)[0], arrByRoadSite.get(endSite)[1], splc); + //计算最短路径 + List shortestPath = PathFinder.findShortestPath(emptyMap, arrByRoadSite.get(startSite)[0], arrByRoadSite.get(startSite)[1], arrByRoadSite.get(endSite)[0], arrByRoadSite.get(endSite)[1]); + + //判断是否能规划出最短路径 + if (shortestPath == null || shortestPath.isEmpty()) { + + return; + } + + //获取拐点路径 + List trun = PathFinder.extractInflectionPoints2(shortestPath, emptyMap); + + // 路径点拼接 + StringBuilder pathBuilder = new StringBuilder(); + Map roadSiteByArr = roadCacheService.getRoadSiteByArr(Integer.parseInt(floor)); + for (int[] point : shortestPath) { + String site = roadSiteByArr.get(point[0] + "," + point[1]); + if (!"000".equals(site)) { + pathBuilder.append(site).append(","); + } + + } + if (pathBuilder.length() > 0) { + pathBuilder.setLength(pathBuilder.length() - 1); // 移除最后一个逗号 + } + + // 拐点拼接 + StringBuilder inflectionBuilder = new StringBuilder(); + if (!trun.isEmpty()) { + for (int[] ints : trun) { + String site = roadSiteByArr.get(ints[0] + "," + ints[1]);//获取站点 + inflectionBuilder.append(site).append(","); + } + if (inflectionBuilder.length() > 0) { + inflectionBuilder.setLength(inflectionBuilder.length() - 1); + } + // 当trun长度小于6时,后面用0拼接 + if (trun.size() < 6) { + for (int i = trun.size(); i < 6; i++) { + inflectionBuilder.append(",0"); + } + } + } + + List sendDataList = new ArrayList<>(); + if (!trun.isEmpty()) { + SendData2 path = new SendData2(); + path.setAddress("4000");//地址 + path.setData(startSite + "," + endSite + "," + inflectionBuilder.toString()); + path.setType(2); + sendDataList.add(path); + } else { + SendData2 path = new SendData2(); + path.setAddress("4000");//地址 + path.setData(startSite + "," + endSite); + path.setType(2); + sendDataList.add(path); + } + + + //任务号、原、目标数据转换 + String siteId = wcsSrgvQueue.getTaskNo();//+ "," + startSite + "," + endSite; + SendData2 site = new SendData2(); + site.setAddress("4400"); + site.setData(siteId); + site.setType(2); + sendDataList.add(site); + + //任务类型转换 + SendData2 taskType = new SendData2(); + taskType.setAddress("2200"); + taskType.setData(wcsSrgvQueue.getDefault02()); + taskType.setType(1); + sendDataList.add(taskType); + + //下发标志转换 + SendData2 flag = new SendData2(); + flag.setAddress("2202"); + flag.setData("1"); + flag.setType(1); + sendDataList.add(flag); + + return sendCarTask(sendDataList, deviceSrgv); + } + + /** + * 小车发送任务 + */ + private boolean sendCarTask(List sendDataList, DeviceSrgv deviceSrgv) { + + boolean b = true; + + for (SendData2 sendData : sendDataList) { + String address = sendData.getAddress(); + String data = sendData.getData();// + + String[] split = data.split(","); + + int type = sendData.getType();//1单字 2双字 3字符串 + + //双字 + if (type == 2) { + int[] intData = Arrays.stream(split) + .map(String::trim) + .mapToInt(Integer::parseInt) + .toArray(); + boolean write = PlcSrgvRead.Write(address, intData); + + if (!write) { + b = false; + break; + } + } + + + //单字 + if (type == 1) { + //String[]转为 short[] + short[] shortData = new short[split.length]; + for (int i = 0; i < split.length; i++) { + try { + shortData[i] = Short.parseShort(split[i].trim()); + } catch (NumberFormatException e) { + // 记录日志并设置默认值 + shortData[i] = 0; + } + } + + boolean write = PlcSrgvRead.Write(address, shortData); + + if (!write) { + b = false; + break; + } + + } + } + + String collect = sendDataList.stream().map(li -> "{" + li.getAddress() + ":" + li.getData() + "}").collect(Collectors.joining(",")); + + log.info("小车:" + deviceSrgv.getDeviceId() + ",下发值:" + collect + ",,结果:" + b); + + return b; + } + + /** + * 处理未指定目的地的任务 + */ + private void handleUnspecifiedDestination(WcsSrgvQueue wcsSrgvQueue, String startSite, int floor, DeviceSrgv deviceSrgv) { + } + + /** + * 判断站点是否是同一层 + * + * @param startSite + * @param endSite + * @return + */ + private boolean isSameFloor(String startSite, String endSite) { + // 参数校验 + if (startSite == null || endSite == null || startSite.length() < 2 || endSite.length() < 2) { + return false; + } + + // 正确提取起始和结束站点的楼层信息 + String startFloor = startSite.substring(startSite.length() - 2); + String endFloor = endSite.substring(endSite.length() - 2); + + // 比较是否在同一层 + return Objects.equals(startFloor, endFloor); + } + /** * 小车完成队列 */ @@ -124,6 +395,21 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { * 2、处理终点不是提升机站点的 * */ boolean taskFinishFlag = finishTaskByQueue(deviceSrgv.getCurrSite(), wcsSrgvQueue, floor); + + if (taskFinishFlag) { + String spare2 = deviceSrgv.getSpare2(); + if ("-".equals(spare2)) { + //换层 不删除队列 + wcsSrgvQueue.setExecutionStatus("2"); + wcsSagvQueueMapper.updateWcsSrgvQueue(wcsSrgvQueue); + } else { + WcsSrgvQueueFinish wcsSrgvQueueFinish = new WcsSrgvQueueFinish(); + BeanUtil.copyProperties(wcsSrgvQueue, wcsSrgvQueueFinish); + wcsSrgvQueueFinish.setFinishDate(DateUtil.now()); + wcsSagvQueueFinishMapper.insertWcsSrgvQueueFinish(wcsSrgvQueueFinish); + wcsSagvQueueMapper.deleteWcsSrgvQueueByGuid(wcsSrgvQueue.getGuid()); + } + } } } }