外行小白的modbus惊心之旅
这是一个悲伤的故事,笔者自真正接触编程后,学习的一直是编写javaweb项目,然而万万没想到的是,有生之年竟然会接触到串口通信的需求。为了能够帮助到一些和我面临着相同困境的小伙伴们,特此将自己这一段时间来的心得体会记录下来,希望能给大家带来一点点的助益,本文新手向,大佬请绕行~
(注意:由于笔者对于这方面也是外行,所以文章中可能有些术语不当、思路不佳之处,大家见谅!)
我接到的需求大致如下:
现有温湿度监控设备N台,需要编写一个软件,部署在用户的电脑上,通过com口对温湿度设备进行数据采集,并将采集到的数据解析后传送给平台。
网上不难找到关于如何采集数据的代码,所以在本文中就不再赘述了,那么接下来我们就聊一下,采集到数据之后,如何解析成用户想要的数据。假设你现在得到了设备返回的一条完整数据,这条数据是以字节数组的形式存储的,示例如下:
byte数组:[01 09 00 01 01 05 49 DB]
数组下标:[0 1 2 3 4 5 6 7]
下标0:设备地址号 下标1:命令号(假设09为采集数据命令) 下标2和下标3:存储器号 下标4和下标5:温度数据 下标6和下标7:校验码
那么问题来了,byte的取值范围为-128~127,难道室内温度还能达到-128或者127不成?为什么存储温度数据要用两字节呢?
因为一个字节占8位,而设备返回的温度数值是一个16位的整数,所以需要用两个字节来存储。比如现在室内温度为25.5摄氏度,那么设备将会返回给我255。由于我使用的是c#语言,在c#中byte是无符号的,取值范围为0~255,那么我最终采集到的数据以十进制的形式打印出来就是 0 255。那么,为什么第一个字节是0,第二个字节是255呢?
255的二进制形式为11111111,如果保存为16位,则应该为0000 0000 1111 1111。由此可见,高八位为8个0,低八位为8个1。而一个字节为八位,所以,高八位存在了第一个字节,它的十进制也就是0,而低八位存在了第二个字节,它的十进制为255。由于我使用的是c#语言,所以,最终看到的结果为 0 255。起初的时候,我没有意识到最本质的问题,计算机存储的实际上是二进制数,所以导致了我以一种奇怪的思路来对数据进行解析,而当踩坑之后,经过不断的查阅资料和询问厂家之后,终于总结出了三个关键字:高八位、低八位、十六位整数。
进而我想到了位运算,将高八位先转为16位进行存储,然后左移八位,低位补零。那么示例中的高八位应该变成了这个样子:0000 0000 0000 0000。
然后将高八位与低八位相加:
0000 0000 0000 0000
1111 1111
最终得到的结果为:0000 0000 1111 1111,转为十进制也就是255。
//代码供参考
((Convert.ToInt16(b[5])) << 8) + b[6]
接下来,再来聊一下我在 这个需求之中遇到的其他坑... ...
粘包和分包问题:
最开始在网上查询资料的时候,就读到了这两个词,当时还抱着一点侥幸心理,暗自思量着这东西说不定是什么高端操作吧?我这需求里应该用不到吧?
结果后来在数据采集的时候,问题就出现了,时而可以采集到数据,时而采集不到数据。我当时的确是百思不得其解,既然能接收到数据,那我的代码应该没什么问题吧?但为什么有时候又接收不到数据呢?
后来下载一个串口监测工具才发现了问题所在,比如一条完整数据应该为8字节,但是,我接到的数据中,有时是一次性接收到8字节的数据,有时则是先接收了三字节,然后接收了五字节。
另外一种情况就是,有时还会一次性接收到十几字节的数据,当时一度怀疑是某些转接设备的问题,经过仔细观察发现,原来在这条数据中,有一部分是第一条数据,另一部分是第二条数据。
解决方案:
采用生产者和消费者的方式来解决,生产者采集数据存入缓存队列,消费者每当从队列中取出八个字节,进行一次校验,校验成功后,再对数据进行解析和转发。