!47 新增 充电桩主动申请启动充电(0x31)2.运营平台确认启动充电(0x32)

* fix(ProtocolUplinkConsumerService):指标初始化代码恢复
* update:启动方式枚举类调整
* update:增加 0x31、0x32 的枚举类
* update:添加下行日志打印
* add:1.充电桩主动申请启动充电(0x31)2.运营平台确认启动充电(0x32)
This commit is contained in:
白板
2025-09-12 05:44:33 +00:00
committed by 三丙
parent 4eebd3d1b0
commit bc5411eb4b
12 changed files with 708 additions and 4 deletions

View File

@@ -72,6 +72,11 @@ public interface PileProtocolService {
*/
void onTransactionRecordRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback);
/**
* 充电桩主动申请启动充电
*/
void onStartChargeRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback);
/**
* 启动充电(支持卡号和并充序号)
* 当 parallelNo 不为空时,自动使用并充启机命令

View File

@@ -394,6 +394,36 @@ public class DefaultPileProtocolService implements PileProtocolService {
callback.onSuccess();
}
@Override
public void onStartChargeRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
log.info("接收到充电桩启动充电请求 {}", uplinkQueueMessage);
StartChargeRequest startChargeRequest = uplinkQueueMessage.getStartChargeRequest();
String pileCode = startChargeRequest.getPileCode();
String gunCode = startChargeRequest.getGunCode();
// TODO 处理相关业务逻辑
// 构造下行回复
DownlinkRequestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, startChargeRequest.getPileCode());
downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.START_CHARGE_ACK.name());
downlinkMessageBuilder.setStartChargeResponse(StartChargeResponse.newBuilder()
.setTradeNo("")
.setPileCode(startChargeRequest.getPileCode())
.setGunCode(startChargeRequest.getGunCode())
.setLogicalCardNo("")
.setLimitYuan("0")
.setAuthSuccess(false)
.setFailReason(FailReason.ACCOUNT_NOT_ALLOWED_ON_PILE.name())
);
log.info("业务[充电桩启动充电请求应答] 发送下行消息到充电桩: {}, 充电枪: {}", pileCode, gunCode);
downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder,pileCode);
callback.onSuccess();
}
@Override
public void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo,
String logicalCardNo, String physicalCardNo, String parallelNo) {

View File

@@ -70,7 +70,7 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService {
private AppQueueConsumerManager<ProtoQueueMsg<UplinkQueueMessage>, AppQueueConfig> appConsumer;
private final StatsFactory statsFactory;
private AppConsumerStats stats;
public ProtocolUplinkConsumerService(PartitionProvider partitionProvider,
@@ -87,7 +87,7 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService {
@PostConstruct
public void init() {
super.init("jcpp-app");
this.stats = new AppConsumerStats(statsFactory, timerTopN);
log.info("Initializing Protocol Uplink Messages Queue Subscriptions.");
@@ -249,7 +249,11 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService {
pileProtocolService.postBmsDemandChargerOutput(uplinkQueueMsg, callback);
}else {
} else if (uplinkQueueMsg.hasStartChargeRequest()) {
pileProtocolService.onStartChargeRequest(uplinkQueueMsg, callback);
} else {
callback.onSuccess();
}

View File

@@ -80,6 +80,7 @@ message UplinkQueueMessage {
OfflineCardSyncResponse offlineCardSyncResponse = 41;
TimeSyncResponse timeSyncResponse = 42;
BmsDemandChargerOutputProto bmsDemandChargerOutputProto = 43;
StartChargeRequest startChargeRequest = 44;
}
message DownlinkRequestMessage {
@@ -105,6 +106,7 @@ message DownlinkRequestMessage {
OfflineCardBalanceUpdateRequest offlineCardBalanceUpdateRequest = 30;
OfflineCardSyncRequest offlineCardSyncRequest = 31;
TimeSyncRequest timeSyncRequest = 32;
StartChargeResponse startChargeResponse = 33;
}
message DownlinkResponseMessage {
@@ -136,6 +138,43 @@ message LoginRequest {
optional string additionalInfo = 20;
}
message StartChargeRequest {
int64 ts = 1;
string pileCode = 2; // 桩编号
string gunCode = 3; // 枪编号
string startType = 4; // 启动类型
string cardNo = 5; // 账号或物理卡号
bool needPassword = 6; // 是否需要密码
string password = 7; // 密码
string carVinCode = 8; // 车辆识别码(VIN)
optional string additionalInfo = 20; // 附加信息
}
message StartChargeResponse {
string tradeNo = 1; // 交易流水号
string pileCode = 2; // 桩编号
string gunCode = 3; // 枪编号
string logicalCardNo = 4; // 逻辑卡号
string limitYuan = 5; // 账户余额
bool authSuccess = 6; // 鉴权成功标志
string failReason = 7; // 失败原因
}
enum FailReason {
SUCCESS = 0; // 成功
ACCOUNT_NOT_EXISTS = 1; // 账户不存在
ACCOUNT_FROZEN = 2; // 账户冻结
INSUFFICIENT_BALANCE = 3; // 账户余额不足
CARD_HAS_UNPAID_RECORD = 4; // 该卡存在未结账记录
PILE_DISABLED = 5; // 桩停用
ACCOUNT_NOT_ALLOWED_ON_PILE = 6; // 该账户不能在此桩上充电
PASSWORD_ERROR = 7; // 密码错误
INSUFFICIENT_STATION_CAPACITY = 8; // 电站电容不足
VIN_CODE_NOT_EXISTS = 9; // 系统中vin码不存在
PILE_HAS_UNPAID_RECORD = 10; // 该桩存在未结账记录
PILE_NOT_SUPPORT_CARD = 11; // 该桩不支持该卡
}
message LoginResponse {
bool success = 1;
string pileCode = 2;

View File

@@ -39,5 +39,7 @@ public enum DownlinkCmdEnum {
OFFLINE_CARD_BALANCE_UPDATE_REQUEST,
OFFLINE_CARD_SYNC_REQUEST
OFFLINE_CARD_SYNC_REQUEST,
START_CHARGE_ACK
}

View File

@@ -45,6 +45,13 @@
---
#### 0x31 充电桩主动申请充电
`68 37 00 04 00 31 20 23 12 12 00 00 10 01 01 00 00 00 00 00 D1 4B 0A 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FA E8`
#### 0x32 运营平台确认启动充电
`68 28 04 00 00 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 23 12 12 00 00 10 01 00 00 00 00 00 00 00 00 00 00 01 06 29 42`
---
#### 0x34 下发启动充电
`68 30 01 00 00 34 00 00 00 00 00 00 12 34 56 78 90 12 34 56 78 90 20 23 12 12 00 00 10 01 56 78 90 12 34 56 78 90 56 78 90 12 34 56 78 90 10 27 00 00 5f d9`
#### 0x33 上行启动应答

View File

@@ -0,0 +1,85 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.enums;
import lombok.Getter;
/**
* 云快充协议密码验证标志枚举
*
* @author baiban
* @since 2024-12-16
*/
@Getter
public enum YunKuaiChongPasswordRequiredEnum {
/** 不需要密码 */
NOT_REQUIRED(0x00, false, "不需要密码"),
/** 需要密码 */
REQUIRED(0x01, true,"需要密码");
/** 密码验证标志代码 */
private final int code;
/** 是否需要密码的布尔值 */
private final boolean value;
/** 密码验证标志描述 */
private final String description;
YunKuaiChongPasswordRequiredEnum(int code, boolean value, String description) {
this.code = code;
this.value = value;
this.description = description;
}
/**
* 根据代码获取密码验证标志枚举
*
* @param code 密码验证标志代码
* @return 密码验证标志枚举未找到时返回NOT_REQUIRED
*/
public static YunKuaiChongPasswordRequiredEnum fromCode(int code) {
for (YunKuaiChongPasswordRequiredEnum passwordRequired : values()) {
if (passwordRequired.code == code) {
return passwordRequired;
}
}
return NOT_REQUIRED;
}
/**
* 根据代码获取密码验证标志描述
*
* @param code 密码验证标志代码
* @return 密码验证标志描述
*/
public static String getDescription(int code) {
return fromCode(code).getDescription();
}
/**
* 根据代码检查是否需要密码
*
* @param code 密码验证标志代码
* @return true表示需要密码false表示不需要密码
*/
public static boolean isPasswordRequired(int code) {
return fromCode(code).isValue();
}
/**
* 根据布尔值获取对应的枚举
*
* @param required 是否需要密码
* @return 对应的枚举值
*/
public static YunKuaiChongPasswordRequiredEnum fromBoolean(boolean required) {
return required ? REQUIRED : NOT_REQUIRED;
}
}

