mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-04-20 02:55:04 +08:00
update 电单车协议
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)) + "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
@@ -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<EBikeMessage> {
|
||||
public class ChargingPileHandler extends SimpleChannelInboundHandler<AbsEBikeMessage> {
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user