仰邦BX.K协议对接

news2024/11/23 10:33:55

背景

使用BX 6K控制卡控制诱导屏显示剩余车位数,由于控制卡和服务端不在一个局域网内,所以不能使用官网提供的案例,官网提供的案例为控制卡为TCP Server,服务端为TCP Client,因此需要开发此程序,服务端左右TCP Server,控制卡为TCP Client。

项目创建

在start.spring.io创建spring boot项目,应用webflux包,或者直接应用netty也可以

<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-webflux</artifactId>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter</artifactId>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-web</artifactId>
  	</dependency>
  	<dependency>
  		<groupId>com.alibaba</groupId>
  		<artifactId>fastjson</artifactId>
  		<version>1.2.83</version>
  	</dependency>
  	<dependency>
  		<groupId>org.projectlombok</groupId>
  		<artifactId>lombok</artifactId>
  		<version>1.18.16</version>
  	</dependency>

  	<dependency>
  		<groupId>cn.hutool</groupId>
  		<artifactId>hutool-all</artifactId>
  		<version>5.8.23</version>
  	</dependency>

启动TCPServer

由于仰邦的协议接口不是固定的,所以不能使用工具拆包粘包,需自行处理,虽然文档说帧结构为:
在这里插入图片描述
但是心跳包固定为:0x61 0x63 0x6B,启动链接的包为:tel+16位自定义字符串+16个字节加密字符,都不是标准格式,不太好处理,如果有大神指导如何处理请在评论区告知一声

package com.fyqj.guidingServer;

import com.fyqj.guidingServer.codec.GuidingDecoder;
import com.fyqj.guidingServer.codec.GuidingEncoder;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import reactor.core.publisher.Flux;
import reactor.netty.tcp.TcpServer;


@SpringBootApplication
public class GuidingDisplay {

  public static void main(String[] args) {
  	SpringApplication.run(GuidingDisplay.class, args);
  }
  @Bean
  CommandLineRunner commandLineRunner() {
  	return string -> {
  		createTcpServer();
  	};
  }

  private void createTcpServer() {
  	TcpServer.create()
  			.host("0.0.0.0").handle((in,out) -> {
  				in.receive()
  						.asByteArray()
  						.subscribe();
  				return Flux.never();
  			})
  			.doOnConnection(c -> c
  					.addHandler("decoder" , new GuidingDecoder())
  					.addHandler("encoder" , new GuidingEncoder())
  			)
  			.port(8306).bindNow();
  }
}

decoder,根据首帧截取11位自定义字符,将channel存入内存,后续用于交互。控制卡上报的帧没有什么具体的意义,所以本项目并没有解析控制卡上报的帧,如需解析,请自行拆包粘包

package com.fyqj.guidingServer.codec;

import com.fyqj.guidingServer.utils.ScreenUtil;
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 GuidingDecoder extends ByteToMessageDecoder{
  private Boolean firstFrame = true;

  @Override
  public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
      log.info("channelRegistered");
      ctx.fireChannelRegistered();
  }
  @Override
  public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
      log.info("channelUnregistered");
      ctx.fireChannelUnregistered();
      String code = ScreenUtil.CONTEXTS_REVERSE.get(ctx);
      ScreenUtil.CONTEXTS.remove(code);
      ScreenUtil.CONTEXTS_REVERSE.remove(ctx);
  }


  @Override
  protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
      if(firstFrame) {
          ByteBuf outByteBuf = buffer.readRetainedSlice(buffer.readableBytes());
          byte[] data = new byte[outByteBuf.readableBytes()];
          outByteBuf.readBytes(data);
          String code = new String(data);
          code = code.substring(3, 14);
          firstFrame = false;
          ScreenUtil.CONTEXTS.put(code, ctx);
          ScreenUtil.CONTEXTS_REVERSE.put(ctx, code);
          out.add(outByteBuf);
      }
      buffer.readRetainedSlice(buffer.readableBytes());
  }
}

消息体encode可以使用官网提供的例子,无需修改

