优化友电协议兼容逻辑

This commit is contained in:
Guoqs
2026-06-13 12:56:09 +08:00
parent 4e774d7d7d
commit 682f1b4b71
7 changed files with 195 additions and 48 deletions

View File

@@ -118,21 +118,23 @@ public class YouDianProtocolDecoder extends ByteToMessageDecoder {
ByteBuf frame = null; ByteBuf frame = null;
try { try {
// 检查剩余数据是否足够 // 检查剩余数据是否足够
if (buffer.readableBytes() < HEADER_LENGTH_DNY + 1) { if (buffer.readableBytes() < HEADER_LENGTH_DNY + 2) {
buffer.readerIndex(beginReader); buffer.readerIndex(beginReader);
return; return;
} }
// 获取消息长度 // DNY协议长度域为2字节小端模式。长度不包含包头和长度域本身。
int length = buffer.getUnsignedByte(beginReader + HEADER_LENGTH_DNY); int length = buffer.getUnsignedByte(beginReader + HEADER_LENGTH_DNY)
| (buffer.getUnsignedByte(beginReader + HEADER_LENGTH_DNY + 1) << 8);
// log.info("获取消息长度, length:{}", length); // 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); buffer.readerIndex(beginReader);
return; return;
} }
// 读取 data 数据 // 读取 data 数据
frame = buffer.retainedSlice(beginReader, HEADER_LENGTH_DNY + length + 2); frame = buffer.retainedSlice(beginReader, frameLength);
buffer.readerIndex(beginReader + HEADER_LENGTH_DNY + length + 2); buffer.readerIndex(beginReader + frameLength);
out.add(frame); out.add(frame);
} finally { } finally {
// if (frame != null) { // if (frame != null) {

View File

@@ -1,8 +1,10 @@
package com.jsowell.netty.handler.electricbicycles; package com.jsowell.netty.handler.electricbicycles;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.jsowell.common.YouDianUtils;
import com.jsowell.common.constant.Constants; import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; import com.jsowell.common.core.domain.ebike.EBikeDataProtocol;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils; import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.EBikeOperateFactory; import com.jsowell.netty.factory.EBikeOperateFactory;
import com.jsowell.pile.domain.ebike.EBikeCommandEnum; import com.jsowell.pile.domain.ebike.EBikeCommandEnum;
@@ -39,12 +41,46 @@ public class RegistrationHandler extends AbstractEBikeHandler {
*/ */
@Override @Override
public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) {
// 解析字节数组 EBikeMessageCmd20 message;
EBikeMessageCmd20 message = new EBikeMessageCmd20(dataProtocol.getBytes()); try {
// 解析字节数组
message = new EBikeMessageCmd20(dataProtocol.getBytes());
} catch (Exception e) {
handleRegistrationParseError(dataProtocol, ctx, e);
return getResult(dataProtocol, Constants.zeroByteArray);
}
// 保存时间 // 保存时间
saveLastTimeAndCheckChannel(message.getPhysicalId() + "", ctx); saveLastTimeAndCheckChannel(message.getPhysicalId() + "", ctx);
log.info("设备注册包:{}", JSON.toJSONString(message)); 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); 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);
}
}
} }

View File

