diff --git a/jsowell-admin/src/main/java/com/jsowell/service/PileService.java b/jsowell-admin/src/main/java/com/jsowell/service/PileService.java index 3a4aee99c..f97f92646 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/PileService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/PileService.java @@ -7,11 +7,7 @@ import com.jsowell.common.constant.Constants; import com.jsowell.common.core.domain.ykc.RealTimeMonitorData; import com.jsowell.common.core.page.PageResponse; import com.jsowell.common.enums.DelFlagEnum; -import com.jsowell.common.enums.ykc.BusinessTypeEnum; -import com.jsowell.common.enums.ykc.OrderPayModeEnum; -import com.jsowell.common.enums.ykc.OrderStatusEnum; -import com.jsowell.common.enums.ykc.PileStatusEnum; -import com.jsowell.common.enums.ykc.ReturnCodeEnum; +import com.jsowell.common.enums.ykc.*; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.util.*; import com.jsowell.pile.domain.*; @@ -235,7 +231,7 @@ public class PileService { return null; } // 枪口状态不为2:占用(未充电) - if (!StringUtils.equals("2", pileConnectorDetailVO.getConnectorStatus())) { + if (!StringUtils.equals(PileConnectorDataBaseStatusEnum.OCCUPIED_NOT_CHARGED.getValue(), pileConnectorDetailVO.getConnectorStatus())) { throw new BusinessException(ReturnCodeEnum.CODE_PILE_CONNECTOR_STATUS_ERROR); } String pileSn = pileConnectorDetailVO.getPileSn(); diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/decoder/MessageDecode.java b/jsowell-netty/src/main/java/com/jsowell/netty/decoder/MessageDecode.java index adc5be264..2b5004a1e 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/decoder/MessageDecode.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/decoder/MessageDecode.java @@ -1,6 +1,6 @@ package com.jsowell.netty.decoder; -import com.jsowell.netty.domain.ebike.EBikeMessage; +import com.jsowell.netty.domain.ebike.AbsEBikeMessage; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; @@ -22,7 +22,7 @@ public class MessageDecode extends ByteToMessageDecoder { in.readBytes(bytes); // 解析字节数组 - EBikeMessage message = EBikeMessage.parseMessage(bytes); + AbsEBikeMessage message = AbsEBikeMessage.parseMessage(bytes); out.add(message); } } \ No newline at end of file diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/EBikeMessage.java b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/AbsEBikeMessage.java similarity index 51% rename from jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/EBikeMessage.java rename to jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/AbsEBikeMessage.java index d2a519754..bcac7aad9 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/EBikeMessage.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/AbsEBikeMessage.java @@ -1,62 +1,64 @@ package com.jsowell.netty.domain.ebike; -import com.jsowell.common.YouDianUtils; import com.jsowell.common.util.BytesUtil; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; +import com.jsowell.netty.domain.ebike.deviceupload.EBikeMessageCmd03; +import com.jsowell.netty.domain.ebike.deviceupload.SettlementInfo; +import com.jsowell.netty.domain.ebike.serversend.EBikeMessageCmd82; +import com.jsowell.netty.domain.ebike.serversend.SpecificDataCmd82; +import lombok.Getter; +import lombok.Setter; import java.nio.charset.StandardCharsets; import java.util.Arrays; -@Slf4j -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class EBikeMessage { +@Getter +@Setter +public abstract class AbsEBikeMessage { private String header; // 包头 (3字节) private int length; // 长度 (2字节) private int physicalId; // 物理ID (4字节) private int messageId; // 消息ID (2字节) private String command; // 命令 (1字节) - private byte[] data; // 数据 (n字节) + private Object payload; // 数据 (n字节) private int checksum; // 校验 (2字节) - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.JSON_STYLE) - .append("header", header) - .append("length", length) - .append("physicalId", physicalId) - .append("messageId", messageId) - .append("command", command) - .append("data", data) - .append("checksum", checksum) - .toString(); + public AbsEBikeMessage(String header, int length, int physicalId, int messageId, String command, Object payload, int checksum) { + this.header = header; + this.length = length; + this.physicalId = physicalId; + this.messageId = messageId; + this.command = command; + this.payload = payload; + this.checksum = checksum; } - public static EBikeMessage parseMessage(byte[] messageBytes) { + public abstract void parsePayload(byte[] dataBytes); + + private static AbsEBikeMessage createMessageInstance(String command, String header, int length, int physicalId, int messageId, int checksum, byte[] dataBytes) { + switch (command) { + case "82": + return new EBikeMessageCmd82(header, length, physicalId, messageId, command, null, checksum, new SpecificDataCmd82(dataBytes)); + case "03": + return new EBikeMessageCmd03(header, length, physicalId, messageId, command, null, checksum, new SettlementInfo(dataBytes)); + default: + throw new IllegalArgumentException("Unsupported command: " + command); + } + } + + + public static AbsEBikeMessage parseMessage(byte[] messageBytes) { if (messageBytes == null || messageBytes.length < 14 || messageBytes.length > 256) { throw new IllegalArgumentException("Invalid message bytes"); } - EBikeMessage message = new EBikeMessage(); - try { // 读取包头 byte[] headerBytes = Arrays.copyOfRange(messageBytes, 0, 3); String header = new String(headerBytes, StandardCharsets.UTF_8); - message.setHeader(header); // 读取长度 byte[] lengthBytes = Arrays.copyOfRange(messageBytes, 3, 5); int length = BytesUtil.bytesToIntLittle(lengthBytes); - message.setLength(length); // 验证长度 if (length != (messageBytes.length - 5)) { @@ -66,38 +68,29 @@ public class EBikeMessage { // 读取物理ID byte[] physicalIdBytes = Arrays.copyOfRange(messageBytes, 5, 9); int physicalId = BytesUtil.bytesToIntLittle(physicalIdBytes); - message.setPhysicalId(physicalId); // 读取消息ID byte[] messageIdBytes = Arrays.copyOfRange(messageBytes, 9, 11); int messageId = BytesUtil.bytesToIntLittle(messageIdBytes); - message.setMessageId(messageId); // 读取命令 byte commandByte = messageBytes[11]; String command = BytesUtil.bcd2StrLittle(new byte[]{commandByte}); - message.setCommand(command); // 读取数据 byte[] dataBytes = Arrays.copyOfRange(messageBytes, 12, messageBytes.length - 2); - message.setData(dataBytes); - String data = BytesUtil.bcd2StrLittle(dataBytes); // 读取校验 byte[] checksumBytes = Arrays.copyOfRange(messageBytes, messageBytes.length - 2, messageBytes.length); int checksum = BytesUtil.bytesToIntLittle(checksumBytes); - message.setChecksum(checksum); - // 校验码验证 - if (!YouDianUtils.validateChecksum(messageBytes)) { - throw new IllegalArgumentException("Checksum validation failed"); - } + // 根据命令创建相应的子类实例 + AbsEBikeMessage parsedMessage = createMessageInstance(command, header, length, physicalId, messageId, checksum, dataBytes); + parsedMessage.parsePayload(dataBytes); - log.info("报文:{}, parseMessage:{}", BytesUtil.binary(messageBytes, 16), message.toString()); - return message; + return parsedMessage; } catch (Exception e) { - log.error("Error parsing message", e); - throw e; + throw new IllegalArgumentException("Error parsing message", e); } } diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/deviceupload/EBikeMessageCmd03.java b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/deviceupload/EBikeMessageCmd03.java new file mode 100644 index 000000000..86abd1998 --- /dev/null +++ b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/deviceupload/EBikeMessageCmd03.java @@ -0,0 +1,22 @@ +package com.jsowell.netty.domain.ebike.deviceupload; + +import com.jsowell.netty.domain.ebike.AbsEBikeMessage; + +public class EBikeMessageCmd03 extends AbsEBikeMessage { + + private SettlementInfo settlementInfo; + + public EBikeMessageCmd03(String header, int length, int physicalId, int messageId, String command, Object payload, int checksum, SettlementInfo settlementInfo) { + super(header, length, physicalId, messageId, command, payload, checksum); + this.settlementInfo = settlementInfo; + } + + @Override + public void parsePayload(byte[] dataBytes) { + this.settlementInfo = new SettlementInfo(dataBytes); + } + + public SettlementInfo getSettlementInfo() { + return settlementInfo; + } +} \ No newline at end of file diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/deviceupload/SettlementInfo.java b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/deviceupload/SettlementInfo.java new file mode 100644 index 000000000..c272f6bff --- /dev/null +++ b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/deviceupload/SettlementInfo.java @@ -0,0 +1,36 @@ +package com.jsowell.netty.domain.ebike.deviceupload; + +import com.jsowell.common.util.BytesUtil; +import lombok.Data; + +import java.util.Arrays; + +@Data +public class SettlementInfo { + private String chargingDuration; // 充电时长, 单位:"秒" + private String maxPower; // 最大功率, 单位:"0.1W" + private String consumedEnergy; // 耗电量, 单位:"0.01度" + private String portNumber; // 端口号 + private String startMode; // 在线/离线启动/验证码 + private String cardNumberOrVerificationCode; // 卡号/验证码 + private String stopReason; // 停止原因 + private String orderNumber; // 订单编号 + private String secondMaxPower; // 第二最大功率 + // private String timestamp; // 时间戳 上发指令当时的时间,有时候不准确,该字段属于调试使用,服务器无需关心此字段 + // private String placeholderDuration; // 占位时长 充电柜专用,其他设备忽略此字段 表示充满后占用设备的时长,单位为分钟 + + public SettlementInfo(byte[] dataBytes) { + this.chargingDuration = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 0, 2)) + ""; + this.maxPower = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 2, 4)) + ""; + this.consumedEnergy = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 4, 6)) + ""; + this.portNumber = BytesUtil.bcd2StrLittle(new byte[]{dataBytes[6]}); + this.startMode = BytesUtil.bcd2StrLittle(new byte[]{dataBytes[7]}); + this.cardNumberOrVerificationCode = BytesUtil.bcd2StrLittle(Arrays.copyOfRange(dataBytes, 8, 12)); + this.stopReason = BytesUtil.bcd2StrLittle(new byte[]{dataBytes[12]}); + this.orderNumber = BytesUtil.bcd2StrLittle(Arrays.copyOfRange(dataBytes, 13, 29)); + this.secondMaxPower = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 29, 31)) + ""; + // this.timestamp = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 31, 35)) + ""; + // this.placeholderDuration = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 35, 37)) + ""; + } + +} \ No newline at end of file diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/serversend/EBikeMessageCmd82.java b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/serversend/EBikeMessageCmd82.java new file mode 100644 index 000000000..d105ccbcd --- /dev/null +++ b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/serversend/EBikeMessageCmd82.java @@ -0,0 +1,22 @@ +package com.jsowell.netty.domain.ebike.serversend; + +import com.jsowell.netty.domain.ebike.AbsEBikeMessage; + +public class EBikeMessageCmd82 extends AbsEBikeMessage { + + private SpecificDataCmd82 specificData; + + public EBikeMessageCmd82(String header, int length, int physicalId, int messageId, String command, Object payload, int checksum, SpecificDataCmd82 specificData) { + super(header, length, physicalId, messageId, command, payload, checksum); + this.specificData = specificData; + } + + @Override + public void parsePayload(byte[] dataBytes) { + this.specificData = new SpecificDataCmd82(dataBytes); + } + + public SpecificDataCmd82 getSpecificData() { + return specificData; + } +} \ No newline at end of file diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/serversend/SpecificDataCmd82.java b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/serversend/SpecificDataCmd82.java new file mode 100644 index 000000000..d8f92dbd9 --- /dev/null +++ b/jsowell-netty/src/main/java/com/jsowell/netty/domain/ebike/serversend/SpecificDataCmd82.java @@ -0,0 +1,78 @@ +package com.jsowell.netty.domain.ebike.serversend; + +import com.jsowell.common.util.BytesUtil; +import lombok.Getter; +import lombok.Setter; + +import java.util.Arrays; + +@Getter +@Setter +public class SpecificDataCmd82 { + private String rateMode; // 费率模式 (1字节) + private String balanceOrValidity; // 余额/有效期 (4字节) + private String portNumber; // 端口号 (1字节) + private String chargeCommand; // 充电命令 (1字节) + private String chargeDurationOrPower; // 充电时长/电量 (2字节) + private String orderNumber; // 订单编号 (16字节) + private String maxChargeDuration; // 最大充电时长 (2字节) + private String overloadPower; // 过载功率 (2字节) + private String qrCodeLight; // 二维码灯 (1字节) + private String longChargeMode; // 长充模式 (1字节) + private String extraFloatChargeTime; // 额外浮充时间 (2字节) + private String skipShortCircuitDetection; // 是否跳过短路检测 (1字节) + private String noUserPullOutCheck; // 不判断用户拔出 (1字节) + private String forceAutoStopWhenFull; // 强制带充满自停 (1字节) + private String fullChargePower; // 充满功率 (1字节) + private String maxFullChargePowerCheckTime; // 充满功率最长判断时间 (1字节) + + public SpecificDataCmd82(byte[] dataBytes) { + byte rateModeBytes = dataBytes[0]; + this.rateMode = BytesUtil.bcd2StrLittle(new byte[]{rateModeBytes}); + + byte[] balanceOrValidityBytes = Arrays.copyOfRange(dataBytes, 1, 5); + this.balanceOrValidity = BytesUtil.bcd2StrLittle(balanceOrValidityBytes); + + byte portNumberBytes = dataBytes[5]; + this.portNumber = BytesUtil.bcd2StrLittle(new byte[]{portNumberBytes}); + + byte chargeCommandBytes = dataBytes[6]; + this.chargeCommand = BytesUtil.bcd2StrLittle(new byte[]{chargeCommandBytes}); + + byte[] chargeDurationOrPowerBytes = Arrays.copyOfRange(dataBytes, 7, 9); + this.chargeDurationOrPower = BytesUtil.bcd2StrLittle(chargeDurationOrPowerBytes); + + byte[] orderNumberBytes = Arrays.copyOfRange(dataBytes, 9, 25); + this.orderNumber = BytesUtil.bcd2StrLittle(orderNumberBytes); + + byte[] maxChargeDurationBytes = Arrays.copyOfRange(dataBytes, 25, 27); + this.maxChargeDuration = BytesUtil.bcd2StrLittle(maxChargeDurationBytes); + + byte[] overloadPowerBytes = Arrays.copyOfRange(dataBytes, 27, 29); + this.overloadPower = BytesUtil.bcd2StrLittle(overloadPowerBytes); + + byte qrCodeLightBytes = dataBytes[29]; + this.qrCodeLight = BytesUtil.bcd2StrLittle(new byte[]{qrCodeLightBytes}); + + byte longChargeModeBytes = dataBytes[30]; + this.longChargeMode = BytesUtil.bcd2StrLittle(new byte[]{longChargeModeBytes}); + + byte[] extraFloatChargeTimeBytes = Arrays.copyOfRange(dataBytes, 31, 33); + this.extraFloatChargeTime = BytesUtil.bcd2StrLittle(extraFloatChargeTimeBytes); + + byte skipShortCircuitDetectionBytes = dataBytes[33]; + this.skipShortCircuitDetection = BytesUtil.bcd2StrLittle(new byte[]{skipShortCircuitDetectionBytes}); + + byte noUserPullOutCheckBytes = dataBytes[34]; + this.noUserPullOutCheck = BytesUtil.bcd2StrLittle(new byte[]{noUserPullOutCheckBytes}); + + byte forceAutoStopWhenFullByte = dataBytes[35]; + this.forceAutoStopWhenFull = BytesUtil.bcd2StrLittle(new byte[]{forceAutoStopWhenFullByte}); + + byte fullChargePowerBytes = dataBytes[36]; + this.fullChargePower = BytesUtil.bcd2StrLittle(new byte[]{fullChargePowerBytes}); + + byte maxFullChargePowerCheckTimeBytes = dataBytes[37]; + this.maxFullChargePowerCheckTime = BytesUtil.bcd2StrLittle(new byte[]{maxFullChargePowerCheckTimeBytes}); + } +} \ No newline at end of file diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ChargingPileHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ChargingPileHandler.java index 4d95c8456..fa90c9fbe 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ChargingPileHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ChargingPileHandler.java @@ -1,7 +1,7 @@ package com.jsowell.netty.server.electricbicycles; import com.alibaba.fastjson2.JSON; -import com.jsowell.netty.domain.ebike.EBikeMessage; +import com.jsowell.netty.domain.ebike.AbsEBikeMessage; import io.netty.channel.*; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -9,10 +9,10 @@ import org.springframework.stereotype.Component; @ChannelHandler.Sharable @Slf4j @Component -public class ChargingPileHandler extends SimpleChannelInboundHandler { +public class ChargingPileHandler extends SimpleChannelInboundHandler { @Override - protected void channelRead0(ChannelHandlerContext ctx, EBikeMessage msg) throws Exception { + protected void channelRead0(ChannelHandlerContext ctx, AbsEBikeMessage msg) throws Exception { log.info("收到消息, channelId:{}, msg:{}", ctx.channel().id().toString(), JSON.toJSONString(msg)); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileBasicInfoServiceImpl.java index 91680f1d8..4f5e70540 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileBasicInfoServiceImpl.java @@ -11,7 +11,10 @@ import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode; import com.jsowell.common.core.redis.RedisCache; import com.jsowell.common.enums.DelFlagEnum; import com.jsowell.common.enums.lianlian.LianLianPileStatusEnum; -import com.jsowell.common.enums.ykc.*; +import com.jsowell.common.enums.ykc.PileConnectorDataBaseStatusEnum; +import com.jsowell.common.enums.ykc.PileConnectorStatusEnum; +import com.jsowell.common.enums.ykc.PileStatusEnum; +import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.util.*; import com.jsowell.pile.domain.*; @@ -25,7 +28,6 @@ import com.jsowell.pile.thirdparty.EquipmentInfo; import com.jsowell.pile.thirdparty.ZDLConnectorInfo; import com.jsowell.pile.thirdparty.ZDLEquipmentInfo; import com.jsowell.pile.util.UserUtils; -import com.jsowell.pile.vo.PileReservationInfoVO; import com.jsowell.pile.vo.base.MerchantInfoVO; import com.jsowell.pile.vo.base.PileInfoVO; import com.jsowell.pile.vo.uniapp.customer.GroundLockInfoVO; @@ -43,7 +45,6 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; -import java.sql.Time; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; @@ -1212,8 +1213,9 @@ public class PileBasicInfoServiceImpl implements PileBasicInfoService { public String startPersonalPileCharging(StartPersonPileDTO dto) { // 查询充电桩信息 PileConnectorDetailVO pileConnectorDetailVO = queryPileConnectorDetail(dto.getPileConnectorCode()); - if (pileConnectorDetailVO == null) { - throw new BusinessException("", ""); + if (pileConnectorDetailVO == null + || !StringUtils.equals(pileConnectorDetailVO.getConnectorStatus(), PileConnectorDataBaseStatusEnum.OCCUPIED_NOT_CHARGED.getValue())) { + throw new BusinessException(ReturnCodeEnum.CODE_PILE_CONNECTOR_STATUS_ERROR); } dto.setMerchantId(pileConnectorDetailVO.getMerchantId()); dto.setStationId(pileConnectorDetailVO.getStationId()); @@ -1221,8 +1223,7 @@ public class PileBasicInfoServiceImpl implements PileBasicInfoService { // String mode = pileMerchantInfoService.getDelayModeByAppIdAndRequestSource(dto.getAppId(), dto.getRequestSource()); String mode = pileMerchantInfoService.getDelayModeByMerchantId(pileConnectorDetailVO.getMerchantId()); AbstractProgramLogic orderLogic = ProgramLogicFactory.getProgramLogic(mode); - String orderCode = orderLogic.startPersonalPileCharging(dto); - return orderCode; + return orderLogic.startPersonalPileCharging(dto); } /**