WebSocket 服务端驱动
该驱动作为 Websocket 服务端接受客户端的连接和数据, 并通过自定义脚本将客户端发送的数据解析为平台要求的数据格式.
脚本说明
脚本语言: JavasScript ECMAScript 5.1
驱动使用时要求提供 连接处理脚本
, 解析处理脚本
, 指令处理脚本
和 定时器脚本
4 个脚本函数来处理接收和发送数据过程中的协议和数据格式问题.
在脚本的上下文中内置了 Buffer
包, 可用于处理接收或发送二进制数据.
除此之外, 还内置了 lodash
, crypto-js
, moment
, xml-js
和 formulajs(Excel函数)
包.
注: 所有的脚本的函数名必须为
handler
.
客户端对象
在每次脚本中的参数中提供了 client
对象, 该对象为当前 WebSocket
连接客户端对象,
可以通过该对象实现向客户端发送数据的功能.
例如: 向客户端发送 ack
信息等. 该对象提供了以下函数:
send(params) 发送数据
用于向客户端发送数据. 例如: 在接收到数据时向客户端发送 ack
信息.
该函数的参数为 {messageType: number; data: Buffer}
对象, 说明如下:
messageType
: 消息类型, 数值. 1: 文本消息, 2: 二进制消息.data
: 发送的数据,Buffer
对象.
示例
function handler(client, buffer) {
// 发送文本消息
client.send({messageType: 1, data: Buffer.from("这是一段文本消息")});
// 发送二进制消息
const binData = Buffer.capacity(128);
binData[0] = '@';
binData.writeInt16LE(15, 1);
binData.writeInt16LE(34, 3);
binData[5] = '@';
// send 函数的返回值为 string 或 undefined
// 如果参数不正确(字节数组为空或空数组)或发送失败则返回 string 内容为错误说明, 如果发送成功则返回 undefined
const result = client.send({messageType: 2, data: binData});
if(result) {
console.log("发送失败,", result);
}
}
register(deviceId) 注册设备
注册设备函数用于绑定设备与客户端的关系, 为后续向设备发送指令提供对应关系.
示例
function handler(client, buffer) {
// 解析客户端发送的数据
const data = JSON.parse(buffer.toString());
// 从数据中提取出设备标识
const devcieId = data.id;
// 注册设备. 当给设备下发指令时, 就会向已注册设备对应的客户端发送数据
client.register(devcieId);
}
close() 关闭连接
主动关闭与客户端的连接. 例如, 客户端发送的数据不正确时, 可以主动关闭与该客户端的连接.
示例
function handler(client, buffer) {
// 解析客户端发送的数据
const data = JSON.parse(buffer.toString());
// 从数据中提取出设备标识
const devcieId = data.id;
// 如果没有设备标识时, 关闭该客户端
if(!deviceId || deviceId === "") {
client.close();
}
}
getRemoteAddr() 获取客户端连接信息
用于获取客户端的连接信息, 格式为: IP:Port. 例如: 192.168.1.132:61431
示例
function handler(client, buffer) {
// 获取客户端的连接信息.
// 例如: 有时可能需要根据客户端的IP区分不同的设备.
const clientAddr = client.getRemoteAddr();
}
setVariable(name, value) 保存数据
用于向客户端保存一些自定义数据, 这些数据在客户端断开之前会一直存在, 可以随便读取. 不同客户端之间的数据相互隔离.
同一客户端的数据, 可以在不同的脚本中共享. 例如: 在 连接处理脚本
中设置的变量, 可以在 数据处理脚本
中读取.
示例
function handler(client, buffer) {
// 保存数据
client.setVariable("myVariable1", 123);
// 保存数据
client.setVariable("myVariable2", "ok");
}
getVariable(name) 获取数据
用于获取客户端内保存的数据, 如果没有找到对应的变量, 会返回 undefined
.
示例
function handler(client, buffer) {
// 读取数据
const value = client.getVariable("myVariable1");
if(value === undefined) {
console.log("没有找到变量");
}
}
removeVariable(name) 删除数据
用于删除客户端内保存的数据.
示例
function handler(client, buffer) {
// 删除数据
client.removeVariable("myVariable1");
}
containsVariable(name) 检查数据是否存在
用于检查客户端内是否存在某个数据, 如果存在返回 true
, 否则返回 false
.
示例
function handler(client, buffer) {
// 检查数据是否存在
if(client.containsVariable("myVariable1")) {
console.log("数据存在");
} else {
console.log("数据不存在");
}
}
连接处理脚本
当连接状态发生变化时会调用该函数. 例如: 当连接建立时向服务端发送数据.
说明:
- 当连接断开时, 会调用该函数, 此时
state
的值为false
, 此时无法通过client
发送数据.- 当驱动启动或者重连建立连接时, 会调用该函数, 此时
state
的值为true
.- 当重启驱动时, 该函数会调用两次, 第一次
state
的值为false
, 第二次为true
.
函数定义
/**
* 连接处理脚本, 当与服务端连接建立或断开时执行的操作
*
* @param {Object} client 客户端对象
* @param {Boolean} state 连接状态, true: 已连接, false: 已断开
* @param {Object} request 连接请求信息, 可以在该对象中获取请求参数和请求头等信息. 当 state 为 false 时该参数为 undefined
*/
function handler(client, state, request) {
if(state) {
// 当连接已建立时执行操作
} else {
// 当连接断开时执行操作
}
}
示例
/**
* 连接处理脚本, 当与服务端连接建立或断开时执行的操作
*
* @param client 客户端对象
* @param {function(Buffer)} client.send({messageType: 1, data: Buffer.from("hello world")}) 向服务端发送该数据(只有 state 为 true 时可用). messageType: 1: 文本消息, 2: 二进制消息
* @param state 连接状态, true: 已连接, false: 已断开
*/
function handler(client, state) {
if(state) {
// 当连接已建立时执行操作. 例如: 可以发送登录请求
const loginParams = {"type": "login", "username": "user1", "password": "123456"};
client.send({messageType: 1, data: Buffer.from(JSON.stringify(loginParams))});
} else {
// 当连接断开时执行操作
}
}
数据处理脚本
该脚本用于处理从服务端接收到的数据, 然后将数据解析为平台规定的格式并返回.
函数定义
/**
* 数据处理脚本, 解析从 websocket 客户端发送的数据并转换为平台规定的数据格式
*
* @param {Object} client 客户端对象
* @param {function(string)} client.register(设备编号) 注册设备. 将设备与当前客户端连接绑定, 当向设备下发指令时, 从注册信息中查询设备关联的客户端连接.
* @param {function()} client.close() 关闭连接. 用于主动关闭当前连接.
* @param {function()} client.getRemoteAddr() 获取当前客户端地址. 例如: 192.168.100.123:54321
* @param {function(key, value)} client.setVariable(key, value) 向当前客户端中保存数据, 不同客户端之间的数据是隔离的. 该数据在客户端连接断开之前可用.
* @param {function(key)} client.getVariable(key) 从当前客户端中获取已保存的数据.
* @param {function(key)} client.removeVariable(key) 从当前客户端中删除数据.
* @param {function(key)} client.containsVariable(key) 判断当前客户端中是否包含指定数据
* @param {function(Buffer)} client.send(数据) 向设备发送该数据.
* @param {Buffer} buffer 接收到的数据
*/
function handler(client, buffer) {
// 返回值说明:
// id: 必填, 设备编号或自定义标识
// table: 可选, 设备所属工作表标识, 可选. 如果接收到的数据中不包含表信息, 则不返回 table 字段
// time: 可选, 采集数据的时间, 毫秒. 如果为 0 则取服务器的时间.
// values: 必填, 数据点的值. key: 数据点标识, value: 数据点的值
//
// 示例:
// 接收到的数据格式为 {id: "SN123456", "time": 1734513430957, "data": {"voltage": 12.34, "state": "online"}}
// const data = buffer.toString();
// return [{"id": data.id, "time": data.time, "values": data.data}];
return [{"id": "deviceId", "table": "tableId", "time": new Date().getTime(), "values": {"tagId1": 123, "tagId2": "abc"}}];
}
示例
function handler(client, data) {
// 以 json 格式为例, 例如: {"id":"d01","time":"2022-10-17 17:57:32","values":[{"name":"temperature","data":17.5},{"name":"humidity","data":35.7}]}
// 将 data 解析为 json 对象
const jsonData = JSON.parse(data.toString());
// 从对象中提取时间信息
const time = moment(jsonData.time, "YYYY-MM-DD HH:mm:ss");
// 将 values 字段解析为平台规定的格式 {"key1": value1, "key2": "value2", ...}
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()}
]
}
指令处理脚本
该脚本用于将发送的指令内容转换为字节数组, 当向设备发送指令时, 驱动会将要发送的内容先经过 handler
函数处理,
返回结果作为实际发送的内容.
函数定义
/**
* 指令处理脚本, 当发送指令时将指令信息转换为字节数组
*
* @param {Object} client 客户端对象
* @param {string} serialNo 平台指令序号
* @param {string} table 表标识
* @param {string} deviceId 设备标识
* @param {object} command 指令信息, 详细格式说明见驱动配置文档
* @return {Buffer} 最终发送数据
*/
function handler(client, serialNo, table, deviceId, command) {
// messageType:
// 1: 文本消息
// 2: 二进制消息
//
// data: 最终发送的数据, 必须为 Buffer 对象
return {"messageType": 1, "data": Buffer.from(command.ops[0].value)};
}
示例
// 发送文本消息
function handler(client, data) {
// 从指令信息中取出要发送的内容, 格式为 base64
const value = data.ops[0].value;
// 将要发送的内容做 base64 解码
return {"messageType": 1, "data": Buffer.from(value, 'hex')};
}
定时器处理脚本
定时器脚本用于定义驱动周期性的向服务端发送数据, 例如: 心跳数据或数据请求等
当开启定时器后, 驱动定时向执行脚本中的 handler
函数, 可以在 handler
脚本中通过调用 client.send
函数向服务端发送数据.
函数定义
/**
* 定时器脚本, 定时执行一些动作. 例如: 定时发送数据
*
* @param {Array<Object>} clients 当前所有连接到驱动的客户端对象
*/
function handler(clients) {
for (let i=0; i<clients.length; i++) {
const client = clients[i];
// 发送文本消息
client.send(1, "文本消息");
// 发送二进制消息. 二进制消息必须为 Buffer 对象
client.send(2, Buffer.from("二进制消息"));
}
}
示例
function handler(clients) {
for(let i=0; i<clients; i++) {
clients[i].send({messageType: 1, data: Buffer.from("ping")});
}
}