From bc5411eb4b9d154c70a8ffda37baf055cda6194d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E6=9D=BF?= <14091831+ianzsc@user.noreply.gitee.com> Date: Fri, 12 Sep 2025 05:44:33 +0000 Subject: [PATCH] =?UTF-8?q?!47=20=E6=96=B0=E5=A2=9E=20=E5=85=85=E7=94=B5?= =?UTF-8?q?=E6=A1=A9=E4=B8=BB=E5=8A=A8=E7=94=B3=E8=AF=B7=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=85=85=E7=94=B5=EF=BC=880x31=EF=BC=892.=E8=BF=90=E8=90=A5?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E7=A1=AE=E8=AE=A4=E5=90=AF=E5=8A=A8=E5=85=85?= =?UTF-8?q?=E7=94=B5=EF=BC=880x32=EF=BC=89=20*=20fix(ProtocolUplinkConsume?= =?UTF-8?q?rService):=E6=8C=87=E6=A0=87=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=81=A2=E5=A4=8D=20*=20update=EF=BC=9A?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=96=B9=E5=BC=8F=E6=9E=9A=E4=B8=BE=E7=B1=BB?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20*=20update=EF=BC=9A=E5=A2=9E=E5=8A=A0=200x?= =?UTF-8?q?31=E3=80=810x32=20=E7=9A=84=E6=9E=9A=E4=B8=BE=E7=B1=BB=20*=20up?= =?UTF-8?q?date=EF=BC=9A=E6=B7=BB=E5=8A=A0=E4=B8=8B=E8=A1=8C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=89=93=E5=8D=B0=20*=20add=EF=BC=9A1.=E5=85=85?= =?UTF-8?q?=E7=94=B5=E6=A1=A9=E4=B8=BB=E5=8A=A8=E7=94=B3=E8=AF=B7=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=85=85=E7=94=B5=EF=BC=880x31=EF=BC=892.=E8=BF=90?= =?UTF-8?q?=E8=90=A5=E5=B9=B3=E5=8F=B0=E7=A1=AE=E8=AE=A4=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=85=85=E7=94=B5=EF=BC=880x32=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jcpp/app/service/PileProtocolService.java | 5 + .../impl/DefaultPileProtocolService.java | 30 +++ .../ProtocolUplinkConsumerService.java | 10 +- .../src/main/proto/protocol.proto | 39 ++++ .../jcpp/protocol/domain/DownlinkCmdEnum.java | 4 +- jcpp-protocol-yunkuaichong/READMD.md | 7 + .../YunKuaiChongPasswordRequiredEnum.java | 85 ++++++++ ...KuaiChongStartChargeFailureReasonEnum.java | 194 ++++++++++++++++++ .../enums/YunKuaiChongStartTypeEnum.java | 126 ++++++++++++ .../YunKuaiChongDownlinkCmdConverter.java | 1 + .../YunKuaiChongV150StartChargeAckDLCmd.java | 84 ++++++++ .../cmd/YunKuaiChongV150StartChargeULCmd.java | 127 ++++++++++++ 12 files changed, 708 insertions(+), 4 deletions(-) create mode 100644 jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongPasswordRequiredEnum.java create mode 100644 jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartChargeFailureReasonEnum.java create mode 100644 jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartTypeEnum.java create mode 100644 jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeAckDLCmd.java create mode 100644 jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeULCmd.java diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java index 09c7e18..f8c2590 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java @@ -72,6 +72,11 @@ public interface PileProtocolService { */ void onTransactionRecordRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback); + /** + * 充电桩主动申请启动充电 + */ + void onStartChargeRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback); + /** * 启动充电(支持卡号和并充序号) * 当 parallelNo 不为空时,自动使用并充启机命令 diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java index 5eca789..b34ba84 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java @@ -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) { diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java index 763e5f9..564a063 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java @@ -70,7 +70,7 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService { private AppQueueConsumerManager, 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(); } diff --git a/jcpp-infrastructure-proto/src/main/proto/protocol.proto b/jcpp-infrastructure-proto/src/main/proto/protocol.proto index 96de82b..0a434f6 100644 --- a/jcpp-infrastructure-proto/src/main/proto/protocol.proto +++ b/jcpp-infrastructure-proto/src/main/proto/protocol.proto @@ -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; diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java index 75aa1c5..715c837 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java @@ -39,5 +39,7 @@ public enum DownlinkCmdEnum { OFFLINE_CARD_BALANCE_UPDATE_REQUEST, - OFFLINE_CARD_SYNC_REQUEST + OFFLINE_CARD_SYNC_REQUEST, + + START_CHARGE_ACK } \ No newline at end of file diff --git a/jcpp-protocol-yunkuaichong/READMD.md b/jcpp-protocol-yunkuaichong/READMD.md index 8b4222b..407d40e 100644 --- a/jcpp-protocol-yunkuaichong/READMD.md +++ b/jcpp-protocol-yunkuaichong/READMD.md @@ -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 上行启动应答 diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongPasswordRequiredEnum.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongPasswordRequiredEnum.java new file mode 100644 index 0000000..153d961 --- /dev/null +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongPasswordRequiredEnum.java @@ -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; + } +} diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartChargeFailureReasonEnum.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartChargeFailureReasonEnum.java new file mode 100644 index 0000000..1d7221b --- /dev/null +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartChargeFailureReasonEnum.java @@ -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; + } +} diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartTypeEnum.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartTypeEnum.java new file mode 100644 index 0000000..3630d7a --- /dev/null +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/enums/YunKuaiChongStartTypeEnum.java @@ -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; + } +} diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java index 27a08c4..9eb9e23 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java @@ -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); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeAckDLCmd.java new file mode 100644 index 0000000..113ced6 --- /dev/null +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeAckDLCmd.java @@ -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)); + } + } +} diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeULCmd.java new file mode 100644 index 0000000..21f410e --- /dev/null +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150StartChargeULCmd.java @@ -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(); + } + +} \ No newline at end of file