diff --git a/README.md b/README.md
index d63686b..528e230 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,10 @@
------------------------------
#### 当前支持的充电桩协议
-| 协议名 | 版本号 |
-|---|------------|
-| 云快充 | 1.5.0、1.6.0 |
+| 协议名 | 版本号 |
+|-----|-------------|
+| 云快充 | 1.5.0、1.6.0 |
+| 绿能 | 3.4 |
------------------------------
#### 充电桩协议文档
diff --git a/jcpp-app-bootstrap/pom.xml b/jcpp-app-bootstrap/pom.xml
index 52e9b2b..23150f1 100644
--- a/jcpp-app-bootstrap/pom.xml
+++ b/jcpp-app-bootstrap/pom.xml
@@ -37,6 +37,10 @@
sanbing
jcpp-protocol-yunkuaichong
+
+ sanbing
+ jcpp-protocol-lvneng
+
org.testcontainers
junit-jupiter
diff --git a/jcpp-app-bootstrap/src/main/resources/app-service.yml b/jcpp-app-bootstrap/src/main/resources/app-service.yml
index 72a6265..6db7cbd 100644
--- a/jcpp-app-bootstrap/src/main/resources/app-service.yml
+++ b/jcpp-app-bootstrap/src/main/resources/app-service.yml
@@ -228,7 +228,7 @@ service:
buffer-memory: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_BUFFER_MEMORY:33554432}"
other-properties: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
yunkuaichongV160:
- enabled: "${PROTOCOLS_YUNKUAICHONGV150_ENABLED:true}"
+ enabled: "${PROTOCOLS_YUNKUAICHONGV160_ENABLED:true}"
listener:
tcp:
bind-address: "${PROTOCOLS_YUNKUAICHONGV160_LISTENER_TCP_BIND_ADDRESS:0.0.0.0}"
@@ -266,4 +266,42 @@ service:
linger-ms: "${PROTOCOLS_YUNKUAICHONGV160_FORWARD_KAFKA_LINGER_MS:0}"
buffer-memory: "${PROTOCOLS_YUNKUAICHONGV160_FORWARD_BUFFER_MEMORY:33554432}"
other-properties: "${PROTOCOLS_YUNKUAICHONGV160_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
-
+ lvnengV340:
+ enabled: "${PROTOCOLS_LVNENG340_ENABLED:true}"
+ listener:
+ tcp:
+ bind-address: "${PROTOCOLS_LVNENG340_LISTENER_TCP_BIND_ADDRESS:0.0.0.0}"
+ bind-port: "${PROTOCOLS_LVNENG340_LISTENER_TCP_BIND_PORT:38011}"
+ boss-group-thread_count: "${PROTOCOLS_LVNENG340_LISTENER_TCP_BOSS_GROUP_THREADS:4}"
+ worker-group-thread-count: "${PROTOCOLS_LVNENG340_LISTENER_TCP_WORKER_GROUP_THREADS:16}"
+ so-keep-alive: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_KEEPALIVE:true}"
+ so-backlog: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_BACKLOG:128}"
+ so-rcvbuf: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_RCVBUF:65536}"
+ so-sndbuf: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_SNDBUF:65536}"
+ nodelay: "${PROTOCOLS_LVNENG340_LISTENER_TCP_NODELAY:true}"
+ handler:
+ idle-timeout-seconds: "${PROTOCOLS_LVNENG340_LISTENER_TCP_HANDLER_IDLE_TIMEOUT_SECONDS:600}"
+ max_connections: "${PROTOCOLS_LVNENG340_LISTENER_TCP_HANDLER_MAX_CONNECTIONS:100000}"
+ # 默认为二进制类型的拆包器
+ # 可选JSON类型的拆包器 "${PROTOCOLS_LVNENG340_NETTY_HANDLER_BINARY_CONFIGURATION:type:JSON}"
+ # 可选纯文本类型的拆包器 "${PROTOCOLS_LVNENG340_NETTY_HANDLER_BINARY_CONFIGURATION:type:TEXT;maxFrameLength:128;stripDelimiter:true;messageSeparator:null;charsetName:UTF-8}"
+ configuration: "${PROTOCOLS_LVNENG340_NETTY_HANDLER_BINARY_CONFIGURATION:type:BINARY;decoder:sanbing.jcpp.protocol.listener.tcp.decoder.JCPPLengthFieldBasedFrameDecoder;byteOrder:LITTLE_ENDIAN;head:AAF5;lengthFieldOffset:2;lengthFieldLength:2;lengthAdjustment:-4;initialBytesToStrip:0}"
+ forwarder:
+ # 如果是单体服务,可选kafka、memory,未来计划扩展RocketMQ, GRpc、REST
+ type: "${PROTOCOLS_LVNENG340_FORWARD_TYPE:memory}"
+ memory:
+ topic: "${PROTOCOLS_LVNENG340_FORWARD_MEMORY_TOPIC:protocol_uplink}"
+ kafka:
+ topic: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_TOPIC:protocol_uplink}"
+ jcpp-partition: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_JCPP_PARTITION:true}" # 是否利用JCPP的分片框架
+ # 以下配置只有在service.type为protocol时且jcpp-partition为false时才生效
+ bootstrap-servers: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_SERVERS:kafka:9092}"
+ acks: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_ACKS:1}"
+ # 可选 protobuf(推荐)、json
+ encoder: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_ENCODER:protobuf}"
+ retries: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_RETRIES:1}"
+ compression-type: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_COMPRESSION_TYPE:none}" # none, gzip, snappy, lz4, zstd
+ batch-size: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_BATCH_SIZE:16384}"
+ linger-ms: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_LINGER_MS:0}"
+ buffer-memory: "${PROTOCOLS_LVNENG340_FORWARD_BUFFER_MEMORY:33554432}"
+ other-properties: "${PROTOCOLS_LVNENG340_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java
index 52acd9e..1d35c3f 100644
--- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java
+++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java
@@ -25,8 +25,8 @@ public class DownlinkRestTemplateConfiguration {
@Bean("downlinkRestTemplate")
public RestTemplate downlinkRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder()
- .setConnectTimeout(Duration.of(3, ChronoUnit.SECONDS))
- .setReadTimeout(Duration.of(3, ChronoUnit.SECONDS))
+ .connectTimeout(Duration.of(3, ChronoUnit.SECONDS))
+ .readTimeout(Duration.of(3, ChronoUnit.SECONDS))
.build();
restTemplate.setMessageConverters(Collections.singletonList(new ProtobufHttpMessageConverter()));
return restTemplate;
diff --git a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPRedisCacheConfiguration.java b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPRedisCacheConfiguration.java
index 4a14bf7..0c13395 100644
--- a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPRedisCacheConfiguration.java
+++ b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPRedisCacheConfiguration.java
@@ -6,7 +6,7 @@
*/
package sanbing.jcpp.infrastructure.cache;
-import io.lettuce.core.api.StatefulRedisConnection;
+import io.lettuce.core.api.StatefulConnection;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
@@ -135,8 +135,8 @@ public abstract class JCPPRedisCacheConfiguration {
registry.addConverter(UUID.class, String.class, UUID::toString);
}
- protected GenericObjectPoolConfig> buildPoolConfig() {
- GenericObjectPoolConfig> poolConfig = new GenericObjectPoolConfig<>();
+ protected GenericObjectPoolConfig> buildPoolConfig() {
+ GenericObjectPoolConfig> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMinIdle(minIdle);
diff --git a/jcpp-infrastructure-queue/pom.xml b/jcpp-infrastructure-queue/pom.xml
index 2d33b24..928f2d3 100644
--- a/jcpp-infrastructure-queue/pom.xml
+++ b/jcpp-infrastructure-queue/pom.xml
@@ -48,6 +48,10 @@
org.apache.curator
curator-recipes
+
+ org.apache.zookeeper
+ zookeeper
+
diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java
index 8176062..2dc602b 100644
--- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java
+++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java
@@ -59,7 +59,7 @@ public class DefaultServiceInfoProvider implements ServiceInfoProvider {
try {
this.serviceId = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
- this.serviceId = RandomStringUtils.randomAlphabetic(10);
+ this.serviceId = RandomStringUtils.secure().nextAlphabetic(10);
}
}
log.info("Current Service ID: {}", serviceId);
diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java
index 6d29e31..834e4be 100644
--- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java
+++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java
@@ -6,6 +6,8 @@
*/
package sanbing.jcpp.infrastructure.util.codec;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
public class BCDUtil {
private static final String HEX = "0123456789ABCDEF";
@@ -171,4 +173,124 @@ public class BCDUtil {
return sb.toString().toUpperCase();
}
+ private static final int HOUR_24 = 0x24;
+ private static final int ZERO_TIME = 0x00;
+ private static final int BCD_DATE_LENGTH = 8;
+ private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+ private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+
+ /**
+ * BCD编码的时间转换为LocalDateTime对象
+ * 格式: YYYYMMDDHHmmss (8字节BCD)
+ * 示例: 20240315235959
+ *
+ * @param bcdBytes BCD编码的时间字节数组,必须是8字节
+ * @return 转换后的LocalDateTime对象,如果输入无效则返回null
+ * @throws IllegalArgumentException 如果输入字节数组长度不是8
+ */
+ public static LocalDateTime bcdToDate(byte[] bcdBytes) {
+ if (bcdBytes == null || bcdBytes.length != BCD_DATE_LENGTH) {
+ throw new IllegalArgumentException("BCD date bytes must be 8 bytes long");
+ }
+
+ // 检查是否全为0
+ boolean allZero = true;
+ for (int i = 0; i < 7; i++) {
+ if (bcdBytes[i] != ZERO_TIME) {
+ allZero = false;
+ break;
+ }
+ }
+ if (allZero) {
+ return null;
+ }
+
+ // 使用StringBuilder预分配容量,避免扩容
+ StringBuilder timeStr = new StringBuilder(14);
+
+ // 年月日
+ for (int i = 0; i < 4; i++) {
+ appendBcdByte(timeStr, bcdBytes[i]);
+ }
+
+ // 小时特殊处理
+ byte hour = bcdBytes[4];
+ if ((hour & 0xff) == HOUR_24) {
+ timeStr.append("00");
+ } else {
+ appendBcdByte(timeStr, hour);
+ }
+
+ // 分秒
+ appendBcdByte(timeStr, bcdBytes[5]);
+ appendBcdByte(timeStr, bcdBytes[6]);
+
+ try {
+ return LocalDateTime.parse(timeStr.toString(), DATETIME_FORMATTER);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 将单个BCD字节追加到StringBuilder
+ * 性能优化: 直接计算BCD值并追加,避免字符串转换
+ */
+ private static void appendBcdByte(StringBuilder sb, byte bcd) {
+ // 高4位
+ sb.append(DIGITS[(bcd >> 4) & 0x0F]);
+ // 低4位
+ sb.append(DIGITS[bcd & 0x0F]);
+ }
+ /**
+ * LocalDateTime转换为8字节BCD编码
+ * 格式: YYYYMMDDHHmmss + 0xFF (8字节BCD)
+ * 示例: 20240315235959FF
+ *
+ * @param dateTime 要转换的LocalDateTime对象
+ * @return 8字节BCD编码的字节数组,最后一个字节固定为0xFF;如果输入为null则返回null
+ */
+ public static byte[] dateToBcd8(LocalDateTime dateTime) {
+ if (dateTime == null) {
+ return null;
+ }
+
+ byte[] bytes = new byte[8];
+
+ // 年 (2字节)
+ int year = dateTime.getYear();
+ bytes[0] = (byte) ((year / 100) << 4 | (year / 10 % 10));
+ bytes[1] = (byte) ((year % 10) << 4);
+
+ // 月 (与年的最后4位共用一个字节)
+ int month = dateTime.getMonthValue();
+ bytes[1] |= (byte) (month / 10);
+ bytes[2] = (byte) ((month % 10) << 4);
+
+ // 日 (与月的最后4位共用一个字节)
+ int day = dateTime.getDayOfMonth();
+ bytes[2] |= (byte) (day / 10);
+ bytes[3] = (byte) ((day % 10) << 4);
+
+ // 时 (与日的最后4位共用一个字节)
+ int hour = dateTime.getHour();
+ bytes[3] |= (byte) (hour / 10);
+ bytes[4] = (byte) ((hour % 10) << 4);
+
+ // 分 (与时的最后4位共用一个字节)
+ int minute = dateTime.getMinute();
+ bytes[4] |= (byte) (minute / 10);
+ bytes[5] = (byte) ((minute % 10) << 4);
+
+ // 秒 (与分的最后4位共用一个字节)
+ int second = dateTime.getSecond();
+ bytes[5] |= (byte) (second / 10);
+ bytes[6] = (byte) ((second % 10) << 4);
+
+ // 最后一个字节固定为0xFF
+ bytes[7] = (byte) 0xFF;
+
+ return bytes;
+ }
+
}
diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java
index b0add1f..b16029c 100644
--- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java
+++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java
@@ -71,8 +71,8 @@ public class ByteUtil {
/**
* ByteBuf转byte数组
*
- * @param byteBuf
- * @return
+ * @param byteBuf ByteBuf对象
+ * @return 转换后的字节数组
*/
public static byte[] toBytes(ByteBuf byteBuf) {
int msgLength = byteBuf.readableBytes();
@@ -80,4 +80,48 @@ public class ByteUtil {
byteBuf.readBytes(bytes);
return bytes;
}
+
+ /**
+ * 计算字节数组的累加和,如果累加结果超过1字节,则只取低8位
+ *
+ * 示例:
+ * byte[] data = {0x01, 0x02, 0x03};
+ * byte sum = calculateSum(data); // sum = 0x06
+ *
+ * byte[] data2 = {(byte)0xFF, (byte)0xFF};
+ * byte sum2 = calculateSum(data2); // sum2 = (byte)0xFE (254 + 255 = 509, 取低8位为254)
+ *
+ * @param data 要计算累加和的字节数组
+ * @return 累加和的低8位
+ * @throws IllegalArgumentException 如果输入数组为null
+ */
+ public static byte calculateSum(byte[] data) {
+ if (data == null) {
+ throw new IllegalArgumentException("输入数组不能为null");
+ }
+
+ int sum = 0;
+ for (byte b : data) {
+ sum += b & 0xFF;
+ }
+
+ return (byte) (sum & 0xFF);
+ }
+
+ /**
+ * 验证数据的累加和是否与期望值相等
+ *
+ * 示例:
+ * byte[] data = {0x01, 0x02, 0x03};
+ * boolean valid = verifySum(data, (byte)0x06); // valid = true
+ *
+ * @param data 要验证的数据
+ * @param expectedSum 期望的累加和
+ * @return 包含验证结果和实际计算出的累加和的键值对
+ * @throws IllegalArgumentException 如果输入数组为null
+ */
+ public static JCPPPair verifySum(byte[] data, byte expectedSum) {
+ byte actualSum = calculateSum(data);
+ return JCPPPair.of(actualSum == expectedSum, actualSum);
+ }
}
\ No newline at end of file
diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java
index f32c8ef..9e6833b 100644
--- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java
+++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java
@@ -190,28 +190,4 @@ public class JacksonUtil {
return node;
}
- /**
- * 合并两个ObjectNode.
- * 如果存在相同的字段,优先保留第二个ObjectNode中的值。
- *
- * @param node1 the first ObjectNode
- * @param node2 the second ObjectNode
- * @return 合并后的结果
- */
- public static ObjectNode merge(ObjectNode node1, ObjectNode node2) {
- ObjectNode mergedNode = OBJECT_MAPPER.createObjectNode();
-
- // 把第一个节点的所有字段添加到mergedNode中
- node1.fields().forEachRemaining(entry -> {
- mergedNode.set(entry.getKey(), entry.getValue());
- });
-
- // 把第二个节点的所有字段添加到mergedNode中,覆盖相同字段
- node2.fields().forEachRemaining(entry -> {
- mergedNode.set(entry.getKey(), entry.getValue());
- });
-
- return mergedNode;
- }
-
}
diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpSession.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpSession.java
index 727bbf5..24faa70 100644
--- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpSession.java
+++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpSession.java
@@ -30,6 +30,8 @@ import java.util.function.Consumer;
@Setter
public class TcpSession extends ProtocolSession {
+ public static final String SCHEDULE_KEY_AUTO_SYNC_TIME = "auto-sync-time";
+
private SocketAddress address;
private ChannelHandlerContext ctx;
diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/decoder/JCPPLengthFieldBasedFrameDecoder.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/decoder/JCPPLengthFieldBasedFrameDecoder.java
index 3bdb8a6..acba4f6 100644
--- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/decoder/JCPPLengthFieldBasedFrameDecoder.java
+++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/decoder/JCPPLengthFieldBasedFrameDecoder.java
@@ -82,7 +82,7 @@ public class JCPPLengthFieldBasedFrameDecoder extends ByteToMessageDecoder {
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) {
if (log.isDebugEnabled()) {
String hexDump = ByteBufUtil.hexDump(in);
- log.debug("{} 开始解析16进制报文:{}", ctx.channel(), hexDump);
+ log.debug("{} 开始拆解16进制报文:{}", ctx.channel(), hexDump);
}
// 帧长
long frameLength = 0;
diff --git a/jcpp-protocol-bootstrap/pom.xml b/jcpp-protocol-bootstrap/pom.xml
index 1d72e50..696e97f 100644
--- a/jcpp-protocol-bootstrap/pom.xml
+++ b/jcpp-protocol-bootstrap/pom.xml
@@ -33,6 +33,10 @@
sanbing
jcpp-protocol-yunkuaichong
+
+ sanbing
+ jcpp-protocol-lvneng
+
diff --git a/jcpp-protocol-bootstrap/src/main/resources/protocol-service.yml b/jcpp-protocol-bootstrap/src/main/resources/protocol-service.yml
index 68c8b49..f3f451e 100644
--- a/jcpp-protocol-bootstrap/src/main/resources/protocol-service.yml
+++ b/jcpp-protocol-bootstrap/src/main/resources/protocol-service.yml
@@ -134,6 +134,45 @@ service:
linger-ms: "${PROTOCOLS_YUNKUAICHONGV160_FORWARD_KAFKA_LINGER_MS:0}"
buffer-memory: "${PROTOCOLS_YUNKUAICHONGV160_FORWARD_BUFFER_MEMORY:33554432}"
other-properties: "${PROTOCOLS_YUNKUAICHONGV160_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
+ lvnengV340:
+ enabled: "${PROTOCOLS_LVNENG340_ENABLED:true}"
+ listener:
+ tcp:
+ bind-address: "${PROTOCOLS_LVNENG340_LISTENER_TCP_BIND_ADDRESS:0.0.0.0}"
+ bind-port: "${PROTOCOLS_LVNENG340_LISTENER_TCP_BIND_PORT:38011}"
+ boss-group-thread_count: "${PROTOCOLS_LVNENG340_LISTENER_TCP_BOSS_GROUP_THREADS:4}"
+ worker-group-thread-count: "${PROTOCOLS_LVNENG340_LISTENER_TCP_WORKER_GROUP_THREADS:16}"
+ so-keep-alive: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_KEEPALIVE:true}"
+ so-backlog: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_BACKLOG:128}"
+ so-rcvbuf: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_RCVBUF:65536}"
+ so-sndbuf: "${PROTOCOLS_LVNENG340_LISTENER_TCP_SO_SNDBUF:65536}"
+ nodelay: "${PROTOCOLS_LVNENG340_LISTENER_TCP_NODELAY:true}"
+ handler:
+ idle-timeout-seconds: "${PROTOCOLS_LVNENG340_LISTENER_TCP_HANDLER_IDLE_TIMEOUT_SECONDS:600}"
+ max_connections: "${PROTOCOLS_LVNENG340_LISTENER_TCP_HANDLER_MAX_CONNECTIONS:100000}"
+ # 默认为二进制类型的拆包器
+ # 可选JSON类型的拆包器 "${PROTOCOLS_LVNENG340_NETTY_HANDLER_BINARY_CONFIGURATION:type:JSON}"
+ # 可选纯文本类型的拆包器 "${PROTOCOLS_LVNENG340_NETTY_HANDLER_BINARY_CONFIGURATION:type:TEXT;maxFrameLength:128;stripDelimiter:true;messageSeparator:null;charsetName:UTF-8}"
+ configuration: "${PROTOCOLS_LVNENG340_NETTY_HANDLER_BINARY_CONFIGURATION:type:BINARY;decoder:sanbing.jcpp.protocol.listener.tcp.decoder.JCPPLengthFieldBasedFrameDecoder;byteOrder:LITTLE_ENDIAN;head:AAF5;lengthFieldOffset:2;lengthFieldLength:2;lengthAdjustment:-4;initialBytesToStrip:0}"
+ forwarder:
+ # 如果是单体服务,可选kafka、memory,未来计划扩展RocketMQ, GRpc、REST
+ type: "${PROTOCOLS_LVNENG340_FORWARD_TYPE:memory}"
+ memory:
+ topic: "${PROTOCOLS_LVNENG340_FORWARD_MEMORY_TOPIC:protocol_uplink}"
+ kafka:
+ topic: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_TOPIC:protocol_uplink}"
+ jcpp-partition: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_JCPP_PARTITION:true}" # 是否利用JCPP的分片框架
+ # 以下配置只有在service.type为protocol时且jcpp-partition为false时才生效
+ bootstrap-servers: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_SERVERS:kafka:9092}"
+ acks: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_ACKS:1}"
+ # 可选 protobuf(推荐)、json
+ encoder: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_ENCODER:protobuf}"
+ retries: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_RETRIES:1}"
+ compression-type: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_COMPRESSION_TYPE:none}" # none, gzip, snappy, lz4, zstd
+ batch-size: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_BATCH_SIZE:16384}"
+ linger-ms: "${PROTOCOLS_LVNENG340_FORWARD_KAFKA_LINGER_MS:0}"
+ buffer-memory: "${PROTOCOLS_LVNENG340_FORWARD_BUFFER_MEMORY:33554432}"
+ other-properties: "${PROTOCOLS_LVNENG340_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
# 应用程序服务注册中心配置
zk:
diff --git a/jcpp-protocol-lvneng/READMD.md b/jcpp-protocol-lvneng/READMD.md
new file mode 100644
index 0000000..54b4bf1
--- /dev/null
+++ b/jcpp-protocol-lvneng/READMD.md
@@ -0,0 +1,20 @@
+### 绿能直流3.4模拟报文
+
+---
+
+> 示例统一桩编号:20231212000010 (HEX:20231212000010)
+> 示例统一枪编号:01
+
+#### 106 上行登录
+`AAF56D0010016A000000000032303233313231323030303031300000000000000000000000000000000000000000010100000000000000000000000200000000000020250808120101FF00000000000000000000000000000000000000000000000001E24000220003E80000B4`
+#### 0x02 下行登录应答
+`AAF51200100169000000000001E24000008C`
+#### 0x56 下行登录后对时
+`AAF530001001030032303233313231323030303031300000000000000000000000000000000000004F353512090819A6`
+
+---
+
+#### 109 充电桩状态信息包上报
+`AAF5D00010C46D00000000003230323331323132303030303130000000000000000000000000000000000000010101000000000000000000000020250808105527FF00000000000000000000000000000000000000000000000000000000000000F567720000000000F5677200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003232460000000000000000000000000000000000000044`
+
+---
diff --git a/jcpp-protocol-lvneng/pom.xml b/jcpp-protocol-lvneng/pom.xml
new file mode 100644
index 0000000..88b41e1
--- /dev/null
+++ b/jcpp-protocol-lvneng/pom.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ sanbing
+ jcpp-parent
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ jcpp-protocol-lvneng
+ jar
+ JChargePointProtocol LvNeng Protocol Module
+ 绿能全版本协议模块
+
+
+ ${basedir}/..
+
+
+
+
+ sanbing
+ jcpp-protocol-api
+
+
+
+
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/AbstractLvnengCmdExe.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/AbstractLvnengCmdExe.java
new file mode 100644
index 0000000..a8d8939
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/AbstractLvnengCmdExe.java
@@ -0,0 +1,85 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import sanbing.jcpp.infrastructure.util.codec.ByteUtil;
+import sanbing.jcpp.protocol.listener.tcp.TcpSession;
+import sanbing.jcpp.protocol.listener.tcp.enums.SequenceNumberLength;
+import sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum;
+
+/**
+ * 绿能协议基础类
+ */
+public class AbstractLvnengCmdExe {
+
+ protected static final int LVNENG_HEAD = 0xAAF5;
+ protected static final int LVNENG_ENCRYPTION_FLAG = 0x10;
+
+ /**
+ * 编码下行消息
+ * 格式:帧头(2) + 长度(2) + 加密标识(1) + 序号(1) + 命令字(2) + 数据域(n) + 校验和(1)
+ */
+ protected byte[] encode(LvnengDownlinkCmdEnum downlinkCmd,
+ int seqNo,
+ int encryptionFlag,
+ ByteBuf msgBody) {
+ // 1. 计算长度
+ int msgBodyLength = msgBody.readableBytes();
+ int totalLength = msgBodyLength + 9; // 总长度 = 数据域长度 + 9字节固定头尾
+
+ // 2. 构建消息头和数据域
+ ByteBuf response = Unpooled.buffer(totalLength);
+ response.writeShort(LVNENG_HEAD); // 帧头
+ response.writeShortLE(totalLength); // 长度
+ response.writeByte(encryptionFlag); // 加密标识
+ response.writeByte(seqNo); // 序号
+ response.writeShortLE(downlinkCmd.getCmd()); // 命令字
+ response.writeBytes(msgBody); // 数据域
+
+ // 3. 准备校验和计算的数据(命令字 + 数据域)
+ byte[] sumData = new byte[2 + msgBodyLength]; // 2字节命令字 + 数据域
+ sumData[0] = (byte) (downlinkCmd.getCmd() & 0xFF);
+ sumData[1] = (byte) ((downlinkCmd.getCmd() >> 8) & 0xFF);
+ if (msgBodyLength > 0) {
+ System.arraycopy(response.array(), 8, sumData, 2, msgBodyLength);
+ }
+
+ // 4. 计算并写入校验和
+ response.writeByte(ByteUtil.calculateSum(sumData));
+
+ // 5. 转换为字节数组
+ return ByteUtil.toBytes(response);
+ }
+
+ /**
+ * 编码并发送消息(完整参数版本)
+ */
+ protected void encodeAndWriteFlush(LvnengDownlinkCmdEnum downlinkCmd,
+ int seqNo,
+ int encryptionFlag,
+ ByteBuf msgBody,
+ TcpSession tcpSession) {
+ byte[] encode = encode(downlinkCmd, seqNo, encryptionFlag, msgBody);
+ tcpSession.writeAndFlush(Unpooled.copiedBuffer(encode));
+ }
+
+ /**
+ * 编码并发送消息(简化参数版本)
+ * 使用默认的加密标识和自增序号
+ */
+ protected void encodeAndWriteFlush(LvnengDownlinkCmdEnum downlinkCmd,
+ ByteBuf msgBody,
+ TcpSession tcpSession) {
+ byte[] encode = encode(downlinkCmd,
+ tcpSession.nextSeqNo(SequenceNumberLength.SHORT),
+ LVNENG_ENCRYPTION_FLAG,
+ msgBody);
+ tcpSession.writeAndFlush(Unpooled.copiedBuffer(encode));
+ }
+}
\ No newline at end of file
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java
new file mode 100644
index 0000000..a02c3ba
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java
@@ -0,0 +1,19 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng;
+
+import sanbing.jcpp.protocol.ProtocolContext;
+import sanbing.jcpp.protocol.listener.tcp.TcpSession;
+
+/**
+ * @author baigod
+ */
+public abstract class LvnengDownlinkCmdExe extends AbstractLvnengCmdExe {
+
+ public abstract void execute(TcpSession tcpSession, LvnengDwonlinkMessage lvnengDwonlinkMessage, ProtocolContext ctx);
+
+}
\ No newline at end of file
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java
new file mode 100644
index 0000000..5e3c007
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java
@@ -0,0 +1,42 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * @author baigod
+ */
+@Data
+@Accessors(chain = true)
+@NoArgsConstructor
+public class LvnengDwonlinkMessage implements Serializable {
+ public static final byte SUCCESS_BYTE = 0x00;
+ public static final byte FAILURE_BYTE = 0x01;
+
+ // 消息ID
+ private UUID id;
+
+ // 请求ID(如有)
+ private UUID requestId;
+
+ // 指令
+ private int cmd;
+
+ // 消息体
+ private DownlinkRequestMessage msg;
+
+ // 上行消息
+ private LvnengUplinkMessage requestData;
+
+}
\ No newline at end of file
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java
new file mode 100644
index 0000000..ae59403
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java
@@ -0,0 +1,191 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng;
+
+import cn.hutool.core.util.ClassUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.extern.slf4j.Slf4j;
+import sanbing.jcpp.infrastructure.util.JCPPPair;
+import sanbing.jcpp.infrastructure.util.codec.ByteUtil;
+import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
+import sanbing.jcpp.proto.gen.ProtocolProto;
+import sanbing.jcpp.protocol.ProtocolContext;
+import sanbing.jcpp.protocol.ProtocolMessageProcessor;
+import sanbing.jcpp.protocol.domain.ListenerToHandlerMsg;
+import sanbing.jcpp.protocol.domain.SessionToHandlerMsg;
+import sanbing.jcpp.protocol.forwarder.Forwarder;
+import sanbing.jcpp.protocol.listener.tcp.TcpSession;
+import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd;
+import sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+public class LvnengProtocolMessageProcessor extends ProtocolMessageProcessor {
+ // 协议常量定义
+ private static final int HEADER_SIZE = 9; // 帧头(2) + 长度(2) + 加密标识(1) + 序号(1) + 命令字(2) + 校验和(1)
+ private static final int FRAME_MIN_LENGTH = 9; // 最小帧长度(无数据域的情况)
+
+ private final Map uplinkCmdExeMap = new ConcurrentHashMap<>();
+ private final Map downlinkCmdExeMap = new ConcurrentHashMap<>();
+
+ public LvnengProtocolMessageProcessor(Forwarder forwarder, ProtocolContext protocolContext) {
+ super(forwarder, protocolContext);
+
+ Set> cmdClasses = ClassUtil.scanPackageByAnnotation(ClassUtil.getPackage(this.getClass()), LvnengCmd.class);
+ cmdClasses.stream().filter(LvnengUplinkCmdExe.class::isAssignableFrom)
+ .forEach(clazz -> {
+ int cmd = clazz.getAnnotation(LvnengCmd.class).value();
+ try {
+ LvnengUplinkCmdExe lvnengUplinkCmdExe = (LvnengUplinkCmdExe) clazz.getDeclaredConstructor().newInstance();
+ uplinkCmdExeMap.put(cmd, lvnengUplinkCmdExe);
+ } catch (InstantiationException |
+ IllegalAccessException |
+ InvocationTargetException |
+ NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ cmdClasses.stream().filter(LvnengDownlinkCmdExe.class::isAssignableFrom)
+ .forEach(clazz -> {
+ int cmd = clazz.getAnnotation(LvnengCmd.class).value();
+ try {
+ LvnengDownlinkCmdExe lvnengDownlinkCmdExe = (LvnengDownlinkCmdExe) clazz.getDeclaredConstructor().newInstance();
+ downlinkCmdExeMap.put(cmd, lvnengDownlinkCmdExe);
+ } catch (InstantiationException |
+ IllegalAccessException |
+ InvocationTargetException |
+ NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Override
+ protected void uplinkHandle(ListenerToHandlerMsg listenerToHandlerMsg) {
+ final UUID msgId = listenerToHandlerMsg.id();
+ final byte[] msg = listenerToHandlerMsg.msg();
+ final TcpSession session = (TcpSession) listenerToHandlerMsg.session();
+
+ ByteBuf in = Unpooled.wrappedBuffer(msg);
+ try {
+ // 1. 解析帧头信息
+ final int startFlag = in.readUnsignedShort();
+ final int dataLength = in.readUnsignedShortLE();
+ final int encryptFlag = in.readUnsignedByte();
+ final int seqNo = in.readUnsignedByte();
+ final int frameType = in.readUnsignedShortLE();
+
+ // 2. 计算并检查消息体长度
+ if (dataLength < FRAME_MIN_LENGTH) {
+ log.warn("{} 绿能协议帧长度异常,期望最小长度:{} 实际长度:{}",
+ session, FRAME_MIN_LENGTH, dataLength);
+ return;
+ }
+ final int msgBodyLength = dataLength - HEADER_SIZE;
+
+ // 3. 读取消息体数据
+ byte[] msgBody = new byte[msgBodyLength];
+ in.readBytes(msgBody);
+
+ // 4. 校验和验证
+ byte receivedCheckSum = in.readByte();
+
+ // 准备校验和数据(命令字 + 数据域)
+ byte[] sumData = new byte[2 + msgBody.length]; // 2字节命令字 + 数据域
+ sumData[0] = (byte) (frameType & 0xFF);
+ sumData[1] = (byte) ((frameType >> 8) & 0xFF);
+ System.arraycopy(msgBody, 0, sumData, 2, msgBody.length);
+
+ // 验证校验和
+ JCPPPair checkResult = ByteUtil.verifySum(sumData, receivedCheckSum);
+ if (!checkResult.getFirst()) {
+ log.warn("{} 绿能校验和验证失败 CMD:0x{} 接收校验和:0x{} 期望校验和:0x{}",
+ session, Integer.toHexString(frameType),
+ String.format("%02x", receivedCheckSum & 0xFF),
+ String.format("%02x", checkResult.getSecond() & 0xFF));
+ return;
+ }
+
+ // 5. 构建上行消息对象并执行
+ LvnengUplinkMessage uplinkMessage = new LvnengUplinkMessage(msgId)
+ .setHead(startFlag)
+ .setDataLength(dataLength)
+ .setSequenceNumber(seqNo)
+ .setEncryptionFlag(encryptFlag)
+ .setCmd(frameType)
+ .setMsgBody(msgBody)
+ .setCheckSum(checkResult.getSecond())
+ .setRawFrame(msg);
+
+ exeCmd(uplinkMessage, session);
+
+ } catch (Exception e) {
+ log.error("{} 处理绿能协议上行消息时发生异常", session, e);
+ } finally {
+ in.release();
+ }
+ }
+
+ @Override
+ protected void downlinkHandle(SessionToHandlerMsg sessionToHandlerMsg) {
+ TcpSession session = (TcpSession) sessionToHandlerMsg.session();
+
+ ProtocolProto.DownlinkRequestMessage protocolDownlinkMsg = sessionToHandlerMsg.downlinkMsg();
+
+ int cmd = LvnengDownlinkCmdEnum.valueOf(protocolDownlinkMsg.getDownlinkCmd()).getCmd();
+
+ LvnengDwonlinkMessage message = new LvnengDwonlinkMessage();
+ message.setId(new UUID(protocolDownlinkMsg.getMessageIdMSB(), protocolDownlinkMsg.getMessageIdLSB()));
+ message.setCmd(cmd);
+ message.setMsg(protocolDownlinkMsg);
+
+ if (protocolDownlinkMsg.hasRequestIdMSB() && protocolDownlinkMsg.hasRequestIdLSB()) {
+ message.setRequestId(new UUID(protocolDownlinkMsg.getRequestIdMSB(), protocolDownlinkMsg.getRequestIdLSB()));
+ }
+
+ if (protocolDownlinkMsg.hasRequestData()) {
+ message.setRequestData(JacksonUtil.fromBytes(protocolDownlinkMsg.getRequestData().toByteArray(), LvnengUplinkMessage.class));
+ }
+
+ exeCmd(message, session);
+ }
+
+
+ private void exeCmd(LvnengUplinkMessage message, TcpSession session) {
+ LvnengUplinkCmdExe uplinkCmdExe = uplinkCmdExeMap.get(message.getCmd());
+
+ if (uplinkCmdExe == null) {
+
+ log.info("{} 绿能协议接收到未知的上行指令 0x{}", session, Integer.toHexString(message.getCmd()));
+
+ return;
+ }
+
+ uplinkCmdExe.execute(session, message, protocolContext);
+ }
+
+ private void exeCmd(LvnengDwonlinkMessage message, TcpSession session) {
+ LvnengDownlinkCmdExe downlinkCmdExe = downlinkCmdExeMap.get(message.getCmd());
+
+ if (downlinkCmdExe == null) {
+
+ log.info("{} 绿能协议接收到未知的下行指令 0x{}", session, Integer.toHexString(message.getCmd()));
+
+ return;
+ }
+
+ downlinkCmdExe.execute(session, message, protocolContext);
+ }
+
+}
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java
new file mode 100644
index 0000000..53a9203
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java
@@ -0,0 +1,35 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng;
+
+import com.google.protobuf.ByteString;
+import lombok.extern.slf4j.Slf4j;
+import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
+import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
+import sanbing.jcpp.protocol.ProtocolContext;
+import sanbing.jcpp.protocol.listener.tcp.TcpSession;
+
+/**
+ * @author baigod
+ */
+@Slf4j
+public abstract class LvnengUplinkCmdExe extends AbstractLvnengCmdExe {
+
+ public abstract void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx);
+
+ protected UplinkQueueMessage.Builder uplinkMessageBuilder(String messageKey, TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage) {
+ return UplinkQueueMessage.newBuilder()
+ .setMessageIdMSB(lvnengUplinkMessage.getId().getMostSignificantBits())
+ .setMessageIdLSB(lvnengUplinkMessage.getId().getLeastSignificantBits())
+ .setSessionIdMSB(tcpSession.getId().getMostSignificantBits())
+ .setSessionIdLSB(tcpSession.getId().getLeastSignificantBits())
+ .setRequestData(ByteString.copyFrom(JacksonUtil.writeValueAsBytes(lvnengUplinkMessage)))
+ .setMessageKey(messageKey)
+ .setProtocolName(tcpSession.getProtocolName());
+ }
+
+}
\ No newline at end of file
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkMessage.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkMessage.java
new file mode 100644
index 0000000..d12f6dc
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkMessage.java
@@ -0,0 +1,53 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+@Data
+@Accessors(chain = true)
+public class LvnengUplinkMessage implements Serializable {
+ // 消息ID
+ private final UUID id;
+
+ // 起始域
+ private int head;
+
+ // 数据长度
+ private int dataLength;
+
+ // 序列号
+ private int sequenceNumber;
+
+ // 加密标识
+ private int encryptionFlag;
+
+ // 指令
+ private int cmd;
+
+ // 消息体
+ private byte[] msgBody;
+
+ // 校验和
+ private int checkSum;
+
+ // 真实报文
+ private byte[] rawFrame;
+
+ public LvnengUplinkMessage(UUID id) {
+ this.id = id;
+ }
+
+ public LvnengUplinkMessage() {
+ this(UUID.randomUUID());
+ }
+
+}
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/annotation/LvnengCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/annotation/LvnengCmd.java
new file mode 100644
index 0000000..b7a9ba0
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/annotation/LvnengCmd.java
@@ -0,0 +1,21 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author baigod
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface LvnengCmd {
+
+ int value();
+
+}
\ No newline at end of file
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java
new file mode 100644
index 0000000..925b022
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java
@@ -0,0 +1,26 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author baigod
+ */
+@AllArgsConstructor
+@Getter
+public enum LvnengDownlinkCmdEnum {
+
+ LOGIN_ACK((short) 105),
+
+ SYNC_TIME((short) 3)
+ ;
+
+ private final short cmd;
+
+}
\ No newline at end of file
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/LvnengV340ProtocolBootstrap.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/LvnengV340ProtocolBootstrap.java
new file mode 100644
index 0000000..cd464f3
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/LvnengV340ProtocolBootstrap.java
@@ -0,0 +1,41 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng.v340;
+
+import lombok.extern.slf4j.Slf4j;
+import sanbing.jcpp.infrastructure.util.annotation.ProtocolComponent;
+import sanbing.jcpp.protocol.ProtocolBootstrap;
+import sanbing.jcpp.protocol.ProtocolMessageProcessor;
+import sanbing.jcpp.protocol.lvneng.LvnengProtocolMessageProcessor;
+
+import static sanbing.jcpp.protocol.lvneng.v340.LvnengV340ProtocolBootstrap.PROTOCOL_NAME;
+
+@ProtocolComponent(PROTOCOL_NAME)
+@Slf4j
+public class LvnengV340ProtocolBootstrap extends ProtocolBootstrap {
+
+ public static final String PROTOCOL_NAME = "lvnengV340";
+ @Override
+ protected String getProtocolName() {
+ return PROTOCOL_NAME;
+ }
+
+ @Override
+ protected void _init() {
+ // do nothing
+ }
+
+ @Override
+ protected void _destroy() {
+ // do nothing
+ }
+
+ @Override
+ protected ProtocolMessageProcessor messageProcessor() {
+ return new LvnengProtocolMessageProcessor(forwarder, protocolContext);
+ }
+}
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java
new file mode 100644
index 0000000..399882b
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java
@@ -0,0 +1,135 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng.v340.cmd;
+
+import cn.hutool.core.util.RandomUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.extern.slf4j.Slf4j;
+import sanbing.jcpp.infrastructure.util.codec.BCDUtil;
+import sanbing.jcpp.infrastructure.util.codec.CP56Time2aUtil;
+import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
+import sanbing.jcpp.infrastructure.util.mdc.MDCUtils;
+import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
+import sanbing.jcpp.proto.gen.ProtocolProto.LoginResponse;
+import sanbing.jcpp.protocol.ProtocolContext;
+import sanbing.jcpp.protocol.listener.tcp.TcpSession;
+import sanbing.jcpp.protocol.listener.tcp.enums.SequenceNumberLength;
+import sanbing.jcpp.protocol.lvneng.LvnengDownlinkCmdExe;
+import sanbing.jcpp.protocol.lvneng.LvnengDwonlinkMessage;
+import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage;
+import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import static sanbing.jcpp.infrastructure.util.config.ThreadPoolConfiguration.PROTOCOL_SESSION_SCHEDULED;
+import static sanbing.jcpp.protocol.domain.SessionCloseReason.MANUALLY;
+import static sanbing.jcpp.protocol.listener.tcp.TcpSession.SCHEDULE_KEY_AUTO_SYNC_TIME;
+import static sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum.LOGIN_ACK;
+import static sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum.SYNC_TIME;
+
+/**
+ * 绿能3.4 服务器应答充电桩签到命令
+ */
+@Slf4j
+@LvnengCmd(105)
+public class LvnengV340LoginAckDLCmd extends LvnengDownlinkCmdExe {
+ @Override
+ public void execute(TcpSession tcpSession, LvnengDwonlinkMessage lvnengDwonlinkMessage, ProtocolContext ctx) {
+ log.debug("{} 绿能3.4登录认证应答", tcpSession);
+
+ if (!lvnengDwonlinkMessage.getMsg().hasLoginResponse()) {
+ return;
+ }
+
+ LoginResponse loginResponse = lvnengDwonlinkMessage.getMsg().getLoginResponse();
+
+ LvnengUplinkMessage requestData = JacksonUtil.fromBytes(lvnengDwonlinkMessage.getMsg().getRequestData().toByteArray(), LvnengUplinkMessage.class);
+
+ // 获取上行报文
+ byte[] uplinkRawFrame = requestData.getRawFrame();
+ // 从上行报文中取出桩编号字节数组
+ byte[] pileCodeBytes = Arrays.copyOfRange(uplinkRawFrame, 12, 44);
+ byte[] randomNumBytes = Arrays.copyOfRange(uplinkRawFrame, 98, 102);
+
+ if (loginResponse.getSuccess()) {
+
+ // 构造并下发登录ACK
+ loginAck(tcpSession, requestData, randomNumBytes);
+
+ // 构造定时对时
+ registerSyncTimeTask(tcpSession, pileCodeBytes, requestData);
+
+ } else {
+
+ log.info("绿能3.4登录认证失败,服务端断开连接。 pileCode:{}", loginResponse.getPileCode());
+
+ // 构造并下发登录ACK
+ loginAck(tcpSession, requestData, new byte[]{0x00, 0x00, 0x00, 0x00});
+
+ // 断开连接
+ tcpSession.close(MANUALLY);
+ }
+ }
+
+
+ private void loginAck(TcpSession tcpSession, LvnengUplinkMessage requestData, byte[] randomNumBytes) {
+ // 创建ACK消息体7字节桩编号+1字节登录结果
+ ByteBuf loginAckMsgBody = Unpooled.buffer(18);
+ loginAckMsgBody.writeShortLE(0x00);
+ loginAckMsgBody.writeShortLE(0x00);
+ loginAckMsgBody.writeBytes(randomNumBytes);
+ loginAckMsgBody.writeByte(0x00);
+
+ encodeAndWriteFlush(LOGIN_ACK,
+ requestData.getSequenceNumber(),
+ requestData.getEncryptionFlag(),
+ loginAckMsgBody,
+ tcpSession);
+ }
+
+ private void registerSyncTimeTask(TcpSession tcpSession, byte[] pileCodeBytes, LvnengUplinkMessage requestData) {
+ tcpSession.addSchedule(SCHEDULE_KEY_AUTO_SYNC_TIME, k -> {
+ log.info("{} 云快充3.4开始注册定时对时任务", tcpSession);
+ return PROTOCOL_SESSION_SCHEDULED.scheduleAtFixedRate(() ->
+ syncTime(tcpSession, pileCodeBytes, requestData),
+ 0, RandomUtil.randomInt(420, 480), TimeUnit.MINUTES);
+ }
+ );
+ }
+
+ private void syncTime(TcpSession tcpSession, byte[] pileCodeBytes, LvnengUplinkMessage requestData) {
+ TracerContextUtil.newTracer();
+ MDCUtils.recordTracer();
+ log.info("{} 绿能3.4开始下发对时报文", tcpSession);
+ ByteBuf syncTimeMsgBody = Unpooled.buffer(14);
+ syncTimeMsgBody.writeBytes(pileCodeBytes);
+ syncTimeMsgBody.writeBytes(CP56Time2aUtil.encode(LocalDateTime.now()));
+
+ ByteBuf msgBodyBuf = Unpooled.buffer();
+ // 预留1
+ msgBodyBuf.writeShortLE(0);
+ // 预留1
+ msgBodyBuf.writeShortLE(0);
+ msgBodyBuf.writeByte(1);
+ // 4 参数起始地址,子命令
+ msgBodyBuf.writeIntLE(2);
+ //6 参数字节长度
+ msgBodyBuf.writeShortLE(8);
+ //7 命令参数数据
+ msgBodyBuf.writeBytes(BCDUtil.dateToBcd8(LocalDateTime.now()));
+
+ encodeAndWriteFlush(SYNC_TIME,
+ tcpSession.nextSeqNo(SequenceNumberLength.SHORT),
+ requestData.getEncryptionFlag(),
+ syncTimeMsgBody,
+ tcpSession);
+ }
+
+}
diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginULCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginULCmd.java
new file mode 100644
index 0000000..833a5c6
--- /dev/null
+++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginULCmd.java
@@ -0,0 +1,118 @@
+/**
+ * 开源代码,仅供学习和交流研究使用,商用请联系三丙
+ * 微信:mohan_88888
+ * 抖音:程序员三丙
+ * 付费课程知识星球:https://t.zsxq.com/aKtXo
+ */
+package sanbing.jcpp.protocol.lvneng.v340.cmd;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import sanbing.jcpp.infrastructure.util.codec.BCDUtil;
+import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
+import sanbing.jcpp.proto.gen.ProtocolProto.LoginRequest;
+import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
+import sanbing.jcpp.protocol.ProtocolContext;
+import sanbing.jcpp.protocol.listener.tcp.TcpSession;
+import sanbing.jcpp.protocol.lvneng.LvnengUplinkCmdExe;
+import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage;
+import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+/**
+ * 绿能3.4 充电桩签到信息上报
+ */
+@Slf4j
+@LvnengCmd(106)
+public class LvnengV340LoginULCmd extends LvnengUplinkCmdExe {
+ @Override
+ public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) {
+ log.debug("{} 绿能3.4登录认证请求", tcpSession);
+ ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody());
+
+ ObjectNode additionalInfo = JacksonUtil.newObjectNode();
+
+ // 预留
+ byteBuf.readShortLE();
+ // 预留
+ byteBuf.readShortLE();
+
+ // 充电桩编码
+ byte[] pileCodeBytes = new byte[32];
+ byteBuf.readBytes(pileCodeBytes);
+ String pileCode = StringUtils.trim(new String(pileCodeBytes, StandardCharsets.US_ASCII));
+
+ //预留
+ int flag = byteBuf.readByte();
+ additionalInfo.put("标识", flag);
+
+ // 充电桩软件版本 (4字节)
+ // 格式: 0x00 0x01 0x0100 表示 V0.1.256
+ int major = byteBuf.readUnsignedByte(); // 主版本号
+ int minor = byteBuf.readUnsignedByte(); // 次版本号
+ int patch = byteBuf.readUnsignedShort(); // 修订版本号
+ String version = String.format("V%d.%d.%d", major, minor, patch);
+ additionalInfo.put("版本", version);
+
+ // 预留
+ byteBuf.skipBytes(10);
+
+ // 充电枪个数
+ int gunsNum = byteBuf.readByte();
+ additionalInfo.put("充电枪个数", gunsNum);
+
+ // 预留
+ byteBuf.skipBytes(6);
+
+ // 当前充电桩时间
+ byte[] pileSystemTimeBytes = new byte[8];
+ byteBuf.readBytes(pileSystemTimeBytes);
+ LocalDateTime pileSystemTime = BCDUtil.bcdToDate(pileSystemTimeBytes);
+ additionalInfo.put("当前充电桩时间", pileSystemTime == null ? null : pileSystemTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+ // 预留
+ byteBuf.readLong();
+ byteBuf.readLong();
+ byteBuf.readLong();
+
+ long randomNum = byteBuf.readUnsignedIntLE();
+ additionalInfo.put("桩生成的随机数", randomNum);
+
+ // 充电桩与服务器通信协议版本 (2字节)
+ // 十进制30表示V3.0
+ int softVersion = byteBuf.readUnsignedShortLE();
+ int softMajor = softVersion / 10; // 主版本号
+ int softMinor = softVersion % 10; // 次版本号
+ additionalInfo.put("桩后台通信协议版本", String.format("V%d.%d", softMajor, softMinor));
+
+ int whiteVersion = byteBuf.readIntLE();
+ additionalInfo.put("白名单版本号", whiteVersion);
+
+ tcpSession.addPileCode(pileCode);
+
+ // 注册前置会话
+ ctx.getProtocolSessionRegistryProvider().register(tcpSession);
+
+ // 转发到后端
+ LoginRequest loginRequest = LoginRequest.newBuilder()
+ .setPileCode(pileCode)
+ .setCredential(pileCode)
+ .setRemoteAddress(tcpSession.getAddress().toString())
+ .setNodeId(ctx.getServiceInfoProvider().getServiceId())
+ .setNodeHostAddress(ctx.getServiceInfoProvider().getHostAddress())
+ .setNodeRestPort(ctx.getServiceInfoProvider().getRestPort())
+ .setNodeGrpcPort(ctx.getServiceInfoProvider().getGrpcPort())
+ .setAdditionalInfo(additionalInfo.toString())
+ .build();
+
+ UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(loginRequest.getPileCode(), tcpSession, lvnengUplinkMessage)
+ .setLoginRequest(loginRequest)
+ .build();
+ tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
+ }
+}
diff --git a/jcpp-protocol-yunkuaichong/READMD.md b/jcpp-protocol-yunkuaichong/READMD.md
index d050784..583b54b 100644
--- a/jcpp-protocol-yunkuaichong/READMD.md
+++ b/jcpp-protocol-yunkuaichong/READMD.md
@@ -10,7 +10,7 @@
#### 0x02 下行登录应答
`68 0C 00 19 00 02 20 23 12 12 00 00 10 00 A1 55`
#### 0x56 下行登录后对时
-`68 12 01 00 00 56 20 23 12 12 00 00 10 30 75 0F 11 12 0C 18 04 7D `
+`68 12 01 00 00 56 20 23 12 12 00 00 10 30 75 0F 11 12 0C 18 04 7D`
---
diff --git a/jcpp-protocol-yunkuaichong/pom.xml b/jcpp-protocol-yunkuaichong/pom.xml
index 4caba18..de542b0 100644
--- a/jcpp-protocol-yunkuaichong/pom.xml
+++ b/jcpp-protocol-yunkuaichong/pom.xml
@@ -21,7 +21,7 @@
jcpp-protocol-yunkuaichong
jar
JChargePointProtocol Yunkuaichong Protocol Module
- 云快充1.5
+ 云快充全版本协议模块
${basedir}/..
diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java
index 8091242..4a3497b 100644
--- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java
+++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java
@@ -71,7 +71,7 @@ public class AbstractYunKuaiChongCmdExe {
protected static byte[] encodePileCode(String pileCode) {
if (StringUtils.length(pileCode) > 32) {
- throw new IllegalArgumentException("云快充1.5可接受最大桩编号为14位");
+ throw new IllegalArgumentException("云快充可接受最大桩编号为14位");
}
String pileCodeStr = StringUtils.leftPad(pileCode, 14, '0');
@@ -81,7 +81,7 @@ public class AbstractYunKuaiChongCmdExe {
protected static byte[] encodeGunCode(String gunCode) {
if (StringUtils.length(gunCode) > 2) {
- throw new IllegalArgumentException("云快充1.5可接受最大枪编号为2位");
+ throw new IllegalArgumentException("云快充可接受最大枪编号为2位");
}
String gunCodeStr = StringUtils.leftPad(gunCode, 2, '0');
diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java
index 7808c29..87bb97b 100644
--- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java
+++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java
@@ -8,6 +8,7 @@ package sanbing.jcpp.protocol.yunkuaichong;
import cn.hutool.core.util.ClassUtil;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.infrastructure.util.JCPPPair;
@@ -100,6 +101,8 @@ public class YunKuaiChongProtocolMessageProcessor extends ProtocolMessageProcess
// ================== 校验和双模式处理 ==================
final int checkSumLE = in.getUnsignedShortLE(checksumPos);
final int checkSumBE = in.getUnsignedShort(checksumPos);
+ byte[] checkSumBytes = new byte[2];
+ in.getBytes(checksumPos, checkSumBytes);
// ================== 校验数据智能拷贝 ==================
final byte[] checkData = Arrays.copyOfRange(msg, 2, 2 + dataLength);
@@ -108,16 +111,16 @@ public class YunKuaiChongProtocolMessageProcessor extends ProtocolMessageProcess
JCPPPair checkResult = checkCrcSum(checkData, checkSumLE);
if (!checkResult.getFirst()) {
if (log.isDebugEnabled()) { // 日志惰性计算
- log.debug("{} 云快充校验域一次校验失败 CMD:{} 校验和:0x{} 期望校验和:0x{}",
- session, frameType, Integer.toHexString(checkSumLE), Integer.toHexString(checkResult.getSecond()));
+ log.debug("{} 云快充校验域一次校验失败 CMD:{} 校验和:0x{} 期望校验和:{}",
+ session, Integer.toHexString(frameType), ByteBufUtil.hexDump(checkSumBytes), checkResult.getSecond());
}
checkResult = checkCrcSum(checkData, checkSumBE);
}
// ================== 最终校验失败处理 ==================
if (!checkResult.getFirst()) {
- log.info("{} 云快充校验域二次校验失败 CMD:{} 校验和:0x{} 期望校验和:0x{}",
- session, frameType, Integer.toHexString(checkSumBE), Integer.toHexString(checkResult.getSecond()));
+ log.info("{} 云快充校验域二次校验失败 CMD:{} 校验和:0x{} 期望校验和:{}",
+ session, Integer.toHexString(frameType), ByteBufUtil.hexDump(checkSumBytes), checkResult.getSecond());
return;
}
diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java
index deadec3..9aea855 100644
--- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java
+++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java
@@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import static sanbing.jcpp.infrastructure.util.config.ThreadPoolConfiguration.PROTOCOL_SESSION_SCHEDULED;
import static sanbing.jcpp.protocol.domain.SessionCloseReason.MANUALLY;
+import static sanbing.jcpp.protocol.listener.tcp.TcpSession.SCHEDULE_KEY_AUTO_SYNC_TIME;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.FAILURE_BYTE;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.SUCCESS_BYTE;
import static sanbing.jcpp.protocol.yunkuaichong.enums.YunKuaiChongDownlinkCmdEnum.LOGIN_ACK;
@@ -94,7 +95,7 @@ public class YunKuaiChongV150LoginAckDLCmd extends YunKuaiChongDownlinkCmdExe {
}
private void registerSyncTimeTask(TcpSession tcpSession, byte[] pileCodeBytes, YunKuaiChongUplinkMessage requestData) {
- tcpSession.addSchedule("auto-sync-time", k -> {
+ tcpSession.addSchedule(SCHEDULE_KEY_AUTO_SYNC_TIME, k -> {
log.info("{} 云快充1.5.0开始注册定时对时任务", tcpSession);
return PROTOCOL_SESSION_SCHEDULED.scheduleAtFixedRate(() ->
syncTime(tcpSession, pileCodeBytes, requestData),
diff --git a/jcpp-testing/pom.xml b/jcpp-testing/pom.xml
index 8235cdd..685ff04 100644
--- a/jcpp-testing/pom.xml
+++ b/jcpp-testing/pom.xml
@@ -60,6 +60,10 @@
sanbing
jcpp-protocol-yunkuaichong
+
+ sanbing
+ jcpp-protocol-lvneng
+
diff --git a/pom.xml b/pom.xml
index ed4fb46..1bbc3ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.3.5
+ 3.5.4
sanbing
@@ -49,11 +49,12 @@
3.13.0
5.8.32
3.5.7
- 5.7.0
+ 5.9.0
6.6.2
- 3.9.2
+ 3.9.3
3.8.16.Final
1.3.2
+ 3.18.0
@@ -106,6 +107,7 @@
jcpp-protocol-api
jcpp-testing
jcpp-protocol-yunkuaichong
+ jcpp-protocol-lvneng
@@ -148,6 +150,11 @@
jcpp-protocol-yunkuaichong
${project.version}
+
+ sanbing
+ jcpp-protocol-lvneng
+ ${project.version}
+
sanbing
jcpp-infrastructure-cache