package com.fyqj.guidingServer.codec;

import com.fyqj.guidingServer.protocol.BxDataPack;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.springframework.stereotype.Component;

public class GuidingEncoder extends MessageToByteEncoder<BxDataPack> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, BxDataPack cmd, ByteBuf out) throws Exception {
        cmd.pack(out);
    }
}

package com.fyqj.guidingServer.protocol;

import com.fyqj.guidingServer.utils.BxUtils;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Arrays;

/**
 *
 */
@Data
@AllArgsConstructor
public class BxDataPack {

    private static final int WRAP_A5_NUM = 8;
    private static final int WRAP_5A_NUM = 1;


    // 目标地址
    private short dstAddr = (short) 0xfffe;

    //
    // 源地址
    private short srcAddr = (short) 0x8000;

    //
    // 保留字
    private byte r0 = 0x00;
    private byte r1 = 0x00;
    private byte r2 = 0x00;

    //
    // option
    // 不发送 barcode
    private byte option = 0x00;

    private String barCode;

    //
    // crc 模式
    // 默认无校验
    private byte crcMode = 0x00;

    //
    // 显示模式
    private byte dispMode = 0x00;

    //
    // 设备类型
    private byte deviceType = (byte) 0xfe;

    //
    // 协议版本号
    private byte version = 0x02;

    //
    // 数据域长度
    private short dataLen;

    //
    // 数据
    private byte[] data;

    //
    // crc
    private short crc;

    private BxDataPack() {}

    public BxDataPack(byte[] data) {
        this.data = data;
        this.dataLen = (short) data.length;
    }

    public BxDataPack(BxCmd cmd) {
        this.data = cmd.build();
        this.dataLen = (short) data.length;
    }


    /**
     * 对数据进行转义
     * @param src
     * @return
     */
    private static byte[] wrap(byte[] src) {


        int len = 0;

        len = src.length;

        for(byte d : src) {
            if((d == (byte)0xa5) || (d == (byte)0x5a) || (d == (byte)0xa6) || (d == (byte)0x5b)) {
                len++;
            }
        }

        //
        // 加上帧头和帧尾的A5,5A
        //len += 2;
        len += WRAP_5A_NUM;
        len += WRAP_A5_NUM;


        //
        // 开始转义

        byte[] dst;
        dst = new byte[len];

        int offset = 0;

        //
        // 帧头
        for(int i=0; i<WRAP_A5_NUM; i++){
            dst[offset++] = (byte) 0xa5;
        }


        for(byte data : src) {
            if(data == (byte)0xa5) {
                dst[offset++] = (byte) 0xa6;
                dst[offset++] = 0x02;
            }
            else if(data == (byte)0xa6) {
                dst[offset++] = (byte) 0xa6;
                dst[offset++] = 0x01;
            }
            else if(data == 0x5a) {
                dst[offset++] = 0x5b;
                dst[offset++] = 0x02;
            }
            else if(data == 0x5b) {
                dst[offset++] = 0x5b;
                dst[offset++] = 0x01;
            }
            else{
                dst[offset++] = data;
            }
        }

        // 帧尾
        for(int i=0; i<WRAP_5A_NUM; i++){
            dst[offset++] = 0x5a;
        }

        //
        return dst;
    }


    /**
     * 对数据进行封装,生成字节流
     */
    public void pack(ByteBuf out) {

        BxByteArray bytes = new BxByteArray();

        //
        // 目标地址
        bytes.add(dstAddr, BxByteArray.Endian.LITTLE);

        //
        // 源地址
        bytes.add(srcAddr, BxByteArray.Endian.LITTLE);

        //
        // 保留字
        bytes.add(r0);
        bytes.add(r1);
        bytes.add(r2);

        //
        // option
        bytes.add(option);

        //
        // crc mode
        bytes.add(crcMode);

        //
        bytes.add(dispMode);

        //
        bytes.add(deviceType);

        //
        bytes.add(version);

        //
        bytes.add(dataLen);

        //
        // 数据域
        bytes.add(data);

        //
        // add crc
        crc = 0x0;
        bytes.add(crc);

        //
        byte[] origin = bytes.build();
        int originLen = origin.length;
        crc = BxUtils.CRC16(origin, 0, originLen-2);

        origin[originLen-2] = (byte)(crc & 0xff);
        origin[originLen-1] = (byte)(crc>>8);

        //
        // 进行转义
        byte[] result = wrap(origin);
        out.writeBytes(result);

    }

