Compare commits

..

1 Commits

Author SHA1 Message Date
xuzhiheng
503771fe7c update 2025-06-05 12:21:31 +08:00
64 changed files with 2911 additions and 537 deletions

View File

@ -19,7 +19,7 @@ ext.guiceSrcDir = sourceSets.guiceConfig.java.srcDirs[0]
compileGuiceConfigJava {
options.release = 21
options.compilerArgs << "-Werror"
// options.compilerArgs << "-Werror"
options.compilerArgs << "-Xlint:all"
options.compilerArgs << "-Xlint:-serial"
}

View File

@ -35,14 +35,14 @@ dependencies {
compileJava {
options.release = 21
options.compilerArgs << "-Werror"
// options.compilerArgs << "-Werror"
options.compilerArgs << "-Xlint:all"
options.compilerArgs << "-Xlint:-serial"
}
compileTestJava {
options.release = 21
options.compilerArgs << "-Werror"
// options.compilerArgs << "-Werror"
options.compilerArgs << "-Xlint:all"
options.compilerArgs << "-Xlint:-serial"
}

View File

@ -8,3 +8,11 @@ apply from: "${rootDir}/gradle/publishing-java.gradle"
task release {
dependsOn build
}
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
tasks.withType(Javadoc){
options.encoding="utf-8"
}

View File

@ -109,4 +109,11 @@ public interface RemoteVehicleService
)
throws RemoteException;
// CHECKSTYLE:ON
void receiveCallback(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
Object data
)
throws RemoteException;
}

View File

@ -29,7 +29,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's energy level.
* 更新车辆的能级
* 更新车辆的能级.
*
* @param ref A reference to the vehicle to be modified.
* @param energyLevel The vehicle's new energy level.
@ -40,7 +40,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's load handling devices.
* 更新车辆的负载处理设备
* 更新车辆的负载处理设备.
*
* @param ref A reference to the vehicle to be modified.
* @param devices The vehicle's new load handling devices.
@ -68,7 +68,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's order sequence.
* 更新车辆的订单序列
* 更新车辆的订单序列.
*
* @param vehicleRef A reference to the vehicle to be modified.
* @param sequenceRef A reference to the order sequence the vehicle processes.
@ -83,7 +83,7 @@ public interface InternalVehicleService
/**
* Updates the vehicle's current orientation angle (-360..360 degrees, or {@link Double#NaN}, if
* the vehicle doesn't provide an angle).
* 如果车辆不提供角度则更新车辆的当前方向角-360..360度{@link double}如果车辆不提供角度
* 如果车辆不提供角度则更新车辆的当前方向角-360..360度{@link double}如果车辆不提供角度.
*
* @param ref A reference to the vehicle to be modified.
* @param angle The vehicle's orientation angle.
@ -97,7 +97,7 @@ public interface InternalVehicleService
/**
* Places a vehicle on a point.
* 将车辆放在一个点上
* 将车辆放在一个点上.
*
* @param vehicleRef A reference to the vehicle to be modified.
* @param pointRef A reference to the point on which the vehicle is to be placed.
@ -125,7 +125,7 @@ public interface InternalVehicleService
/**
* Updates the vehicle's pose.
* 更新车辆的姿势
* 更新车辆的姿势.
*
* @param ref A reference to the vehicle to be modified.
* @param pose The vehicle's new pose.
@ -143,7 +143,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's processing state.
* 更新车辆的处理状态
* 更新车辆的处理状态.
*
* @param ref A reference to the vehicle to be modified.
* @param state The vehicle's new processing state.
@ -154,7 +154,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's recharge operation.
* 更新车辆的充电操作
* 更新车辆的充电操作.
*
* @param ref A reference to the vehicle to be modified.
* @param rechargeOperation The vehicle's new recharge action.
@ -165,7 +165,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's claimed resources.
* 更新车辆声称的资源
* 更新车辆生成的资源
*
* @param ref A reference to the vehicle to be modified.
* @param resources The new resources.
@ -179,7 +179,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's allocated resources.
* 更新车辆分配的资源
* 更新车辆已分配的资源.
*
* @param ref A reference to the vehicle to be modified.
* @param resources The new resources.
@ -193,7 +193,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's state.
* 更新车辆的状态
* Update Vehicle Status
*
* @param ref A reference to the vehicle to be modified.
* @param state The vehicle's new state.
@ -204,7 +204,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's length.
* 更新车辆的长度
* 更新车辆长度.
*
* @param ref A reference to the vehicle to be modified.
* @param length The vehicle's new length.
@ -218,7 +218,7 @@ public interface InternalVehicleService
/**
* Updates the vehicle's bounding box.
* 更新车辆的边界框
* 更新车辆的边界框.
*
* @param ref A reference to the vehicle.
* @param boundingBox The vehicle's new bounding box (in mm).
@ -234,7 +234,7 @@ public interface InternalVehicleService
/**
* Updates a vehicle's transport order.
* 更新车辆的运输订单
* 更新车辆运输订单.
*
* @param vehicleRef A reference to the vehicle to be modified.
* @param orderRef A reference to the transport order the vehicle processes.

View File

@ -1633,16 +1633,39 @@ public class Vehicle
ERROR,
/**
* The vehicle is currently idle/available for processing movement orders.
* 空闲对应科聪控制器状态0x00
*/
IDLE,
/**
* The vehicle is processing a movement order.
* 运行对应科聪控制器状态0x01
*/
EXECUTING,
/**
* The vehicle is currently recharging its battery/refilling fuel.
*/
CHARGING
CHARGING,
/**
* 暂停对应科聪控制器状态0x02
*/
PAUSED,
/**
* 车辆初始化中对应科聪控制器状态0x03
*/
INITIALIZING,
/**
* 人工确认,对应科聪控制器状态0x04
*/
CONFIRMATION,
/**
* 车辆未初始化对应科聪控制器状态0x05
*/
UNINITIALIZED,
/**
* 低电量模式
*/
FEED
}
/**
@ -1652,19 +1675,23 @@ public class Vehicle
/**
* The vehicle's reported position is ignored.
* 车辆报告的位置将被忽略
*/
TO_BE_IGNORED,
/**
* The vehicle's reported position is noticed, meaning that resources will not be reserved for
* it.
* 车辆报告的位置会被注意到这意味着不会为其保留资源
*/
TO_BE_NOTICED,
/**
* The vehicle's reported position is respected, meaning that resources will be reserved for it.
* 车辆报告的位置将被遵守这意味着将为其保留资源
*/
TO_BE_RESPECTED,
/**
* The vehicle is fully integrated and may be assigned to transport orders.
* 车辆已完全集成可以分配给运输订单
*/
TO_BE_UTILIZED
}

View File