@@ -14,6 +14,8 @@ import java.util.Arrays;
@Builder @Builder
@ToString @ToString
public class AbsEBikeMessage2 { public class AbsEBikeMessage2 {
protected static final int DATA_START_INDEX = 12;
protected String header; // 包头 (3字节) protected String header; // 包头 (3字节)
protected int msgLength; // 长度 (2字节) protected int msgLength; // 长度 (2字节)
protected int physicalId; // 物理ID (4字节) protected int physicalId; // 物理ID (4字节)
@@ -59,7 +61,7 @@ public class AbsEBikeMessage2 {
startIndex += length; startIndex += length;
length = 1; length = 1;
byte[] commandBytes = BytesUtil.copyBytes(messageBytes, startIndex, length); byte[] commandBytes = BytesUtil.copyBytes(messageBytes, startIndex, length);
this.command = BytesUtil.bcd2StrLittle(commandBytes); this.command = BytesUtil.printHexBinary(commandBytes);
// 读取数据, 暂不处理, 交给子类处理 // 读取数据, 暂不处理, 交给子类处理
// byte[] dataBytes = BytesUtil.copyBytes(messageBytes, startIndex, length); // byte[] dataBytes = BytesUtil.copyBytes(messageBytes, startIndex, length);
@@ -72,4 +74,26 @@ public class AbsEBikeMessage2 {
public byte[] getMessageBytes() { public byte[] getMessageBytes() {
return null; 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));
}
} }

View File

@@ -36,7 +36,7 @@ public class EBikeMessageCmd02 extends AbsEBikeMessage2 {
/** /**
* 时间戳 * 时间戳
*/ */
// private String timestamp; private String timestamp;
/** /**
* 卡号2字节数 * 卡号2字节数
@@ -51,7 +51,7 @@ public class EBikeMessageCmd02 extends AbsEBikeMessage2 {
public EBikeMessageCmd02(byte[] messageBytes) { public EBikeMessageCmd02(byte[] messageBytes) {
super(messageBytes); super(messageBytes);
int startIndex = 12; int startIndex = DATA_START_INDEX;
int length = 4; int length = 4;
this.cardId = BytesUtil.bcd2Str(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; this.cardId = BytesUtil.bcd2Str(BytesUtil.copyBytes(messageBytes, startIndex, length)) + "";
@@ -67,15 +67,20 @@ public class EBikeMessageCmd02 extends AbsEBikeMessage2 {
length = 2; length = 2;
this.cardBalance = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + ""; this.cardBalance = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + "";
// length = 4; startIndex += length;
// this.timestamp = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, startIndex, startIndex = startIndex + length)) + ""; length = 4;
if (hasDataBytes(messageBytes, startIndex, length)) {
if (messageBytes.length > startIndex) { this.timestamp = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)) + "";
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));
} }
} }
} }

View File

@@ -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) { public EBikeMessageCmd06(byte[] messageBytes) {
super(messageBytes); super(messageBytes);
int dataEndIndex = getDataEndIndex(messageBytes);
int startIndex = 12; int startIndex = DATA_START_INDEX;
int length = 1; int length = 1;
this.connectorCode = YouDianUtils.convertPortNumberToString(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))); this.connectorCode = YouDianUtils.convertPortNumberToString(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)));
@@ -160,31 +161,63 @@ public class EBikeMessageCmd06 extends AbsEBikeMessage2 {
startIndex += length; startIndex += length;
length = 2; length = 2;
this.peakPower = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) if (hasPeakPower(dataEndIndex - startIndex) && hasBytes(messageBytes, startIndex, length)) {
.multiply(new BigDecimal("0.1")).toString(); this.peakPower = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)))
.multiply(new BigDecimal("0.1")).toString();
startIndex += length;
}
startIndex += length;
length = 2; length = 2;
this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) if (hasBytes(messageBytes, startIndex, length)) {
.multiply(new BigDecimal("0.1")).toString(); this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)))
.multiply(new BigDecimal("0.1")).toString();
startIndex += length;
}
startIndex += length;
length = 2; length = 2;
this.current = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) if (hasBytes(messageBytes, startIndex, length)) {
.multiply(new BigDecimal("0.001")).toString(); this.current = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)))
.multiply(new BigDecimal("0.001")).toString();
startIndex += length;
}
startIndex += length;
length = 1; length = 1;
this.ambientTemperature = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) if (hasBytes(messageBytes, startIndex, length)) {
.subtract(new BigDecimal("65")).toString(); this.ambientTemperature = parseTemperature(messageBytes, startIndex, length);
startIndex += length;
}
startIndex += length;
length = 1; length = 1;
this.portTemperature = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) if (hasBytes(messageBytes, startIndex, length)) {
.subtract(new BigDecimal("65")).toString(); 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; 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);
} }
} }

View File

@@ -12,6 +12,8 @@ import java.math.BigDecimal;
@Setter @Setter
@ToString(callSuper = true) @ToString(callSuper = true)
public class EBikeMessageCmd20 extends AbsEBikeMessage2 { public class EBikeMessageCmd20 extends AbsEBikeMessage2 {
private static final BigDecimal VERSION_FACTOR = new BigDecimal("0.01");
/** /**
* 固件版本如100则表示V1.00版本 * 固件版本如100则表示V1.00版本
*/ */
@@ -42,13 +44,30 @@ public class EBikeMessageCmd20 extends AbsEBikeMessage2 {
*/ */
private String powerBoardVersion; private String powerBoardVersion;
/**
* 设备分时计费功能0=不支持1=支持。旧协议无此字段。
*/
private Integer deviceSeparateBillingSupport;
/**
* TC模式。旧协议无此字段。
*/
private Integer tcMode;
/**
* 扩展数据:标准注册字段后的原始扩展字段
*/
private String extendedData;
public EBikeMessageCmd20(byte[] messageBytes) { public EBikeMessageCmd20(byte[] messageBytes) {
super(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; int length = 2;
this.firmwareVersion = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) this.firmwareVersion = parseVersion(messageBytes, startIndex, length);
.multiply(new BigDecimal("0.1")).toString();
startIndex += length; startIndex += length;
length = 1; length = 1;
@@ -64,10 +83,34 @@ public class EBikeMessageCmd20 extends AbsEBikeMessage2 {
startIndex += length; startIndex += length;
length = 1; length = 1;
this.workMode = BytesUtil.bcd2StrLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); this.workMode = BytesUtil.printHexBinary(BytesUtil.copyBytes(messageBytes, startIndex, length));
startIndex += length; startIndex += length;
length = 2; 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);
} }
} }

View File

@@ -53,7 +53,7 @@ public class EBikeMessageCmd21 extends AbsEBikeMessage2 {
super(messageBytes); super(messageBytes);
// 读取结果 // 读取结果
int startIndex = 12; int startIndex = DATA_START_INDEX;
int length = 2; int length = 2;
this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length))) this.voltage = new BigDecimal(BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)))
.multiply(new BigDecimal("0.1")).toString(); .multiply(new BigDecimal("0.1")).toString();
@@ -78,14 +78,18 @@ public class EBikeMessageCmd21 extends AbsEBikeMessage2 {
startIndex += length; startIndex += length;
length = 1; 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; startIndex += length;
length = 1; length = 1;
int i = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length)); if (hasDataBytes(messageBytes, startIndex, length)) {
if (i > 65) { int i = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(messageBytes, startIndex, length));
i = i - 65; if (i > 0) {
i = i - 65;
}
this.temperature = i + "";
} }
this.temperature = i + "";
} }
} }