物联网开发对接
物联网设备
- 摄像头
- 读卡器
- 道闸
- 车牌识别
- 等等等等
通讯协议
框架选型
设备SDK
- 海康摄像头、车辆识别等设备
- 大华的摄像头
- 红门道闸
原理: 使用java的JNI或者JNA技术,调用c的驱动,实现设备数据的对接。 使用时注意区分部署平台
Linux下的驱动是so文件 , windwos 下的驱动是 dll文件
JNI : Java Native Interface
JNA: Java Native Access
netty-网络
-
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 我的理解,边缘的意思就是靠近设备的端的处理能力,利用这种边缘计算网关,可以让数据在设备端就完成实时的计算和分析,从而减轻服务器的压力,方便我们关注数据分析的最终结果。
来源:oschina
链接:https://my.oschina.net/u/3226414/blog/3220810