物联网开发对接

℡╲_俬逩灬. 提交于 2020-04-06 11:53:31

物联网开发对接

物联网设备

  • 摄像头
  • 读卡器
  • 道闸
  • 车牌识别
  • 等等等等

通讯协议

框架选型

设备SDK

  • 海康摄像头、车辆识别等设备
  • 大华的摄像头
  • 红门道闸

原理: 使用java的JNI或者JNA技术,调用c的驱动,实现设备数据的对接。 使用时注意区分部署平台

Linux下的驱动是so文件 , windwos 下的驱动是 dll文件

JNI : Java Native Interface

JNA: Java Native Access

netty-网络

  • ByteBuf对常见的字节操作做了封装

    学习ByteBuf的文章

  • 解决了TCP的粘包和拆包问题

    什么是粘包和拆包?

    消息在网络中传播是乱序的,同时由于节省带宽,它会尽可能多在一次通讯中发送更多的数据。于是会出现这种情况。

    举个例子:

    南京市长江大桥(粘包) 出现了消息二义性,表达不清

    一句话说一半 (拆包) 不知道后面的消息要表达啥?

    怎么解决呢?

    通过协议规范,约定对方消息的分隔符和长度等等。

  • 对java的网络操作做了封装

    • Bootstrap

      • Socket TCP客户端
      • ServerSocket TCP 服务端
      • DatagramSocket UDP

jSerialComm-串口

  • 针对java操作串口进行的封装
  • 平台无关性
 SerialPort gpsPort = SerialPort.getCommPort(device.getSerialName());
 gpsPort.setBaudRate(device.getBaudRate());
 gpsPort.setNumDataBits(8);
 gpsPort.setNumStopBits(1);
 boolean opened = gpsPort.openPort();
 if (opened) {
    InputStream inStream = gpsPort.getInputStream();
    // do sth stream op
 }

测试工具-wireshark

网络抓包工具,能够抓取到设备发送到我们系统数据,通过这些数据,我们可以模拟设备向我们的程序发送信息,方便我们进行代码测试,同时也能够定位程序中出现的问题。

打开程序后,首页是这个样子的

如何进行抓包呢?

首先我们要选择本地正在使用的网卡信息,在捕获下面选中一个连接。双击之后,他就可以抓到通过这个网卡的所有数据包,这样我们可以通过分析其中抓到的数据,来验证我们程序的正确性。

一般情况下,所有的设备都会有固定的IP地址,因此,我们可以利用wireshark的过滤功能,把我们要的数据找出来

编程的基础知识

java 基本数据类型所占的字节大小

1 字节 = 8 位
byte = 1 字节
short = 2 字节
int = 4 字节     
long = 8字节
// 不太常用
char = 2 字节
float = 4字节
double = 8字节
boolean = 1 字节

当然很多时候,由于物联网设备的协议有C编写,那么针对c中的数据类型要做特殊处理

unsigned byte  =》》 short
unsigned short =》》 int
unsigned int =》》 long

TIPS:什么是有符号和无符号

计算机使用二进制标识数字。

以byte这种数据类型为例

有符号的它能表示

1 1 1 1 1 1 1 1 ~ 0 1 1 1 1 1 1 1

第一位为符号位 ,这个值就是 - 2^7 ~ 2^7 - 1

无符号的它能表示

0 0 0 0 0 0 0 0 ~ 1 1 1 1 1 1 1 1

第一位就变成了数字位, 这个值是 0 ~ 2^8 -1

大端模式:Big-Endian 将高序字节存储在起始地址(高位编址)

最直观的字节序:地址低位存储值的高位,地址高位存储值的低位。

小端模式:Little-Endian 将低序字节存储在起始地址(低位编址)

最符合人的思维的字节序:地址低位存储值的低位,地址高位存储值的高位。

举个例子:

二进制中 的6 举例

1 1 0 -> 小端模式

0 1 1 -> 大端模式

TCP/IP协议

一般来说,使用网络去对接物理网设备时,都是通过TCP的方式去连接的,那么你就要知道TCP的服务端和客户端的概念。

系统作为TCP服务端:简单说就是让设备主动连接我们

系统作为TCP客户端:简单说就是我们主动连接设备

大多数情况下,我们是让设备主动上报数据,让设备主动连接我们,这样我们就不需要花费过多的时间去针对设备连接做更多的文章。

如果想要了解有关TCP更加深层次的东西请自行阅读这篇文章TCP/IP

基于Netty开发示例

有关Netty学习的文章手册

Netty服务端和客户端的实现

TCP示例

用几个简单的示例,展示一些常用的几种连接方式

Netty-服务端实现(TCP server)

public class TcpServer {


    public static void main(String[] args) throws Exception {
        //负责建立连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //负责干活的,处理数据
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .localAddress(
                        //绑定本地8888端口
                        new InetSocketAddress(8888))
                //增加一个消息处理器
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) throws Exception {
                        channel.pipeline().addLast(new ServerHandler());
                    }
                });
        //异步方式建立连接
        ChannelFuture f = b.bind().sync();
        f.channel().closeFuture().sync();
    }

    /**
     * 实现一个简单的消息处理器,当收到客户端发送来的消息后,回复一个收到!
     */
    static class ServerHandler extends SimpleChannelInboundHandler<ByteBuf> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
            ByteBuf copy = msg.copy();
            System.out.println("服务端收到消息: " + copy.toString(CharsetUtil.UTF_8));
            // 回复收到!
            ctx.writeAndFlush(Unpooled.copiedBuffer("收到!!", CharsetUtil.UTF_8));
        }


    }
}

