优化友电协议兼容逻辑

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

View File

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

View File

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

View File

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

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

View File

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

View File

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