!21 绿能109,103,102,101,203,201指令提交

* 合并后代码修改
* 合并后代码修改
* Merge remote-tracking branch 'refs/remotes/upstream/master' into maste…
* PR后代码修改
* 109)充电桩状态信息包上报
* 109)充电桩状态信息包上报
This commit is contained in:
红中
2025-08-20 11:21:10 +00:00
committed by 三丙
parent 6b054c31a9
commit babd1696f6
12 changed files with 1541 additions and 2 deletions

View File

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

View File

@@ -344,6 +344,7 @@ message BmsChargingErrorProto {
message TransactionRecordResponse {
string tradeNo = 6;
bool success = 7;
optional string additionalInfo = 20;
}
message BmsParamConfigReportProto {

View File

@@ -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`
---

View File

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

View File

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

View File

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

View File

@@ -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本地管理员启动
* 3VIN 启动 生产个枚举类
*/
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<Integer, LvnengPileStartTypeEnum> 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();
}
}

View File

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

View File

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

View File

@@ -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 码 直流桩有效正常有效长度是字节1718 位为‘\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;
}
}

View File

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

View File

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