View File

@@ -0,0 +1,194 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.enums;
import lombok.Getter;
/**
* 云快充协议启动充电失败原因枚举
*
* @author baiban
* @since 2024-12-16
*/
@Getter
public enum YunKuaiChongStartChargeFailureReasonEnum {
/** 成功(无失败原因) */
SUCCESS(0x00, "SUCCESS", "成功"),
/** 账户不存在 */
ACCOUNT_NOT_EXISTS(0x01, "ACCOUNT_NOT_EXISTS", "账户不存在"),
/** 账户冻结 */
ACCOUNT_FROZEN(0x02, "ACCOUNT_FROZEN", "账户冻结"),
/** 账户余额不足 */
INSUFFICIENT_BALANCE(0x03, "INSUFFICIENT_BALANCE", "账户余额不足"),
/** 该卡存在未结账记录 */
CARD_HAS_UNPAID_RECORD(0x04, "CARD_HAS_UNPAID_RECORD", "该卡存在未结账记录"),
/** 桩停用 */
PILE_DISABLED(0x05, "PILE_DISABLED", "桩停用"),
/** 该账户不能在此桩上充电 */
ACCOUNT_NOT_ALLOWED_ON_PILE(0x06, "ACCOUNT_NOT_ALLOWED_ON_PILE", "该账户不能在此桩上充电"),
/** 密码错误 */
PASSWORD_ERROR(0x07, "PASSWORD_ERROR", "密码错误"),
/** 电站电容不足 */
INSUFFICIENT_STATION_CAPACITY(0x08, "INSUFFICIENT_STATION_CAPACITY", "电站电容不足"),
/** 系统中vin码不存在 */
VIN_CODE_NOT_EXISTS(0x09, "VIN_CODE_NOT_EXISTS", "系统中vin码不存在"),
/** 该桩存在未结账记录 */
PILE_HAS_UNPAID_RECORD(0x0A, "PILE_HAS_UNPAID_RECORD", "该桩存在未结账记录"),
/** 该桩不支持刷卡 */
PILE_NOT_SUPPORT_CARD(0x0B, "PILE_NOT_SUPPORT_CARD", "该桩不支持刷卡"),
/** 未知错误 */
UNKNOWN(0xFF, "UNKNOWN", "未知错误");
/** 失败原因代码 */
private final int code;
/** 失败原因值 */
private final String value;
/** 失败原因描述 */
private final String description;
YunKuaiChongStartChargeFailureReasonEnum(int code, String value, String description) {
this.code = code;
this.value = value;
this.description = description;
}
/**
* 根据代码获取失败原因枚举
*
* @param code 失败原因代码
* @return 失败原因枚举未找到时返回UNKNOWN
*/
public static YunKuaiChongStartChargeFailureReasonEnum fromCode(int code) {
for (YunKuaiChongStartChargeFailureReasonEnum reason : values()) {
if (reason.code == code) {
return reason;
}
}
return UNKNOWN;
}
/**
* 根据值获取失败原因枚举
*
* @param value 失败原因值
* @return 失败原因枚举未找到时返回UNKNOWN
*/
public static YunKuaiChongStartChargeFailureReasonEnum fromValue(String value) {
if (value == null || value.trim().isEmpty()) {
return SUCCESS;
}
for (YunKuaiChongStartChargeFailureReasonEnum reason : values()) {
if (reason.value.equals(value)) {
return reason;
}
}
return UNKNOWN;
}
/**
* 根据描述获取失败原因枚举
*
* @param description 失败原因描述
* @return 失败原因枚举未找到时返回UNKNOWN
*/
public static YunKuaiChongStartChargeFailureReasonEnum fromDescription(String description) {
if (description == null || description.trim().isEmpty()) {
return SUCCESS;
}
for (YunKuaiChongStartChargeFailureReasonEnum reason : values()) {
if (reason.description.equals(description)) {
return reason;
}
}
return UNKNOWN;
}
/**
* 根据代码获取失败原因值
*
* @param code 失败原因代码
* @return 失败原因值
*/
public static String getValue(int code) {
return fromCode(code).getValue();
}
/**
* 根据值获取失败原因代码
*
* @param value 失败原因值
* @return 失败原因代码
*/
public static int getCode(String value) {
return fromValue(value).getCode();
}
/**
* 根据值获取失败原因描述
*
* @param value 失败原因值
* @return 失败原因描述
*/
public static String getDescription(String value) {
return fromValue(value).getDescription();
}
/**
* 根据代码获取失败原因描述
*
* @param code 失败原因代码
* @return 失败原因描述
*/
public static String getDescription(int code) {
return fromCode(code).getDescription();
}
/**
* 检查是否为成功状态
*
* @param code 失败原因代码
* @return true表示成功false表示失败
*/
public static boolean isSuccess(int code) {
return code == SUCCESS.code;
}
/**
* 检查是否为成功状态
*
* @return true表示成功false表示失败
*/
public boolean isSuccess() {
return this == SUCCESS;
}
/**
* 获取字节形式的代码
*
* @return 字节形式的失败原因代码
*/
public byte getByteCode() {
return (byte) code;
}
}

