From 682f1b4b71d52a3915ac0320b9c01982f6879c15 Mon Sep 17 00:00:00 2001 From: Guoqs <123456@jsowell.com> Date: Sat, 13 Jun 2026 12:56:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8F=8B=E7=94=B5=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E5=85=BC=E5=AE=B9=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../netty/decoder/YouDianProtocolDecoder.java | 14 ++-- .../electricbicycles/RegistrationHandler.java | 42 ++++++++++- .../pile/domain/ebike/AbsEBikeMessage2.java | 26 ++++++- .../ebike/deviceupload/EBikeMessageCmd02.java | 23 ++++--- .../ebike/deviceupload/EBikeMessageCmd06.java | 69 ++++++++++++++----- .../ebike/deviceupload/EBikeMessageCmd20.java | 53 ++++++++++++-- .../ebike/deviceupload/EBikeMessageCmd21.java | 16 +++-- 7 files changed, 195 insertions(+), 48 deletions(-) diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/decoder/YouDianProtocolDecoder.java b/jsowell-netty/src/main/java/com/jsowell/netty/decoder/YouDianProtocolDecoder.java index 4bc09ecd6..033b3293c 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/decoder/YouDianProtocolDecoder.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/decoder/YouDianProtocolDecoder.java @@ -118,21 +118,23 @@ public class YouDianProtocolDecoder extends ByteToMessageDecoder { ByteBuf frame = null; try { // 检查剩余数据是否足够 - if (buffer.readableBytes() < HEADER_LENGTH_DNY + 1) { + if (buffer.readableBytes() < HEADER_LENGTH_DNY + 2) { buffer.readerIndex(beginReader); return; } - // 获取消息长度 - int length = buffer.getUnsignedByte(beginReader + HEADER_LENGTH_DNY); + // DNY协议长度域为2字节,小端模式。长度不包含包头和长度域本身。 + int length = buffer.getUnsignedByte(beginReader + HEADER_LENGTH_DNY) + | (buffer.getUnsignedByte(beginReader + HEADER_LENGTH_DNY + 1) << 8); // log.info("获取消息长度, length:{}", length); + int frameLength = HEADER_LENGTH_DNY + 2 + length; // 检查剩余数据是否足够 - if (buffer.readableBytes() < HEADER_LENGTH_DNY + 1 + length) { + if (buffer.readableBytes() < frameLength) { buffer.readerIndex(beginReader); return; } // 读取 data 数据 - frame = buffer.retainedSlice(beginReader, HEADER_LENGTH_DNY + length + 2); - buffer.readerIndex(beginReader + HEADER_LENGTH_DNY + length + 2); + frame = buffer.retainedSlice(beginReader, frameLength); + buffer.readerIndex(beginReader + frameLength); out.add(frame); } finally { // if (frame != null) { diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java index 698502fe9..5eeecfc25 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java @@ -1,8 +1,10 @@ package com.jsowell.netty.handler.electricbicycles; import com.alibaba.fastjson2.JSON; +import com.jsowell.common.YouDianUtils; import com.jsowell.common.constant.Constants; import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; +import com.jsowell.common.util.BytesUtil; import com.jsowell.common.util.YKCUtils; import com.jsowell.netty.factory.EBikeOperateFactory; import com.jsowell.pile.domain.ebike.EBikeCommandEnum; @@ -39,12 +41,46 @@ public class RegistrationHandler extends AbstractEBikeHandler { */ @Override public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { - // 解析字节数组 - EBikeMessageCmd20 message = new EBikeMessageCmd20(dataProtocol.getBytes()); + EBikeMessageCmd20 message; + try { + // 解析字节数组 + message = new EBikeMessageCmd20(dataProtocol.getBytes()); + } catch (Exception e) { + handleRegistrationParseError(dataProtocol, ctx, e); + return getResult(dataProtocol, Constants.zeroByteArray); + } + // 保存时间 saveLastTimeAndCheckChannel(message.getPhysicalId() + "", ctx); log.info("设备注册包:{}", JSON.toJSONString(message)); - pileBasicInfoService.registrationEBikePile(message); + try { + pileBasicInfoService.registrationEBikePile(message); + } catch (Exception e) { + log.error("设备注册包自动建档失败, pileSn:{}, portNumber:{}, msg:{}", + message.getPhysicalId(), message.getPortNumber(), BytesUtil.binary(dataProtocol.getBytes(), 16), e); + } return getResult(dataProtocol, Constants.zeroByteArray); } + + private void handleRegistrationParseError(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx, Exception e) { + String pileSn = null; + int portNumber = 0; + try { + pileSn = YouDianUtils.convertToPhysicalId(dataProtocol.getPhysicalId()) + ""; + byte[] msgBody = dataProtocol.getMsgBody(); + if (msgBody != null && msgBody.length >= 3) { + portNumber = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(msgBody, 2, 1)); + } + saveLastTimeAndCheckChannel(pileSn, ctx); + log.error("设备注册包解析失败, pileSn:{}, portNumber:{}, msg:{}, msgBodyLength:{}", + pileSn, portNumber, BytesUtil.binary(dataProtocol.getBytes(), 16), + msgBody == null ? 0 : msgBody.length, e); + if (portNumber > 0) { + pileBasicInfoService.ensureEBikePileRegistered(pileSn, portNumber, "registration_0x20_parse_fallback"); + } + } catch (Exception fallbackException) { + log.error("设备注册包解析失败后兜底处理失败, pileSn:{}, portNumber:{}, msg:{}", + pileSn, portNumber, BytesUtil.binary(dataProtocol.getBytes(), 16), fallbackException); + } + } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage2.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage2.java index b989972a0..3272e0d6b 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage2.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage2.java @@ -14,6 +14,8 @@ import java.util.Arrays; @Builder @ToString public class AbsEBikeMessage2 { + protected static final int DATA_START_INDEX = 12; + protected String header; // 包头 (3字节) protected int msgLength; // 长度 (2字节) protected int physicalId; // 物理ID (4字节) @@ -59,7 +61,7 @@ public class AbsEBikeMessage2 { startIndex += length; length = 1; byte[] commandBytes = BytesUtil.copyBytes(messageBytes, startIndex, length); - this.command = BytesUtil.bcd2StrLittle(commandBytes); + this.command = BytesUtil.printHexBinary(commandBytes); // 读取数据, 暂不处理, 交给子类处理 // byte[] dataBytes = BytesUtil.copyBytes(messageBytes, startIndex, length); @@ -72,4 +74,26 @@ public class AbsEBikeMessage2 { public byte[] getMessageBytes() { return null; }; + + protected int getBodyLength() { + return this.msgLength - 9; + } + + protected int getDataEndIndex(byte[] messageBytes) { + return messageBytes.length - 2; + } + + protected boolean hasDataBytes(byte[] messageBytes, int startIndex, int length) { + return length >= 0 + && startIndex >= DATA_START_INDEX + && startIndex + length <= getDataEndIndex(messageBytes); + } + + protected String getExtendedDataHex(byte[] messageBytes, int startIndex) { + int extendedLength = getDataEndIndex(messageBytes) - startIndex; + if (extendedLength <= 0) { + return null; + } + return BytesUtil.printHexBinary(BytesUtil.copyBytes(messageBytes, startIndex, extendedLength)); + } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd02.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd02.java index 76f7521be..15b34966f 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd02.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd02.java @@ -36,7 +36,7 @@ public class EBikeMessageCmd02 extends AbsEBikeMessage2 { /** * 时间戳 */ - // private String timestamp; + private String timestamp; /** * 卡号2字节数 @@ -51,7 +51,7 @@ public class EBikeMessageCmd02 extends AbsEBikeMessage2 { public EBikeMessageCmd02(byte[] messageBytes) { super(messageBytes); - int startIndex = 12; + int startIndex = DATA_START_INDEX; int length = 4; this.cardId = BytesUtil.bcd2Str(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; @@ -67,15 +67,20 @@ public class EBikeMessageCmd02 extends AbsEBikeMessage2 { length = 2; this.cardBalance = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; - // length = 4; - // this.timestamp = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, startIndex, startIndex = startIndex + length)) + ""; - - if (messageBytes.length > startIndex) { + startIndex += length; + length = 4; + if (hasDataBytes(messageBytes, startIndex, length)) { + this.timestamp = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; startIndex += length; - length = 1; - card2Length = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + } - this.card2Code = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; + length = 1; + if (hasDataBytes(messageBytes, startIndex, length)) { + card2Length = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + startIndex += length; + } + if (card2Length > 0 && hasDataBytes(messageBytes, startIndex, card2Length)) { + this.card2Code = BytesUtil.printHexBinary(BytesUtil.copyBytes(messageBytes, startIndex, card2Length)); } } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd06.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd06.java index 17a20212c..bc67982bf 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd06.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd06.java @@ -99,7 +99,7 @@ public class EBikeMessageCmd06 extends AbsEBikeMessage2 { /** * 上发指令当时的时间,有时候不准确,该字段属于调试使用,服务器无需关心此字段 */ - // private String timestamp; + private String timestamp; /** * 占位时长:(充电柜专用,其他设备忽略此字段)表示充满后占用设备的时长,单位为分钟 @@ -108,8 +108,9 @@ public class EBikeMessageCmd06 extends AbsEBikeMessage2 { public EBikeMessageCmd06(byte[] messageBytes) { super(messageBytes); + int dataEndIndex = getDataEndIndex(messageBytes); - int startIndex = 12; + int startIndex = DATA_START_INDEX; int length = 1; this.connectorCode = YouDianUtils.convertPortNumberToString(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))); @@ -160,31 +161,63 @@ public class EBikeMessageCmd06 extends AbsEBikeMessage2 { startIndex += length; length = 2; - this.peakPower = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) - .multiply(new BigDecimal("0.1")).toString(); + if (hasPeakPower(dataEndIndex - startIndex) && hasBytes(messageBytes, startIndex, length)) { + this.peakPower = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) + .multiply(new BigDecimal("0.1")).toString(); + startIndex += length; + } - startIndex += length; length = 2; - this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) - .multiply(new BigDecimal("0.1")).toString(); + if (hasBytes(messageBytes, startIndex, length)) { + this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) + .multiply(new BigDecimal("0.1")).toString(); + startIndex += length; + } - startIndex += length; length = 2; - this.current = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) - .multiply(new BigDecimal("0.001")).toString(); + if (hasBytes(messageBytes, startIndex, length)) { + this.current = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) + .multiply(new BigDecimal("0.001")).toString(); + startIndex += length; + } - startIndex += length; length = 1; - this.ambientTemperature = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) - .subtract(new BigDecimal("65")).toString(); + if (hasBytes(messageBytes, startIndex, length)) { + this.ambientTemperature = parseTemperature(messageBytes, startIndex, length); + startIndex += length; + } - startIndex += length; length = 1; - this.portTemperature = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) - .subtract(new BigDecimal("65")).toString(); + if (hasBytes(messageBytes, startIndex, length)) { + this.portTemperature = parseTemperature(messageBytes, startIndex, length); + startIndex += length; + } + + length = 4; + if (hasBytes(messageBytes, startIndex, length)) { + this.timestamp = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; + startIndex += length; + } - startIndex += length; length = 2; - this.occupancyTime = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; + if (hasBytes(messageBytes, startIndex, length)) { + this.occupancyTime = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; + } + } + + private boolean hasBytes(byte[] messageBytes, int startIndex, int length) { + return hasDataBytes(messageBytes, startIndex, length); + } + + private boolean hasPeakPower(int optionalLength) { + return optionalLength == 2 || optionalLength == 8 || optionalLength >= 12; + } + + private String parseTemperature(byte[] messageBytes, int startIndex, int length) { + int value = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + if (value == 0) { + return "0"; + } + return String.valueOf(value - 65); } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java index 93f5beada..bf022be54 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java @@ -12,6 +12,8 @@ import java.math.BigDecimal; @Setter @ToString(callSuper = true) public class EBikeMessageCmd20 extends AbsEBikeMessage2 { + private static final BigDecimal VERSION_FACTOR = new BigDecimal("0.01"); + /** * 固件版本,如100则表示V1.00版本 */ @@ -42,13 +44,30 @@ public class EBikeMessageCmd20 extends AbsEBikeMessage2 { */ private String powerBoardVersion; + /** + * 设备分时计费功能:0=不支持,1=支持。旧协议无此字段。 + */ + private Integer deviceSeparateBillingSupport; + + /** + * TC模式。旧协议无此字段。 + */ + private Integer tcMode; + + /** + * 扩展数据:标准注册字段后的原始扩展字段 + */ + private String extendedData; + public EBikeMessageCmd20(byte[] messageBytes) { super(messageBytes); + if (getBodyLength() < 8) { + throw new IllegalArgumentException("Invalid command 0x20 body length: " + getBodyLength()); + } - int startIndex = 12; + int startIndex = DATA_START_INDEX; int length = 2; - this.firmwareVersion = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) - .multiply(new BigDecimal("0.1")).toString(); + this.firmwareVersion = parseVersion(messageBytes, startIndex, length); startIndex += length; length = 1; @@ -64,10 +83,34 @@ public class EBikeMessageCmd20 extends AbsEBikeMessage2 { startIndex += length; length = 1; - this.workMode = BytesUtil.bcd2StrLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + this.workMode = BytesUtil.printHexBinary(BytesUtil.copyBytes(messageBytes, startIndex, length)); startIndex += length; length = 2; - this.powerBoardVersion = BytesUtil.bcd2StrLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + this.powerBoardVersion = parseVersion(messageBytes, startIndex, length); + + startIndex += length; + int extendedStartIndex = startIndex; + length = 1; + if (hasBytes(messageBytes, startIndex, length)) { + this.deviceSeparateBillingSupport = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + startIndex += length; + } + + length = 1; + if (hasBytes(messageBytes, startIndex, length)) { + this.tcMode = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + } + + this.extendedData = getExtendedDataHex(messageBytes, extendedStartIndex); + } + + private String parseVersion(byte[] messageBytes, int startIndex, int length) { + return new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) + .multiply(VERSION_FACTOR).toString(); + } + + private boolean hasBytes(byte[] messageBytes, int startIndex, int length) { + return hasDataBytes(messageBytes, startIndex, length); } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java index e9a59276e..1f95e858b 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java @@ -53,7 +53,7 @@ public class EBikeMessageCmd21 extends AbsEBikeMessage2 { super(messageBytes); // 读取结果 - int startIndex = 12; + int startIndex = DATA_START_INDEX; int length = 2; this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) .multiply(new BigDecimal("0.1")).toString(); @@ -78,14 +78,18 @@ public class EBikeMessageCmd21 extends AbsEBikeMessage2 { startIndex += length; length = 1; - this.rssi = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; + if (hasDataBytes(messageBytes, startIndex, length)) { + this.rssi = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; + } startIndex += length; length = 1; - int i = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); - if (i > 65) { - i = i - 65; + if (hasDataBytes(messageBytes, startIndex, length)) { + int i = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); + if (i > 0) { + i = i - 65; + } + this.temperature = i + ""; } - this.temperature = i + ""; } }