From da1da312c7f5fc5d4739f6ca7527c9f932517986 Mon Sep 17 00:00:00 2001 From: Guoqs <123456@jsowell.com> Date: Thu, 18 Jun 2026 13:06:06 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BE=BD=E4=BF=A1=E9=A2=84=E7=BA=A6=E5=85=85?= =?UTF-8?q?=E7=94=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationChargingResponseHandler.java | 45 +++++- .../pile/service/PileRemoteService.java | 56 ++++++-- .../impl/PileReservationInfoServiceImpl.java | 131 +++++++----------- .../impl/YKCPushCommandServiceImpl.java | 79 +++++++++-- 4 files changed, 200 insertions(+), 111 deletions(-) diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingResponseHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingResponseHandler.java index cf468e394..2fb31b644 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingResponseHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingResponseHandler.java @@ -66,23 +66,39 @@ public class ReservationChargingResponseHandler extends AbstractYkcHandler { startIndex += length; length = 1; byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); - String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr); + String responseFrameType = YKCUtils.frameType2Str(ykcDataProtocol.getFrameType()); + boolean yuxinResponse = StringUtils.equals(responseFrameType, yuxinType); + String connectorCode = yuxinResponse ? parseYuxinConnectorCode(connectorCodeByteArr) : BytesUtil.bcd2Str(connectorCodeByteArr); + + String reservationType = null; + if (yuxinResponse) { + // 羽信0xB1在枪号后多一个预约方式字段: 0x00 立即预约, 0x01 取消预约。 + startIndex += length; + length = 1; + byte[] reservationTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + reservationType = BytesUtil.bin2HexStr(reservationTypeByteArr); + } // 启动结果 0x00失败 0x01成功 startIndex += length; length = 1; byte[] resultCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); - String resultCode = BytesUtil.bcd2Str(resultCodeByteArr); + String resultCode = yuxinResponse ? BytesUtil.bin2HexStr(resultCodeByteArr) : BytesUtil.bcd2Str(resultCodeByteArr); // 失败原因 startIndex += length; length = 1; byte[] failedReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); - String failedReason = BytesUtil.bcd2Str(failedReasonByteArr); + String failedReason = yuxinResponse ? BytesUtil.bin2HexStr(failedReasonByteArr) : BytesUtil.bcd2Str(failedReasonByteArr); + String failedReasonMsg = yuxinResponse ? getYuxinFailedReasonMsg(failedReason) : failedReason; - String responseFrameType = YKCUtils.frameType2Str(ykcDataProtocol.getFrameType()); - log.info("{}预约充电响应, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 结果:{}, 失败原因:{}", - responseFrameType, transactionCode, pileSn, connectorCode, resultCode, failedReason); + if (yuxinResponse) { + log.info("{}预约充电响应, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 预约方式:{}, 结果:{}, 失败原因:{}", + responseFrameType, transactionCode, pileSn, connectorCode, reservationType, resultCode, failedReasonMsg); + } else { + log.info("{}预约充电响应, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 结果:{}, 失败原因:{}", + responseFrameType, transactionCode, pileSn, connectorCode, resultCode, failedReason); + } // 根据请求id,在集合中找到与外部线程通信的SyncPromise对象 String requestFrameType = YKCFrameTypeCode.PileAnswersRelation.getRequestFrameType(responseFrameType); @@ -114,4 +130,21 @@ public class ReservationChargingResponseHandler extends AbstractYkcHandler { } return null; } + + private String parseYuxinConnectorCode(byte[] connectorCodeByteArr) { + return String.format("%02d", connectorCodeByteArr[0] & 0xFF); + } + + private String getYuxinFailedReasonMsg(String failedReason) { + if (StringUtils.equals(failedReason, "00")) { + return "无"; + } else if (StringUtils.equals(failedReason, "01")) { + return "保存失败"; + } else if (StringUtils.equals(failedReason, "02")) { + return "系统当前时间小于主板时间"; + } else if (StringUtils.equals(failedReason, "03")) { + return "预约时间小于当前时间"; + } + return failedReason; + } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java index 0df5d2352..12afc531d 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java @@ -5,6 +5,7 @@ import com.google.common.collect.Lists; import com.jsowell.common.constant.CacheConstants; import com.jsowell.common.constant.Constants; import com.jsowell.common.core.domain.ykc.YKCDataProtocol; +import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode; import com.jsowell.common.core.redis.RedisCache; import com.jsowell.common.enums.ebike.EBikeChargeResponseEnum; import com.jsowell.common.enums.ykc.ChargingFailedReasonEnum; @@ -513,13 +514,16 @@ public class PileRemoteService { * @return result: 1-成功; 0-失败 */ public String reservationCharging(ReservationChargingCommand command) { - String result = "1"; + String result = "0"; byte[] bytes = ykcPushCommandService.pushReservationChargingCommand(command); // 解析结果 if (Objects.isNull(bytes)) { result = "0"; } else { YKCDataProtocol ykcDataProtocol = new YKCDataProtocol(bytes); + String responseFrameType = YKCUtils.frameType2Str(ykcDataProtocol.getFrameType()); + boolean yuxinResponse = StringUtils.equals(responseFrameType, + YKCUtils.frameType2Str(YKCFrameTypeCode.YUXIN_RESERVATION_CHARGING_SETUP_ANSWER_CODE.getBytes())); byte[] msgBody = ykcDataProtocol.getMsgBody(); int startIndex = 0; int length = 16; @@ -538,17 +542,24 @@ public class PileRemoteService { startIndex += length; length = 1; byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); - String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr); + String connectorCode = yuxinResponse ? parseYuxinConnectorCode(connectorCodeByteArr) : BytesUtil.bcd2Str(connectorCodeByteArr); + + String reservationType = null; + if (yuxinResponse) { + // 羽信0xB1在枪号后多一个预约方式字段: 0x00 立即预约, 0x01 取消预约。 + startIndex += length; + length = 1; + byte[] reservationTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + reservationType = BytesUtil.bin2HexStr(reservationTypeByteArr); + } // 启动结果 0x00失败 0x01成功 startIndex += length; length = 1; byte[] resultCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); - String resultCode = BytesUtil.bcd2Str(resultCodeByteArr); + String resultCode = yuxinResponse ? BytesUtil.bin2HexStr(resultCodeByteArr) : BytesUtil.bcd2Str(resultCodeByteArr); - if (StringUtils.equals(resultCode, "00")) { - result = "0"; - } else { + if (StringUtils.equals(resultCode, "01")) { result = "1"; } @@ -556,15 +567,38 @@ public class PileRemoteService { startIndex += length; length = 1; byte[] failedReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); - String failedReason = BytesUtil.bcd2Str(failedReasonByteArr); - String failedReasonMsg = ChargingFailedReasonEnum.getMsgByCode(Integer.parseInt(failedReason, 16)); + String failedReason = yuxinResponse ? BytesUtil.bin2HexStr(failedReasonByteArr) : BytesUtil.bcd2Str(failedReasonByteArr); + String failedReasonMsg = yuxinResponse + ? getYuxinFailedReasonMsg(failedReason) + : ChargingFailedReasonEnum.getMsgByCode(Integer.parseInt(failedReason, 16)); - String responseFrameType = YKCUtils.frameType2Str(ykcDataProtocol.getFrameType()); - log.info("{}预约充电响应sync, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 结果(00-失败; 01成功):{}, 失败原因:{}", - responseFrameType, transactionCode, pileSn, connectorCode, resultCode, failedReasonMsg); + if (yuxinResponse) { + log.info("{}预约充电响应sync, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 预约方式:{}, 结果(00-失败; 01成功):{}, 失败原因:{}", + responseFrameType, transactionCode, pileSn, connectorCode, reservationType, resultCode, failedReasonMsg); + } else { + log.info("{}预约充电响应sync, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 结果(00-失败; 01成功):{}, 失败原因:{}", + responseFrameType, transactionCode, pileSn, connectorCode, resultCode, failedReasonMsg); + } } return result; } + private String parseYuxinConnectorCode(byte[] connectorCodeByteArr) { + return String.format("%02d", connectorCodeByteArr[0] & 0xFF); + } + + private String getYuxinFailedReasonMsg(String failedReason) { + if (StringUtils.equals(failedReason, "00")) { + return "无"; + } else if (StringUtils.equals(failedReason, "01")) { + return "保存失败"; + } else if (StringUtils.equals(failedReason, "02")) { + return "系统当前时间小于主板时间"; + } else if (StringUtils.equals(failedReason, "03")) { + return "预约时间小于当前时间"; + } + return failedReason; + } + } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java index 56d6cc2cc..f237926bb 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java @@ -136,56 +136,17 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic */ @Override public void activateReserved(PileReservationDTO dto) { - // 查询其他生效中的预约 - // List infoList = pileReservationInfoMapper.findByMemberIdAndPileSnAndStatus(dto.getMemberId(), dto.getPileSn(), "1"); - // if (CollectionUtils.isNotEmpty(infoList)) { - // throw new BusinessException(ReturnCodeEnum.CODE_RESERVATION_ALREADY_EXISTS_ERROR); - // } - PileReservationInfo pileReservationInfo = pileReservationInfoMapper.selectByPrimaryKey(Integer.parseInt(dto.getReservedId())); if (pileReservationInfo == null) { return; } - // if (!StringUtils.equals(dto.getMemberId(), pileReservationInfo.getMemberId())) { - // return; - // } pileReservationInfo.setStatus(Constants.ONE); - // 保存之前,校验时间是否重叠 - int i = saveReservation(pileReservationInfo); - if (i > 0) { - // 查询会员的绑定vin列表 - List plateNumberVOList = memberPlateNumberRelationService.selectMemberPlateNumberRelation(pileReservationInfo.getMemberId()); - List vinCodes = Lists.newArrayList(); - int count = 0; - for (MemberPlateNumberVO vo : plateNumberVOList) { - if (count < 3 && StringUtils.isNotBlank(vo.getVinCode())) { - vinCodes.add(vo.getVinCode()); - count++; - } - } - // 如果 vinCodes 的数量少于 3,用 "0" 补足 - while (vinCodes.size() < 3) { - vinCodes.add(""); - } - - String type = StringUtils.equals(pileReservationInfo.getReservationType(), "single") ? "00" : "01"; - // 发送指令 - ReservationChargingCommand command = ReservationChargingCommand.builder() - .transactionCode(Constants.ILLEGAL_TRANSACTION_CODE) - .pileSn(pileReservationInfo.getPileSn()) - .connectorCode(pileReservationInfo.getPileConnectorCode().replace(pileReservationInfo.getPileSn(), "")) - .operation("01") - .reservationType(type) - .verifyIdentity(pileReservationInfo.getVerifyIdentity()) - .vin1(vinCodes.get(0)) - .vin2(vinCodes.get(1)) - .vin3(vinCodes.get(2)) - .reservedStartTime(pileReservationInfo.getStartTime().toLocalTime()) - .reservedEndTime(pileReservationInfo.getEndTime().toLocalTime()) - .amount(Constants.WHITELIST_DEFAULT_AMOUNT) - .build(); - // pileRemoteService.reservationCharging(command); + if (!isTimeSlotAvailable(pileReservationInfo.getMemberId(), pileReservationInfo.getPileSn(), + pileReservationInfo.getStartTime(), pileReservationInfo.getEndTime(), pileReservationInfo.getId())) { + throw new BusinessException(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_REFUSED); } + sendReservationCommandAndAssertSuccess(pileReservationInfo, pileReservationInfo.getMemberId(), "01"); + this.insertOrUpdateSelective(pileReservationInfo); } /** @@ -197,48 +158,55 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic if (pileReservationInfo == null) { return; } - // if (!StringUtils.equals(dto.getMemberId(), pileReservationInfo.getMemberId())) { - // return; - // } - // 校验通过可以修改预约 pileReservationInfo.setStatus(Constants.ZERO); - int i = pileReservationInfoMapper.updateByPrimaryKeySelective(pileReservationInfo); - if (i > 0) { - // 查询会员的绑定vin列表 - List plateNumberVOList = memberPlateNumberRelationService.selectMemberPlateNumberRelation(pileReservationInfo.getMemberId()); - List vinCodes = Lists.newArrayList(); - int count = 0; - for (MemberPlateNumberVO vo : plateNumberVOList) { - if (count < 3 && StringUtils.isNotBlank(vo.getVinCode())) { - vinCodes.add(vo.getVinCode()); - count++; - } - } - // 如果 vinCodes 的数量少于 3,用 "0" 补足 - while (vinCodes.size() < 3) { - vinCodes.add(""); - } + sendReservationCommandAndAssertSuccess(pileReservationInfo, pileReservationInfo.getMemberId(), "02"); + this.insertOrUpdateSelective(pileReservationInfo); + } - String type = StringUtils.equals(pileReservationInfo.getReservationType(), "single") ? "00" : "01"; - // 发送指令 - ReservationChargingCommand command = ReservationChargingCommand.builder() - .transactionCode(Constants.ILLEGAL_TRANSACTION_CODE) - .pileSn(pileReservationInfo.getPileSn()) - .connectorCode(pileReservationInfo.getPileConnectorCode().replace(pileReservationInfo.getPileSn(), "")) - .operation("02") - .reservationType(type) - .verifyIdentity(pileReservationInfo.getVerifyIdentity()) - .vin1(vinCodes.get(0)) - .vin2(vinCodes.get(1)) - .vin3(vinCodes.get(2)) - .reservedStartTime(pileReservationInfo.getStartTime().toLocalTime()) - .reservedEndTime(pileReservationInfo.getEndTime().toLocalTime()) - .amount(Constants.WHITELIST_DEFAULT_AMOUNT) - .build(); - // pileRemoteService.reservationCharging(command); + private void sendReservationCommandAndAssertSuccess(PileReservationInfo pileReservationInfo, String memberId, String operation) { + ReservationChargingCommand command = buildReservationChargingCommand(pileReservationInfo, memberId, operation); + String result = pileRemoteService.reservationCharging(command); + if (!StringUtils.equals(result, Constants.ONE)) { + throw new BusinessException(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR.getValue(), + ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR.getLabel() + ": 充电桩返回修改失败"); } } + private ReservationChargingCommand buildReservationChargingCommand(PileReservationInfo pileReservationInfo, String memberId, String operation) { + List vinCodes = getReservationVinCodes(memberId); + String type = StringUtils.equals(pileReservationInfo.getReservationType(), "single") ? "00" : "01"; + return ReservationChargingCommand.builder() + .transactionCode(Constants.ILLEGAL_TRANSACTION_CODE) + .pileSn(pileReservationInfo.getPileSn()) + .connectorCode(pileReservationInfo.getPileConnectorCode().replace(pileReservationInfo.getPileSn(), "")) + .operation(operation) + .reservationType(type) + .verifyIdentity(pileReservationInfo.getVerifyIdentity()) + .vin1(vinCodes.get(0)) + .vin2(vinCodes.get(1)) + .vin3(vinCodes.get(2)) + .reservedStartTime(pileReservationInfo.getStartTime().toLocalTime()) + .reservedEndTime(pileReservationInfo.getEndTime().toLocalTime()) + .amount(Constants.WHITELIST_DEFAULT_AMOUNT) + .build(); + } + + private List getReservationVinCodes(String memberId) { + List plateNumberVOList = memberPlateNumberRelationService.selectMemberPlateNumberRelation(memberId); + List vinCodes = Lists.newArrayList(); + int count = 0; + for (MemberPlateNumberVO vo : plateNumberVOList) { + if (count < 3 && StringUtils.isNotBlank(vo.getVinCode())) { + vinCodes.add(vo.getVinCode()); + count++; + } + } + while (vinCodes.size() < 3) { + vinCodes.add(""); + } + return vinCodes; + } + /** * 校验时间是否重叠 * @param memberId @@ -621,4 +589,3 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic pileReservationInfoMapper.deleteByPileSn(pileSn); } } - diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java index ff36a177b..cf016e8f3 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java @@ -33,6 +33,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Date; import java.util.List; @@ -765,6 +767,25 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { */ @Override public byte[] pushReservationChargingCommand(ReservationChargingCommand command) { + String pileSn = command.getPileSn(); + YKCFrameTypeCode frameTypeCode = getReservationChargingFrameType(pileSn); + byte[] msg = frameTypeCode == YKCFrameTypeCode.YUXIN_RESERVATION_CHARGING_SETUP_CODE + ? buildYuxinReservationChargingMsg(command) + : buildReservationChargingMsg(command); + byte[] response; + try { + response = this.supplySend(msg, pileSn, frameTypeCode); + } catch (Exception e) { + log.error("发送消息异常", e); + response = null; + } + + log.info("【=====平台下发指令=====】: 预约充电指令, 帧类型:{}, 交易流水号:{}, 桩编号:{}, 枪口号:{}, 操作(01-启动; 02-取消; 03-修改):{}, 身份验证:{}, 开始时间:{}, 结束时间:{}, 启动金额:{}", + YKCUtils.frameType2Str(frameTypeCode.getBytes()), command.getTransactionCode(), pileSn, command.getConnectorCode(), command.getOperation(), command.getVerifyIdentity(), DateUtils.formatDateTime(command.getReservedStartTime()), DateUtils.formatDateTime(command.getReservedEndTime()), command.getAmount()); + return response; + } + + private byte[] buildReservationChargingMsg(ReservationChargingCommand command) { // 交易流水号 String transactionCode = command.getTransactionCode(); byte[] transactionCodeArr = BytesUtil.str2Bcd(transactionCode); @@ -815,23 +836,57 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { BigDecimal amount = command.getAmount(); byte[] amountByteArr = YKCUtils.getPriceByte(amount.toString(), 2); - // 拼装msg信息 - byte[] msg = Bytes.concat(transactionCodeArr, pileSnByteArr, connectorCodeByteArr, operateByteArr, + return Bytes.concat(transactionCodeArr, pileSnByteArr, connectorCodeByteArr, operateByteArr, reservationTypeByteArr, verifyIdentityByteArr, vin1ByteArr, vin2ByteArr, vin3ByteArr, reservedStartTimeByteArr, reservedEndTimeByteArr, amountByteArr); + } - YKCFrameTypeCode frameTypeCode = getReservationChargingFrameType(pileSn); - byte[] response; - try { - response = this.supplySend(msg, pileSn, frameTypeCode); - } catch (Exception e) { - log.error("发送消息异常", e); - response = null; + private byte[] buildYuxinReservationChargingMsg(ReservationChargingCommand command) { + byte[] transactionCodeArr = BytesUtil.ensureLengthPrependZero(BytesUtil.str2Bcd(command.getTransactionCode()), 16); + byte[] pileSnByteArr = BytesUtil.ensureLengthPrependZero(BytesUtil.str2Bcd(command.getPileSn()), 7); + byte[] connectorCodeByteArr = BytesUtil.intToBytes(parseDecimalByte(command.getConnectorCode(), "枪口号"), 1); + byte[] accountBalanceByteArr = YKCUtils.getPriceByte(getAmount(command).toString(), 2); + byte[] reservationTypeByteArr = BytesUtil.intToBytes(StringUtils.equals(command.getOperation(), "02") ? 0x01 : 0x00, 1); + byte[] chargingStrategyByteArr = BytesUtil.intToBytes(0x00, 1); + byte[] chargingParamByteArr = YKCUtils.getPriceByte(BigDecimal.ZERO.toString(), 2); + byte[] systemTimeByteArr = Cp56Time2aUtil.date2Hbyte(new Date()); + byte[] reservedStartTimeByteArr = Cp56Time2aUtil.date2Hbyte(getYuxinReservedStartDate(command.getReservedStartTime())); + byte[] reservationTimeoutByteArr = BytesUtil.intToBytes(getYuxinReservationTimeout(command), 1); + + return Bytes.concat(transactionCodeArr, pileSnByteArr, connectorCodeByteArr, accountBalanceByteArr, + reservationTypeByteArr, chargingStrategyByteArr, chargingParamByteArr, systemTimeByteArr, + reservedStartTimeByteArr, reservationTimeoutByteArr); + } + + private BigDecimal getAmount(ReservationChargingCommand command) { + return command.getAmount() == null ? BigDecimal.ZERO : command.getAmount(); + } + + private Date getYuxinReservedStartDate(LocalTime reservedStartTime) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime reservedStartDateTime = now.toLocalDate().atTime(reservedStartTime); + if (!reservedStartDateTime.isAfter(now)) { + reservedStartDateTime = reservedStartDateTime.plusDays(1); } + return DateUtils.localDateTime2Date(reservedStartDateTime); + } - log.info("【=====平台下发指令=====】: 预约充电指令, 帧类型:{}, 交易流水号:{}, 桩编号:{}, 枪口号:{}, 操作(01-启动; 02-取消; 03-修改):{}, 身份验证:{}, 开始时间:{}, 结束时间:{}, 启动金额:{}", - YKCUtils.frameType2Str(frameTypeCode.getBytes()), transactionCode, pileSn, connectorCode, operation, verifyIdentity, DateUtils.formatDateTime(reservedStartTime), DateUtils.formatDateTime(reservedEndTime), amount); - return response; + private int getYuxinReservationTimeout(ReservationChargingCommand command) { + LocalTime reservedStartTime = command.getReservedStartTime(); + LocalTime reservedEndTime = command.getReservedEndTime(); + long timeout = Duration.between(reservedStartTime, reservedEndTime).toMinutes(); + if (timeout <= 0) { + timeout += TimeUnit.DAYS.toMinutes(1); + } + return (int) Math.min(timeout, 0xFF); + } + + private int parseDecimalByte(String value, String fieldName) { + int result = Integer.parseInt(value); + if (result < 0 || result > 0xFF) { + throw new IllegalArgumentException(fieldName + "超出1字节范围:" + value); + } + return result; } private YKCFrameTypeCode getReservationChargingFrameType(String pileSn) {