View File

@@ -0,0 +1,126 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.enums;
import lombok.Getter;
/**
* 云快充协议启动方式枚举
*
* @author baiban
* @since 2024-12-16
*/
@Getter
public enum YunKuaiChongStartTypeEnum {
/** 通过刷卡启动充电 */
CARD_START(0x01, "CARD_START", "刷卡启动"),
/** 通过账号启动充电(暂不支持) */
ACCOUNT_START(0x02, "ACCOUNT_START", "账号启动"),
/** VIN码启动充电 */
VIN_START(0x03, "VIN_START", "VIN码启动"),
/** 未知启动方式 */
UNKNOWN(0xFF, "UNKNOWN", "未知启动方式");
/** 启动方式代码 */
private final int code;
/** 启动方式值 */
private final String value;
/** 启动方式描述 */
private final String description;
YunKuaiChongStartTypeEnum(int code, String value, String description) {
this.code = code;
this.value = value;
this.description = description;
}
/**
* 根据代码获取启动方式枚举
*
* @param code 启动方式代码
* @return 启动方式枚举未找到时返回UNKNOWN
*/
public static YunKuaiChongStartTypeEnum fromCode(int code) {
for (YunKuaiChongStartTypeEnum startType : values()) {
if (startType.code == code) {
return startType;
}
}
return UNKNOWN;
}
/**
* 根据值获取启动方式枚举
*
* @param value 启动方式值
* @return 启动方式枚举未找到时返回UNKNOWN
*/
public static YunKuaiChongStartTypeEnum fromValue(String value) {
for (YunKuaiChongStartTypeEnum startType : values()) {
if (startType.value.equals(value)) {
return startType;
}
}
return UNKNOWN;
}
/**
* 根据代码获取启动方式值
*
* @param code 启动方式代码
* @return 启动方式值
*/
public static String getValue(int code) {
return fromCode(code).getValue();
}
/**
* 根据值获取启动方式代码
*
* @param value 启动方式值
* @return 启动方式代码
*/
public static int getCode(String value) {
return fromValue(value).getCode();
}
/**
* 根据值获取启动方式描述
*
* @param value 启动方式值
* @return 启动方式描述
*/
public static String getDescription(String value) {
return fromValue(value).getDescription();
}
/**
* 根据代码获取启动方式描述
*
* @param code 启动方式代码
* @return 启动方式描述
*/
public static String getDescription(int code) {
return fromCode(code).getDescription();
}
/**
* 检查是否为有效的启动方式代码
*
* @param code 启动方式代码
* @return true表示有效false表示无效
*/
public static boolean isValid(int code) {
return fromCode(code) != UNKNOWN;
}
}

