一.TCP客户端驱动说明
该驱动每个模型可以配置一个服务器地址或为为每个设备单独配置一个服务器地址, 同一模型内服务器地址相同的设备将会使用同一个 TCP 连接(如果模型内所有设备都未配置服务器地址, 则该模型内的所有设备共用同一个连接).
驱动启动后, 每个模型都会主动与服务端建立连接, 然后接收服务端的数据.
1.当模型没有添加任何设备时, 该模型不会与服务器建立连接.
2.即使多个模型连接同一服务器, 也会为每个模型会创建一个独立的连接.
二.驱动脚本执行流程说明
以下处理流程以模型为单位, 多个模型之间独立执行, 互不影响.
- 当与服务器成功建立连接后, 会执行 连接处理脚本, 此时 state 参数的值为 true.
- 当接收到服务端发送的数据时, 会读取所有的发送数据并存放到 Buffer 中, 然后调用 数据包拆分脚本.
- 如果从已接收到的数据中拆分出完整的数据包,则将完整的数据包封装为 Buffer 交由 数据处理脚本.
- 如果未从已接收到的数据中拆分出完整的数据包, 则根据 数据包拆分脚本 返回的数据决定是等待接收更多的数据或是丢弃部分数据.
- 当连接断开时, 会执行 连接处理脚本, 此时 state 参数的值为 false.
三.客户端对象
客户端对象为当前 TCP 连接对象. 该对象除了提供与服务器通讯的功能外, 并且供一些常用的功能用于简化使用,提高效率.
在 数据包拆分脚本, 数据处理脚本, 指令处理脚本 和 连接处理脚本 函数的参数中提供了 client 对象参数, 在脚本中可以通过使用 client 中提供的函数完成数据的发送与采集.
该对象提供了以下函数:
- 与服务器交互:send(data) 向服务端发送数据
- 指令相关:reportCommand(serialNo,table,deviceId,state,result) 向平台上报指令执行结果
- 媒体库(未实现):
-
- getMediaFile(path) 请求媒体库文件
-
- getMediaFileByURL(url) 请求媒体库文件
-
- uploadMediaFile(filename,catalog,action,data) 上传文件到媒体库
-
- deleteMediaFile(path) 删除媒体库文件
- 工作表:
-
- saveWorkTableRow(tableId, rowData) 向工作表中写入数据
-
- updateWorkTableRow(tableId,query,rowData) 更新工作表数据(未实现)
-
- updateWorkTableRowById(tableId,rowId,rowData) 根据记录标识更新工作表数据
- 数据存储:
-
- getContext(contextId) 获取数据上下文
-
- removeContext(contextId) 删除数据上下文
-
- getContextIds() 获取全部上下文标识
-
- getDeviceContext(deviceId) 获取设备数据上下文
-
- removeDeviceContext(deviceId) 删除设备上下文
-
- getDeviceContextIds() 获取全部设备上下文标识**
函数案例:
①向服务端发送数据
用于向服务端发送数据. 例如: 在接收到数据时向服务端发送 ack 信息或发送心跳数据.
返回值
string 或 undefined. 如果参数不正确(字节数组为空或空数组)或发送失败则返回 string 内容为错误说明, 如果发送成功则返回 undefined.
②向平台上报指令执行结果
上报指令执行结果. 有些场景指令的执行结果反馈是异步的, 在指令发送后, 过一段时间才会收到响应报文, 此时可以在接收到响应报文时通过 client.reportCommand(...) 方法上报指令执行结果. 通过该方法上报的结果信息可以在平台中 指令状态管理 页面中查看.
1.serialNo 为平台在发送指令时生成, 会传入到指令处理脚本中, 当指令执行结果为异步反馈时, 可以将 serialNo 保存到设备上下文中,以便在后续 reportCommand 使用.
2.参数中的 table 来自于 指令处理函数 的参数,当需要通过 reportCommand 上报自定义的命令执行结果信息时,需要在指令处理函数中保存相关参数信息以便后续使用.
返回值
string 或 undefined. 如果返回 undefined 则表示发送成功, 否则返回数据为错误原因.
示例
③请求媒体库文件-路径
请求媒体库文件
参数说明
返回值
示例
④请求媒体库文件-URL
请求媒体库文件
参数说明
url 中 default 为项目ID, test 为目录, hello.txt 为文件名.
返回值
示例
⑤上传文件到媒体库
上传文件到媒体库
参数说明
返回值
示例
⑥删除媒体库文件
删除媒体库文件
参数说明
返回值
示例
⑦向工作表中写入数据
向工作表写入一条数据
参数说明
rowData 对象中必须包含 id 字段, 并且该字段做为数据的唯一标识, 必须唯一.
返回值
示例
⑧更新工作表数据
更新工作表中的数据
参数说明
返回值
示例
⑨根据记录标识更新工作表数据
根据记录标识更新记录
参数说明
返回值
示例
⑩获取数据上下文
用于获取上下文对象, 可以在上下文中存储数据. 详细信息
第一次调用的时候才会创建上下文
参数说明
返回值
Object. 数据上下文对象
示例
⑪删除数据上下文
删除上下文对象
参数说明
返回值
无
示例
⑫获取全部上下文标识
获取全部上下文标识(不包含设备上下文)
参数说明
无
返回值
String[]
示例
⑬获取设备数据上下文
用于获取设备上下文对象, 使用设备标识作为上下文标识, 可以在上下文中存储数据. 详细信息
与 getContext 不同的是, 当设备被删除后, 重启驱动时会自动清理被删除设备的上下文对象.
第一次调用的时候才会创建上下文
参数说明
返回值
Object. 数据上下文对象
示例
⑭删除设备上下文
删除设备上下文对象
参数说明
返回值
无
示例
⑮获取全部设备上下文标识
获取全部设备上下文标识(只包含设备上下文)
参数说明
无
返回值
String[]
示例
四.数据上下文
上下文对象, 用来存储数据, 上下文中的数据可以在不同的脚本中共享. 例如: 可以在 指令处理脚本中 写入数据, 然后从 数据处理脚本 中读取数据. 不同上下文彼此独立, 互不影响.
CAUTION
可以根据需求创建多个上下文对象, 但是上下文对象以及上下文中的数据需要及时清理, 否则会造成 OOM 问题, 导致驱动程序崩溃.
①向上下文中保存数据
用于向上下文中存储数据. 如果 key 已存在则会覆盖已有数据.
参数说明
返回值
无
示例
②判断上下文中是否包含指定数据
判断上下文中是否存在指定的 key
参数说明
返回值
bool. true 表示 key 存在, false 表示 key 不存在
示例
③获取数据
从上下文中获取指定的 key 对应的数据. 如果 key 不存在则返回 undefined
参数说明
返回值
any 或 undefined. 返回 put 时写入的数据.
示例
④获取并删除数据
从上下文中获取指定的 key 对应的数据并且在返回后 删除 该 key. 如果 key 不存在则返回 undefined.
TIP
该函数返回后, 再使用 get 或 getAndRemove 均返回 undefined.
参数说明
返回值
any 或 undefined. 返回 put 时写入的数据.
示例
⑤删除数据
从上下文中删除指定的 key, 如果 key 不存在则不执行任何操作.
参数说明
返回值
无
示例
五.内置对象
crc 循环冗余校验
驱动中内置了 crc 对象, 可以在脚本中直接使用 crc 对象中的校验函数.
- checksum16(Buffer,Poly) CRC-16循环冗余校验
- checksum32(Buffer,Poly) CRC-32循环冗余校验
- checksum64(Buffer,Poly) CRC-64循环冗余校验
- checksumModbus(Buffer) CRC-Modbus循环冗余校验
CRC-16循环冗余校验
使用 checksum16(Buffer, Poly) 实现 16位 的循环冗余校验
参数说明
返回值
uint16. 16位无符号整型
示例
CRC-32循环冗余校验
使用 checksum32(Buffer, Poly) 实现 32位 的循环冗余校验
参数说明
返回值
uint32. 32位无符号整型
示例
CRC-64循环冗余校验
使用 checksum64(Buffer, Poly) 实现 64位 的循环校验码
参数说明
返回值
uint64. 64位无符号整型
CRC-Modbus循环冗余校验
使用 checksumModbus(Buffer) 实现 modbus 的循环校验码.
modbus 校验与 crc16 相似, 校验码类型为 uint16. 另外, checksumModbus 不需要 poly 多项式参数.
参数说明
返回值
uint16. 16位无符号整型
六.内置函数
数据包拆分函数
- createFixedLengthSplitFn(Length,ByteOrder) 固定长度头
- createDelimiterSplitFn(Delimiter) 固定分隔符
- createStartEndDelimiterSplitFn(StartChars,EndChars) 固定开始和结束符
TIP
内置数据包拆分函数仅能用于 数据拆包脚本 中
固定长度头
该函数会取前 N 个字节作为长度信息, 然后根据长度信息读取主体内容.
在数据包拆分脚本中直接使用内置的拆分函数, 如下所示:
固定分隔符
该函数会使用固定分隔符拆分数据包. 使用方式如下所示:
CAUTION
数据包内容中不能包含分隔符
固定开始和结束符
该函数会读取指定开始符和结束符之间的数据做为一个完整的数据包. 使用方式如下所示:
INFO
开始符 和 结束符 可以包含多个字符.
CAUTION
如果一个数据包中存在多个 开始符 或 结束符 时无法使用该内置函数.
例如: 使用 @ 和 # 作为开始符和结束符时, 数据包主体部分不能包含 @ 和 #, 否则拆包结果不正确.
七.脚本说明
脚本语言: JavasScript ECMAScript 5.1
驱动使用时要求提供 数据包拆分脚本, 数据处理脚本, 连接处理脚本 和 指令处理脚本 脚本函数来处理接收和发送数据过程中的协议和数据格式问题.
在脚本的上下文中内置了 Buffer 包, 可用于处理接收或发送二进制数据.
除此之外, 还内置了 lodash, crypto-js, moment, xml-js 和 formulajs(Excel函数) 包.
TIP
所有的脚本中的函数名必须为 handler, 参数列表参考各脚本说明.
数据包拆分脚本
该脚本用于处理接收数据过程中的半包和粘包问题, 该函数需要根据数据格式判断接收到的字节数组中是否包含完整的数据包, 如果包含了完整数据包则返回完据包的起始位置和长度. 或者当数据包有错误时丢掉该数据包或有问题部分的数据.
如果字节数组中不包含完整的数据包, 直接返回 undefined 表示需要从客户端接收更多的数据.
函数定义如下:
TIP
驱动内置了常用的数据包拆分函数. 详情
参数说明
返回值说明
- 当字节数组中包含完整数据包时, 返回包含 package 字段的对象.
- 如果字节数组中存在错误, 则返回包含 drop 字段的对象, 丢弃掉错误的数据.
- 如果字节数组中即不包含完整的数据包也不存在错误, 直接返回 undefined, 表示需要从客户端接收更多的数据.
正常接收数据
当数据包拆分脚本返回以下内容时, 表示已接收到的数据中包含了完整的数据包. 该数据包在所有数据中的起始位置为 0, 长度为 128, 下一个数据包的起始位置为 129
例如, 使用 \r\n 作为数据包间的分隔符, 以解析该数据时 {"a":1,"b":2}\r\n{"a":3,"b":4}\r\n, 拆分结果为 {"package":{"start":0,"length":13,"next":15}}. 此时, 数据处理脚本 中接收到的数据为 {"a":1,"b":2}, 并不会包含 \r\n, 而下一次数据拆分脚本接收到的数据为 {"a":3,"b":4}\r\n.
包含错误或不完整数据包时
返回以下内容时, 表示接收到的数据不完整, 需要丢弃 0 - 12 部分内容.
例如, 接收到的内容为 :1,"b":2}\r\n{"a":3,"b":4}\r\n 时, :1,"b":2}\r\n 为不完整的数据包需要丢弃, 则需要返回 {"drop":11}. 下一次数据拆分脚本接收到的数据为 {"a":3,"b":4}\r\n
不包含完整的数据包和错误包时
例如, 接收到的数据为 {"a":1, 未找到分隔符 \r\n, 此时返回 undefined 表示需要接收更多的数据. 当客户端发送 "b":2}\r\n{"a":3,"b":4}\r\n 数据时, 会再次调用 数据包拆分脚本, 此时 Buffer 中的数据为 {"a":1,"b":2}\r\n{"a":3,"b":4}\r\n, 此时可以提取出完整的数据包.
注: 如果返回结果中同时包含了 drop 和 package 字段并且 drop 的值大于 0 时, 只做丢弃数据处理.
示例
数据处理脚本
该脚本用于处理 数据包拆分脚本 函数解析得到的完整数据包, 根据协议和数据格式将数据包解析为平台定义的数据格式.
函数定义如下:
参数说明
返回值
注: 返回值必须为 Object[] 或 undefined 其中之一. 返回空数组表示未从接收到的数据中解析出有效数据, undefined 表示无返回结果, 驱动程序无须处理返回结果. 例如: 接收到的数据为心跳数据, 不包含采集数据.
示例
指令处理脚本
该脚本用于将发送的指令内容转换为字节数组, 当向设备发送指令时, 驱动会将要发送的内容先经过 命令处理脚本 函数处理, 返回结果作为实际发送的内容.
函数定义如下:
参数说明
指令格式如下:
以 test 指令为例
注:ops.value 通常为实际发送的内容, 该字段为必填值. opts 为数组,
目前长度固定为 1.
当要发送的数据内容比较复杂时, 可以先转发送内容转换为 base64 格式的字符串
然后在在指令脚本中再进行 base64 解码后再发送.
返回值说明
必须值必须为 Buffer 对象.
注: 可以使用 Buffer.from("this is a string"") 等方法创建 Buffer 对象. 关于 Buffer 的使用说明可以点击查看
示例
连接处理脚本
当客户端连接到驱动或断开时, 会调用 连接处理脚本.
函数定义如下:
参数说明
返回值说明
无返回值
示例
定时器脚本
用于周期性的执行一些操作. 例如: 定时发送心跳.
函数定义如下:
参数说明
返回值说明
无返回值
示例
示例
假设某设备使用 TCP 上报数据, 并且上报的数据格式如下所示: