背景
最近接手了一个充电桩项目,为了提高效率打算找一些开源项目来参考或改造,但遗憾的是没有找到与云快充1.5协议匹配的现有资源,只好自己从零开始了。在实现CRC校验这部分功能时,借鉴了网上的代码片段(链接稍后附上)。自我感觉代码还有待优化,还请各位高手不吝赐教,指出其中的不足之处。
采用netty跟充电桩交互
项目依赖netty跟hutool工具类
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.92.Final</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
编写Netty服务端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class NettyServer {
@Autowired
private NettyServerInitializer nettyServerInitializer;
public void serverRun() {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);// 默认128 Socket参数,服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128。
bootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 1024);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//Socket参数,连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);//TCP参数,立即发送数据,默认值为Ture。
bootstrap.handler(new LoggingHandler(LogLevel.INFO));
bootstrap.childHandler(nettyServerInitializer);
ChannelFuture future = bootstrap.bind(17001).sync();
log.info("server start ......");
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
import com.xtjc.recsyslog.netty.decoder.CustomDataDecoder;
import com.xtjc.recsyslog.netty.encoder.CustomDataEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private NettyHandler nettyHandler;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new CustomDataDecoder());
pipeline.addLast(new CustomDataEncoder());
//IdleStateHandler心跳机制,如果超时触发Handle中userEventTrigger()方法
pipeline.addLast("idleStateHandler", new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
pipeline.addLast(nettyHandler);
}
}
自定义编解码器
import cn.hutool.core.util.ArrayUtil;
import com.xtjc.recsyslog.util.ConverUtil;
import com.xtjc.recsyslog.util.YunCrcUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class CustomDataDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) {
// 打印当前缓冲区的内容
byte[] buffer = new byte[in.readableBytes()];
in.markReaderIndex();
in.readBytes(buffer);
log.info("接收到的10进制:{}",ArrayUtil.toString(buffer));
log.info("接收到的16进制: {}", ArrayUtil.toString(ConverUtil.bytesToHexArray(buffer)));
in.resetReaderIndex();
while (in.isReadable()) {
// 查找开始标志
int startFlagIndex = in.bytesBefore((byte) 0x68);
if (startFlagIndex == -1) {
break; // 没有找到开始标志,退出循环
}
// in.skipBytes(startFlagIndex + 1); // 跳过开始标志
byte startByte = in.readByte();
byte value = in.readByte();
/**
* 如果你希望将 A2 转换成 162 而不是 -94,你需要将 byte 类型的值视为无符号数。在 Java 中,byte 类型默认是有符号的,但你可以通过一些技巧将其视为无符号数。
* 解决方案
* 将 byte 转换为无符号整数。
* 调整 ByteToMessageDecoder 以处理无符号 byte 值。
* 1. 将 byte 转换为无符号整数
* 在 Java 中,可以通过将 byte 类型的值与 0xFF 进行按位与操作来将其视为无符号数。
* 2. 调整 ByteToMessageDecoder
* 下面是一个示例代码,展示了如何在 ByteToMessageDecoder 中处理无符号 byte 值。
*/
// 将 byte 类型的值转换为无符号整数
int length = value & 0xFF;//多亏有了GPT
// 读取消息体
byte[] body = new byte[length]; // 减去开始标志的长度
in.readBytes(body);
byte[] crcData = YunCrcUtil.calculateCrc(body);
// log.info("Crc : {}", crcData);
byte lowByte = in.readByte();
byte hiByte = in.readByte();
if (hiByte == crcData[0] && lowByte == crcData[1]) {
// log.info("crc校验通过");
out.add(ConverUtil.bytesToHexArray(body));
// out.add(HexUtil.encodeHex(body));
} else {
log.info("crc校验失败");
// 没有找到结束标志,保存已读取的部分并等待更多数据
in.resetReaderIndex();
break;
}
}
}
}
import com.xtjc.recsyslog.util.ConverUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomDataEncoder extends MessageToByteEncoder<String[]> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, String[] bytes, ByteBuf out) throws Exception {
log.info("发送数据:{}", (Object) bytes);
byte[] res = ConverUtil.hexArrayToByteArray(bytes);
// log.info("发送数据10进制:{}",res);
out.writeBytes(res);
}
}
处理接收充电桩报文
import cn.hutool.core.util.ArrayUtil;
import com.xtjc.recsyslog.netty.analyze.AnalyzeYunCharge;
import com.xtjc.recsyslog.netty.entity.ChargeEntity;
import com.xtjc.recsyslog.netty.util.NettyUtil;
import com.xtjc.recsyslog.util.ConverUtil;
import com.xtjc.recsyslog.util.Cp56Time2aUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyHandler extends SimpleChannelInboundHandler<String[]> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String[] sourceMsg) throws Exception {
try {
log.info("接收16进制原数据:{}", (Object) sourceMsg);
AnalyzeYunCharge analyzeYunCharge = AnalyzeYunCharge.analyzeYunCharge(sourceMsg);
String msgType = analyzeYunCharge.getMsgType();
switch (msgType) {
case "01":
R01(analyzeYunCharge, ctx);//充电桩登录认证
break;
case "03":
R03(analyzeYunCharge, ctx);//充电桩心跳包
break;
case "05":
R05(analyzeYunCharge, ctx);//计费模型验证请求
break;
case "09":
R09(analyzeYunCharge, ctx);//充电桩计费模型请求
break;
case "13":
R13(analyzeYunCharge);//上传实时监测数据
break;
case "33":
R33(analyzeYunCharge);//远程启动充电命令回复
break;
case "35":
R35(analyzeYunCharge);//远程停机命令回复
break;
case "51":
R51(analyzeYunCharge);//充电桩工作参数设置应答
break;
case "55":
R55(analyzeYunCharge);//对时设置应答
break;
case "3B"://交易记录
R59(analyzeYunCharge, ctx);
break;
case "91":
R91(analyzeYunCharge);//远程重启应答
break;
case "F1":
RF1(analyzeYunCharge);//桩应答远程下发二维码前缀指令
break;
default:
Rdefault(analyzeYunCharge, ctx);//打印默认值
break;
}
log.info("receive end");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
String ip = address.toString();
log.info("新客户端接入channelActive --> RamoteAddress : {} connected ", ip);
}
// 客户端断开
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
Attribute<ChargeEntity> chargeInfo = ctx.attr(NettyUtil.NETTY_CHANNEL_KEY);
ChargeEntity data = chargeInfo.get();
NettyUtil.removeChannel(data.getSnBCD());
log.debug("设备断开连接");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("异常关闭 exceptionCaught : {} ctx = {}", cause.toString(), ctx.channel().toString());
cause.printStackTrace();
Attribute<ChargeEntity> chargeInfo = ctx.attr(NettyUtil.NETTY_CHANNEL_KEY);
ChargeEntity data = chargeInfo.get();
NettyUtil.removeChannel(data.getSnBCD());
ctx.close();
}
private void RF1(AnalyzeYunCharge analyzeYunCharge) {
log.info("桩应答远程下发二维码前缀指令");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
String status = data[7];
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("下发结果:{}", status);
}
private void R59(AnalyzeYunCharge analyzeYunCharge, ChannelHandlerContext ctx) {
log.info("交易记录");
String[] data = analyzeYunCharge.getData();
String[] txnSeqNum = ArrayUtil.sub(data, 0, 16);
String[] sn = ArrayUtil.sub(data, 16, 23);
String gNum = data[23];
String[] startTime = ArrayUtil.sub(data, 24, 31);
String[] endTime = ArrayUtil.sub(data, 31, 38);
String[] A = ArrayUtil.sub(data, 38, 42);
String[] A_1 = ArrayUtil.sub(data, 42, 46);
String[] A_2 = ArrayUtil.sub(data, 46, 50);
String[] A_M = ArrayUtil.sub(data, 50, 54);
String[] B = ArrayUtil.sub(data, 54, 58);
String[] B_1 = ArrayUtil.sub(data, 58, 62);
String[] B_2 = ArrayUtil.sub(data, 62, 66);
String[] B_M = ArrayUtil.sub(data, 66, 70);
String[] C = ArrayUtil.sub(data, 70, 74);
String[] C_1 = ArrayUtil.sub(data, 74, 78);
String[] C_2 = ArrayUtil.sub(data, 78, 82);
String[] C_M = ArrayUtil.sub(data, 82, 86);
String[] D = ArrayUtil.sub(data, 86, 90);
String[] D_1 = ArrayUtil.sub(data, 90, 94);
String[] D_2 = ArrayUtil.sub(data, 94, 98);
String[] D_M = ArrayUtil.sub(data, 98, 102);
String[] start_total = ArrayUtil.sub(data, 102, 107);
String[] end_total = ArrayUtil.sub(data, 107, 112);
String[] total = ArrayUtil.sub(data, 112, 116);
String[] total_1 = ArrayUtil.sub(data, 116, 120);
String[] money = ArrayUtil.sub(data, 120, 124);
String[] vin = ArrayUtil.sub(data, 124, 141);
String code = data[141];
String[] tradingTime = ArrayUtil.sub(data, 142, 149);
String stopReason = data[149];
String[] wl_card_no = ArrayUtil.sub(data, 150, 158);
log.info("data:{}", ArrayUtil.toString(data));
log.info("交易流水号:{}", ArrayUtil.toString(txnSeqNum));
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("枪号:{}", gNum);
log.info("开始时间:{}", ArrayUtil.toString(startTime));
log.info("结束时间:{}", ArrayUtil.toString(endTime));
log.info("尖单价:{}", ArrayUtil.toString(A));
log.info("尖电量:{}", ArrayUtil.toString(A_1));
log.info("计损尖电量:{}", ArrayUtil.toString(A_2));
log.info("尖金额:{}", ArrayUtil.toString(A_M));
log.info("峰单价:{}", ArrayUtil.toString(B));
log.info("峰电量:{}", ArrayUtil.toString(B_1));
log.info("计损峰电量:{}", ArrayUtil.toString(B_2));
log.info("峰金额:{}", ArrayUtil.toString(B_M));
log.info("平单价:{}", ArrayUtil.toString(C));
log.info("平电量:{}", ArrayUtil.toString(C_1));
log.info("计损平电量:{}", ArrayUtil.toString(C_2));
log.info("平金额:{}", ArrayUtil.toString(C_M));
log.info("谷单价:{}", ArrayUtil.toString(D));
log.info("谷电量:{}", ArrayUtil.toString(D_1));
log.info("计损谷电量:{}", ArrayUtil.toString(D_2));
log.info("谷金额:{}", ArrayUtil.toString(D_M));
log.info("电表总起值:{}", ArrayUtil.toString(start_total));
log.info("电表总止值:{}", ArrayUtil.toString(end_total));
log.info("总电量:{}", ArrayUtil.toString(total));
log.info("计损总电量:{}", ArrayUtil.toString(total_1));
log.info("消费金额:{}", ArrayUtil.toString(money));
log.info("电动汽车唯一标识:{}", ArrayUtil.toString(vin));
log.info("交易标识:{}", code);
log.info("交易日期、时间:{}", ArrayUtil.toString(tradingTime));
log.info("停止原因:{}", stopReason);
log.info("物理卡号:{}", ArrayUtil.toString(wl_card_no));
String[] resData = AnalyzeYunCharge.tradingAck(analyzeYunCharge);
ctx.writeAndFlush(resData);
}
private void R35(AnalyzeYunCharge analyzeYunCharge) {
log.info("远程停机命令回复");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
String gNum = data[7];
String res = data[8];
String reason = data[9];
log.info("data:{}", ArrayUtil.toString(data));
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("枪号:{}", gNum);
log.info("停止结果:{}", res);
log.info("失败原因:{}", reason);
}
private void R33(AnalyzeYunCharge analyzeYunCharge) {
log.info("远程启动充电命令回复");
String[] data = analyzeYunCharge.getData();
String[] txnSeqNum = ArrayUtil.sub(data, 0, 16);
String[] sn = ArrayUtil.sub(data, 16, 23);
String gNum = data[23];
String status = data[24];
String re = data[25];
log.info("data:{}", ArrayUtil.toString(data));
log.info("交易流水号:{}", ArrayUtil.toString(txnSeqNum));
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("启动结果:{}", status);
log.info("枪号:{}", gNum);
log.info("失败原因:{}", re);
}
private void R91(AnalyzeYunCharge analyzeYunCharge) {
log.info("远程重启应答");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
String res = data[7];
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("设置结果:{}", res);
}
private void R55(AnalyzeYunCharge analyzeYunCharge) {
log.info("对时设置应答");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
String[] time = ArrayUtil.sub(data, 7, 14);
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("当前时间:{}", Cp56Time2aUtil.toDate(ConverUtil.hexArrayToByteArray(time)));
}
private void R51(AnalyzeYunCharge analyzeYunCharge) {
log.info("充电桩工作参数设置应答");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
String res = data[7];
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("设置结果:{}", res);
}
private void R13(AnalyzeYunCharge analyzeYunCharge) {
log.info("上传实时监测数据");
String[] data = analyzeYunCharge.getData();
String[] serialNumber = ArrayUtil.sub(data, 0, 16);
String[] sn = ArrayUtil.sub(data, 16, 23);
String gunNumber = data[23];
String chargeStatus = data[24];//0x00:离线 ,0x01:故障 ,0x02:空闲 ,0x03:充电 --- 需做到变位上送
String gunStatus = data[25];//0x00 否 0x01 是 0x02 未知 (无法检测到枪是否插回枪座即未知)
String sfcq = data[26];//0x00 否 0x01 是
String[] outVoltage = ArrayUtil.sub(data, 27, 29);
String[] outElectricity = ArrayUtil.sub(data, 29, 31);
String gunT = data[31];//整形,偏移量-50;待机置零
String[] gunCode = ArrayUtil.sub(data, 32, 40);//没有置零
String SOC = data[40];//待机置零;交流桩置零
String higT = data[41];
String[] chargingTime = ArrayUtil.sub(data, 42, 44);//单位:min;待机置零
String[] remaining = ArrayUtil.sub(data, 44, 46);//单位:min;待机置零、交流桩置零
String[] chargingDegree = ArrayUtil.sub(data, 46, 50);//精确到小数点后四位;待机置零
String[] jscdds = ArrayUtil.sub(data, 50, 54);
String[] ycje = ArrayUtil.sub(data, 54, 58);
String[] yjgz = ArrayUtil.sub(data, 58, 60);
log.info("data:{}",ArrayUtil.toString(data));
log.info("交易流水号:{}", ArrayUtil.toString(serialNumber));
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("枪号:{}", gunNumber);
log.info("状态:{}", chargeStatus);
log.info("枪是否归位:{}", gunStatus);
log.info("是否插枪:{}", sfcq);
log.info("输出电压:{}", ArrayUtil.toString(outVoltage));
log.info("输出电流:{}", ArrayUtil.toString(outElectricity));
log.info("枪线温度:{}", gunT);
log.info("枪线编码:{}", ArrayUtil.toString(gunCode));
log.info("SOC:{}", SOC);
log.info("电池组最高温度:{}", higT);
log.info("累计充电时间:{}", ArrayUtil.toString(chargingTime));
log.info("剩余充电时间:{}", ArrayUtil.toString(remaining));
log.info("充电度数:{}", ArrayUtil.toString(chargingDegree));
log.info("计损充电度数:{}", ArrayUtil.toString(jscdds));
log.info("已充金额:{}", ArrayUtil.toString(ycje));
log.info("硬件故障:{}", ArrayUtil.toString(yjgz));
}
private void R09(AnalyzeYunCharge analyzeYunCharge, ChannelHandlerContext ctx) {
log.info("充电桩计费模型请求");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
log.info("桩编码:{}", ArrayUtil.toString(sn));
String[] resData = AnalyzeYunCharge.chargeModelAck(analyzeYunCharge);
ctx.writeAndFlush(resData);
}
private void R05(AnalyzeYunCharge analyzeYunCharge, ChannelHandlerContext ctx) {
log.info("计费模型验证请求");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
String chargeModel1 = data[7];
String chargeModel2 = data[8];
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("计费模型编码:{},{}", chargeModel1, chargeModel2);
String[] resData = AnalyzeYunCharge.chargeModelCheckAck(analyzeYunCharge, chargeModel2.equals("07"));
ctx.writeAndFlush(resData);
}
private void R03(AnalyzeYunCharge analyzeYunCharge, ChannelHandlerContext ctx) {
// log.info("充电桩心跳包");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
NettyUtil.putChannel(String.join("", sn), new ChargeEntity(sn, ctx.channel()));//测试用
String gunNumber = data[7];
String gunStatus = data[8];
// log.info("桩编码:{}",ArrayUtil.toString(sn));
// log.info("枪号:{}",gunNumber);
// log.info("枪状态:{}",gunStatus);
String[] resData = AnalyzeYunCharge.heartBeatAck(analyzeYunCharge);
ctx.writeAndFlush(resData);
}
private void R01(AnalyzeYunCharge analyzeYunCharge, ChannelHandlerContext ctx) {
log.info("充电桩登录认证");
String[] data = analyzeYunCharge.getData();
String[] sn = ArrayUtil.sub(data, 0, 7);
//缓存充电桩channel
NettyUtil.putChannel(String.join("", sn), new ChargeEntity(sn, ctx.channel()));
String type = data[7];
String num = data[8];
String protocol = data[9];
String[] version = ArrayUtil.sub(data, 10, 18);
String networkType = data[18];
String[] sim = ArrayUtil.sub(data, 19, 29);
String operator = data[29];
log.info("桩编码:{}", ArrayUtil.toString(sn));
log.info("桩类型:{}", type);
log.info("充电枪数量:{}", num);
log.info("通信协议版本:{}", protocol);
log.info("程序版本:{}", ArrayUtil.toString(version));
log.info("网络链接类型:{}", networkType);
log.info("Sim 卡:{}", ArrayUtil.toString(sim));
log.info("运营商:{}", operator);
String[] resData = AnalyzeYunCharge.loginAck(analyzeYunCharge, true);
ctx.writeAndFlush(resData);
}
private void Rdefault(AnalyzeYunCharge analyzeYunCharge, ChannelHandlerContext ctx) {
log.debug("未知消息类型{}", analyzeYunCharge.getMsgType());
String[] data = analyzeYunCharge.getData();
log.debug("接收其他协议值:{}", (Object) data);
}
}
应答充电桩报文
import cn.hutool.core.util.ArrayUtil;
import com.xtjc.recsyslog.util.ConverUtil;
import com.xtjc.recsyslog.util.YunCrcUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data
@Slf4j
public class AnalyzeYunCharge {
private static final String before = "68";
//序列号域
private String number1;
private String number2;
//加密标志
private String encryptFlag;
//帧类型码
private String msgType;
//具体数据包
private String[] data;
private String[] loginAck;
public static AnalyzeYunCharge analyzeYunCharge(String[] sourceMsg) {
AnalyzeYunCharge analyzeYunCharge = new AnalyzeYunCharge();
analyzeYunCharge.setNumber1(sourceMsg[0]);
analyzeYunCharge.setNumber2(sourceMsg[1]);
analyzeYunCharge.setEncryptFlag(sourceMsg[2]);
analyzeYunCharge.setMsgType(sourceMsg[3]);
String[] data = new String[sourceMsg.length - 4];
System.arraycopy(sourceMsg, 4, data, 0, sourceMsg.length - 4);
analyzeYunCharge.setData(data);
return analyzeYunCharge;
}
/**
* 充电桩认证应答模式
*
* @param data 接收的数据
* @param status true 成功 false 失败
* @return
*/
public static String[] loginAck(AnalyzeYunCharge data, boolean status) {
String before = "68";
String number1 = data.getNumber1();
String number2 = data.getNumber2();
String encryptFlag = data.getEncryptFlag();
String type = "02";//登录认证应答
String[] sn = ArrayUtil.sub(data.getData(), 0, 7);
String loginStatus = status ? "00" : "01";
String[] body = ArrayUtil.newArray(String.class, sn.length + 5);
body[0] = number1;
body[1] = number2;
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[sn.length + 4] = loginStatus;
byte[] crcData = YunCrcUtil.calculateCrc(ConverUtil.hexArrayToByteArray(body));
byte lowByte = crcData[1];
byte hiByte = crcData[0];
int length = sn.length + 9;
String[] loginAck = ArrayUtil.newArray(String.class, length);
loginAck[0] = before;
loginAck[1] = String.format("%02X", body.length);
loginAck[2] = number1;
loginAck[3] = number2;
loginAck[4] = encryptFlag;
loginAck[5] = type;
System.arraycopy(sn, 0, loginAck, 6, sn.length);
loginAck[sn.length + 6] = loginStatus;
loginAck[sn.length + 7] = ConverUtil.byteToHex(lowByte);
loginAck[sn.length + 8] = ConverUtil.byteToHex(hiByte);
return loginAck;
}
/**
* 心跳应答
*
* @param data
* @return
*/
public static String[] heartBeatAck(AnalyzeYunCharge data) {
String before = "68";
String number1 = data.getNumber1();
String number2 = data.getNumber2();
String encryptFlag = data.getEncryptFlag();
String type = "04";//心跳包应答
String[] sn = ArrayUtil.sub(data.getData(), 0, 7);
String gunNumber = data.getData()[7];
String heartbeatResponse = "00";
String[] body = ArrayUtil.newArray(String.class, sn.length + 6);
body[0] = number1;
body[1] = number2;
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[sn.length + 4] = gunNumber;
body[sn.length + 5] = heartbeatResponse;
byte[] crcData = YunCrcUtil.calculateCrc(ConverUtil.hexArrayToByteArray(body));
byte lowByte = crcData[1];
byte hiByte = crcData[0];
int length = sn.length + 10;
String[] heartBeatAck = ArrayUtil.newArray(String.class, length);
heartBeatAck[0] = before;
heartBeatAck[1] = String.format("%02X", body.length);
heartBeatAck[2] = number1;
heartBeatAck[3] = number2;
heartBeatAck[4] = encryptFlag;
heartBeatAck[5] = type;
System.arraycopy(sn, 0, heartBeatAck, 6, sn.length);
heartBeatAck[sn.length + 6] = gunNumber;
heartBeatAck[sn.length + 7] = heartbeatResponse;
heartBeatAck[sn.length + 8] = ConverUtil.byteToHex(lowByte);
heartBeatAck[sn.length + 9] = ConverUtil.byteToHex(hiByte);
return heartBeatAck;
}
/**
* 计费模型验证请求应答
*
* @param data
* @param status 验证通过true 验证失败false
* @return
*/
public static String[] chargeModelCheckAck(AnalyzeYunCharge data, boolean status) {
String before = "68";
String number1 = data.getNumber1();
String number2 = data.getNumber2();
String encryptFlag = data.getEncryptFlag();
String type = "06";//计费模型验证请求应答
String[] sn = ArrayUtil.sub(data.getData(), 0, 7);
String chargeModel1 = data.getData()[7];
String chargeModel2 = data.getData()[8];
String checkFlag = status ? "00" : "01";
String[] body = ArrayUtil.newArray(String.class, sn.length + 7);
body[0] = number1;
body[1] = number2;
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[sn.length + 4] = chargeModel1;
body[sn.length + 5] = chargeModel2;
body[sn.length + 6] = checkFlag;
byte[] crcData = YunCrcUtil.calculateCrc(ConverUtil.hexArrayToByteArray(body));
byte lowByte = crcData[1];
byte hiByte = crcData[0];
int length = sn.length + 11;
String[] chargeModelAck = ArrayUtil.newArray(String.class, length);
chargeModelAck[0] = before;
chargeModelAck[1] = String.format("%02X", body.length);
chargeModelAck[2] = number1;
chargeModelAck[3] = number2;
chargeModelAck[4] = encryptFlag;
chargeModelAck[5] = type;
System.arraycopy(sn, 0, chargeModelAck, 6, sn.length);
chargeModelAck[sn.length + 6] = chargeModel1;
chargeModelAck[sn.length + 7] = chargeModel2;
chargeModelAck[sn.length + 8] = checkFlag;
chargeModelAck[sn.length + 9] = ConverUtil.byteToHex(lowByte);
chargeModelAck[sn.length + 10] = ConverUtil.byteToHex(hiByte);
return chargeModelAck;
}
/**
* 计费模型请求应答
*
* @param data
* @return
*/
public static String[] chargeModelAck(AnalyzeYunCharge data) {
String before = "68";
String number1 = data.getNumber1();
String number2 = data.getNumber2();
String encryptFlag = data.getEncryptFlag();
String type = "0A";//计费模型请求应答
String[] sn = ArrayUtil.sub(data.getData(), 0, 7);
String chargeModel1 = "01";
String chargeModel2 = "07";
String[] chargeRate_1 = {"40", "0D", "03", "00"};//2.00
String[] chargeRate_2 = {"40", "0D", "03", "00"};
String[] chargeRate_3 = {"40", "0D", "03", "00"};
String[] chargeRate_4 = {"40", "0D", "03", "00"};
String[] serverRate_1 = {"9C", "40", "00", "00"};//0.16
String[] serverRate_2 = {"9C", "40", "00", "00"};
String[] serverRate_3 = {"9C", "40", "00", "00"};
String[] serverRate_4 = {"9C", "40", "00", "00"};
String[] body = ArrayUtil.newArray(String.class, 2 + 1 + 1 + 7 + 2 + 32 + 1 + 48);
body[0] = number1;
body[1] = number2;
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[11] = chargeModel1;
body[12] = chargeModel2;
System.arraycopy(chargeRate_1, 0, body, 13, chargeRate_1.length);
System.arraycopy(serverRate_1, 0, body, 17, serverRate_1.length);
System.arraycopy(chargeRate_2, 0, body, 21, chargeRate_2.length);
System.arraycopy(serverRate_2, 0, body, 25, serverRate_2.length);
System.arraycopy(chargeRate_3, 0, body, 29, chargeRate_3.length);
System.arraycopy(serverRate_3, 0, body, 33, serverRate_3.length);
System.arraycopy(chargeRate_4, 0, body, 37, chargeRate_4.length);
System.arraycopy(serverRate_4, 0, body, 41, serverRate_4.length);
body[45] = "00";
for (int i = 1; i < 49; i++) {
body[45 + i] = "00";
}
byte[] crcData = YunCrcUtil.calculateCrc(ConverUtil.hexArrayToByteArray(body));
byte lowByte = crcData[1];
byte hiByte = crcData[0];
int length = 1 + 1 + 2 + 1 + 1 + 7 + 2 + 32 + 1 + 48 + 2;
String[] chargeModelAck = ArrayUtil.newArray(String.class, length);
chargeModelAck[0] = before;
chargeModelAck[1] = String.format("%02X", body.length);
System.arraycopy(body, 0, chargeModelAck, 2, body.length);
chargeModelAck[length - 2] = ConverUtil.byteToHex(lowByte);
chargeModelAck[length - 1] = ConverUtil.byteToHex(hiByte);
return chargeModelAck;
}
/**
* 交易记录确认
*
* @param data
* @return
*/
public static String[] tradingAck(AnalyzeYunCharge data) {
String[] number = {data.getNumber1(), data.getNumber2()};
String encryptFlag = "00";
String type = "40";//交易记录确认
String[] txnSeqNum = ArrayUtil.sub(data.getData(), 0, 16);
String[] body = ArrayUtil.newArray(String.class, 21);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(txnSeqNum, 0, body, 4, txnSeqNum.length);
body[20] = "00";
return commend(body);
}
private static String[] commend(String[] body) {
byte[] crcData = YunCrcUtil.calculateCrc(ConverUtil.hexArrayToByteArray(body));
byte lowByte = crcData[1];
byte hiByte = crcData[0];
int length = body.length + 4;
String[] resAck = ArrayUtil.newArray(String.class, body.length + 4);
resAck[0] = before;
resAck[1] = String.format("%02X", body.length);
System.arraycopy(body, 0, resAck, 2, body.length);
resAck[length - 2] = ConverUtil.byteToHex(lowByte);
resAck[length - 1] = ConverUtil.byteToHex(hiByte);
return resAck;
}
}
主动发起操作
import cn.hutool.core.util.ArrayUtil;
import com.xtjc.recsyslog.netty.entity.ChargeEntity;
import com.xtjc.recsyslog.util.ConverUtil;
import com.xtjc.recsyslog.util.Cp56Time2aUtil;
import com.xtjc.recsyslog.util.YunCrcUtil;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Slf4j
public class ChargeCommand {
private static final String before = "68";
/**
* 读取实时监测数据
*
* @param gunNumber 枪号
* @return 这个命令下发完 没达到预期效果 待沟通确认
*/
public static String[] getChargeData(String gunNumber, ChargeEntity charge) {
String[] number = charge.getSerialNumber();
String encryptFlag = "00";
String type = "12";//读取实时监测数据
String[] sn = charge.getSn16();
String[] body = ArrayUtil.newArray(String.class, 12);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[11] = gunNumber;
return commend(body);
}
/**
* 充电桩工作参数设置
*
* @param workStatus 工作状态
* @param output 输出电压功率百分之比
* @return 这个命令下发完 没达到预期效果 待沟通确认
*/
public static String[] setChargeWorkParam(boolean workStatus, int output, ChargeEntity charge) {
String[] number = charge.getSerialNumber();
String encryptFlag = "00";
String type = "52";//充电桩工作参数设置
String[] sn = charge.getSn16();
String[] body = ArrayUtil.newArray(String.class, 13);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[11] = workStatus ? "00" : "01";
body[12] = ConverUtil.byteToHex(output);
return commend(body);
}
/**
* 对时设置
*
* @param charge
*/
public static String[] setChargeTimeZone(ChargeEntity charge) {
String[] number = charge.getSerialNumber();
String encryptFlag = "00";
String type = "56";//对时设置
String[] sn = charge.getSn16();
String[] time = Cp56Time2aUtil.date2HexArr(new Date());
String[] body = ArrayUtil.newArray(String.class, 18);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
System.arraycopy(time, 0, body, 11, time.length);
return commend(body);
}
/**
* 重启充电桩
*
* @param flag 立即重启 空闲重启
* @param charge
* @return 这个命令下发完 没达到预期效果 待沟通确认
*/
public static String[] restart(boolean flag, ChargeEntity charge) {
String[] number = charge.getSerialNumber();
String encryptFlag = "00";
String type = "92";//远程重启
String[] sn = charge.getSn16();
String[] body = ArrayUtil.newArray(String.class, 12);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[11] = flag ? "01" : "02";
return commend(body);
}
/**
* 远程启动充电
*
* @param charge
* @return
*/
public static String[] remoteStartCharge(ChargeEntity charge) {
String[] number = charge.getSerialNumber();
String[] txnSeqNum = charge.getTxnSeqNum();
String[] sn = charge.getSn16();
String[] ljNum = {"00", "00", "00", "00", "00", "00", "00", "01"};
String[] wlNum = {"00", "00", "00", "00", "00", "00", "00", "01"};
String[] balance = {"46", "22"};
String encryptFlag = "00";
String type = "34";//运营平台远程控制启机
String[] body = ArrayUtil.newArray(String.class, 46);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(txnSeqNum, 0, body, 4, txnSeqNum.length);
System.arraycopy(sn, 0, body, 20, sn.length);
body[27] = "01";//枪号
System.arraycopy(ljNum, 0, body, 28, ljNum.length);
System.arraycopy(wlNum, 0, body, 36, wlNum.length);
System.arraycopy(balance, 0, body, 44, balance.length);
return commend(body);
}
/**
* 运营平台远程停机
*
* @param charge
* @return
*/
public static String[] remoteStopCharge(ChargeEntity charge) {
String[] number = charge.getSerialNumber();
String[] sn = charge.getSn16();
String encryptFlag = "00";
String gNum = "01";
String type = "36";//运营平台远程停机
String[] body = ArrayUtil.newArray(String.class, 12);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[11] = gNum;
return commend(body);
}
/**
* 下发二维码前缀指令
*
* @param charge
* @return
*/
public static String[] sendQrCodeData(ChargeEntity charge) {
String uri = "www.baidu.com?No=";
String[] uri_Ascii = ConverUtil.bytesToHexArray(uri.getBytes(StandardCharsets.US_ASCII));
String[] number = charge.getSerialNumber();
String[] sn = charge.getSn16();
String encryptFlag = "00";
String type = "F0";//下发二维码前缀指令
String[] body = ArrayUtil.newArray(String.class, 13 + uri_Ascii.length);
System.arraycopy(number, 0, body, 0, number.length);
body[2] = encryptFlag;
body[3] = type;
System.arraycopy(sn, 0, body, 4, sn.length);
body[11] = "00";
body[12] = ConverUtil.byteToHex(uri_Ascii.length);
System.arraycopy(uri_Ascii, 0, body, 13, uri_Ascii.length);
return commend(body);
}
private static String[] commend(String[] body) {
byte[] crcData = YunCrcUtil.calculateCrc(ConverUtil.hexArrayToByteArray(body));
byte lowByte = crcData[1];
byte hiByte = crcData[0];
int length = body.length + 4;
String[] resAck = ArrayUtil.newArray(String.class, body.length + 4);
resAck[0] = before;
resAck[1] = String.format("%02X", body.length);
System.arraycopy(body, 0, resAck, 2, body.length);
resAck[length - 2] = ConverUtil.byteToHex(lowByte);
resAck[length - 1] = ConverUtil.byteToHex(hiByte);
return resAck;
}
}
启动netty服务端
import com.xtjc.recsyslog.netty.NettyServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class InitTask implements CommandLineRunner {
@Autowired
private NettyServer nettyServer;
@Override
public void run(String... args) throws Exception {
nettyServer.serverRun();
}
}
其他工具类
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayOutputStream;
/**
* 进制转换
*
* @author hasee
*/
@Slf4j
public class ConverUtil {
/**
* 将16进制字符串转换为字符串数组
*
* @param hexString 16进制字符串
* @return 字符串数组
*/
public static String[] convertHexStringToStringArray(String hexString) {
int length = hexString.length();
int arrayLength = length / 2;
String[] hexArray = new String[arrayLength];
for (int i = 0; i < length; i += 2) {
hexArray[i / 2] = hexString.substring(i, i + 2);
}
return hexArray;
}
public static byte[] hexArrayToByteArray(String[] hexStrings) {
int i = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
for (String hexString : hexStrings) {
if (StrUtil.isEmpty(hexString)) {
log.error("第{}个字节为空", i);
}
i++;
int intValue = Integer.parseInt(hexString, 16);
byteArrayOutputStream.write(intValue); // 写入 byte 值
}
return byteArrayOutputStream.toByteArray(); // 转换为 byte 数组并返回
}
public static String byteToHex(byte b) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
hex = "0" + hex;
}
return hex.toUpperCase(); // 可选:如果需要大写的 16 进制字符
}
public static String byteToHex(int b) {
String hex = Integer.toHexString(b);
if (hex.length() == 1 || hex.length() == 3) {
hex = "0" + hex;
}
return hex.toUpperCase(); // 可选:如果需要大写的 16 进制字符
}
public static String[] bytesToHexArray(byte[] bytes) {
String[] hexStrings = new String[bytes.length];
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]).toUpperCase();
if (hex.length() == 1) {
hex = "0" + hex;
}
hexStrings[i] = hex;
}
return hexStrings;
}
/**
* 字符串转换为Ascii
*
* @param value
* @return
*/
public static String stringTransformAscii(String value) {
StringBuffer sbu = new StringBuffer();
char[] chars = value.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (i != chars.length - 1) {
sbu.append((int) chars[i]).append(",");
} else {
sbu.append((int) chars[i]);
}
}
return sbu.toString();
}
public static String[] stringTransformAsciiToArray(String value) {
// 获取输入字符串的长度
int length = value.length();
// 初始化结果数组,长度与输入字符串相同
String[] asciiValues = new String[length];
// 遍历输入字符串中的每个字符
char[] chars = value.toCharArray();
for (int i = 0; i < length; i++) {
// 将当前字符的 ASCII 值转换为字符串,并存储在结果数组中
asciiValues[i] = String.valueOf((int) chars[i]);
}
return asciiValues;
}
/**
* Ascii转换为字符串
*
* @param value
* @return
*/
public static String asciiTransformString(String value) {
StringBuffer sbu = new StringBuffer();
String[] chars = value.split(",");
for (int i = 0; i < chars.length; i++) {
sbu.append((char) Integer.parseInt(chars[i]));
}
return sbu.toString();
}
}
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.HexUtil;
import java.util.Calendar;
import java.util.Date;
public class Cp56Time2aUtil {
/**
* Cp56Time2a转时间字符串
*
* @param bytes 字符数组
* @return 时间字符串
*/
public static String toDateString(byte[] bytes) {
int milliseconds1 = bytes[0] < 0 ? 256 + bytes[0] : bytes[0];
int milliseconds2 = bytes[1] < 0 ? 256 + bytes[1] : bytes[1];
int milliseconds = milliseconds2 * 256 + milliseconds1;
// 位于 0011 1111
int minutes = bytes[2] & 0x3F;
// 位于 0001 1111
int hours = bytes[3] & 0x1F;
// 位于 0001 1111
int days = bytes[4] & 0x1F;
// 位于 0000 1111
int months = bytes[5] & 0x0F;
// 位于 0111 1111
int years = bytes[6] & 0x7F;
return "20" + String.format("%02d", years) + "-" + String.format("%02d", months) + "-" + String.format("%02d", days) +
" " + String.format("%02d", hours) + ":" + String.format("%02d", minutes) + ":" +
String.format("%02d", milliseconds / 1000);
}
/**
* 转为时间格式
*
* @param bytes 字符数组
* @return 时间
*/
public static Date toDate(byte[] bytes) {
String dateString = toDateString(bytes);
return DateUtil.parse(dateString, "yyyy-MM-dd HH:mm:ss");
}
/**
* 时间转16进制字符串
*
* @param date 时间
* @return 16进制字符串
*/
public static String date2HexStr(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
StringBuilder builder = new StringBuilder();
String milliSecond = String.format("%04X", (calendar.get(Calendar.SECOND) * 1000) + calendar.get(Calendar.MILLISECOND));
builder.append(milliSecond.substring(2, 4));
builder.append(milliSecond.substring(0, 2));
builder.append(String.format("%02X", calendar.get(Calendar.MINUTE) & 0x3F));
builder.append(String.format("%02X", calendar.get(Calendar.HOUR_OF_DAY) & 0x1F));
int week = calendar.get(Calendar.DAY_OF_WEEK);
if (week == Calendar.SUNDAY) {
week = 7;
} else {
week--;
}
builder.append(String.format("%02X", (week << 5) + (calendar.get(Calendar.DAY_OF_MONTH) & 0x1F)));
builder.append(String.format("%02X", calendar.get(Calendar.MONTH) + 1));
builder.append(String.format("%02X", calendar.get(Calendar.YEAR) - 2000));
return builder.toString();
}
public static String[] date2HexArr(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String[] resData = new String[7];
String milliSecond = String.format("%04X", (calendar.get(Calendar.SECOND) * 1000) + calendar.get(Calendar.MILLISECOND));
resData[0] = milliSecond.substring(2, 4);
resData[1] = milliSecond.substring(0, 2);
resData[2] = String.format("%02X", calendar.get(Calendar.MINUTE) & 0x3F);
resData[3] = String.format("%02X", calendar.get(Calendar.HOUR_OF_DAY) & 0x1F);
int week = calendar.get(Calendar.DAY_OF_WEEK);
if (week == Calendar.SUNDAY) {
week = 7;
} else {
week--;
}
resData[4] = String.format("%02X", (week << 5) + (calendar.get(Calendar.DAY_OF_MONTH) & 0x1F));
resData[5] = String.format("%02X", calendar.get(Calendar.MONTH) + 1);
resData[6] = String.format("%02X", calendar.get(Calendar.YEAR) - 2000);
return resData;
}
}
import java.util.Arrays;
/**
* @author hua
* @date 2023-08-22
*/
public class YunCrcUtil {
public static final byte[] gabyCRCHi = {
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40
};
public static final byte[] gabyCRCLo = {
(byte) 0x00, (byte) 0xC0, (byte) 0xC1, (byte) 0x01, (byte) 0xC3, (byte) 0x03, (byte) 0x02, (byte) 0xC2, (byte) 0xC6, (byte) 0x06, (byte) 0x07, (byte) 0xC7, (byte) 0x05, (byte) 0xC5, (byte) 0xC4, (byte) 0x04,
(byte) 0xCC, (byte) 0x0C, (byte) 0x0D, (byte) 0xCD, (byte) 0x0F, (byte) 0xCF, (byte) 0xCE, (byte) 0x0E, (byte) 0x0A, (byte) 0xCA, (byte) 0xCB, (byte) 0x0B, (byte) 0xC9, (byte) 0x09, (byte) 0x08, (byte) 0xC8,
(byte) 0xD8, (byte) 0x18, (byte) 0x19, (byte) 0xD9, (byte) 0x1B, (byte) 0xDB, (byte) 0xDA, (byte) 0x1A, (byte) 0x1E, (byte) 0xDE, (byte) 0xDF, (byte) 0x1F, (byte) 0xDD, (byte) 0x1D, (byte) 0x1C, (byte) 0xDC,
(byte) 0x14, (byte) 0xD4, (byte) 0xD5, (byte) 0x15, (byte) 0xD7, (byte) 0x17, (byte) 0x16, (byte) 0xD6, (byte) 0xD2, (byte) 0x12, (byte) 0x13, (byte) 0xD3, (byte) 0x11, (byte) 0xD1, (byte) 0xD0, (byte) 0x10,
(byte) 0xF0, (byte) 0x30, (byte) 0x31, (byte) 0xF1, (byte) 0x33, (byte) 0xF3, (byte) 0xF2, (byte) 0x32, (byte) 0x36, (byte) 0xF6, (byte) 0xF7, (byte) 0x37, (byte) 0xF5, (byte) 0x35, (byte) 0x34, (byte) 0xF4,
(byte) 0x3C, (byte) 0xFC, (byte) 0xFD, (byte) 0x3D, (byte) 0xFF, (byte) 0x3F, (byte) 0x3E, (byte) 0xFE, (byte) 0xFA, (byte) 0x3A, (byte) 0x3B, (byte) 0xFB, (byte) 0x39, (byte) 0xF9, (byte) 0xF8, (byte) 0x38,
(byte) 0x28, (byte) 0xE8, (byte) 0xE9, (byte) 0x29, (byte) 0xEB, (byte) 0x2B, (byte) 0x2A, (byte) 0xEA, (byte) 0xEE, (byte) 0x2E, (byte) 0x2F, (byte) 0xEF, (byte) 0x2D, (byte) 0xED, (byte) 0xEC, (byte) 0x2C,
(byte) 0xE4, (byte) 0x24, (byte) 0x25, (byte) 0xE5, (byte) 0x27, (byte) 0xE7, (byte) 0xE6, (byte) 0x26, (byte) 0x22, (byte) 0xE2, (byte) 0xE3, (byte) 0x23, (byte) 0xE1, (byte) 0x21, (byte) 0x20, (byte) 0xE0,
(byte) 0xA0, (byte) 0x60, (byte) 0x61, (byte) 0xA1, (byte) 0x63, (byte) 0xA3, (byte) 0xA2, (byte) 0x62, (byte) 0x66, (byte) 0xA6, (byte) 0xA7, (byte) 0x67, (byte) 0xA5, (byte) 0x65, (byte) 0x64, (byte) 0xA4,
(byte) 0x6C, (byte) 0xAC, (byte) 0xAD, (byte) 0x6D, (byte) 0xAF, (byte) 0x6F, (byte) 0x6E, (byte) 0xAE, (byte) 0xAA, (byte) 0x6A, (byte) 0x6B, (byte) 0xAB, (byte) 0x69, (byte) 0xA9, (byte) 0xA8, (byte) 0x68,
(byte) 0x78, (byte) 0xB8, (byte) 0xB9, (byte) 0x79, (byte) 0xBB, (byte) 0x7B, (byte) 0x7A, (byte) 0xBA, (byte) 0xBE, (byte) 0x7E, (byte) 0x7F, (byte) 0xBF, (byte) 0x7D, (byte) 0xBD, (byte) 0xBC, (byte) 0x7C,
(byte) 0xB4, (byte) 0x74, (byte) 0x75, (byte) 0xB5, (byte) 0x77, (byte) 0xB7, (byte) 0xB6, (byte) 0x76, (byte) 0x72, (byte) 0xB2, (byte) 0xB3, (byte) 0x73, (byte) 0xB1, (byte) 0x71, (byte) 0x70, (byte) 0xB0,
(byte) 0x50, (byte) 0x90, (byte) 0x91, (byte) 0x51, (byte) 0x93, (byte) 0x53, (byte) 0x52, (byte) 0x92, (byte) 0x96, (byte) 0x56, (byte) 0x57, (byte) 0x97, (byte) 0x55, (byte) 0x95, (byte) 0x94, (byte) 0x54,
(byte) 0x9C, (byte) 0x5C, (byte) 0x5D, (byte) 0x9D, (byte) 0x5F, (byte) 0x9F, (byte) 0x9E, (byte) 0x5E, (byte) 0x5A, (byte) 0x9A, (byte) 0x9B, (byte) 0x5B, (byte) 0x99, (byte) 0x59, (byte) 0x58, (byte) 0x98,
(byte) 0x88, (byte) 0x48, (byte) 0x49, (byte) 0x89, (byte) 0x4B, (byte) 0x8B, (byte) 0x8A, (byte) 0x4A, (byte) 0x4E, (byte) 0x8E, (byte) 0x8F, (byte) 0x4F, (byte) 0x8D, (byte) 0x4D, (byte) 0x4C, (byte) 0x8C,
(byte) 0x44, (byte) 0x84, (byte) 0x85, (byte) 0x45, (byte) 0x87, (byte) 0x47, (byte) 0x46, (byte) 0x86, (byte) 0x82, (byte) 0x42, (byte) 0x43, (byte) 0x83, (byte) 0x41, (byte) 0x81, (byte) 0x80, (byte) 0x40
};
private YunCrcUtil() {
}
public static byte[] calculateCrc(byte[] data) {
int crc;
int ucCRCHi = 0x00ff;
int ucCRCLo = 0x00ff;
int iIndex;
for (int i = 0; i < data.length; ++i) {
iIndex = (ucCRCLo ^ data[i]) & 0x00ff;
ucCRCLo = ucCRCHi ^ gabyCRCHi[iIndex];
ucCRCHi = gabyCRCLo[iIndex];
}
crc = ((ucCRCHi & 0x00ff) << 8) | (ucCRCLo & 0x00ff) & 0xffff;
crc = ((crc & 0xFF00) >> 8) | ((crc & 0x00FF) << 8);
byte[] bytes = toBytes(crc);
return Arrays.copyOfRange(bytes, 2, 4);
}
public static byte[] toBytes(int value) {
byte[] result = new byte[]{(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value};
return result;
}
}
import com.xtjc.recsyslog.netty.entity.ChargeEntity;
import io.netty.channel.Channel;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class NettyUtil {
public static final AttributeKey<ChargeEntity> NETTY_CHANNEL_KEY = AttributeKey.valueOf("netty.channel");
private static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> ONLINE_SN_MAP = new ConcurrentHashMap<>();
public static AttributeKey<String> SN = AttributeKey.valueOf("sn");
//保存所有充电桩回话
public static ConcurrentHashMap<String, ChargeEntity> channelMap = new ConcurrentHashMap<>();
public static <T> void setAttr(Channel channel, AttributeKey<T> attributeKey, T data) {
Attribute<T> attr = channel.attr(attributeKey);
attr.set(data);
}
public static <T> T getAttr(Channel channel, AttributeKey<T> ip) {
return channel.attr(ip).get();
}
public static void putChannel(String key, ChargeEntity charge) {
if (channelMap.containsKey(key)) {
return;
}
setAttr(charge.getChannel(),NETTY_CHANNEL_KEY,charge);
channelMap.put(key, charge);
}
public static void removeChannel(String key){
channelMap.remove(key);
}
public static ChargeEntity getChannel(String key) {
return channelMap.get(key);
}
public static String[] getChannelNames() {
return channelMap.keySet().toArray(new String[0]);
}
}
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import com.xtjc.recsyslog.util.ConverUtil;
import io.netty.channel.Channel;
import lombok.Data;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
@Data
public class ChargeEntity {
//唯一标识
private String[] sn16;
private String snBCD;
//通道
private Channel channel;
//发送序列号
private String[] serialNumber;
private AtomicInteger counter = new AtomicInteger(1);
private AtomicInteger sequenceNumber = new AtomicInteger(1);
public ChargeEntity(String[] sn16, Channel channel) {
this.sn16 = sn16;
// this.snBCD = BCD.bcdToStr(ConverUtil.hexArrayToByteArray(sn16));
this.snBCD = String.join("", sn16);
this.channel = channel;
this.serialNumber = new String[]{"00", "00"};
}
public static void main(String[] args) {
String[] sn16 = {"22", "00", "00", "00", "00", "00", "01"};
ChargeEntity a = new ChargeEntity(sn16, null);
for (int i = 0; i < 100; i++) {
String[] serialNumber1 = a.getSerialNumber();
System.out.println(ArrayUtil.toString(serialNumber1));
}
}
public String[] getSerialNumber() {
return getNum(counter);
}
//32 01 02 00 00 00 00 SN 11 抢号 15 11 16 15 55 35 02 60
public String[] getTxnSeqNum() {
String[] res = new String[16];
System.arraycopy(sn16, 0, res, 0, sn16.length);
res[7] = "01";
String[] times = getTimes();
System.arraycopy(times, 0, res, 8, times.length);
String[] num = getNum(sequenceNumber);
System.arraycopy(num, 0, res, 14, num.length);
return res;
}
private String[] getNum(AtomicInteger num) {
int i = num.getAndIncrement();
if (i == 65534) {
num.set(1);
}
String s1 = ConverUtil.byteToHex(i);
if (s1.length() == 2) {
return new String[]{"00", s1};
} else {
return ConverUtil.convertHexStringToStringArray(s1);
}
}
private String[] getTimes() {
Date date = DateUtil.date();
String res = DateUtil.format(date, "yy-MM-dd-HH-mm-ss");
return res.split("-");
}
}
引用文章代码
小结
刚开始拿到文档的时候还以为挺简单,接收到报文就行,后来发现文档给的内容是16进制的java接收到的是10进制的
现在这块的数据格式还没解析明白。。。研究明白了回来我再更新
如内容中存在任何冒犯之处,请联系作者以便立即修正。