View File

@@ -42,6 +42,7 @@ public class YunKuaiChongDownlinkCmdConverter implements DownlinkCmdConverter {
COMMAND_MAP.put(DownlinkCmdEnum.VERIFY_PRICING_ACK, 0x06);
COMMAND_MAP.put(DownlinkCmdEnum.QUERY_PRICING_ACK, 0X0A);
COMMAND_MAP.put(DownlinkCmdEnum.SET_PRICING, 0x58);
COMMAND_MAP.put(DownlinkCmdEnum.START_CHARGE_ACK, 0x32);
COMMAND_MAP.put(DownlinkCmdEnum.REMOTE_START_CHARGING, 0x34);
COMMAND_MAP.put(DownlinkCmdEnum.REMOTE_STOP_CHARGING, 0x36);
COMMAND_MAP.put(DownlinkCmdEnum.TRANSACTION_RECORD_ACK, 0x40);

View File

@@ -0,0 +1,84 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
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;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
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.enums.YunKuaiChongStartChargeFailureReasonEnum;
import java.math.BigDecimal;
import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.START_CHARGE_ACK;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.FAILURE_BYTE;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.SUCCESS_BYTE;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.*;
/**
* 云快充1.5.0 充电桩主动申请启动充电
*
* @author baiban
*/
@Slf4j
@ProtocolCmd(value = 0x32, protocolNames = {V150, V160, V170})
public class YunKuaiChongV150StartChargeAckDLCmd extends YunKuaiChongDownlinkCmdExe {
@Override
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage yunKuaiChongDwonlinkMessage, ProtocolContext ctx) {
log.info("{} 云快充1.5.0运营平台确认启动充电", tcpSession);
if (!yunKuaiChongDwonlinkMessage.getMsg().hasStartChargeResponse()) {
return;
}
ProtocolProto.StartChargeResponse startChargeResponse = yunKuaiChongDwonlinkMessage.getMsg().getStartChargeResponse();
String tradeNo = startChargeResponse.getTradeNo();
String pileCode = startChargeResponse.getPileCode();
String gunCode = startChargeResponse.getGunCode();
String logicalCardNo = startChargeResponse.getLogicalCardNo();
String limitYuan = startChargeResponse.getLimitYuan();
String failReasonValue = startChargeResponse.getFailReason();
boolean authSuccess = startChargeResponse.getAuthSuccess();
int failReasonCode = YunKuaiChongStartChargeFailureReasonEnum.getCode(failReasonValue);
ByteBuf msgBody = Unpooled.buffer(44);
// 交易流水号
msgBody.writeBytes(encodeTradeNo(tradeNo));
// 桩编码
msgBody.writeBytes(encodePileCode(pileCode));
// 枪号
msgBody.writeBytes(encodeGunCode(gunCode));
// 逻辑卡号
msgBody.writeBytes(encodeCardNo(logicalCardNo));
// 账户余额
msgBody.writeIntLE(new BigDecimal(limitYuan).multiply(new BigDecimal("100")).intValue());
// 鉴权成功标志
msgBody.writeByte(authSuccess ? SUCCESS_BYTE : FAILURE_BYTE);
// 失败原因
msgBody.writeByte(failReasonCode);
YunKuaiChongUplinkMessage requestData = JacksonUtil.fromBytes(yunKuaiChongDwonlinkMessage.getMsg().getRequestData().toByteArray(), YunKuaiChongUplinkMessage.class);
encodeAndWriteFlush(START_CHARGE_ACK,
requestData.getSequenceNumber(),
requestData.getEncryptionFlag(),
msgBody,
tcpSession);
if (!authSuccess) {
log.info("业务[云快充1.5.0 充电桩主动申请启动充电失败] 失败原因:{}", YunKuaiChongStartChargeFailureReasonEnum.getDescription(failReasonCode));
}
}
}

