概述
TDS脚本可以用于:
- 对监控点数值进行二次计算和统计
- 根据条件判断进行输出控制
脚本使用标准javascript编写,并且提供了一些内置函数,与TDS系统进行交互
脚本类型
设备脚本
在【系统组态】-【设备接入】页面中,可选择自定义脚本(即设备脚本)进行绑定,会自动执行脚本
执行方式 | 说明 |
---|---|
周期采集 | 自动周期执行 |
控制输出 | 根据条件判断进行输出控制 |
主动上送处理 | 处理设备主动上送的数据 |
实例请看 使用内置脚本模拟第三方设备
在脚本管理中配置脚本
全局脚本
脚本模式 | 值 | 功能描述 |
---|---|---|
周期执行 | cyclic | 保存后开始轮询 |
监控点值改变时 | onMpValChange | 监控点值改变时自动执行 |
接口调用 | apiCall | 点击执行跑一次脚本 |
表达式脚本
当系统组态-监控对象-IO类型设置为【内部变量】时,可以通过其他监控点计算得出值,设置的【计算表达式】即为表达式脚本
配置用于计算该监控点实时值的表达式,自动周期执行
比如val("温度")/2
,是由 科技大楼.一楼.温度
值 / 2 实时计算的
可在【脚本管理】-【表达式脚本状态监控】查看运行情况
环境位号
全局脚本/设备脚本的环境位号
全局脚本可以配置一个环境位号,默认环境位号为根节点。
配置环境位号后,所有脚本当中的位号,都是相对于该环境位号的相对位号。
例如配置某脚本环境位号为 A幢.1层.空调1#
那么脚本不需要写成:
output("A幢.1层.空调1#.开关",true)
写成,相对环境位号来指定位号
output("开关",true)
计算表达式的环境位号
计算表达式一般配置在某个监控点上,该表达式脚本的环境位号是该监控点的父节点。
例如有位号:
"A幢.1层.空调1#电量" //可从设备中读取
"A幢.1层.空调2#电量" //可从设备中读取
"A幢.1层.总电量" //需要二次计算
可以给A幢.1层.总电量位号配置计算表达式,此时环境位号为 A幢.1层
val("空调1#电量") + val("空调2#电量")
计算表达式中使用相对位号,可以计算表达式在不同对象使用同一个配置,并可以进行配置的批量应用。
例如假设有位号
"A幢.2层.空调1#电量" //可从设备中读取
"A幢.2层.空调2#电量" //可从设备中读取
"A幢.2层.总电量" //需要二次计算
此时 A幢.2层.总电量 使用和 A幢.1层.总电量 相同的计算脚本。
位号表达式
相对位号
相对位号是相对于当前环境位号的位号,例如环境位号为 A幢.1层.空调1#
默认表达式都是相对于该位号的位号,例如:
位号表达式 | 对应的绝对位号 |
---|---|
开关 | A幢.1层.空调1#.开关 |
湿度 | A幢.1层.空调1#.湿度 |
上级位号
tds使用 ^ 符号表示上一级对象树节点:
位号表达式 | 对应的绝对位号 | |
---|---|---|
^ | A幢.1层 | |
^.空调2# | A幢.1层.空调2# | |
^.空调2#.开关 | A幢.1层.空调2#.开关 | |
^^ | A幢 | |
^^.2层.空调1# | A幢.2层.空调1# |
绝对位号
以 ::
开头的位号,表示绝对位号,假设当前脚本的环境位号为A幢.1层.空调1#
那么使用 ::A幢.2层.空调1#.温度
来引用环境位号范围外的位号
局部全局统一查找
可以使用A幢.2层.空调1#.温度
来表示全局位号,忽略::
符号,但是会优先匹配环境位号下的位号 A幢.1层.空调1#.A幢.2层.空调1#.温度
,匹配不到,才会去匹配全局位号
无效计算表达式
表达式脚本中如果使用了val函数,该函数返回null时,将不会生成计算结果。
例如,需要计算今天的用电量 val("总电量") - val("总电量","today","first"),使用当前值减去今天的第一个值,
如果今天没有采集到过任何数据,后面那个val函数返回null
前面的val函数返回最新值,可能是昨天采集的
那么该二次计算表达式将不返回计算结果
数据服务交互函数
sum 求和函数
sum(tag, invalidAsZero)
参数 | 值类型 | 说明 |
---|---|---|
tag | string / json | 参考位号选择器 |
invalidAsZero | bool | 无效值做为0处理,否则遇到无效值,将无法计算最终结果,返回null |
avg 求平均函数
avg(tag)
参数 | 值类型 | 说明 |
---|---|---|
tag | string / json | 参考位号选择器,比如使用 *.温度 求整个项目中的温度的平均值 |
val 取位号值函数
取实时值
如果该位号是一个监控点且为数字型或者布尔型,返回当前值。其他情况返回null
val("三楼.温度")
取历史值
取某个指定时间的历史值
val("三楼.温度","2020-02-02 11:30:00")
使用时间和聚合参数配置取历史值
取本月的第一个数值
val("三楼.温度","this-month","first")
参数说明:
参数 | 值类型 | 是否必填 | 说明 |
---|---|---|---|
tag | string | 必填 | 精确指定的位号。 不支持位号选择器多位号选择 |
time | string | 可选 | 精确指定的时间。参考时间选择器的精确指定模式。 |
第三个参数可选,取值有多种方式
参数 | 值类型 | 说明 |
---|---|---|
aggregate | string | time参数指定一个范围,则必须指定aggregate参数,聚合成为1个返回值 |
max | string | 取最大值 |
min | string | 取最小值 |
avg | string | 取平均值 |
sum | string | 取和 |
first | string | 取最早值 |
diff | string | 求最大与最小值之差 |
diff.last-first | string | 分组最后一条记录 减去 第一条记录的值 |
diff.first-last | string | 分组第一条记录 减去 最后一条记录的值 |
last | string | 取最晚值 |
count | string | 统计数量 |
output 输出到设备
该函数一般用于设备控制输出
//输出一个 模拟量
output("三楼.空调.设定温度",27.3);
//输出一个开关量
output("三楼.智能空开.开关",true);
//输出一个枚举值
//枚举值的具体含义在监控点属性当中配置
output("三楼.空调.运行模式","制热")
input 数据输入
一般用于进行二次计算之后,将计算的结果输入到系统
//取到原始值
var pUp = val("三楼.某设备.上压力");
var pDown = val("三楼.某设备.下压力");
//根据某种计算公式,计算出实际值
var k = (0.87*pUp + 3)/(0.45*pDown + 5);
//输入到某个位号
input("三楼.某设备.压力系数",k);
log 打印日志
可以在脚本编辑下方的输出窗口看到打印信息,一般用于脚本调试
var a = 1;
var b = 1;
var c = a + b;
log("c = " + c);
console.log("c = " + c); //这行代码和上面是等效的,考虑到编码习惯,上下两种都支持
将日志打印到 tds 的日志和服务程序命令行窗口
console.log("log to tds logfile",true); //第二个参数设置为true
一般用于进行二次计算之后,将计算的结果输入到系统
db是一个内置的全局变量,可以调用相关数据库操作
db.insert("温控器.温度","2023-12-25 11:30:00",24.5); //插入浮点
db.insert("温控器.开关","2023-12-25 11:30:00",true); //插入布尔
db.insert("温控器.模式","2023-12-25 11:30:00",1); //插入整形
time函数
返回当前时间对象TIME,支持increaseSeconds对时间进行加减
支持toStr(),fromStr 函数与字符串互转
var t = time();
t.fromStr("2023-01-01 00:00:00");
t.increaseSeconds(10); // 2023-01-01 00:00:10
log(t.toStr());
t.increaseSeconds(10); // 2023-01-01 00:00:20
log(t.toStr());
http.request 同步http请求
目前tds脚本当中所有的脚本执行都是同步的,不使用回调函数
以下演示通过脚本,调用http请求监控点数据
//http的请求参数
var opt = {
hostname: 'localhost',
port: 667,
path: '/rpc',
method: 'POST'
};
//http是tds脚本环境的内置对象,使用http.request函数发起http请求
//http.request是一个同步函数,无需设置回调函数,阻塞代码运行
var resp = http.request(opt);
if(resp!=null){
console.log(resp.body);
}
else{
console.log("请求失败");
}
sleep 睡眠
用于延时控制等,单位毫秒 以下示例表示当温度小于20度,延时4秒打开空调开关
if(val("三楼.温度") < 20){
sleep(4000);
output("三楼.空调.开关",true);
}
setReturn设置返回值
某些脚本需要设置返回值,比如 控制输出脚本,需要设置
rpc的output函数的返回
JSON.stringify 格式化json字符串
var a = 1;
var s = "123";
a = JSON.stringify(a);
s = JSON.stringify(s);
STR.toHexStr 转为16进制字符串
var arr = [1,2,3,20];
var s = STR.toHexStr(arr);
s == "01 02 03 14"
设备IO脚本函数
IO脚本中,外部会传递环境变量到脚本,在脚本中可以直接调用,环境变量都是用大驼峰模式
Dev 当前设备
属性 | 类型 | 说明 |
---|---|---|
Dev.addr | object | |
Dev.addr.ip | string | 设备IP地址 |
Dev.addr.port | string | 设备端口 |
Dev.addr.id | string | 设备id |
Dev.children | object | 如果当前设备是网关设备,可以访问设备子设备 |
Dev.input(val,chanAddr)
参数 | 值类型 | 说明 |
---|---|---|
val | any | 输入值 |
chanAddr | string | 通道地址 |
Dev.doTransaction(req)
//同时支持以下两种参数格式
Dev.doTransaction([0x01,0x01])
Dev.doTransaction("01 01")
参数 | 值类型 | 说明 |
---|---|---|
req | uint8 array | 请求二进制数据包,字节数组 |
RecvData 当前接收到的设备主动上送数据
数据接收脚本支持
uint8 array
收到的二进制数据包,字节数组
Output 当前数据输出命令
属性 | 说明 |
---|---|
Output.val | 输出值 |
Output.chan | 输出通道 |
Req 当前TDSP请求
TDSP请求脚本支持
Req符合jsonRPC格式,格式用户自定义,并在脚本中实现rpc到自定义协议格式的转换
{
"method":"setTempLimit",
"params":{
"highLimit": 30,
"lowLimit" : 20
}
}
使用内置脚本模拟第三方设备
http脚本
仓库地址:https://svn.liangtusoft.com:8443/demo-conf
运行仿真程序 httpServer.js
找到【设备接入-扩展IO-内置脚本模式】文件夹
使用nodejs运行设备仿真工具 testServer/httpServer.js
该测试工具内置了http文件服务器,打开测试界面 http://127.0.0.1:30001/ 测试http接口协议
通过 /get 接口可以获取到设备的当前所有实时值
使用 /set 接口设置某个参数,可以同时设置多个参数
配置监控对象树
名称 | 对象层级 | IO类型 |
---|---|---|
测试项目 | ||
科技大楼 | 组织结构 | |
一楼 | 监控对象 | |
温度(当前温度) | 监控点 | |
湿度 | 监控点 | |
开关 | 监控点(布尔型) | 输入/输出 (来自硬件数据采集,支持控制输出) |
设定温度(空调) | 监控点 | 输入/输出 (来自硬件数据采集,支持控制输出) |
添加设备
在tds的io配置界面新增一个【自定义设备】,设置地址模式为【http服务端】,并配置为仿真设备的对外服务 ip:127.0.0.1 和端口:30001
将设备内的数据抽象为4个通道,此处根据不同的设备协议,需要自主设计不同的通道抽象方法
每个通道都需绑定监控点
添加完设备,在node脚本运行的情况下,可以看到设备上线
添加全局脚本
进入脚本管理,添加2个【设备脚本】,一个负责周期采集,一个负责控制输出
HTTP设备_周期采集脚本
// http的请求参数
const res = http.request({
hostname: 'localhost',
port: 30001, //仿真设备的服务端口
path: '/get', //获取当前所有实时数据的接口
method: 'POST'
});
console.log(JSON.stringify(res));
//http是tds脚本环境的内置对象,使用http.request函数发起http请求
//http.request是一个同步函数,无需设置回调函数,阻塞代码运行
if (res && res.body) {
// Dev 是 tds脚本内置对象,具有 setOnline,setOffline,input函数
Dev.setOnline();
const resObj = JSON.parse(res.body);
Dev.input(resObj.temp, "temp"); //第一个参数是输入值,第二个参数是通道地址
Dev.input(resObj.humi, "humi");
Dev.input(resObj.power, "power");
Dev.input(resObj.tempSet, "tempSet");
} else {
Dev.setOffline();
console.log('请求失败')
}
HTTP设备_控制输出脚本
console.log(JSON.stringify(Dev.addr));
var token = Dev.getDevVar("token");
console.log(token);
var setParams = {
}
//当脚本被设置为设备的控制输出脚本时,脚本会有一个 Output 内置对象
//Output.val 是输出值, Output.chan 是输出通道地址
setParams[Output.chan] = Output.val;
console.log(JSON.stringify(setParams));
var s = JSON.stringify(setParams);
var opt = {
hostname: Dev.addr.ip,
port: Dev.addr.port,
path: '/set',
method: 'POST',
body: s,
headers:{
"token":token,
"user":"admin"
}
};
var resp = http.request(opt);
var ret = {};
if(resp!=null){
console.log(resp.body);
Dev.setOnline();
ret["result"] = "ok";
}
else{
Dev.setOffline();
ret["error"] = "请求失败";
}
// 控制输出脚本使用该函数返回执行信息,返回是否输出成功
setReturn(ret);
环境变量脚本
var output = {
chan: "tempSet",
val: 37.3
}
勾选环境变量时会出现一个新的编辑框在右侧,作为正文脚本的上下文调试运行
当【HTTP设备_控制输出脚本】被绑定到设备时,环境变量脚本会被忽略,由设备提供数据
设备绑定自定义脚本
进入到【自定义设备】的编辑页面,设置【周期采集】和【控制输出】使用的脚本。下拉框中,可以选择在脚本管理中预先编辑好的脚本
设置控制输出命令的返回信息,第三方api一般是http请求,为了将自定义脚本的执行结果返回给tds,在tds脚本当中使用 setReturn函数,将一个错误信息对象返回给tds
当设置 ret["result"]="ok"时,tds可以获得成功信息,如图点击开关进行开关输出,弹出成功信息
关闭设备仿真程序,模拟设备不在线时,执行控制输出脚本的效果,返回的错误信息就是在脚本中设置的ret["error"] 中的信息
UDP脚本
使用nodejs运行设备仿真工具 testServer/udpServer.js,文件目录和 httpServer.js同级
监控对象复制一楼的数据后,粘贴改名为二楼
接入设备
添加设备脚本
var req = [0x01,0x01];
var resp = Dev.doTransaction(req);
if(resp!=null){
console.log(resp);
Dev.input(resp[3],String(resp[2]));
Dev.input(resp[5],String(resp[4]));
Dev.setOnline();
}
else{
Dev.setOffline();
}
点击调试运行向node仿真程序发送数据
在【点位列表】可以看到更新的数据
TCP 脚本
使用nodejs运行设备仿真工具 testServer/tcpServer.js,文件目录和 httpServer.js同级
添加tcp设备
var req = [0x01,0x01];
var resp = Dev.doTransaction(req);
if(resp!=null){
console.log(resp);
Dev.input(resp[3],String(resp[2]));
Dev.input(resp[5],String(resp[4]));
Dev.setOnline();
}
else{
Dev.setOffline();
}
TCP设备-控制输出
//01 02 03 30
//地址 01
//命令码 02 表示输出
//通道 03
//输出值 30
var req = [01,02,03,30];
var resp = Dev.doTransaction(req);
console.log(resp)
var ret = {};
if(resp){
console.log("输出成功");
ret["result"] = "ok";
} else {
ret["error"] = "请求失败";
}
setReturn(ret);