脚本和公式
服务器应用程序有一个用于执行用户脚本的内置引擎。脚本用于计算通道值和状态,以及计算命令值。
脚本被写入脚本配置数据库表。使用模板创建的项目已包含一组初始脚本,可以将其用作开发自己的脚本的示例。中实现的变量和函数脚本然后在表中调用输入公式和输出公式的列通道桌子。要对通道执行公式计算,请选中启用公式柱子。
脚本创建规则
编写和使用脚本的一般规则:
- 脚本是根据以下内容编写的C#语言的语法。可以使用各种 .NET 框架类,例如 Math、DateTime 和 File。
- 新的常量、字段、属性和方法被添加到
脚本 表并在通道公式中可用。 - 如果至少一个脚本或公式包含错误,服务器将无法运行。有关脚本错误的信息写入服务器应用程序日志中。
通道公式计算规则:
- 通道的输入公式输入和输入/输出仅当服务器收到该通道的新数据时才计算类型。如果数据来自设备,请使用提到的通道类型。
- 通道的输入公式已计算和计算/输出类型在主服务器循环的每次迭代中连续计算。计算顺序是通道数从小到大。如果某个通道的值和状态是根据其他通道的数据计算的,则使用计算通道类型。
- 通道的输出公式输入/输出,计算/输出和输出类型是在向相应通道发送命令时计算的。
- 计算通道的输入公式后的通道状态输入和输入/输出如果公式中未明确指定状态计算,则 types 等于传输到服务器的数据的状态。
- 对于通道已计算和计算/输出类型,状态设置为定义如果公式中没有明确指定状态计算。
- 如果输入公式包含“;”符号,它分为两部分:第一部分计算通道值,第二部分计算通道状态。
- 如果通道有指定的限制,则在计算通道的输入公式后,将考虑限制来重新计算通道状态。
- 公式必须返回某些数据类型的值,如下所述。
通道输入公式返回以下类型的数据:
| 数据类型 | 描述 |
|---|---|
| double | 通道值 |
| CnlData | 通道值和状态 |
| long | 数据类型在 Channels 表中设置为 Integer 的通道的 64 位整数值 |
| string | 数据类型在 Channels 表中设置为 ASCII string 或 Unicode string 的通道字符串值 |
如果通道的输入公式返回上表中列出的数据类型以外的数据类型,则公式返回的值将根据通道的数据类型转换为适当的类型。通道输入公式中计算通道状态的部分必须返回 type 的整数值int。
通道输出公式返回以下类型的数据:
| 数据类型 | 描述 |
|---|---|
| double | 命令值。要取消命令,公式必须返回 double.NaN |
| CnlData | 命令值。要取消命令,公式必须返回 CnlData.Empty |
| byte[] | 二进制命令数据。要取消命令,公式必须返回 null |
| string | 字符串命令数据。由 Server 转换为字节数组 |
可用变量
脚本引擎提供以下内置变量:
| 多变的 | 数据类型 | 描述 |
|---|---|---|
| Timestamp | DateTime | 处理数据的时间戳 (UTC) |
| IsCurrent | bool | 处理后的数据是当前数据 |
| CnlNum | int | 计算公式的通道号 |
| Channel | Cnl | 计算公式的通道的属性 |
| ArrIdx | int | 已处理数组元素的索引 |
| Cnl, CnlVal | double | 计算前传输给Server的通道值 |
| CnlStat | int | 计算前传输至Server的通道状态 |
| CnlData | CnlData | 计算前通道数据传输至Server |
| Cmd, CmdVal | double | 计算前传送给服务器的命令值 |
| CmdData | byte[] | 命令数据传输到服务器 |
| CmdDataStr | string | 命令数据作为字符串 |
可用功能
脚本引擎提供以下内置函数:
| 功能 | 数据类型 | 描述 |
|---|---|---|
| N(n) | int | 返回通道号 n。用于通道克隆 |
| Val() | double | 公式通道的实际值 |
| Val(n) | double | 通道n的实际值 |
| SetVal(n, val) | double | 设置通道n的当前值 |
| Stat() | int | 配方通道实际状态 |
| Stat(n) | int | 通道n的实际状态 |
| SetStat(n, stat) | int | 设置通道n的当前状态 |
| Data() | CnlData | 公式通道实际数据 |
| Data(n) | CnlData | n通道实际数据 |
| SetData(n, val, stat) | double | 设置通道n的当前值和状态 |
| SetData(n, cnlData) | double | 设置通道n的当前数据 |
| NewData(val, stat) | CnlData | 创建一个新的通道数据实例 |
| PrevVal() | double | 公式通道的前一个值 |
| PrevVal(n) | double | 通道 n 的先前值 |
| PrevStat() | int | 公式通道的先前状态 |
| PrevStat(n) | int | 通道 n 的先前状态 |
| PrevData() | CnlData | 公式通道历史数据 |
| PrevData(n) | CnlData | 通道n的历史数据 |
| Time() | DateTime | 公式通道的实际时间戳 |
| Time(n) | DateTime | 通道n的实际时间戳 |
| PrevTime() | DateTime | 公式通道的上一个时间戳 |
| PrevTime(n) | DateTime | 通道 n 的前一个时间戳 |
| Deriv(n) | double | 通道 n 的时间导数 |
项目模板中的函数
在基于标准模板创建的项目中,脚本表包含以下函数:
| 功能 | 数据类型 | 描述 |
|---|---|---|
| GetBit(val, n) | double | 返回指定值的第 n 位 |
| GetBit(cnlData, n) | CnlData | 返回一个通道数据,由第n位值和通道状态组成 |
| GetBits(val, n, len) | double | 返回给定值的指定位 |
| GetBits(cnlData, n, len) | CnlData | 返回由指定位的值和通道状态组成的通道数据 |
| SetBit(val, n, isOn) | double | 设置指定值的第n位 |
| SetBit(cnlData, n, isOn) | CnlData | 设置通道值的第n位,保持通道状态不变 |
| GetByte(val, n) | double | 返回指定值的第 n 个字节 |
| DataRel(offset) | CnlData | 相对于当前通道的通道数据 |
| SetData() | double | 根据命令值设置当前通道数据 |
| GetDefaultData(val) | CnlData | 获取计算通道的默认数据 |
| Now() | double | 当前日期和时间为浮点数。使用服务器时区 |
| UtcNow() | double | 当前日期和时间为浮点数。使用通用时区 (UTC) |
| UnixTime() | long | 当前 Unix 时间(以秒为单位) |
| EncodeDate( |
double | 将指定的日期和时间转换为Double类型的通道值 |
| DecodeDate(val) | DateTime | 将通道值转换为日期和时间 |
| EncodeAscii(s) | double | 将最长 8 个字符的 ASCII 字符串转换为浮点数 |
| EncodeUnicode(s) | double | 将最长 4 个字符的 Unicode 字符串转换为浮点数 |
| DecodeAscii(val) | string | 将指定值转换为最长 8 个字符的 ASCII 字符串 |
| DecodeUnicode( |
string | 将指定值转换为最长 4 个字符的 Unicode 字符串 |
| Substring(s, startIndex, length) | string | 通过边界检查从字符串中提取子字符串 |
| SplitAscii( |
string | 将 ASCII 字符串拆分为多个通道存储 |
| SplitUnicode( |
string | 将 Unicode 字符串拆分为多个通道存储 |
| EverySec( |
CnlData | 每秒执行指定的函数 |
| EveryMin( |
CnlData | 每分钟执行指定的函数 |
| EveryHour( |
CnlData | 每小时执行指定的函数 |
| CountPulse( |
double | 统计指定通道的脉冲数 |
| HourStarted() | bool | 表示新的一小时已经开始。每个通道的结果为真一次 |
| DayStarted() | bool | 预示着新的一天开始了。每个通道的结果为真一次 |
| MonthStarted() | bool | 预示着新的一个月已经开始。每个通道的结果为真一次 |
其他脚本,包括用于计算平均值的脚本,可在GitHub。
调试脚本
开发自己的脚本时,请遵循语法规则并检查脚本是否正常工作。如果Server服务启动时编译脚本失败,Server操作日志中会显示错误信息ScadaServer.log,编译后的源代码可以在CalcEngine.cs文件,位于服务器日志目录中。要开发复杂的公式,我们建议使用 Microsoft Visual Studio 或 Visual Studio Code。
公式示例
示例 1:从设备接收的通道值的线性变换。该公式用于通道输入类型。
10 * Cnl + 1
示例 2:通道 101 和 102 的值之和。状态从通道 101 中提取。该公式用于已计算类型。
Val(101) + Val(102); Stat(101)
示例3:用于计算类型通道的公式提取0th位来自通道 105 的数据。
GetBit(Data(105), 0)
示例 4:该公式每分钟将计数器增加 1,并在每小时开始时重置计数器。
EveryMin(() => HourStarted() ? 0 : Val() + 1)