java基于netty实现充电桩云快充1.5协议Demo

背景

最近接手了一个充电桩项目,为了提高效率打算找一些开源项目来参考或改造,但遗憾的是没有找到与云快充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("-");
    }


}

引用文章代码

云快充协议中的校验码用java实现

小结

刚开始拿到文档的时候还以为挺简单,接收到报文就行,后来发现文档给的内容是16进制的java接收到的是10进制的
在这里插入图片描述
现在这块的数据格式还没解析明白。。。研究明白了回来我再更新
如内容中存在任何冒犯之处,请联系作者以便立即修正。

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值