@ -14,3 +14,11 @@ dependencies {
task release {
dependsOn build
}
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
tasks.withType(Javadoc){
options.encoding="utf-8"
}

View File

@ -9,8 +9,18 @@ apply from: "${rootDir}/gradle/publishing-java.gradle"
dependencies {
api project(':opentcs-api-injection')
api project(':opentcs-common')
//fastjson
implementation 'com.alibaba:fastjson:1.2.83'
}
task release {
dependsOn build
}
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
tasks.withType(Javadoc){
options.encoding="utf-8"
}

View File

@ -1,5 +1,3 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static java.util.Objects.requireNonNull;
@ -8,24 +6,19 @@ import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.opentcs.common.LoopbackAdapterConstants;
import org.opentcs.communication.http.enums.Actions;
import org.opentcs.communication.http.service.ExecuteAction;
import org.opentcs.communication.http.service.ExecuteMove;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Pose;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.data.model.Triple;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.Route.Step;
@ -37,22 +30,9 @@ import org.opentcs.drivers.vehicle.SimVehicleCommAdapter;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.drivers.vehicle.VehicleProcessModel;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.opentcs.kc.udp.Service.ActImmediately;
import org.opentcs.kc.udp.Service.ConfirmRelocation;
import org.opentcs.kc.udp.Service.HybridNavigation;
import org.opentcs.kc.udp.Service.ManualPosition;
import org.opentcs.kc.udp.Service.QryRobotRunStatus;
import org.opentcs.kc.udp.Service.QryRobotStatus;
import org.opentcs.kc.udp.Service.ReadValue;
import org.opentcs.kc.udp.Service.SubCargoStatus;
import org.opentcs.kc.udp.Service.SubRobotStatue;
import org.opentcs.kc.udp.Service.SwitchAutomaticMode;
import org.opentcs.kc.udp.Service.SwitchManualMode;
import org.opentcs.kc.udp.agv.param.function.af.LocationStatusInfo;
import org.opentcs.kc.udp.agv.param.function.af.QueryRobotStatusRsp;
import org.opentcs.kc.udp.agv.param.function.b0.QueryCargoStatusRsp;
import org.opentcs.kc.udp.agv.param.function.x17.QueryRobotRunStatusRsp;
import org.opentcs.kc.udp.agv.param.rsp.RcvEventPackage;
import org.opentcs.manage.entity.AgvInfo;
import org.opentcs.manage.entity.AgvInfoParams;
import org.opentcs.manage.entity.AgvStatus;
import org.opentcs.util.ExplainedBoolean;
import org.opentcs.virtualvehicle.VelocityController.WayEntry;
import org.slf4j.Logger;
@ -70,7 +50,6 @@ public class LoopbackCommunicationAdapter
/**
* The name of the load handling device set by this adapter.
* 此适配器设置的负载处理设备的名称
*/
public static final String LHD_NAME = "default";
/**
@ -79,84 +58,54 @@ public class LoopbackCommunicationAdapter
private static final Logger LOG = LoggerFactory.getLogger(LoopbackCommunicationAdapter.class);
/**
* An error code indicating that there's a conflict between a load operation and the vehicle's
* 一个错误代码指示加载作与车辆的
* current load state.
* 当前负载状态
*/
private static final String LOAD_OPERATION_CONFLICT = "cannotLoadWhenLoaded";
/**
* An error code indicating that there's a conflict between an unload operation and the vehicle's
* 一个错误代码指示卸载作与车辆的
* current load state.
* 当前负载状态
*/
private static final String UNLOAD_OPERATION_CONFLICT = "cannotUnloadWhenNotLoaded";
/**
* The time (in ms) of a single simulation step.
* 单个仿真步骤的时间 毫秒
*/
private static final int SIMULATION_PERIOD = 100;
/**
* This instance's configuration.
* 此实例的配置
*/
private final VirtualVehicleConfiguration configuration;
/**
* Indicates whether the vehicle simulation is running or not.
* 指示车辆模拟是否正在运行
*/
private volatile boolean isSimulationRunning;
/**
* The vehicle to this comm adapter instance.
* 车辆到此通信适配器实例
*/
private final Vehicle vehicle;
/**
* The vehicle's load state.
* 车辆的负载状态
*/
private LoadState loadState = LoadState.EMPTY;
/**
* Whether the loopback adapter is initialized or not.
* 环回适配器是否已初始化
*/
private boolean initialized;
/**
* 0xAF上报截止时间.
* 动作执行状态: true = 执行中false = 执行结束
*/
private long sub0xafDeadline;
private boolean ACTION_STATUS;
/**
* 0xB0上报截止时间.
* 通讯序列号
*/
private long sub0xb0Deadline;
private static int serialNum = 0;
/**
* 创建线程
* 记录执行订单名称
*/
private final ExecutorService messageProcessingPool = new ThreadPoolExecutor(
4,
8,
60L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
private static String ORDER_NAME;
/**
* 订阅状态
* 记录当前车辆位置
*/
private static boolean SUBSCRIBE_STATUS;
/**
* 最后经过点位
*/
private static String LAST_PASSED_POINT;
/**
* 唯一订单name.
* 用于判断是否切换执行订单
*/
private static String uniqueOrderName;
/**
* AGV最后上报时间
*/
private static long AFLastReportTime;
private static String CURRENT_POS;
/**
* Creates a new instance.
@ -218,52 +167,41 @@ public class LoopbackCommunicationAdapter
@Override
public void propertyChange(PropertyChangeEvent evt) {
//调用父类的 propertyChange 方法处理事件
super.propertyChange(evt);
//如果事件源不是 LoopbackVehicleModel 类型直接返回
if (!((evt.getSource()) instanceof LoopbackVehicleModel)) {
return;
}
//如果事件属性名为 LOAD_HANDLING_DEVICES
if (Objects.equals(
evt.getPropertyName(),
VehicleProcessModel.Attribute.LOAD_HANDLING_DEVICES.name()
)) {
if (!getProcessModel().getLoadHandlingDevices().isEmpty()
&& getProcessModel().getLoadHandlingDevices().get(0).isFull()) {
//检查负载处理设备是否为空且第一个设备是否满载更新负载状态为 FULL 并设置车辆长度为加载状态下的长度
loadState = LoadState.FULL;
getProcessModel().setBoundingBox(
getProcessModel().getBoundingBox().withLength(configuration.vehicleLengthLoaded())
);
}
else {
//否则更新负载状态为 EMPTY 并设置车辆长度为未加载状态下的长度
loadState = LoadState.EMPTY;
getProcessModel().setBoundingBox(
getProcessModel().getBoundingBox().withLength(configuration.vehicleLengthUnloaded())
);
}
}
//如果事件属性名为 SINGLE_STEP_MODE
if (Objects.equals(
evt.getPropertyName(),
LoopbackVehicleModel.Attribute.SINGLE_STEP_MODE.name()
)) {
// When switching from single step mode to automatic mode and there are commands to be
// processed, ensure that we start/continue processing them.
//如果单步模式关闭待处理命令队列非空且模拟未运行则启动车辆模拟
if (!getProcessModel().isSingleStepModeEnabled()
&& !getSentCommands().isEmpty()
&& !isSimulationRunning) {
//标记模拟正在运行
isSimulationRunning = true;
//提交任务到线程池执行队列中的第一个命令
((ExecutorService) getExecutor()).submit(
() -> startVehicle(getSentCommands().peek())
() -> startVehicleExec(getSentCommands().peek())
);
}
}
@ -294,41 +232,32 @@ public class LoopbackCommunicationAdapter
public synchronized void sendCommand(MovementCommand cmd) {
requireNonNull(cmd, "cmd");
System.out.println(cmd);
//停止订阅
SUBSCRIBE_STATUS = false;
//订单ID
String orderName = cmd.getTransportOrder().getName();
//下发起点
String sourcePointName = null;
String sourcePoint = null;
if (cmd.getStep().getSourcePoint() != null) {
sourcePointName = cmd.getStep().getSourcePoint().getName();
sourcePoint = cmd.getStep().getSourcePoint().getName();
//下发AGV移动指令
ExecuteMove.sendCmd(cmd, getSerialNum());
}
//检查当前车辆模型是否处于单步模式且未运行若满足条件则设置运行状态为true
// Start the simulation task if we're not in single step mode and not simulating already.
if (!getProcessModel().isSingleStepModeEnabled()
&& !isSimulationRunning) {
System.out.println("sendCommand-----====123321");
isSimulationRunning = true;
if (uniqueOrderName == null || !uniqueOrderName.equals(orderName)) {
//设置唯一订单名称
uniqueOrderName = orderName;
//记录订单起点---更新复位后可
LAST_PASSED_POINT = sourcePointName;
}
//记录订单名称和name
ORDER_NAME = cmd.getTransportOrder().getName();
//下发起点
CURRENT_POS = sourcePoint;
// 展示模拟车辆
// The command is added to the sent queue after this method returns. Therefore
// we have to explicitly start the simulation like this.
if (getSentCommands().isEmpty()) {
((ExecutorService) getExecutor()).submit(() -> startVehicle(cmd));
((ExecutorService) getExecutor()).submit(() -> startVehicleExec(cmd));
}
else {
((ExecutorService) getExecutor()).submit(
() -> startVehicle(getSentCommands().peek())
() -> startVehicleExec(getSentCommands().peek())
);
}
}
@ -341,109 +270,14 @@ public class LoopbackCommunicationAdapter
@Override
public void processMessage(Object message) {
LOG.info("processMessage Received message: {}", message);
if (message instanceof byte[]) {
updateVehicleModel(message);
}
else if (message instanceof HashMap<?, ?>) {
//todo 测试代码----成功
HashMap<?, ?> msg = (HashMap<?, ?>) message;
getProcessModel().setEnergyLevel((int) msg.get("energy"));
getProcessModel().setState(Vehicle.State.EXECUTING);
long positionX = (long) msg.get("positionX");
long positionY = (long) msg.get("positionY");
Triple triple = new Triple(positionX, positionY, 0);
double positionAngle = (double) msg.get("positionAngle");
getProcessModel().setPose(new Pose(triple, positionAngle));
}
}
private void updateVehicleModel(Object message) {
try {
byte[] body = (byte[])message;
RcvEventPackage rcv = new RcvEventPackage(body[22], body);
if (body[21] == (byte) 0xAF) {
//最后上报时间
Date now = new Date();
AFLastReportTime = now.getTime();
System.out.println("0xAF sub success");
//AGV状态订阅
QueryRobotStatusRsp queryRobotStatusRsp = new QueryRobotStatusRsp(rcv.getDataBytes());
System.out.println();
//电量--目前无电量返回设置一个随机值
float batteryPercentage = queryRobotStatusRsp.batteryStatusInfo.batteryPercentage;
getProcessModel().setEnergyLevel(89);
//设置AGV最后一个点位置,不设置最后经过点opentcs无法调度
String vehicleNowPosition = getProcessModel().getPosition();
String lastPassPointId = (queryRobotStatusRsp.locationStatusInfo.lastPassPointId).toString();
if (vehicleNowPosition == null || !vehicleNowPosition.equals(lastPassPointId)) {
if ("0".equals(lastPassPointId)) {
//最终经过点为0手动设置当前位置
initVehiclePosition("0");
} else {
initVehiclePosition(lastPassPointId);
}
}
//设置车辆姿势(官方弃用设置车辆精确位置)-------------------目前车辆返回位置为固定值
/* long positionX = (long) queryRobotStatusRsp.locationStatusInfo.globalX;
long positionY = (long) queryRobotStatusRsp.locationStatusInfo.globalY;
Triple triple = new Triple(positionX, positionY, 0);
double positionAngle = queryRobotStatusRsp.locationStatusInfo.absoluteDirecAngle;
LocalDateTime now = LocalDateTime.now();
System.out.println(now + "[positionX:" + positionX + "] [positionY:" + positionY + "] [positionAngle:" + positionAngle + "]");
getProcessModel().setPose(new Pose(triple, positionAngle));*/
//到期续订
renewalSubscribe0xAF();
} else if (body[21] == (byte) 0xB0) {
System.out.println("0xB0 sub success");
//载货状态订阅
QueryCargoStatusRsp queryCargoStatusRsp = new QueryCargoStatusRsp(rcv.getDataBytes());
if (queryCargoStatusRsp.isCargo == 0) {
this.loadState = LoadState.EMPTY;
}
else {
this.loadState = LoadState.FULL;
}
//到期续订
renewalSubscribe0xB0();
}
} catch (Exception e) {
throw new RuntimeException("processMessage_messageExecutorPool:" + e);
}
}
/**
* 订阅到期自动续订0xAF.
*/
private void renewalSubscribe0xAF() {
Date now = new Date();
if (sub0xafDeadline - now.getTime() <= SubRobotStatue.intervalTime && SUBSCRIBE_STATUS) {
sub0xAF();
}
}
/**
* 订阅到期自动续订0xB0.
*/
private void renewalSubscribe0xB0() {
Date now = new Date();
if (sub0xb0Deadline - now.getTime() <= SubCargoStatus.intervalTime && SUBSCRIBE_STATUS) {
sub0xB0();
if (message instanceof AgvInfo agvInfo) {
//通讯适配器车辆模型更新
handleCallbacks(agvInfo.getParams());
} else if (message instanceof AgvStatus agvStatus) {
//自动管理通讯适配器状态和适配器动作执行状态
handleAdapterAuthEnable(agvStatus);
}
}
@ -504,13 +338,11 @@ public class LoopbackCommunicationAdapter
@Override
protected synchronized void connectVehicle() {
// getProcessModel().setCommAdapterConnected(true);
initAGV();
getProcessModel().setCommAdapterConnected(true);
}
@Override
protected synchronized void disconnectVehicle() {
SUBSCRIBE_STATUS = false;
getProcessModel().setCommAdapterConnected(false);
}
@ -548,70 +380,107 @@ public class LoopbackCommunicationAdapter
}
/**
* 执行车辆移动指令
*
* @param command 移动指令
* 开始动作执行
* @param command 命令
*/
private void startVehicle(MovementCommand command) {
LOG.debug("-Starting vehicle for command: {}", command);
private void startVehicleExec(MovementCommand command) {
LOG.debug("VEHICLE: {} BEGINS TO EXECUTE THE COMMAND: {}", getProcessModel().getName(),command);
Step step = command.getStep();
getProcessModel().setState(Vehicle.State.EXECUTING);
//设置车辆占用
// vehicle.withAllocatedResources();
List<Set<TCSResourceReference<?>>> allocatedResources = vehicle.getAllocatedResources();
for (Set<TCSResourceReference<?>> allocatedResource : allocatedResources) {
System.out.println("-startVehicle allocatedResource" + allocatedResource);
}
// vehicle.withClaimedResources()
if (step.getPath() == null) {
System.out.println("-startVehicle operation...");
operationExec(command);
actionExec(command);
} else {
getProcessModel().getVelocityController().addWayEntry(
new WayEntry(
step.getPath().getLength(),
maxVelocity(step),
step.getDestinationPoint().getName(),
step.getVehicleOrientation()
)
);
sendMoveCmdToKc(command);
LOG.debug("-Starting movement ...");
//todo 移动
movementExec(command);
}
}
/**
* 发送移动指令到KC
* @param command 指令内容
* 执行动作
* @param command 命令
*/
private void sendMoveCmdToKc(MovementCommand command) {
//订单ID
String orderName = command.getTransportOrder().getName();
private void actionExec(MovementCommand command){
//下发起点
String sourcePointName = null;
if (command.getStep().getSourcePoint() == null) {
//起点为空不下发路径
System.out.println("sourcePointName is null");
return;
String action = command.getOperation();
LOG.info( "Starting action {}...", action);
//校验动作是否存在
if (!Actions.contains(action)) {
LOG.info( "Action {} does not exist!", action);
//结束动作
finishCmd(command);
//继续向下执行
nextCmd();
} else {
//设置状态为执行中通过回调修改参数结束动作阻塞
ACTION_STATUS = true;
//下发动作
ExecuteAction.sendCmd(command.getOperation(), getSerialNum());
// 结束动作
finishCmd(command);
//进入阻塞
while (ACTION_STATUS) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
//继续执行动作
nextCmd();
}
sourcePointName = command.getStep().getSourcePoint().getName();
}
//下发终点
String destinationPointName = command.getStep().getDestinationPoint().getName();
/**
* 执行移动
* @param command 命令
*/
private void movementExec(MovementCommand command) {
//当前点位操作
String operation = command.getOperation();
String currentPosition = getProcessModel().getPosition();
if (currentPosition != null && currentPosition.equals(CURRENT_POS)) {
getExecutor().schedule(
() -> movementExec(command),
500,
TimeUnit.MILLISECONDS
);
} else {
CURRENT_POS = currentPosition;
//AGV控制器执行命令为实现不停车导航所以指令下发不进行阻塞
HybridNavigation.command(orderName, sourcePointName, destinationPointName, operation);
System.out.println("-Starting movement ...");
if (!command.hasEmptyOperation()) {
actionExec(command);
} else {
finishCmd(command);
nextCmd();
}
}
}
private void finishCmd(MovementCommand command) {
if (Objects.equals(getSentCommands().peek(), command)) {
//清理任务队列
getProcessModel().commandExecuted(requireNonNull(getSentCommands().poll()));
} else {
LOG.warn("Command {} was not the first command in the queue!", command);
}
}
private void nextCmd() {
if (getSentCommands().isEmpty() && getUnsentCommands().isEmpty() || getProcessModel().isSingleStepModeEnabled()) {
LOG.info("Vehicle: {} ,order: {} is done", vehicle.getName(), ORDER_NAME);
isSimulationRunning = false;
} else {
LOG.info("Vehicle: {} ,order: {} exec next command: {}", vehicle.getName(), ORDER_NAME, getSentCommands().peek());
((ExecutorService) getExecutor()).submit(
() -> startVehicleExec(getSentCommands().peek())
);
}
}
private void startVehicleSimulation(MovementCommand command) {
@ -652,67 +521,12 @@ public class LoopbackCommunicationAdapter
: step.getPath().getMaxVelocity();
}
/**
* 执行运行指令.
*
* @param command 要执行的命令
*/
private void movementExec(MovementCommand command) {
//检查当前车辆模型的速度控制器是否有路径条目若无则直接返回
if (!getProcessModel().getVelocityController().hasWayEntries()) {
return;
}
//获取AGV最终经过点
String currentPoint = null;
long specifyTheTime = SubRobotStatue.intervalTime * 2 + 200;
Date now = new Date();
if ((now.getTime() - AFLastReportTime) >= specifyTheTime && isVehicleConnected()) {
//默认订阅结束
QueryRobotStatusRsp qryRobotStatusRsp = QryRobotStatus.command();
currentPoint = (qryRobotStatusRsp.locationStatusInfo.lastPassPointId).toString();
initVehiclePosition(currentPoint);
} else {
//订阅未结束
currentPoint = getProcessModel().getPosition();
}
//判断当前AGV点位是否和最新点位相同
if (currentPoint.equals(LAST_PASSED_POINT)) {
//若是则重新调度当前方法进行递归
getExecutor().schedule(
() -> movementExec(command),
SIMULATION_PERIOD * 2,
TimeUnit.MILLISECONDS
);
} else {
//若否更新当前车辆位置并根据命令是否有操作决定进入操作模拟或完成命令并模拟下一个命令
LAST_PASSED_POINT = currentPoint;
System.out.println("-emptyOperation:" + command.hasEmptyOperation());
if (!command.hasEmptyOperation()) {
System.out.println("-movementExec operation...");
//执行AGV动作
operationExec(command);
} else {
LOG.debug("-Movement Finishing command.");
//完成当前命令
finishMoveCmd(command);
//执行下一个命令
nextCommand();
}
}
}
/**
* Simulate the movement part of a MovementCommand.
*
* @param command The command to simulate.
*/
private void movementSimulation(MovementCommand command) {
//检查当前车辆模型的速度控制器是否有路径条目若无则直接返回
if (!getProcessModel().getVelocityController().hasWayEntries()) {
return;
}
@ -721,9 +535,7 @@ public class LoopbackCommunicationAdapter
getProcessModel().getVelocityController().advanceTime(getSimulationTimeStep());
WayEntry currentWayEntry = getProcessModel().getVelocityController().getCurrentWayEntry();
//if we are still on the same way entry then reschedule to do it again
//获取当前路径条目并推进时间步长检查是否仍处于同一路径条目
if (prevWayEntry == currentWayEntry) {
//若是则重新调度当前方法以继续模拟
getExecutor().schedule(
() -> movementSimulation(command),
SIMULATION_PERIOD,
@ -733,7 +545,6 @@ public class LoopbackCommunicationAdapter
else {
//if the way enties are different then we have finished this step
//and we can move on.
//若否更新车辆位置为上一路径条目的目标点并根据命令是否有操作决定进入操作模拟或完成命令并模拟下一个命令
getProcessModel().setPosition(prevWayEntry.getDestPointName());
LOG.debug("Movement simulation finished.");
if (!command.hasEmptyOperation()) {
@ -751,73 +562,8 @@ public class LoopbackCommunicationAdapter
}
}
/**
* 执行移动命令的操作部分
*
* @param command 要执行的命令
*/
private synchronized void operationExec(MovementCommand command) {
while (true) {
//AGV运行中不能执行动作进入阻塞状态
QueryRobotStatusRsp qryRobotStatusRsp = QryRobotStatus.command();
if (qryRobotStatusRsp.runningStatusInfo.agvStatus == 0) {
System.out.println();
System.out.println("-operationExec AGV exec operation");
break;
}
try {
//AGV运行中阻塞500ms后轮询
System.out.println("-operationExec AGV IS Running, Thread sleep 500ms");
Thread.sleep(500);
} catch (InterruptedException e) {
// 处理中断异常重置中断状态
Thread.currentThread().interrupt();
}
}
//获取动作
String operation = command.getOperation();
//下发立即动作指令
if ("LIFT".equals(operation)) {
System.out.println("-Operation exec LoadOperation.");
LOG.debug("-Operation exec LoadOperation.");
float height = 1.1f;
byte modeOfMovement = 0x01;
ActImmediately.allotsOperation(height, modeOfMovement);
} else if ("DECLINE".equals(operation)) {
System.out.println("-Operation exec UnloadOperation.");
LOG.debug("-Operation exec UnloadOperation.");
float height = 0.1f;
byte modeOfMovement = 0x02;
//降叉齿指令
ActImmediately.allotsOperation(height, modeOfMovement);
} else if (operation.equals(this.getRechargeOperation())) {
System.out.println("-Operation exec RechargeOperation.");
LOG.debug("-Operation exec RechargeOperation.");
//执行充电指令
} else {
System.out.println("-NOT EXECUTED OPERATION:" + operation);
LOG.debug("-NOT EXECUTED OPERATION:" + operation);
}
//完成当前命令
finishMoveCmd(command);
//执行下一个命令
nextCommand();
}
/**
* Simulate the operation part of a movement command.
* 模拟移动命令的操作部分
*
* @param command The command to simulate.
* @param timePassed The amount of time passed since starting the simulation.
@ -872,7 +618,6 @@ public class LoopbackCommunicationAdapter
/**
* Simulate recharging the vehicle.
* 模拟为车辆充电
*
* @param rechargePosition The vehicle position where the recharge simulation was started.
* @param rechargePercentage The recharge percentage of the vehicle while it is charging.
@ -918,51 +663,14 @@ public class LoopbackCommunicationAdapter
+ (float) (configuration.rechargePercentagePerSecond() / 1000.0) * SIMULATION_PERIOD;
}
/**
* 结束命令
* @param command 指令
*/
private void finishMoveCmd(MovementCommand command) {
//检查已发送命令队列的大小是否小于等于1且未发送命令队列是否为空
System.out.println("-finishMoveCmd getSentCommands size:" + getSentCommands().size());
if (getSentCommands().size() <= 1 && getUnsentCommands().isEmpty()) {
System.out.println("-getSentCommands <= 1 && getUnsentCommands is null");
getProcessModel().setState(Vehicle.State.IDLE);
//清除订单对应唯一ID
HybridNavigation.delUniqueOrderID(command);
//任务执行结束开启订阅
SUBSCRIBE_STATUS = true;
sub0xAF();
isSimulationRunning = false;
LOG.debug("-callback wms");
//订单结束调用wms接口
}
//如果传入指令和移动指令队列第一条数据相同
if (Objects.equals(getSentCommands().peek(), command)) {
// 完成当前任务
getProcessModel().commandExecuted(getSentCommands().poll());
} else {
LOG.warn(
"-{}: Exec command not oldest in sent queue: {} != {}",
getName(),
command,
getSentCommands().peek()
);
}
}
private void finishMovementCommand(MovementCommand command) {
//Set the vehicle state to idle
// if (getSentCommands().size() <= 1 && getUnsentCommands().isEmpty()) {
// getProcessModel().setState(Vehicle.State.IDLE);
// }
if (getSentCommands().size() <= 1 && getUnsentCommands().isEmpty()) {
getProcessModel().setState(Vehicle.State.IDLE);
}
if (Objects.equals(getSentCommands().peek(), command)) {
// Let the comm adapter know we have finished this command. 让通信适配器知道我们已经完成了这个命令
// Let the comm adapter know we have finished this command.
getProcessModel().commandExecuted(getSentCommands().poll());
// HybridNavigation.delUniqueOrderID(command);
}
else {
LOG.warn(
@ -974,20 +682,6 @@ public class LoopbackCommunicationAdapter
}
}
void nextCommand() {
if (getUnsentCommands().isEmpty() || getProcessModel().isSingleStepModeEnabled()) {
LOG.debug("Vehicle exec is done.");
getProcessModel().setState(Vehicle.State.IDLE);
isSimulationRunning = false;
}
else {
LOG.debug("Triggering exec for next command: {}", getSentCommands().peek());
((ExecutorService) getExecutor()).submit(
() -> startVehicle(getSentCommands().peek())
);
}
}
void simulateNextCommand() {
if (getSentCommands().isEmpty() || getProcessModel().isSingleStepModeEnabled()) {
LOG.debug("Vehicle simulation is done.");
@ -1014,69 +708,100 @@ public class LoopbackCommunicationAdapter
FULL;
}
// /**
// * 通讯适配器处理平台上报信息
// * @param agvInfo 平台上报信息对象
// */
// private void handleCallbacks(AgvInfo agvInfo) {
//// //获取响应指令码
//// JSONObject jsonObject = JSON.parseObject(data);
//// Integer type = jsonObject.getInteger("type");
//
// //更新车辆当前位置和锁占用方法
//// Point point = new Point("1");
//// point.withPose(new Pose(new Triple(0, 0, 0), 0));
//// TCSResourceReference<Point> reference = point.getReference();
//// vehicle.withCurrentPosition(reference);
//// vehicle.withPose(new Pose(new Triple(0, 0, 0), 0));
// }
/**
* 初始化AGV
* 步骤:
* 1调度软件启动机器人启动无顺序要求
* 2等待调度系统以及机器人控制器启动完成调度系统启动即向机器人发送状态查询查询成功即为启动完成
* 3调度软件发送订阅信令至机器人表明订阅机器人状态信息或载货状态机器人接收到订阅信令会依据订阅要求推送订阅信息度软件需要根据订阅信令中上报持续时间提前刷新机器人推送的上报持续时间
* 4调度软件持续监控机器人实时状态
* 5导航初始化
* 通讯适配器处理平台上报信息
* @param params 平台上报信息对象
*/
private void initAGV() {
private void handleCallbacks(AgvInfoParams params) {
// //0xAF获取AGV状态
// System.out.println("=================---initAGV_0xAF");
// QueryRobotStatusRsp qryRobotStatusRsp = QryRobotStatus.command();
// if (qryRobotStatusRsp == null) {
// getProcessModel().setCommAdapterConnected(false);
// throw new RuntimeException("initAGV 0xAF response is null");
// }
//记录最终车辆等级
Vehicle.IntegrationLevel integrationLevel;
//记录车辆最终状态
Vehicle.State vehicleState;
//设置订阅状态为true && 开启0xAF订阅
SUBSCRIBE_STATUS = true;
sub0xAF();
//开启0xB0订阅
// sub0xB0()
//查询机器人运行状态命令码0x17等待机器人定位状态为定位完成
System.out.println("=================---initAGV_0x17");
QueryRobotRunStatusRsp qryRobotRunStatusRsp = QryRobotRunStatus.command();
if (qryRobotRunStatusRsp.robotLocalizationState == 0) {
getProcessModel().setCommAdapterConnected(false);
throw new RuntimeException("-If the AGV positioning fails, please perform manual positioning");
}
else if (qryRobotRunStatusRsp.robotLocalizationState == 2) {
getProcessModel().setCommAdapterConnected(false);
throw new RuntimeException("-agv positioning please try again later");
switch (params.getAgv_model()) {
case 3 -> integrationLevel = Vehicle.IntegrationLevel.TO_BE_UTILIZED;
case 6 -> integrationLevel = Vehicle.IntegrationLevel.TO_BE_IGNORED;
default -> integrationLevel = Vehicle.IntegrationLevel.TO_BE_RESPECTED;
}
//切换成自动模式命令码0x03变量NaviControl 修改为1
System.out.println();
System.out.println("=================---initAGV_0x03-----111111");
SwitchAutomaticMode.command();
//更新车辆状态
if (params.getCharge_status() == 1) {
vehicleState = Vehicle.State.CHARGING;
} else {
switch (params.getAgv_status()) {
case 0 -> vehicleState = Vehicle.State.IDLE;
case 1 -> vehicleState = Vehicle.State.EXECUTING;
case 2 -> {
vehicleState = Vehicle.State.PAUSED;
integrationLevel = Vehicle.IntegrationLevel.TO_BE_IGNORED;
}
case 3 -> vehicleState = Vehicle.State.INITIALIZING;
case 4 -> vehicleState = Vehicle.State.CONFIRMATION;
case 5 -> vehicleState = Vehicle.State.UNINITIALIZED;
default -> vehicleState = Vehicle.State.UNKNOWN;
}
}
//打开通讯适配器连接
getProcessModel().setCommAdapterConnected(true);
//切换车辆状态
getProcessModel().setState(Vehicle.State.IDLE);
if (getProcessModel().isVehiclePaused()) {
integrationLevel = Vehicle.IntegrationLevel.TO_BE_IGNORED;
}
//更新车辆姿态
if (params.getPoint() != 0) {
if (!Objects.equals(getProcessModel().getPosition(), params.getPoint().toString())) {
getProcessModel().setPosition(params.getPoint().toString()); //更新最终经过点
}
getProcessModel().setPose(new Pose(new Triple(Math.round(params.getX()), Math.round(params.getY()), 0), params.getAngle()));
} else {
//最后经过点为0应该是车辆重启过车辆关机时应该正常对点位进行交管防止车辆碰撞
//todo 可能需要实现原点自动定位暂时不做处理
}
//更新电量
getProcessModel().setEnergyLevel(Math.round(params.getPower() * 100));
//更新车辆等级
getProcessModel().integrationLevelChangeRequested(integrationLevel);
//更新车辆状态
getProcessModel().setState(vehicleState);
//更新载货状态
loadState = params.getCargo_status() == 1 ? LoadState.FULL : LoadState.EMPTY;
}
private void sub0xAF(){
Date now = new Date();
sub0xafDeadline = now.getTime() + SubRobotStatue.subDuration;
messageProcessingPool.submit(() -> {
SubRobotStatue.command();
});
private synchronized int getSerialNum() {
serialNum++;
return serialNum;
}
private void sub0xB0(){
Date now = new Date();
sub0xb0Deadline = now.getTime() + 10000;
messageProcessingPool.submit(() -> {
SubCargoStatus.command();
});
private void handleAdapterAuthEnable(AgvStatus agvStatus) {
if (agvStatus.getActionStatus()) {
ACTION_STATUS = false;
}
if (agvStatus.getStatus()) {
enable();
} else {
disable();
}
}
}

View File

@ -296,30 +296,37 @@ public class LoopbackVehicleModel
public enum Attribute {
/**
* Indicates a change of the virtual vehicle's single step mode setting.
* 表示虚拟车辆的单步模式设置的更改
*/
SINGLE_STEP_MODE,
/**
* Indicates a change of the virtual vehicle's default operating time.
* 表示虚拟车辆的默认运行时间的更改
*/
OPERATING_TIME,
/**
* Indicates a change of the virtual vehicle's maximum acceleration.
* 表示虚拟车辆的最大加速度的变化
*/
ACCELERATION,
/**
* Indicates a change of the virtual vehicle's maximum deceleration.
* 表示虚拟车辆的最大减速度发生变化
*/
DECELERATION,
/**
* Indicates a change of the virtual vehicle's maximum forward velocity.
* 表示虚拟车辆的最大前进速度的变化
*/
MAX_FORWARD_VELOCITY,
/**
* Indicates a change of the virtual vehicle's maximum reverse velocity.
* 表示虚拟车辆的最大倒车速度的变化
*/
MAX_REVERSE_VELOCITY,
/**
* Indicates a change of the virtual vehicle's paused setting.
* 表示更改虚拟车辆的暂停设置
*/
VEHICLE_PAUSED,
}

View File

@ -22,8 +22,17 @@ dependencies {
implementation group: 'org.springframework.retry', name: 'spring-retry', version: '2.0.5'
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.16'
// Lombok依赖
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
//fastjson
implementation 'com.alibaba:fastjson:1.2.83'
//okhttp
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
// MQTT
implementation 'io.moquette:moquette-broker:0.15'
// MQTT
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
}
processResources.doLast {
@ -40,3 +49,11 @@ processResources.doLast {
task release {
dependsOn build
}
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
tasks.withType(Javadoc){
options.encoding="utf-8"
}

View File

@ -0,0 +1,56 @@
package org.opentcs.charge;
import java.util.ArrayList;
import java.util.HashMap;
import org.opentcs.charge.entity.ChargePiles;
import org.opentcs.data.model.Vehicle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @date 2025-5-19
* @desc 充电策略
*/
public class ChargeStrategy {
/**
* LOG
*/
private static final Logger LOG = LoggerFactory.getLogger(ChargeStrategy.class);
/**
* 所有车辆电量key为车辆名称value为电量百分比
*/
public static HashMap<String, Integer> vehiclePower = new HashMap<>();
/**
* 充电桩维护对象
*/
public static ArrayList<ChargePiles> AllChargePiles;
/**
* 闲时充电
* @param vehicle 车辆
* @param power 电量百分比
* @param batteryStatus 电池状态
* @param energyLevelThresholdSet 设定阈值
*/
public synchronized static void chargeStrategy(String vehicle, Integer power, Integer batteryStatus, Vehicle.EnergyLevelThresholdSet energyLevelThresholdSet) {
LOG.debug("vehicle: {}, start exec chargeStrategy" , vehicle);
//充电桩数组还是为空直接返回
if (AllChargePiles.isEmpty()) {
//无充电桩无法执行自动充电,返回未知状态
return;
}
//如果 电池为放电状态
if (batteryStatus == 0) {
} else { //释放充电中的车辆
}
}
}

View File

@ -0,0 +1,24 @@
package org.opentcs.charge.entity;
import lombok.Data;
@Data
public class ChargePiles {
/**
* 充电桩名称
*/
private String pilesName;
/**
* 占用车辆
*/
private String vehicleName;
/**
* 充电桩状态true充电中false空闲
*/
private Boolean status;
/**
* 电量
*/
private Integer power;
}

View File

@ -18,7 +18,7 @@ public interface GuestUserCredentials {
/**
* 主机IP
*/
String IP = "192.168.124.109";
String IP = "0.0.0.0";
/**
* 内核开放端口
*/

View File

@ -53,9 +53,5 @@ public interface LoopbackAdapterConstants {
* AGV 端口
*/
String AGV_PORT = "AGV:PORT";
/**
* 定制点位类型1=WMS请求点位
*/
String POINT_TYPE = "tcs:point";
}

View File

@ -0,0 +1,232 @@
package org.opentcs.communication.http;
import com.alibaba.fastjson.JSON;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc http客户端
*/
public class HttpClient {
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
private final OkHttpClient client;
private final String url = "http://192.168.124.121:2005";
public HttpClient() {
this.client = new OkHttpClient();
}
/**
* 发送 POST 请求
* @return ApiResponse 对象
*/
public String sendCommand(Object data) {
String jsonBody = JSON.toJSONString(data);
System.out.println("SENDING POST REQUEST data:" + jsonBody);
LOG.debug("SENDING POST REQUEST data: {}", jsonBody);
RequestBody body = RequestBody.create(jsonBody, MediaType.get("application/json"));
Request request = new Request.Builder().url(url).post(body).build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
} catch (IOException e) {
throw new RuntimeException("POST Request Failed", e);
}
}
/**
* 测试代码
*/
public static void main(String[] args) {
HttpClient httpClient = new HttpClient();
// { //0x02
// //变量成员
// ArrayList<org.opentcs.communication.http.dto.kc.cmd02.ValueMember> valueMemberList = new ArrayList<>();
// org.opentcs.communication.http.dto.kc.cmd02.ValueMember valueMember = new org.opentcs.communication.http.dto.kc.cmd02.ValueMember();
// valueMember.setLength(4);
// valueMember.setOffset(0);
// valueMemberList.add(valueMember);
//
// //变量结构体
// ArrayList<org.opentcs.communication.http.dto.kc.cmd02.StrValue> strValueList = new ArrayList<>();
// org.opentcs.communication.http.dto.kc.cmd02.StrValue strValue = new org.opentcs.communication.http.dto.kc.cmd02.StrValue();
// strValue.setName("NaviControl");
// strValue.setCount(1);
// strValue.setMember(valueMemberList);
// strValueList.add(strValue);
//
// //请求对象
// Request02 request02 = new Request02();
// request02.setVar(strValueList);
// request02.setVarCount(1);
//
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0x02), "OPENTCS", "KC-CTRL", request02);
//
// httpClient.sendCommand(baseRequestTo);
// }
// { //0xB0
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0xB0), "OPENTCS", "KC-CTRL", new Object());
// httpClient.sendCommand(baseRequestTo);
// }
// { //0xB2
// //action: 0x01
// Action01 action01 = new Action01();
// action01.setIsStopImmediately(1);
// ArrayList<Action01> actionList = new ArrayList<>();
// actionList.add(action01);
//
//// //action: 0x02
//// Action02 action02 = new Action02();
//// action02.setOrderId(1);
//// action02.setTaskKey(1);
//// ArrayList<Action02> actionList = new ArrayList<>();
//// actionList.add(action02);
////
//// //action: 0x03
//// Action03 action03 = new Action03();
//// action03.setOrderId(1);
//// action03.setIsStopImmediately(1);
//// ArrayList<Action03> actionList = new ArrayList<>();
//// actionList.add(action03);
//
// RequestB2 requestB2 = new RequestB2();
// requestB2.setActionType(KcActionCmdEnum.getValueByKey(0x03));
// requestB2.setActionParallelManner(0x02);
// requestB2.setParamSize(8);
// requestB2.setParamData(actionList);
//
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0xB2), "OPENTCS", "KC-CTRL", requestB2);
// httpClient.sendCommand(baseRequestTo);
// }
// { //0x17
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0x17), "OPENTCS", "KC-CTRL", new Object());
// httpClient.sendCommand(baseRequestTo);
// }
// { //0x1F
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0x1F), "OPENTCS", "KC-CTRL", new Object());
// httpClient.sendCommand(baseRequestTo);
// }
// { //0x14
// Request14 request14 = new Request14();
// request14.setX(3.0);
// request14.setY(2.0);
// request14.setAngle(1.0);
//
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0x14), "OPENTCS", "KC-CTRL", request14);
// httpClient.sendCommand(baseRequestTo);
// }
// { //AF
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0xAF), "OPENTCS", "KC-CTRL", new Object());
// httpClient.sendCommand(baseRequestTo);
// }
// { //0x03
// //变量成员
// ArrayList<ValueMember> valueMemberList = new ArrayList<>();
// org.opentcs.communication.http.dto.kc.cmd03.ValueMember valueMember = new org.opentcs.communication.http.dto.kc.cmd03.ValueMember();
// valueMember.setLength(4);
// valueMember.setOffset(0);
// valueMember.setVelue(1);
// valueMemberList.add(valueMember);
//
// //变量结构体
// ArrayList<StrValue> strValueList = new ArrayList<>();
// org.opentcs.communication.http.dto.kc.cmd03.StrValue strValue = new org.opentcs.communication.http.dto.kc.cmd03.StrValue();
// strValue.setName("NaviControl");
// strValue.setCount(1);
// strValue.setMember(valueMemberList);
// strValueList.add(strValue);
//
// //请求对象
// Request03 request03 = new Request03();
// request03.setVar(strValueList);
// request03.setVarCount(1);
//
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0x03), "OPENTCS", "KC-CTRL", request03);
//
// httpClient.sendCommand(baseRequestTo);
// }
// { //0xAE
// RequestAe ae = new RequestAe();
// ae.setOrderId(1);
// ae.setTaskKey(1);
// ae.setPointSize(2);
// ae.setPathSize(1);
//
// ArrayList<Point> pointList = new ArrayList<>();
// for (int i = 0; i < ae.getPointSize(); i++) {
// Point point = new Point();
// if (i == 0) {
// point.setPointId(1);
// point.setPointSerialNumber(0);
// point.setType(0);
// point.setIsFrontAngle(0);
// point.setFrontAngle(0);
// point.setPointActionSize(0);
// point.setPointActionList(new ArrayList<>());
// } else {
// point.setPointId(2);
// point.setPointSerialNumber(2);
// point.setType(1);
// point.setIsFrontAngle(0);
// point.setFrontAngle(0);
// point.setPointActionSize(1);
// for (int j = 0; j < point.getPointActionSize(); j++) {
// //实际代码中修改为实际逻辑
// point.setPointActionList(new ArrayList<>(Arrays.asList("LOAD")));
// }
// }
// pointList.add(point);
// }
//
// ArrayList<Path> pathList = new ArrayList<>();
// for (int i = 0; i < ae.getPathSize(); i++) {
// Path path = new Path();
// path.setPathId(12);
// path.setPathSerialNumber(1);
// path.setFixedAngle(0);
// path.setIsFixedAngle(0);
// path.setDrivePosture(1);
// path.setPathActionSize(0);
// path.setMaximumSpeed(1);
// path.setMaximumAngularVelocity(1);
// for (int j = 0; j < path.getPathActionSize(); j++) {
// path.setPathActionList(new ArrayList<>(Arrays.asList("test")));
// }
//
// if (path.getPathActionSize() == 0) {
// path.setPathActionList(new ArrayList<>());
// }
//
// pathList.add(path);
// }
//
// ae.setPoint(pointList);
// ae.setPath(pathList);
//
// BaseRequestTo baseRequestTo = new BaseRequestTo(KcCmdEnum.getValueByKey(0xAE), "OPENTCS", "KC-CTRL", ae);
//
// httpClient.sendCommand(baseRequestTo);
// }
}
}

