小车调度

This commit is contained in:
liuhanqing
2025-11-21 14:25:33 +08:00
parent c3156725d5
commit e58b6ce7c4
9 changed files with 1484 additions and 26 deletions

View File

@@ -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;
}
}

View File

@@ -102,6 +102,10 @@ public class MsgDdj extends BaseEntity
private String plcDbReadAddress;
private String ip;
private String port;
}

View File

@@ -20,15 +20,15 @@ import java.util.concurrent.ConcurrentHashMap;
public class PlcDdjRead implements Runnable {
public static ConcurrentHashMap<String, MsgDdj> 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<String, MsgDdj> 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();

View File

@@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class PlcSrgvRead implements Runnable {
public static ConcurrentHashMap<String, DeviceSrgv> 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);
}
}

View File

@@ -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<int[]> path, List<int[]> 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();
}
}
}

View File

@@ -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<int[]> 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;
}
}

View File

@@ -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<int[]> path;
int inflectionCount;
PathInfo(List<int[]> path, int inflectionCount) {
this.path = path;
this.inflectionCount = inflectionCount;
}
}
private static class Node implements Comparable<Node> {
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<int[]> 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<Node> 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<int[]> 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<int[]> 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<AStarNode> 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<int[]> 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<AStarNode> {
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<int[]> extractInflectionPoints(List<int[]> path, int[][] grid) {
//
// 识别并输出拐点坐标
List<int[]> 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<int[]> 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<int[]> 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<int[]> 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<int[]> 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<int[]> extractInflectionPoints2(List<int[]> path, int[][] grid) {
List<int[]> inflectionPoints = new ArrayList<>();
// 记录类型转换点和方向变化点
Set<String> 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<Site> 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<Site> 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<Site> tarSites){
//所有车先都设置为障碍
tarSites.forEach(site -> {
int[] pos = site.getStartPoint();
if (pos != null) grid[pos[0]][pos[1]] = 0; // 标记为障碍
});
// 并行计算所有车辆的路径
Map<Site, PathInfo> 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<int[]> 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<int[]> inflectionPoints = extractInflectionPoints2(path, grid);
return new PathInfo(path, inflectionPoints.size());
}
));
// 过滤无效路径
Map<Site, PathInfo> 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<Site> tarSites){
// 并行计算所有车辆的路径
Map<Site, PathInfo> 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<int[]> 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<int[]> inflectionPoints = extractInflectionPoints2(path, grid);
return new PathInfo(path, inflectionPoints.size());
}
));
// 过滤无效路径
Map<Site, PathInfo> 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<Site> tarSites){
// 并行计算所有车辆的路径
Map<Site, PathInfo> carPathInfo = tarSites.parallelStream()
.collect(Collectors.toMap(
car -> car,
car -> {
int[] startPoint = car.getStartPoint();
if (startPoint == null || startPoint.length < 2) {
return null;
}
// 计算路径
List<int[]> 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<int[]> inflectionPoints = extractInflectionPoints2(path, grid);
return new PathInfo(path, inflectionPoints.size());
}
));
// 过滤无效路径
Map<Site, PathInfo> 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<Site> cars) {
// 创建网格副本
int[][] tempGrid = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new);
// 并行计算所有车辆的路径
Map<Site, PathInfo> 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<int[]> 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<int[]> inflectionPoints = extractInflectionPoints2(path, tempGrid);
return new PathInfo(path, inflectionPoints.size());
},
(existing, replacement) -> existing // 合并函数:如果键冲突,保留现有的值
));
// 过滤无效路径(检查特殊标记值)
Map<Site, PathInfo> 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<Site> cars) {
// 创建网格副本
int[][] tempGrid = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new);
// 并行计算所有车辆的路径
Map<Site, PathInfo> carPathInfo = cars.parallelStream()
.collect(Collectors.toMap(
car -> car,
car -> {
int[] startPoint = car.getStartPoint();
if (startPoint == null || startPoint.length < 2) {
return null;
}
// 计算路径
List<int[]> 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<int[]> inflectionPoints = extractInflectionPoints2(path, tempGrid);
return new PathInfo(path, inflectionPoints.size());
}
));
// 过滤无效路径
Map<Site, PathInfo> 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<Site> cars) {
// 1. 首先尝试常规路径规划
Site selectedCar = findOptimalCarForDestination(grid, endX, endY, cars);
if (selectedCar != null) {
return selectedCar; // 如果有车可以直接到达,直接返回
}
// 2. 检查是否有车辆被其他车辆阻挡
for (Site car : cars) {
int[] start = car.getStartPoint();
List<int[]> 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<int[]> 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; // 没有找到可用位置
}
}

View File

@@ -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;
}
}

View File

@@ -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<WcsSrgvQueue> 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<String, int[]> 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<int[]> 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<int[]> trun = PathFinder.extractInflectionPoints2(shortestPath, emptyMap);
// 路径点拼接
StringBuilder pathBuilder = new StringBuilder();
Map<String, String> 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<SendData2> 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<SendData2> 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());
}
}
}
}
}