云快充1.5.0 初始化

This commit is contained in:
3god
2024-10-08 09:38:54 +08:00
parent dea6774942
commit cb19b45919
297 changed files with 18020 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 -> "未知错误代码";
};
}
}

View File

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

View File

@@ -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 -> "未知错误"; // 可以根据需求自定义
};
}
}

View File

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

View File

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

View File

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

View File

@@ -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.交易标识 0x01app 启动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 -> "无效的错误码";
};
}
}

View File

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

View File

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

View File

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