View File

@ -0,0 +1,44 @@
package org.opentcs.communication.http.dto;
import jakarta.annotation.Nonnull;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc 实体基类
*/
@Data
public class BaseRequestTo {
public BaseRequestTo(
@Nonnull Integer type,
@Nonnull String sender,
@Nonnull String receiver,
@Nonnull Integer serial_num,
@Nonnull String time,
@Nonnull Object data
) {
this.type = type;
this.sender = sender;
this.receiver = receiver;
this.data = data;
}
@Nonnull
private Integer type;
@Nonnull
private String sender;
@Nonnull
private String receiver;
@Nonnull
private Integer serial_num;
@Nonnull
private String time;
private Object data;
}

View File

@ -0,0 +1,16 @@
package org.opentcs.communication.http.dto.kc.action;
import lombok.Data;
/**
* @author xuzhiheng
*/
@Data
public class RequestAction {
/**
* 动作
*/
private String action;
}

View File

@ -0,0 +1,70 @@
package org.opentcs.communication.http.dto.kc.cmdb2;
import java.util.ArrayList;
/**
* @author xuzhiheng
* @date 2025/5/14
* @desc 实体类
*/
public class RequestB2 {
/**
* 动作类型-填写动作命令码
*/
private Integer actionType;
/**
* 执行动作并行方式: 0x00为移动和动作间都可并行, 0x01为动作间可以并行不能移动, 0x02 只能执行当前动作
*/
private Integer actionParallelManner;
/**
* 参数内容长度
*/
private Integer paramSize;
/**
* 参数内容
*/
private ArrayList<?> paramData;
public Integer getActionType() {
return actionType;
}
public void setActionType(Integer actionType) {
this.actionType = actionType;
}
public Integer getActionParallelManner() {
return actionParallelManner;
}
public void setActionParallelManner(Integer actionParallelManner) {
this.actionParallelManner = actionParallelManner;
}
public Integer getParamSize() {
return paramSize;
}
public void setParamSize(Integer paramSize) {
this.paramSize = paramSize;
}
public ArrayList<?> getParamData() {
return paramData;
}
public void setParamData(ArrayList<?> paramData) {
this.paramData = paramData;
}
@Override
public String toString() {
return "RequestB2{" +
"actionType=" + actionType +
", actionParallelManner=" + actionParallelManner +
", paramSize=" + paramSize +
", paramData=" + paramData +
'}';
}
}