View File

@@ -0,0 +1,127 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
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.springframework.util.DigestUtils;
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;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
import sanbing.jcpp.protocol.yunkuaichong.enums.YunKuaiChongStartTypeEnum;
import sanbing.jcpp.protocol.yunkuaichong.enums.YunKuaiChongPasswordRequiredEnum;
import java.nio.charset.StandardCharsets;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.*;
/**
* 云快充1.5.0 充电桩主动申请启动充电
*
* @author baiban
*/
@Slf4j
@ProtocolCmd(value = 0x31, protocolNames = {V150, V160, V170})
public class YunKuaiChongV150StartChargeULCmd extends YunKuaiChongUplinkCmdExe {
@Override
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) {
log.debug("{} 云快充1.5.0充电桩主动申请启动充电", tcpSession);
ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody());
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
// 从Tracer中获取当前时间
long ts = TracerContextUtil.getCurrentTracer().getTracerTs();
// 桩编号
byte[] pileCodeBytes = new byte[7];
byteBuf.readBytes(pileCodeBytes);
String pileCode = BCDUtil.toString(pileCodeBytes);
// 枪号
byte gunCodeByte = byteBuf.readByte();
String gunCode = BCDUtil.toString(gunCodeByte);
// 启动方式
int startTypeCode = byteBuf.readUnsignedByte();
String startType = YunKuaiChongStartTypeEnum.getValue(startTypeCode);
// 是否需要密码
int needPasswordCode = byteBuf.readUnsignedByte();
boolean needPassword = YunKuaiChongPasswordRequiredEnum.isPasswordRequired(needPasswordCode);
// 物理卡号
byte[] cardNoBytes = new byte[8];
byteBuf.readBytes(cardNoBytes);
String cardNo = BCDUtil.toString(cardNoBytes);
// 密码
byte[] passwordBytes = new byte[16];
byteBuf.readBytes(passwordBytes);
String password = DigestUtils.md5DigestAsHex(passwordBytes).substring(8, 24).toLowerCase();
// VIN码
byte[] carVinCodeBytes = new byte[17];
byteBuf.readBytes(carVinCodeBytes);
// VIN码反序处理
String carVinCode = reverseVinCode(new String(carVinCodeBytes, StandardCharsets.US_ASCII));
// 转发到后端
ProtocolProto.StartChargeRequest startChargingRequest = ProtocolProto.StartChargeRequest.newBuilder()
.setTs(ts)
.setPileCode(pileCode)
.setGunCode(gunCode)
.setStartType(startType)
.setNeedPassword(needPassword)
.setCardNo(cardNo)
.setPassword(password)
.setCarVinCode(carVinCode)
.setAdditionalInfo(additionalInfo.toString())
.build();
ProtocolProto.UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(startChargingRequest.getPileCode(), tcpSession, yunKuaiChongUplinkMessage)
.setStartChargeRequest(startChargingRequest)
.build();
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
}
/**
* VIN码反序处理
*
* @param originalVinCode 原始VIN码
* @return 反序后的VIN码
*/
private String reverseVinCode(String originalVinCode) {
if (originalVinCode == null || originalVinCode.trim().isEmpty()) {
return "";
}
// 移除末尾的null字符和空格
String trimmedVin = originalVinCode.trim().replaceAll("\0", "");
if (trimmedVin.isEmpty()) {
return "";
}
// 反序VIN码
return new StringBuilder(trimmedVin).reverse().toString();
}
}