mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-03 17:39:55 +08:00
@@ -15,9 +15,10 @@
|
||||
|
||||
------------------------------
|
||||
#### 当前支持的充电桩协议
|
||||
| 协议名 | 版本号 |
|
||||
|---|------------|
|
||||
| 云快充 | 1.5.0、1.6.0 |
|
||||
| 协议名 | 版本号 |
|
||||
|-----|-------------|
|
||||
| 云快充 | 1.5.0、1.6.0 |
|
||||
| 绿能 | 3.4 |
|
||||
|
||||
------------------------------
|
||||
#### 充电桩协议文档
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-yunkuaichong</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-lvneng</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
|
||||
@@ -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:}"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<StatefulRedisConnection<String, String>> buildPoolConfig() {
|
||||
GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfig = new GenericObjectPoolConfig<>();
|
||||
protected GenericObjectPoolConfig<StatefulConnection<?, ?>> buildPoolConfig() {
|
||||
GenericObjectPoolConfig<StatefulConnection<?, ?>> poolConfig = new GenericObjectPoolConfig<>();
|
||||
poolConfig.setMaxTotal(maxTotal);
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Boolean, Byte> verifySum(byte[] data, byte expectedSum) {
|
||||
byte actualSum = calculateSum(data);
|
||||
return JCPPPair.of(actualSum == expectedSum, actualSum);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-yunkuaichong</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-lvneng</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -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:
|
||||
|
||||
20
jcpp-protocol-lvneng/READMD.md
Normal file
20
jcpp-protocol-lvneng/READMD.md
Normal file
@@ -0,0 +1,20 @@
|
||||
### 绿能直流3.4模拟报文
|
||||
|
||||
---
|
||||
|
||||
> 示例统一桩编号:20231212000010 (HEX:20231212000010)
|
||||
> 示例统一枪编号:01
|
||||
|
||||
#### 106 上行登录
|
||||
`AAF56D0010016A000000000032303233313231323030303031300000000000000000000000000000000000000000010100000000000000000000000200000000000020250808120101FF00000000000000000000000000000000000000000000000001E24000220003E80000B4`
|
||||
#### 0x02 下行登录应答
|
||||
`AAF51200100169000000000001E24000008C`
|
||||
#### 0x56 下行登录后对时
|
||||
`AAF530001001030032303233313231323030303031300000000000000000000000000000000000004F353512090819A6`
|
||||
|
||||
---
|
||||
|
||||
#### 109 充电桩状态信息包上报
|
||||
`AAF5D00010C46D00000000003230323331323132303030303130000000000000000000000000000000000000010101000000000000000000000020250808105527FF00000000000000000000000000000000000000000000000000000000000000F567720000000000F5677200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003232460000000000000000000000000000000000000044`
|
||||
|
||||
---
|
||||
37
jcpp-protocol-lvneng/pom.xml
Normal file
37
jcpp-protocol-lvneng/pom.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
微信:mohan_88888
|
||||
抖音:程序员三丙
|
||||
付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jcpp-protocol-lvneng</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>JChargePointProtocol LvNeng Protocol Module</name>
|
||||
<description>绿能全版本协议模块</description>
|
||||
|
||||
<properties>
|
||||
<main.dir>${basedir}/..</main.dir>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<Integer, LvnengUplinkCmdExe> uplinkCmdExeMap = new ConcurrentHashMap<>();
|
||||
private final Map<Integer, LvnengDownlinkCmdExe> downlinkCmdExeMap = new ConcurrentHashMap<>();
|
||||
|
||||
public LvnengProtocolMessageProcessor(Forwarder forwarder, ProtocolContext protocolContext) {
|
||||
super(forwarder, protocolContext);
|
||||
|
||||
Set<Class<?>> 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<Boolean, Byte> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<artifactId>jcpp-protocol-yunkuaichong</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>JChargePointProtocol Yunkuaichong Protocol Module</name>
|
||||
<description>云快充1.5</description>
|
||||
<description>云快充全版本协议模块</description>
|
||||
|
||||
<properties>
|
||||
<main.dir>${basedir}/..</main.dir>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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<Boolean, Integer> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -60,6 +60,10 @@
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-yunkuaichong</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-lvneng</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
13
pom.xml
13
pom.xml
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.5</version>
|
||||
<version>3.5.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>sanbing</groupId>
|
||||
@@ -49,11 +49,12 @@
|
||||
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
|
||||
<hutool-all.version>5.8.32</hutool-all.version>
|
||||
<mybatis-plus-boot-starter.version>3.5.7</mybatis-plus-boot-starter.version>
|
||||
<curator-recipes.version>5.7.0</curator-recipes.version>
|
||||
<curator-recipes.version>5.9.0</curator-recipes.version>
|
||||
<oshi-core.version>6.6.2</oshi-core.version>
|
||||
<zookeeper.version>3.9.2</zookeeper.version>
|
||||
<zookeeper.version>3.9.3</zookeeper.version>
|
||||
<xnio-api.version>3.8.16.Final</xnio-api.version>
|
||||
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
|
||||
<commons-lang3.version>3.18.0</commons-lang3.version>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
@@ -106,6 +107,7 @@
|
||||
<module>jcpp-protocol-api</module>
|
||||
<module>jcpp-testing</module>
|
||||
<module>jcpp-protocol-yunkuaichong</module>
|
||||
<module>jcpp-protocol-lvneng</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
@@ -148,6 +150,11 @@
|
||||
<artifactId>jcpp-protocol-yunkuaichong</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-lvneng</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-infrastructure-cache</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user