View File

@ -0,0 +1,29 @@
package org.opentcs.communication.http.dto.kc.cmdb2.action;
/**
* @author xuzhiheng
* @date 2025/5/14
* @desc 实体类
*/
public class Action01 {
/**
* 是否立即停止移动: 0机器人正常移动到点上停止停不下来就移动到下一个点, 1立刻停止(缓停)
*/
private Integer isStopImmediately;
public Integer getIsStopImmediately() {
return isStopImmediately;
}
public void setIsStopImmediately(Integer isStopImmediately) {
this.isStopImmediately = isStopImmediately;
}
@Override
public String toString() {
return "Action01{" +
"isStopImmediately=" + isStopImmediately +
'}';
}
}

View File

@ -0,0 +1,42 @@
package org.opentcs.communication.http.dto.kc.cmdb2.action;
/**
* @author xuzhiheng
* @date 2025/5/13
* @desc 实体类
*/
public class Action02 {
/**
* 订单ID
*/
private Integer orderId;
/**
* 任务key
*/
private Integer taskKey;
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public Integer getTaskKey() {
return taskKey;
}
public void setTaskKey(Integer taskKey) {
this.taskKey = taskKey;
}
@Override
public String toString() {
return "Action02{" +
"orderId=" + orderId +
", taskKey=" + taskKey +
'}';
}
}

View File

@ -0,0 +1,42 @@
package org.opentcs.communication.http.dto.kc.cmdb2.action;
/**
* @author xuzhiheng
* @date 2025/5/13
* @desc 实体类
*/
public class Action03 {
/**
* 订单ID
*/
private Integer orderId;
/**
* 是否立即停止移动: 1 立刻停止(缓停)0 AGV 正常移动到点上停止停不下来就移动到下一个点
*/
private Integer isStopImmediately;
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public Integer getIsStopImmediately() {
return isStopImmediately;
}
public void setIsStopImmediately(Integer isStopImmediately) {
this.isStopImmediately = isStopImmediately;
}
@Override
public String toString() {
return "Action03{" +
"orderId=" + orderId +
", isStopImmediately=" + isStopImmediately +
'}';
}
}

View File

@ -0,0 +1,38 @@
package org.opentcs.communication.http.dto.kc.move;
import java.util.ArrayList;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc 实体类
*/
@Data
public class Path {
/**
* 路径ID
*/
private Integer path_id;
/**
* 路径序列号: 1 开始奇数递增例如1->3->5->7
*/
private Integer path_serial;
/**
* 机器人固定角度: 0 = 不指定
*/
private float path_angle;
/**
* 行驶姿态: 0x0 正走,0x1 倒走,0x02 左横移支持横移类底盘进,行二维码导航时生效,0x03 右横移支持横移类底盘进行二维码导航时生效
*/
private Integer drive_pose;
/**
* 指定的目标最大速度
*/
private float max_speed;
/**
* 指定的目标最大角速度
*/
private float max_angle_speed;
}

View File

