新增了 和 科聪控制器通讯

This commit is contained in:
CaiXiang 2025-02-14 16:27:16 +08:00
parent aa56926258
commit ece8013fec
60 changed files with 5141 additions and 0 deletions

View File

@ -12,6 +12,18 @@ dependencies {
api group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '2.3.9'
implementation group: 'org.semver4j', name: 'semver4j', version: '5.4.0'
// https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine
implementation group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '3.1.8'
// https://mvnrepository.com/artifact/io.netty/netty-all
implementation group: 'io.netty', name: 'netty-all', version: '4.1.110.Final'
// https://mvnrepository.com/artifact/org.springframework.retry/spring-retry
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'
}
processResources.doLast {

View File

@ -0,0 +1,143 @@
package org.opentcs.kc.common;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.opentcs.kc.common.byteutils.ByteUtils;
public class CaffeineUtil {
/**
* 缓存的最大容量
*/
private static final int MAXIMUM_SIZE = 1000;
/**
* 缓存项的写入后过期时间
*/
private static final int EXPIRE_AFTER_WRITE_DURATION = 10;
/**
* 过期时间单位分钟
*/
private static final TimeUnit EXPIRE_AFTER_WRITE_TIMEUNIT = TimeUnit.MINUTES;
private static Cache<String, Object> cache;
private static Cache<String, AtomicInteger> cacheUUID;
/**
* 初始化Caffeine缓存配置
*/
static {
//todo https://github.com/ben-manes/caffeine/wiki 研究一下然后升级一下版本
cache = Caffeine.newBuilder()
.maximumSize(MAXIMUM_SIZE)
.expireAfterWrite(EXPIRE_AFTER_WRITE_DURATION, EXPIRE_AFTER_WRITE_TIMEUNIT)
.build();
cacheUUID = Caffeine.newBuilder()
.maximumSize(10)
.expireAfterWrite(Long.MAX_VALUE, EXPIRE_AFTER_WRITE_TIMEUNIT)
.build();
}
public static void main(String[] args) {
byte[] a = getUUID();
byte[] b = getUUID();
byte[] d = ByteUtils.intToBytes(300);
byte[] c = ByteUtils.intToBytes(32000);
//todo int 转两个字节长度
System.out.println(Arrays.asList(getUUID()));
}
public static synchronized byte[] getUUID() {
AtomicInteger uuid = cacheUUID.getIfPresent("UUID");
if(uuid == null){
//transationId 从1 开始0留给心跳变量这样就固定报文了
cacheUUID.put("UUID",new AtomicInteger(1));
return ByteUtils.intToBytes(1);
}else {
if(uuid.get() >= 32000){
cacheUUID.put("UUID",new AtomicInteger(1));
return ByteUtils.intToBytes(1);
}else {
return ByteUtils.intToBytes(uuid.incrementAndGet());
}
}
}
public static synchronized byte[] getUUIDAGV() {
AtomicInteger agvuuid = cacheUUID.getIfPresent("AGVUUID");
if(agvuuid == null){
//transationId 从1 开始0留给心跳变量这样就固定报文了
cacheUUID.put("AGVUUID",new AtomicInteger(1));
return ByteUtils.intToBytesS(1);
}else {
if(agvuuid.get() >= 32000){
cacheUUID.put("AGVUUID",new AtomicInteger(1));
return ByteUtils.intToBytesS(1);
}else {
return ByteUtils.intToBytesS(agvuuid.incrementAndGet());
}
}
}
/**
* 获取缓存值
*
* @param key 缓存键
* @return 缓存值
*/
public static Object get(String key) {
return cache.getIfPresent(key);
}
/**
* 设置缓存值
*
* @param key 缓存键
* @param value 缓存值
*/
public static void put(String key, Object value) {
cache.put(key, value);
}
/**
* 移除缓存项
*
* @param key 缓存键
*/
public static void remove(String key) {
cache.invalidate(key);
}
/**
* 清空缓存
*/
public static void clear() {
cache.invalidateAll();
}
/**
* 获取缓存中的所有值
*
* @return 缓存中的所有值集合
*/
public static Collection<Object> getAllValues() {
return cache.asMap().values();
}
/**
* 清空缓存中的所有值
*/
public static void removeAllValues() {
cache.invalidateAll();
}
}

View File

@ -0,0 +1,39 @@
package org.opentcs.kc.common;
/**
*
* @author tanyaowu
* 2017年3月30日 下午7:05:54
*/
public interface Const {
/**
* 服务器地址
*/
public static final String SERVER = "127.0.0.1";
public static void main(String[] args) {
String s = "123, 34, 104, 101, 97, 100, 101, 114, 34, 58, 123, 34, 116, 114, 97, 110, 115, 97, 99, 116, 105, 111, 110, 73, 100, 34, 58, 34, 50, 48, 50, 52, 48, 49, 48, 50, 49, 53, 52, 48, 50, 55, 95, 51, 100, 56, 49, 99, 34, 44, 34, 109, 101, 115, 115, 97, 103, 101, 84, 121, 112, 101, 34, 58, 51, 44, 34, 109, 101, 115, 115, 97, 103, 101, 78, 97, 109, 101, 34, 58, 34, 72, 101, 97, 114, 116, 66, 101, 97, 116, 34, 44, 34, 115, 101, 110, 100, 84, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 34, 50, 48, 50, 52, 45, 48, 49, 45, 48, 50, 32, 49, 53, 58, 52, 48, 58, 50, 55, 34, 125, 44, 34, 98, 111, 100, 121, 34, 58, 34, 49, 34, 44, 34, 114, 101, 116, 117, 114, 110, 115, 34, 58, 110, 117, 108, 108, 125";
String[] split = s.split(",");
System.out.println(split.length);
}
/**
* 监听端口
*/
public static final int PORT = 6789;
//public static final int PORT = 9000;
/**
* 心跳超时时间
*/
public static final int TIMEOUT = 5000;
public static final int NEED_REPLY_TYPE = 1;
public static final int NO_REPLY_TYPE = 2;
public static final int HEARTBEAT_TYPE = 3;
public static final Integer REQUEST_TYPE = 1;
public static final Integer RESPONSE_TYPE = 2;
}

View File

@ -0,0 +1,45 @@
package org.opentcs.kc.common;
import java.util.Arrays;
/**
* @Desc: "通用的数据传输底层类"
* @Author: caixiang
* @DATE: 2022/10/18 16:22
*/
public class Package {
private byte[] body;
private String transationId;
public Package(byte[] body, String transationId) {
this.body = body;
this.transationId = transationId;
}
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
public String getTransationId() {
return transationId;
}
public void setTransationId(String transationId) {
this.transationId = transationId;
}
@Override
public String toString() {
return "Package{" +
"body=" + Arrays.toString(body) +
", transationId='" + transationId + '\'' +
'}';
}
}

View File

@ -0,0 +1,64 @@
/*
Copyright 2016 S7connector members (github.com/s7connector)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.opentcs.kc.common;
/**
* The Class S7Exception is an exception related to S7 Communication
*/
public final class ParseDataException extends RuntimeException {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -4761415733559374116L;
/**
* Instantiates a new s7 exception.
*/
public ParseDataException() {
}
/**
* Instantiates a new s7 exception.
*
* @param message
* the message
*/
public ParseDataException(final String message) {
super(message);
}
/**
* Instantiates a new s7 exception.
*
* @param message
* the message
* @param cause
* the cause
*/
public ParseDataException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Instantiates a new s7 exception.
*
* @param cause
* the cause
*/
public ParseDataException(final Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,49 @@
package org.opentcs.kc.common;
/**
* @Desc: "通用的数据传输底层类"
* @Author: caixiang
* @DATE: 2022/10/18 16:22
*/
public class RcvPackage {
private boolean isOk;
private Object value;
private String content;
public RcvPackage(boolean isOk, Object value, String content) {
this.isOk = isOk;
this.value = value;
this.content = content;
}
public RcvPackage() {
}
public boolean isOk() {
return isOk;
}
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
public String getContent() {
return content;
}
@Override
public String toString() {
return "RcvPackage{" +
"isOk=" + isOk +
", value=" + value +
", content='" + content + '\'' +
'}';
}
}

View File

@ -0,0 +1,78 @@
package org.opentcs.kc.common;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
import java.util.UUID;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2022/10/24 16:27
*/
public class Utils {
/**
* java生成随机数字15位数
* @return
*/
public static String getTransationId() {
String val = String.valueOf(System.currentTimeMillis());
Random random = new Random();
for (int i = 0; i < 2; i++) {
val += String.valueOf(random.nextInt(10));
}
return val;
}
/**
* 获取指定长度的随机数
* (获取指定长度uuid)
*
* @return
*/
public static String getUUID(int len)
{
if(0 >= len)
{
return null;
}
String uuid = getUUID();
StringBuffer str = new StringBuffer();
for (int i = 0; i < len; i++)
{
str.append(uuid.charAt(i));
}
return str.toString();
}
/**
*32位默认长度的uuid
* (获取32位uuid)
*
* @return
*/
public static String getUUID()
{
return UUID.randomUUID().toString().replace("-", "");
}
/*
type:
1.返回的是这种格式2021-08-16 15:00:05
2.返回的是这种格式20210816150021
*/
public static String getNowDate(Integer type){
if(type==1){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}else {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
}
}

View File

@ -0,0 +1,349 @@
package org.opentcs.kc.common.byteutils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2022/4/1 17:32
*/
//对数字和字节进行转换 假设数据存储是以大端模式存储的
// byte: 字节类型 占8位二进制 00000000
// char: 字符类型 占2个字节 16位二进制 byte[0] byte[1]
// int : 整数类型 占4个字节 32位二进制 byte[0] byte[1] byte[2] byte[3]
// long: 长整数类型 占8个字节 64位二进制 byte[0] byte[1] byte[2] byte[3] byte[4] byte[5]
// long: 长整数类型 占8个字节 64位二进制 byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7]
// float: 浮点数(小数) 占4个字节 32位二进制 byte[0] byte[1] byte[2] byte[3]
// double: 双精度浮点数(小数) 占8个字节 64位二进制 byte[0] byte[1] byte[2] byte[3] byte[4]byte[5] byte[6] byte[7]
public class ByteUtil {
/**
* int转byte
*
* @param intValue int值
* @return byte值
*/
public static byte intToByte(int intValue) {
return (byte) intValue;
}
public static byte intTo1Byte(int intValue) {
ByteBuffer byteBuffer = ByteBuffer.allocate(4);
byteBuffer.putInt(intValue);
byte[] byteArray = byteBuffer.array();
return byteArray[3]; // 取低字节
}
/**
* byte转无符号int
*
* @param byteValue byte值
* @return 无符号int值
* @since 3.2.0
*/
public static int byteToUnsignedInt(byte byteValue) {
// Java 总是把 byte 当做有符处理我们可以通过将其和 0xFF 进行二进制与得到它的无符值
return byteValue & 0xFF;
}
/**
*
* 字符串转成16个字节的byte[] 不足16个字节前面填充0
* */
public static byte[] stringTo16Byte(String input) {
byte[] originalBytes = input.getBytes(StandardCharsets.UTF_8);
ByteBuffer buffer = ByteBuffer.allocate(16);
buffer.put(originalBytes);
buffer.put(new byte[16 - originalBytes.length]);
return buffer.array();
}
/**
* byte数组转short<br>
* 默认以小端序转换
*
* @param bytes byte数组
* @return short值
*/
public static short bytesToShort(byte[] bytes) {
return bytesToShort(bytes, ByteOrder.LITTLE_ENDIAN);
}
/**
* byte数组转short<br>
* 自定义端序
*
* @param bytes byte数组
* @param byteOrder 端序
* @return short值
*/
public static short bytesToShort(byte[] bytes, ByteOrder byteOrder) {
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
//小端模式数据的高字节保存在内存的高地址中而数据的低字节保存在内存的低地址中
return (short) (bytes[0] & 0xff | (bytes[1] & 0xff) << Byte.SIZE);
} else {
return (short) (bytes[1] & 0xff | (bytes[0] & 0xff) << Byte.SIZE);
}
}
/**
* short转byte数组<br>
* 默认以小端序转换
*
* @param shortValue short值
* @return byte数组
*/
public static byte[] shortToBytes(short shortValue) {
return shortToBytes(shortValue, ByteOrder.LITTLE_ENDIAN);
}
/**
* short转byte数组<br>
* 自定义端序
*
* @param shortValue short值
* @param byteOrder 端序
* @return byte数组
*/
public static byte[] shortToBytes(short shortValue, ByteOrder byteOrder) {
byte[] b = new byte[Short.BYTES];
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
b[0] = (byte) (shortValue & 0xff);
b[1] = (byte) ((shortValue >> Byte.SIZE) & 0xff);
} else {
b[1] = (byte) (shortValue & 0xff);
b[0] = (byte) ((shortValue >> Byte.SIZE) & 0xff);
}
return b;
}
/**
* byte[]转int值<br>
* 默认以小端序转换
*
* @param bytes byte数组
* @return int值
*/
public static int bytesToInt(byte[] bytes) {
return bytesToInt(bytes, ByteOrder.LITTLE_ENDIAN);
}
/**
* byte[]转int值<br>
* 自定义端序
*
* @param bytes byte数组
* @param byteOrder 端序
* @return int值
*/
public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) {
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
return bytes[0] & 0xFF | //
(bytes[1] & 0xFF) << 8 | //
(bytes[2] & 0xFF) << 16 | //
(bytes[3] & 0xFF) << 24; //
} else {
return bytes[3] & 0xFF | //
(bytes[2] & 0xFF) << 8 | //
(bytes[1] & 0xFF) << 16 | //
(bytes[0] & 0xFF) << 24; //
}
}
/**
* int转byte数组<br>
* 默认以小端序转换
*
* @param intValue int值
* @return byte数组
*/
public static byte[] intToBytes(int intValue) {
return intToBytes(intValue, ByteOrder.LITTLE_ENDIAN);
}
/**
* int转byte数组<br>
* 自定义端序
*
* @param intValue int值
* @param byteOrder 端序
* @return byte数组
*/
public static byte[] intToBytes(int intValue, ByteOrder byteOrder) {
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
return new byte[]{ //
(byte) (intValue & 0xFF), //
(byte) ((intValue >> 8) & 0xFF), //
(byte) ((intValue >> 16) & 0xFF), //
(byte) ((intValue >> 24) & 0xFF) //
};
} else {
return new byte[]{ //
(byte) ((intValue >> 24) & 0xFF), //
(byte) ((intValue >> 16) & 0xFF), //
(byte) ((intValue >> 8) & 0xFF), //
(byte) (intValue & 0xFF) //
};
}
}
/**
* long转byte数组<br>
* 默认以小端序转换<br>
* from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
*
* @param longValue long值
* @return byte数组
*/
public static byte[] longToBytes(long longValue) {
return longToBytes(longValue, ByteOrder.LITTLE_ENDIAN);
}
/**
* long转byte数组<br>
* 自定义端序<br>
* from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
*
* @param longValue long值
* @param byteOrder 端序
* @return byte数组
*/
public static byte[] longToBytes(long longValue, ByteOrder byteOrder) {
byte[] result = new byte[Long.BYTES];
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
for (int i = 0; i < result.length; i++) {
result[i] = (byte) (longValue & 0xFF);
longValue >>= Byte.SIZE;
}
} else {
for (int i = (result.length - 1); i >= 0; i--) {
result[i] = (byte) (longValue & 0xFF);
longValue >>= Byte.SIZE;
}
}
return result;
}
/**
* byte数组转long<br>
* 默认以小端序转换<br>
* from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
*
* @param bytes byte数组
* @return long值
*/
public static long bytesToLong(byte[] bytes) {
return bytesToLong(bytes, ByteOrder.LITTLE_ENDIAN);
}
/**
* byte数组转long<br>
* 自定义端序<br>
* from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
*
* @param bytes byte数组
* @param byteOrder 端序
* @return long值
*/
public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) {
long values = 0;
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
for (int i = (Long.BYTES - 1); i >= 0; i--) {
values <<= Byte.SIZE;
values |= (bytes[i] & 0xff);
}
} else {
for (int i = 0; i < Long.BYTES; i++) {
values <<= Byte.SIZE;
values |= (bytes[i] & 0xff);
}
}
return values;
}
/**
* double转byte数组<br>
* 默认以小端序转换<br>
*
* @param doubleValue double值
* @return byte数组
*/
public static byte[] doubleToBytes(double doubleValue) {
return doubleToBytes(doubleValue, ByteOrder.LITTLE_ENDIAN);
}
/**
* double转byte数组<br>
* 自定义端序<br>
* from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
*
* @param doubleValue double值
* @param byteOrder 端序
* @return byte数组
*/
public static byte[] doubleToBytes(double doubleValue, ByteOrder byteOrder) {
return longToBytes(Double.doubleToLongBits(doubleValue), byteOrder);
}
/**
* byte数组转Double<br>
* 默认以小端序转换<br>
*
* @param bytes byte数组
* @return long值
*/
public static double bytesToDouble(byte[] bytes) {
return bytesToDouble(bytes, ByteOrder.LITTLE_ENDIAN);
}
/**
* byte数组转double<br>
* 自定义端序<br>
*
* @param bytes byte数组
* @param byteOrder 端序
* @return long值
*/
public static double bytesToDouble(byte[] bytes, ByteOrder byteOrder) {
return Double.longBitsToDouble(bytesToLong(bytes, byteOrder));
}
/**
* {@link Number}转换为
*
* @param number 数字
* @return bytes
*/
public static byte[] numberToBytes(Number number) {
return numberToBytes(number, ByteOrder.LITTLE_ENDIAN);
}
/**
* {@link Number}转换为
*
* @param number 数字
* @param byteOrder 端序
* @return bytes
*/
public static byte[] numberToBytes(Number number, ByteOrder byteOrder) {
if (number instanceof Double) {
return doubleToBytes((Double) number, byteOrder);
} else if (number instanceof Long) {
return longToBytes((Long) number, byteOrder);
} else if (number instanceof Integer) {
return intToBytes((Integer) number, byteOrder);
} else if (number instanceof Short) {
return shortToBytes((Short) number, byteOrder);
} else {
return doubleToBytes(number.doubleValue(), byteOrder);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
package org.opentcs.kc.common.byteutils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2022/12/5 11:11
*/
public class CommonFunctions {
/**
* a 整除 b 如果有余数+1
* */
public static Integer exactDivision(Integer a,Integer b) {
int c = a/b;
if(a%b!=0){
c = a/b+1;
}
return c;
}
}

View File

@ -0,0 +1,32 @@
package org.opentcs.kc.common.byteutils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024-05-08 11:02
*/
public class StringArrayStruc {
private String[] content;
private Integer strSize;
public StringArrayStruc(String[] content, Integer strSize) {
this.content = content;
this.strSize = strSize;
}
public String[] getContent() {
return content;
}
public void setContent(String[] content) {
this.content = content;
}
public Integer getStrSize() {
return strSize;
}
public void setStrSize(Integer strSize) {
this.strSize = strSize;
}
}

View File

@ -0,0 +1,34 @@
package org.opentcs.kc.common.enmuc;
public enum ModbusFC {
//心跳变量这个可以要求电控同事加一个不和业务关联只用于通讯
MB_HOLD_REG((byte) 0x03, (byte) 0x10),
Q_OUT((byte) 0x01, (byte) 0x05),
I_IN((byte) 0x02, null),
IW_IN((byte) 0x04, null),
;
private Byte fread;
private Byte fwrite;
ModbusFC(Byte fread, Byte fwrite){
this.fread = fread;
this.fwrite = fwrite;
}
public Byte getFread() {
return fread;
}
public void setFread(Byte fread) {
this.fread = fread;
}
public Byte getFwrite() {
return fwrite;
}
public void setFwrite(Byte fwrite) {
this.fwrite = fwrite;
}
}

View File

@ -0,0 +1,65 @@
package org.opentcs.kc.syn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author: 蔡翔
* @Date: 2019/11/6 14:11
* @Version 1.0
*
* 重点
* AsyncFuture 这个类就是票据 刚拿到这个票据是没有信息的当done == true 的时候这个票据 上就自动有信息了
* 这个结果类设计的比较神奇
*/
public class AsyncFuture<Object> implements Future<Object> {
private static final Logger logger = LoggerFactory.getLogger(AsyncFuture.class);
private volatile boolean done = false;
private Object oldRequest;
private Object result;
public AsyncFuture(Object oldRequest) {
this.oldRequest = oldRequest;
}
public AsyncFuture() {
}
public void done(Object result){
synchronized (this){
this.result = result;
this.done = true;
//注意这里的notifyAll只能唤醒 本锁的所有 下的所有 wait(),这里的锁就是 AsyncFuture这个类
notifyAll();
}
}
@Override
public Object get(Long timeout) throws Exception {
return null;
}
@Override
public Object get(Long timeout, String transationId) throws Exception {
synchronized (this){
//其实有 synchronize就相当于有一个阻塞队列当有线程执行了wait 方法就会把执行wait的这个线程给加入wait<Thread> 队列
//当有线程执行notify方法的时候就会往这个队列中取出一个或者多个Thread,取出来以后就能执行后续代码了
// System.out.println("get");
// 当线程执行wait()会把当前的锁释放然后让出CPU进入等待状态 wait()会立刻释放synchronizedobj中的obj锁以便其他线程可以执行obj.notify()
// * 当线程执行notify()/notifyAll()方法时会唤醒一个处于等待状态该对象锁的线程然后继续往下执行直到执行完退出对象锁锁住的区域synchronized修饰的代码块后再释放锁
this.wait(timeout);
if(!done){
//logger.error("T3 timeout , request information: "+oldRequest.toString());
SendedList.remove(transationId);
throw new Exception("T3 timeout , request information: "+oldRequest.toString());
}
//因为上面的代码是加锁的所以这里的代码也是加锁的
return result;
}
}
}

View File

@ -0,0 +1,14 @@
package org.opentcs.kc.syn;
/**
* @Author: 蔡翔
* @Date: 2019/11/6 13:49
* @Version 1.0
*/
public interface Future<MQMessage> {
//别人调用我的时候我先给他们返回一个结果
MQMessage get(Long timeout) throws Exception;
MQMessage get(Long timeout, String transationId) throws Exception;
}

View File

@ -0,0 +1,41 @@
package org.opentcs.kc.syn;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2021/8/17 14:35
*/
public class Main {
// public static void main(String[] args) throws Exception {
//
// new Thread(()->{
// TResponse TResponse = new TResponse();
//
// TResponse.setHeader(new Header("123",1));
// TResponse.setBody("request-body-123");
//
//
// AsyncFuture<TResponse> add = SendedList.add(TResponse.getHeader().getTransationId(), TResponse);
// try {
// TResponse TResponseResponse = add.get(3000L);
// System.out.println(TResponseResponse.getReturns());
// } catch (Exception e) {
// e.printStackTrace();
// }
// }).start();
//
// Thread.sleep(1000);
//
// new Thread(()->{
// TResponse TResponse = new TResponse();
// TResponse.setReturns(1);
// TResponse.setBody("response-body-123");
// TResponse.setHeader(new Header("123",2));
// SendedList.set(TResponse.getHeader().getTransationId() , TResponse);
// }).start();
// }
}

View File

@ -0,0 +1,51 @@
package org.opentcs.kc.syn;
import org.opentcs.kc.common.CaffeineUtil;
import org.opentcs.kc.common.Package;
/**
* @Desc: "MES端 发送远程指令列表"
* @Author: caixiang
* @DATE: 2021/8/17 14:14
*/
public class SendedList {
// private static HashMap<String, AsyncFuture<MBPackage>> list = new HashMap<>();
// public static synchronized AsyncFuture<MQMessage> get(String transitionId){
// return list.get(transitionId);
// }
// public static synchronized void put(String transitionId,AsyncFuture<MQMessage> asyncFuture){
// list.put(transitionId,asyncFuture);
// }
public static synchronized AsyncFuture<Package> add(String transitionId, Package PackageRequest) {
AsyncFuture<Package> objectAsyncFuture = new AsyncFuture<>(PackageRequest);
//list.put(transitionId,objectAsyncFuture);
CaffeineUtil.put(transitionId,objectAsyncFuture);
return objectAsyncFuture;
}
//如果超时了那么让外部把这个key 给清除掉防止 list 过大
public static synchronized void remove(String transitionId) {
//list.remove(transitionId);
CaffeineUtil.remove(transitionId);
}
@SuppressWarnings("unchecked")
public static synchronized Boolean set(String transitionId, Package message) {
//AsyncFuture<MBPackage> mqMessageAsyncFuture = list.get(transitionId);
AsyncFuture<Package> mqMessageAsyncFuture = (AsyncFuture<Package>)CaffeineUtil.get(transitionId);
if(mqMessageAsyncFuture == null){
return false;
}
mqMessageAsyncFuture.done(message);
//清除 防止这个hashMap过大
CaffeineUtil.remove(transitionId);
return true;
}
}

View File

@ -0,0 +1,35 @@
package org.opentcs.kc.udp.GB;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/15 10:53
*/
import java.util.Random;
/**
* 作者DarkKIng
* 类说明日志信息用String数组代替
*/
public class LogConst {
public final static int MONITOR_SIDE_PORT = 9998;
private static final String[] LOG_INFOS = {
"晨光微好,暖在夕阳。幽幽的巷子里,有着岁月酝酿的酒,愈久愈淳。一笔墨香,一盏明灯,记千帆过浪,数不尽的悲欢离合,待那水莲花开。",
"未来无期,静在安好。一剪寒梅,纷扰了岁月,抚平了伤痕。摆动的双桨,拨动了心的潭水。陌上花开,落一地秋霜,红枫染了红尘,便许了你十里红装。",
"离别的风,风干了月的泪。夜里的美",
"是梦的呢喃低语,挥走一片彩云,段落成珠。拂袖离去,乘鹤而来,古道西风瘦马。斑驳的树影中,眉目如画的眼,轻语告别了往事如烟。",
"无言的殇,几世沧桑,几生悲凉。一起剪了西窗烛,听了夜来风吹雨。昨日的叹息,今日的迷离,执一伞,存了一世一笔的温情。一曲长歌,唱尽了一世繁华,一世缘……",
"一世恋书,那便十里花开。一生凄凉,那便霜花十里。" ,
"一抹浓烟,便翻页书,展颜一笑,是时间带来遥远的梦。细数树的年轮,感受昨日惆怅,留一半清醒,梦一半叶落。在指尖流过的沙,海边浪花一朵朵,不相遇,才有不约而同。",
"这世俗,太多牵挂留在心间,一点朱砂泪,一曲相诗歌。岁月朦胧,梦醒了人生,风雨相容,演绎了一段风情。雪亦梦,雨亦梦,万张红纸从天洒来。惊动了山,撼动了天。" +
"一纸情愁,一指烟凉。一相思,一思量,水漫岸头,我们都有着自己不同的三生故事。迎一夜秋风,送一世暖阳,一切冰雪里的花开,是我一生的柔情。" +
"记忆中的短笛,有着清风须来的气息,那时我们面向大海,海风在耳边述说着大海边缘的温暖故事。安好一轮冷月,静好了一残红日,这便是我的语言,我的情丝。" +
"一漫山水,一段情,留在了岁月,拭去了风,晴雨清风,倒是暖阳拂绿草。" +
"这便,晨光微好,花开静好……"};
private final static Random r = new Random();
public static String getLogInfo(){
return LOG_INFOS[r.nextInt(LOG_INFOS.length-1)];
}
}

View File

@ -0,0 +1,74 @@
package org.opentcs.kc.udp.GB;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/15 10:55
*/
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.net.InetSocketAddress;
/**
* 作者DarkKIng
* 类说明日志的广播端 ( Client )
*/
public class LogEventBroadcaster {
private final EventLoopGroup group;
private final Bootstrap bootstrap;
public LogEventBroadcaster(InetSocketAddress remoteAddress) {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
//引导该 NioDatagramChannel无连接的
bootstrap.group(group).channel(NioDatagramChannel.class)
//todo 设置 SO_BROADCAST 套接字选项option so_broadcast差异这里和单播 是有差异的
.option(ChannelOption.SO_BROADCAST, true)
.handler(new LogEventEncoder(remoteAddress));
}
public void run() throws Exception {
//绑定 Channel
Channel ch = bootstrap.bind(0).sync().channel();
long count = 0;
//启动主处理循环模拟日志发送
for (;;) {
ch.writeAndFlush(new LogMsg(null, ++count,
LogConst.getLogInfo()));
try {
//休眠 2 如果被中断则退出循环
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
}
}
public void stop() {
group.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
//创建并启动一个新的 UdpQuestionSide 的实例
LogEventBroadcaster broadcaster = new LogEventBroadcaster(
//表明本应用发送的报文并没有一个确定的目的地也就是进行广播
//就是往这个网络下 所有主机发送数据往这些主机的 LogConst.MONITOR_SIDE_PORT端口 发送数据
//todo 这里的设置和 单播是有差异的
new InetSocketAddress("255.255.255.255",
LogConst.MONITOR_SIDE_PORT));
try {
System.out.println("广播服务启动");
broadcaster.run();
}
finally {
broadcaster.stop();
}
}
}

View File

@ -0,0 +1,47 @@
package org.opentcs.kc.udp.GB;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/15 10:56
*/
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.util.CharsetUtil;
import java.util.Date;
import java.util.List;
/**
* 作者DarkKIng
* 类说明解码将DatagramPacket解码为实际的日志实体类
*/
public class LogEventDecoder extends MessageToMessageDecoder<DatagramPacket> {
@Override
protected void decode(ChannelHandlerContext ctx,
DatagramPacket datagramPacket, List<Object> out)
throws Exception {
//获取对 DatagramPacket 中的数据ByteBuf的引用
ByteBuf data = datagramPacket.content();
long time = new Date().getTime();
System.out.println(time+" 接受到发送的消息:");
//获得消息的id
long msgId = data.readLong();
//获得分隔符SEPARATOR
byte sepa = data.readByte();
//获取读索引的当前位置就是分隔符的索引+1
int idx = data.readerIndex();
//提取日志消息从读索引开始到最后为日志的信息
String sendMsg = data.slice(idx ,
data.readableBytes()).toString(CharsetUtil.UTF_8);
//构建一个新的 LogMsg 对象并且将它添加到已经解码的消息的列表中
LogMsg event = new LogMsg(datagramPacket.sender(),
msgId, sendMsg);
//作为本handler的处理结果交给后面的handler进行处理
out.add(event);
}
}

View File

@ -0,0 +1,48 @@
package org.opentcs.kc.udp.GB;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/15 10:55
*/
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;
import java.net.InetSocketAddress;
import java.util.List;
/**
* 作者DarkKIng
* 类说明编码将实际的日志实体类编码为DatagramPacket
*/
public class LogEventEncoder extends MessageToMessageEncoder<LogMsg> {
private final InetSocketAddress remoteAddress;
//LogEventEncoder 创建了即将被发送到指定的 InetSocketAddress
// DatagramPacket 消息
public LogEventEncoder(InetSocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext,
LogMsg logMsg, List<Object> out) throws Exception {
byte[] msg = logMsg.getMsg().getBytes(CharsetUtil.UTF_8);
//容量的计算两个long型+消息的内容+分割符
ByteBuf buf = channelHandlerContext.alloc()
.buffer(8*2 + msg.length + 1);
//将发送时间写入到 ByteBuf中
buf.writeLong(logMsg.getTime());
//将消息id写入到 ByteBuf中
buf.writeLong(logMsg.getMsgId());
//添加一个 SEPARATOR
buf.writeByte(LogMsg.SEPARATOR);
//将日志消息写入 ByteBuf中
buf.writeBytes(msg);
//将一个拥有数据和目的地地址的新 DatagramPacket 添加到出站的消息列表中
out.add(new DatagramPacket(buf, remoteAddress));
}
}

View File

@ -0,0 +1,42 @@
package org.opentcs.kc.udp.GB;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/15 10:57
*/
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* 作者DarkKIng
* 类说明日志的业务处理类,实际的业务处理接受日志信息
*/
public class LogEventHandler
extends SimpleChannelInboundHandler<LogMsg> {
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
//当异常发生时打印栈跟踪信息并关闭对应的 Channel
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRead0(ChannelHandlerContext ctx,
LogMsg event) throws Exception {
//创建 StringBuilder并且构建输出的字符串
StringBuilder builder = new StringBuilder();
builder.append(event.getTime());
builder.append(" [");
builder.append(event.getSource().toString());
builder.append("] [");
builder.append(event.getMsgId());
builder.append("] ");
builder.append(event.getMsg());
//打印 LogMsg 的数据
System.out.println(builder.toString());
}
}

View File

@ -0,0 +1,63 @@
package org.opentcs.kc.udp.GB;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.net.InetSocketAddress;
/**
* @Desc: ""
* @Author: caixiang ( Server 接受消息 )
* @DATE: 2024/12/15 10:57
*/
public class LogEventMonitor {
private final EventLoopGroup group;
private final Bootstrap bootstrap;
public LogEventMonitor(InetSocketAddress address) {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
//引导该 NioDatagramChannel
bootstrap.group(group)
.channel(NioDatagramChannel.class)
//设置套接字选项 SO_BROADCAST
.option(ChannelOption.SO_BROADCAST, true)
//允许重用
.option(ChannelOption.SO_REUSEADDR,true)
.handler( new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel)
throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new LogEventDecoder());
pipeline.addLast(new LogEventHandler());
}
} )
.localAddress(address);
}
public Channel bind() {
//绑定 Channel注意DatagramChannel 是无连接的
return bootstrap.bind().syncUninterruptibly().channel();
}
public void stop() {
group.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
//构造一个新的 UdpAnswerSide并指明监听端口
LogEventMonitor monitor = new LogEventMonitor(
new InetSocketAddress(LogConst.MONITOR_SIDE_PORT));
try {
//绑定本地监听端口
Channel channel = monitor.bind();
System.out.println("UdpAnswerSide running");
channel.closeFuture().sync();
} finally {
monitor.stop();
}
}
}

