mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-05 18:39:56 +08:00
云快充1.5.0 初始化
This commit is contained in:
35
jcpp-protocol-yunkuaichong/pom.xml
Normal file
35
jcpp-protocol-yunkuaichong/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
抖音关注:程序员三丙
|
||||
知识星球:https://t.zsxq.com/j9b21
|
||||
|
||||
-->
|
||||
<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-yunkuaichong</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>JChargePointProtocol Yunkuaichong Protocol Module</name>
|
||||
<description>云快充1.5</description>
|
||||
|
||||
<properties>
|
||||
<main.dir>${basedir}/..</main.dir>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>sanbing</groupId>
|
||||
<artifactId>jcpp-protocol-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import sanbing.jcpp.infrastructure.util.codec.BCDUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.listener.tcp.enums.SequenceNumberLength;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
|
||||
import static sanbing.jcpp.infrastructure.util.codec.ByteUtil.crcSum;
|
||||
import static sanbing.jcpp.infrastructure.util.codec.ByteUtil.toBytes;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public class AbstractYunKuaiChongCmdExe {
|
||||
|
||||
private static final byte TOP_BYTE = 0x00;
|
||||
private static final byte PEAK_BYTE = 0x01;
|
||||
private static final byte FLAT_BYTE = 0x02;
|
||||
private static final byte VALLEY_BYTE = 0x03;
|
||||
|
||||
protected static final byte YUNKUAICHONG_HEAD = 0x68;
|
||||
protected static final int YUNKUAICHONG_NORMAL_ENCRYPTION_FLAG = 0;
|
||||
|
||||
private static final DecimalFormat PRICING_ID_DECIMAL_FORMAT = new DecimalFormat("0000");
|
||||
|
||||
protected static String decodeTradeNo(byte[] tradeNo) {
|
||||
String tradeNoStr = BCDUtil.toString(tradeNo);
|
||||
return CharSequenceUtil.strip(tradeNoStr, "0", null);
|
||||
}
|
||||
|
||||
protected byte[] encodePricingId(long pricingId) {
|
||||
return BCDUtil.toBytes(PRICING_ID_DECIMAL_FORMAT.format(pricingId % 10000));
|
||||
}
|
||||
|
||||
protected static byte getFlagForCurrentTime(List<ProtocolProto.PeriodProto> periodList, LocalTime currentTime) {
|
||||
for (ProtocolProto.PeriodProto period : periodList) {
|
||||
LocalTime beginLt = LocalTime.parse(period.getBegin());
|
||||
LocalTime endLt = "00:00".equals(period.getEnd()) ? LocalTime.MAX : LocalTime.parse(period.getEnd());
|
||||
if ((currentTime.equals(beginLt) || currentTime.isAfter(beginLt)) && currentTime.isBefore(endLt)) {
|
||||
switch (period.getFlag()) {
|
||||
case TOP:
|
||||
return TOP_BYTE;
|
||||
case PEAK:
|
||||
return PEAK_BYTE;
|
||||
case FLAT:
|
||||
return FLAT_BYTE;
|
||||
case VALLEY:
|
||||
return VALLEY_BYTE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FLAT_BYTE; // 默认情况下返回平价
|
||||
}
|
||||
|
||||
protected static byte[] encodePileCode(String pileCode) {
|
||||
if (StringUtils.length(pileCode) > 32) {
|
||||
throw new IllegalArgumentException("云快充1.5可接受最大桩编号为14位");
|
||||
}
|
||||
|
||||
String pileCodeStr = StringUtils.leftPad(pileCode, 14, '0');
|
||||
|
||||
return BCDUtil.toBytes(pileCodeStr);
|
||||
}
|
||||
|
||||
protected static byte[] encodeGunCode(String gunCode) {
|
||||
if (StringUtils.length(gunCode) > 2) {
|
||||
throw new IllegalArgumentException("云快充1.5可接受最大枪编号为2位");
|
||||
}
|
||||
|
||||
String gunCodeStr = StringUtils.leftPad(gunCode, 2, '0');
|
||||
|
||||
return BCDUtil.toBytes(gunCodeStr);
|
||||
}
|
||||
|
||||
protected static byte[] encodeTradeNo(String tradeNo) {
|
||||
if (StringUtils.length(tradeNo) > 32) {
|
||||
throw new IllegalArgumentException("云快充1.5可接受最大交易流水号为32位");
|
||||
}
|
||||
|
||||
String tradeNoStr = StringUtils.leftPad(tradeNo, 32, '0');
|
||||
|
||||
return BCDUtil.toBytes(tradeNoStr);
|
||||
}
|
||||
|
||||
|
||||
protected byte[] encode(YunKuaiChongV150DownlinkCmdEnum downlinkCmd,
|
||||
int seqNo,
|
||||
int encryptionFlag,
|
||||
ByteBuf msgBody) {
|
||||
int msgBodyLength = msgBody.readableBytes();
|
||||
ByteBuf response = Unpooled.buffer(msgBodyLength + 6);
|
||||
response.writeByte(YUNKUAICHONG_HEAD);
|
||||
response.writeByte(msgBodyLength + 4);
|
||||
response.writeShortLE(seqNo);
|
||||
response.writeByte(encryptionFlag);
|
||||
response.writeByte(downlinkCmd.getCmd());
|
||||
response.writeBytes(msgBody);
|
||||
|
||||
// 帧校验域:从序列号域到数据域的 CRC 校验,校验多项式为 0x180D,低字节在前,高字节在后
|
||||
byte[] checkArr = new byte[msgBodyLength + 4];
|
||||
System.arraycopy(response.array(), 2, checkArr, 0, checkArr.length);
|
||||
|
||||
response.writeShortLE(crcSum(checkArr));
|
||||
|
||||
return toBytes(response);
|
||||
}
|
||||
|
||||
protected void encodeAndWriteFlush(YunKuaiChongV150DownlinkCmdEnum downlinkCmd,
|
||||
int seqNo,
|
||||
int encryptionFlag,
|
||||
ByteBuf msgBody,
|
||||
TcpSession tcpSession) {
|
||||
|
||||
byte[] encode = encode(downlinkCmd, seqNo, encryptionFlag, msgBody);
|
||||
|
||||
tcpSession.writeAndFlush(Unpooled.copiedBuffer(encode));
|
||||
}
|
||||
|
||||
protected void encodeAndWriteFlush(YunKuaiChongV150DownlinkCmdEnum downlinkCmd,
|
||||
ByteBuf msgBody,
|
||||
TcpSession tcpSession) {
|
||||
|
||||
byte[] encode = encode(downlinkCmd,
|
||||
tcpSession.nextSeqNo(SequenceNumberLength.SHORT),
|
||||
YUNKUAICHONG_NORMAL_ENCRYPTION_FLAG,
|
||||
msgBody);
|
||||
|
||||
tcpSession.writeAndFlush(Unpooled.copiedBuffer(encode));
|
||||
}
|
||||
|
||||
protected static BigDecimal reduceMagnification(long value, int magnification) {
|
||||
return new BigDecimal(value).divide(new BigDecimal(magnification), 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
protected static BigDecimal reduceMagnification(long value, int magnification, int scale) {
|
||||
return new BigDecimal(value).divide(new BigDecimal(magnification), scale, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong;
|
||||
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public abstract class YunKuaiChongDownlinkCmdExe extends AbstractYunKuaiChongCmdExe{
|
||||
|
||||
public abstract void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRestMessage;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
public class YunKuaiChongDwonlinkMessage 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 DownlinkRestMessage msg;
|
||||
|
||||
// 上行消息
|
||||
private YunKuaiChongUplinkMessage requestData;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong;
|
||||
|
||||
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 YunKuaiChongUplinkCmdExe extends AbstractYunKuaiChongCmdExe{
|
||||
|
||||
public abstract void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx);
|
||||
|
||||
protected static UplinkQueueMessage.Builder uplinkMessageBuilder(String messageKey, TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage) {
|
||||
return UplinkQueueMessage.newBuilder()
|
||||
.setMessageIdMSB(yunKuaiChongUplinkMessage.getId().getMostSignificantBits())
|
||||
.setMessageIdLSB(yunKuaiChongUplinkMessage.getId().getLeastSignificantBits())
|
||||
.setSessionIdMSB(tcpSession.getId().getMostSignificantBits())
|
||||
.setSessionIdLSB(tcpSession.getId().getLeastSignificantBits())
|
||||
.setRequestData(ByteString.copyFrom(JacksonUtil.writeValueAsBytes(yunKuaiChongUplinkMessage)))
|
||||
.setMessageKey(messageKey)
|
||||
.setProtocolName(tcpSession.getProtocolName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class YunKuaiChongUplinkMessage 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 YunKuaiChongUplinkMessage(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public YunKuaiChongUplinkMessage() {
|
||||
this(UUID.randomUUID());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface YunKuaiChongCmd {
|
||||
|
||||
byte value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150;
|
||||
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRestMessage;
|
||||
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.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static sanbing.jcpp.infrastructure.util.codec.ByteUtil.checkCrcSum;
|
||||
|
||||
@Slf4j
|
||||
public class YunKuaiChongV15ProtocolMessageProcessor extends ProtocolMessageProcessor {
|
||||
private final Map<Byte, YunKuaiChongUplinkCmdExe> UPLINK_CMD_EXE_MAP = new ConcurrentHashMap<>();
|
||||
private final Map<Byte, YunKuaiChongDownlinkCmdExe> DOWNLINK_CMD_EXE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public YunKuaiChongV15ProtocolMessageProcessor(Forwarder forwarder, ProtocolContext protocolContext) {
|
||||
super(forwarder, protocolContext);
|
||||
|
||||
Set<Class<?>> cmdClasses = ClassUtil.scanPackageByAnnotation(ClassUtil.getPackage(this.getClass()), YunKuaiChongCmd.class);
|
||||
cmdClasses.stream().filter(YunKuaiChongUplinkCmdExe.class::isAssignableFrom)
|
||||
.forEach(clazz -> {
|
||||
byte cmd = clazz.getAnnotation(YunKuaiChongCmd.class).value();
|
||||
try {
|
||||
YunKuaiChongUplinkCmdExe yunKuaiChongUplinkCmdExe = (YunKuaiChongUplinkCmdExe) clazz.getDeclaredConstructor().newInstance();
|
||||
UPLINK_CMD_EXE_MAP.put(cmd, yunKuaiChongUplinkCmdExe);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
|
||||
NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
cmdClasses.stream().filter(YunKuaiChongDownlinkCmdExe.class::isAssignableFrom)
|
||||
.forEach(clazz -> {
|
||||
byte cmd = clazz.getAnnotation(YunKuaiChongCmd.class).value();
|
||||
try {
|
||||
YunKuaiChongDownlinkCmdExe yunKuaiChongDownlinkCmdExe = (YunKuaiChongDownlinkCmdExe) clazz.getDeclaredConstructor().newInstance();
|
||||
DOWNLINK_CMD_EXE_MAP.put(cmd, yunKuaiChongDownlinkCmdExe);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
|
||||
NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uplinkHandle(ListenerToHandlerMsg listenerToHandlerMsg) {
|
||||
UUID msgId = listenerToHandlerMsg.id();
|
||||
byte[] msg = listenerToHandlerMsg.msg();
|
||||
TcpSession session = (TcpSession) listenerToHandlerMsg.session();
|
||||
|
||||
ByteBuf in = Unpooled.copiedBuffer(msg);
|
||||
|
||||
in.markReaderIndex();
|
||||
|
||||
findStartFlag(in);
|
||||
|
||||
// 判断是否可以读取报头,8个字节
|
||||
if (in.readableBytes() < 6) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
// 起始标识, 固定为0x68
|
||||
int startFlag = in.readUnsignedByte();
|
||||
if (startFlag != 0x68) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
// 数据长度 = 序列号域+加密标志+帧类型标志+消息体
|
||||
int dataLength = in.readUnsignedByte();
|
||||
|
||||
// 报文的流水号
|
||||
int seqNo = in.readUnsignedShortLE();
|
||||
|
||||
// 加密标志
|
||||
int encrpyFlag = in.readUnsignedByte();
|
||||
|
||||
// 帧类型标志
|
||||
int frameType = in.readUnsignedByte();
|
||||
|
||||
// 判断是否可以读取消息体,N-4个字节
|
||||
int msgBodyLength = dataLength - 4;
|
||||
if (in.readableBytes() < msgBodyLength) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
// 消息体
|
||||
byte[] msgBody = new byte[msgBodyLength];
|
||||
in.readBytes(msgBody);
|
||||
|
||||
// 判断是否可以读取校验和, 2个字节
|
||||
if (in.readableBytes() < 2) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] byCheckSum = new byte[2];
|
||||
in.readBytes(byCheckSum);
|
||||
ByteBuf csTemp = Unpooled.buffer();
|
||||
csTemp.writeBytes(byCheckSum);
|
||||
|
||||
// 校验校验和
|
||||
int checkSum = csTemp.readUnsignedShort();
|
||||
|
||||
byte[] checkData = new byte[dataLength];
|
||||
|
||||
System.arraycopy(msg, 2, checkData, 0, dataLength);
|
||||
|
||||
JCPPPair<Boolean, Integer> checkResult = checkCrcSum(checkData, checkSum);
|
||||
|
||||
if (!checkResult.getFirst()) {
|
||||
csTemp.writeBytes(byCheckSum);
|
||||
checkSum = csTemp.readUnsignedShortLE();
|
||||
checkResult = checkCrcSum(checkData, checkSum);
|
||||
log.info("云快充V1.5检验和 第二次检查: checkResult:{}, checkSum:{}", checkResult, checkSum);
|
||||
}
|
||||
|
||||
if (!checkResult.getFirst()) {
|
||||
log.info("云快充V1.5检验和不一致两次不通过 不处理! CMD:{},校验域:{},正确校验和:{}", frameType, checkSum, checkResult.getSecond());
|
||||
return;
|
||||
}
|
||||
|
||||
YunKuaiChongUplinkMessage message = new YunKuaiChongUplinkMessage(msgId);
|
||||
message.setHead(startFlag);
|
||||
message.setDataLength(dataLength);
|
||||
message.setSequenceNumber(seqNo);
|
||||
message.setEncryptionFlag(encrpyFlag);
|
||||
message.setCmd(frameType);
|
||||
message.setMsgBody(msgBody);
|
||||
message.setCheckSum(checkSum);
|
||||
message.setRawFrame(msg);
|
||||
|
||||
exeCmd(message, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downlinkHandle(SessionToHandlerMsg sessionToHandlerMsg) throws Exception {
|
||||
TcpSession session = (TcpSession) sessionToHandlerMsg.session();
|
||||
|
||||
DownlinkRestMessage protocolDownlinkMsg = sessionToHandlerMsg.downlinkMsg();
|
||||
|
||||
int cmd = YunKuaiChongV150DownlinkCmdEnum.valueOf(protocolDownlinkMsg.getDownlinkCmd()).getCmd();
|
||||
|
||||
YunKuaiChongDwonlinkMessage message = new YunKuaiChongDwonlinkMessage();
|
||||
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(), YunKuaiChongUplinkMessage.class));
|
||||
}
|
||||
|
||||
exeCmd(message, session);
|
||||
}
|
||||
|
||||
private void exeCmd(YunKuaiChongUplinkMessage message, TcpSession session) {
|
||||
YunKuaiChongUplinkCmdExe uplinkCmdExe = UPLINK_CMD_EXE_MAP.get((byte) message.getCmd());
|
||||
|
||||
if (uplinkCmdExe == null) {
|
||||
|
||||
log.info("[{}] 云快充V1.5协议接收到未知的上行指令 {}", session, message.getCmd());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uplinkCmdExe.execute(session, message, protocolContext);
|
||||
}
|
||||
|
||||
private void exeCmd(YunKuaiChongDwonlinkMessage message, TcpSession session) {
|
||||
YunKuaiChongDownlinkCmdExe downlinkCmdExe = DOWNLINK_CMD_EXE_MAP.get((byte) message.getCmd());
|
||||
|
||||
if (downlinkCmdExe == null) {
|
||||
|
||||
log.info("[{}] 云快充V1.5协议接收到未知的下行指令 {}", session, message.getCmd());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
downlinkCmdExe.execute(session, message, protocolContext);
|
||||
}
|
||||
|
||||
private static void findStartFlag(ByteBuf buf) {
|
||||
int count = buf.readableBytes();
|
||||
for (int index = buf.readerIndex(); index < count - 1; index++) {
|
||||
if (buf.getByte(index) == (byte) 0x68) {
|
||||
buf.readerIndex(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
buf.resetReaderIndex();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.util.annotation.ProtocolComponent;
|
||||
import sanbing.jcpp.protocol.ProtocolBootstrap;
|
||||
import sanbing.jcpp.protocol.ProtocolMessageProcessor;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.YunkuaichongV150ProtocolBootstrap.PROTOCOL_NAME;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
|
||||
@ProtocolComponent(PROTOCOL_NAME)
|
||||
@Slf4j
|
||||
public class YunkuaichongV150ProtocolBootstrap extends ProtocolBootstrap {
|
||||
|
||||
public static final String PROTOCOL_NAME = "yunkuaichongV150";
|
||||
|
||||
@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 YunKuaiChongV15ProtocolMessageProcessor(forwarder, protocolContext);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 充电握手
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x15)
|
||||
public class YunKuaiChongV150BmsHandshakeULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.debug("{} 云快充1.5.0充电握手", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
// 1.交易流水号
|
||||
byte[] tradeNoBytes = new byte[16];
|
||||
byteBuf.readBytes(tradeNoBytes);
|
||||
String tradeNo = BCDUtil.toString(tradeNoBytes);
|
||||
additionalInfo.put("交易流水号", tradeNo);
|
||||
|
||||
// 2.桩编号
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
additionalInfo.put("桩编号", tradeNo);
|
||||
|
||||
// 3.抢号
|
||||
byte gunCodeByte = byteBuf.readByte();
|
||||
String gunCode = BCDUtil.toString(gunCodeByte);
|
||||
additionalInfo.put("抢号", tradeNo);
|
||||
|
||||
// 4.BMS 通信协议版本号
|
||||
byte[] bmsConnectVersionBytes = new byte[3];
|
||||
byteBuf.readBytes(bmsConnectVersionBytes);
|
||||
additionalInfo.put("BMS 通信协议版本号", HexUtil.encodeHexStr(bmsConnectVersionBytes));
|
||||
|
||||
// 5.BMS 电池类型
|
||||
int bmsBatteryType = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("BMS电池类型", bmsBatteryType);
|
||||
|
||||
// 6.BMS 整车动力蓄电池系统额定容量
|
||||
int bmsPowerCapacity = byteBuf.readUnsignedShortLE();
|
||||
additionalInfo.put("BMS整车动力蓄电池系统额定容量", bmsPowerCapacity);
|
||||
|
||||
// 7.BMS 整车动力蓄电池系统额定总电压
|
||||
int bmsPowerMaxVoltage = byteBuf.readUnsignedShortLE();
|
||||
additionalInfo.put("BMS整车动力蓄电池系统额定总电压", bmsPowerMaxVoltage);
|
||||
|
||||
// 8.BMS 电池生产厂商名称
|
||||
byte[] bmsFactoryBytes = new byte[4];
|
||||
byteBuf.readBytes(bmsFactoryBytes);
|
||||
String bmsFactory = new String(bmsFactoryBytes, StandardCharsets.US_ASCII);
|
||||
additionalInfo.put("BMS电池生产厂商名称", bmsFactory);
|
||||
|
||||
// 9.BMS 电池组序号
|
||||
int bmsSerialNo = byteBuf.readIntLE();
|
||||
additionalInfo.put("BMS 电池组序号", bmsSerialNo);
|
||||
|
||||
// 10.BMS 电池组生产日期年
|
||||
int bmsCreateYear = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("BMS 电池组生产日期年", bmsCreateYear);
|
||||
|
||||
// 11.BMS 电池组生产日期月
|
||||
int bmsCreateMonth = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("BMS 电池组生产日期月", bmsCreateMonth);
|
||||
|
||||
// 12.BMS 电池组生产日期日
|
||||
int bmsCreateDay = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("BMS 电池组生产日期日", bmsCreateDay);
|
||||
|
||||
// 13.BMS 电池组充电次数
|
||||
int bmsChargeCount = byteBuf.readUnsignedMedium();
|
||||
additionalInfo.put("BMS 电池组充电次数", bmsChargeCount);
|
||||
|
||||
// 14.BMS 电池组产权标识
|
||||
int bmsPropertyRightLabel = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("BMS 电池组产权标识", bmsPropertyRightLabel);
|
||||
|
||||
// 15.预留位
|
||||
byteBuf.skipBytes(1);
|
||||
|
||||
// 16.BMS 车辆识别码
|
||||
byte[] carVINBytes = new byte[17];
|
||||
byteBuf.readBytes(carVINBytes);
|
||||
String bmsVinCode = new String(carVINBytes, StandardCharsets.US_ASCII);
|
||||
additionalInfo.put("电动汽车唯一标识", bmsVinCode);
|
||||
|
||||
// 17.BMS 软件版本号
|
||||
byte[] bmsSoftVersionBytes = new byte[8];
|
||||
byteBuf.readBytes(bmsSoftVersionBytes);
|
||||
additionalInfo.put("BMS 软件版本号", HexUtil.encodeHexStr(bmsSoftVersionBytes));
|
||||
|
||||
// TODO 先打印日志,暂不转发
|
||||
log.debug("{} 云快充1.5.0充电握手信息解析完成:{}", tcpSession, additionalInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.HeartBeatRequest;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.HEARTBEAT;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 充电桩心跳包
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x03)
|
||||
public class YunKuaiChongV150HeartbeatULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.debug("{} 云快充1.5.0充电桩心跳包", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
byte gunCodeByte = byteBuf.readByte();
|
||||
int gunCode = Integer.parseInt(BCDUtil.toString(gunCodeByte));
|
||||
additionalInfo.put("枪号", gunCode);
|
||||
|
||||
int gunState = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("枪状态(0正常 1故障)", gunState);
|
||||
|
||||
// 刷新前置会话
|
||||
ctx.getProtocolSessionRegistryProvider().activate(tcpSession);
|
||||
|
||||
// 转发到后端
|
||||
HeartBeatRequest heartBeatRequest = HeartBeatRequest.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setRemoteAddress(tcpSession.getAddress().toString())
|
||||
.setNodeId(ctx.getServiceInfoProvider().getServiceId())
|
||||
.setNodeWebapiIpPort(ctx.getServiceInfoProvider().getServiceWebapiEndpoint())
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(heartBeatRequest.getPileCode(), tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setHeartBeatRequest(heartBeatRequest)
|
||||
.build();
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
|
||||
pingAck(tcpSession, yunKuaiChongUplinkMessage, pileCodeBytes, gunCodeByte);
|
||||
}
|
||||
|
||||
private void pingAck(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, byte[] pileCodeBytes, byte gunCodeByte) {
|
||||
ByteBuf pingAckMsgBody = Unpooled.buffer(9);
|
||||
pingAckMsgBody.writeBytes(pileCodeBytes);
|
||||
pingAckMsgBody.writeByte(gunCodeByte);
|
||||
pingAckMsgBody.writeByte(0);
|
||||
|
||||
encodeAndWriteFlush(HEARTBEAT,
|
||||
pingAckMsgBody,
|
||||
tcpSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.time.Instant;
|
||||
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.yunkuaichong.YunKuaiChongDwonlinkMessage.FAILURE_BYTE;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.SUCCESS_BYTE;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.LOGIN_ACK;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.SYNC_TIME;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0登录认证应答
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x02)
|
||||
public class YunKuaiChongV150LoginAckDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0登录认证应答", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasLoginResponse()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LoginResponse loginResponse = yunKuaiChongDwonlinkMessage.getMsg().getLoginResponse();
|
||||
|
||||
YunKuaiChongUplinkMessage requestData = JacksonUtil.fromBytes(yunKuaiChongDwonlinkMessage.getMsg().getRequestData().toByteArray(), YunKuaiChongUplinkMessage.class);
|
||||
|
||||
// 获取上行报文
|
||||
byte[] uplinkRawFrame = requestData.getRawFrame();
|
||||
// 从上行报文中取出桩编号字节数组
|
||||
byte[] pileCodeBytes = Arrays.copyOfRange(uplinkRawFrame, 6, 13);
|
||||
|
||||
if (loginResponse.getSuccess()) {
|
||||
|
||||
// 构造并下发登录ACK
|
||||
loginAck(tcpSession, pileCodeBytes, requestData, true);
|
||||
|
||||
// 构造定时对时
|
||||
registerSyncTimeTask(tcpSession, pileCodeBytes, requestData);
|
||||
|
||||
} else {
|
||||
|
||||
log.info("云快充V1.5登录认证失败,服务端断开连接。 pileCode:{}", loginResponse.getPileCode());
|
||||
|
||||
// 构造并下发登录ACK
|
||||
loginAck(tcpSession, pileCodeBytes, requestData, false);
|
||||
|
||||
// 断开连接
|
||||
tcpSession.close(MANUALLY);
|
||||
}
|
||||
}
|
||||
|
||||
private void loginAck(TcpSession tcpSession, byte[] pileCodeBytes, YunKuaiChongUplinkMessage requestData, boolean loginSuccess) {
|
||||
// 创建ACK消息体7字节桩编号+1字节登录结果
|
||||
ByteBuf loginAckMsgBody = Unpooled.buffer(8);
|
||||
loginAckMsgBody.writeBytes(pileCodeBytes);
|
||||
loginAckMsgBody.writeByte(loginSuccess ? SUCCESS_BYTE : FAILURE_BYTE);
|
||||
|
||||
encodeAndWriteFlush(LOGIN_ACK,
|
||||
requestData.getSequenceNumber(),
|
||||
requestData.getEncryptionFlag(),
|
||||
loginAckMsgBody,
|
||||
tcpSession);
|
||||
}
|
||||
|
||||
private void registerSyncTimeTask(TcpSession tcpSession, byte[] pileCodeBytes, YunKuaiChongUplinkMessage requestData) {
|
||||
tcpSession.addSchedule("auto-sync-time", k -> {
|
||||
log.info("{} 云快充1.5.0开始注册定时对时任务", tcpSession);
|
||||
return PROTOCOL_SESSION_SCHEDULED.scheduleAtFixedRate(() ->
|
||||
syncTime(tcpSession, pileCodeBytes, requestData),
|
||||
0, 8, TimeUnit.HOURS);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void syncTime(TcpSession tcpSession, byte[] pileCodeBytes, YunKuaiChongUplinkMessage requestData) {
|
||||
TracerContextUtil.newTracer();
|
||||
MDCUtils.recordTracer();
|
||||
log.info("{} 云快充1.5.0开始下发对时报文", tcpSession);
|
||||
ByteBuf syncTimeMsgBody = Unpooled.buffer(14);
|
||||
syncTimeMsgBody.writeBytes(pileCodeBytes);
|
||||
syncTimeMsgBody.writeBytes(CP56Time2aUtil.encode(Instant.now()));
|
||||
|
||||
encodeAndWriteFlush(SYNC_TIME,
|
||||
tcpSession.nextSeqNo(SequenceNumberLength.SHORT),
|
||||
requestData.getEncryptionFlag(),
|
||||
syncTimeMsgBody,
|
||||
tcpSession);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.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.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0充电桩登录认证
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x01)
|
||||
public class YunKuaiChongV150LoginULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0登录认证请求", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
int pileType = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("桩类型(0直流1交流)", pileType);
|
||||
|
||||
int gunsNum = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("充电枪数量", gunsNum);
|
||||
additionalInfo.put("通信协议版本", byteBuf.readUnsignedByte());
|
||||
byte[] bytes = new byte[8];
|
||||
byteBuf.readBytes(bytes);
|
||||
additionalInfo.put("程序版本", new String(bytes, StandardCharsets.US_ASCII));
|
||||
additionalInfo.put("网络链接类型", byteBuf.readUnsignedByte());
|
||||
|
||||
byte[] simB = new byte[10];
|
||||
byteBuf.readBytes(simB);
|
||||
String sim = BCDUtil.toString(simB);
|
||||
additionalInfo.put("Sim卡", sim);
|
||||
additionalInfo.put("运营商", byteBuf.readUnsignedByte());
|
||||
|
||||
tcpSession.addPileCode(pileCode);
|
||||
|
||||
// 注册前置会话
|
||||
ctx.getProtocolSessionRegistryProvider().register(tcpSession);
|
||||
|
||||
// 转发到后端
|
||||
LoginRequest loginRequest = LoginRequest.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setRemoteAddress(tcpSession.getAddress().toString())
|
||||
.setNodeId(ctx.getServiceInfoProvider().getServiceId())
|
||||
.setNodeWebapiIpPort(ctx.getServiceInfoProvider().getServiceWebapiEndpoint())
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(loginRequest.getPileCode(), tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setLoginRequest(loginRequest)
|
||||
.build();
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.FlagPriceProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.PeriodProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.PricingModelProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.QueryPricingResponse;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelFlag.*;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.QUERY_PRICING_ACK;
|
||||
|
||||
/**
|
||||
* 计费模型请求应答
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x0A)
|
||||
public class YunKuaiChongV150QueryPricingModelAckDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0计费模型请求应答", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasQueryPricingResponse()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QueryPricingResponse queryPricingResponse = yunKuaiChongDwonlinkMessage.getMsg().getQueryPricingResponse();
|
||||
|
||||
YunKuaiChongUplinkMessage requestData = JacksonUtil.fromBytes(yunKuaiChongDwonlinkMessage.getMsg().getRequestData().toByteArray(), YunKuaiChongUplinkMessage.class);
|
||||
|
||||
long pricingId = queryPricingResponse.getPricingId();
|
||||
String pileCode = queryPricingResponse.getPileCode();
|
||||
PricingModelProto pricingModel = queryPricingResponse.getPricingModel();
|
||||
Map<Integer, FlagPriceProto> flagPriceMap = pricingModel.getFlagPriceMap();
|
||||
List<PeriodProto> periodList = pricingModel.getPeriodList();
|
||||
|
||||
// 从上行报文中取出桩编号字节数组
|
||||
byte[] pileCodeBytes = encodePileCode(pileCode);
|
||||
|
||||
// 创建ACK消息体7字节桩编号+2字节计费模型编号+4x4x2字节尖峰平谷电价和服务费+1字节计损比例+48字节时段标识
|
||||
ByteBuf queryPricingAckMsgBody = Unpooled.buffer(90);
|
||||
queryPricingAckMsgBody.writeBytes(pileCodeBytes);
|
||||
queryPricingAckMsgBody.writeBytes(encodePricingId(pricingId));
|
||||
|
||||
// 4字节电价+4字节服务费
|
||||
BigDecimal accurate = new BigDecimal(1000);
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(TOP.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(TOP.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(PEAK.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(PEAK.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(FLAT.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(FLAT.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(VALLEY.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
queryPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(VALLEY.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
|
||||
// 计损比例
|
||||
queryPricingAckMsgBody.writeByte(0);
|
||||
|
||||
// 48段半小时
|
||||
byte[] bytes = new byte[48];
|
||||
LocalTime currentTime = LocalTime.MIDNIGHT;
|
||||
for (int i = 0; i < 48; i++) {
|
||||
bytes[i] = getFlagForCurrentTime(periodList, currentTime);
|
||||
currentTime = currentTime.plusMinutes(30); // 每次时间增加30分钟
|
||||
}
|
||||
queryPricingAckMsgBody.writeBytes(bytes);
|
||||
|
||||
encodeAndWriteFlush(QUERY_PRICING_ACK,
|
||||
queryPricingAckMsgBody,
|
||||
tcpSession);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.QueryPricingRequest;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0充电桩计费模型请求
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x09)
|
||||
public class YunKuaiChongV150QueryPricingModelULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0充电桩计费模型请求", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
// 转发到后端
|
||||
QueryPricingRequest queryPricingRequest = QueryPricingRequest.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(queryPricingRequest.getPileCode(), tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setQueryPricingRequest(queryPricingRequest)
|
||||
.build();
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.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.infrastructure.util.trace.TracerContextUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.ChargingProgressProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatus;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatusProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0上传实时监测数据
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x13)
|
||||
public class YunKuaiChongV150RealTimeDataULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
|
||||
// 故障说明列表
|
||||
private static final String[] faultDescriptions = {
|
||||
"急停按钮动作故障", // Bit 1
|
||||
"无可用整流模块", // Bit 2
|
||||
"出风口温度过高", // Bit 3
|
||||
"交流防雷故障", // Bit 4
|
||||
"交直流模块 DC20 通信中断", // Bit 5
|
||||
"绝缘检测模块 FC08 通信中断", // Bit 6
|
||||
"电度表通信中断", // Bit 7
|
||||
"读卡器通信中断", // Bit 8
|
||||
"RC10 通信中断", // Bit 9
|
||||
"风扇调速板故障", // Bit 10
|
||||
"直流熔断器故障", // Bit 11
|
||||
"高压接触器故障", // Bit 12
|
||||
"门打开" // Bit 13
|
||||
};
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0上传实时监测数据", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
// 从Tracer总获取当前时间
|
||||
long ts = TracerContextUtil.getCurrentTracer().getTracerTs();
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
// 1.交易流水号
|
||||
byte[] tradeNoBytes = new byte[16];
|
||||
byteBuf.readBytes(tradeNoBytes);
|
||||
String tradeNo = decodeTradeNo(tradeNoBytes);
|
||||
|
||||
// 2.桩编号
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
// 3.抢号
|
||||
byte gunCodeByte = byteBuf.readByte();
|
||||
String gunCode = BCDUtil.toString(gunCodeByte);
|
||||
|
||||
// 4.状态 0x00:离线 0x01:故障 0x02:空闲 0x03:充电
|
||||
int gunStatus = byteBuf.readUnsignedByte();
|
||||
|
||||
// 5.枪是否归位 0x00:否 0x01:是 0x02:未知
|
||||
int gunHoming = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("枪是否归位(0否1是)", gunHoming);
|
||||
|
||||
// 6.是否插枪 0x00:否 0x01:是
|
||||
int gunInsert = byteBuf.readUnsignedByte();
|
||||
|
||||
// 7.输出电压
|
||||
BigDecimal outputVoltage = reduceMagnification(byteBuf.readUnsignedShortLE(), 10);
|
||||
|
||||
// 8.输出电流
|
||||
BigDecimal outputCurrent = reduceMagnification(byteBuf.readUnsignedShortLE(), 10);
|
||||
|
||||
// 9.枪线温度
|
||||
short gunLineTemperature = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("枪线温度", gunLineTemperature);
|
||||
|
||||
// 10.枪线编码
|
||||
long gunLineCode = byteBuf.readLongLE();
|
||||
additionalInfo.put("枪线编码", gunLineCode);
|
||||
|
||||
// 11.soc
|
||||
int soc = byteBuf.readUnsignedByte();
|
||||
|
||||
// 12.电池组最高温度
|
||||
short maxBatteryTemperature = byteBuf.readUnsignedByte();
|
||||
additionalInfo.put("电池组最高温度", maxBatteryTemperature);
|
||||
|
||||
// 13.累计充电时间(分钟)
|
||||
int totalChargeTime = byteBuf.readUnsignedShortLE();
|
||||
|
||||
// 14.剩余时间(分钟)
|
||||
int remainMin = byteBuf.readUnsignedShortLE();
|
||||
additionalInfo.put("剩余时间", remainMin);
|
||||
|
||||
//15.充电度数(kWh)
|
||||
BigDecimal chargeEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000, 4);
|
||||
additionalInfo.put("充电度数", chargeEnergy);
|
||||
|
||||
//16.计损充电度数(kWh)
|
||||
BigDecimal loseEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000, 4);
|
||||
|
||||
// 17.已充金额 (电费+服务费)*计损充电度数
|
||||
BigDecimal chargeAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100);
|
||||
|
||||
// 18.硬件故障 测试发现需要使用小端计算bit, 然后对照故障表查询故障码
|
||||
byte[] warnCodeBytes = new byte[2];
|
||||
byteBuf.readBytes(warnCodeBytes);
|
||||
// 解析 14 个比特位
|
||||
List<String> faults = getFaultDescriptions(parseFaults(warnCodeBytes));
|
||||
|
||||
// 抢状态
|
||||
GunRunStatus gunRunStatus = parseGUnRunStatus(gunStatus, gunInsert, tradeNo);
|
||||
GunRunStatusProto.Builder gunRunStatusProtoBuilder = GunRunStatusProto.newBuilder()
|
||||
.setTs(ts)
|
||||
.setPileCode(pileCode)
|
||||
.setGunCode(gunCode)
|
||||
.setGunRunStatus(gunRunStatus)
|
||||
.addAllFaultMessages(faults)
|
||||
.setAdditionalInfo(additionalInfo.toString());
|
||||
|
||||
// 转发到后端
|
||||
UplinkQueueMessage gunRunStatusMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setGunRunStatusProto(gunRunStatusProtoBuilder)
|
||||
.build();
|
||||
|
||||
tcpSession.getForwarder().sendMessage(gunRunStatusMessage);
|
||||
|
||||
if (StringUtils.isNotBlank(tradeNo)) {
|
||||
|
||||
// 充电进度
|
||||
ChargingProgressProto.Builder chargingProgressProtoBuilder = ChargingProgressProto.newBuilder()
|
||||
.setTs(ts)
|
||||
.setPileCode(pileCode)
|
||||
.setGunCode(gunCode)
|
||||
.setTradeNo(tradeNo)
|
||||
.setOutputVoltage(outputVoltage.floatValue())
|
||||
.setOutputCurrent(outputCurrent.floatValue())
|
||||
.setSoc(soc)
|
||||
.setTotalChargingDurationMin(totalChargeTime)
|
||||
.setTotalChargingEnergyKWh(loseEnergy.floatValue())
|
||||
.setTotalChargingCostCent(chargeAmount.longValue())
|
||||
.setAdditionalInfo(additionalInfo.toString());
|
||||
|
||||
UplinkQueueMessage chargingProgressMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setChargingProgressProto(chargingProgressProtoBuilder)
|
||||
.build();
|
||||
|
||||
tcpSession.getForwarder().sendMessage(chargingProgressMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 解释枪运行状态
|
||||
*/
|
||||
private static GunRunStatus parseGUnRunStatus(int gunStatus, int gunInsert, String tradeNo) {
|
||||
GunRunStatus gunRunStatus = GunRunStatus.UNKNOWN;
|
||||
if (gunStatus == 0) {
|
||||
gunRunStatus = GunRunStatus.FAULT;
|
||||
} else if (gunStatus == 1) {
|
||||
gunRunStatus = GunRunStatus.FAULT;
|
||||
} else if (gunStatus == 2) {
|
||||
gunRunStatus = GunRunStatus.IDLE;
|
||||
if (gunInsert == 1) {
|
||||
gunRunStatus = GunRunStatus.INSERTED;
|
||||
}
|
||||
} else if (gunStatus == 3) {
|
||||
if (StringUtils.isBlank(tradeNo)) {
|
||||
gunRunStatus = GunRunStatus.INSERTED;
|
||||
} else {
|
||||
gunRunStatus = GunRunStatus.CHARGING;
|
||||
}
|
||||
}
|
||||
return gunRunStatus;
|
||||
}
|
||||
|
||||
|
||||
public static boolean[] parseFaults(byte[] bytes) {
|
||||
// 确保输入有效
|
||||
if (bytes.length != 2) {
|
||||
throw new IllegalArgumentException("输入 byte 数组长度不为 2");
|
||||
}
|
||||
|
||||
// 创建一个布尔数组来存储故障状态
|
||||
boolean[] faults = new boolean[14];
|
||||
|
||||
// 读取每个比特并设置到布尔数组中
|
||||
for (int i = 0; i < 14; i++) {
|
||||
// 计算对应的字节和比特位置
|
||||
int byteIndex = i / 8; // 字节索引
|
||||
int bitIndex = i % 8; // 比特索引
|
||||
|
||||
// 使用位运算检查该比特位
|
||||
faults[i] = ((bytes[byteIndex] >> bitIndex) & 1) == 1; // 如果为 1 则故障
|
||||
}
|
||||
|
||||
return faults;
|
||||
}
|
||||
|
||||
public static List<String> getFaultDescriptions(boolean[] faults) {
|
||||
List<String> faultList = new ArrayList<>();
|
||||
|
||||
// 遍历布尔数组,筛选出有故障的说明
|
||||
for (int i = 0; i < faults.length; i++) {
|
||||
if (faults[i]) {
|
||||
faultList.add(faultDescriptions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 转换 List 为数组并返回
|
||||
return faultList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
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.proto.gen.ProtocolProto.RemoteStartChargingRequest;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.REMOTE_START_CHARGING;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 运营平台远程控制启机
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x34)
|
||||
public class YunKuaiChongV150RemoteStartDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0运营平台远程控制启机", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasRemoteStartChargingRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteStartChargingRequest remoteStartChargingRequest = yunKuaiChongDwonlinkMessage.getMsg().getRemoteStartChargingRequest();
|
||||
String pileCode = remoteStartChargingRequest.getPileCode();
|
||||
String gunCode = remoteStartChargingRequest.getGunCode();
|
||||
String tradeNo = remoteStartChargingRequest.getTradeNo();
|
||||
int limitCent = remoteStartChargingRequest.getLimitCent();
|
||||
|
||||
byte[] cardNo = encodeCardNo(tradeNo);
|
||||
|
||||
ByteBuf msgBody = Unpooled.buffer(44);
|
||||
// 交易流水号
|
||||
msgBody.writeBytes(encodeTradeNo(tradeNo));
|
||||
// 桩编码
|
||||
msgBody.writeBytes(encodePileCode(pileCode));
|
||||
// 枪号
|
||||
msgBody.writeBytes(encodeGunCode(gunCode));
|
||||
// 逻辑卡号 BCD码
|
||||
msgBody.writeBytes(cardNo);
|
||||
// 物理卡号
|
||||
msgBody.writeBytes(cardNo);
|
||||
// 账户余额
|
||||
msgBody.writeIntLE(limitCent);
|
||||
|
||||
encodeAndWriteFlush(REMOTE_START_CHARGING,
|
||||
msgBody,
|
||||
tcpSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用交易流水号做卡号
|
||||
*/
|
||||
private static byte[] encodeCardNo(String tradeNo) {
|
||||
tradeNo = StringUtils.right(tradeNo, 16);
|
||||
tradeNo = StringUtils.leftPad(tradeNo, 16, '0');
|
||||
return BCDUtil.toBytes(tradeNo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStartChargingResponse;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 远程启动充电命令回复
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x33)
|
||||
public class YunKuaiChongV150RemoteStartResultULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0远程启动充电命令回复", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
// 从Tracer总获取当前时间
|
||||
long ts = TracerContextUtil.getCurrentTracer().getTracerTs();
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
// 1.交易流水号
|
||||
byte[] tradeNoBytes = new byte[16];
|
||||
byteBuf.readBytes(tradeNoBytes);
|
||||
String tradeNo = decodeTradeNo(tradeNoBytes);
|
||||
|
||||
// 2.桩编号
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
// 3.抢号
|
||||
byte gunCodeByte = byteBuf.readByte();
|
||||
String gunCode = BCDUtil.toString(gunCodeByte);
|
||||
|
||||
// 4.命令执行结果 0x00失败 0x01成功
|
||||
boolean isSuccess = (byteBuf.readByte() == 0x01);
|
||||
|
||||
// 5.失败原因 0无 1设备编码不匹配 2枪已在充电 3设备故障 4设备离线 5未插枪
|
||||
byte failReasonByte = byteBuf.readByte();
|
||||
String failReason = mapFailCode(failReasonByte);
|
||||
|
||||
RemoteStartChargingResponse remoteStartChargingResponse = RemoteStartChargingResponse.newBuilder()
|
||||
.setTs(ts)
|
||||
.setPileCode(pileCode)
|
||||
.setGunCode(gunCode)
|
||||
.setTradeNo(tradeNo)
|
||||
.setSuccess(isSuccess)
|
||||
.setFailReason(failReason)
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
|
||||
// 转发到后端
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setRemoteStartChargingResponse(remoteStartChargingResponse)
|
||||
.build();
|
||||
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
}
|
||||
|
||||
public static String mapFailCode(byte failCode) {
|
||||
return switch (failCode) {
|
||||
case 0x00 -> "无";
|
||||
case 0x01 -> "设备编号不匹配";
|
||||
case 0x02 -> "枪已在充电";
|
||||
case 0x03 -> "设备故障";
|
||||
case 0x04 -> "设备离线";
|
||||
case 0x05 -> "未插枪";
|
||||
case 0x33 -> "充电失败"; // 充电失败或其他相关信息
|
||||
case 0x34 -> "待启充"; // 示例,处理收到充电命令
|
||||
default -> "未知错误代码";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStopChargingRequest;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.REMOTE_START_CHARGING;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 运营平台远程停机
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x36)
|
||||
public class YunKuaiChongV150RemoteStopDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0运营平台远程停机", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasRemoteStopChargingRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteStopChargingRequest remoteStopChargingRequest = yunKuaiChongDwonlinkMessage.getMsg().getRemoteStopChargingRequest();
|
||||
String pileCode = remoteStopChargingRequest.getPileCode();
|
||||
String gunCode = remoteStopChargingRequest.getGunCode();
|
||||
|
||||
ByteBuf msgBody = Unpooled.buffer(44);
|
||||
// 桩编码
|
||||
msgBody.writeBytes(encodePileCode(pileCode));
|
||||
// 枪号
|
||||
msgBody.writeBytes(encodeGunCode(gunCode));
|
||||
|
||||
encodeAndWriteFlush(REMOTE_START_CHARGING,
|
||||
msgBody,
|
||||
tcpSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStopChargingResponse;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 远程停机命令回复
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x35)
|
||||
public class YunKuaiChongV150RemoteStopResultULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0远程停机命令回复", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
// 从Tracer总获取当前时间
|
||||
long ts = TracerContextUtil.getCurrentTracer().getTracerTs();
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
// 1.桩编号
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
// 2.抢号
|
||||
byte gunCodeByte = byteBuf.readByte();
|
||||
String gunCode = BCDUtil.toString(gunCodeByte);
|
||||
|
||||
// 3.命令执行结果 0x00失败 0x01成功
|
||||
boolean isSuccess = (byteBuf.readByte() == 0x01);
|
||||
|
||||
// 4.失败原因 0无 1设备编码不匹配 2枪已在充电 3设备故障 4设备离线 5未插枪
|
||||
byte failReasonByte = byteBuf.readByte();
|
||||
String failReason = mapFailCode(failReasonByte);
|
||||
|
||||
RemoteStopChargingResponse remoteStopChargingResponse = RemoteStopChargingResponse.newBuilder()
|
||||
.setTs(ts)
|
||||
.setPileCode(pileCode)
|
||||
.setGunCode(gunCode)
|
||||
.setSuccess(isSuccess)
|
||||
.setFailReason(failReason)
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
|
||||
// 转发到后端
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setRemoteStopChargingResponse(remoteStopChargingResponse)
|
||||
.build();
|
||||
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
}
|
||||
|
||||
public static String mapFailCode(byte failCode) {
|
||||
return switch (failCode) {
|
||||
case 0x00 -> "无";
|
||||
case 0x01 -> "设备编号不匹配";
|
||||
case 0x02 -> "枪未处于充电状态";
|
||||
case 0x03 -> "其他";
|
||||
default -> "未知错误"; // 可以根据需求自定义
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
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.proto.gen.ProtocolProto.SetPricingResponse;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.QUERY_PRICING_ACK;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 计费模型应答
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x57)
|
||||
public class YunKuaiChongV150SetPricingModelAckULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0计费模型应答", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
// 1.桩编号
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
// 2.设置结果 0x00:失败 0x01:成功
|
||||
boolean isSuccess = (byteBuf.readByte() == 0x01);
|
||||
|
||||
// 从缓存取上个请求的pricingId
|
||||
Object pricingId = tcpSession.getRequestCache().asMap().getOrDefault(pileCode + QUERY_PRICING_ACK.getCmd(), null);
|
||||
|
||||
if (pricingId instanceof Long pricingIdL) {
|
||||
// 转发到后端
|
||||
SetPricingResponse setPricingResponse = SetPricingResponse.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setSuccess(isSuccess)
|
||||
.setPricingId(pricingIdL)
|
||||
.build();
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(setPricingResponse.getPileCode(), tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setSetPricingResponse(setPricingResponse)
|
||||
.build();
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.FlagPriceProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.PeriodProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.PricingModelProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.SetPricingRequest;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelFlag.*;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.QUERY_PRICING_ACK;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.SET_PRICING;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 计费模型设置
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x58)
|
||||
public class YunKuaiChongV150SetPricingModelDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0计费模型设置", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasSetPricingRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetPricingRequest setPricingRequest = yunKuaiChongDwonlinkMessage.getMsg().getSetPricingRequest();
|
||||
|
||||
long pricingId = setPricingRequest.getPricingId();
|
||||
String pileCode = setPricingRequest.getPileCode();
|
||||
PricingModelProto pricingModel = setPricingRequest.getPricingModel();
|
||||
Map<Integer, FlagPriceProto> flagPriceMap = pricingModel.getFlagPriceMap();
|
||||
List<PeriodProto> periodList = pricingModel.getPeriodList();
|
||||
|
||||
// 反转取出桩编号字节数组
|
||||
byte[] pileCodeBytes = encodePileCode(pileCode);
|
||||
|
||||
// 创建ACK消息体7字节桩编号+2字节计费模型编号+4x4x2字节尖峰平谷电价和服务费+1字节计损比例+48字节时段标识
|
||||
ByteBuf setPricingAckMsgBody = Unpooled.buffer(90);
|
||||
setPricingAckMsgBody.writeBytes(pileCodeBytes);
|
||||
setPricingAckMsgBody.writeBytes(encodePricingId(pricingId));
|
||||
|
||||
// 4字节电价+4字节服务费
|
||||
BigDecimal accurate = new BigDecimal(1000);
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(TOP.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(TOP.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(PEAK.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(PEAK.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(FLAT.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(FLAT.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(VALLEY.ordinal()).getElec()).multiply(accurate).intValue());
|
||||
setPricingAckMsgBody.writeIntLE(new BigDecimal(flagPriceMap.get(VALLEY.ordinal()).getServ()).multiply(accurate).intValue());
|
||||
|
||||
// 计损比例
|
||||
setPricingAckMsgBody.writeByte(0);
|
||||
|
||||
// 48段半小时
|
||||
byte[] bytes = new byte[48];
|
||||
LocalTime currentTime = LocalTime.MIDNIGHT;
|
||||
for (int i = 0; i < 48; i++) {
|
||||
bytes[i] = getFlagForCurrentTime(periodList, currentTime);
|
||||
currentTime = currentTime.plusMinutes(30); // 每次时间增加30分钟
|
||||
}
|
||||
setPricingAckMsgBody.writeBytes(bytes);
|
||||
|
||||
// 放进缓存后再下发
|
||||
tcpSession.getRequestCache().put(pileCode + QUERY_PRICING_ACK.getCmd(), Long.valueOf(pricingId));
|
||||
|
||||
encodeAndWriteFlush(SET_PRICING,
|
||||
setPricingAckMsgBody,
|
||||
tcpSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.TransactionRecordAck;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.FAILURE_BYTE;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.SUCCESS_BYTE;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.VERIFY_PRICING_ACK;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 交易记录确认
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x40)
|
||||
public class YunKuaiChongV150TransactionRecordAckDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0交易记录确认", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasTransactionRecordAck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionRecordAck transactionRecordAck = yunKuaiChongDwonlinkMessage.getMsg().getTransactionRecordAck();
|
||||
|
||||
YunKuaiChongUplinkMessage requestData = JacksonUtil.fromBytes(yunKuaiChongDwonlinkMessage.getMsg().getRequestData().toByteArray(), YunKuaiChongUplinkMessage.class);
|
||||
|
||||
// 创建ACK消息体16字节交易流水号 + 1字节确认结果
|
||||
ByteBuf msgBody = Unpooled.buffer(17);
|
||||
msgBody.writeBytes(encodeTradeNo(transactionRecordAck.getTradeNo()));
|
||||
msgBody.writeByte(transactionRecordAck.getSuccess() ? SUCCESS_BYTE : FAILURE_BYTE);
|
||||
|
||||
encodeAndWriteFlush(VERIFY_PRICING_ACK,
|
||||
requestData.getSequenceNumber(),
|
||||
requestData.getEncryptionFlag(),
|
||||
msgBody,
|
||||
tcpSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.proto.gen.ProtocolProto.TransactionRecord;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0 交易记录
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x3B)
|
||||
public class YunKuaiChongV150TransactionRecordULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0交易记录", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
// 1.交易流水号
|
||||
byte[] tradeNoBytes = new byte[16];
|
||||
byteBuf.readBytes(tradeNoBytes);
|
||||
String tradeNo = decodeTradeNo(tradeNoBytes);
|
||||
|
||||
// 2.桩编号
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
// 3.抢号
|
||||
byte gunCodeByte = byteBuf.readByte();
|
||||
String gunCode = BCDUtil.toString(gunCodeByte);
|
||||
|
||||
// 4.开始时间
|
||||
byte[] startTimeBytes = new byte[7];
|
||||
byteBuf.readBytes(startTimeBytes);
|
||||
Instant startTime = CP56Time2aUtil.decode(startTimeBytes);
|
||||
|
||||
// 5.结束时间
|
||||
byte[] endTimeBytes = new byte[7];
|
||||
byteBuf.readBytes(endTimeBytes);
|
||||
Instant endTime = CP56Time2aUtil.decode(endTimeBytes);
|
||||
|
||||
// 6.尖单价
|
||||
BigDecimal topPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000);
|
||||
additionalInfo.put("尖单价", topPrice);
|
||||
// 7. 尖电量
|
||||
BigDecimal topEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
// 8.计损尖电量
|
||||
BigDecimal topLoseEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
additionalInfo.put("计损尖电量", topLoseEnergy);
|
||||
// 9.尖金额
|
||||
BigDecimal topAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100);
|
||||
|
||||
// 10.峰单价
|
||||
BigDecimal peakPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000);
|
||||
additionalInfo.put("峰单价", peakPrice);
|
||||
// 11. 峰电量
|
||||
BigDecimal peakEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
// 12.计损峰电量
|
||||
BigDecimal peakLoseEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
additionalInfo.put("计损峰电量", peakLoseEnergy);
|
||||
// 13.峰金额
|
||||
BigDecimal peakAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100);
|
||||
|
||||
// 14.平单价
|
||||
BigDecimal flatPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000);
|
||||
additionalInfo.put("平单价", flatPrice);
|
||||
// 15. 平电量
|
||||
BigDecimal flatEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
// 16.计损平电量
|
||||
BigDecimal flatLoseEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
additionalInfo.put("计损平电量", flatLoseEnergy);
|
||||
// 17.平金额
|
||||
BigDecimal flatAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100);
|
||||
|
||||
// 18.谷单价
|
||||
BigDecimal valleyPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000);
|
||||
additionalInfo.put("谷单价", valleyPrice);
|
||||
// 19. 谷电量
|
||||
BigDecimal valleyEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
// 20.计损谷电量
|
||||
BigDecimal valleyLoseEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000);
|
||||
additionalInfo.put("计损谷电量", valleyLoseEnergy);
|
||||
// 21.谷金额
|
||||
BigDecimal valleyAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100);
|
||||
|
||||
// 22.电表总起值
|
||||
byte[] meterStartValueBytes = new byte[5];
|
||||
byteBuf.readBytes(meterStartValueBytes);
|
||||
BigDecimal startMeterValue = reduceMagnification(readLongLE5Byte(meterStartValueBytes), 10000, 4);
|
||||
additionalInfo.put("电表总起值", startMeterValue);
|
||||
|
||||
// 23.电表总止值
|
||||
byte[] meterEndValueBytes = new byte[5];
|
||||
byteBuf.readBytes(meterEndValueBytes);
|
||||
BigDecimal endMeterValue = reduceMagnification(readLongLE5Byte(meterEndValueBytes), 10000, 4);
|
||||
additionalInfo.put("电表总止值", endMeterValue);
|
||||
|
||||
// 24.总电量
|
||||
BigDecimal totalEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000, 4);
|
||||
// 25.计损总电量
|
||||
BigDecimal totalLoseEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 10000, 4);
|
||||
additionalInfo.put("计损总电量", totalLoseEnergy);
|
||||
// 26 .消费金额
|
||||
BigDecimal totalAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100);
|
||||
|
||||
// 27.电动汽车唯一标识
|
||||
byte[] carVINBytes = new byte[17];
|
||||
byteBuf.readBytes(carVINBytes);
|
||||
String bmsVinCode = new String(carVINBytes, StandardCharsets.US_ASCII);
|
||||
additionalInfo.put("电动汽车唯一标识", bmsVinCode);
|
||||
|
||||
// 28.交易标识 0x01:app 启动0x02:卡启动 0x04:离线卡启动 0x05: vin 码启动充电
|
||||
byte tradeFlag = byteBuf.readByte();
|
||||
additionalInfo.put("交易标识", mapStartFlag(tradeFlag));
|
||||
|
||||
// 29.交易日期、时间
|
||||
byte[] tradeTimeBytes = new byte[7];
|
||||
byteBuf.readBytes(tradeTimeBytes);
|
||||
Instant tradeTime = CP56Time2aUtil.decode(tradeTimeBytes);
|
||||
|
||||
// 30.停止原因
|
||||
byte stopReasonByte = byteBuf.readByte();
|
||||
String stopReason = mapStopReason(stopReasonByte);
|
||||
|
||||
//31 物理卡号
|
||||
byte[] cardNoBytes = new byte[8];
|
||||
byteBuf.readBytes(cardNoBytes);
|
||||
String cardNo = BCDUtil.toString(cardNoBytes);
|
||||
additionalInfo.put("物理卡号", cardNo);
|
||||
|
||||
TransactionRecord transactionRecord = TransactionRecord.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setGunCode(gunCode)
|
||||
.setTradeNo(tradeNo)
|
||||
.setStartTs(startTime.toEpochMilli())
|
||||
.setEndTs(endTime.toEpochMilli())
|
||||
.setTopEnergyKWh(topEnergy.floatValue())
|
||||
.setTopAmountCent(topAmount.longValue())
|
||||
.setPeakEnergyKWh(peakEnergy.floatValue())
|
||||
.setPeakAmountCent(peakAmount.longValue())
|
||||
.setFlatEnergyKWh(flatEnergy.floatValue())
|
||||
.setFlatAmountCent(flatAmount.longValue())
|
||||
.setValleyEnergyKWh(valleyEnergy.floatValue())
|
||||
.setValleyAmountCent(valleyAmount.longValue())
|
||||
.setTotalEnergyKWh(totalEnergy.floatValue())
|
||||
.setTotalAmountCent(totalAmount.longValue())
|
||||
.setTradeTs(tradeTime.toEpochMilli())
|
||||
.setStopReason(stopReason)
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
|
||||
// 转发到后端
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setTransactionRecord(transactionRecord)
|
||||
.build();
|
||||
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
}
|
||||
|
||||
public static long readLongLE5Byte(byte[] bytes) {
|
||||
// 确保字节数组的长度至少为 5
|
||||
if (bytes.length < 5) {
|
||||
throw new IllegalArgumentException("Byte array must contain at least 5 bytes.");
|
||||
}
|
||||
|
||||
// 使用小端字节序读取 5 字节数字
|
||||
int byte1 = bytes[0] & 0xFF;
|
||||
int byte2 = bytes[1] & 0xFF;
|
||||
int byte3 = bytes[2] & 0xFF;
|
||||
int byte4 = bytes[3] & 0xFF;
|
||||
int byte5 = bytes[4] & 0xFF;
|
||||
|
||||
// 将读取的字节合并成一个 long 值
|
||||
return ((long) byte1) |
|
||||
((long) byte2 << 8) |
|
||||
((long) byte3 << 16) |
|
||||
((long) byte4 << 24) |
|
||||
((long) byte5 << 32);
|
||||
}
|
||||
|
||||
public static String mapStartFlag(byte startFlag) {
|
||||
return switch (startFlag) {
|
||||
case 0x01 -> "app 启动";
|
||||
case 0x02 -> "卡启动";
|
||||
case 0x04 -> "离线卡启动";
|
||||
case 0x05 -> "vin 码启动充电";
|
||||
default -> "未知启动方式";
|
||||
};
|
||||
}
|
||||
|
||||
public static String mapStopReason(byte stopReasonCode) {
|
||||
return switch (stopReasonCode) {
|
||||
case (byte) 0x40 -> "结束充电,APP 远程停止";
|
||||
case (byte) 0x41 -> "结束充电,SOC 达到 100%";
|
||||
case (byte) 0x42 -> "结束充电,充电电量满足设定条件";
|
||||
case (byte) 0x43 -> "结束充电,充电金额满足设定条件";
|
||||
case (byte) 0x44 -> "结束充电,充电时间满足设定条件";
|
||||
case (byte) 0x45 -> "结束充电,手动停止充电";
|
||||
case (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49 -> "结束充电,其他方式(预留)";
|
||||
case (byte) 0x4A -> "充电启动失败,充电桩控制系统故障(需要重启或自动恢复)";
|
||||
case (byte) 0x4B -> "充电启动失败,控制导引断开";
|
||||
case (byte) 0x4C -> "充电启动失败,断路器跳位";
|
||||
case (byte) 0x4D -> "充电启动失败,电表通信中断";
|
||||
case (byte) 0x4E -> "充电启动失败,余额不足";
|
||||
case (byte) 0x4F -> "充电启动失败,充电模块故障";
|
||||
case (byte) 0x50 -> "充电启动失败,急停开入";
|
||||
case (byte) 0x51 -> "充电启动失败,防雷器异常";
|
||||
case (byte) 0x52 -> "充电启动失败,BMS 未就绪";
|
||||
case (byte) 0x53 -> "充电启动失败,温度异常";
|
||||
case (byte) 0x54 -> "充电启动失败,电池反接故障";
|
||||
case (byte) 0x55 -> "充电启动失败,电子锁异常";
|
||||
case (byte) 0x56 -> "充电启动失败,合闸失败";
|
||||
case (byte) 0x57 -> "充电启动失败,绝缘异常";
|
||||
case (byte) 0x58 -> "充电启动失败,预留";
|
||||
case (byte) 0x59 -> "充电启动失败,接收 BMS 握手报文 BHM 超时";
|
||||
case (byte) 0x5A -> "充电启动失败,接收 BMS 和车辆的辨识报文超时 BRM";
|
||||
case (byte) 0x5B -> "充电启动失败,接收电池充电参数报文超时 BCP";
|
||||
case (byte) 0x5C -> "充电启动失败,接收 BMS 完成充电准备报文超时 BRO AA";
|
||||
case (byte) 0x5D -> "充电启动失败,接收电池充电总状态报文超时 BCS";
|
||||
case (byte) 0x5E -> "充电启动失败,接收电池充电要求报文超时 BCL";
|
||||
case (byte) 0x5F -> "充电启动失败,接收电池状态信息报文超时 BSM";
|
||||
case (byte) 0x60 -> "充电启动失败,GB2015 电池在 BHM 阶段有电压不允许充电";
|
||||
case (byte) 0x61 -> "充电启动失败,GB2015 辨识阶段在 BRO_AA 时候电池实际电压与 BCP 报文电池电压差距大于 5%";
|
||||
case (byte) 0x62 -> "充电启动失败,B2015 充电机在预充电阶段从 BRO_AA 变成BRO_00 状态";
|
||||
case (byte) 0x63 -> "充电启动失败,接收主机配置报文超时";
|
||||
case (byte) 0x64 -> "充电启动失败,充电机未准备就绪,我们没有回 CRO AA,对应老国标";
|
||||
case (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69 -> "充电启动失败,其他原因(预留)";
|
||||
case (byte) 0x6A -> "充电异常中止,系统闭锁";
|
||||
case (byte) 0x6B -> "充电异常中止,导引断开";
|
||||
case (byte) 0x6C -> "充电异常中止,断路器跳位";
|
||||
case (byte) 0x6D -> "充电异常中止,电表通信中断";
|
||||
case (byte) 0x6E -> "充电异常中止,余额不足";
|
||||
case (byte) 0x6F -> "充电异常中止,交流保护动作";
|
||||
case (byte) 0x70 -> "充电异常中止,直流保护动作";
|
||||
case (byte) 0x71 -> "充电异常中止,充电模块故障";
|
||||
case (byte) 0x72 -> "充电异常中止,急停开入";
|
||||
case (byte) 0x73 -> "充电异常中止,防雷器异常";
|
||||
case (byte) 0x74 -> "充电异常中止,温度异常";
|
||||
case (byte) 0x75 -> "充电异常中止,输出异常";
|
||||
case (byte) 0x76 -> "充电异常中止,充电无流";
|
||||
case (byte) 0x77 -> "充电异常中止,电子锁异常";
|
||||
case (byte) 0x78 -> "充电异常中止,预留";
|
||||
case (byte) 0x79 -> "充电异常中止,总充电电压异常";
|
||||
case (byte) 0x7A -> "充电异常中止,总充电电流异常";
|
||||
case (byte) 0x7B -> "充电异常中止,单体充电电压异常";
|
||||
case (byte) 0x7C -> "充电异常中止,电池组过温";
|
||||
case (byte) 0x7D -> "充电异常中止,最高单体充电电压异常";
|
||||
case (byte) 0x7E -> "充电异常中止,最高电池组过温";
|
||||
case (byte) 0x7F -> "充电异常中止,BMV 单体充电电压异常";
|
||||
case (byte) 0x80 -> "充电异常中止,BMT 电池组过温";
|
||||
case (byte) 0x81 -> "充电异常中止,电池状态异常停止充电";
|
||||
case (byte) 0x82 -> "充电异常中止,车辆发报文禁止充电";
|
||||
case (byte) 0x83 -> "充电异常中止,充电桩断电";
|
||||
case (byte) 0x84 -> "充电异常中止,接收电池充电总状态报文超时";
|
||||
case (byte) 0x85 -> "充电异常中止,接收电池充电要求报文超时";
|
||||
case (byte) 0x86 -> "充电异常中止,接收电池状态信息报文超时";
|
||||
case (byte) 0x87 -> "充电异常中止,接收 BMS 中止充电报文超时";
|
||||
case (byte) 0x88 -> "充电异常中止,接收 BMS 充电统计报文超时";
|
||||
case (byte) 0x89 -> "充电异常中止,接收对侧 CCS 报文超时";
|
||||
case (byte) 0x8A, (byte) 0x8B, (byte) 0x8C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F ->
|
||||
"充电异常中止,其他原因(预留)";
|
||||
case (byte) 0x90 -> "未知原因停止";
|
||||
default -> "无效的错误码";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.VerifyPricingResponse;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.FAILURE_BYTE;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.SUCCESS_BYTE;
|
||||
import static sanbing.jcpp.protocol.yunkuaichong.v150.enums.YunKuaiChongV150DownlinkCmdEnum.VERIFY_PRICING_ACK;
|
||||
|
||||
/**
|
||||
* 云快充1.5.0计费模型验证请求应答
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x06)
|
||||
public class YunKuaiChongV150VerifyPricingModelAckDLCmd extends YunKuaiChongDownlinkCmdExe {
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0计费模型验证请求应答", tcpSession);
|
||||
|
||||
if (!yunKuaiChongDwonlinkMessage.getMsg().hasVerifyPricingResponse()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VerifyPricingResponse verifyPricingResponse = yunKuaiChongDwonlinkMessage.getMsg().getVerifyPricingResponse();
|
||||
|
||||
YunKuaiChongUplinkMessage requestData = JacksonUtil.fromBytes(yunKuaiChongDwonlinkMessage.getMsg().getRequestData().toByteArray(), YunKuaiChongUplinkMessage.class);
|
||||
|
||||
// 获取上行报文
|
||||
byte[] uplinkRawFrame = requestData.getRawFrame();
|
||||
// 从上行报文中取出桩编号字节数组
|
||||
byte[] pileCodeBytes = Arrays.copyOfRange(uplinkRawFrame, 6, 13);
|
||||
|
||||
// 创建ACK消息体7字节桩编号+2字节计费模型编号+1字节验证结果
|
||||
ByteBuf verifyPricingAckMsgBody = Unpooled.buffer(10);
|
||||
verifyPricingAckMsgBody.writeBytes(pileCodeBytes);
|
||||
verifyPricingAckMsgBody.writeBytes(encodePricingId(verifyPricingResponse.getPricingId()));
|
||||
verifyPricingAckMsgBody.writeByte(verifyPricingResponse.getSuccess() ? SUCCESS_BYTE : FAILURE_BYTE);
|
||||
|
||||
encodeAndWriteFlush(VERIFY_PRICING_ACK,
|
||||
requestData.getSequenceNumber(),
|
||||
requestData.getEncryptionFlag(),
|
||||
verifyPricingAckMsgBody,
|
||||
tcpSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.VerifyPricingRequest;
|
||||
import sanbing.jcpp.protocol.ProtocolContext;
|
||||
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||
|
||||
|
||||
/**
|
||||
* 云快充1.5.0计费模型验证请求
|
||||
*
|
||||
* @author baigod
|
||||
*/
|
||||
@Slf4j
|
||||
@YunKuaiChongCmd(0x05)
|
||||
public class YunKuaiChongV150VerifyPricingModelULCmd extends YunKuaiChongUplinkCmdExe {
|
||||
|
||||
@Override
|
||||
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
|
||||
log.info("{} 云快充1.5.0计费模型验证请求", tcpSession);
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
|
||||
|
||||
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
|
||||
|
||||
byte[] pileCodeBytes = new byte[7];
|
||||
byteBuf.readBytes(pileCodeBytes);
|
||||
String pileCode = BCDUtil.toString(pileCodeBytes);
|
||||
|
||||
byte[] pricingModelIdBytes = new byte[2];
|
||||
byteBuf.readBytes(pricingModelIdBytes);
|
||||
long pricingModelId = BCDUtil.bcdBytesToLong(pricingModelIdBytes);
|
||||
|
||||
// 转发到后端
|
||||
VerifyPricingRequest heartBeatRequest = VerifyPricingRequest.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setPricingId(pricingModelId)
|
||||
.setAdditionalInfo(additionalInfo.toString())
|
||||
.build();
|
||||
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(heartBeatRequest.getPileCode(), tcpSession, yunKuaiChongUplinkMessage)
|
||||
.setVerifyPricingRequest(heartBeatRequest)
|
||||
.build();
|
||||
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.yunkuaichong.v150.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum YunKuaiChongV150DownlinkCmdEnum {
|
||||
|
||||
LOGIN_ACK(0x02),
|
||||
|
||||
SYNC_TIME(0x56),
|
||||
|
||||
HEARTBEAT(0x04),
|
||||
|
||||
VERIFY_PRICING_ACK(0x06),
|
||||
|
||||
QUERY_PRICING_ACK(0X0A),
|
||||
|
||||
SET_PRICING(0x58),
|
||||
|
||||
REMOTE_START_CHARGING(0x34),
|
||||
|
||||
REMOTE_STOP_CHARGING(0x36),
|
||||
|
||||
TRANSACTION_RECORD(0x40)
|
||||
;
|
||||
|
||||
private int cmd;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user