@ -0,0 +1,26 @@
package org.opentcs.communication.http.dto.kc.move;
import java.util.ArrayList;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc 实体类
*/
@Data
public class Point {
/**
* 点ID
*/
private Integer point_id;
/**
* 点序列号: 0开始偶数递增例如0->2->4->6
*/
private Integer point_serial;
/**
* 角度0=不指定角度
*/
private double point_angle;
}

View File

@ -0,0 +1,45 @@
package org.opentcs.communication.http.dto.kc.move;
import jakarta.annotation.Nonnull;
import java.util.ArrayList;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc 实体类
*/
@Data
public class RequestAe {
public RequestAe() {}
/**
* 订单ID
*/
@Nonnull
private Integer orderId;
/**
* 任务ID task_key: 任务的唯一标识与订单 ID 绑定 1 开始当同一订单下发新的资源时加 1
*/
@Nonnull
private Integer taskKey;
/**
* 点个数 point_size
*/
@Nonnull
private Integer pointSize;
/**
* 路径个数 path_size
*/
@Nonnull
private Integer pathSize;
/**
* 点信息结构
*/
private ArrayList<Point> point;
/**
* 路线信息结构
*/
private ArrayList<Path> path;
}

View File

@ -0,0 +1,17 @@
package org.opentcs.communication.http.dto.kc.operation;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/14
* @desc 实体类
*/
@Data
public class Action01 {
/**
* 是否立即停止移动: 0机器人正常移动到点上停止停不下来就移动到下一个点, 1立刻停止(缓停)
*/
private Integer isStopImmediately;
}

View File

@ -0,0 +1,21 @@
package org.opentcs.communication.http.dto.kc.operation;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/13
* @desc 实体类
*/
@Data
public class Action02 {
/**
* 订单ID
*/
private Integer orderId;
/**
* 任务key
*/
private Integer taskKey;
}

View File

@ -0,0 +1,21 @@
package org.opentcs.communication.http.dto.kc.operation;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/13
* @desc 实体类
*/
@Data
public class Action03 {
/**
* 订单ID
*/
private Integer orderId;
/**
* 是否立即停止移动: 1 立刻停止(缓停)0 AGV 正常移动到点上停止停不下来就移动到下一个点
*/
private Integer isStopImmediately;
}

View File

@ -0,0 +1,25 @@
package org.opentcs.communication.http.dto.kc.operation;
import lombok.Data;
/**
* @author xuzhiheng
* @date 2025/5/14
* @desc 实体类
*/
@Data
public class RequestB2 {
/**
* 动作类型-填写动作命令码
*/
private Integer actionType;
/**
* 执行动作并行方式: 0x00为移动和动作间都可并行, 0x01为动作间可以并行不能移动, 0x02 只能执行当前动作
*/
private Integer actionParallelManner;
/**
* 参数内容长度
*/
private Integer paramSize;
}

View File