View File

@ -0,0 +1,63 @@
package org.opentcs.kc.udp.GB;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/15 10:54
*/
import java.net.InetSocketAddress;
/**
* 作者DarkKIng
* 类说明日志实体类
*/
public final class LogMsg {
public static final byte SEPARATOR = (byte) ':';
/*源的 InetSocketAddress*/
private final InetSocketAddress source;
/*消息内容*/
private final String msg;
/*消息id*/
private final long msgId;
/*消息发送的时间*/
private final long time;
//用于传入消息的构造函数
public LogMsg(String msg) {
this(null, msg,-1,System.currentTimeMillis());
}
//用于传出消息的构造函数
public LogMsg(InetSocketAddress source, long msgId,
String msg) {
this(source,msg,msgId,System.currentTimeMillis());
}
public LogMsg(InetSocketAddress source, String msg, long msgId, long time) {
this.source = source;
this.msg = msg;
this.msgId = msgId;
this.time = time;
}
//返回发送 LogMsg 的源的 InetSocketAddress
public InetSocketAddress getSource() {
return source;
}
//返回消息内容
public String getMsg() {
return msg;
}
//返回消息id
public long getMsgId() {
return msgId;
}
//返回消息中的时间
public long getTime() {
return time;
}
}

View File

@ -0,0 +1,407 @@
package org.opentcs.kc.udp;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
//import org.opentcs.kc.udp.agv.param.AgvEvent;
import org.opentcs.kc.common.byteutils.ByteUtils;
import org.opentcs.kc.udp.agv.param.AgvEvent;
import org.opentcs.kc.udp.agv.param.AgvEventConstant;
import org.opentcs.kc.udp.agv.param.function.b1.SubscribeInfo;
import org.opentcs.kc.udp.agv.param.function.b1.SubscribeParam;
import org.opentcs.kc.udp.agv.param.function.navigation.*;
import org.opentcs.kc.udp.agv.param.function.read.ReadParam;
import org.opentcs.kc.udp.agv.param.function.read.ReadStrValue;
import org.opentcs.kc.udp.agv.param.function.read.ReadValueMember;
import org.opentcs.kc.udp.agv.param.function.write.WriteParam;
import org.opentcs.kc.udp.agv.param.function.write.WriteStrValue;
import org.opentcs.kc.udp.agv.param.function.write.WriteValueMember;
import org.opentcs.kc.udp.agv.param.function.x14.RobotSetPosition;
import org.opentcs.kc.udp.agv.param.rsp.RcvEventPackage;
import org.opentcs.kc.udp.io.UDPClient;
import org.opentcs.kc.udp.agv.param.AgvEvent;
/**
*
* AGV启动
* 0xAF(查询机器人状态) 👌
* 0xB1(下发订阅信令) 👌
* 初始化
* 0x03(切换手自动) 👌
* 0x14(手动定位) 👌
* 0x17(查询机器人运行状态) 👌
* 0x1F(确认初始位置) 👌
* 运行
* 0xAE(导航控制导航点控制) 👌
*
*
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/17 16:25
*/
public class KCCommandDemo {
public static void main(String [] args) throws Exception{
// {
// //0xAF(查询机器人状态)
// AgvEvent agvEvent = queryStatus();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// QueryRobotStatusRsp queryRobotStatusRsp = new QueryRobotStatusRsp(rcv.getDataBytes());
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// for (byte b:rcv.getValue()){
// System.out.print(byteToHex(b)+" ");
// }
// }else {
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
// {
// //0xB0(查询载货状态)
// AgvEvent agvEvent = checkCargoStatus();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// QueryCargoStatusRsp queryCargoStatusRsp = new QueryCargoStatusRsp(rcv.getDataBytes());
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// for (byte b:rcv.getValue()){
// System.out.print(byteToHex(b)+" ");
// }
// }else {
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
// {
// //0xB1(订阅信息)
// AgvEvent agvEvent = issueSubscribe();
// printInfo(agvEvent);
// //todo 订阅参数构建完毕 去写 回调部分
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// SubscribeRsp subscribeRsp = new SubscribeRsp(rcv.getDataBytes());
// if(subscribeRsp.isOk()){
// //...
// }else {
// //...
// }
// }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(切换手自动)
// AgvEvent agvEvent = writeValue();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// System.out.println();
// System.out.println("received "+ "isok:"+rcv.isOk()+" dataBytes:");
// printInfo(rcv);
// if(rcv.isOk()){
// //get and parse value
// System.out.println("write ok");
// }else {
// System.out.println("write failed");
// }
// }else {
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
// {
// //0x14(手动定位)
// AgvEvent agvEvent = manualLocation();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// System.out.println();
// System.out.println("received "+ "isok:"+rcv.isOk()+" dataBytes:");
// printInfo(rcv);
// if(rcv.isOk()){
// //get and parse value
// System.out.println("0x14 ok");
// }else {
// System.out.println("0x14 failed");
// }
// }else {
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
// {
// //0x17(查询机器人运行状态)
// AgvEvent agvEvent = queryRobotRunStatus();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// //QueryCargoStatusRsp queryCargoStatusRsp = new QueryCargoStatusRsp(rcv.getDataBytes());
// QueryRobotRunStatusRsp queryRobotRunStatusRsp = new QueryRobotRunStatusRsp(rcv.getDataBytes());
// System.out.println(queryRobotRunStatusRsp.toString());
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// for (byte b:rcv.getValue()){
// System.out.print(byteToHex(b)+" ");
// }
// }else {
// System.out.println();
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
{
//0x1F(确认初始位置)
AgvEvent agvEvent = confirmInitialPosition();
printInfo(agvEvent);
RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
if(rcv.isOk()){
System.out.println("0x1F ok");
}else {
System.out.println();
System.out.println("0x1F fail");
System.out.println("received transationId : "+ "isok:"+rcv.isOk());
}
}
// {
// //0xAE(导航控制导航点控制)
// AgvEvent agvEvent = navigationControl();
// printInfo(agvEvent);
// RcvEventPackage rcv = UDPClient.localAGV.send(agvEvent);
// if(rcv.isOk()){
// System.out.println("0xAE ok");
// }else {
// System.out.println();
// System.out.println("0xAE fail");
// System.out.println("received transationId : "+ "isok:"+rcv.isOk());
// }
// }
}
public static String byteToHex(byte b) {
// 将byte转换为无符号整数
int unsignedByte = b & 0xFF;
// 使用Integer.toHexString方法转换为十六进制字符串
String hexString = Integer.toHexString(unsignedByte);
// 如果字符串长度为1需要在前面补0
if (hexString.length() == 1) {
return "0" + hexString;
}
return hexString;
}
public static void printInfo(AgvEvent agvEvent){
System.out.println("sended transationId : "+agvEvent.getTransationIdString());
for (byte b:agvEvent.toBytes().getBody()){
System.out.print(byteToHex(b)+" ");
}
}
public static void printInfo(RcvEventPackage rcv){
for (byte b: rcv.getDataBytes()){
System.out.print(byteToHex(b)+" ");
}
}
/**
* decs: read操作
* 指令0x02
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent readValue() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_READ);
List<ReadValueMember> readValueMemberList = new ArrayList<>();
ReadValueMember readValueMember1 = new ReadValueMember(Short.valueOf("0"),Short.valueOf("1"));
readValueMemberList.add(readValueMember1);
List<ReadStrValue> readStrValueList = new ArrayList<>();
ReadStrValue readStrValue = new ReadStrValue("Battry_SOC", 1, readValueMemberList);
readStrValueList.add(readStrValue);
ReadParam readParam = new ReadParam(agvEvent.getTransationId(),readStrValueList );
agvEvent.setBody(readParam.toBytes());
return agvEvent;
}
/**
* decs: write操作
* 指令0x03
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent writeValue() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_WRITE);
List<WriteValueMember> valueMemberList = new ArrayList<>();
WriteValueMember valueMember1 = new WriteValueMember(Short.valueOf("0"),Short.valueOf("4"), ByteUtils.uintToBytes(3, ByteOrder.LITTLE_ENDIAN));
valueMemberList.add(valueMember1);
List<WriteStrValue> strValueList = new ArrayList<>();
WriteStrValue strValue = new WriteStrValue("TestRW", 1, valueMemberList);
strValueList.add(strValue);
WriteParam param = new WriteParam(1,strValueList );
agvEvent.setBody(param.toBytes());
return agvEvent;
}
/**
* decs: 查询机器人状态
* 指令0xAF
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent queryStatus() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_QUERY_ROBOT_STATUS);
return agvEvent;
}
/**
* decs: 导航控制
* 指令0xAE
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent navigationControl() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_QUERY_ROBOT_STATUS);
//TODO 构建
Integer orderId = 1;
//构建point
Action[] pointActions1 = new Action[]{
new Action(ActionSet.stop0x01, (byte) 0x00, 1, ActionSet.stop0x01_paramsize, ActionSet.stop0x01(orderId, (byte) 0x01))
//,new Action()... 每一个point 可以绑定一个或者多个 action
};
Action[] pointActions2 = new Action[]{
new Action(ActionSet.stop0x01, (byte) 0x00, 1, ActionSet.stop0x01_paramsize, ActionSet.stop0x01(orderId, (byte) 0x01))
};
Action[] pointActions3 = new Action[]{
new Action(ActionSet.stop0x01, (byte) 0x00, 1, ActionSet.stop0x01_paramsize, ActionSet.stop0x01(orderId, (byte) 0x01))
};
Point[] points = new Point[]{
new Point(0, 1, 1f, (byte)0x00, ByteUtils.usintTo1Byte(pointActions1.length),pointActions1),
new Point(2, 2, 1f, (byte)0x00, ByteUtils.usintTo1Byte(pointActions2.length),pointActions2),
new Point(4, 3, 1f, (byte)0x00, ByteUtils.usintTo1Byte(pointActions3.length),pointActions3)
};
//构建path
Action[] pathActions1 = new Action[]{
new Action(ActionSet.stop0x01, (byte) 0x00, 1, ActionSet.stop0x01_paramsize, ActionSet.stop0x01(orderId, (byte) 0x01))
//,new Action()... 每一个path 可以绑定一个或者多个 action
};
Action[] pathActions2 = new Action[]{
new Action(ActionSet.stop0x01, (byte) 0x00, 1, ActionSet.stop0x01_paramsize, ActionSet.stop0x01(orderId, (byte) 0x01))
};
Path[] paths = new Path[]{
new Path(1,1,1f,(byte)0x00,(byte)0x00,ByteUtils.usintTo1Byte(pathActions1.length),5f,1f,pathActions1) ,
new Path(3,2,1f,(byte)0x00,(byte)0x00,ByteUtils.usintTo1Byte(pathActions2.length),5f,1f,pathActions2) ,
};
NavigationParam navigationParam = new NavigationParam(1,1,ByteUtils.usintTo1Byte(points.length),ByteUtils.usintTo1Byte(points.length-1),points,paths);
agvEvent.setBody(navigationParam.toBytes());
return agvEvent;
}
/**
* decs: 确认机器人位置
* 指令0x1F
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent confirmInitialPosition() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_CONFIRM_ROBOT_POSITION);
return agvEvent;
}
/**
* decs: 查询机器人运行状态
* 指令0x17
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent queryRobotRunStatus() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_QUERY_ROBOT_RUN_STATUS);
return agvEvent;
}
public static AgvEvent manualLocation() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_ROBOT_SET_POSITION);
RobotSetPosition robotSetPosition = new RobotSetPosition(11, 11, 11);
byte[] bytes = robotSetPosition.toBytes();
agvEvent.setBody(bytes);
return agvEvent;
}
/**
* decs: 查询载货状态
* 指令0xB0
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent checkCargoStatus() {
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_QUERY_CARRY_STATUS);
return agvEvent;
}
/**
* decs: 下发订阅信息
* 指令0xB1
* author: caixiang
* date: 2025/1/17 16:25
* */
public static AgvEvent issueSubscribe() {
List<SubscribeInfo> subscribeInfoList = new ArrayList<>();
SubscribeInfo subscribeInfo = new SubscribeInfo(new byte[]{(byte)0xaf,(byte)0x00}, (short) 100,1000);
//SubscribeInfo subscribeInfo = new SubscribeInfo(new byte[]{(byte)0xb0,(byte)0x00}, (short) 100,1000);
subscribeInfoList.add(subscribeInfo);
SubscribeParam subscribeParam = new SubscribeParam(subscribeInfoList);
AgvEvent agvEvent = new AgvEvent(AgvEventConstant.CommandCode_ISSUE_SUBSCRIPTION,subscribeParam.toBytes());
return agvEvent;
}
}

