北斗传输采集数据的自定义通信协议
最近在做一个项目的数据采集,需采用北斗传输采集到的数据。
自然的,笔者首选了【docker版jxTMS使用指南:自定义协议包】作为数据传输的通信协议。
这个自定义协议包是笔者针对海上恶劣环境下采集数据回传所设计,在我司所有的数据采集以及内部通信上得到了全面的应用。
经过长时间的使用,该通信协议表现出如下的优势:
1、能有效抗击恶劣环境下的数据传输
我们回传数据采用的是mqtt协议,mqtt协议基于TCP传输数据,所以笔者在设计时特意选择了和TCP的反码求和算法不同的CRC算法,同时也更适合嵌入式资源使用的异或算法。
两种不同的CRC算法,证明了TCP确实不是那么的可靠:我们经常能收到包格式错误的异常报告。即错误数据通过了TCP的校验,但为我们协议所选用的CRC校验算法检出。
2、更灵活的数据传输
笔者在编码数据时,使用了key:Value【键值对】编组数据、对数据使用TLV【Type-Length-Value】进行编码,这种编码方案有两个好处:
- 数据自解析,因传输的字节流中自带了数据名和Type(数据类型)、Length(数据长度),所以通信协议无需规定所传数据的定义,这就不需要在传输新数据前先修订通信协议,所以这个通信协议不管要传什么样的新数据,都不需要修订而是直接进行传输即可
- 强约束,异或的CRC算法毕竟太简单了,传输错误的检出能力很小,这时数据自带的Type(数据类型)、Length(数据长度)等信息就产生了额外的约束能力:键值对封包、数据类型未识别、数据类型和长度不匹配等提供了额外的传输错误检出能力
因此,在项目之初,笔者自然而然的就沿用了此协议来传输数据。但在上星测试后发现了严重的问题:带宽利用率比较低。
我们在海上传输数据采用的是4G卡,虽然通信环境恶劣,但带宽却是充足的。所以笔者在设计通信协议时,主要考虑的是抗击恶劣环境以及数据自解析。
但在北斗这种带宽非常有限的环境下,数据自解析就带来了严重的信息冗余问题,导致带宽利用率非常低,加上协议内置开销,平均下来要四十个字节左右【北斗要求的ASCII化后的长度】才能传递一个数据。
针对这个问题,笔者只能重新研发针对北斗的通信协议。
这个协议主要包括两个部分:包格式、数据编码计划。
包格式
整个数据包包括有包头、数据区两部分:
包头(8个字节)
1、冠字,4个字节,我们自用就固定为:DAIS
冠字主要是用于在各种接收环境下,辅助用于识别出包的开始位置。
在我们的实践中,有字节流的批提交【TCP传输】、有逐字节提交【串口输入】、有不合规的数据提交【破包、黏包】、有包损坏【部分数据缺失】、有位翻转、吞字、错字、衍字等等情况。
所以,接收一个包最可靠的方法就是缓存后逐字节扫描,识别到冠字就准备识别,然后只有符合了一个包的全部识别特征,如包长无误、CRC正确、上述的TLV、键值对等内置约束全部正确。才能识别为正确的包提交到应用层。否则就需要移动接收窗口,开始下一轮扫描。
所以,一个包必须定义一个冠字来作为预备识别的启动点。
2、包类型,1个字节,之前笔者定义了四种包类型,本次新增了一个包类型专用于北斗通信格式:0x81
注1:为了和【docker版jxTMS使用指南:自定义协议包】兼容,前五个字节采取了相同的定义
3、CRC,1个字节
依据之前的成功经验:每层的CRC校验码必须不同。因北斗采用的就是异或算法,所以笔者采用了CRC8-ITU算法,其多项式为:
X^8+X^2+X^1+1
4、数据区长,2个字节
数据区
因本项目有两个比较特殊的点:
- 需要有补发功能
- 现场有可能部署多台同类型设备
这两点都会导致一个比较严重的问题:同名数据重复。
因此,数据区是需要分块的。重名的重发数据、同类型0号设备以外设备的数据,都必须放到不同的块中来避免数据重名问题。
所以,数据区是由一块块的数据块顺序排列组成。每个数据块包含两个字节的块头:
- 0xFF,块开始标记
- 本块的编码计划识别号
同时必须满足所有的编码约束,以及最终的数据长约束。
块内数据依据本块的编码计划对所提交的数据依次进行编码后组合而成。
编码计划
笔者针对不同的场景,编列了两种编码计划:
- 针对离散数据传输的OV编码计划
- 针对成组数据传输的GV编码计划
可以看到由于失去了之前协议中数据编码方案【键值对+TLV】的数据自解析特性,这两种编码计划都必须预置数据元信息。
预置的数据元信息包括:
- 数据的属类信息,如来自气象设备、安防设备等等
- 数据标识名,即数据作为变量使用时的变量名【笔者一般定义为数据库中的数据字段名,这样方便直接插入到数据库中】
- 数据语义名,即显示给用户阅读的、中文的数据语义简称,如风速、风向等
- 数据编号,数据的编号,不同的编码计划有各自的数据编号方案
- 数据类型,就是整数、浮点数等
- 数据放大比例因子,类似modbus中常用的,放大10倍、100倍后,用一个U16来表示一个正浮点数,可以节省一半的数据带宽
- 数据单位
预置数据元信息的优势就是避免了在传输数据时为了提供足够的数据元信息而导致的带宽效率太低的问题,即在传输只需要提供:
- 编码计划的识别号【一块数据集中提供一次即可】
- 数据编号
- 数值
在预置数据元信息的帮助下就足以完成数据的解析,这就极大的压缩了所需传输的数据总量。但带来的问题就是必须提前预定义这些数据元信息。
但是,当需要传输新数据时,就必须修订通信协议、并升级相应的通信模块、然后还要进行充分的测试。而且老设备还无法处理新数据,所以必须对汇聚数据的骨干节点停机升级后才能维持通信。
所以,相比之前的老协议,新协议的代价可谓惨烈,为了提高北斗的数据传输能力,需要付出极为巨大的代价。
OV编码计划
OV编码计划是由Order-Value编码而成的数据。
即,对所有需要传输的数据,都要先定义一个数据序号。
OV编码计划主要针对类似于安防中各防区报警这样存在大量的、散发的数据采集场景。
对于OV编码计划,数据元信息中的数据编号就是序号。根据业务需要传输的数据项的分布和数量等特点,可以规划相应的序号编号规划。
如,最常用的200个数据项,可编码为1-223,用一个字节传输,224-254为扩展序号,可再携带1、2个字节进行序号的扩展。
GV编码计划
GV编码计划即组数据,顾名思义,就是成组发送的数据的编码计划。如气象仪,采集到的气象六要素都是成组传输的。
GV编码计划是将这些成组的数据划分为一个一个的组,组内数据用bit序号来压缩数据序号所占空间。即,GV编码计划会把一组数据编码为如下的格式:
- 组序号:1个字节,即本组数据所在组所给的编号
- 组内数据是否存在标记:2个字节,16个数据编为一组,每个数据分配一个bit序号,如果本次传输某数据,则在标记中置位该数据对应的bit位
- 值列:根据标记,按bit序由低到高依次排列各数据的取值
对于GV编码计划,数据元信息中的数据编号就是:(组序号, bit序号)。
相比OV编码计划,大多数的数据采集场景下,GV编码计划的编码效率更高。OV编码计划只在非常零散的多数据偶发应用场景下有一定的优势。
注1:OV编码计划和GV编码计划中所有涉及到的多字节数值,其字节序都是小端排序
注2:编码计划的编号是放到各块的块头中的,也就意味着一个协议包,可以同时传输离散数据和成组的数据,只需要将其合理的分散到不同的数据块中即可
结语
针对北斗的通信协议上线后,数据传输量得到极大的提升:
12个数据84字节
9个数据67字节
17个数据115字节
即一个数据只需要7–8个字节即可传输,相比老协议,带宽节省了80%左右!本项目所有的实时数据基本可以在一个北斗消息中发送。
但是,我们还是需要再次强调本协议传输新数据时的巨大代价:
- 修订通信协议,以增补预定义的数据元信息,由于需要避免数据编号重复,所以修订工作需要反反复复的排查、核对
- 通信双方必须对齐数据元信息【即需要对齐通信协议的版本】
- 如果业务需要覆盖的数据项比较多,则预置的数据元信息开销较大
- 必须根据通信协议重新开发通信模块以添加新增的数据元信息
- 必须对升级后的通信模块进行全面的测试
- 必须对通信设备停机升级,骨干节点损失巨大