    /**
     * 将BYTE数组解析成 bx.k.BxDataPack
     * @param src
     * @return
     */
    public static BxDataPack parse(byte[] src, int length) {

        //
        // 反转义
        byte[] dst = unwrap(src, length);
        if(dst == null) {
            return null;
        }
        else {

            //
            // check crc
            //if(bx.k.BxUtils.CRC16())
            short crcCalculated = BxUtils.CRC16(dst, 0, dst.length-2);
            short crcGot = BxUtils.bytesToShort(dst, dst.length-2, BxUtils.ENDIAN.LITTLE);

            if(crcCalculated != crcGot)
                return null;


            BxDataPack pack = new BxDataPack();

            int offset = 0;

            //
            // 目标地址
            pack.dstAddr = BxUtils.bytesToShort(dst, offset, BxUtils.ENDIAN.LITTLE);
            offset += 2;

            //
            // 源地址
            pack.srcAddr = BxUtils.bytesToShort(dst, offset, BxUtils.ENDIAN.LITTLE);
            offset += 2;

            //
            // 保留字 r0, r1, r2
            pack.r0 = dst[offset++];
            pack.r1 = dst[offset++];
            pack.r2 = dst[offset++];

            //
            // option
            pack.option = dst[offset++];
            if(pack.option == 0X01) {
                byte[] code = Arrays.copyOfRange(dst, offset, offset+16);
                offset = offset+16;
                pack.barCode= new String(code);
            }
            //
            // 校验模式
            pack.crcMode = dst[offset++];

            //
            // 显示模式
            pack.dispMode = dst[offset++];

            //
            // 设备类型
            pack.deviceType = dst[offset++];

            //
            // 协议版本
            pack.version = dst[offset++];

            //
            // 数据域长度
            pack.dataLen = BxUtils.bytesToShort(dst, offset, BxUtils.ENDIAN.LITTLE);
            offset += 2;

            //
            // 数据
            //pack.data = new byte[pack.dataLen];
            pack.data = Arrays.copyOfRange(dst, offset, offset+pack.dataLen);
            offset += pack.dataLen;

            //
            // crc
            pack.crc = BxUtils.bytesToShort(dst, offset, BxUtils.ENDIAN.LITTLE);

            //
            return pack;
        }

    }


    /**
     * 去除数据转义
     * @param src
     * @param length
     * @return
     */
    private static byte[] unwrap(byte[] src, int length) {

        int len = 0;

        if(length == 0)
            len = 0;

        if(src[0] != (byte)0xa5)
            len = 0;

        if(src[length-1] != (byte)0x5a)
            len = 0;

        len = length;

        for(byte d : src) {
            if((d == (byte)0xa5) || (d == (byte)0x5a) || (d == (byte)0xa6) || (d == (byte)0x5b)) {
                len--;
            }
        }

        byte[] dst;

        //
        // 如果计算的帧长度为0,说明数据不正确
        if(len == 0)
            return null;

        dst = new byte[len];

        int offset = 0;
        for(int i=0; i<length; ) {

            if((src[i] == (byte)0xa5) || (src[i] == 0x5a)) {
                i++;
            }  else if(src[i] == (byte)0xa6) {
                if(src[i+1] == 0x01) {
                    dst[offset++] = (byte)0xa6;
                    i = i+2;
                }
                else if(src[i+1] == 0x02) {
                    dst[offset++] = (byte)0xa5;
                    i = i+2;
                }
                else
                    return null;
            } else if(src[i] == 0x5b) {
                if(src[i+1] == 0x01) {
                    dst[offset++] = (byte)0x5b;
                    i = i+2;
                }
                else if(src[i+1] == 0x02) {
                    dst[offset++] = (byte)0x5a;
                    i = i+2;
                }
                else
                    return null;
            }

            else {
                dst[offset++] = src[i++];
            }
        }


        return dst;
    }
}

