UDP驱动
该驱动内置了一个 UDP Server 服务, 驱动启动后可以接收客户端发送的数据并且也可以主动向设备发送数据. 如果使用容器部署时, 需要将 udp 服务端口开放.
配置说明
以下内容为 udp
驱动的配置项
server:
port: 9607 # UDP 监听的端口, 默认 9607
脚本说明
脚本语言: JavasScript ECMAScript 5.1
驱动使用时要求提供 数据处理脚本
来处理接收和发送数据过程中的协议和数据格式问题.
在脚本的上下文中内置了 Buffer
包, 可用于处理接收或发送二进制数据.
除此之外, 还内置了 lodash
, crypto-js
, moment
, xml-js
和 formulajs(Excel函数)
包.
注: 所有的脚本函数名必须为
handler
客户端对象
客户端对象, 可以获取当前数据包的来源信息(包括: IP和Port). 并且可以管理数据上下文对象.
remoteAddr(属性)
用于获取发送数据包的客户端信息. 例如: 192.168.100.123:53041
示例
/**
* 连接处理脚本, 当与服务端连接建立或断开时执行的操作
*
* @param {object} client 客户端对象
* @param {Buffer} data 接收到的数据包
*/
function handler(client, data) {
// 例如: 192.168.100.123:53041
const remoteAddr = client.remoteAddr;
}
ip(属性)
用于获取发送数据包的客户端的IP地址. 例如: 192.168.100.123
示例
/**
* 连接处理脚本, 当与服务端连接建立或断开时执行的操作
*
* @param {object} client 客户端对象
* @param {Buffer} data 接收到的数据包
*/
function handler(client, data) {
// 例如: 192.168.100.123
const ipAddr = client.ip;
}
port(属性)
用于获取发送数据包的客户端的端口. 例如: 53041
示例
/**
* 连接处理脚本, 当与服务端连接建立或断开时执行的操作
*
* @param {object} client 客户端对象
* @param {Buffer} data 接收到的数据包
*/
function handler(client, data) {
// 例如: 53041
const port = client.port;
}
getContext
用于获取数据上下文对象, 可以在数据上下文中存储数据. 数据上下文对象
注: 第一次调用的时候才会创建数据上下文
参数说明
参数名 | 参数类型 | 参数说明 | 示例值 |
---|---|---|---|
contextId | String | 上下文标识 | "myContext" |
返回值
Object
. 数据上下文对象
示例
function handler(client, data) {
// 获取一个标识为 myContext 的上下文
var myContext = client.getContext("myContext");
// 使用客户端 IP 地址作为上下文的标识
var context = client.getContext(client.getRemoteIp());
}
getDeviceContext
用于获取设备数据上下文对象, 使用设备标识作为上下文标识, 可以在上下文中存储数据. 数据上下文对象
与 getContext
不同的是, 当设备被删除后, 重启驱动时会自动清理被删除设备的上下文对象.
注: 第一次调用的时候才会创建数据上下文
参数说明
参数名 | 参数类型 | 参数说明 | 示例值 |
---|---|---|---|
deviceId | String | 设备标识 | "ST10001" |
返回值
Object
. 数据上下文对象
示例
function handler(client) {
// 获取设备 ST10001 的上下文
var context = client.getDeviceContext("ST10001");
}
removeContext
删除上下文对象
参数说明
参数名 | 参数类型 | 参数说明 | 示例值 |
---|---|---|---|
contextId | String | 上下文标识 | "myContext" |
返回值
无
示例
function handler(client) {
// 删除标识为 myContext 的上下文
client.removeContext("myContext");
// 删除以客户端 IP 地址作为标识的上下文
client.removeContext(client.getRemoteIp());
}
removeDeviceContext
删除设备上下文对象
参数说明
参数名 | 参数类型 | 参数说明 | 示例值 |
---|---|---|---|
deviceId | String | 上下文标识 | "ST10001" |
返回值
无
示例
function handler(client) {
// 删除设备 ST10001 的上下文
client.removeDeviceContext("ST10001");
}
getContextIds
获取全部上下文标识(不包含设备上下文)
参数说明
无
返回值
String[]
示例
function handler(client) {
// 创建两个上下文
const context1 = client.getContext("context1");
const context2 = client.getContext("context2");
// 获取全部上下文标识 ["context1", "context2"]
const contextIds = client.getContextIds();
}
getDeviceContextIds
获取全部设备上下文标识(只包含设备上下文)
参数说明
无
返回值
String[]
示例
function handler(client) {
// 创建两个设备上下文
const context1 = client.getDeviceContext("ST10001");
const context2 = client.getDeviceContext("ST10002");
// 获取全部设备上下文标识 ["ST10001", "ST10002"]
const contextIds = client.getDeviceContextIds();
}
数据上下文
数据上下文对象, 用来存储数据.
注: 可以根据需求创建多个上下文对象, 但是上下文对象以及上下文中的数据需要及时清理, 否则会造成
OOM
问题, 导致驱动程序崩溃.
put
用于向上下文中存储数据.
注: 存入的数据需要自行清理, 否则可能导致
OOM
等问题.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
value | any | 数据项的值 |
返回值
无
示例
function handler(client, request) {
// 获取或创建标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 向上下文中存储一个数值
context.put("number", 3.141);
// 向上下文中存储一个对象
context.put("object", {name: "张三", age: 18});
// 获取或创建设备 ST10001 的上下文
const deviceContext = client.getDeviceContext("ST10001");
// 向上下文中存储一个字符串
deviceContext.put("string", "this is a string");
// 向上下文中存储一个数值
deviceContext.put("number", 3.141);
// 向上下文中存储一个对象
deviceContext.put("object", {name: "张三", age: 18});
}
containsKey
判断上下文中是否存在指定的 key
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
bool
. true
表示 key
存在, false
表示 key
不存在
示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 返回 true
context.containsKey("string");
// 返回 false
context.containsKey("string1");
}
get
从上下文中获取指定的 key
对应的数据. 如果 key
不存在则返回 undefined
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
any
或 undefined
. 返回 put
时写入的数据.
示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 返回 "this is a string"
context.get("string");
// 返回 undefined
context.get("string1");
}
getAndRemove
从上下文中获取指定的 key
对应的数据并且在返回后 删除 该 key
. 如果 key
不存在则返回 undefined
.
注: 该函数返回后, 再使用
get
或getAndRemove
均返回undefined
.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
any
或 undefined
. 返回 put
时写入的数据.
示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 返回 "this is a string"
context.getAndRemove("string");
// 返回 undefined
context.get("string");
// 返回 undefined
context.getAndRemove("string");
}
remove
从上下文中删除指定的 key
, 如果 key
不存在则不执行任何操作.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
无
示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 数据被删除
context.remove("string");
// 不执行任何操作
context.remove("string");
}
数据处理脚本
该脚本用于处理接收到的数据包, 根据协议和数据格式将数据包解析为平台定义的数据格式.
函数定义
/**
* 数据处理脚本, 解析从客户端接收到的数据并转换为平台规定的数据格式
*
* @param {Object} client 客户端对象. 可以通过该对象获取客户端的信息.
* @param {String} client.remoteAddr 客户端地址. 例如: 192.168.2.150:32567
* @param {String} client.ip 客户端地址. 例如: 192.168.2.150
* @param {number} client.port 客户端地址. 例如: 32567
* @param {function(Buffer)} client.send 向发送数据的客户端发送信息.
* @param {Buffer} buffer 本次接收到的数据
* @return {Array} 解析出的采集数据信息. 如果返回 undefined 则表示未解析出有效数据.
*/
function handler(client, buffer) {
// 返回值说明:
// id: 必填, 设备编号或自定义标识
// table: 可选, 设备所属工作表标识, 可选. 当多个工作表中存在相同的设备编号或自定义标识时, 需要指定该设备所属的工作表, 否则会丢弃访数据
// time: 可选, 采集数据的时间, 毫秒. 如果为 0 则取服务器的时间.
// values: 必填, 数据点的值. key: 数据点标识, value: 数据点的值
return [{"id": "deviceId", "table": "tableId", "time": new Date().getTime(), "values": {"tagId1": 123, "tagId2": "abc"}}];
}
示例
function handler(target, buffer) {
// 以 json 格式为例, 例如: {"id":"d01","time":"2022-10-17 17:57:32","values":[{"name":"temperature","data":17.5},{"name":"humidity","data":35.7}]}
const jsonData = JSON.parse(buffer.toString());
const time = moment(jsonData.time, "YYYY-MM-DD HH:mm:ss");
const values = {};
for (let i = 0; i < jsonData.values.length; i++) {
const value = jsonData.values[i];
values[value.name] = value.data;
}
return [
{id: jsonData.id, values: values, time: time.valueOf()}
]
}
指令处理脚本
当向设备发送指令时, 驱动会将指令信息通过参数传递给 指令处理脚本
, 在脚本中根据指令信息构建最终要发送的数据并返回,
最终驱动会将脚本返回的数据发送给设备.
函数定义
/**
* 指令处理脚本, 下发指令时, 在该脚本中根据指令参数组装发送的报文
*
* @param {Object} client 客户端对象
* @param {String} serialNo 平台指令序号
* @param {String} table 设备所属的设备表的标识
* @param {String} deviceId 设备的标识, 设备编号或自定义标识
* @param {String} deviceIp 目标设备的IP, 在设备上配置的IP或主机名. 例如: 192.168.2.150
* @param {number} devicePort 目标设备的端口, 在设备上配置的端口. 例如: 32567
* @param {Object} command 指令参数.
* @return {Buffer} 根据指令信息组装好的字节数组
*/
function handler(client, serialNo, table, deviceId, deviceIp, devicePort, command) {
// 函数的返回值作最终发送的数据, 必须为 Buffer 对象
}
示例
/**
* 指令处理脚本, 下发指令时, 在该脚本中根据指令参数组装发送的报文
*
* @param {Object} client 客户端对象
* @param {String} serialNo 平台指令序号
* @param {String} table 设备所属的设备表的标识
* @param {String} deviceId 设备的标识, 设备编号或自定义标识
* @param {String} deviceIp 目标设备的IP, 在设备上配置的IP或主机名. 例如: 192.168.2.150
* @param {number} devicePort 目标设备的端口, 在设备上配置的端口. 例如: 32567
* @param {Object} command 指令参数.
* @return {Buffer} 根据指令信息组装好的字节数组
*/
function handler(client, serialNo, table, deviceId, deviceIp, devicePort, command) {
// 最终发送的数据, 必须为 Buffer 对象
//
// 以下为示例代码:
const data = Buffer.alloc(12);
// 写入报文头
data[0] = "@";
// 写入指令类型
data[1] = 0x01;
// 写入报文长度
data.writeUInt16BE(0x0009, 2);
// 写入数据
data.writeUInt32BE(0x12345678, 4);
// 写入 CRC 校验码(此处为示例, 实际使用时需要根据报文内容计算 CRC)
data.writeUInt16BE(0x1234, 8);
// 写入报文尾
data[10] = "#";
return data;
}
定时器脚本
定时器脚本用于周期性的向设备发送数据, 例如: 数据请求报文.
函数定义
/**
* 定时器脚本, 定时执行一些动作. 例如: 定时发送数据请求报文
*
* @param {Object} client 客户端对象. 提供了发送数据的方法
* @param {Object} device 当前设备信息
* @param {string} device.id 设备编号或自定义标识. 如果填写了自定义标识则使用自定义标识, 否则为设备编号
* @param {string} device.tableId 设备所属表标识
* @param {string} device.host 设备配置的IP或主机名
* @param {number} device.port 设备配置的端口
*/
function handler(client, device) {
// 使用 send 方法向设备发送数据, 参数为要发送的数据, 必须为 Buffer 对象
client.send(Buffer.from("ping"));
}
示例
/**
* 定时器脚本, 定时执行一些动作. 例如: 定时发送数据请求报文
*
* @param {Object} client 客户端对象. 提供了发送数据的方法
* @param {Object} device 当前设备信息
* @param {string} device.id 设备编号或自定义标识. 如果填写了自定义标识则使用自定义标识, 否则为设备编号
* @param {string} device.tableId 设备所属表标识
* @param {string} device.host 设备配置的IP或主机名
* @param {number} device.port 设备配置的端口
*/
function handler(client, device) {
// 使用 send 方法向设备发送数据, 参数为要发送的数据, 必须为 Buffer 对象
client.send(Buffer.from(device.id));
}