!19 绿能模块

* 绿能模块
This commit is contained in:
三丙
2025-08-09 11:00:12 +00:00
parent 3d441d75a3
commit 199711026c
34 changed files with 1122 additions and 50 deletions

View File

@@ -0,0 +1,20 @@
### 绿能直流3.4模拟报文
---
> 示例统一桩编号20231212000010 (HEX:20231212000010)
> 示例统一枪编号01
#### 106 上行登录
`AAF56D0010016A000000000032303233313231323030303031300000000000000000000000000000000000000000010100000000000000000000000200000000000020250808120101FF00000000000000000000000000000000000000000000000001E24000220003E80000B4`
#### 0x02 下行登录应答
`AAF51200100169000000000001E24000008C`
#### 0x56 下行登录后对时
`AAF530001001030032303233313231323030303031300000000000000000000000000000000000004F353512090819A6`
---
#### 109 充电桩状态信息包上报
`AAF5D00010C46D00000000003230323331323132303030303130000000000000000000000000000000000000010101000000000000000000000020250808105527FF00000000000000000000000000000000000000000000000000000000000000F567720000000000F5677200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003232460000000000000000000000000000000000000044`
---

View 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>

View File

@@ -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));
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}