源码地址:https://gitee.com/pengchao0903/city-guiding-display-server.git
私有项目地址,如有需要,请在评论区@我

有问题还请指出,用于及时修改文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1885610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker mysql cpu100% cpu打满排查 mysql cpu爆了 mysql cpu 100%问题排查

1. docker 启动了一个mysql 实例&#xff0c;近期忽然发现cpu100% 如下图所示 命令&#xff1a; top 2.进入容器内排查&#xff1a; docker exec mysql&#xff08;此处可以是docker ps -a 查找出来的image_id&#xff09; -it /bin/bash cd /var/log cat mysqld.log 容器内m…

Android LayoutInflater 深度解析

在 Android 开发中&#xff0c;LayoutInflater 是一个非常重要的工具。它允许我们从 XML 布局文件中动态地创建 View 对象&#xff0c;从而使得 UI 的创建和管理更加灵活。本文将深入解析 android.view.LayoutInflater&#xff0c;包括它的基本用法、常见问题以及高级用法。 什…

Java高级重点知识点-19-Lambda

文章目录 Lambda表达式函数式编程思想Lambda表达式写法代码讲解 Lambda表达式 函数式编程思想 强调做什么&#xff0c;而不是以什么形式做。 以函数式接口Runnable为例讲解&#xff1a; public class LambdaDemo {public static void main(String[] args) {Runnable runnab…

学习无人机飞行技术,有哪些就业方向?

随着无人机技术的不断进步和应用领域的拓展&#xff0c;研发创新人才的需求也将不断增加&#xff0c;那就业前景还是很广阔的。学习无人机飞行技术后&#xff0c;有以下多个就业方向可供选择&#xff1a; 1. 无人机操作员&#xff1a; - 负责操控和监控无人机飞行&#xff0c;…

第57期|GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

“探索价值增长消费:让每一笔购物都成为增值之旅“

亲爱的顾客们&#xff0c;你们好&#xff01;今天&#xff0c;我将带你们探索一种革命性的消费哲学——价值增长消费&#xff0c;让每一次购物都成为一次增值之旅&#xff01; 在传统消费观念里&#xff0c;我们付出金钱换取商品或服务&#xff0c;随后这些便成为过去。但如今…

38. 扫描系统设计

导论: 扫描系统根据反射旋转类型分为平面振镜扫描和转股扫描&#xff0c;根据光路途径分为一维、二维和三维扫描&#xff0c;根据振镜和扫描镜头的位置又可分为镜前扫描和镜后扫描。 设计流程&#xff1a; zemax设计与优化&#xff1a; 系统建模 在孔径类型中选择入瞳直径…

fastapi swagger js css 国内访问慢问题解决

fastapi swagger js css 国内访问慢问题解决 直接修改fastapi包中静态资源地址为如下地址 swagger_js_url: str "https://cdn.bootcdn.net/ajax/libs/swagger-ui/3.9.3/swagger-ui-bundle.js", swagger_css_url: str "https://cdn.bootcdn.net/ajax/libs/sw…

发电机保护屏的作用及其重要性

发电机保护屏的作用及其重要性 发电机保护屏是电力系统中的重要组成部分&#xff0c;它负责监测和控制发电机的运行状态&#xff0c;确保发电机在正常运行和故障情况下都能得到及时、准确的保护。负责监测和控制发电机运行状态&#xff0c;确保及时准确的保护。它由显示屏、控制…

python学习-list

List(列表的定义语法) [元素1, 元素2, 元素3, ......] 什么是元素&#xff1f; 数据容器内的每一份数据&#xff0c;都称之为元素 元素的类型有限制吗&#xff1f; 元素的数据类型没有任何限制&#xff0c;甚至元素也可以是列表&#xff0c;这样就定义了嵌套列表 但是打印…

