mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-08 11:59:56 +08:00
!47 新增 充电桩主动申请启动充电(0x31)2.运营平台确认启动充电(0x32)
* fix(ProtocolUplinkConsumerService):指标初始化代码恢复 * update:启动方式枚举类调整 * update:增加 0x31、0x32 的枚举类 * update:添加下行日志打印 * add:1.充电桩主动申请启动充电(0x31)2.运营平台确认启动充电(0x32)
This commit is contained in:
@@ -72,6 +72,11 @@ public interface PileProtocolService {
|
|||||||
*/
|
*/
|
||||||
void onTransactionRecordRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
void onTransactionRecordRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 充电桩主动申请启动充电
|
||||||
|
*/
|
||||||
|
void onStartChargeRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动充电(支持卡号和并充序号)
|
* 启动充电(支持卡号和并充序号)
|
||||||
* 当 parallelNo 不为空时,自动使用并充启机命令
|
* 当 parallelNo 不为空时,自动使用并充启机命令
|
||||||
|
|||||||
@@ -394,6 +394,36 @@ public class DefaultPileProtocolService implements PileProtocolService {
|
|||||||
callback.onSuccess();
|
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
|
@Override
|
||||||
public void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo,
|
public void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo,
|
||||||
String logicalCardNo, String physicalCardNo, String parallelNo) {
|
String logicalCardNo, String physicalCardNo, String parallelNo) {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService {
|
|||||||
private AppQueueConsumerManager<ProtoQueueMsg<UplinkQueueMessage>, AppQueueConfig> appConsumer;
|
private AppQueueConsumerManager<ProtoQueueMsg<UplinkQueueMessage>, AppQueueConfig> appConsumer;
|
||||||
|
|
||||||
private final StatsFactory statsFactory;
|
private final StatsFactory statsFactory;
|
||||||
|
|
||||||
private AppConsumerStats stats;
|
private AppConsumerStats stats;
|
||||||
|
|
||||||
public ProtocolUplinkConsumerService(PartitionProvider partitionProvider,
|
public ProtocolUplinkConsumerService(PartitionProvider partitionProvider,
|
||||||
@@ -87,7 +87,7 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
super.init("jcpp-app");
|
super.init("jcpp-app");
|
||||||
|
|
||||||
this.stats = new AppConsumerStats(statsFactory, timerTopN);
|
this.stats = new AppConsumerStats(statsFactory, timerTopN);
|
||||||
|
|
||||||
log.info("Initializing Protocol Uplink Messages Queue Subscriptions.");
|
log.info("Initializing Protocol Uplink Messages Queue Subscriptions.");
|
||||||
@@ -249,7 +249,11 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService {
|
|||||||
|
|
||||||
pileProtocolService.postBmsDemandChargerOutput(uplinkQueueMsg, callback);
|
pileProtocolService.postBmsDemandChargerOutput(uplinkQueueMsg, callback);
|
||||||
|
|
||||||
}else {
|
} else if (uplinkQueueMsg.hasStartChargeRequest()) {
|
||||||
|
|
||||||
|
pileProtocolService.onStartChargeRequest(uplinkQueueMsg, callback);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
callback.onSuccess();
|
callback.onSuccess();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ message UplinkQueueMessage {
|
|||||||
OfflineCardSyncResponse offlineCardSyncResponse = 41;
|
OfflineCardSyncResponse offlineCardSyncResponse = 41;
|
||||||
TimeSyncResponse timeSyncResponse = 42;
|
TimeSyncResponse timeSyncResponse = 42;
|
||||||
BmsDemandChargerOutputProto bmsDemandChargerOutputProto = 43;
|
BmsDemandChargerOutputProto bmsDemandChargerOutputProto = 43;
|
||||||
|
StartChargeRequest startChargeRequest = 44;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DownlinkRequestMessage {
|
message DownlinkRequestMessage {
|
||||||
@@ -105,6 +106,7 @@ message DownlinkRequestMessage {
|
|||||||
OfflineCardBalanceUpdateRequest offlineCardBalanceUpdateRequest = 30;
|
OfflineCardBalanceUpdateRequest offlineCardBalanceUpdateRequest = 30;
|
||||||
OfflineCardSyncRequest offlineCardSyncRequest = 31;
|
OfflineCardSyncRequest offlineCardSyncRequest = 31;
|
||||||
TimeSyncRequest timeSyncRequest = 32;
|
TimeSyncRequest timeSyncRequest = 32;
|
||||||
|
StartChargeResponse startChargeResponse = 33;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DownlinkResponseMessage {
|
message DownlinkResponseMessage {
|
||||||
@@ -136,6 +138,43 @@ message LoginRequest {
|
|||||||
optional string additionalInfo = 20;
|
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 {
|
message LoginResponse {
|
||||||
bool success = 1;
|
bool success = 1;
|
||||||
string pileCode = 2;
|
string pileCode = 2;
|
||||||
|
|||||||
@@ -39,5 +39,7 @@ public enum DownlinkCmdEnum {
|
|||||||
|
|
||||||
OFFLINE_CARD_BALANCE_UPDATE_REQUEST,
|
OFFLINE_CARD_BALANCE_UPDATE_REQUEST,
|
||||||
|
|
||||||
OFFLINE_CARD_SYNC_REQUEST
|
OFFLINE_CARD_SYNC_REQUEST,
|
||||||
|
|
||||||
|
START_CHARGE_ACK
|
||||||
}
|
}
|
||||||
@@ -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 下发启动充电
|
#### 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`
|
`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 上行启动应答
|
#### 0x33 上行启动应答
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ public class YunKuaiChongDownlinkCmdConverter implements DownlinkCmdConverter {
|
|||||||
COMMAND_MAP.put(DownlinkCmdEnum.VERIFY_PRICING_ACK, 0x06);
|
COMMAND_MAP.put(DownlinkCmdEnum.VERIFY_PRICING_ACK, 0x06);
|
||||||
COMMAND_MAP.put(DownlinkCmdEnum.QUERY_PRICING_ACK, 0X0A);
|
COMMAND_MAP.put(DownlinkCmdEnum.QUERY_PRICING_ACK, 0X0A);
|
||||||
COMMAND_MAP.put(DownlinkCmdEnum.SET_PRICING, 0x58);
|
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_START_CHARGING, 0x34);
|
||||||
COMMAND_MAP.put(DownlinkCmdEnum.REMOTE_STOP_CHARGING, 0x36);
|
COMMAND_MAP.put(DownlinkCmdEnum.REMOTE_STOP_CHARGING, 0x36);
|
||||||
COMMAND_MAP.put(DownlinkCmdEnum.TRANSACTION_RECORD_ACK, 0x40);
|
COMMAND_MAP.put(DownlinkCmdEnum.TRANSACTION_RECORD_ACK, 0x40);
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user