!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,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);
}
}