diff --git a/opentcs-common/build.gradle b/opentcs-common/build.gradle index 8c174b8..da0d706 100644 --- a/opentcs-common/build.gradle +++ b/opentcs-common/build.gradle @@ -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 { diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/CaffeineUtil.java b/opentcs-common/src/main/java/org/opentcs/kc/common/CaffeineUtil.java new file mode 100644 index 0000000..476eaf8 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/CaffeineUtil.java @@ -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 cache; + private static Cache 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 getAllValues() { + return cache.asMap().values(); + } + + /** + * 清空缓存中的所有值 + */ + public static void removeAllValues() { + cache.invalidateAll(); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/Const.java b/opentcs-common/src/main/java/org/opentcs/kc/common/Const.java new file mode 100644 index 0000000..d404334 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/Const.java @@ -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; +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/Package.java b/opentcs-common/src/main/java/org/opentcs/kc/common/Package.java new file mode 100644 index 0000000..e078b23 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/Package.java @@ -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 + '\'' + + '}'; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/ParseDataException.java b/opentcs-common/src/main/java/org/opentcs/kc/common/ParseDataException.java new file mode 100644 index 0000000..1689b03 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/ParseDataException.java @@ -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); + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/RcvPackage.java b/opentcs-common/src/main/java/org/opentcs/kc/common/RcvPackage.java new file mode 100644 index 0000000..a5156d8 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/RcvPackage.java @@ -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 + '\'' + + '}'; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/Utils.java b/opentcs-common/src/main/java/org/opentcs/kc/common/Utils.java new file mode 100644 index 0000000..c5557bf --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/Utils.java @@ -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")); + } + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/ByteUtil.java b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/ByteUtil.java new file mode 100644 index 0000000..de15900 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/ByteUtil.java @@ -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
+ * 默认以小端序转换 + * + * @param bytes byte数组 + * @return short值 + */ + public static short bytesToShort(byte[] bytes) { + return bytesToShort(bytes, ByteOrder.LITTLE_ENDIAN); + } + + /** + * byte数组转short
+ * 自定义端序 + * + * @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数组
+ * 默认以小端序转换 + * + * @param shortValue short值 + * @return byte数组 + */ + public static byte[] shortToBytes(short shortValue) { + return shortToBytes(shortValue, ByteOrder.LITTLE_ENDIAN); + } + + /** + * short转byte数组
+ * 自定义端序 + * + * @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值
+ * 默认以小端序转换 + * + * @param bytes byte数组 + * @return int值 + */ + public static int bytesToInt(byte[] bytes) { + return bytesToInt(bytes, ByteOrder.LITTLE_ENDIAN); + } + + /** + * byte[]转int值
+ * 自定义端序 + * + * @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数组
+ * 默认以小端序转换 + * + * @param intValue int值 + * @return byte数组 + */ + public static byte[] intToBytes(int intValue) { + return intToBytes(intValue, ByteOrder.LITTLE_ENDIAN); + } + + /** + * int转byte数组
+ * 自定义端序 + * + * @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数组
+ * 默认以小端序转换
+ * 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数组
+ * 自定义端序
+ * 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
+ * 默认以小端序转换
+ * 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
+ * 自定义端序
+ * 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数组
+ * 默认以小端序转换
+ * + * @param doubleValue double值 + * @return byte数组 + */ + public static byte[] doubleToBytes(double doubleValue) { + return doubleToBytes(doubleValue, ByteOrder.LITTLE_ENDIAN); + } + + /** + * double转byte数组
+ * 自定义端序
+ * 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
+ * 默认以小端序转换
+ * + * @param bytes byte数组 + * @return long值 + */ + public static double bytesToDouble(byte[] bytes) { + return bytesToDouble(bytes, ByteOrder.LITTLE_ENDIAN); + } + + /** + * byte数组转double
+ * 自定义端序
+ * + * @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); + } + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/ByteUtils.java b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/ByteUtils.java new file mode 100644 index 0000000..43ad0ea --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/ByteUtils.java @@ -0,0 +1,1340 @@ +package org.opentcs.kc.common.byteutils; + +/** + * @Desc: "" + * @Author: caixiang + * @DATE: 2021/12/16 9:08 + */ + + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Stack; +import org.opentcs.kc.common.ParseDataException; + +public class ByteUtils { + public static byte[] copyBytes(byte[] src, int start, int length) { + byte[] dest = new byte[length]; + System.arraycopy(src, start, dest, 0, length); + return dest; + } + /** + * 将 float 类型的数值按照小端模式转换为字节数组 + * @param value 要转换的 float 值 + * @return 转换后的字节数组 + */ + public static byte[] floatToBytes(float value) { + // 先将 float 转换为对应的 32 位整数表示 + int intBits = Float.floatToIntBits(value); + byte[] bytes = new byte[4]; + // 按照小端模式将整数的每一个字节存储到字节数组中 + for (int i = 0; i < 4; i++) { + bytes[i] = (byte) ((intBits >> (i * 8)) & 0xFF); + } + return bytes; + } + /** + * 将字节数组中从指定偏移位置开始的指定长度的字节转换为字符串 + * @param src 字节数组 + * @param offset 起始偏移量 + * @param length 要转换的字节长度 + * @return 转换后的字符串,如果参数不合法则返回空字符串 + */ + public static String bytesToString(byte[] src, int offset, int length) { + // 检查字节数组是否为空 + if (src == null) { + return ""; + } + // 检查偏移量和长度是否合法 + if (offset < 0 || length < 0 || offset + length > src.length) { + return ""; + } + try { + // 创建一个新的字节数组,用于存储从指定偏移位置开始的指定长度的字节 + byte[] subArray = new byte[length]; + // 从原字节数组中复制指定范围的字节到新数组 + System.arraycopy(src, offset, subArray, 0, length); + // 使用 UTF-8 编码将字节数组转换为字符串 + return new String(subArray, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static double bytesToDouble(byte[] src, int offset) { + // 确保字节数组长度足够 + if (src.length - offset < 8) { + throw new IllegalArgumentException("字节数组长度不足"); + } + + // 使用ByteBuffer并设置为小端模式 + ByteBuffer bb = ByteBuffer.wrap(src, offset, 8).order(ByteOrder.LITTLE_ENDIAN); + // 直接获取double值 + return bb.getDouble(); + } + + // 将short转换为小端模式的byte数组 + public static byte[] shortToBytes(short intervalTime) { + byte[] bytes = new byte[2]; + // 取低8位 + bytes[0] = (byte) (intervalTime & 0xFF); + // 取高8位 + bytes[1] = (byte) (intervalTime >> 8); + return bytes; + } +// //2个字节的byte数组 转成short +// public static short bytesToShort(byte[] bytes) { +// if (bytes == null || bytes.length < 2) { +// throw new IllegalArgumentException("字节数组必须包含至少两个字节"); +// } +// short result = 0; +// result |= ((short) bytes[0] & 0xff) << 8; +// result |= (short) bytes[1] & 0xff; +// return result; +// } + + public static short bytesToShort(byte[] bytes,int isBigEndian) { + // 确保字节数组长度至少为 2,因为 short 类型是 2 字节 + if (bytes == null || bytes.length < 2) { + throw new IllegalArgumentException("Input byte array must have at least 2 bytes."); + } + // 先将低字节转换为无符号整数,再左移 0 位(实际上不移动) + int lowByte = bytes[0] & 0xFF; + // 将高字节转换为无符号整数,再左移 8 位 + int highByte = (bytes[1] & 0xFF) << 8; + // 将低字节和高字节进行按位或操作得到最终结果,并转换为 short 类型 + return (short) (lowByte | highByte); + } + + /** + * 将字节数组按照小端模式转换为 short 类型 + * @param src 字节数组 + * @param offset 起始偏移量 + * @return 转换后的 short 值 + */ + public static short bytesToShortLitt(byte[] src, int offset) { + // 检查字节数组长度是否足够,short 类型占 2 个字节 + if (src.length - offset < 2) { + throw new IllegalArgumentException("字节数组长度不足,无法转换为 short 类型"); + } + // 使用 ByteBuffer 包装字节数组,并设置为小端模式 + ByteBuffer bb = ByteBuffer.wrap(src, offset, 2).order(ByteOrder.LITTLE_ENDIAN); + // 从 ByteBuffer 中获取 short 值 + return bb.getShort(); + } + + + public static double bytes2Double(byte[] arr) { + long value = 0; + for (int i = 0; i < 8; i++) { + value |= ((long) (arr[i] & 0xff)) << (8 * i); + } + + return Double.longBitsToDouble(value); + } + +// public static Boolean toBoolean(byte[] bytes){ +// if(bytes.length ==0){ +// return null; +// } +// if(bytes[0] == 1){ +// return true; +// }else { +// return false; +// } +// } + + public static byte[] toByteArrayAndInvert(byte b){ + byte[] arr=new byte[8]; + int len=arr.length; + for(int i=0;i>(len-1-i))&0x1); + } + return invert(arr); + } + public static Boolean toBoolean(byte[] bytes){ + if(bytes.length ==0){ + return null; + } + //只要!=0,那就是大于等于1 ,其实读单个bool变量 ,读取到的byte[] 数组长度都是1. + byte[] byteArrayAndInvert = toByteArrayAndInvert(bytes[0]); + if(byteArrayAndInvert[0] == 1){ + return true; + }else { + return false; + } + } + + public static Boolean toBoolean(byte bytes){ + if(bytes == 1){ + return true; + }else { + return false; + } + } + + public static String addDate(String timeParam, Long day) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 日期格式 + Date date = null; + try { + date = dateFormat.parse(timeParam); // 指定日期 + }catch (Exception e){ + throw new ParseDataException("ByteUtils.addDate error:", e); + } + + long time = date.getTime(); // 得到指定日期的毫秒数 + day = day * 24 * 60 * 60 * 1000; // 要加上的天数转换成毫秒数 + time += day; // 相加得到新的毫秒数 + Date newDate = new Date(time); + return dateFormat.format(newDate); // 将毫秒数转换成日期 + } + + + public static byte[] invert(byte[] a){ + byte[] b = new byte[a.length]; + Stack st = new Stack(); + for(int i=0;i toLrealArray(byte[] bytes) { + List list = new ArrayList<>(); + int i=0; + while (i doubleValue) { + List bytes = new ArrayList<>(); + for(Double b : doubleValue){ + byte[] array = ByteBuffer.allocate(8).putDouble(b).array(); + for(int i=0;i(b.length-2)){ +// return null; +// } +// byte[] content = new byte[b.length-2]; +// for(int i=0;i(b.length-2)){ + return null; + } + List content = new ArrayList<>(); + for(int i=0;i toStrArray(byte[] value) { + int cur = 0; + List res= new ArrayList<>(); + while (cur= 0; i--) { //对于byte的每bit进行判定 + + array[i] = (b & 1) == 1; //判定byte的最后一位是否为1,若为1,则是true;否则是false + + b = (byte) (b >> 1); //将byte右移一位 + + } + if(returns){ + boolean[] array1 = new boolean[8]; + array1[0] = array[7]; + array1[1] = array[6]; + array1[2] = array[5]; + array1[3] = array[4]; + array1[4] = array[3]; + array1[5] = array[2]; + array1[6] = array[1]; + array1[7] = array[0]; + array = array1; + } + + return array; + + } + /** + * desc : 获取字节 最高位的符号位 + * return + * 负数 true + * 正数 false + */ + public static boolean getByteSignbit(byte b,boolean returns) { + return getBooleanArray(b, false)[0]; + } + + + public static Byte fromBooleanArray(boolean[] a) { + byte b = 0; + if (a[7]) + b += 1; + if (a[6]) + b += 2; + if (a[5]) + b += 4; + if (a[4]) + b += 8; + if (a[3]) + b += 16; + if (a[2]) + b += 32; + if (a[1]) + b += 64; + if (a[0]) + b += (byte) 128; + return b; + } + + public static List toBoolArray(byte[] b) { + List res = new ArrayList<>(); + for(int i=0;i toBoolArray(byte[] b,Integer length) { + List res = new ArrayList<>(); + for(int i=0;i toBoolArray(byte[] b) throws UnsupportedEncodingException { +// List res = new ArrayList<>(); +// for(int i=0;i toByteArray(byte[] b) { + List res = new ArrayList<>(); + for(int i=0;i toCharArray(byte[] b) { + List res = new ArrayList<>(); + for(int i=0;i 有符号的整形 + * */ + public static List toWordArray(byte[] b) { + List res = new ArrayList<>(); + int i=0; + while ((i+2)<=b.length){ + res.add( + toInt(b[i],b[i+1]) + ); + i+=2; + } + return res; + } + + //toDWord + /** + * 默认:dword => 有符号的整形 + * */ + public static List toDWordArray(byte[] b) { + List res = new ArrayList<>(); + int i=0; + while ((i+4)<=b.length){ + res.add( + toInt(b[i],b[i+1],b[i+2],b[i+3]) + ); + i+=4; + } + return res; + } + /** + * + * 4个字节byte[] 转成有符号的Double + * 默认大端 + * */ + public static Float realbytesToFloat(byte[] bytes) { + + return Float.intBitsToFloat(toInt(bytes[0],bytes[1],bytes[2],bytes[3])); + } + public static List toRealArray(byte[] bytes) { + List res = new ArrayList<>(); + int i=0; + while (i list) { + List res = new ArrayList<>(); + for(Float f : list){ + byte[] invert = invert(float2byte(f)); + for(int j=0;j> (24 - i * 8)); + } + + // 翻转数组 + int len = b.length; + // 建立一个与源数组元素类型相同的数组 + byte[] dest = new byte[len]; + // 为了防止修改源数组,将源数组拷贝一份副本 + System.arraycopy(b, 0, dest, 0, len); + byte temp; + // 将顺位第i个与倒数第i个交换 + for (int i = 0; i < len / 2; ++i) { + temp = dest[i]; + dest[i] = dest[len - i - 1]; + dest[len - i - 1] = temp; + } + + return dest; + + } + + //toUInt + /** + * USInt 无符号整形 1个字节 =》 Integer + * */ + public static List toUSIntArray(byte[] b) { + List res = new ArrayList<>(); + for(int i=0;i toUIntArray(byte[] b) { + List res = new ArrayList<>(); + int i=0; + while ((i+2)<=b.length){ + res.add( + toUInt(b[i],b[i+1]) + ); + i+=2; + } + return res; + } + /** + * UDInt 无符号整形 4个字节 =》 Long + * */ + public static List toUDIntArray(byte[] b) { + List res = new ArrayList<>(); + int i=0; + while ((i+4)<=b.length){ + res.add( + toUInt(b[i],b[i+1],b[i+2],b[i+3]) + ); + i+=4; + } + return res; + } + + /** + * SInt 无符号整形 1个字节 =》 Integer + * */ + public static List toSIntArray(byte[] b) { + List res = new ArrayList<>(); + for(int i=0;i toIntArray(byte[] b) { + List res = new ArrayList<>(); + int i=0; + while ((i+2)<=b.length){ + res.add( + toInt(b[i],b[i+1]) + ); + i+=2; + } + return res; + } + /** + * DInt 无符号整形 4个字节 =》 Integer + * */ + public static List toDIntArray(byte[] b) { + List res = new ArrayList<>(); + int i=0; + while ((i+4)<=b.length){ + res.add( + toInt(b[i],b[i+1],b[i+2],b[i+3]) + ); + i+=4; + } + return res; + } + + /** + * sint(1个字节) =》 byte[] + * 默认大端模式 + * */ + public static byte[] sintToBytes(Integer i){ + byte[] res = new byte[1]; + res[0] = Byte.valueOf(i.toString()); + return res; + } + /** + * sintArray(1个字节) =》 byte[] + * + * */ + public static byte[] sintArrayToBytes(int[] sintArray) { + byte[] res = new byte[sintArray.length]; + for(int i=0;i> 8) & 0xFF); + // 右移 16 位后取 8 位 + bytes[2] = (byte) ((intervalTime >> 16) & 0xFF); + // 右移 24 位后取 8 位 + bytes[3] = (byte) ((intervalTime >> 24) & 0xFF); + return bytes; + } + + + + /** + * intArray(2个字节) =》 byte[] + * */ + public static byte[] intArrayToBytes(int[] intArray) { + byte[] res = new byte[intArray.length*2]; + for(int i=0 ; i< intArray.length ; i++){ + byte[] bytes = intToBytes(intArray[i]); + res[i*2] = bytes[0]; + res[i*2+1] = bytes[1]; + } + return res; + } + + /** + * int(2个字节) =》 byte[] + * 默认大端模式 + * */ + public static byte[] dintToBytes(Integer i) { + Number intNumber = i; + return ByteUtil.numberToBytes(intNumber,ByteOrder.BIG_ENDIAN); + } + + /** + * intArray(2个字节) =》 byte[] + * */ + public static byte[] dintArrayToBytes(int[] dintArray) { + byte[] res = new byte[dintArray.length*4]; + for(int i=0 ; i< dintArray.length ; i++){ + byte[] bytes = dintToBytes(dintArray[i]); + res[i*4] = bytes[0]; + res[i*4+1] = bytes[1]; + res[i*4+2] = bytes[2]; + res[i*4+3] = bytes[3]; + + } + return res; + } + + /** + * sint(1个字节) =》 byte[] + * 默认大端模式 + * */ + public static byte[] usintToBytes(Integer i){ + if(i<0){ + return null; + } + byte[] res = new byte[1]; + res[0] = Byte.valueOf(i.toString()); + return res; + } + public static byte usintTo1Byte(Integer i){ + if(i<0 || i>=256){ + return 0x00; + } + return Byte.valueOf(i.toString()); + } + + /** + * usintArrayToBytes =》 byte[] + * 默认大端模式 + * */ + public static byte[] usintArrayToBytes(int[] usintArray) { + byte[] res = new byte[usintArray.length]; + for(int i=0 ; i< usintArray.length ; i++){ + byte[] bytes = usintToBytes(usintArray[i]); + if(bytes == null){ + return null; + } + res[i] = bytes[0]; + } + return res; + } + + + /** + * int(2个字节) =》 byte[] + * 默认大端模式 + * */ + public static byte[] uintToBytes(Integer i) { + if(i<0){ + return null; + } + Number shortNumber = Short.valueOf(i.toString()); + return ByteUtil.numberToBytes(shortNumber,ByteOrder.BIG_ENDIAN); + } + + public static byte[] uintToBytes(Integer value, ByteOrder byteOrder) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); + buffer.putInt(value); + return buffer.array(); + }else { + ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(value); + return buffer.array(); + } + } + + + public static byte[] uintArrayToBytes(int[] uintArray) { + byte[] res = new byte[uintArray.length*2]; + for(int i=0 ; i< uintArray.length ; i++){ + byte[] bytes = uintToBytes(uintArray[i]); + if(bytes == null){ + return null; + } + res[i*2] = bytes[0]; + res[i*2+1] = bytes[1]; + } + return res; + } + + /** + * int(2个字节) =》 byte[] + * 默认大端模式 + * */ + public static byte[] udintToBytes(Integer i) { + if(i<0){ + return null; + } + Number intNumber = i; + return ByteUtil.numberToBytes(intNumber,ByteOrder.BIG_ENDIAN); + } + + /** + * long(8个字节) =》 byte[] + * 默认大端模式 + * */ + public static byte[] longToBytes(Long i) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(i); + byte[] array = buffer.array(); + return array; + } + public static byte[] longArrayToBytes(List longs) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES*longs.size()); + for(Long i:longs){ + buffer.putLong(i); + } + byte[] array = buffer.array(); + return array; + } + + public static Long toLong(byte[] input, int offset, boolean littleEndian) { + // 将byte[] 封装为 ByteBuffer + ByteBuffer buffer = ByteBuffer.wrap(input,offset,8); + if(littleEndian){ + // ByteBuffer.order(ByteOrder) 方法指定字节序,即大小端模式(BIG_ENDIAN/LITTLE_ENDIAN) + // ByteBuffer 默认为大端(BIG_ENDIAN)模式 + buffer.order(ByteOrder.LITTLE_ENDIAN); + } + return buffer.getLong(); + } + /** + * byte[] ==> List(8个字节) + * @param source + * @param littleEndian 输入数组是否小端模式 + * @return + */ + public static List toLongArray(byte[] source, boolean littleEndian) { + List res = new ArrayList<>(); + + int fortimes = source.length/8; + for(int i=0;i queue = new LinkedList(); + for(int i=0;i0){ + boolean[] input = new boolean[8]; + + if(boolLength>=8){ + for(int i = 0;i<8;i++){ + input[i]= queue.poll(); + } + + + Byte aByte = fromBooleanArray(reverse(input)); + res[z] = aByte; + z++; + }else { + for(int i=0;i strList) { + List byteList = new ArrayList<>(); + for(String s : strList){ + byte[] bytes = strToBytes(s); + for (int i = 0; i < bytes.length; i++) { + byteList.add(bytes[i]); + } + } + byte[] res = new byte[byteList.size()]; + for(int i=0;istrSize){ + res[1] = strSize.byteValue(); + for(int i=0;i 0){ + a+=1; + } + return intToBytes(a); + } + + public static void main(String[] args) { + long i = 23288; + byte[] bytes = longToBytes(i); + long l = toLong(bytes, 0, false); + List longs = new ArrayList<>(); + longs.add(1l); + longs.add(23288l); + longs.add(-3l); + byte[] bytes1 = longArrayToBytes(longs); + List longs1 = toLongArray(bytes1, false); + System.out.println(Arrays.asList(bytes)); + System.out.println(Arrays.asList(bytes1)); + } + + /** + * 参数 + * desc : 把字符串数组 ==> byte[] [x,x,..,..,..,..,... x,x,..,..,..,..,... ...] + * array : 就是需要被写入plc的内容,, array.length 就是你要写入数组的长度。 注意array.length必须小于plc中设置的 “数组变量” length + * strSize : 就是数组中某个变量的长度 比如 String str = new String[18] 这里的18就是strSize,但是在博图偏移量地址上可以发现它已经把你+2了,就是你设置最大string长度18 但偏移量=18+2=20;; + * 而上面的array.length是 List list = new ArrayList<>; 的list.length + * 返回 + * byte[] 就是整个数组字符串的 总的字节流 + * + * */ + public static byte[] strArrayToBytes(String[] array,Integer strSize) { + //str0 [18, 17, 51, 48, 49, 49, 48, 48, 49, 50, 49, 48, 53, 51, 48, 49, 49, 49, 53] + //str1 [18, 18, 51, 48, 49, 49, 48, 48, 49, 50, 49, 48, 53, 51, 48, 49, 49, 49, 53, 49] + //str2 [18, 18, 51, 48, 49, 49, 48, 48, 49, 50, 49, 48, 53, 51, 48, 49, 49, 49, 53, 50] + + //list [18, 17, 51, 48, 49, 49, 48, 48, 49, 50, 49, 48, 53, 51, 48, 49, 49, 49, 53, 0, 18, 18, 51, 48, 49, 49, 48, 48, 49, 50, 49, 48, 53, 51, 48, 49, 49, 49, 53, 49, 18, 18, 51, 48, 49, 49, 48, 48, 49, 50, 49, 48, 53, 51, 48, 49, 49, 49, 53, 50] + //strSize+2 以后就是 实际plc 变量的字节数了。 + Integer allStrSize = strSize+2; + byte[] res = new byte[array.length*allStrSize]; + for(int i=0;i> 8); + b[1] = (byte) (c & 0xFF); + return b; + } + private static char byteToChar(byte[] b) { + char c = (char) (((b[0] & 0xFF) << 8) | (b[1] & 0xFF)); + return c; + } + private static char byteToChar(byte b) { + char c = (char) (b & 0xFF); + return c; + } + + /** + * 4个字节 ==> float,小端模式 + * */ + public static float bytesToFloat(byte[] src, int offset) { +// int i = ((src[offset] & 0xFF) << 24) | ((src[offset + 1] & 0xFF) << 16) | ((src[offset + 2] & 0xFF) << 8) | (src[offset + 3] & 0xFF); +// return Float.intBitsToFloat(i); + // 使用ByteBuffer,指定字节序为小端 + ByteBuffer buffer = ByteBuffer.wrap(src, offset, 4).order(ByteOrder.LITTLE_ENDIAN); + // 从ByteBuffer中读取float值 + return buffer.getFloat(); + } + + /** + * 4个字节 ==> int,小端模式 + * */ + public static int bytesToInt(byte[] src, int offset) { + //return ((src[offset] & 0xFF) << 24) | ((src[offset + 1] & 0xFF) << 16) | ((src[offset + 2] & 0xFF) << 8) | (src[offset + 3] & 0xFF); + + // 将字节数组包装到 ByteBuffer 中 + ByteBuffer buffer = ByteBuffer.wrap(src, offset, 4); + // 设置字节序为小端模式 + buffer.order(ByteOrder.LITTLE_ENDIAN); + // 从 ByteBuffer 中读取 int 值 + return buffer.getInt(); + } + + public static byte[] copyOfRange(byte[] src, int from, int to) { + if (src == null) { + throw new IllegalArgumentException("src cannot be null"); + } + if (from < 0 || from > src.length) { + throw new IllegalArgumentException("from index is out of range"); + } + if (to < 0 || to > src.length) { + throw new IllegalArgumentException("to index is out of range"); + } + if (from > to) { + throw new IllegalArgumentException("from index cannot be greater than to index"); + } + + int newLength = to - from; + byte[] dest = new byte[newLength]; + System.arraycopy(src, from, dest, 0, newLength); + return dest; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/CommonFunctions.java b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/CommonFunctions.java new file mode 100644 index 0000000..4f41225 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/CommonFunctions.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/StringArrayStruc.java b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/StringArrayStruc.java new file mode 100644 index 0000000..174b2f4 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/byteutils/StringArrayStruc.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/common/enmuc/ModbusFC.java b/opentcs-common/src/main/java/org/opentcs/kc/common/enmuc/ModbusFC.java new file mode 100644 index 0000000..b7a5072 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/common/enmuc/ModbusFC.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/syn/AsyncFuture.java b/opentcs-common/src/main/java/org/opentcs/kc/syn/AsyncFuture.java new file mode 100644 index 0000000..20daa73 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/syn/AsyncFuture.java @@ -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 implements Future { + + 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 队列, + //当有线程执行notify方法的时候,就会往这个队列中取出一个或者多个Thread,取出来以后就能执行后续代码了 +// System.out.println("get"); + + // 当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。 wait()会立刻释放synchronized(obj)中的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; + } + + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/syn/Future.java b/opentcs-common/src/main/java/org/opentcs/kc/syn/Future.java new file mode 100644 index 0000000..88201ef --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/syn/Future.java @@ -0,0 +1,14 @@ +package org.opentcs.kc.syn; + + +/** + * @Author: 蔡翔 + * @Date: 2019/11/6 13:49 + * @Version 1.0 + */ +public interface Future { + //别人调用我的时候,我先给他们返回一个结果, + MQMessage get(Long timeout) throws Exception; + + MQMessage get(Long timeout, String transationId) throws Exception; +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/syn/Main.java b/opentcs-common/src/main/java/org/opentcs/kc/syn/Main.java new file mode 100644 index 0000000..75e8c32 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/syn/Main.java @@ -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 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(); +// } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/syn/SendedList.java b/opentcs-common/src/main/java/org/opentcs/kc/syn/SendedList.java new file mode 100644 index 0000000..2fc8c07 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/syn/SendedList.java @@ -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> list = new HashMap<>(); +// public static synchronized AsyncFuture get(String transitionId){ +// return list.get(transitionId); +// } +// public static synchronized void put(String transitionId,AsyncFuture asyncFuture){ +// list.put(transitionId,asyncFuture); +// } + + public static synchronized AsyncFuture add(String transitionId, Package PackageRequest) { + AsyncFuture 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 mqMessageAsyncFuture = list.get(transitionId); + AsyncFuture mqMessageAsyncFuture = (AsyncFuture)CaffeineUtil.get(transitionId); + if(mqMessageAsyncFuture == null){ + return false; + } + mqMessageAsyncFuture.done(message); + //清除 ,防止这个hashMap过大。 + CaffeineUtil.remove(transitionId); + return true; + } + + + + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogConst.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogConst.java new file mode 100644 index 0000000..9f3ad60 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogConst.java @@ -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)]; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventBroadcaster.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventBroadcaster.java new file mode 100644 index 0000000..3c1039a --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventBroadcaster.java @@ -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(); + } + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventDecoder.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventDecoder.java new file mode 100644 index 0000000..d5a3a52 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventDecoder.java @@ -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 { + + @Override + protected void decode(ChannelHandlerContext ctx, + DatagramPacket datagramPacket, List 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); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventEncoder.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventEncoder.java new file mode 100644 index 0000000..99b2319 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventEncoder.java @@ -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 { + private final InetSocketAddress remoteAddress; + + //LogEventEncoder 创建了即将被发送到指定的 InetSocketAddress + // 的 DatagramPacket 消息 + public LogEventEncoder(InetSocketAddress remoteAddress) { + this.remoteAddress = remoteAddress; + } + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, + LogMsg logMsg, List 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)); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventHandler.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventHandler.java new file mode 100644 index 0000000..399e2ed --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventHandler.java @@ -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 { + + @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()); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventMonitor.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventMonitor.java new file mode 100644 index 0000000..144ba5e --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogEventMonitor.java @@ -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() { + @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(); + } + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogMsg.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogMsg.java new file mode 100644 index 0000000..0b8e0ec --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/GB/LogMsg.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/KCCommandDemo.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/KCCommandDemo.java new file mode 100644 index 0000000..064b326 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/KCCommandDemo.java @@ -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 readValueMemberList = new ArrayList<>(); + ReadValueMember readValueMember1 = new ReadValueMember(Short.valueOf("0"),Short.valueOf("1")); + readValueMemberList.add(readValueMember1); + + List 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 valueMemberList = new ArrayList<>(); + WriteValueMember valueMember1 = new WriteValueMember(Short.valueOf("0"),Short.valueOf("4"), ByteUtils.uintToBytes(3, ByteOrder.LITTLE_ENDIAN)); + valueMemberList.add(valueMember1); + + List 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 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; + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/codec/AgvUdpChannelInitializer.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/codec/AgvUdpChannelInitializer.java new file mode 100644 index 0000000..1804759 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/codec/AgvUdpChannelInitializer.java @@ -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 { + 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 + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/codec/AgvUdpDecode.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/codec/AgvUdpDecode.java new file mode 100644 index 0000000..1afa3aa --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/codec/AgvUdpDecode.java @@ -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 { + + 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(); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEvent.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEvent.java new file mode 100644 index 0000000..ae20cf1 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEvent.java @@ -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 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 ""; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEventConstant.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEventConstant.java new file mode 100644 index 0000000..afd9d75 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEventConstant.java @@ -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; + //命令码 结束 + + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEventHeader.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEventHeader.java new file mode 100644 index 0000000..8f920c2 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/AgvEventHeader.java @@ -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 getHeaderBytes(){ + List 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; + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/IAgvEvent.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/IAgvEvent.java new file mode 100644 index 0000000..10e2836 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/IAgvEvent.java @@ -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(); +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/AbnormalEventStatusInfo.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/AbnormalEventStatusInfo.java new file mode 100644 index 0000000..4e8d02e --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/AbnormalEventStatusInfo.java @@ -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); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/ActionInfo.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/ActionInfo.java new file mode 100644 index 0000000..70698b0 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/ActionInfo.java @@ -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); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/BatteryStatusInfo.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/BatteryStatusInfo.java new file mode 100644 index 0000000..385fdc1 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/BatteryStatusInfo.java @@ -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]; + } + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/LocationStatusInfo.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/LocationStatusInfo.java new file mode 100644 index 0000000..a15f27e --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/LocationStatusInfo.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/PathStateSequence.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/PathStateSequence.java new file mode 100644 index 0000000..89ab406 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/PathStateSequence.java @@ -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); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/PointStateSequence.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/PointStateSequence.java new file mode 100644 index 0000000..8d1c3e8 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/PointStateSequence.java @@ -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); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/QueryRobotStatusRsp.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/QueryRobotStatusRsp.java new file mode 100644 index 0000000..1e6de1b --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/af/QueryRobotStatusRsp.java @@ -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 abnormalEventStatusInfoList; + public List 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;i0){ + for(int i=0;i 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)); + } + } + } +} + diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b0/QueryCargoStatusRsp.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b0/QueryCargoStatusRsp.java new file mode 100644 index 0000000..44d463a --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b0/QueryCargoStatusRsp.java @@ -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); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeInfo.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeInfo.java new file mode 100644 index 0000000..29c9b23 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeInfo.java @@ -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]; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeParam.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeParam.java new file mode 100644 index 0000000..02a7b21 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeParam.java @@ -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 subscribeInfoList; + //uuid,64个字节 + private byte[] uuid; + public SubscribeParam(List 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 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; + } + + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeRsp.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeRsp.java new file mode 100644 index 0000000..6b808ec --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/b1/SubscribeRsp.java @@ -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; + } + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Action.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Action.java new file mode 100644 index 0000000..c628dcc --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Action.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/ActionSet.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/ActionSet.java new file mode 100644 index 0000000..0aad093 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/ActionSet.java @@ -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(); + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/NavigationParam.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/NavigationParam.java new file mode 100644 index 0000000..b2532a0 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/NavigationParam.java @@ -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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Path.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Path.java new file mode 100644 index 0000000..2b60b5e --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Path.java @@ -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 开始奇数递增(如 1、3、5......)(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; + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Point.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Point.java new file mode 100644 index 0000000..74a945a --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/navigation/Point.java @@ -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 开始偶数递增(如 0、2、4、6......),以区分同一个点 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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadParam.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadParam.java new file mode 100644 index 0000000..ed7ff78 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadParam.java @@ -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 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 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 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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadRsp.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadRsp.java new file mode 100644 index 0000000..00c6034 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadRsp.java @@ -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; + } + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadStrValue.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadStrValue.java new file mode 100644 index 0000000..a24ec96 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadStrValue.java @@ -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 memberList) { + this.varName = ByteUtil.stringTo16Byte(varName); + this.memberVarNum = ByteUtil.intToBytes(memberVarNum); + List 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 toBytes() { + List 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; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadValueMember.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadValueMember.java new file mode 100644 index 0000000..6921279 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/read/ReadValueMember.java @@ -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 toBytes() { + List bytes = new ArrayList<>(); + for (byte b : offsetValue) { + bytes.add(b); + } + for (byte b : memberVarLength) { + bytes.add(b); + } + return bytes; + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteParam.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteParam.java new file mode 100644 index 0000000..49e2873 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteParam.java @@ -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 values; + public WriteParam(Integer valueNum, List 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; + } + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteStrValue.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteStrValue.java new file mode 100644 index 0000000..2c443fc --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteStrValue.java @@ -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 members; + + public WriteStrValue(String valueName, Integer valueNum, List 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; + } + + +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteValueMember.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteValueMember.java new file mode 100644 index 0000000..b38e39f --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/write/WriteValueMember.java @@ -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(); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/x14/RobotSetPosition.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/x14/RobotSetPosition.java new file mode 100644 index 0000000..dc0ce35 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/x14/RobotSetPosition.java @@ -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(); + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/x17/QueryRobotRunStatusRsp.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/x17/QueryRobotRunStatusRsp.java new file mode 100644 index 0000000..364504a --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/function/x17/QueryRobotRunStatusRsp.java @@ -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) + + '}'; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/rsp/RcvEventPackage.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/rsp/RcvEventPackage.java new file mode 100644 index 0000000..38d2438 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/agv/param/rsp/RcvEventPackage.java @@ -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 + '\'' + + '}'; + } +} diff --git a/opentcs-common/src/main/java/org/opentcs/kc/udp/io/UDPClient.java b/opentcs-common/src/main/java/org/opentcs/kc/udp/io/UDPClient.java new file mode 100644 index 0000000..a800313 --- /dev/null +++ b/opentcs-common/src/main/java/org/opentcs/kc/udp/io/UDPClient.java @@ -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 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() { + @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()); + } + } + + +} diff --git a/opentcs-common/src/main/resources/logback.xml b/opentcs-common/src/main/resources/logback.xml new file mode 100644 index 0000000..1546995 --- /dev/null +++ b/opentcs-common/src/main/resources/logback.xml @@ -0,0 +1,51 @@ + + + + + + + logback-spring + + + + + + + + + + + + ${logging.agvlog}/today/log-info.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${logging.agvlog}/agv-log-info-%d{yyyy-MM-dd}.%i.log + + 100MB + 999 + 200GB + + + + info + ACCEPT + DENY + + + + + + + + + + + + +