From babd1696f61c306ea7e591edd712dcfceacaba31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=A2=E4=B8=AD?= <851474045@qq.com> Date: Wed, 20 Aug 2025 11:21:10 +0000 Subject: [PATCH] =?UTF-8?q?!21=20=E7=BB=BF=E8=83=BD109=EF=BC=8C103?= =?UTF-8?q?=EF=BC=8C102=EF=BC=8C101=EF=BC=8C203=EF=BC=8C201=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E6=8F=90=E4=BA=A4=20*=20=E5=90=88=E5=B9=B6=E5=90=8E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9=20*=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E5=90=8E=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9=20*=20Merge=20rem?= =?UTF-8?q?ote-tracking=20branch=20'refs/remotes/upstream/master'=20into?= =?UTF-8?q?=20maste=E2=80=A6=20*=20PR=E5=90=8E=E4=BB=A3=E7=A0=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20*=20109)=E5=85=85=E7=94=B5=E6=A1=A9=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=BF=A1=E6=81=AF=E5=8C=85=E4=B8=8A=E6=8A=A5=20*=2010?= =?UTF-8?q?9)=E5=85=85=E7=94=B5=E6=A1=A9=E7=8A=B6=E6=80=81=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=8C=85=E4=B8=8A=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DefaultPileProtocolService.java | 5 + .../src/main/proto/protocol.proto | 1 + jcpp-protocol-lvneng/READMD.md | 16 + .../lvneng/enums/LvnengAlarmCodeEnum.java | 158 ++++++ .../lvneng/enums/LvnengDownlinkCmdEnum.java | 9 +- .../enums/LvnengPileFinishReasonEnum.java | 511 ++++++++++++++++++ .../lvneng/enums/LvnengPileStartTypeEnum.java | 63 +++ .../lvneng/enums/LvnengPileStatusEnum.java | 63 +++ .../v340/cmd/LvnengV340HeartbeatULCmd.java | 91 ++++ .../v340/cmd/LvnengV340RealTimeDataULCmd.java | 320 +++++++++++ .../LvnengV340TransactionRecordAckDLCmd.java | 77 +++ .../cmd/LvnengV340TransactionRecordULCmd.java | 229 ++++++++ 12 files changed, 1541 insertions(+), 2 deletions(-) create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengAlarmCodeEnum.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileFinishReasonEnum.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStartTypeEnum.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStatusEnum.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340HeartbeatULCmd.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordAckDLCmd.java create mode 100644 jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java 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 9edc53c..879a234 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 @@ -6,6 +6,7 @@ */ package sanbing.jcpp.app.service.impl; +import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -21,6 +22,7 @@ import sanbing.jcpp.infrastructure.proto.model.PricingModel; import sanbing.jcpp.infrastructure.proto.model.PricingModel.FlagPrice; import sanbing.jcpp.infrastructure.proto.model.PricingModel.Period; import sanbing.jcpp.infrastructure.queue.Callback; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; import sanbing.jcpp.proto.gen.ProtocolProto.*; import sanbing.jcpp.protocol.domain.DownlinkCmdEnum; @@ -254,6 +256,7 @@ public class DefaultPileProtocolService implements PileProtocolService { @Override public void onTransactionRecordRequest(UplinkQueueMessage uplinkQueueMessage, Callback callback) { log.info("接收到充电桩交易记录上报 {}", uplinkQueueMessage); + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); TransactionRecordRequest transactionRecordRequest = uplinkQueueMessage.getTransactionRecordRequest(); @@ -262,10 +265,12 @@ public class DefaultPileProtocolService implements PileProtocolService { // 构造下行计费 DownlinkRequestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, pileCode); + downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.TRANSACTION_RECORD_ACK.name()); downlinkMessageBuilder.setTransactionRecordResponse(TransactionRecordResponse.newBuilder() .setTradeNo(tradeNo) .setSuccess(true) + .setAdditionalInfo(additionalInfo.toString()) .build()); downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode); diff --git a/jcpp-infrastructure-proto/src/main/proto/protocol.proto b/jcpp-infrastructure-proto/src/main/proto/protocol.proto index 87faf12..bf41b26 100644 --- a/jcpp-infrastructure-proto/src/main/proto/protocol.proto +++ b/jcpp-infrastructure-proto/src/main/proto/protocol.proto @@ -344,6 +344,7 @@ message BmsChargingErrorProto { message TransactionRecordResponse { string tradeNo = 6; bool success = 7; + optional string additionalInfo = 20; } message BmsParamConfigReportProto { diff --git a/jcpp-protocol-lvneng/READMD.md b/jcpp-protocol-lvneng/READMD.md index 54b4bf1..8bd2b46 100644 --- a/jcpp-protocol-lvneng/READMD.md +++ b/jcpp-protocol-lvneng/READMD.md @@ -16,5 +16,21 @@ #### 109 充电桩状态信息包上报 `AAF5D00010C46D00000000003230323331323132303030303130000000000000000000000000000000000000010101000000000000000000000020250808105527FF00000000000000000000000000000000000000000000000000000000000000F567720000000000F5677200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003232460000000000000000000000000000000000000044` +#### 103 服务器应答充电桩状态信息包 +`AA F5 0E 00 10 04 67 00 00 00 00 00 01 68 ` --- + +#### 203 充电桩上报充电订单 +`AA F5 95 01 10 08 CB 00 00 00 00 00 32 30 32 33 31 32 31 32 30 30 30 30 31 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 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 20 25 08 12 20 07 08 FF 20 25 08 12 20 22 06 FF 82 03 00 00 1E 3A 02 01 00 00 F3 3A 00 00 94 63 4F 01 00 00 00 00 87 9E 4F 01 00 00 00 00 42 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4C 4E 42 53 43 31 33 4B 58 50 5A 30 34 36 35 34 34 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 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 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 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 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 00 00 00 F3 3A 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 01 31 30 36 30 33 30 33 37 35 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F3 02 00 00 3B` +#### 201 服务器应答订单信息 +`AA F5 32 00 10 02 C9 00 00 00 00 00 02 31 30 36 30 33 30 33 37 35 32 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 C6` + +--- + +#### 102 充电桩上传心跳包信息 +`AA F5 3F 00 10 02 66 00 00 00 00 00 32 30 32 33 31 32 31 32 30 30 30 30 31 30 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 00 00 00 14` +#### 101 服务器应答心跳包信息 +`AA F5 0F 00 10 03 65 00 00 00 00 00 00 00 65` + +--- \ No newline at end of file diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengAlarmCodeEnum.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengAlarmCodeEnum.java new file mode 100644 index 0000000..cf5a253 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengAlarmCodeEnum.java @@ -0,0 +1,158 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +/** + * 告警码-故障码定义 + */ +@AllArgsConstructor +@Getter +public enum LvnengAlarmCodeEnum { + + CODE_304(304, "通道匹配异常"), + CODE_305(305, "主输出接触器故障"), + CODE_307(307, "交流接触器故障"), + CODE_308(308, "熔断器故障"), + CODE_309(309, "防雷故障"), + CODE_310(310, "电子锁故障"), + CODE_312(312, "充电模块风扇故障"), + CODE_313(313, "直流输出过压"), + CODE_314(314, "直流输出欠压"), + CODE_315(315, "直流输出过流"), + CODE_316(316, "车辆端口电压负压"), + CODE_317(317, "交流输入过压"), + CODE_318(318, "交流输入欠压"), + CODE_319(319, "交流输入频率过频"), + CODE_320(320, "交流输入频率欠频"), + CODE_321(321, "交流输入电压不平衡"), + CODE_322(322, "交流输入 A 相缺相"), + CODE_323(323, "交流输入 B 相缺相"), + CODE_324(324, "交流输入 C 相缺相"), + CODE_325(325, "交流输入过载"), + CODE_326(326, "交流输入异常"), + CODE_327(327, "充电模块输出过压"), + CODE_328(328, "充电模块过流"), + CODE_329(329, "充电模块过温"), + CODE_330(330, "环境温度过温"), + CODE_331(331, "环境温度过低"), + CODE_332(332, "系统无可用模块"), + CODE_333(333, "充电模块命令执行失败"), + CODE_334(334, "充电控制器通信故障"), + CODE_335(335, "采集板通讯故障"), + CODE_336(336, "电表离线"), + CODE_337(337, "与集控器通信中断"), + CODE_338(338, "读卡器通信故障"), + CODE_339(339, "绝缘故障"), + CODE_340(340, "系统模块混插"), + CODE_341(341, "系统急停故障"), + CODE_342(342, "主从通信异常"), + CODE_343(343, "电表校验错误"), + CODE_344(344, "系统门磁故障"), + CODE_345(345, "系统风机故障"), + CODE_346(346, "并联接触器故障"), + CODE_347(347, "并联接触器驱动失效"), + CODE_348(348, "绝缘监测告警"), + CODE_351(351, "TMU ID 重复"), + CODE_352(352, "BMS 数据异常"), + CODE_353(353, "电池单体过压"), + CODE_354(354, "电池整包过压保护"), + CODE_355(355, "电池过流保护"), + CODE_356(356, "电池过充保护"), + CODE_357(357, "电池电压异常"), + CODE_358(358, "电池低温保护"), + CODE_359(359, "BMS 热失控"), + CODE_360(360, "BMS 辅源异常"), + CODE_361(361, "CMU 辅源异常"), + CODE_362(362, "车辆继电器开路保护"), + CODE_364(364, "TMU 过温"), + CODE_368(368, "电池电压过高"), + CODE_369(369, "电池 SOC 过高"), + CODE_370(370, "电池 SOC 过低"), + CODE_371(371, "电池充电过流"), + CODE_372(372, "电池温度过高"), + CODE_373(373, "电池绝缘故障"), + CODE_374(374, "电池输出连接器异常"), + CODE_385(385, "CC1 电压异常"), + CODE_386(386, "TMU 所有通道不匹配"), + CODE_387(387, "电表参数不匹配"), + CODE_388(388, "枪头短路"), + CODE_389(389, "TMU 条码异常"), + CODE_390(390, "TMU 风扇故障"), + CODE_391(391, "充电系统不匹配"), + CODE_392(392, "模块开启失败"), + CODE_394(394, "交流接触器反馈故障"), + CODE_432(432, "湿度告警故障"), + CODE_433(433, "枪头过温告警"), + CODE_434(434, "系统过温故障"), + CODE_435(435, "机柜浸水故障"), + CODE_436(436, "机柜倾倒故障"), + CODE_437(437, "机柜烟雾故障"), + CODE_442(442, "CMU ID 重复"), + CODE_443(443, "机柜防尘网告警"), + CODE_506(506, "未定义的错误"), + CODE_513(513, "IO 板离线"), + CODE_514(514, "IO 板故障"), + CODE_515(515, "TMU 互锁异常"), + CODE_516(516, "输出接触器前级电压异常"), + CODE_517(517, "模块地址超范围或地址重复"), + CODE_518(518, "模块槽位不匹配"), + CODE_519(519, "TMU 急停故障"), + CODE_520(520, "CMU 门磁故障"), + CODE_521(521, "TMU 门磁故障"), + CODE_522(522, "IO 板互锁故障"), + CODE_523(523, "CMU 互锁异常"), + CODE_524(524, "CMU 无可用模块故障"), + CODE_525(525, "交流输入接触器拒动/误动故障"), + CODE_526(526, "交流输入接触器粘连故障"), + CODE_528(528, "泄放回路故障"), + CODE_529(529, "TMU 无模块可用故障"), + CODE_531(531, "TCU 离线故障"), + CODE_532(532, "电表分流器反接故障"), + CODE_533(533, "绝缘检测仪通信故障"), + CODE_534(534, "终端 TCU 平台类型未配置"), + CODE_535(535, "车辆接触器粘连"), + CODE_536(536, "TMU 程序与终端类型不匹配"), + CODE_537(537, "SW2 与 SW3 拨码设置错误"), + CODE_538(538, "TMU 拨码地址超范围"); + + /** + * 故障代码 + */ + private final int code; + + /** + * 故障描述 + */ + private final String description; + + + private static final String UNKNOWN_DESC = "未知故障码"; + private static final Map CODE_TO_ENUM_MAP = new HashMap<>(); + + static { + for (LvnengAlarmCodeEnum enumValue : LvnengAlarmCodeEnum.values()) { + CODE_TO_ENUM_MAP.put(enumValue.getCode(), enumValue); + } + } + + public static String getByCode(Long code) { + LvnengAlarmCodeEnum lvnengAlarmCodeEnum = CODE_TO_ENUM_MAP.get(code.intValue()); + if (lvnengAlarmCodeEnum == null) { + return UNKNOWN_DESC; + } + return lvnengAlarmCodeEnum.getDescription(); + + + } + +} \ No newline at end of file diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java index 925b022..ca801d5 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengDownlinkCmdEnum.java @@ -18,8 +18,13 @@ public enum LvnengDownlinkCmdEnum { LOGIN_ACK((short) 105), - SYNC_TIME((short) 3) - ; + SYNC_TIME((short) 3), + + HEARTBEAT_ACK((short) 101), + + REAL_TIME_DATA_ACK((short) 103), + + TRANSACTION_RECORD_ACK((short) 201); private final short cmd; diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileFinishReasonEnum.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileFinishReasonEnum.java new file mode 100644 index 0000000..30afd46 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileFinishReasonEnum.java @@ -0,0 +1,511 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +/** + * 充电桩结束原因编码定义 + */ +@AllArgsConstructor +@Getter +public enum LvnengPileFinishReasonEnum { + // 中止代码1 → 上报值:1 + 256 = 257 + CODE_257(257, "BMS 中止"), + + // 中止代码2 → 上报值:2 + 256 = 258 + CODE_258(258, "平台中止"), + + // 中止代码3 → 上报值:3 + 256 = 259 + CODE_259(259, "人工设定条件中止"), + + // 中止代码4 → 上报值:4 + 256 = 260 + CODE_260(260, "人工中止"), + + // 中止代码5 → 上报值:5 + 256 = 261 + CODE_261(261, "充电机故障中止"), + + // 中止代码6 → 上报值:6 + 256 = 262 + CODE_262(262, "连接器故障中止"), + + // 中止代码7 → 上报值:7 + 256 = 263 + CODE_263(263, "连接器拔出中止"), + + // 中止代码9 → 上报值:9 + 256 = 265 + CODE_265(265, "主接触器异常中止"), + + // 中止代码10 → 上报值:10 + 256 = 266 + CODE_266(266, "并联接触器异常中止"), + + // 中止代码11 → 上报值:11 + 256 = 267 + CODE_267(267, "电子锁异常中止"), + + // 中止代码12 → 上报值:12 + 256 = 268 + CODE_268(268, "充电电压异常中止"), + + // 中止代码13 → 上报值:13 + 256 = 269 + CODE_269(269, "充电电流小中止"), + + // 中止代码14 → 上报值:14 + 256 = 270 + CODE_270(270, "充电电流过流中止"), + + // 中止代码15 → 上报值:15 + 256 = 271 + CODE_271(271, "充电电压不匹配中止"), + + // 中止代码16 → 上报值:16 + 256 = 272 + CODE_272(272, "BMS 数据不刷新中止"), + + // 中止代码17 → 上报值:17 + 256 = 273 + CODE_273(273, "电能表通信故障中止"), + + // 中止代码19 → 上报值:19 + 256 = 275 + CODE_275(275, "SOC 满中止"), + + // 中止代码22 → 上报值:22 + 256 = 278 + CODE_278(278, "主动防护电池过温中止"), + + // 中止代码23 → 上报值:23 + 256 = 279 + CODE_279(279, "主动防护电池低温中止"), + + // 中止代码24 → 上报值:24 + 256 = 280 + CODE_280(280, "主动防护电池热失控中止"), + + // 中止代码25 → 上报值:25 + 256 = 281 + CODE_281(281, "主动防护电池过充中止"), + + // 中止代码26 → 上报值:26 + 256 = 282 + CODE_282(282, "BMS 辅助电源异常中止"), + + // 中止代码27 → 上报值:27 + 256 = 283 + CODE_283(283, "BMS 接触器开路故障中止"), + + // 中止代码28 → 上报值:28 + 256 = 284 + CODE_284(284, "主动防护 BMS 数据超范围中止"), + + // 中止代码29 → 上报值:29 + 256 = 285 + CODE_285(285, "TMU 过温中止"), + + // 中止代码30 → 上报值:30 + 256 = 286 + CODE_286(286, "主动防护电池端口电压异常中止"), + + // 中止代码33 → 上报值:33 + 256 = 289 + CODE_289(289, "拔枪中止"), + + // 中止代码34 → 上报值:34 + 256 = 290 + CODE_290(290, "设备过温中止"), + + // 中止代码35 → 上报值:35 + 256 = 291 + CODE_291(291, "过流中止"), + + // 中止代码36 → 上报值:36 + 256 = 292 + CODE_292(292, "过压中止"), + + // 中止代码37 → 上报值:37 + 256 = 293 + CODE_293(293, "欠压中止"), + + // 中止代码40 → 上报值:40 + 256 = 296 + CODE_296(296, "充电枪座过温中止"), + + // 中止代码43 → 上报值:43 + 256 = 299 + CODE_299(299, "枪头过温中止"), + + // 中止代码44 → 上报值:44 + 256 = 300 + CODE_300(300, "模块开启失败中止"), + + // 中止代码46 → 上报值:46 + 256 = 302 + CODE_302(302, "浸水故障中止"), + + // 中止代码49 → 上报值:49 + 256 = 305 + CODE_305(305, "结束中不响应启动充电命令中止"), + + // 中止代码51 → 上报值:51 + 256 = 307 + CODE_307(307, "主动防护电池单体过压中止"), + + // 中止代码52 → 上报值:52 + 256 = 308 + CODE_308(308, "主动防护电池整包过压中止"), + + // 中止代码53 → 上报值:53 + 256 = 309 + CODE_309(309, "电表校验错误中止"), + + // 中止代码54 → 上报值:54 + 256 = 310 + CODE_310(310, "BMS 通讯超时中止"), + + // 中止代码55 → 上报值:55 + 256 = 311 + CODE_311(311, "充电电压不匹配中止"), + + // 中止代码56 → 上报值:56 + 256 = 312 + CODE_312(312, "系统风扇故障中止"), + + // 中止代码57 → 上报值:57 + 256 = 313 + CODE_313(313, "CMU 急停中止"), + + // 中止代码58 → 上报值:58 + 256 = 314 + CODE_314(314, "充电机绝缘异常中止"), + + // 中止代码59 → 上报值:59 + 256 = 315 + CODE_315(315, "充电机无可用模块中止"), + + // 中止代码62 → 上报值:62 + 256 = 318 + CODE_318(318, "熔断器故障中止"), + + // 中止代码63 → 上报值:63 + 256 = 319 + CODE_319(319, "模块匹配异常中止"), + + // 中止代码64 → 上报值:64 + 256 = 320 + CODE_320(320, "配置或升级中不响应启动充电中止"), + + // 中止代码65 → 上报值:65 + 256 = 321 + CODE_321(321, "主动防护未解除中止"), + + // 中止代码66 → 上报值:66 + 256 = 322 + CODE_322(322, "车辆电池达到目标 SOC 中止"), + + // 中止代码67 → 上报值:67 + 256 = 323 + CODE_323(323, "车辆达到总电压目标值中止"), + + // 中止代码68 → 上报值:68 + 256 = 324 + CODE_324(324, "车辆达到单体电压目标值中止"), + + // 中止代码69 → 上报值:69 + 256 = 325 + CODE_325(325, "车辆 bms 绝缘故障中止"), + + // 中止代码70 → 上报值:70 + 256 = 326 + CODE_326(326, "车辆输出连接器过温故障中止"), + + // 中止代码71 → 上报值:71 + 256 = 327 + CODE_327(327, "车辆 BMS 元件、输出连接器过温中止"), + + // 中止代码72 → 上报值:72 + 256 = 328 + CODE_328(328, "车辆充电连接器故障中止"), + + // 中止代码73 → 上报值:73 + 256 = 329 + CODE_329(329, "车辆电池组温度过高故障中止"), + + // 中止代码74 → 上报值:74 + 256 = 330 + CODE_330(330, "车辆高压继电器故障中止"), + + // 中止代码75 → 上报值:75 + 256 = 331 + CODE_331(331, "车辆监测点 CC2 电压检测故障中止"), + + // 中止代码76 → 上报值:76 + 256 = 332 + CODE_332(332, "车辆其他故障"), + + // 中止代码77 → 上报值:77 + 256 = 333 + CODE_333(333, "车辆电流过大中止"), + + // 中止代码78 → 上报值:78 + 256 = 334 + CODE_334(334, "车辆电压异常中止"), + + // 中止代码79 → 上报值:79 + 256 = 335 + CODE_335(335, "BMS 中止"), + + // 中止代码91 → 上报值:91 + 256 = 347 + CODE_347(347, "终端离线中止"), + + // 中止代码92 → 上报值:92 + 256 = 348 + CODE_348(348, "平台离线中止"), + + // 中止代码93 → 上报值:93 + 256 = 349 + CODE_349(349, "充电命令超时中止"), + + // 中止代码94 → 上报值:94 + 256 = 350 + CODE_350(350, "启动超时中止"), + + // 中止代码95 → 上报值:95 + 256 = 351 + CODE_351(351, "放电启动超时中止"), + + // 中止代码96 → 上报值:96 + 256 = 352 + CODE_352(352, "非法启动中止"), + + // 中止代码97 → 上报值:97 + 256 = 353 + CODE_353(353, "显示屏操作中止"), + + // 中止代码98 → 上报值:98 + 256 = 354 + CODE_354(354, "内部 VIN 申请中止"), + + // 中止代码99 → 上报值:99 + 256 = 355 + CODE_355(355, "显示屏刷卡中止"), + + // 中止代码100 → 上报值:100 + 256 = 356 + CODE_356(356, "异常电度中止"), + + // 中止代码101 → 上报值:101 + 256 = 357 + CODE_357(357, "余额不足中止"), + + // 中止代码102 → 上报值:102 + 256 = 358 + CODE_358(358, "定电量充中止"), + + // 中止代码103 → 上报值:103 + 256 = 359 + CODE_359(359, "定时间充中止"), + + // 中止代码104 → 上报值:104 + 256 = 360 + CODE_360(360, "错峰充电中止"), + + // 中止代码105 → 上报值:105 + 256 = 361 + CODE_361(361, "定金额充中止"), + + // 中止代码134 → 上报值:134 + 256 = 390 + CODE_390(390, "短路故障中止"), + + // 中止代码136 → 上报值:136 + 256 = 392 + CODE_392(392, "车辆端口电压负压"), + + // 中止代码149 → 上报值:149 + 256 = 405 + CODE_405(405, "机柜倾倒中止"), + + // 中止代码150 → 上报值:150 + 256 = 406 + CODE_406(406, "机柜烟雾故障中止"), + + // 中止代码151 → 上报值:151 + 256 = 407 + CODE_407(407, "机柜温湿度报警故障中止"), + + // 中止代码257 → 上报值:257 + 256 = 513 + CODE_513(513, "IO 板离线中止"), + + // 中止代码258 → 上报值:258 + 256 = 514 + CODE_514(514, "TMU 与 CMU 离线中止"), + + // 中止代码265 → 上报值:265 + 256 = 521 + CODE_521(521, "CMU 门磁触发中止"), + + // 中止代码266 → 上报值:266 + 256 = 522 + CODE_522(522, "TMU 急停中止"), + + // 中止代码267 → 上报值:267 + 256 = 523 + CODE_523(523, "TMU 互锁异常中止"), + + // 中止代码268 → 上报值:268 + 256 = 524 + CODE_524(524, "输出接触器前级电压异常中止"), + + // 中止代码269 → 上报值:269 + 256 = 525 + CODE_525(525, "TMU 门磁触发中止"), + + // 终止代码270 → 上报值:270 + 256 = 526 + CODE_526(526, "IO 板互锁异常中止"), + + // 终止代码271 → 上报值:271 + 256 = 527 + CODE_527(527, "CMU 互锁异常中止"), + + // 终止代码272 → 上报值:272 + 256 = 528 + CODE_528(528, "CMU 无可用模块中止"), + + // 终止代码273 → 上报值:273 + 256 = 529 + CODE_529(529, "TMU 无可用模块中止"), + + // 终止代码274 → 上报值:274 + 256 = 530 + CODE_530(530, "交流输入接触器拒动/误动中止"), + + // 终止代码275 → 上报值:275 + 256 = 531 + CODE_531(531, "交流输入接触器粘连中止"), + + // 终止代码276 → 上报值:276 + 256 = 532 + CODE_532(532, "交流输入异常中止"), + + // 终止代码277 → 上报值:277 + 256 = 533 + CODE_533(533, "泄放回路故障中止"), + + // 终止代码278 → 上报值:278 + 256 = 534 + CODE_534(534, "BMS 上报电压异常中止"), + + // 终止代码279 → 上报值:279 + 256 = 535 + CODE_535(535, "电池电压低中止"), + + // 终止代码280 → 上报值:280 + 256 = 536 + CODE_536(536, "直流输出接触器后级电压异常中止"), + + // 终止代码282 → 上报值:282 + 256 = 538 + CODE_538(538, "BRM 数据异常中止"), + + // 终止代码283 → 上报值:283 + 256 = 539 + CODE_539(539, "BCP 数据异常中止"), + + // 终止代码284 → 上报值:284 + 256 = 540 + CODE_540(540, "BRO 超时中止"), + + // 终止代码285 → 上报值:285 + 256 = 541 + CODE_541(541, "BCL 超时中止"), + + // 终止代码286 → 上报值:286 + 256 = 542 + CODE_542(542, "BCS 超时中止"), + + // 终止代码287 → 上报值:287 + 256 = 543 + CODE_543(543, "BMS 通信协议版本不匹配中止"), + + // 终止代码288 → 上报值:288 + 256 = 544 + CODE_544(544, "预充阶段调压失败中止"), + + // 终止代码289 → 上报值:289 + 256 = 545 + CODE_545(545, "防雷器故障中止"), + + // 终止代码290 → 上报值:290 + 256 = 546 + CODE_546(546, "交流输入断路器故障中止"), + + // 终止代码291 → 上报值:291 + 256 = 547 + CODE_547(547, "充电中辅源故障"), + + // 终止代码292 → 上报值:292 + 256 = 548 + CODE_548(548, "直流母线输出电压过压故障中止"), + + // 终止代码293 → 上报值:293 + 256 = 549 + CODE_549(549, "直流母线输出电压欠压故障中止"), + + // 终止代码294 → 上报值:294 + 256 = 550 + CODE_550(550, "直流母线输出电流过流故障中止"), + + // 终止代码295 → 上报值:295 + 256 = 551 + CODE_551(551, "电池最高允许电压小于充电机最小电压"), + + // 终止代码296 → 上报值:296 + 256 = 552 + CODE_552(552, "绝缘监测前直流输出接触器外侧电压≥10 V"), + + // 终止代码297 → 上报值:297 + 256 = 553 + CODE_553(553, "电池端电压大于电池最高允许充电电压"), + + // 终止代码298 → 上报值:298 + 256 = 554 + CODE_554(554, "BRM 超时中止"), + + // 终止代码299 → 上报值:299 + 256 = 555 + CODE_555(555, "BCP 超时中止"), + + // 终止代码300 → 上报值:300 + 256 = 556 + CODE_556(556, "BRO 超时中止(0xAA)"), + + // 终止代码301 → 上报值:301 + 256 = 557 + CODE_557(557, "灯板通信故障"), + + // 终止代码302 → 上报值:302 + 256 = 558 + CODE_558(558, "BSM 超时"), + + // 终止代码303 → 上报值:303 + 256 = 559 + CODE_559(559, "BSM 报文中单体动力蓄电池电压过压"), + + // 终止代码304 → 上报值:304 + 256 = 560 + CODE_560(560, "BSM 报文中单体动力蓄电池电压过低"), + + // 终止代码305 → 上报值:305 + 256 = 561 + CODE_561(561, "BSM 报文中 SOC 过高"), + + // 终止代码306 → 上报值:306 + 256 = 562 + CODE_562(562, "BSM 报文中 SOC 过低"), + + // 终止代码307 → 上报值:307 + 256 = 563 + CODE_563(563, "BSM 报文中充电过电流"), + + // 终止代码308 → 上报值:308 + 256 = 564 + CODE_564(564, "BSM 报文中动力蓄电池温度过高"), + + // 终止代码309 → 上报值:309 + 256 = 565 + CODE_565(565, "BSM 报文中动力蓄电池绝缘状态异常"), + + // 终止代码310 → 上报值:310 + 256 = 566 + CODE_566(566, "BSM 报文中连接器连接状态异常"), + + // 终止代码311 → 上报值:311 + 256 = 567 + CODE_567(567, "双枪同充辅枪充电异常"), + + // 终止代码313 → 上报值:313 + 256 = 569 + CODE_569(569, "TCU 离线中止"), + + // 终止代码314 → 上报值:314 + 256 = 570 + CODE_570(570, "电表分流器反接故障"), + + // 终止代码315 → 上报值:315 + 256 = 571 + CODE_571(571, "终端按钮中止"), + + // 终止代码316 → 上报值:316 + 256 = 572 + CODE_572(572, "安全防护电池包单体过压保护中止"), + + // 终止代码317 → 上报值:317 + 256 = 573 + CODE_573(573, "安全防护电池过温保护中止"), + + // 终止代码318 → 上报值:318 + 256 = 574 + CODE_574(574, "安全防护电池温升热失控异常保护中止"), + + // 终止代码319 → 上报值:319 + 256 = 575 + CODE_575(575, "安全防护温差过大保护中止"), + + // 终止代码320 → 上报值:320 + 256 = 576 + CODE_576(576, "安全防护 BMS 数据不更新保护中止"), + + // 终止代码321 → 上报值:321 + 256 = 577 + CODE_577(577, "安全防护充电电量过充保护中止"), + + // 终止代码322 → 上报值:322 + 256 = 578 + CODE_578(578, "安全防护整车充电过流保护中止"), + + // 终止代码323 → 上报值:323 + 256 = 579 + CODE_579(579, "安全防护整车电池包过压保护中止"), + + // 终止代码324 → 上报值:324 + 256 = 580 + CODE_580(580, "安全防护 SOC 数据异常保护中止"), + + // 终止代码325 → 上报值:325 + 256 = 581 + CODE_581(581, "安全防护 BCP 电池电压异常保护中止"), + + // 终止代码326 → 上报值:326 + 256 = 582 + CODE_582(582, "安全防护 BCP 电压与实际电池电压不符保护中止"), + + // 终止代码327 → 上报值:327 + 256 = 583 + CODE_583(583, "安全防护电芯电压极差异常保护中止"), + + // 终止代码328 → 上报值:328 + 256 = 584 + CODE_584(584, "安全防护设备空闲枪头带电异常保护中止"), + + // 终止代码329 → 上报值:329 + 256 = 585 + CODE_585(585, "CHademo 充电车辆主动中止"), + + // 终止代码330 → 上报值:330 + 256 = 586 + CODE_586(586, "CHademo 充电 Premission 信号异常中止"), + + // 终止代码331 → 上报值:331 + 256 = 587 + CODE_587(587, "CHademo 充电车辆接触器异常中止"), + + // 终止代码332 → 上报值:332 + 256 = 588 + CODE_588(588, "CHademo 充电车辆故障中止"), + + // 终止代码333 → 上报值:333 + 256 = 589 + CODE_589(589, "CHademo 充电通信超时中止"), + + // 终止代码334 → 上报值:334 + 256 = 590 + CODE_590(590, "CHademo 充电车辆报充电机异常中止"), + + // 终止代码335 → 上报值:335 + 256 = 591 + CODE_591(591, "绝缘检测仪通信故障中止"), + + // 终止代码336 → 上报值:336 + 256 = 592 + CODE_592(592, "液冷 AB 板 CAN3 离线中止"); + + + private final int code; + + private final String description; + + + private static final String UNKNOWN_DESC = "未知结束原因"; + private static final Map CODE_TO_ENUM_MAP = new HashMap<>(); + + static { + for (LvnengPileFinishReasonEnum enumValue : LvnengPileFinishReasonEnum.values()) { + CODE_TO_ENUM_MAP.put(enumValue.getCode(), enumValue); + } + } + + public static String getByCode(Long code) { + LvnengPileFinishReasonEnum lvnengPileFinishReasonEnum = CODE_TO_ENUM_MAP.get(code.intValue()); + if (lvnengPileFinishReasonEnum == null) { + return UNKNOWN_DESC; + } + return lvnengPileFinishReasonEnum.getDescription(); + + + } + +} \ No newline at end of file diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStartTypeEnum.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStartTypeEnum.java new file mode 100644 index 0000000..2b59141 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStartTypeEnum.java @@ -0,0 +1,63 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +/** + * 充电桩启动类型枚举 + */ +@AllArgsConstructor +@Getter +public enum LvnengPileStartTypeEnum { + /** + * 0:刷卡启动 + * 1:服务器启动 + * 2:本地管理员启动 + * 3:VIN 启动 生产个枚举类 + */ + + CARD_SWIPE(0, "刷卡启动"), + + + SERVER(1, "服务器启动"), + + + LOCAL_ADMIN(2, "本地管理员启动"), + + + VIN(3, "VIN启动"); + + + private final int code; + + private final String description; + + private static final String UNKNOWN_DESC = "未知类型"; + private static final Map CODE_TO_ENUM_MAP = new HashMap<>(); + + static { + for (LvnengPileStartTypeEnum enumValue : LvnengPileStartTypeEnum.values()) { + CODE_TO_ENUM_MAP.put(enumValue.getCode(), enumValue); + } + } + + public static String getByCode(int code) { + LvnengPileStartTypeEnum lvnengPileStartTypeEnum = CODE_TO_ENUM_MAP.get(code); + if (lvnengPileStartTypeEnum == null) { + return UNKNOWN_DESC; + } + return lvnengPileStartTypeEnum.getDescription(); + + + } + +} \ No newline at end of file diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStatusEnum.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStatusEnum.java new file mode 100644 index 0000000..943b2a9 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/enums/LvnengPileStatusEnum.java @@ -0,0 +1,63 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +/** + * 充电桩状态枚举 + */ +@AllArgsConstructor +@Getter +public enum LvnengPileStatusEnum { + /** + * 0-空闲中 + * 1-正准备开始充电 + * 2-充电进行中 + * 3-充电结東 + * 4-启动失败 + * 5-预约状态 + * 6-系统故障(不能给汽车充电) + * 注:目前工作状态只有 0,2,6 + */ + + IDLE(0, "空闲中"), + + CHARGING(2, "充电进行中"), + + SYSTEM_FAILURE(6, "系统故障(不能给汽车充电)"); + + + private final int code; + + private final String description; + + + private static final String UNKNOWN_DESC = "未知状态"; + private static final Map CODE_TO_ENUM_MAP = new HashMap<>(); + + static { + for (LvnengPileStatusEnum enumValue : LvnengPileStatusEnum.values()) { + CODE_TO_ENUM_MAP.put(enumValue.getCode(), enumValue); + } + } + + public static String getByCode(int code) { + LvnengPileStatusEnum lvnengPileStatusEnum = CODE_TO_ENUM_MAP.get(code); + if (lvnengPileStatusEnum == null) { + return UNKNOWN_DESC; + } + return lvnengPileStatusEnum.getDescription(); + + + } + +} \ No newline at end of file diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340HeartbeatULCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340HeartbeatULCmd.java new file mode 100644 index 0000000..f3b72e2 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340HeartbeatULCmd.java @@ -0,0 +1,91 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.v340.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.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.lvneng.LvnengUplinkCmdExe; +import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage; +import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd; + +import java.nio.charset.StandardCharsets; + +import static sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum.HEARTBEAT_ACK; + +/** + * 绿能3.4 充电桩上传心跳包 + */ +@Slf4j +@LvnengCmd(102) +public class LvnengV340HeartbeatULCmd extends LvnengUplinkCmdExe { + @Override + public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) { + log.debug("{} 绿能3.4充电桩上报心跳包请求", tcpSession); + ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody()); + + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + //1预留 + byteBuf.skipBytes(2); + //2预留 + byteBuf.skipBytes(2); + + //3充电桩编码 + byte[] pileCodeBytes = new byte[32]; + byteBuf.readBytes(pileCodeBytes); + String pileCode = StringUtils.trim(new String(pileCodeBytes, StandardCharsets.US_ASCII)); + + //4心跳序号 + int flag = byteBuf.readShortLE(); + additionalInfo.put("心跳序号", flag); + //5预留 + byteBuf.skipBytes(16); + + + tcpSession.addPileCode(pileCode); + + // 注册前置会话 + ctx.getProtocolSessionRegistryProvider().register(tcpSession); + + // 转发到后端 + HeartBeatRequest heartBeatRequest = HeartBeatRequest.newBuilder() + .setPileCode(pileCode) + .setRemoteAddress(tcpSession.getAddress().toString()) + .setNodeId(ctx.getServiceInfoProvider().getServiceId()) + .setNodeHostAddress(ctx.getServiceInfoProvider().getHostAddress()) + .setNodeRestPort(ctx.getServiceInfoProvider().getRestPort()) + .setNodeGrpcPort(ctx.getServiceInfoProvider().getGrpcPort()) + .setAdditionalInfo(additionalInfo.toString()) + .build(); + UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(heartBeatRequest.getPileCode(), tcpSession, lvnengUplinkMessage) + .setHeartBeatRequest(heartBeatRequest) + .build(); + + tcpSession.getForwarder().sendMessage(uplinkQueueMessage); + //服务器应答心跳包信息 + pingAck(tcpSession, flag); + } + + private void pingAck(TcpSession tcpSession, int flag) { + ByteBuf pingAckMsgBody = Unpooled.buffer(6); + pingAckMsgBody.writeShortLE(0); + pingAckMsgBody.writeShortLE(0); + pingAckMsgBody.writeShortLE(flag); + + encodeAndWriteFlush(HEARTBEAT_ACK, + pingAckMsgBody, + tcpSession); + } +} diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java new file mode 100644 index 0000000..cd95365 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java @@ -0,0 +1,320 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.v340.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; +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.lvneng.LvnengUplinkCmdExe; +import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage; +import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd; +import sanbing.jcpp.protocol.lvneng.enums.LvnengAlarmCodeEnum; +import sanbing.jcpp.protocol.lvneng.enums.LvnengPileStartTypeEnum; +import sanbing.jcpp.protocol.lvneng.enums.LvnengPileStatusEnum; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum.REAL_TIME_DATA_ACK; + +/** + * 绿能3.4 充电桩状态信息包上报 + */ +@Slf4j +@LvnengCmd(109) +public class LvnengV340RealTimeDataULCmd extends LvnengUplinkCmdExe { + @Override + public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) { + log.debug("{} 绿能3.4充电桩状态信息包上报请求", tcpSession); + ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody()); + + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + // 从Tracer总获取当前时间 + long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); + + //1预留 + byteBuf.skipBytes(2); + //2预留 + byteBuf.skipBytes(2); + + //3充电桩编码 + byte[] pileCodeBytes = new byte[32]; + byteBuf.readBytes(pileCodeBytes); + String pileCode = StringUtils.trim(new String(pileCodeBytes, StandardCharsets.US_ASCII)); + + //4 充电枪数量 + byte gunsNum = byteBuf.readByte(); + additionalInfo.put("充电枪数量", gunsNum); + + //5 充电枪口 + byte gunCode = byteBuf.readByte(); + additionalInfo.put("充电枪口", gunCode); + + //6 充电枪类型 + byte gunType = byteBuf.readByte(); + additionalInfo.put("充电枪类型", gunType); + + //7 工作状态 + byte pileStatus = byteBuf.readByte(); + String pileStatusCode = LvnengPileStatusEnum.getByCode(pileStatus); + additionalInfo.put("工作状态", pileStatusCode); + + //8 当前 SOC % + byte soc = byteBuf.readByte(); + additionalInfo.put("soc", soc); + + /** 9 告警码,0-无告警 非0参靠枚举类 + * @see LvnengAlarmCodeEnum + */ + long alarmCode = byteBuf.readUnsignedInt(); + String alarmCodeDesc = alarmCode == 0L ? "" : LvnengAlarmCodeEnum.getByCode(alarmCode); + additionalInfo.put("告警码", alarmCodeDesc); + + //10 车连接状态 + byte carLinkStatus = byteBuf.readByte(); + additionalInfo.put("车连接状态", getCarLinkDesc(carLinkStatus)); + + //11 本次充电累计充电费用 精度0.01元 + BigDecimal totalAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100, 2); + additionalInfo.put("本次充电累计充电费用", totalAmount); + //12 当前时间 + byte[] bytes = new byte[8]; + byteBuf.readBytes(bytes); + LocalDateTime localDateTime = BCDUtil.bcdToDate(bytes); + long instant = localDateTime!=null?localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli():0L; + additionalInfo.put("当前时间", instant); + + //13 直流充电电压 + short DCV = byteBuf.readShortLE(); + additionalInfo.put("直流充电电压", DCV); + + //14 直流充电电流 + short DCC = byteBuf.readShortLE(); + additionalInfo.put("直流充电电压", DCC); + + //15 BMS 需求电压 + short bmsDCV = byteBuf.readShortLE(); + additionalInfo.put(" BMS 需求电压", bmsDCV); + + //16 BMS 需求电流 + short bmsDCC = byteBuf.readShortLE(); + additionalInfo.put(" BMS 需求电压", bmsDCC); + + //17 BMS 充电模式 1- 恒压 2- 恒流 + byte bmsChargingType = byteBuf.readByte(); + additionalInfo.put(" BMS 需求电压", getBmsChargingTypeDesc(bmsChargingType)); + + //18 交流 A 相充电电压 + short aACV = byteBuf.readShortLE(); + additionalInfo.put("交流 A 相充电电压", aACV); + + //19 交流B相充电电压 + short bACV = byteBuf.readShortLE(); + additionalInfo.put("交流B相充电电压", bACV); + + //20 交流 C相充电电压 + short cACV = byteBuf.readShortLE(); + additionalInfo.put("交流 C相充电电压", cACV); + + //21 预留 + byteBuf.skipBytes(2); + //22 预留 + byteBuf.skipBytes(2); + //23 预留 + byteBuf.skipBytes(2); + + //24 剩余充电时间 min + short remainingTimeMin = byteBuf.readShortLE(); + additionalInfo.put("剩余充电时间 min", remainingTimeMin); + + //25 充电时长 秒 + long totalChargingTime = byteBuf.readUnsignedIntLE(); + additionalInfo.put("充电时长 秒", totalChargingTime); + + //26 本次充电累计充电电量 精度:0.001lkWh + BigDecimal totalChargingEnergyKWh = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000, 3); + additionalInfo.put("本次充电累计充电电量", totalChargingEnergyKWh.toString()); + + //27 充电前电表读数 精度:0.001lkWh + BigDecimal beforeNum = reduceMagnification(byteBuf.readLongLE(), 1000, 3); + additionalInfo.put("充电前电表读数", beforeNum.toString()); + + //28 当前电表读数 精度:0.001lkWh + BigDecimal currentNum = reduceMagnification(byteBuf.readLongLE(), 1000, 3); + additionalInfo.put("当前电表读数", currentNum.toString()); + + //29 充电启动方式 + byte chargingType = byteBuf.readByte(); + String chargingTypeDesc = LvnengPileStartTypeEnum.getByCode(chargingType); + additionalInfo.put("充电启动方式", chargingTypeDesc); + + //30 预留 + byteBuf.skipBytes(1); + //31 预留 + byteBuf.skipBytes(4); + //32 预留 + byteBuf.skipBytes(1); + + //33 云平台充电流水号/充电卡号 + byte[] tradeNoBytes = new byte[32]; + byteBuf.readBytes(tradeNoBytes); + String tradeNo = BCDUtil.toString(tradeNoBytes); + additionalInfo.put("云平台充电流水号/充电卡号", tradeNo); + + //34 预留 + byteBuf.skipBytes(1); + //35 预留 + byteBuf.skipBytes(8); + + //36 当前电费,这里是整型,要乘以 0.01 才能得到真实的金额,单位元 + BigDecimal currentFee = reduceMagnification(byteBuf.readUnsignedIntLE(), 100, 2); + additionalInfo.put("当前电费", currentFee.toString()); + + //37 当前服务费 这里是整型,要乘以 0.01 才能得到真实的金额,单位元 + BigDecimal currentServiceFee = reduceMagnification(byteBuf.readUnsignedIntLE(), 100, 2); + additionalInfo.put("当前服务费", currentServiceFee.toString()); + + //38 充电功率 分辨率:0.1kW + BigDecimal chargingPower = reduceMagnification(byteBuf.readUnsignedIntLE(), 10, 1); + additionalInfo.put("充电功率", chargingPower.toString()); + + //39 预留 + byteBuf.skipBytes(4); + //40 预留 + byteBuf.skipBytes(4); + //41 预留 + byteBuf.skipBytes(4); + + //42 出风口温度 预留 偏移量-50, -50-200 + byteBuf.skipBytes(1); + //43 环境温度 预留 偏移量-50, -50-200 + byteBuf.skipBytes(1); + //44 充电枪温度 预留 偏移量-50, -50-200 + byteBuf.skipBytes(1); + + //45 车辆 VIN 码 直流桩有效,正常有效长度是字节17,18 位为‘\0’ + byte[] vinCodeByte = new byte[18]; + byteBuf.readBytes(vinCodeByte); + String vinCode = new String(vinCodeByte, StandardCharsets.US_ASCII); + additionalInfo.put("车辆 VIN 码", vinCode); + + //46 预留 1 + byteBuf.skipBytes(1); + + + tcpSession.addPileCode(pileCode); + + // 注册前置会话 + ctx.getProtocolSessionRegistryProvider().register(tcpSession); + + // 抢状态 + GunRunStatus gunRunStatus = parseGunRunStatus(pileStatus); + GunRunStatusProto gunRunStatusProto = GunRunStatusProto.newBuilder() + .setTs(ts) + .setPileCode(pileCode) + .setGunCode(gunCode + "") + .setGunRunStatus(gunRunStatus) + .addAllFaultMessages(Stream.of(alarmCodeDesc).collect(Collectors.toList())) + .setAdditionalInfo(additionalInfo.toString()) + .build(); + + + // 转发到后端 + UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(gunRunStatusProto.getPileCode(), tcpSession, lvnengUplinkMessage) + .setGunRunStatusProto(gunRunStatusProto) + .build(); + tcpSession.getForwarder().sendMessage(uplinkQueueMessage); + + if (StringUtils.isNotBlank(tradeNo)) { + + // 充电进度 + ProtocolProto.ChargingProgressProto.Builder chargingProgressProtoBuilder = ProtocolProto.ChargingProgressProto.newBuilder() + .setTs(ts) + .setPileCode(pileCode) + .setGunCode(gunCode + "") + .setTradeNo(tradeNo) + .setOutputVoltage(String.valueOf(DCV)) + .setOutputCurrent(String.valueOf(DCC)) + .setSoc(soc) + .setTotalChargingDurationMin((int)totalChargingTime) + .setTotalChargingEnergyKWh(totalChargingEnergyKWh.toPlainString()) + .setTotalChargingCostYuan(totalAmount.toPlainString()) + .setAdditionalInfo(additionalInfo.toString()); + + UplinkQueueMessage chargingProgressMessage = uplinkMessageBuilder(pileCode, tcpSession, lvnengUplinkMessage) + .setChargingProgressProto(chargingProgressProtoBuilder) + .build(); + + tcpSession.getForwarder().sendMessage(chargingProgressMessage); + } + + //服务器应答充电桩状态信息包 + realTimeDataAck(tcpSession, gunCode); + } + + private void realTimeDataAck(TcpSession tcpSession, byte gunCode) { + ByteBuf pingAckMsgBody = Unpooled.buffer(5); + pingAckMsgBody.writeShortLE(0); + pingAckMsgBody.writeShortLE(0); + pingAckMsgBody.writeByte(gunCode); + + encodeAndWriteFlush(REAL_TIME_DATA_ACK, + pingAckMsgBody, + tcpSession); + } + + protected static BigDecimal reduceMagnification(long value, int magnification, int scale) { + return new BigDecimal(value).divide(new BigDecimal(magnification), scale, RoundingMode.HALF_UP); + } + + + protected static String getBmsChargingTypeDesc(int value) { + return switch (value) { + case 1 -> "恒压"; + case 2 -> "恒流"; + default -> "未知"; + }; + } + + protected static String getCarLinkDesc(int value) { + // 目前只有 0 和 2 状态。 + return switch (value) { + case 0 -> "断开"; + case 1 -> "半连接"; + case 2 -> "连接"; + default -> "未知"; + }; + } + + private static GunRunStatus parseGunRunStatus(int gunStatus) { + GunRunStatus gunRunStatus = GunRunStatus.UNKNOWN; + if (gunStatus == 0) { + gunRunStatus = GunRunStatus.IDLE; + } else if (gunStatus == 2) { + gunRunStatus = GunRunStatus.CHARGING; + } else if (gunStatus == 6) { + gunRunStatus = GunRunStatus.FAULT; + } + return gunRunStatus; + } +} diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordAckDLCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordAckDLCmd.java new file mode 100644 index 0000000..26ad632 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordAckDLCmd.java @@ -0,0 +1,77 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.v340.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.TransactionRecordResponse; +import sanbing.jcpp.protocol.ProtocolContext; +import sanbing.jcpp.protocol.listener.tcp.TcpSession; +import sanbing.jcpp.protocol.lvneng.LvnengDownlinkCmdExe; +import sanbing.jcpp.protocol.lvneng.LvnengDwonlinkMessage; +import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage; +import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static sanbing.jcpp.protocol.lvneng.enums.LvnengDownlinkCmdEnum.TRANSACTION_RECORD_ACK; + +/** + * 绿能3.4 服务器应答订单信息 + */ +@Slf4j +@LvnengCmd(201) +public class LvnengV340TransactionRecordAckDLCmd extends LvnengDownlinkCmdExe { + @Override + public void execute(TcpSession tcpSession, LvnengDwonlinkMessage lvnengDwonlinkMessage, ProtocolContext ctx) { + log.debug("{} 绿能3.4服务器订单应答", tcpSession); + + if (!lvnengDwonlinkMessage.getMsg().hasTransactionRecordResponse()) { + return; + } + + TransactionRecordResponse transactionRecordResponse = lvnengDwonlinkMessage.getMsg().getTransactionRecordResponse(); + + LvnengUplinkMessage requestData = JacksonUtil.fromBytes(lvnengDwonlinkMessage.getMsg().getRequestData().toByteArray(), LvnengUplinkMessage.class); + + // 获取上行报文 + byte[] uplinkRawFrame = requestData.getRawFrame(); + + // 从上行报文中取出桩编号字节数组 + byte[] gunCodeBytes = Arrays.copyOfRange(uplinkRawFrame, 45, 46); + byte[] indexBytes = Arrays.copyOfRange(uplinkRawFrame, 128, 132); + + ByteBuf pingAckMsgBody = Unpooled.buffer(41); + //1预留 + pingAckMsgBody.writeShortLE(0x00); + //2预留 + pingAckMsgBody.writeShortLE(0x00); + //3充电枪口 + pingAckMsgBody.writeBytes(gunCodeBytes); + //4流水号 + pingAckMsgBody.writeBytes(encodeTradeNo(transactionRecordResponse.getTradeNo())); + //5内部索引号 + pingAckMsgBody.writeBytes(indexBytes); + encodeAndWriteFlush(TRANSACTION_RECORD_ACK, + pingAckMsgBody, + tcpSession); + } + + protected static byte[] encodeTradeNo(String tradeNo) { + // 将tradeNo 读取到32字节数组内 + byte[] bytes = new byte[32]; + byte[] tradeNoBytes = tradeNo.getBytes(StandardCharsets.US_ASCII); + int copyLength = Math.min(tradeNoBytes.length, bytes.length); + System.arraycopy(tradeNoBytes, 0, bytes, 0, copyLength); + return bytes; + } + + +} diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java new file mode 100644 index 0000000..29fbdb4 --- /dev/null +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java @@ -0,0 +1,229 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.lvneng.v340.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.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.TimePeriodDetail; +import sanbing.jcpp.proto.gen.ProtocolProto.TimePeriodDetail.PeriodItem; +import sanbing.jcpp.proto.gen.ProtocolProto.TransactionDetail; +import sanbing.jcpp.proto.gen.ProtocolProto.TransactionRecordRequest; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; +import sanbing.jcpp.protocol.ProtocolContext; +import sanbing.jcpp.protocol.listener.tcp.TcpSession; +import sanbing.jcpp.protocol.lvneng.LvnengUplinkCmdExe; +import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage; +import sanbing.jcpp.protocol.lvneng.annotation.LvnengCmd; +import sanbing.jcpp.protocol.lvneng.enums.LvnengPileFinishReasonEnum; +import sanbing.jcpp.protocol.lvneng.enums.LvnengPileStartTypeEnum; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * 绿能3.4 充电桩上报充电订单 + */ +@Slf4j +@LvnengCmd(203) +public class LvnengV340TransactionRecordULCmd extends LvnengUplinkCmdExe { + + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); + + @Override + public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) { + log.debug("{} 绿能3.4充电桩上报充电订单请求", tcpSession); + ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody()); + + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + //1预留 + byteBuf.skipBytes(2); + //2预留 + byteBuf.skipBytes(2); + + //3充电桩编码 + byte[] pileCodeBytes = new byte[32]; + byteBuf.readBytes(pileCodeBytes); + String pileCode = StringUtils.trim(new String(pileCodeBytes, StandardCharsets.US_ASCII)); + + //4 充电枪位置类型 + byte gunType = byteBuf.readByte(); + additionalInfo.put("充电枪位置类型", gunType); + + //5 充电枪口 + byte gunCode = byteBuf.readByte(); + additionalInfo.put("充电枪口", gunCode); + + //6 卡号 + byte[] cardCodeBytes = new byte[32]; + byteBuf.readBytes(cardCodeBytes); + String cardCode = StringUtils.trim(new String(cardCodeBytes, StandardCharsets.US_ASCII)); + additionalInfo.put("卡号", cardCode); + + //7 充电开始时间 + byte[] chargingStartTimeBytes = new byte[8]; + byteBuf.readBytes(chargingStartTimeBytes); + LocalDateTime start = BCDUtil.bcdToDate(chargingStartTimeBytes); + long startTs = start != null ? start.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : 0L; + + //8 充电结束时间 + byte[] chargingEndTimeBytes = new byte[8]; + byteBuf.readBytes(chargingEndTimeBytes); + LocalDateTime endTime = BCDUtil.bcdToDate(chargingEndTimeBytes); + long endTs = endTime != null ? endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : 0L; + + //9 充电时间长度 单位秒 + long chargingTotalTime = byteBuf.readUnsignedIntLE(); + additionalInfo.put("充电时间长度 单位秒", chargingTotalTime); + + //10 开始 SOC + byte startSoc = byteBuf.readByte(); + additionalInfo.put("开始 SOC", startSoc); + + //11 结束 SOC + byte endSoc = byteBuf.readByte(); + additionalInfo.put("结束 SOC", endSoc); + + //12 充电结束原因 + long finishReasonCode = byteBuf.readUnsignedIntLE(); + String finishReason = LvnengPileFinishReasonEnum.getByCode(finishReasonCode); + + //13 本次充电电量 精度:0.001kWh + BigDecimal totalEnergy = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000, 3); + + //14 充电前电表读数 精度:0.001kWh + BigDecimal chargingBeforeNum = reduceMagnification(byteBuf.readLongLE(), 1000, 3); + additionalInfo.put("充电前电表读数", chargingBeforeNum); + + //15 充电后电表读数 精度:0.001kWh + BigDecimal chargingAfterNum = reduceMagnification(byteBuf.readLongLE(), 1000, 3); + additionalInfo.put("充电后电表读数", chargingAfterNum); + + //16 本次充电金额 0.01 元 ,电费和服务费之和。 + BigDecimal totalAmount = reduceMagnification(byteBuf.readUnsignedIntLE(), 100, 2); + + //17 内部索引号 4 字节有符号整形,每一条充电记录 都唯一编号,用于充电机内部做唯一标志 + int index = byteBuf.readIntLE(); + additionalInfo.put("内部索引号", index); + additionalInfo.put("index", index); + + //18 预留 + byteBuf.skipBytes(4); + //19 预留 + byteBuf.skipBytes(4); + //20 预留 + byteBuf.skipBytes(4); + //21 预留 + byteBuf.skipBytes(1); + //22 预留 + byteBuf.skipBytes(1); + //23 预留 + byteBuf.skipBytes(4); + + //24 车辆 VIN + byte[] cardVinBytes = new byte[17]; + byteBuf.readBytes(cardVinBytes); + String cardVin = StringUtils.trim(new String(cardVinBytes, StandardCharsets.US_ASCII)); + additionalInfo.put("车辆 VIN", cardVin); + + //25 车牌号 + byte[] cardNumBytes = new byte[8]; + byteBuf.readBytes(cardNumBytes); + String cardNum = StringUtils.trim(new String(cardNumBytes, StandardCharsets.US_ASCII)); + additionalInfo.put("车牌号", cardNum); + + // 24小时,48个时间段,开始时间段[00:00:00〜00:30:00],最后时间段[23:30:00-00:00:00] 0.001kwh + LocalTime currentTime = LocalTime.MIDNIGHT; + List list = new ArrayList<>(); + for (int i = 1; i < 49; i++) { + BigDecimal chargingCapacity = reduceMagnification(byteBuf.readUnsignedIntLE(), 1000, 3); + PeriodItem timePeriodDetail = PeriodItem.newBuilder() + .setPeriodNo(i) + .setStartTime(dateFormat(currentTime)) + .setEndTime(dateFormat(currentTime.plusMinutes(30))) + .setEnergyKWh(chargingCapacity.toString()) + .build(); + list.add(timePeriodDetail); + } + + //74 启动方式 + byte startType = byteBuf.readByte(); + String startTypeCode = LvnengPileStartTypeEnum.getByCode(startType); + additionalInfo.put("启动方式", startTypeCode); + + //75 充电流水号 + byte[] tradeNoBytes = new byte[32]; + byteBuf.readBytes(tradeNoBytes); + String tradeNo = StringUtils.trim(new String(tradeNoBytes, StandardCharsets.US_ASCII)); + + //76 充电服务费 + BigDecimal serviceFee = reduceMagnification(byteBuf.readUnsignedIntLE(), 100, 2); + additionalInfo.put("充电服务费", serviceFee.toString()); + + tcpSession.addPileCode(pileCode); + + + //充电明细 + TimePeriodDetail timePeriodDetail = TimePeriodDetail.newBuilder() + .addAllPeriods(list) + .build(); + //订单明细 + TransactionDetail transactionDetail = TransactionDetail.newBuilder() + .setType(ProtocolProto.DetailType.TIME_PERIOD) + .setTimePeriod(timePeriodDetail) + .build(); + + // 注册前置会话 + ctx.getProtocolSessionRegistryProvider().register(tcpSession); + + // 转发到后端 + TransactionRecordRequest transactionRecord = TransactionRecordRequest.newBuilder() + .setPileCode(pileCode) + .setGunCode(gunCode + "") + .setTradeNo(tradeNo) + .setStartTs(startTs) + .setEndTs(endTs) + .setTotalEnergyKWh(totalEnergy.toString()) + .setTotalAmountYuan(totalAmount.toString()) + .setStopReason(finishReason) + .setDetail(transactionDetail) + .setAdditionalInfo(additionalInfo.toString()) + .build(); + + UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(transactionRecord.getPileCode(), tcpSession, lvnengUplinkMessage) + .setTransactionRecordRequest(transactionRecord) + .build(); + tcpSession.getForwarder().sendMessage(uplinkQueueMessage); + + + } + + + protected static BigDecimal reduceMagnification(long value, int magnification, int scale) { + return new BigDecimal(value).divide(new BigDecimal(magnification), scale, RoundingMode.HALF_UP); + } + + protected static String dateFormat(LocalTime time) { + return time.format(DATE_TIME_FORMATTER); + } + + +}