View File

@ -0,0 +1,36 @@
package org.opentcs.kc.udp.agv.codec;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.nio.NioDatagramChannel;
import org.opentcs.kc.udp.io.UDPClient;
/**
* 作者蔡翔
*/
public class AgvUdpChannelInitializer extends ChannelInitializer<NioDatagramChannel> {
private UDPClient client;
public AgvUdpChannelInitializer(UDPClient client) {
System.out.println(client.getHost());
this.client = client;
}
@Override
protected void initChannel(NioDatagramChannel channel) throws Exception {
// Modbus
// 在管道中添加我们自己的接收数据实现方法
//todo 到时候这里改成 FixedLengthFrameDecoder 资料https://www.cnblogs.com/java-chen-hao/p/11571229.html
// channel.pipeline().addLast(
// new LengthFieldBasedFrameDecoder(
// 200,
// 4,
// 2,
// 1,
// 7,
// true)
// );
channel.pipeline().addLast(new AgvUdpDecode(this.client)); //InBoundHandler
//channel.pipeline().addLast(new AgvUdpEncode()); //OutBoundHandler
}
}

View File

@ -0,0 +1,82 @@
package org.opentcs.kc.udp.agv.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import org.opentcs.kc.common.Package;
import org.opentcs.kc.syn.SendedList;
import org.opentcs.kc.udp.agv.param.rsp.RcvEventPackage;
import org.opentcs.kc.udp.io.UDPClient;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024-06-06 10:45
*/
public class AgvUdpDecode extends SimpleChannelInboundHandler<DatagramPacket> {
private UDPClient client;
//授权码16字节 + 报文头8个字节 + 报文数据长度2个字节 + 保留2字节 = 28字节
private static final int HEADER_SIZE = 28;
public AgvUdpDecode(UDPClient client) {
this.client = client;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
throws Exception {
//获得应答DatagramPacket提供了content()方法取得报文的实际内容
ByteBuf in = msg.content();
if (in.readableBytes() < HEADER_SIZE){
throw new Exception("readed bytes < header length");
}
int dataLength = in.getShortLE(24);
if (dataLength < 0) {
throw new Exception("bodyLength [" + dataLength + "] is not right, remote:" + ctx.channel().remoteAddress());
}
int neededLength = HEADER_SIZE + dataLength;
int isDataEnough = in.readableBytes() - neededLength;
//收到的数据是否足够组包
if(isDataEnough<0){
// 不够消息体长度(剩下的buffe组不了消息体),重新去组包
throw new Exception("readed bytes < content length");
}else {
//todo这里重写subscribe 的逻辑,注意要区分是 订阅的还是 主动请求的
//组包成功
byte[] body = new byte[neededLength];
in.readBytes(body);
//System.out.println("received bytes :"+ Arrays.toString(body));
String uuid = body[18]+"-"+body[19];
Package mbPackage = new Package(body,uuid);
byte commandCode = body[21];
if(body[18]==(byte)0x00 && body[19]==(byte)0x00){
if(commandCode == (byte)0xAF ){
client.subscribe0xAF(new RcvEventPackage(body[22],body));
}else if(commandCode == (byte)0xB0){
client.subscribe0xB0(new RcvEventPackage(body[22],body));
}else {
SendedList.set(uuid , mbPackage);
}
}else {
SendedList.set(uuid , mbPackage);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}

View File

@ -0,0 +1,93 @@
package org.opentcs.kc.udp.agv.param;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import org.opentcs.kc.common.Package;
import org.opentcs.kc.common.byteutils.ByteUtil;
//import org.opentcs.kc.common.byteutils.ByteUtil;
//import org.opentcs.kc.common.byteutils.ByteUtil;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/27 15:59
*/
public class AgvEvent extends AgvEventHeader implements IAgvEvent, Serializable {
private byte[] bodyLength;
private byte[] retain;
private byte[] body;
//for Request
//有content 没有content 传new byte[]
public AgvEvent(Byte commandCode) {
super(commandCode);
//初始化
bodyLength = new byte[]{0x00,0x00};
retain = new byte[]{0x00,0x00};
body = new byte[]{};
}
public AgvEvent(Byte commandCode,byte[] body) {
super(commandCode);
//初始化
bodyLength = new byte[]{0x00,0x00};
retain = new byte[]{0x00,0x00};
if(commandCode.equals(AgvEventConstant.CommandCode_ISSUE_SUBSCRIPTION)){
//依据命令码 构建参数
this.bodyLength = ByteUtil.shortToBytes((short) body.length);
this.body = body;
}
}
public void setBody(byte[] body) {
this.bodyLength = ByteUtil.shortToBytes((short) body.length);
this.body = body;
}
//for Response
public AgvEvent(Byte serviceCode, Byte commandCode, Byte executionCode) {
super(serviceCode, commandCode, executionCode);
}
@Override
public Package toBytes() {
List<Byte> headerBytes = getHeaderBytes();
for (Byte b : bodyLength) {
headerBytes.add(b);
}
for (Byte b : retain) {
headerBytes.add(b);
}
for (Byte b : body) {
headerBytes.add(b);
}
byte[] bytes = new byte[headerBytes.size()];
for (int i = 0; i < headerBytes.size(); i++) {
bytes[i] = headerBytes.get(i);
}
return new Package(bytes, getTransationIdString());
}
public String getTransationIdString() {
return transationId[0]+"-"+transationId[1];
}
@Override
public Package toBytes(Object newValue) {
return null;
}
@Override
public String toString() {
return "AgvEvent{" +
"Header=" + headerToString() +
", bodyLength=" + Arrays.toString(bodyLength) +
", body=" + Arrays.toString(body) +
'}';
}
@Override
public String protocolName() {
return "";
}
}

View File

@ -0,0 +1,38 @@
package org.opentcs.kc.udp.agv.param;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/27 10:19
*/
public class AgvEventConstant {
public static final byte[] AuthorizationCode = new byte[]{ (byte)0xd4 , (byte)0x97 , (byte)0x44 , (byte)0x9c , (byte)0xcb , (byte)0xcf , (byte)0x0b , (byte)0x4c , (byte)0x95 , (byte)0x51 , (byte)0xd8 , (byte)0x61 , (byte)0x70 , (byte)0xf1 , (byte)0xe7 , (byte)0x94};
public static final byte VersionNum = 0x01;
public static final byte MSGTypeRequest = 0x00;
public static final byte MSGTypeResponse = 0x01;
//命令码 开始
public static final byte ServiceCode = 0x10;
public static final byte CommandCode_READ = 0x02;
public static final byte CommandCode_WRITE = 0x03;
public static final byte CommandCode_MIXED_ISSUANCE_TASK = (byte) 0xAE;
public static final byte CommandCode_QUERY_ROBOT_STATUS = (byte) 0xAF;
public static final byte CommandCode_QUERY_CARRY_STATUS = (byte) 0xB0;
public static final byte CommandCode_ISSUE_SUBSCRIPTION = (byte) 0xB1;
public static final byte CommandCode_ACT_IMMEDIATELY = (byte) 0xB2;
public static final byte CommandCode_SET_ABILITY= (byte) 0xB7;
public static final byte CommandCode_ROBOT_SET_POSITION= (byte) 0x14;
public static final byte CommandCode_GET_ROBOT_POSITION= (byte) 0x15;
public static final byte CommandCode_NAVIGATION_CONTROL= (byte) 0x16;
public static final byte CommandCode_QUERY_ROBOT_NAVIGATION_STATUS= (byte) 0x1D;
public static final byte CommandCode_QUERY_ROBOT_RUN_STATUS= (byte) 0x17;
public static final byte CommandCode_CONFIRM_ROBOT_POSITION= (byte) 0x1F;
//命令码 结束
}

View File

@ -0,0 +1,112 @@
package org.opentcs.kc.udp.agv.param;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.opentcs.kc.common.CaffeineUtil;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024-06-07 13:29
*/
public class AgvEventHeader {
//常量区域 开始
//授权码
public byte[] authorizationCode;
//header 开始
//版本号
public byte versionNum;
//报文类型 0x00请求报文 ; 0x01应答报文
public byte msgType;
//request response transationId一致,2个字节
public byte[] transationId;
//服务码
public byte serviceCode;
//命令码用于区分不同命令request response 相同
public byte commandCode;
//执行码应答报文填写表明命令执行情况请求数据包置 0
public byte executionCode;
//保留
public byte retain;
//header 结束
//常量区域 结束
//request
public AgvEventHeader(Byte commandCode) {
this.authorizationCode = AgvEventConstant.AuthorizationCode;
this.versionNum = AgvEventConstant.VersionNum;
this.msgType = AgvEventConstant.MSGTypeRequest;
this.transationId = CaffeineUtil.getUUIDAGV();
this.serviceCode = 0x10;
this.commandCode = commandCode;
this.executionCode = 0x00;
this.retain = 0x00;
}
public AgvEventHeader(byte[] src){
this.authorizationCode = ByteUtils.copyOfRange(src,0,16);
this.versionNum = src[16];
this.msgType = src[17];
this.transationId = ByteUtils.copyOfRange(src,18,20);
this.serviceCode = src[20];
this.commandCode = src[21];
this.executionCode = src[22];
this.retain = src[23];
}
//response
public AgvEventHeader(Byte serviceCode, Byte commandCode, Byte executionCode) {
this.authorizationCode = AgvEventConstant.AuthorizationCode;
this.versionNum = AgvEventConstant.VersionNum;
this.msgType = AgvEventConstant.MSGTypeResponse;
this.transationId = CaffeineUtil.getUUIDAGV();
this.serviceCode = serviceCode;
}
public byte[] getTransationId() {
return transationId;
}
public String headerToString() {
return "AgvEventHeader{" +
"authorizationCode=" + Arrays.toString(authorizationCode) +
", versionNum=" + versionNum +
", msgType=" + msgType +
", transationId=" + Arrays.toString(transationId) +
", serviceCode=" + serviceCode +
", commandCode=" + commandCode +
", executionCode=" + executionCode +
", retain=" + retain +
'}';
}
public List<Byte> getHeaderBytes(){
List<Byte> bytes = new ArrayList<>();
//add 授权码
for (byte b : authorizationCode) {
bytes.add(b);
}
//add 协议版本号
bytes.add(versionNum);
//add 报文类型
bytes.add(msgType);
//add 报文标识
for (byte b : transationId) {
bytes.add(b);
}
bytes.add(serviceCode);
bytes.add(commandCode);
bytes.add(executionCode);
bytes.add(retain);
return bytes;
}
}

View File

@ -0,0 +1,12 @@
package org.opentcs.kc.udp.agv.param;
import org.opentcs.kc.common.byteutils.ByteUtil;
import org.opentcs.kc.common.Package;
public interface IAgvEvent {
//read sended
public Package toBytes();
//write sended
public Package toBytes(Object newValue);
public String protocolName();
}

View File

@ -0,0 +1,25 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/21 10:03
*/
public class AbnormalEventStatusInfo {
private byte[] src;
//事件码,2个字节
public byte[] eventCode;
//异常等级,2个字节
public byte[] abnormalLevel;
//remain,8个字节
public byte[] remain;
public AbnormalEventStatusInfo(byte[] src) {
this.src = src;
this.eventCode = ByteUtils.copyBytes(src, 0, 2);
this.abnormalLevel = ByteUtils.copyBytes(src, 2, 2);
this.remain = ByteUtils.copyBytes(src, 4, 8);
}
}

View File

@ -0,0 +1,25 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/21 10:38
*/
public class ActionInfo {
private byte[] src;
//动作 ID,4个字节
public byte[] actionId;
//动作状态,1个字节
public byte actionStatus;
//预留,7个字节
public byte[] remain;
public ActionInfo(byte[] src) {
this.src = src;
this.actionId = ByteUtils.copyBytes(src, 0, 4);
this.actionStatus = src[4];
this.remain = ByteUtils.copyBytes(src,5,7);
}
}

View File

@ -0,0 +1,35 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 16:28
*/
public class BatteryStatusInfo {
//src
private byte[] src;
//电量百分比
public float batteryPercentage;
//电压
public float voltage;
//电流
public float electricCurrent;
//充电情况,1个字节
public byte chargingState;
//预留,7个字节
public byte[] remain;
public BatteryStatusInfo(byte[] src) {
this.src = src;
this.batteryPercentage = ByteUtils.bytesToFloat(src, 0);
this.voltage = ByteUtils.bytesToFloat(src, 4);
this.electricCurrent = ByteUtils.bytesToFloat(src, 8);
this.chargingState = src[12];
this.remain=new byte[7];
for (int i = 0; i < 7; i++) {
this.remain[i]=src[13+i];
}
}
}

View File

@ -0,0 +1,80 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 14:56
*/
public class LocationStatusInfo {
private byte[] src;
//4个字节, 全局x,单位 m
public float globalX;
//4个字节, 全局y,单位 m
public float globalY;
//机器人绝对车体方向角,单位 rad
public float absoluteDirecAngle;
//最后通过点 ID
public Integer lastPassPointId;
//最后通过edge ID
public Integer lastPassEdgeId;
//最后通过点在任务中的序列号
public Integer serialNumber;
//置信度,1个字节
public byte confidenceLevel;
//预留8个字节
public byte[] remain;
public static void main(String[] args) {
byte[] src = new byte[32];
src[0] = (byte) 0x73;
src[1] = (byte) 0xde;
src[2] = (byte) 0xba;
src[3] = (byte) 0xbd;
src[4] = (byte) 0x34;
src[5] = (byte) 0xbf;
src[6] = (byte) 0xd0;
src[7] = (byte) 0x40;
src[8] = (byte) 0x59;
src[9] = (byte) 0x3c;
src[10] = (byte) 0x1d;
src[11] = (byte) 0x3d;
System.out.println("x: "+ByteUtils.bytesToFloat(src, 0));
System.out.println("y: "+ByteUtils.bytesToFloat(src, 4));
System.out.println("z: "+ByteUtils.bytesToFloat(src, 8));
}
public LocationStatusInfo(byte[] src) {
this.src = src;
for (byte b:src){
System.out.print(byteToHex(b)+" ");
}
//[115, -34, -70, -67, 52, -65, -48, 64, 89, 60, 29, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
this.globalX = ByteUtils.bytesToFloat(src, 0);
this.globalY = ByteUtils.bytesToFloat(src, 4);
this.absoluteDirecAngle = ByteUtils.bytesToFloat(src, 8);
this.lastPassPointId = ByteUtils.bytesToInt(src, 12);
this.lastPassEdgeId = ByteUtils.bytesToInt(src, 16);
this.serialNumber = ByteUtils.bytesToInt(src, 20);
this.confidenceLevel = src[24];
this.remain = new byte[7];
for (int i = 0; i < 7; i++) {
this.remain[i] = src[25 + i];
}
}
public static String byteToHex(byte b) {
// 将byte转换为无符号整数
int unsignedByte = b & 0xFF;
// 使用Integer.toHexString方法转换为十六进制字符串
String hexString = Integer.toHexString(unsignedByte);
// 如果字符串长度为1需要在前面补0
if (hexString.length() == 1) {
return "0" + hexString;
}
return hexString;
}
}

View File

@ -0,0 +1,22 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 15:49
*/
public class PathStateSequence {
//src
private byte[] src;
//序列号,4个字节
public Integer serialNumber;
//序列号
public Integer pathId;
public PathStateSequence(byte[] src) {
this.src = src;
this.serialNumber = ByteUtils.bytesToInt(src, 0);
this.pathId = ByteUtils.bytesToInt(src, 4);
}
}

View File

@ -0,0 +1,22 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 15:49
*/
public class PointStateSequence {
//src
private byte[] src;
//序列号,4个字节
public Integer serialNumber;
//序列号
public Integer pointId;
public PointStateSequence(byte[] src) {
this.src = src;
this.serialNumber = ByteUtils.bytesToInt(src, 0);
this.pointId = ByteUtils.bytesToInt(src, 4);
}
}

View File

@ -0,0 +1,50 @@
package org.opentcs.kc.udp.agv.param.function.af;
import java.util.List;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 14:55
*/
public class QueryRobotStatusRsp {
private byte[] src;
public byte abnormal_size;
public byte action_size;
//预留2个字节
public byte[] remain;
public LocationStatusInfo locationStatusInfo;
public RunningStatusInfo runningStatusInfo;
public TaskStatusInfo taskStatusInfo;
public BatteryStatusInfo batteryStatusInfo;
public List<AbnormalEventStatusInfo> abnormalEventStatusInfoList;
public List<ActionInfo> actionInfoList;
public QueryRobotStatusRsp(byte[] src) {
this.src = src;
this.abnormal_size = src[0];
this.action_size = src[1];
this.remain = ByteUtils.copyBytes(src,2,2); //index 2
this.locationStatusInfo = new LocationStatusInfo(ByteUtils.copyBytes(src,4,32));
this.runningStatusInfo = new RunningStatusInfo(ByteUtils.copyBytes(src,36,20));
Integer pointSize = ByteUtils.toInt(src[60]);
Integer edgeSize = ByteUtils.toInt(src[61]);
Integer taskByteSize = 12+8*(pointSize+edgeSize);
this.taskStatusInfo = new TaskStatusInfo(ByteUtils.copyBytes(src,56,taskByteSize));
this.batteryStatusInfo = new BatteryStatusInfo(ByteUtils.copyBytes(src,56+taskByteSize,20));
if(this.abnormal_size>0){
for(int i=0;i<this.abnormal_size;i++){
this.abnormalEventStatusInfoList.add(new AbnormalEventStatusInfo(ByteUtils.copyBytes(src,56+taskByteSize+20+12*i,12)));
}
}
if(this.action_size>0){
for(int i=0;i<this.action_size;i++){
this.actionInfoList.add(new ActionInfo(ByteUtils.copyBytes(src,56+taskByteSize+20+12*this.abnormal_size+12*i,12)));
}
}
}
}

View File

@ -0,0 +1,40 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 15:22
*/
public class RunningStatusInfo {
private byte[] src;
//4个字节, 轴x 速度,单位 m/s
public float speedX;
//4个字节, 轴y 速度,单位 m/s
public float speedY;
//车角速度,单位 rad/s
public float angularSpeed;
//工作模式
public byte workMode;
//agv 状态1个字节
public byte agvStatus;
//机器人能力集设置状态,1个字节
public byte abilitySetStatus;
//预留,5个字节
public byte[] remain;
public RunningStatusInfo(byte[] src) {
this.src = src;
this.speedX = ByteUtils.bytesToFloat(src, 0);
this.speedY = ByteUtils.bytesToFloat(src, 4);
this.angularSpeed = ByteUtils.bytesToFloat(src, 8);
this.workMode = src[12];
this.agvStatus = src[13];
this.abilitySetStatus = src[14];
this.remain = new byte[5];
for (int i = 0; i < 5; i++) {
this.remain[i] = src[15 + i];
}
}
}

View File

@ -0,0 +1,49 @@
package org.opentcs.kc.udp.agv.param.function.af;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/20 15:22
*/
public class TaskStatusInfo {
private byte[] src;
//订单 ID
public Integer orderId;
//任务 KEY
public Integer taskKey;
//点状态序列数量
public Integer pointStatusNum;
//段状态序列数量
public Integer edgeStatusNum;
//预留,2个字节
public byte[] remain;
//点状态序列
public PointStateSequence[] pointStatusInfo;
//段状态序列
public PathStateSequence[] pathStatusInfo;
public TaskStatusInfo(byte[] src) {
this.src = src;
this.orderId = ByteUtils.bytesToInt(src, 0);
this.taskKey = ByteUtils.bytesToInt(src, 4);
this.pointStatusNum = ByteUtils.toInt(src[8]);
this.edgeStatusNum = ByteUtils.toInt(src[9]);
this.remain = ByteUtils.copyBytes(src, 10, 2);
if (pointStatusNum > 0) {
this.pointStatusInfo = new PointStateSequence[pointStatusNum];
for (int i = 0; i < pointStatusNum; i++) {
this.pointStatusInfo[i] = new PointStateSequence(ByteUtils.copyBytes(src, 12 + 8 * i, 8));
}
}
if (edgeStatusNum > 0) {
this.pathStatusInfo = new PathStateSequence[edgeStatusNum];
for (int i = 0; i < edgeStatusNum; i++) {
this.pathStatusInfo[i] = new PathStateSequence(ByteUtils.copyBytes(src, 12 + pointStatusNum*8 + 8 * pointStatusNum + 8 * i, 8));
}
}
}
}

View File

@ -0,0 +1,23 @@
package org.opentcs.kc.udp.agv.param.function.b0;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/23 13:34
*/
public class QueryCargoStatusRsp {
//src
private byte[] src;
//是否载货
public byte isCargo;
//预留,3个字节
public byte[] reserved;
public QueryCargoStatusRsp(byte[] src) {
this.src = src;
this.isCargo = src[0];
this.reserved = ByteUtils.copyBytes(src, 1, 3);
}
}

View File

@ -0,0 +1,26 @@
package org.opentcs.kc.udp.agv.param.function.b1;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/23 14:30
*/
public class SubscribeInfo {
//站控协议命令码2个字节
public byte[] commandCode;
//上报间隔时间 ,单位ms 2个字节
public byte[] intervalTime;
//上报持续时间,单位ms 4个字节
public byte[] durationTime;
//预留,8个字节
public byte[] reserved;
public SubscribeInfo(byte[] commandCode, Short intervalTime, Integer durationTime) {
this.commandCode = commandCode;
this.intervalTime = ByteUtils.shortToBytes(intervalTime);
this.durationTime = ByteUtils.intToBytes(durationTime,4);
this.reserved = new byte[8];
}
}

View File

@ -0,0 +1,70 @@
package org.opentcs.kc.udp.agv.param.function.b1;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/1/23 14:23
*/
public class SubscribeParam {
private byte[] src;
private List<SubscribeInfo> subscribeInfoList;
//uuid,64个字节
private byte[] uuid;
public SubscribeParam(List<SubscribeInfo> subscribeInfoList) {
this.subscribeInfoList = subscribeInfoList;
this.uuid = generate64ByteUUID();
}
public byte[] getUuid() {
return uuid;
}
// 生成 64 字节长度的 UUID返回 byte[] 类型
public static byte[] generate64ByteUUID() {
// 创建 SecureRandom 实例用于生成安全的随机数
SecureRandom secureRandom = new SecureRandom();
// 定义一个长度为 64 的字节数组
byte[] uuidBytes = new byte[64];
// 使用 SecureRandom 生成随机字节填充字节数组
secureRandom.nextBytes(uuidBytes);
return uuidBytes;
}
public byte[] toBytes(){
src = new byte[192];
List<Byte> bytes = new ArrayList<>();
for (SubscribeInfo subscribeInfo : subscribeInfoList) {
bytes.add(subscribeInfo.commandCode[0]);
bytes.add(subscribeInfo.commandCode[1]);
bytes.add(subscribeInfo.intervalTime[0]);
bytes.add(subscribeInfo.intervalTime[1]);
bytes.add(subscribeInfo.durationTime[0]);
bytes.add(subscribeInfo.durationTime[1]);
bytes.add(subscribeInfo.durationTime[2]);
bytes.add(subscribeInfo.durationTime[3]);
bytes.add(subscribeInfo.reserved[0]);
bytes.add(subscribeInfo.reserved[1]);
bytes.add(subscribeInfo.reserved[2]);
bytes.add(subscribeInfo.reserved[3]);
bytes.add(subscribeInfo.reserved[4]);
bytes.add(subscribeInfo.reserved[5]);
bytes.add(subscribeInfo.reserved[6]);
bytes.add(subscribeInfo.reserved[7]);
}
for (int i = 0; i < bytes.size(); i++) {
src[i] = bytes.get(i);
}
for (int i = 0; i < uuid.length; i++) {
src[i+128] = uuid[i];
}
return src;
}
}

View File

@ -0,0 +1,33 @@
package org.opentcs.kc.udp.agv.param.function.b1;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/5 14:21
*/
public class SubscribeRsp {
//源数组
private byte[] src;
//uuid 64个字节
private byte[] uuid;
//errCode 1个字节
private byte errCode;
//reserved 3个字节
private byte[] reserved;
public SubscribeRsp(byte[] src) {
this.src = src;
this.uuid = ByteUtils.copyOfRange(src,0,64);
this.errCode = src[64];
this.reserved = ByteUtils.copyOfRange(src,65,3);
}
public boolean isOk(){
if(this.errCode==0){
return true;
}else {
return false;
}
}
}

View File

@ -0,0 +1,53 @@
package org.opentcs.kc.udp.agv.param.function.navigation;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/8 10:05
*/
public class Action {
//动作类型,2个字节
private byte[] actionType;
//执行动作并行方式,1个字节
private byte actionParallel;
//预留1个字节
private byte actionRetain;
//动作 id,4个字节actionId 自己定义不要重复就行可以从0开始累加
private byte[] actionId;
//参数长度,1个字节
private byte paramSize;
//预留3个字节
private byte[] paramRetain2;
//参数内容,这里的长度 paramSize 长度,todo 注意 这里的paramContent 需要字节去构建一下 目前 这里还没有
private byte[] paramContent;
public Action(byte actionType, byte actionParallel, Integer actionId, Integer paramSize, byte[] paramContent) {
this.actionType = new byte[]{actionType, (byte)0x00};
this.actionParallel = actionParallel;
this.actionRetain = (byte)0x00;
this.actionId = ByteUtils.intToBytes(actionId,1);
this.paramSize = ByteUtils.usintTo1Byte(paramSize);
this.paramRetain2 = new byte[]{(byte)0x00, (byte)0x00,(byte)0x00};
this.paramContent = paramContent;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(actionType);
byteBuf.writeByte(actionParallel);
byteBuf.writeByte(actionRetain);
byteBuf.writeBytes(actionId);
byteBuf.writeByte(paramSize);
byteBuf.writeBytes(paramRetain2);
byteBuf.writeBytes(paramContent);
int i = byteBuf.writerIndex();
byte[] bytes = new byte[i];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,77 @@
package org.opentcs.kc.udp.agv.param.function.navigation;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/10 8:56
*/
public class ActionSet {
public static void main(String[] args) {
byte[] stop = stop0x01(1, (byte) 0x01);
System.out.println();
}
public static byte stop0x01 = 0x01;
public static byte recover0x02 = 0x02;
public static byte cancelTask0x03 = 0x03;
public static byte forkliftElevation0x12 = 0x12;
public static byte trayElevation0x16 = 0x16;
public static Integer stop0x01_paramsize = 8;
public static Integer recover0x02_paramsize = 8;
public static Integer cancelTask0x03_paramsize = 8;
public static Integer forkliftElevation0x12_paramsize = 8;
public static Integer trayElevation0x16_paramsize = 4;
public static byte[] stop0x01(Integer orderId,byte isStopImmediately){
ByteBuf byteBuf = Unpooled.buffer(8);
byteBuf.writeBytes(ByteUtils.intToBytes(orderId,1));
byteBuf.writeByte(isStopImmediately);
return byteBuf.array();
}
public static byte[] recover0x02(Integer orderId,Integer taskKey){
ByteBuf byteBuf = Unpooled.buffer(8);
byteBuf.writeBytes(ByteUtils.intToBytes(orderId,1));
byteBuf.writeBytes(ByteUtils.intToBytes(taskKey,1));
return byteBuf.array();
}
//取消任务
public static byte[] cancelTask0x03(Integer orderId,byte isStopImmediately){
ByteBuf byteBuf = Unpooled.buffer(8);
byteBuf.writeBytes(ByteUtils.intToBytes(orderId,1));
byteBuf.writeByte(isStopImmediately);
return byteBuf.array();
}
/**
* desc:叉齿升降
* 支持的命令0xAE 0xB2
*
* */
public static byte[] forkliftElevation0x12(float lifitingHeight,byte hightMean,byte moveType, byte taskOperationType){
ByteBuf byteBuf = Unpooled.buffer(8);
byteBuf.writeBytes(ByteUtils.floatToBytes(lifitingHeight));
byteBuf.writeByte(hightMean);
byteBuf.writeByte(moveType);
byteBuf.writeByte(taskOperationType);
return byteBuf.array();
}
//托盘升降
public static byte[] trayElevation0x16(byte palletMovementMode){
ByteBuf byteBuf = Unpooled.buffer(4);
byteBuf.writeByte(palletMovementMode);
return byteBuf.array();
}
}

View File

@ -0,0 +1,57 @@
package org.opentcs.kc.udp.agv.param.function.navigation;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: "0xAE 导航参数"
* @Author: caixiang
* @DATE: 2024/12/30 15:44
*/
public class NavigationParam {
//订单 ID,4个字节 ,Integer,从1开始依次累加
private byte[] orderId;
//任务 KEY,4个字节,Integer,从1开始依次累加
private byte[] taskKey;
//路径点信息个数单条任务最大支持 8 个路径点,1个字节 ,byte
private byte pointNum;
//路径信息个数单条任务最大支持 8 个路径点,1个字节,byte
private byte pathNum;
//保留,2个字节
private byte[] retain;
//任务中路径点信息结构体,长度 pointNum
private Point[] points;
//任务中路径信息结构体长度 pathNum
private Path[] paths;
public NavigationParam(Integer orderId, Integer taskKey, byte pointNum, byte pathNum, Point[] points, Path[] paths) {
this.orderId = ByteUtils.intToBytes(orderId,1);
this.taskKey = ByteUtils.intToBytes(taskKey,1);
this.pointNum = pointNum;
this.pathNum = pathNum;
this.retain = new byte[]{(byte)0x00, (byte)0x00};
this.points = points;
this.paths = paths;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(orderId);
byteBuf.writeBytes(taskKey);
byteBuf.writeByte(pointNum);
byteBuf.writeByte(pathNum);
byteBuf.writeBytes(retain);
for (Point point : points) {
byteBuf.writeBytes(point.toBytes());
}
for (Path path : paths) {
byteBuf.writeBytes(path.toBytes());
}
int i = byteBuf.writerIndex();
byte[] bytes = new byte[i];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,74 @@
package org.opentcs.kc.udp.agv.param.function.navigation;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/8 9:39
*/
public class Path {
//序列号,4个字节,Integer,用于定位段在整个任务中的位置 1 开始奇数递增 135......point 2 4 6这样path 和point就可以结合起来了以区分同一个段 ID 在任务中是否多次出现
private byte[] serialNum;
// ID,4个字节,Integer
private byte[] pathId;
//指定角度时路径点的车头角度,4个字节,float , isSpecifyAngle==1 的时候这个字段才生效
private byte[] angle;
//是否指定路径点角度,1个字节,byte,1 表示指定0 表示不指定
private byte isSpecifyAngle;
//行驶姿态,1个字节,byte
private byte drivePose;
//任务中边上动作信息个数,1个字节,byte
private byte pathActionSize;
//保留,1个字节
private byte pathRetain;
//指定的目标最大速度,4个字节,float
private byte[] maxSpeed;
//指定的目标最大角速度,4个字节,float
private byte[] maxAngularSpeed;
//预留4个字节
private byte[] pathRetain2;
//任务中点上动作结构体,这里数组的长度是 pathActionSize 长度
private Action[] actions;
public Path(Integer serialNum, Integer pathId, float angle, byte isSpecifyAngle, byte drivePose, byte pathActionSize, float maxSpeed, float maxAngularSpeed, Action[] actions) {
this.serialNum = ByteUtils.intToBytes(serialNum,1);
this.pathId = ByteUtils.intToBytes(pathId,1);
this.angle = ByteUtils.floatToBytes(angle);
this.isSpecifyAngle = isSpecifyAngle;
this.drivePose = drivePose;
this.pathActionSize = pathActionSize;
this.pathRetain = (byte)0x00;
this.maxSpeed = ByteUtils.floatToBytes(maxSpeed);
this.maxAngularSpeed = ByteUtils.floatToBytes(maxAngularSpeed);
this.pathRetain2 = new byte[]{(byte)0x00, (byte)0x00,(byte)0x00,(byte)0x00};
this.actions = actions;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(serialNum);
byteBuf.writeBytes(pathId);
byteBuf.writeBytes(angle);
byteBuf.writeByte(isSpecifyAngle);
byteBuf.writeByte(drivePose);
byteBuf.writeByte(pathActionSize);
byteBuf.writeByte(pathRetain);
byteBuf.writeBytes(maxSpeed);
byteBuf.writeBytes(maxAngularSpeed);
byteBuf.writeBytes(pathRetain2);
for (Action action : actions) {
byteBuf.writeBytes(action.toBytes());
}
int i = byteBuf.writerIndex();
byte[] bytes = new byte[i];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,54 @@
package org.opentcs.kc.udp.agv.param.function.navigation;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/8 9:39
*/
public class Point {
//序列号,4个字节,Integer,用于定位点在整个任务中的位置 0 开始偶数递增 0246......以区分同一个点 ID 在任务中是否多次出现
private byte[] serialNum;
//路径点 ID,4个字节,Integer
private byte[] pointId;
//指定角度时路径点的车头角度,4个字节,float , isSpecifyAngle==1 的时候这个字段才生效
private byte[] angle;
//是否指定路径点角度,1个字节,byte,1 表示指定0 表示不指定
private byte isSpecifyAngle;
//任务中点上动作信息个数,1个字节,byte
private byte pointActionSize;
//保留,6个字节
private byte[] pointRetain;
//任务中点上动作结构体,这里数组的长度是 pointActionSize 长度
private Action[] actions;
public Point(Integer serialNum, Integer pointId, float angle, byte isSpecifyAngle, byte pointActionSize, Action[] actions) {
this.serialNum = ByteUtils.intToBytes(serialNum,1);
this.pointId = ByteUtils.intToBytes(pointId,1);
this.angle = ByteUtils.floatToBytes(angle);
this.isSpecifyAngle = isSpecifyAngle;
this.pointActionSize = pointActionSize;
this.pointRetain = new byte[]{(byte)0x00, (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00};
this.actions = actions;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(serialNum);
byteBuf.writeBytes(pointId);
byteBuf.writeBytes(angle);
byteBuf.writeByte(isSpecifyAngle);
byteBuf.writeByte(pointActionSize);
byteBuf.writeBytes(pointRetain);
for (Action action : actions) {
byteBuf.writeBytes(action.toBytes());
}
int i = byteBuf.writerIndex();
byte[] bytes = new byte[i];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,51 @@
package org.opentcs.kc.udp.agv.param.function.read;
import java.util.ArrayList;
import java.util.List;
import org.opentcs.kc.common.byteutils.ByteUtil;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/30 15:44
*/
public class ReadParam {
private byte valueNum;
private byte[] retain;
private byte[] valueId;
private byte[] readStrValues;
public ReadParam(byte[] valueId, List<ReadStrValue> rvalues) {
// 这里的valueId 也是 header里面的 transationId
valueNum = ByteUtil.intTo1Byte(rvalues.size());
retain = new byte[]{0x00,0x00,0x00};
this.valueId = new byte[]{valueId[0],valueId[1],0x00,0x00};
List<Byte> bytes = new ArrayList<>();
for (ReadStrValue b : rvalues) {
bytes.addAll(b.toBytes());
}
readStrValues = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); i++) {
readStrValues[i] = bytes.get(i);
}
}
public byte[] toBytes() {
List<Byte> bytes = new ArrayList<>();
bytes.add(valueNum);
for (byte b : retain) {
bytes.add(b);
}
for (byte b : valueId) {
bytes.add(b);
}
for (byte b : readStrValues) {
bytes.add(b);
}
byte[] b = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); i++) {
b[i] = bytes.get(i);
}
return b;
}
}

View File

@ -0,0 +1,37 @@
package org.opentcs.kc.udp.agv.param.function.read;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/5 14:55
*/
public class ReadRsp {
//源数组
private byte[] src;
//ValueID ,4个字节
public byte[] valueId;
//应答数据总长度,2个字节
public short valueByteLength;
//预留,2个字节
public byte[] reserved;
//变量值,长度是 valueByteLength
public byte[] dataValue;
public ReadRsp(byte[] src) {
this.src = src;
this.valueId = ByteUtils.copyBytes(src, 0, 4);
this.valueByteLength = ByteUtils.bytesToShort(ByteUtils.copyBytes(src, 4, 2),1);
this.reserved = ByteUtils.copyBytes(src, 6, 2);
this.dataValue = ByteUtils.copyBytes(src, 8, valueByteLength-8);
}
public boolean isOk(){
if(valueByteLength<=0){
return false;
}else {
return true;
}
}
}

View File

@ -0,0 +1,49 @@
package org.opentcs.kc.udp.agv.param.function.read;
import java.util.ArrayList;
import java.util.List;
import org.opentcs.kc.common.byteutils.ByteUtil;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/30 15:46
*/
public class ReadStrValue {
//变量名16个字节( 以这个变量名来定位变量的)
private byte[] varName;
//成员变量数量4个字节
private byte[] memberVarNum;
private byte[] memberList;
public ReadStrValue(String varName, Integer memberVarNum, List<ReadValueMember> memberList) {
this.varName = ByteUtil.stringTo16Byte(varName);
this.memberVarNum = ByteUtil.intToBytes(memberVarNum);
List<Byte> bytes = new ArrayList<>();
for (ReadValueMember member : memberList) {
bytes.addAll(member.toBytes());
}
this.memberList = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); i++) {
this.memberList[i] = bytes.get(i);
}
}
public ReadStrValue(String varName) {
this.varName = ByteUtil.stringTo16Byte(varName);
this.memberVarNum = ByteUtil.intToBytes(0);
this.memberList = new byte[0];
}
public List<Byte> toBytes() {
List<Byte> bytes = new ArrayList<>();
for (byte b : varName) {
bytes.add(b);
}
for (byte b : memberVarNum) {
bytes.add(b);
}
for (byte b : memberList) {
bytes.add(b);
}
return bytes;
}
}

View File

@ -0,0 +1,34 @@
package org.opentcs.kc.udp.agv.param.function.read;
import java.util.ArrayList;
import java.util.List;
import org.opentcs.kc.common.byteutils.ByteUtil;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2024/12/30 15:49
*/
public class ReadValueMember {
//偏移值就是以这个变量的起始点为基准偏移几个字节来读取数组里的后面几个变量基本上用于数组变量单体变量用不着 2个字节
private byte[] offsetValue;
//变量成员长度就是你要读取的content长度可以是1个变量的长度也可以是n个变量的长度(就是数据类型 的字节长度)
private byte[] memberVarLength;
public ReadValueMember(Short offsetValue, Short memberVarLength) {
this.offsetValue = ByteUtil.shortToBytes(offsetValue);
this.memberVarLength = ByteUtil.shortToBytes(memberVarLength);
}
public List<Byte> toBytes() {
List<Byte> bytes = new ArrayList<>();
for (byte b : offsetValue) {
bytes.add(b);
}
for (byte b : memberVarLength) {
bytes.add(b);
}
return bytes;
}
}

View File

@ -0,0 +1,39 @@
package org.opentcs.kc.udp.agv.param.function.write;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.List;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/5 16:41
*/
public class WriteParam {
//变量数量 1个字节 ,最大只支持 15 个变量
private byte valueNum;
//保留 3个字节
private byte[] retain;
//这里的length 就是 valueNum
private List<WriteStrValue> values;
public WriteParam(Integer valueNum, List<WriteStrValue> values) {
this.valueNum = ByteUtils.usintTo1Byte(valueNum);
this.retain = new byte[]{(byte)0x00,(byte)0x00,(byte)0x00};
this.values = values;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeByte(valueNum);
byteBuf.writeBytes(retain);
for (WriteStrValue value : values) {
byteBuf.writeBytes(value.toBytes());
}
int i = byteBuf.writerIndex();
byte[] bytes = new byte[i];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,41 @@
package org.opentcs.kc.udp.agv.param.function.write;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.List;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/5 16:43
*/
public class WriteStrValue {
//变量名 16个字节
private String valueName;
//变量成员数量 4个字节
private Integer valueNum;
private List<WriteValueMember> members;
public WriteStrValue(String valueName, Integer valueNum, List<WriteValueMember> members) {
this.valueName = valueName;
this.valueNum = valueNum;
this.members = members;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(ByteUtils.stringToBytes(valueName, 16));
byteBuf.writeBytes(ByteUtils.intToBytes(valueNum,1));
for (WriteValueMember member : members) {
byteBuf.writeBytes(member.toBytes());
}
int i = byteBuf.writerIndex();
byte[] bytes = new byte[i];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,32 @@
package org.opentcs.kc.udp.agv.param.function.write;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/5 16:44
*/
public class WriteValueMember {
//变量成员偏移 2个字节
private byte[] valueOffset;
//变量成员长度 2个字节
private byte[] valueLength;
//变量成员值 4个字节
private byte[] newValue;
public WriteValueMember(Short valueOffset, Short valueLength, byte[] newValue) {
this.valueOffset = ByteUtils.shortToBytes(valueOffset);
this.valueLength = ByteUtils.shortToBytes(valueLength);
this.newValue = newValue;
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer(8);
byteBuf.writeBytes(valueOffset);
byteBuf.writeBytes(valueLength);
byteBuf.writeBytes(newValue);
return byteBuf.array();
}
}

View File

@ -0,0 +1,32 @@
package org.opentcs.kc.udp.agv.param.function.x14;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.opentcs.kc.common.byteutils.ByteUtil;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/7 9:21
*/
public class RobotSetPosition {
//机器人 x 坐标 ,8 个字节
private byte[] robotX;
//机器人 y 坐标 ,8 个字节
private byte[] robotY;
//机器人朝向角度 ,8 个字节
private byte[] robotAngle;
public RobotSetPosition(double robotX, double robotY, double robotAngle) {
this.robotX = ByteUtil.doubleToBytes(robotX);
this.robotY = ByteUtil.doubleToBytes(robotY);
this.robotAngle = ByteUtil.doubleToBytes(robotAngle);
}
public byte[] toBytes() {
ByteBuf byteBuf = Unpooled.buffer(24);
byteBuf.writeBytes(robotX);
byteBuf.writeBytes(robotY);
byteBuf.writeBytes(robotAngle);
return byteBuf.array();
}
}

View File

@ -0,0 +1,130 @@
package org.opentcs.kc.udp.agv.param.function.x17;
import java.util.Arrays;
import org.opentcs.kc.common.byteutils.ByteUtils;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2025/2/7 9:43
*/
public class QueryRobotRunStatusRsp {
//本体温度 ,8个字节 ,double
private double temp;
//位置的 X 坐标 , 8个字节 ,double
private double currentX;
//位置的 Y 坐标,8个字节 ,double
private double currentY;
//位置的 角度 ,8个字节 ,double
private double currentAngle;
//电池电量,,8个字节 ,double
private double battery;
//是否被阻挡,1个字节,byte
private byte isBlocked;
//是否在充电,1个字节,byte
private byte isCharging;
//运行模式,1个字节,byte
private byte runMode;
//地图载入状态,1个字节,byte
private byte mapLoadState;
//当前的目标点 id,U32 ,4个字节
private Integer currentTargetId;
//前进速度,8个字节,double
private double forwardSpeed;
//转弯速度,8个字节,double
private double turnSpeed;
//电池电压充电为正放电为负,8个字节,double
private double batteryVoltage;
//电流充电为正放电为负,double
private double electricCurrent;
//当前任务状态,1个字节,byte
private byte taskState;
//保留1个字节
private byte retain1;
//地图版本号,2个字节
private short mapVersion;
//保留4个字节
private byte[] retain2;
//累计行驶里程单位 m,8个字节
private double cumulativeMileage;
//本次运行时间单位 ms,8个字节
private double currentRunTime;
//累计运行时间单位 ms,8个字节
private double cumulativeRunTime;
//机器人定位状态,1个字节
private byte robotLocalizationState;
//保留3个字节
private byte[] retain3;
//地图数量U32,4个字节
private Integer mapNum;
//当前地图名称,64字节
private String currentMapName;
//置信度,4个字节
private float confidence;
//保留4个字节
private byte[] retain4;
public QueryRobotRunStatusRsp(byte[] src) {
this.temp = ByteUtils.bytesToDouble(src, 0);
this.currentX = ByteUtils.bytesToDouble(src, 8);
this.currentY = ByteUtils.bytesToDouble(src, 16);
this.currentAngle = ByteUtils.bytesToDouble(src, 24);
this.battery = ByteUtils.bytesToDouble(src, 32);
this.isBlocked = src[40];
this.isCharging = src[41];
this.runMode = src[42];
this.mapLoadState = src[43];
this.currentTargetId = ByteUtils.bytesToInt(src, 44);
this.forwardSpeed = ByteUtils.bytesToDouble(src, 48);
this.turnSpeed = ByteUtils.bytesToDouble(src, 56);
this.batteryVoltage = ByteUtils.bytesToDouble(src, 64);
this.electricCurrent = ByteUtils.bytesToDouble(src, 72);
this.taskState = src[80];
this.retain1 = src[81];
this.mapVersion = ByteUtils.bytesToShortLitt(src, 82);
this.retain2 = ByteUtils.copyBytes(src, 84, 4);
this.cumulativeMileage = ByteUtils.bytesToDouble(src, 88);
this.currentRunTime = ByteUtils.bytesToDouble(src, 96);
this.cumulativeRunTime = ByteUtils.bytesToDouble(src, 104);
this.robotLocalizationState = src[112];
this.retain3 = ByteUtils.copyBytes(src, 113, 3);
this.mapNum = ByteUtils.bytesToInt(src, 116);
this.currentMapName = ByteUtils.bytesToString(src, 120, 64);
this.confidence = ByteUtils.bytesToFloat(src, 184);
this.retain4 = ByteUtils.copyBytes(src, 188, 4);
}
@Override
public String toString() {
return "QueryRobotRunStatusRsp{" +
"temp=" + temp +
", currentX=" + currentX +
", currentY=" + currentY +
", currentAngle=" + currentAngle +
", battery=" + battery +
", isBlocked=" + isBlocked +
", isCharging=" + isCharging +
", runMode=" + runMode +
", mapLoadState=" + mapLoadState +
", currentTargetId=" + currentTargetId +
", forwardSpeed=" + forwardSpeed +
", turnSpeed=" + turnSpeed +
", batteryVoltage=" + batteryVoltage +
", electricCurrent=" + electricCurrent +
", taskState=" + taskState +
", retain1=" + retain1 +
", mapVersion=" + mapVersion +
", retain2=" + Arrays.toString(retain2) +
", cumulativeMileage=" + cumulativeMileage +
", cuttentRunTime=" + currentRunTime +
", cumulativeRunTime=" + cumulativeRunTime +
", robotLocalizationState=" + robotLocalizationState +
", retain3=" + Arrays.toString(retain3) +
", mapNum=" + mapNum +
", currentMapName='" + currentMapName + '\'' +
", confidence=" + confidence +
", retain4=" + Arrays.toString(retain4) +
'}';
}
}

View File

@ -0,0 +1,89 @@
package org.opentcs.kc.udp.agv.param.rsp;
import org.opentcs.kc.common.byteutils.ByteUtils;
import org.opentcs.kc.udp.agv.param.AgvEventHeader;
/**
* @Desc: "通用的数据传输底层类"
* @Author: caixiang
* @DATE: 2022/10/18 16:22
*/
public class RcvEventPackage {
private boolean isOk;
private byte[] value;
private String content;
// header size = 授权码16 + 报文头12(包括报文数据长度2个字节+保留2个字节) = 28
private AgvEventHeader header;
private byte[] dataBytes;
private String getContent(byte i) {
if(i==0x00){
return "成功执行";
}else if(i==0x01){
return "执行失败,原因未知";
}else if(i==0x02){
return "服务码错误";
}else if(i==0x03){
return "命令码错误";
}else if(i==0x04){
return "报文头部错误";
}else if(i==0x80){
return "无法执行命令,因为当前车辆导航状态与命令冲突";
}else if(i==0xFF){
return "协议授权码错误";
}else {
return "未知错误";
}
}
public RcvEventPackage(byte executionCode, byte[] value) {
if(executionCode == 0x00){
this.isOk = true;
this.header = new AgvEventHeader(ByteUtils.copyBytes(value,0,28));
this.dataBytes = ByteUtils.copyBytes(value,28,value.length-28);
}else {
this.isOk = false;
}
this.value = value;
this.content = getContent(executionCode);
}
public RcvEventPackage() {
}
public boolean isOk() {
return isOk;
}
public void setValue(byte[] value) {
this.value = value;
}
public byte[] getValue() {
return value;
}
public String getContent() {
return content;
}
public AgvEventHeader getHeader() {
return header;
}
public byte[] getDataBytes() {
return dataBytes;
}
@Override
public String toString() {
return "RcvPackage{" +
"isOk=" + isOk +
", value=" + value +
", content='" + content + '\'' +
'}';
}
}

View File

@ -0,0 +1,212 @@
package org.opentcs.kc.udp.io;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.net.InetSocketAddress;
import org.opentcs.kc.common.Package;
import org.opentcs.kc.common.byteutils.ByteUtil;
import org.opentcs.kc.syn.AsyncFuture;
import org.opentcs.kc.syn.SendedList;
import org.opentcs.kc.udp.agv.codec.AgvUdpChannelInitializer;
import org.opentcs.kc.udp.agv.param.AgvEvent;
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.rsp.RcvEventPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Desc: ""
* @Author: caixiang
* @DATE: 2022/1/15 13:01
*/
public enum UDPClient {
//如果要配置多个链接 local1 local2 .... 这样排下去好了
localAGV("agv1","192.168.0.211",17804,55678),
//local("127.0.0.1",502,true),
;
private String name;
private String host;
//默认 0 port
private Integer port;
private Integer bindPort;
private Bootstrap bootstrap;
private NioEventLoopGroup group;
private Channel conn;
private boolean isOnline;
private static final Logger logger = LoggerFactory.getLogger("AGVLOGGER");
// ==== 连接节点配置信息 ===== 开始
// ==== 连接节点配置信息 ===== 结束
UDPClient() {
}
//coreSize 是线程池的数量
UDPClient(String name, String host, Integer port, Integer bindPort) {
this.name = name;
this.host = host;
this.port = port;
this.conn = null;
this.bindPort = bindPort;
initClient();
}
public String getHost(){
return this.host;
}
public String getName(){
return this.name;
}
public void setIsOnline(boolean isOnline){
this.isOnline = isOnline;
}
/**
* desc : 判断此链接 健康状况
* return
* true : 此tcp连接 正常
* false : 此tcp连接 异常
* */
public boolean isOnline(){
return isOnline;
}
public void close(){
//手动关闭连接会出发InActivite 事件
group.shutdownGracefully();
}
public static ByteBuf byteArrayToByteBuf(byte[] byteArray) {
// 使用Unpooled类创建ByteBuf
ByteBuf byteBuf = Unpooled.buffer(byteArray.length);
// 将byte[]写入ByteBuf
byteBuf.writeBytes(byteArray);
return byteBuf;
}
/**
* --线程安全的--
*
* 如果返回null就代表出现了异常并且尝试了 retryMax 次数并且尝试重置连接
* */
public RcvEventPackage send(AgvEvent event) {
try {
//sendread 报文
//应该是ReadRequestFrame 继承 Packet 然后直接 Tio.bSend(this.conn, ReadRequestFrame)
Package mbPackage = event.toBytes();
AsyncFuture<Package> add = SendedList.add(mbPackage.getTransationId(),null);
this.conn.writeAndFlush(
new DatagramPacket(
byteArrayToByteBuf(mbPackage.getBody()),
new InetSocketAddress(this.host,this.port)))
.sync();
Package aPackage = add.get(5000L, mbPackage.getTransationId());
byte[] body = aPackage.getBody();
String errMsg = " [ AGVclient - send success ] [ "+name+" host: "+ this.host +" ]"+event.toString();
logger.info(errMsg);
//注意这里的body 是整个 response结构包括 : 授权码 + header + body
return new RcvEventPackage(body[22],body);
}catch (Throwable e) {
//e.printStackTrace();
String errMsg = " [ AGVclient - Read ] [ "+name+" host: "+ this.host +" ] ( occur err ) errTime: "+" ; send errMsg : "+e.getMessage()+" ; event :"+event.toString();
logger.info(errMsg);
throw new RuntimeException(errMsg);
}
}
public void subscribe0xB0(RcvEventPackage rcv){
if(name.equals("agv1")){
QueryCargoStatusRsp queryCargoStatusRsp = new QueryCargoStatusRsp(rcv.getDataBytes());
System.out.println();
System.out.println("received subscribe 0xB0 List : "+ "isok:"+rcv.isOk());
for (byte b:rcv.getValue()){
System.out.print(byteToHex(b)+" ");
}
}else if(name.equals("agv2")){
//....
}
}
public void subscribe0xAF(RcvEventPackage rcv){
if(name.equals("agv1")){
if(rcv.isOk()){
QueryRobotStatusRsp queryRobotStatusRsp = new QueryRobotStatusRsp(rcv.getDataBytes());
System.out.println();
System.out.println("received subscribe 0xAF List : "+ "isok:"+rcv.isOk());
for (byte b:rcv.getValue()){
System.out.print(byteToHex(b)+" ");
}
}else {
System.out.println();
System.out.println("received transationId : "+ "isok:"+rcv.isOk());
}
}else if(name.equals("agv2")){
//....
}
}
public static void printInfo(AgvEvent agvEvent){
System.out.println("sended transationId : "+agvEvent.getTransationIdString());
for (byte b:agvEvent.toBytes().getBody()){
System.out.print(byteToHex(b)+" ");
}
}
public static String byteToHex(byte b) {
// 将byte转换为无符号整数
int unsignedByte = b & 0xFF;
// 使用Integer.toHexString方法转换为十六进制字符串
String hexString = Integer.toHexString(unsignedByte);
// 如果字符串长度为1需要在前面补0
if (hexString.length() == 1) {
return "0" + hexString;
}
return hexString;
}
/**
* decs: 在项目启动的时候初始化 后面就不会初始化了
* */
private void initClient() {
//NioEventLoopGroup Bootstrap 这些资源其实是不用 release的因为全局共用一份的你释放了 下次还得再new
NioEventLoopGroup group = new NioEventLoopGroup();
this.bootstrap = new Bootstrap();
this.bootstrap.group(group)
/*由于我们用的是UDP协议所以要用NioDatagramChannel来创建*/
.channel(NioDatagramChannel.class)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//0 代表禁用 readerIdleTime 事件的监听
//这两个是固定的
//pipeline.addLast(new IdleStateHandler(0,0,10, TimeUnit.SECONDS));
//协议层的handler 这里要做以区分
pipeline.addLast(new AgvUdpChannelInitializer(UDPClient.this));
}
});
try {
this.conn = this.bootstrap.bind(this.bindPort).sync().channel();
}catch (Exception e){
logger.info("AGV UDP Initial Exception : "+e.getMessage());
}
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL如果设置为WARN则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时配置文档如果发生改变将会被重新加载默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
当scan为true时此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时将打印出logback内部日志信息实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logback-spring</contextName>
<property name="logging.eqlog" value="/var/log/logger/EQLog/njlm" />
<!-- agv logger 开始 -->
<property name="logging.agvlog" value="/var/log/logger/agvUdp/" />
<appender name="AGV_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.agvlog}/today/log-info.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${logging.agvlog}/agv-log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>999</maxHistory>
<totalSizeCap>200GB</totalSizeCap>
</rollingPolicy>
<!-- 此日志文档只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<logger name="AGVLOGGER" level="INFO" additivity="false">
<appender-ref ref="AGV_APPENDER" />
</logger>
<!-- agv logger 结束 -->
</configuration>