Netty-客户端实现 (TCP client)

public class TcpClient {


    public static void main(String[] args) throws Exception {
        //建立一个客户端连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(bossGroup)
                .channel(NioSocketChannel.class)
                .remoteAddress(
                        //连接到刚刚我们创建的服务端上
                        new InetSocketAddress("127.0.0.1", 8888))
                //增加一个消息处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel channel) throws Exception {
                        channel.pipeline().addLast(new ClientHandler());
                    }
                });
        //异步方式建立连接
        ChannelFuture f = b.connect().sync();
        f.channel().closeFuture().sync();
    }

    /**
     * 简单的消息处理器
     */
    static class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
        /**
         * 当建立连接时,会回调这个函数。
         * 与服务端建立连接后,上报一条消息
         *
         * @param ctx 建立的通道
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("我是客户端,发送上线消息", CharsetUtil.UTF_8));
        }

        /**
         * 接收来自服务器返回的消息
         *
         * @param ctx 建立的通道
         * @param in  服务器返回的消息
         */
        @Override
        public void channelRead0(ChannelHandlerContext ctx,
                                 ByteBuf in) {
            System.out.println("客户端收到服务端的消息:" + in.toString(CharsetUtil.UTF_8));
        }

    }


}

协议分析

以翰岳读卡器6540TP为例,硬件厂商提供协议如下:

读卡机刷卡后主动上报卡片序列号,共10字节,包格式如下:
AA 00 05 00 BA 3B A0 F6 D2 BB
AA --起始字节
00 --读卡机地址,一般为00
05 --数据包长度 
00 -- 状态,默认为00
BA 3B A0 F6  --4个字节卡号
D2 --校验  ( 00 05 00 BA 3B A0 F6)异或 等于D2
BB --结束位

自定义消息的解码器或编码器

@Slf4j
public class ByteBufToHy6540TpRfidDataMsgDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        int byteLength = byteBuf.readableBytes();
        log.debug("读卡器[Hy6540Tp] 接收到的原始数据为: {}", byteBuf.toString());
        log.debug("读卡器[Hy6540Tp]  接收到的原始数据长度为: {}", byteLength);
        ByteBuf checkData = byteBuf.copy();
        if (checkIsLegal(checkData)) {
            Hy6540TpRfidDataMsg msg = new Hy6540TpRfidDataMsg();
            msg.setHead(byteBuf.readUnsignedByte());
            msg.setAddress(byteBuf.readUnsignedByte());
            short dataLen = byteBuf.readUnsignedByte();
            msg.setLength(dataLen);
            msg.setStatus(byteBuf.readByte());
            byte[] numBit = new byte[4];
            byteBuf.readBytes(numBit);
            long no = Long.parseLong(HexUtil.encodeHexStr(ArrayUtil.reverse(numBit)), 16);
            msg.setCardNum(StrUtil.padPre(String.valueOf(no),10,"0"));
            msg.setCheckBit(byteBuf.readUnsignedByte());
            msg.setTail(byteBuf.readUnsignedByte());
            msg.setDevice(new Hy6540TpRfidDevice((InetSocketAddress) channelHandlerContext.channel().remoteAddress()));
            list.add(msg);
        } else {
            byteBuf.skipBytes(byteBuf.readableBytes());
        }
    }

    /**
     * 校验数据合法性
     *
     * @param byteBuf 校验数据
     * @return true 合法 false 不合法
     */
    private boolean checkIsLegal(ByteBuf byteBuf) {
        ByteBuf checkData = byteBuf.copy(1, byteBuf.readableBytes() - 3);
        ByteBuf checkBit = byteBuf.copy(byteBuf.readableBytes() - 2, 1);
        byte checkNum = checkBit.readByte();
        byte[] needCheck = new byte[checkData.readableBytes()];
        checkData.readBytes(needCheck);
        byte needCheckNum = 0;
        // (b & 0xFF)的意思是转为无符号整数
        for (byte b : needCheck) {
            needCheckNum ^= b;
        }
        return checkNum == needCheckNum;
    }

}

在Netty中添加自定义处理器

  try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(
                            new InetSocketAddress(this.serverProperties.getHy6540TpPort()))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    .addLast(new FixedLengthFrameDecoder(10))
                                    .addLast(new ByteBufToHy6540TpRfidDataMsgDecoder())
                                    .addLast(hy6540TpInboundHandler);
                        }
                    });

            ChannelFuture f = b.bind().sync();
            log.info("读卡器[Hy6540Tp],服务启动成功[端口:{}]", this.serverProperties.getHy6540TpPort());
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            log.debug("读卡器[Hy6540Tp]服务故障");
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            e.printStackTrace();
        }

物联网设备越来越多、计算需求密集、数据量大怎么办?

时序数据库

InfluxDB 开源分布式时间序列数据库 。以时间序列为基础存储数据,处理带时间标签的数据,高效、实时分析大量数据。 时间序列数据主要由电力行业、化工行业等各类型实时监测、检查与分析设备所采集、产生的数据,这些工业数据的典型特点是:产生频率快(每一个监测点一秒钟内可产生多条数据)、严重依赖于采集时间(每一条数据均要求对应唯一的时间)、测点多信息量大(常规的实时监测系统均有成千上万的监测点,监测点每秒钟都产生数据,每天产生几十GB的数据量)。

边缘计算网关

边缘计算wiki 我的理解,边缘的意思就是靠近设备的端的处理能力,利用这种边缘计算网关,可以让数据在设备端就完成实时的计算和分析,从而减轻服务器的压力,方便我们关注数据分析的最终结果。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!