This commit is contained in:
xuzhiheng
2025-06-05 12:21:31 +08:00
parent 8c508f3a74
commit 503771fe7c
64 changed files with 2911 additions and 537 deletions

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当前车辆需要占用的下一个点位的x、y坐标。在点位坐标根据车辆长、宽计算车辆轮廓
* 通过上报获取所有车辆的xy坐标计算轮廓,
* 最终通过剩余车辆的x、y坐标判断是否与当前车辆轮廓重叠重叠则不通过否则通过。
*
* 使用算法时间节点:
* 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();
}
}