物联网开发对接

编程

框架选型

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

以上是 物联网开发对接 的全部内容, 来源链接: utcz.com/z/515131.html

回到顶部