RT-Thread Studio与CubeMX联合编程之rtthread stm32h743的使用(十一)spi设备SFUD驱动的使用

我们要在rtthread studio 开发环境中建立stm32h743xih6芯片的工程。我们使用一块stm32h743及fpga的核心板完成相关实验&#xff0c;核心板如图&#xff1a; 1.建立新工程&#xff0c;选择相应的芯片型号及debug引脚及调试器 2.编译下载&#xff0c;可以看到串口打印正常 3.…

数据结构_1.0

一、数据结构概述 1.1 概念 在计算机科学中&#xff0c;数据结构是一种数据组织、管理和存储的格式 。它是相互之间存在一种或多种特定关系的数据元素的集合。通常情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技…

【】AI八股-神经网络相关

Epoch Epoch 是指完成一次完整的数据集训练的过程。比如&#xff0c;有一个数据集有1000个样本&#xff0c;当网络用这些样本训练一次后&#xff0c;这就是一个epoch。 Iteration Iteration 是指在一个epoch中&#xff0c;使用一个batch进行训练的次数。如果你的数据集有100…

2024年【四川省安全员A证】试题及解析及四川省安全员A证模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 四川省安全员A证试题及解析根据新四川省安全员A证考试大纲要求&#xff0c;安全生产模拟考试一点通将四川省安全员A证模拟考试试题进行汇编&#xff0c;组成一套四川省安全员A证全真模拟考试试题&#xff0c;学员可通…

深度解密Spark性能优化之道

课程介绍 课程通过实战案例解析和性能调优技巧的讲解&#xff0c;帮助学员提升大数据处理系统的性能和效率。课程内容涵盖了Spark性能调优的各个方面&#xff0c;包括内存管理、并行度设置、数据倾斜处理、Shuffle调优、资源配置等关键技术和策略。学员将通过实际案例的演示和…

文件操作详解(C语言)

1.为什么要用到文件&#xff1f;怎样数据才能持久化&#xff1f; 保存在内存中的数不安全&#xff08;一次断电&#xff0c;忘记保存&#xff0c;不用了还给系统&#xff09; 持久化&#xff1a;保存在硬盘上&#xff08;放在文件中&#xff09; 什么是文件&#xff1f;文件…

鱼塘专用激光冲击波声压网驱鸟器

随着生态环境的日益改善&#xff0c;越来越多的鸟类频繁光临鱼塘&#xff0c;捕食鱼苗或成鱼&#xff0c;给养殖户造成巨大的经济损失。 如何有效地让鸟远离你的鱼塘呢&#xff1f; 鱼塘专用激光冲击波声压网驱鸟器&#xff0c;结合了多种驱鸟功能&#xff0c;包括激光驱鸟技术…

分页导航DOM更新实践:JavaScript与jQuery的结合使用

分页导航DOM更新实践&#xff1a;JavaScript与jQuery的结合使用 在Web开发中&#xff0c;分页导航是展示大量数据时不可或缺的UI组件。合理的分页不仅可以提高应用性能&#xff0c;还能优化用户体验。本博客将通过一个实际的DOM结构和模拟数据&#xff0c;讲解如何使用JavaScr…

CentOS修复OpenSSH漏洞升级到openssh 9.7 RPM更新包

在做政府和学校单位网站时&#xff0c;经常需要服务器扫描检测&#xff0c;经常被OpenSSH Server远程代码执行漏洞&#xff08;CVE-2024-6387&#xff09;安全风险通告&#xff0c;出了报告需要升级OpenSSH。 使用yum update openssh是无法更新到最新的&#xff0c;因为系统里的…

基于蜉蝣优化的聚类算法(MATLAB)

优化问题广泛存在于人们的日常生活和工程领域&#xff0c;其解决如何寻找使目标值达到最优的可行解的问题。伴随着科技发展&#xff0c;优化问题在生产调度、神经网络训练、图像处理、能源系统等领域起到举足轻重的作用&#xff0c;有助于提高系统效率。优化问题依据不同标准可…