@ -0,0 +1,29 @@
package org.opentcs.communication.http.enums;
public enum Actions {
//取请求
PICK_UP_REQUEST,
//放请求
RELEASE_REQUEST,
//充电
CHARGE,
//取消充电
CANCEL_CHARGE
;
/**
* 判断是否存在当前动作
* @param value
* @return 存在返回true不存在返回false
*/
public static boolean contains(String value) {
for (Actions actions : Actions.values()) {
if (actions.name().equals(value)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,75 @@
package org.opentcs.communication.http.enums;
import java.util.HashMap;
import java.util.Map;
/**
* @author xuzhiheng
* @date 2025/5/14
* @desc 将十进制value绑定到小端模式16进制key
*/
public enum KcActionCmdEnum {
CMD_ACTION_0x01(0x01, 1), //暂停车辆
CMD_ACTION_0x02(0x02, 2), //恢复订单执行
CMD_ACTION_0x03(0x03, 3); //取消任务
private Integer key;
private Integer value;
KcActionCmdEnum(Integer key, Integer value) {
this.key = key;
this.value = value;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
// 静态缓存提升查询性能
private static final Map<Integer, KcActionCmdEnum> KEY_MAP = new HashMap<>();
static {
// 初始化时将所有枚举实例的键值存入Map
for (KcActionCmdEnum entry : values()) {
if (KEY_MAP.containsKey(entry.key)) {
throw new IllegalStateException("REPEATED KEY: " + entry.key);
}
KEY_MAP.put(entry.key, entry);
}
}
/**
* 根据key获取对应的value
* @param key 要查找的键
* @return 对应的值
* @throws IllegalArgumentException 如果key不存在
*/
public static Integer getValueByKey(Integer key) {
KcActionCmdEnum entry = KEY_MAP.get(key);
if (entry == null) {
throw new IllegalArgumentException("无效的key: " + key);
}
return entry.value;
}
/**
* 根据key获取完整的枚举实例
*/
public static KcActionCmdEnum getEntryByKey(Integer key) {
return KEY_MAP.get(key);
}
}

View File

@ -0,0 +1,86 @@
package org.opentcs.communication.http.enums;
import java.util.HashMap;
import java.util.Map;
/**
* @author xuzhiheng
* @date 2025/5/14
* @desc 将十进制value绑定到小端模式16进制key
*/
public enum KcCmdEnum {
CMD_0x02(0x02, 2), //读多变量
CMD_0x03(0x03, 3), //写多变量
CMD_0x14(0x14, 20), //执行机器人手动定位
CMD_0x17(0x17, 23), //查询机器人运行状态
CMD_0x1F(0x1F, 31), //确认初始位置
CMD_0xAE(0xAE, 174), //导航指令
CMD_0xAF(0xAF, 175), //获取AGV详细信息
CMD_0xB0(0xB0, 176), //查询载货状态
CMD_0xB2(0xB2, 178); //订阅AGV信息
/**
* 小端模式十六进制命令码
*/
private Integer key;
/**
* 十进制命令码
*/
private Integer value;
KcCmdEnum(Integer key, Integer value) {
this.key = key;
this.value = value;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
// 静态缓存提升查询性能
private static final Map<Integer, KcCmdEnum> KEY_MAP = new HashMap<>();
static {
// 初始化时将所有枚举实例的键值存入Map
for (KcCmdEnum entry : values()) {
if (KEY_MAP.containsKey(entry.key)) {
throw new IllegalStateException("REPEATED KEY: " + entry.key);
}
KEY_MAP.put(entry.key, entry);
}
}
/**
* 根据key获取对应的value
* @param key 要查找的键
* @return 对应的值
* @throws IllegalArgumentException 如果key不存在
*/
public static Integer getValueByKey(Integer key) {
KcCmdEnum entry = KEY_MAP.get(key);
if (entry == null) {
throw new IllegalArgumentException("无效的key: " + key);
}
return entry.value;
}
/**
* 根据key获取完整的枚举实例
*/
public static KcCmdEnum getEntryByKey(Integer key) {
return KEY_MAP.get(key);
}
}

View File

@ -0,0 +1,38 @@
package org.opentcs.communication.http.service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.opentcs.communication.http.HttpClient;
import org.opentcs.communication.http.dto.BaseRequestTo;
import org.opentcs.communication.http.dto.kc.action.RequestAction;
public class ExecuteAction {
/**
* 下发动作到平台
* @param action 动作
*/
public static void sendCmd(String action, Integer serialNum) {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String time = now.format(formatter);
RequestAction requestAction = new RequestAction();
requestAction.setAction(action);
BaseRequestTo baseRequestTo = new BaseRequestTo(
4,
"OPENTCS",
"KC-CTRL",
serialNum,
time,
requestAction
);
HttpClient httpClient = new HttpClient();
httpClient.sendCommand(baseRequestTo);
}
}

View File

@ -0,0 +1,160 @@
package org.opentcs.communication.http.service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.opentcs.communication.http.HttpClient;
import org.opentcs.communication.http.dto.BaseRequestTo;
import org.opentcs.communication.http.dto.kc.move.Path;
import org.opentcs.communication.http.dto.kc.move.Point;
import org.opentcs.communication.http.dto.kc.move.RequestAe;
import org.opentcs.drivers.vehicle.MovementCommand;
public class ExecuteMove {
/**
* 订单名映射int类型数据.
*/
private static final HashMap<String, Integer> orderNameMap = new HashMap<>();
/**
* 记录上次下发订单名称
*/
private static String oldOrderName;
/**
* 当前订单名称对应唯一整数ID
*/
private static Integer currentOrderId = 0;
/**
* 任务ID
* 任务的唯一标识与订单ID绑定从1开始当同一订单下发新的资源时加1订单ID发生改变任务KEY需要重新计数
*/
private static Integer taskKey;
/**
* 序列号
* 用于定位点在整个任务中的位置目的是区分同一个点ID是否在一个任务中出现多次从0开始偶数递增例如0->2->4->6
*/
private static Integer pointSerialNum = 0;
/**
* 用于定位段在整个任务中的位置目的是区分同一个段ID是否在一个任务中出现多次从1开始奇数递增例如1->3->5->7
*/
private static Integer pathSerialNum = 1;
public static void sendCmd(MovementCommand cmd, Integer serialNum)
{
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String time = now.format(formatter);
RequestAe ae = buildCommand(cmd);
BaseRequestTo baseRequestTo = new BaseRequestTo(2, "OPENTCS", "KC-CTRL", serialNum, time, ae);
HttpClient httpClient = new HttpClient();
httpClient.sendCommand(baseRequestTo);
}
private static RequestAe buildCommand(MovementCommand cmd)
{
//订单ID
int orderID = getUniqueOrderID(cmd.getTransportOrder().getName());
//下发起点
String sourcePoint = cmd.getStep().getSourcePoint().getName();
int newSourcePoint = Integer.parseInt(sourcePoint);
//下发终点
String destinationPoint = cmd.getStep().getDestinationPoint().getName();
int newDestination = Integer.parseInt(destinationPoint);
//获取路径ID
int pathID;
if (newSourcePoint < newDestination) {
pathID = Integer.parseInt(sourcePoint + destinationPoint);
} else {
pathID = Integer.parseInt(destinationPoint + sourcePoint);
}
RequestAe ae = new RequestAe();
ae.setOrderId(orderID);
ae.setTaskKey(taskKey);
ae.setPointSize(2);
ae.setPathSize(1);
ArrayList<Point> pointList = new ArrayList<>();
for (int i = 0; i < ae.getPointSize(); i++) {
Point point = new Point();
if (i == 0) {
point.setPoint_id(newSourcePoint);
} else {
point.setPoint_id(newDestination);
}
point.setPoint_serial(pointSerialNum);
point.setPoint_angle(0);
pointSerialNum = pointSerialNum + 2;
pointList.add(point);
}
ae.setPoint(pointList);
ArrayList<Path> pathList = new ArrayList<>();
for (int i = 0; i < ae.getPathSize(); i++) {
Path path = new Path();
path.setPath_id(pathID);
path.setPath_serial(pathSerialNum);
path.setPath_angle(0);
path.setDrive_pose(1);
path.setMax_speed(1f);
path.setMax_angle_speed(1f);
pathSerialNum = pathSerialNum + 2;
pathList.add(path);
}
ae.setPath(pathList);
return ae;
}
/**
* 维护订单名对应int类型唯一ID--------todo 待优化如果调度重启控制器也需要重启否则0xAE指令会因为重置订单ID和任务key下发失败应改成wms数据库ID交互
* @param orderName 订单名
* @return Integer
*/
private static int getUniqueOrderID(String orderName){
Integer orderId;
if (orderNameMap.containsKey(orderName)) {
//订单名已存在
orderId = orderNameMap.get(orderName);
} else { //订单名不存在
//初始化参数
initParams();
//删除上次订单映射唯一ID
orderNameMap.remove(oldOrderName);
//更新记录订单名称
oldOrderName = orderName;
// 创建对应映射
orderId = currentOrderId;
currentOrderId++;
orderNameMap.put(orderName, orderId);
}
return orderId;
}
/**
* 初始化参数
*/
private static void initParams() {
taskKey = 1;
pointSerialNum = 0;
pathSerialNum = 1;
}
}

View File

@ -0,0 +1,7 @@
package org.opentcs.communication.http.service;
public class ExecuteOperation {
}

View File

@ -0,0 +1,95 @@
package org.opentcs.communication.mqtt;
import io.moquette.interception.InterceptHandler;
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
import io.moquette.interception.messages.InterceptConnectMessage;
import io.moquette.interception.messages.InterceptConnectionLostMessage;
import io.moquette.interception.messages.InterceptDisconnectMessage;
import io.moquette.interception.messages.InterceptPublishMessage;
import io.moquette.interception.messages.InterceptSubscribeMessage;
import io.moquette.interception.messages.InterceptUnsubscribeMessage;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.mqtt.MqttQoS;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @date 2025/5/22
* @desc 重写MQTT代理拦截器用于监听MQTT代理的连接断开发布订阅取消订阅消息确认等事件
*/
public class BrokerInterceptor implements InterceptHandler {
/**
* 客户端ID-定义为车辆名称
*/
private String clientID;
private static final Logger LOG = LoggerFactory.getLogger(BrokerInterceptor.class);
@Override public String getID() {
return clientID;
}
@Override
public Class<?>[] getInterceptedMessageTypes() {
return new Class<?>[] {
InterceptConnectMessage.class,
InterceptDisconnectMessage.class,
InterceptConnectionLostMessage.class,
InterceptPublishMessage.class,
InterceptSubscribeMessage.class,
InterceptUnsubscribeMessage.class,
InterceptAcknowledgedMessage.class,
};
}
@Override
public void onConnect(InterceptConnectMessage msg) {
LOG.info("[连接] 客户端ID: {} 连接到服务端", msg.getClientID());
}
@Override
public void onDisconnect(InterceptDisconnectMessage msg) {
LOG.info("[断开] 客户端ID: {} 断开连接", msg.getClientID());
}
@Override
public void onConnectionLost(InterceptConnectionLostMessage interceptConnectionLostMessage) {
}
@Override
public void onPublish(InterceptPublishMessage msg) {
// 解码消息内容
ByteBuf payload = msg.getPayload();
String content = payload.toString(StandardCharsets.UTF_8);
System.out.println("[MessageInterception] topic: " + msg.getTopicName() + ", clientID: " + msg.getClientID() + ", QoS: " + MqttQoS.valueOf(String.valueOf(msg.getQos())) + ", content: " + content);
LOG.info("[MessageInterception] topic: {}, clientID: {}, QoS: {}, content: {}",
msg.getTopicName(),
msg.getClientID(),
MqttQoS.valueOf(String.valueOf(msg.getQos())),
content);
// 必须保留引用计数防止内存泄漏
payload.retain();
}
// 其他需要实现的接口方法可以留空
@Override public void onSubscribe(InterceptSubscribeMessage msg) {}
@Override public void onUnsubscribe(InterceptUnsubscribeMessage msg) {}
@Override public void onMessageAcknowledged(InterceptAcknowledgedMessage msg) {}
/**
* 解析消息内容
* @param msg InterceptPublishMessage
* @return String
*/
private String parsePayload(InterceptPublishMessage msg) {
byte[] payloadBytes = new byte[msg.getPayload().readableBytes()];
msg.getPayload().getBytes(0, payloadBytes);
return new String(payloadBytes, StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,107 @@
package org.opentcs.communication.mqtt;
import io.moquette.broker.Server;
import io.moquette.broker.config.IConfig;
import io.moquette.broker.config.MemoryConfig;
import io.moquette.broker.subscriptions.Topic;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessageType;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import io.netty.handler.codec.mqtt.MqttQoS;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import org.opentcs.common.GuestUserCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @date 2025/5/22
* @desc MQTT服务
*/
public class MqttBrokerServer {
private static final Logger LOG = LoggerFactory.getLogger(MqttBrokerServer.class);
private Server mqttServer;
public MqttBrokerServer() {
mqttServer = new Server();
}
/**
* start MQTT server
* @throws IOException start MQTT server
*/
public void startMqtt() {
try {
// 从资源目录加载配置文件
Properties configProps = new Properties();
try (var input = getClass().getResourceAsStream("/mqtt.conf")) {
if (input == null) {
throw new IOException("profiles [mqtt.conf] not found");
}
configProps.load(input);
}
// 使用配置文件初始化
IConfig config = new MemoryConfig(configProps);
mqttServer.startServer(config);
mqttServer.addInterceptHandler(new BrokerInterceptor());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 关闭MQTT服务
*/
public void stopMqtt() {
mqttServer.stopServer();
LOG.info("MQTT service stopped successfully");
}
/**
* 发送消息给指定客户端 todo 待实现
* @param clientId 客户端ID 暂时定义为车辆ID
* @param qosLevel 服务质量等级
* @param message 消息
*/
public void sendToClient(String clientId, MqttQoS qosLevel, String message) {
// LOG.info("Sending message to client: {}, MqttQoS: {}, message: {}", clientId, qosLevel, message);
//
// String topicPath = TopicBuilder.buildCommandTopic(clientId);
// Topic topic = new Topic(topicPath);
//
// MqttFixedHeader fixedHeader = new MqttFixedHeader(
// MqttMessageType.PUBLISH, // 消息类型
// false, // 是否重复发送
// qosLevel, // 服务质量等级: QoS 0 最多交付一次,可能丢失消息; QoS 1 至少交付一次,可以保证收到消息但消息可能重复; QoS 2 只交付一次,使用 QoS 2 可以保证消息既不丢失也不重复,开销最高
// false, // 是否保留消息
// 0 // 剩余长度自动计算
// );
//
// MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(
// topic.toString(), // 主题
// MqttQoS.AT_LEAST_ONCE.value() // 消息IDQoS>0时需要
// );
//
// // 将消息内容转换为ByteBuf
// byte[] payload = message.getBytes(StandardCharsets.UTF_8);
// MqttPublishMessage publishMessage = new MqttPublishMessage(
// fixedHeader,
// variableHeader,
// Unpooled.wrappedBuffer(payload)
// );
//
// // 发布消息到指定客户端
// mqttServer.internalPublish(
// publishMessage,
// clientId // 指定接收客户端ID
// );
}
}

View File

@ -0,0 +1,66 @@
package org.opentcs.communication.mqtt;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class MqttClientExample {
public static void main(String[] args) {
String broker = "tcp://192.168.124.114:1883";
String clientId = "50";
String topic = "vehicles/50/commands";
int qos = 1;
try (MqttClient client = new MqttClient(broker, clientId, new MemoryPersistence())) {
// 配置连接选项
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setAutomaticReconnect(true);
// 设置回调
client.setCallback(new SimpleMqttCallback());
// 连接
client.connect(options);
System.out.println("Connected to broker");
// 先订阅后发布
client.subscribe(topic, qos);
System.out.println("Subscribed to: " + topic);
// 发布消息
String content = "Hello, MQTT";
client.publish(topic, content.getBytes(), qos, false);
System.out.println("Published message: " + content);
// 等待接收消息
Thread.sleep(5000);
} catch (MqttException | InterruptedException e) {
e.printStackTrace();
}
}
private static class SimpleMqttCallback implements MqttCallback {
@Override
public void connectionLost(Throwable cause) {
System.err.println("Connection lost: " + cause.getMessage());
}
@Override
public void messageArrived(String topic, MqttMessage message) {
System.out.printf("[%s] Received: %s%n", topic, new String(message.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("Message delivered");
}
}
}

View File

@ -0,0 +1,20 @@
package org.opentcs.communication.mqtt;
/**
* @author xuzhiheng
* @date 2025/5/22
* @desc 返回mqtt主题
*/
public class TopicBuilder {
//预定义模板
private static final String COMMAND_TOPIC_TEMPLATE = "vehicles/%s/commands";
/**
* 构建命令主题带QoS验证
*/
public static String buildCommandTopic(String clientId) {
return String.format(COMMAND_TOPIC_TEMPLATE, clientId);
}
}

View File

@ -0,0 +1,223 @@
package org.opentcs.communication.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TcpClient
implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(TcpClient.class);
// 配置参数
private final String serverIp;
private final int serverPort;
private final int heartbeatInterval; // 心跳间隔
private final int reconnectInterval; // 重连间隔
// 网络连接
private Socket socket;
private PrintWriter writer; //输入
private BufferedReader reader; //输出
// 状态管理
private final AtomicBoolean connected = new AtomicBoolean(false);
private final AtomicBoolean running = new AtomicBoolean(true);
// 线程池
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
// 监听器接口
public interface TcpListener {
void onMessageReceived(String message);
void onConnectionStatusChanged(boolean connected);
}
private TcpListener listener;
public TcpClient(String ip, int port) {
this.serverIp = ip;
this.serverPort = port;
this.heartbeatInterval = 5;
this.reconnectInterval = 3;
}
public void setListener(TcpListener listener) {
this.listener = listener;
}
public void connect() {
executor.execute(() -> {
while (running.get()) {
try {
initConnection();
startHeartbeat();
startReading();
setConnected(true);
// 保持重连循环中断
TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
catch (Exception e) {
setConnected(false);
System.err.println("Connection error: " + e.getMessage());
LOG.debug("Connection error: " + e.getMessage());
}
tryReconnect();
}
});
}
private void initConnection() throws IOException {
socket = new Socket(serverIp, serverPort);
socket.setKeepAlive(true);
writer = new PrintWriter(socket.getOutputStream(), true);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
/**
* 启动定时任务每隔固定时间heartbeatInterval发送一次心跳包,只有在客户端处于连接状态connected为true时才发送
*/
private void startHeartbeat() {
executor.scheduleAtFixedRate(() -> {
if (connected.get()) {
sendData("HEARTBEAT");
}
}, heartbeatInterval, heartbeatInterval, TimeUnit.SECONDS);
}
/**
* 从服务器读取消息
*/
private void startReading() {
executor.execute(() -> {
try {
String inputLine;
while ((inputLine = reader.readLine()) != null) {
if (listener != null) {
listener.onMessageReceived(inputLine);
}
}
}
catch (IOException e) {
setConnected(false);
}
});
}
/**
* 重连 TCP 服务器
*/
private void tryReconnect() {
if (!running.get()) return;
System.out.println("Attempting reconnect in " + reconnectInterval + "s...");
LOG.debug("Attempting reconnect in " + reconnectInterval + "s...");
try {
TimeUnit.SECONDS.sleep(reconnectInterval);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* 发送数据
* @param data 数据
*/
public synchronized void sendData(String data) {
if (connected.get()) {
writer.println(data);
}
}
/**
* 设置连接状态
* @param status 状态
*/
private void setConnected(boolean status) {
connected.set(status);
if (listener != null) {
listener.onConnectionStatusChanged(status);
}
}
/**
* 获取连接状态
* @return boolean
*/
public boolean getConnected() {
return connected.get();
}
/**
* 关闭连接
*/
@Override
public void close() {
running.set(false);
executor.shutdownNow();
try {
if (socket != null) socket.close();
if (writer != null) writer.close();
if (reader != null) reader.close();
}
catch (IOException e) {
System.err.println("Close error: " + e.getMessage());
LOG.debug("Close error: " + e.getMessage());
}
}
// 使用示例
public static void main(String[] args) throws InterruptedException {
// 创建客户端实例
TcpClient client = new TcpClient("192.168.124.114", 60000);
// 设置监听器
client.setListener(new TcpListener() {
@Override
public void onMessageReceived(String message) {
System.out.println("[RESPONSE] " + message);
LOG.debug("[RESPONSE] " + message);
}
@Override
public void onConnectionStatusChanged(boolean connected) {
System.out.println("[STATE] CONNECTION-STATUS: " + (connected ? "SUCCESS" : "FAIL"));
LOG.debug("[STATE] CONNECTION-STATUS: " + (connected ? "SUCCESS" : "FAIL"));
}
});
// 启动连接
client.connect();
// 等待连接建立
Thread.sleep(2000);
// 发送测试数据
new Thread(() -> {
for (int i = 1; i <= 3; i++) {
String data = "TEST MESSAGE-" + i;
System.out.println("[SEND] " + data);
client.sendData(data);
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
}

View File

@ -106,27 +106,27 @@ public class KCCommandDemo {
// }
// }
// {
// //0x02(read操作)
// AgvEvent agvEvent = readValue();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// printInfo(rcv);
// ReadRsp readRsp = new ReadRsp(rcv.getDataBytes());
// if(readRsp.isOk()){
// //get and parse value
// System.out.println("read ok");
// }else {
// System.out.println("read failed");
// }
// }else {
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
{
//0x02(read操作)
AgvEvent agvEvent = readValue();
printInfo(agvEvent);
RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
if(rcv.isOk()){
System.out.println();
System.out.println("received transationId : "+ "isok:"+rcv.isOk());
printInfo(rcv);
ReadRsp readRsp = new ReadRsp(rcv.getDataBytes());
if(readRsp.isOk()){
//get and parse value
System.out.println("read ok");
}else {
System.out.println("read failed");
}
}else {
System.out.println();
System.out.println("received transationId : "+ "isok:"+rcv.isOk());
}
}
// {
// //0x03(切换手自动)
@ -277,11 +277,11 @@ public class KCCommandDemo {
// Thread.sleep(100);
// ActImmediately.reset();
// }
{
float height = 1.3f;
byte modeOfMovement = 0x01;
ActImmediately.allotsOperation(height, modeOfMovement);
// {
//
// float height = 1.3f;
// byte modeOfMovement = 0x01;
// ActImmediately.allotsOperation(height, modeOfMovement);
// //读取动作执行状态
// ActImmediately.readActionState();
@ -301,7 +301,7 @@ public class KCCommandDemo {
// ActImmediately.readActionState();
//
// ActImmediately.ACTION_EXECUTE_STATE = false;
}
// }
// {
// ActImmediately.reset();
@ -592,7 +592,7 @@ public class KCCommandDemo {
public static AgvEvent manualLocation() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_ROBOT_SET_POSITION);
RobotSetPosition robotSetPosition = new RobotSetPosition(11, 11, 11);
RobotSetPosition robotSetPosition = new RobotSetPosition(3, 2, 1);
byte[] bytes = robotSetPosition.toBytes();
agvEvent.setBody(bytes);
return agvEvent;

View File

@ -49,7 +49,7 @@ public class HybridNavigation
/**
* 订单映射最大订单ID.
*/
private static int currentMaxiOrderId = 50;
private static int currentMaxiOrderId = 0;
/**

View File

@ -0,0 +1,205 @@
package org.opentcs.manage;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.opentcs.common.GuestUserCredentials;
import org.opentcs.manage.entity.AgvInfo;
import org.opentcs.manage.entity.AgvInfoParams;
import org.opentcs.manage.entity.AgvStatus;
import org.opentcs.util.KernelCommunication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
*/
public class AdapterManage {
/**
* LOG
*/
private static final Logger LOG = LoggerFactory.getLogger(AdapterManage.class);
/**
* 创建调度线程池单线程
*/
private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
/**
* Kernel服务对象
*/
private static KernelCommunication kernel;
/**
* 记录对应通讯适配器数据
* 基于线程安全HASHMAP
*/
private static final ConcurrentHashMap<String, AgvStatus> adapterStatusMap = new ConcurrentHashMap<>();
/**
* 设置自动关闭适配器时间阈值单位毫秒
*/
private final Long AUTO_CLOSE_TIME = 2000L;
/**
* 记录通讯适配器数据实现异步不阻塞更新车辆模型
* 基于线程安全HASHMAP
*/
private static final ConcurrentHashMap<String, AgvInfo> adapterDataMap = new ConcurrentHashMap<>();
//开启通讯信息
private static final String USER = GuestUserCredentials.USER;
private static final String PASSWORD = GuestUserCredentials.PASSWORD;
private static final String IP = GuestUserCredentials.IP;
private static final Integer PORT = GuestUserCredentials.PORT;
//开启定时任务
public void START() {
scheduler.scheduleWithFixedDelay(task, 0, 500, TimeUnit.MILLISECONDS);
}
//关闭定时任务
public void STOP() {
scheduler.shutdownNow();
}
Runnable task = () -> {
kernel = new KernelCommunication(USER, PASSWORD, IP, PORT);
autoManageAdapterStatus();
updateAdapterVehicleModel();
kernel.logout();
};
/**
* 自动管理通讯适配器
*/
private void autoManageAdapterStatus() {
Date date = new Date();
long currentTime = date.getTime();
adapterStatusMap.forEach((key, value) -> {
Long time = value.getTime();
AgvStatus agvStatus = new AgvStatus();
agvStatus.setTime(value.getTime());
agvStatus.setActionStatus(value.getActionStatus());
if (currentTime - time > AUTO_CLOSE_TIME) {
agvStatus.setStatus(AdapterStatus.DISABLE);
} else {
agvStatus.setStatus(AdapterStatus.ENABLE);
}
LOG.info("update the adapter: {} status{}", key, value);
kernel.sendToAdapter(key, agvStatus);
if (agvStatus.getActionStatus()) {
//动作执行结束修改数据
agvStatus.setActionStatus(false);
}
//更新记录数据
adapterStatusMap.put(key, agvStatus);
});
}
/**
* 更新适配器车辆模型
*/
private void updateAdapterVehicleModel() {
adapterDataMap.forEach((key, value) -> {
kernel.sendToAdapter(key, value);
adapterDataMap.remove(key);
});
}
/**
* 更新通讯适配器状态
* @param name 车辆名称
*/
public static void setAdapterStatus(String name) {
Date date = new Date();
long time = date.getTime();
AgvStatus newAgvStatus = new AgvStatus();
if (adapterStatusMap.containsKey(name)) {
//已记录只更新时间
AgvStatus agvStatus = adapterStatusMap.get(name);
newAgvStatus.setTime(time);
newAgvStatus.setStatus(agvStatus.getStatus());
} else {
//未记录初始化
newAgvStatus.setTime(time);
newAgvStatus.setStatus(AdapterStatus.DISABLE);
}
adapterStatusMap.put(name, newAgvStatus);
}
/**
* 设置动作完成状态
* @param name 车辆名称
*/
public static void setActionStatus(String name) {
AgvStatus agvStatus = adapterStatusMap.get(name);
agvStatus.setActionStatus(true);
adapterStatusMap.put(name, agvStatus);
}
/**
* 记录对应通讯适配器最后一次上报数据
* @param name 车辆名称
* @param data 数据
*/
public static void setAdapterVehicleModel(String name, String data) {
JSONObject jsonObject = JSON.parseObject(data);
//因为是异步上报所以需要校验序列号防止旧数据覆盖新数据
if (adapterDataMap.get(name).getSerial_num() > jsonObject.getInteger("serial_num")) {
return;
}
AgvInfo agvInfo = new AgvInfo();
agvInfo.setSender(jsonObject.getString("sender"));
agvInfo.setReceiver(jsonObject.getString("receiver"));
agvInfo.setType(jsonObject.getInteger("type"));
agvInfo.setSerial_num(jsonObject.getInteger("serial_num"));
agvInfo.setTime(jsonObject.getString("time"));
agvInfo.setParams(getAgvInfoParams(jsonObject.getString("params")));
adapterDataMap.put(name, agvInfo);
}
private static AgvInfoParams getAgvInfoParams(String paramsStr) {
JSONObject params = JSON.parseObject(paramsStr);
AgvInfoParams agvInfoParams = new AgvInfoParams();
agvInfoParams.setX(params.getFloat("x"));
agvInfoParams.setY(params.getFloat("y"));
agvInfoParams.setAngle(params.getFloat("angle"));
agvInfoParams.setPoint(params.getInteger("point"));
agvInfoParams.setPower(params.getFloat("power"));
agvInfoParams.setError(params.getString("error"));
agvInfoParams.setError_level(params.getInteger("error_level"));
agvInfoParams.setCargo_status(params.getInteger("cargo_status"));
agvInfoParams.setAgv_status(params.getInteger("agv_status"));
agvInfoParams.setAgv_model(params.getInteger("agv_model"));
agvInfoParams.setCharge_status(params.getInteger("charge_status"));
agvInfoParams.setAction_status(params.getInteger("action_status"));
return agvInfoParams;
}
/**
* 状态管理
*/
public interface AdapterStatus {
Boolean ENABLE = true;
Boolean DISABLE = false;
}
}

View File

@ -0,0 +1,36 @@
package org.opentcs.manage.entity;
import lombok.Data;
@Data
public class AgvInfo {
/**
* 发送方
*/
private String sender;
/**
* 接收
*/
private String receiver;
/**
* 消息类型
*/
private Integer type;
/**
* 序列号
*/
private Integer serial_num;
/**
* 车辆名称
*/
private String vehicle_name;
/**
* 时间
*/
private String time;
/**
* 详细数据
*/
private AgvInfoParams params;
}

View File

@ -0,0 +1,55 @@
package org.opentcs.manage.entity;
import lombok.Data;
@Data
public class AgvInfoParams {
/**
* X坐标
*/
private Float x;
/**
* Y坐标
*/
private Float y;
/**
* 角度
*/
private Float angle;
/**
* 最终经过点
*/
private Integer point;
/**
* 电池电量
*/
private float power;
/**
* 异常信息
*/
private String error;
/**
* 异常等级信息 = 0x00警告 = 0x01错误 = 0x02
*/
private Integer error_level;
/**
* agv状态空闲 = 0运行 = 1暂停 = 2初始化中= 3无法执行任务时的状态开机后无位置载地图中程序初始化中等情况人工确认 = 4未初始化=5未设置能力集
*/
private Integer agv_status;
/**
* 载货状态0=无货物1=有货物
*/
private Integer cargo_status;
/**
* 工作模式待机 = 0手动 = 1半自动 = 2自动 = 3示教 = 4服务 = 5升级程序中的工作状态维修 = 6
*/
private Integer agv_model;
/**
* 充电情况放电=0充电=1
*/
private Integer charge_status;
/**
* 执行动作状态1=完成2=执行中
*/
private Integer action_status;
}

View File

@ -0,0 +1,21 @@
package org.opentcs.manage.entity;
import lombok.Data;
@Data
public class AgvStatus {
/**
* 更新时间
*/
private Long time;
/**
* 通讯适配器状态trye=开启false=关闭
*/
private Boolean status;
/**
* 动作状态true=上报状态false=无任务
*/
private Boolean actionStatus;
}

View File

@ -0,0 +1,22 @@
package org.opentcs.park;
import java.util.ArrayList;
import java.util.HashMap;
import org.opentcs.park.entity.Park;
public class ParkStrategy {
private static ArrayList<Park> AllPark = new ArrayList<>();
public static void setPark(Park park) {
AllPark.add(park);
printPark();
}
private static void printPark() {
for (Park park : AllPark) {
System.out.println("print park:" + park.getName() + ", status:" + park.getStatus());
}
}
}

View File

@ -0,0 +1,17 @@
package org.opentcs.park.entity;
import lombok.Data;
@Data
public class Park {
/**
* 休息点名称
*/
private String name;
/**
* 休息点状态: true=已被占用 false=空闲
*/
private Boolean status;
}

View File

@ -0,0 +1,78 @@
package org.opentcs.traffic;
import org.opentcs.traffic.common.AvoidanceAlgorithm;
import org.opentcs.traffic.common.ContourAlgorithm;
import org.opentcs.traffic.common.ResourceLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @Date 2025-5-12
* @description 交管策略实现类
*/
public class TrafficControl {
/**
* 日志
*/
private static final Logger LOG = LoggerFactory.getLogger(TrafficControl.class);
/**
* 已占用资源对象
*/
private static final ResourceLock resourceLock = new ResourceLock();
/**
* 轮廓算法
*/
private static final ContourAlgorithm contourAlgorithm = new ContourAlgorithm();
/**
* 交管避让权重算法
*/
private static final AvoidanceAlgorithm avoidanceAlgorithm = new AvoidanceAlgorithm();
/**
* 交通管制策略
* @param vehiclePosition 车辆当前位置
* @param finalOccupiedPosition 车辆最终占用点位
* @param resource 资源
* @param type 类型
* @return 是否可执行
*/
public static boolean trafficControlStrategy(String vehiclePosition, String finalOccupiedPosition, String resource, String type) {
//进行资源占用
boolean lockStatus = resourceLock.trafficControlOccupy(vehiclePosition, finalOccupiedPosition, resource, type);
if (!lockStatus) {
LOG.debug("trafficControlStrategy Failed to lock resource: {} type: {}", resource, type);
return false;
}
//todo: 轮廓算法判断下一个点位是否会被其他车辆干涉
boolean contourStatus = contourAlgorithm.interferenceDetection();
if (!contourStatus) {
LOG.debug("trafficControlStrategy Contour algorithm failed: resource: {} type: {}", resource, type);
return false;
}
//todo: 订单避让算法根据权重计算车辆优先级执行避让算法
boolean avoidanceStatus = avoidanceAlgorithm.weightCalculation();
if (!avoidanceStatus) {
LOG.debug("trafficControlStrategy Avoidance algorithm failed: resource: {} type: {}", resource, type);
return false;
}
//有所有校验通过返回true
return true;
}
/**
* 步骤执行结束释放对应资源
* @param resource 资源
* @param type 类型
*/
public static void trafficControlRelease(String resource, String type) {
resourceLock.trafficControlRelease(resource, type);
}
}

View File

@ -0,0 +1,21 @@
package org.opentcs.traffic.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AvoidanceAlgorithm {
private static final Logger LOG = LoggerFactory.getLogger(AvoidanceAlgorithm.class);
/**
* 思路判断当前车辆是否和其他车辆路径有重叠
*
* @return true: 订单避让算法通过false: 订单避让算法不通过
*/
public boolean weightCalculation() {
return true;
}
}

View File

@ -0,0 +1,38 @@
package org.opentcs.traffic.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc 轮廓算法
*/
public class ContourAlgorithm {
private static final Logger LOG = LoggerFactory.getLogger(ContourAlgorithm.class);
/**
* 多车轮廓干扰检测
*
* desc当前车辆需要占用的下一个点位的xy坐标在点位坐标根据车辆长宽计算车辆轮廓
* 通过上报获取所有车辆的xy坐标计算轮廓,
* 最终通过剩余车辆的xy坐标判断是否与当前车辆轮廓重叠重叠则不通过否则通过
*
* 使用算法时间节点
* 1.车辆运行中每次递归都进行判断------运行效率可能会低当车辆数量上升时服务器性能有要求车辆运行中指定时间间隔后执行轮廓检测
* 优化方案1为减小压力应该在车辆连接时计算车辆轮廓可行性考虑车辆轮廓不是根据实时坐标计算是否有算法可以将车辆轮廓移到当前坐标上会不会有误差
* 2递归时间节点层面延长线程阻塞时间方法可行性
* 待验证算法实现算法效率问题
* 2.车辆下发运动指令前检测-------运行效率高下发前检测车辆运动中无轮廓检测可能会发生当前车辆不知道其他车辆位置可能会撞车--------放弃会有撞车风险
*
* @return true,可通过false不通过
*/
public boolean interferenceDetection() {
//
return true;
}
}

View File

@ -0,0 +1,203 @@
package org.opentcs.traffic.common;
import java.util.concurrent.ConcurrentSkipListSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xuzhiheng
* @date 2025/5/12
* @desc 公共资源锁保证线程安全
*/
public class ResourceLock {
private static final Logger LOG = LoggerFactory.getLogger(ResourceLock.class);
/**
* 静态内部类用于封装有序的线程安全集合基于红黑树.
* 已锁定资源集合.
*/
public static class DeclaredResource {
/**
* 点资源集合
*/
public static final ConcurrentSkipListSet<String> points = new ConcurrentSkipListSet<>();
/**
* 路线资源集合
*/
public static final ConcurrentSkipListSet<String> paths = new ConcurrentSkipListSet<>();
/**
* 库位资源集合
*/
public static final ConcurrentSkipListSet<String> locations = new ConcurrentSkipListSet<>();
/**
* 检查对应对象是否包含某个字符串区分大小写
* @param target 目标
* @param type 类型
* @return bool
*/
public static boolean checkContains(String target, String type) {
if (target == null) {
LOG.debug("Error checkContains: Cannot check for null.");
throw new RuntimeException("Error checkContains: Cannot check for null.");
}
LOG.info("checkContains: {} {}", target, type);
return switch (type) {
case ResourceTypeConstants.POINT -> points.contains(target);
case ResourceTypeConstants.PATH -> paths.contains(target);
case ResourceTypeConstants.LOCATION -> locations.contains(target);
default -> {
LOG.debug("checkContains Invalid resource type.");
throw new RuntimeException("Error checkContains: Cannot check for null.");
// yield false; //可能不需要抛出错误根据具体情况修改
}
};
}
/**
* 添加资源占用
* @param target 目标
* @param type 类型
*/
public static boolean resourceAdd(String target, String type) {
if (target == null) {
LOG.debug("Error resourceAdd: Cannot add for null.");
throw new RuntimeException("Error resourceAdd: Cannot add for null.");
}
LOG.info("resourceAdd: {} {}", target, type);
return switch (type) {
case ResourceTypeConstants.POINT -> points.add(target);
case ResourceTypeConstants.PATH -> paths.add(target);
case ResourceTypeConstants.LOCATION -> locations.add(target);
default -> {
LOG.debug("resourceAdd Invalid resource type.");
throw new RuntimeException("resourceAdd Invalid resource type.");
// yield false; //可能不需要抛出错误根据具体情况修改
}
};
}
/**
* 释放资源占用
* @param target 目标
* @param type 类型
*/
public static boolean resourceRemove(String target, String type) {
if (target == null) {
LOG.debug("Error resourceRemove: Cannot remove for null.");
throw new RuntimeException("Error resourceRemove: Cannot remove for null.");
}
LOG.info("resourceRemove: {} {}", target, type);
return switch (type) {
case ResourceTypeConstants.POINT -> points.remove(target);
case ResourceTypeConstants.PATH -> paths.remove(target);
case ResourceTypeConstants.LOCATION -> locations.remove(target);
default -> {
LOG.debug("resourceRemove Invalid resource type.");
throw new RuntimeException("resourceRemove Invalid resource type.");
// yield false; //可能不需要抛出错误根据具体情况修改
}
};
}
}
/**
* 占用交管资源
* @param vehiclePosition 车辆当前位置
* @param finalOccupiedPosition 车辆最终占用资源位置
* @param resource 资源
* @param type 类型
*/
public boolean trafficControlOccupy(String vehiclePosition, String finalOccupiedPosition, String resource, String type) {
//交管过程判断为
//获取资源占用情况
while (true) {
boolean lockStatus = DeclaredResource.checkContains(resource, type);
if (!lockStatus) {
//设置AGV状态为执行中
break;
}
//资源已被占用
try {
//如果车辆到达最后站用电位下一个点位未解锁修改AGV状态为交管中
if (!finalOccupiedPosition.equals("0") && vehiclePosition.equals(finalOccupiedPosition)) {
//设置AGV状态为交管中需要考虑车辆初始化时位置占用问题
}
//进入线程阻塞500ms
Thread.sleep(500);
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}
boolean addStatus = DeclaredResource.resourceAdd(resource, type);
if (addStatus) {
LOG.info("trafficControlInit Successfully add resource: {} type: {}", resource, type);
return true;
} else {
LOG.debug("trafficControlInit Failed to add resource: {} type: {}", resource, type);
return false;
}
}
/**
* 释放资源占用使用节点手自动切换车辆重启调度断开车辆
* @param resource 资源
* @param type 类型
*/
public void trafficControlRelease(String resource, String type) {
boolean releaseStatus = DeclaredResource.resourceRemove(resource, type);
if (!releaseStatus) {
LOG.debug("trafficControlRelease Failed to release resource: {} type: {}", resource, type);
throw new RuntimeException("trafficControlRelease Failed to release resource: " + resource + " type: " + type);
}
}
//测试示例
public static void main(String[] args) {
// 测试线程安全的动态维护
Runnable task = () -> {
DeclaredResource.points.add("point_" + Thread.currentThread().getName());
DeclaredResource.paths.add("path_" + Thread.currentThread().getName());
DeclaredResource.locations.add("Location_" + Thread.currentThread().getName());
};
// 启动多个线程验证线程安全性
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
// 等待线程执行完毕
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出结果
System.out.println("Points: " + DeclaredResource.points);
System.out.println("Paths: " + DeclaredResource.paths);
System.out.println("Locations: " + DeclaredResource.locations);
}
}

View File

@ -0,0 +1,25 @@
package org.opentcs.traffic.common;
/**
* @author xuzhiheng
* @date 2025-5-12
* @description 资源类型常量
*/
public interface ResourceTypeConstants {
/**
*
*/
String POINT = "POINT";
/**
* 路线
*/
String PATH = "PATH";
/**
* 库位
*/
String LOCATION = "LOCATION";
}

View File

@ -0,0 +1,40 @@
package org.opentcs.util;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.access.rmi.KernelServicePortalBuilder;
import org.opentcs.components.kernel.services.VehicleService;
import org.opentcs.data.ObjectUnknownException;
import org.opentcs.data.model.Vehicle;
/**
* @author xuzhiheng
*/
public class KernelCommunication {
private static KernelServicePortal servicePortal;
public KernelCommunication(String USER, String PASSWORD, String IP, Integer PORT) {
servicePortal = new KernelServicePortalBuilder(USER, PASSWORD).build();
servicePortal.login(IP, PORT);
}
public void logout() {
servicePortal.logout();
}
/**
* 通讯适配器通讯
* @param name 车辆名称
* @param message 数据
*/
public void sendToAdapter(String name, Object message) {
VehicleService vehicleService = servicePortal.getVehicleService();
Vehicle vehicle = vehicleService.fetchObject(Vehicle.class, name);
if (vehicle == null) {
throw new ObjectUnknownException("sendToAdapter Unknown vehicle: " + name);
}
vehicleService.sendCommAdapterMessage(vehicle.getReference(), message);
}
}

View File

@ -0,0 +1,28 @@
# Moquette 配置文件
# 监听端口
port 1883
# 使用WebSocket的端口可选
# websocket_port 8080
# 主机地址
host 0.0.0.0
# 持久化存储设置
persistent_store true
# 存储路径(如果使用持久化)
# store_file moquette_store.mapdb
# SSL配置可选
# ssl_port 8883
# jks_path serverkeystore.jks
# key_store_password passw0rd
# key_manager_password passw0rd
# 允许匿名登录true表示允许false表示需要用户名密码
allow_anonymous true
# 如果允许匿名登录为false则需要配置认证文件
# password_file password_file.conf
# 其他配置...

View File

@ -20,6 +20,10 @@ import org.opentcs.access.KernelServicePortal;
import org.opentcs.access.rmi.KernelServicePortalBuilder;
import org.opentcs.components.kernel.services.PlantModelService;
import org.opentcs.components.kernel.services.VehicleService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.LocationType;
import org.opentcs.data.model.PlantModel;
import org.opentcs.data.model.Vehicle;
/**
@ -128,4 +132,20 @@ class SameThreadExecutorServiceTest {
}
@Test
void getLocation() {
KernelServicePortal servicePortal = new KernelServicePortalBuilder(GuestUserCredentials.USER, GuestUserCredentials.PASSWORD).build();
servicePortal.login(GuestUserCredentials.IP, GuestUserCredentials.PORT);
PlantModel plantModel = servicePortal.getPlantModelService().getPlantModel();
Set<Location> locations = plantModel.getLocations();
for (Location location : locations) {
TCSObjectReference<LocationType> type = location.getType();
String typeName = type.getName();
//location类型为充电桩记录当前location到充电桩集合中
System.out.println("LOCATION NAME: " + location.getName() + ", TYPE NAME: " + typeName);
}
servicePortal.logout();
}
}

View File

@ -15,8 +15,18 @@ dependencies {
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.0'
api group: 'com.fasterxml.jackson.module', name: 'jackson-module-jsonSchema', version: '2.18.0'
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.18.0'
//fastjson
implementation 'com.alibaba:fastjson:1.2.83'
}
task release {
dependsOn build
}
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
tasks.withType(Javadoc){
options.encoding="utf-8"
}

View File

@ -279,6 +279,20 @@ public class V1RequestHandler
"/peripheralJobs/dispatcher/trigger",
this::handlePostPeripheralJobsDispatchTrigger
);
//新建接口接收平台异步响应
service.post(
"/vehicles/receiveCallback",
this::handlePostReceiveCallback
);
}
private Object handlePostReceiveCallback(Request request, Response response)
throws KernelRuntimeException {
response.type(HttpConstants.CONTENT_TYPE_TEXT_PLAIN_UTF8);
vehicleHandler.postReceiveCallback(
request.body()
);
return "";
}
private Object handlePostDispatcherTrigger(Request request, Response response)

View File

@ -4,6 +4,8 @@ package org.opentcs.kernel.extensions.servicewebapi.v1;
import static java.util.Objects.requireNonNull;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import java.util.Comparator;
@ -13,6 +15,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.access.rmi.KernelServicePortalBuilder;
import org.opentcs.common.GuestUserCredentials;
import org.opentcs.components.kernel.services.RouterService;
import org.opentcs.components.kernel.services.VehicleService;
import org.opentcs.data.ObjectUnknownException;
@ -31,6 +36,7 @@ import org.opentcs.kernel.extensions.servicewebapi.v1.binding.GetVehicleResponse
import org.opentcs.kernel.extensions.servicewebapi.v1.binding.PostVehicleRoutesRequestTO;
import org.opentcs.kernel.extensions.servicewebapi.v1.binding.PutVehicleAllowedOrderTypesTO;
import org.opentcs.kernel.extensions.servicewebapi.v1.binding.PutVehicleEnergyLevelThresholdSetTO;
import org.opentcs.manage.AdapterManage;
/**
* Handles requests related to vehicles.
@ -59,6 +65,29 @@ public class VehicleHandler {
this.executorWrapper = requireNonNull(executorWrapper, "executorWrapper");
}
/**
* 接收平台异步回调处理
*/
public void postReceiveCallback(Object data) {
System.out.println("jsonObject-----ssss: " + data.toString());
//截取平台响应的字符串
// String jsonStr = data.toString().split("=", 2)[1];
String jsonStr = data.toString();
JSONObject jsonObject = JSON.parseObject(jsonStr);
String name = jsonObject.getString("name");
Integer type = jsonObject.getInteger("type");
//将数据更新到线程安全的集合中防止线程阻塞
AdapterManage.setAdapterStatus(name);
if (type == 1) {
AdapterManage.setAdapterVehicleModel(name, jsonStr);
} else if (type == 5) {
//动作完成上报
AdapterManage.setActionStatus(name);
}
}
/**
* Find all vehicles orders and filters depending on the given parameters.
*

View File

@ -344,4 +344,12 @@ public class StandardRemoteVehicleService
throw findSuitableExceptionFor(exc);
}
}
@Override
public void receiveCallback(ClientID clientId, TCSObjectReference<Vehicle> ref, Object data)
throws RemoteException {
System.out.println("receiveCallback org.opentcs.kernel.extensions.rmi:" + data);
}
}

View File

@ -26,6 +26,8 @@ dependencies {
implementation group: 'de.huxhorn.sulky', name: 'de.huxhorn.sulky.ulid', version: '8.3.0'
runtimeOnly group: 'org.slf4j', name: 'slf4j-jdk14', version: '2.0.16'
//fastjson
implementation 'com.alibaba:fastjson:1.2.83'
}
distributions {
@ -47,6 +49,14 @@ task release {
dependsOn installDist
}
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
tasks.withType(Javadoc){
options.encoding="utf-8"
}
run {
systemProperties(['java.util.logging.config.file':'./config/logging.config',\
'opentcs.base':'.',\

View File

@ -7,6 +7,7 @@ import static java.util.Objects.requireNonNull;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.inject.Provider;
import jakarta.inject.Inject;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -17,11 +18,13 @@ import org.opentcs.access.Kernel;
import org.opentcs.access.Kernel.State;
import org.opentcs.access.KernelStateTransitionEvent;
import org.opentcs.access.LocalKernel;
import org.opentcs.communication.mqtt.MqttBrokerServer;
import org.opentcs.components.kernel.KernelExtension;
import org.opentcs.components.kernel.services.NotificationService;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.notification.UserNotification;
import org.opentcs.manage.AdapterManage;
import org.opentcs.util.event.EventBus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -70,6 +73,14 @@ public class StandardKernel
* The kernel implementing the actual functionality for the current mode.
*/
private KernelState kernelState;
/**
* MQTT 服务
*/
private MqttBrokerServer mqttBrokerServer = new MqttBrokerServer();
/**
* 通讯适配器管理类
*/
private AdapterManage adapterManage = new AdapterManage();
/**
* Creates a new kernel.
@ -108,6 +119,13 @@ public class StandardKernel
// Initial state is modelling.
setState(State.MODELLING);
//开启MQTT服务
// mqttBrokerServer.startMqtt();
//开启通讯适配器自动管理
adapterManage.START();
LOG.info("Communication adapter auto management started successfully");
initialized = true;
LOG.debug("Starting kernel thread");
Thread kernelThread = new Thread(this, "kernelThread");
@ -127,6 +145,13 @@ public class StandardKernel
// Note that the actual shutdown of extensions should happen when the kernel
// thread (see run()) finishes, not here.
// Set the terminated flag and wake up this kernel's thread for termination.
//关闭MQTT服务
// mqttBrokerServer.stopMqtt();
//关闭通讯适配器管理类
adapterManage.STOP();
initialized = false;
terminationSemaphore.release();
}

View File

@ -51,6 +51,8 @@ import org.opentcs.data.order.TransportOrder;
import org.opentcs.data.peripherals.PeripheralJob;
import org.opentcs.data.peripherals.PeripheralOperation;
import org.opentcs.drivers.vehicle.LoadHandlingDevice;
import org.opentcs.park.ParkStrategy;
import org.opentcs.park.entity.Park;
import org.opentcs.util.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -1383,6 +1385,14 @@ public class PlantModelManager
private Point createPoint(PointCreationTO to)
throws ObjectExistsException {
// Get a unique ID for the new point and create an instance.
if (to.getType() == Point.Type.PARK_POSITION) {
//是休息点记录
Park park = new Park();
park.setName(to.getName());
park.setStatus(false);
ParkStrategy.setPark(park);
}
Point newPoint = new Point(to.getName())
.withPose(new Pose(to.getPose().getPosition(), to.getPose().getOrientationAngle()))
.withType(to.getType())
@ -1500,6 +1510,7 @@ public class PlantModelManager
throws ObjectUnknownException,
ObjectExistsException {
LocationType type = getObjectRepo().getObject(LocationType.class, to.getTypeName());
System.out.println("test print location:" + to.getName() + ", type:" + type.getName());
Location newLocation = new Location(to.getName(), type.getReference())
.withPosition(to.getPosition())
.withLocked(to.isLocked())

View File

@ -42,7 +42,6 @@ public class DefaultPropertySuggestions
keySuggestions.add(LoopbackAdapterConstants.AGV_AUTHORIZE_CODE);
keySuggestions.add(LoopbackAdapterConstants.AGV_IP);
keySuggestions.add(LoopbackAdapterConstants.AGV_PORT);
keySuggestions.add(LoopbackAdapterConstants.POINT_TYPE);
}
@Override