跳到主要内容

脚本管理

脚本管理功能支持创建、编辑和编译 C# 表达式脚本,用于扩展网关的业务逻辑,可在变量读写、数据转换等环节使用。

功能入口

在工程开发页面,点击左侧菜单"脚本"进入管理页面。

Studio脚本管理页面

图1:脚本管理页面

脚本列表

说明
名称脚本的标识名称
分类脚本所属分类
类型脚本类型
类名脚本类名
更新时间最后修改时间

列表功能

功能说明
分页支持分页显示(20/50/100条/页)
多选支持复选框多选,用于批量操作
分类筛选支持按分类筛选脚本列表

脚本类型

类型说明
数据转换变量读取后的数据转换处理
内存变量内存变量的数据转换处理
动态模型-变量数据动态模型变量数据处理
动态模型-设备数据动态模型设备数据处理
动态模型-告警变量动态模型告警变量处理
动态模型-插件事件动态模型插件事件处理
动态SQL-变量数据动态SQL变量数据处理
动态SQL-设备数据动态SQL设备数据处理
动态SQL-告警变量动态SQL告警变量处理
动态SQL-插件事件动态SQL插件事件处理
MQTT RPCMQTT RPC 脚本

脚本示例

数据转换

用于变量读取后的数据转换处理,如单位转换、数值计算等。

基类: ExpressionDatatrans

示例:温度单位转换(开尔文转摄氏度)

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;

public class KelvinToCelsius : ExpressionDatatrans
{
public override string Name => "KelvinToCelsius";

public override object Execute(object raw, Logger? logger)
{
if (raw == null) return 0;

double kelvin = Convert.ToDouble(raw);
double celsius = kelvin - 273.15;

return Math.Round(celsius, 2);
}
}

示例:数值范围映射

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;

public class RangeMapping : ExpressionDatatrans
{
public override string Name => "RangeMapping";

// 输入参数:原始范围最小值
public double InputMin { get; set; } = 0;
// 输入参数:原始范围最大值
public double InputMax { get; set; } = 100;
// 输入参数:目标范围最小值
public double OutputMin { get; set; } = 0;
// 输入参数:目标范围最大值
public double OutputMax { get; set; } = 10;

public override object Execute(object raw, Logger? logger)
{
if (raw == null) return 0;

double value = Convert.ToDouble(raw);

// 线性映射公式
double result = OutputMin + (value - InputMin) * (OutputMax - OutputMin) / (InputMax - InputMin);

return Math.Round(result, 2);
}
}

内存变量

用于内存变量的数据转换处理,可引用其他变量的值进行计算。

基类: MemoryVariableExpressionDatatrans

示例:多变量求和

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;

public class SumVariables : MemoryVariableExpressionDatatrans
{
public override string Name => "SumVariables";

public override object Execute(object raw, Logger? logger)
{
// 引用其他变量的值
var var1 = Tag("Device1", "Temperature1");
var var2 = Tag("Device1", "Temperature2");
var var3 = Tag("Device1", "Temperature3");

double sum = 0;
if (var1.Value != null) sum += Convert.ToDouble(var1.Value);
if (var2.Value != null) sum += Convert.ToDouble(var2.Value);
if (var3.Value != null) sum += Convert.ToDouble(var3.Value);

return sum;
}
}

示例:平均值计算

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;

public class AverageValue : MemoryVariableExpressionDatatrans
{
public override string Name => "AverageValue";

public override object Execute(object raw, Logger? logger)
{
var var1 = Tag("Device1", "Sensor1");
var var2 = Tag("Device1", "Sensor2");
var var3 = Tag("Device1", "Sensor3");

double sum = 0;
int count = 0;

if (var1.IsOnline && var1.Value != null) { sum += Convert.ToDouble(var1.Value); count++; }
if (var2.IsOnline && var2.Value != null) { sum += Convert.ToDouble(var2.Value); count++; }
if (var3.IsOnline && var3.Value != null) { sum += Convert.ToDouble(var3.Value); count++; }

return count > 0 ? Math.Round(sum / count, 2) : 0;
}
}

动态模型-变量数据

用于将变量数据转换为自定义模型结构,常用于数据上报格式转换。

基类: DynamicModelBase<VariableBasicData>

示例:简化变量模型

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;

public class SimpleVariableModel : DynamicModelBase<VariableBasicData>
{
public override string Name => "SimpleVariableModel";

public override IEnumerable<object> GetList(IEnumerable<VariableBasicData> datas, Logger? logger)
{
foreach (var data in datas)
{
yield return new
{
Name = data.Name,
Value = data.Value,
Time = data.CollectTime.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Online = data.IsOnline ? 1 : 0
};
}
}
}

动态模型-设备数据

用于将设备数据转换为自定义模型结构。

基类: DynamicModelBase<DeviceBasicData>

示例:设备状态汇总

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;

public class DeviceStatusModel : DynamicModelBase<DeviceBasicData>
{
public override string Name => "DeviceStatusModel";

public override IEnumerable<object> GetList(IEnumerable<DeviceBasicData> datas, Logger? logger)
{
foreach (var device in datas)
{
yield return new
{
DeviceName = device.Name,
Status = device.DeviceStatus.ToString(),
ActiveTime = device.ActiveTime.ToString("yyyy-MM-dd HH:mm:ss"),
PluginName = device.PluginName,
LastError = device.LastErrorMessage
};
}
}
}

动态模型-告警变量

用于将告警数据转换为自定义模型结构。

基类: DynamicModelBase<AlarmVariable>

示例:告警汇总模型

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;

public class AlarmSummaryModel : DynamicModelBase<AlarmVariable>
{
public override string Name => "AlarmSummaryModel";

public override IEnumerable<object> GetList(IEnumerable<AlarmVariable> datas, Logger? logger)
{
foreach (var alarm in datas)
{
yield return new
{
AlarmId = alarm.VariableId,
VariableName = alarm.Name,
DeviceName = alarm.DeviceName,
AlarmLevel = alarm.AlarmLevel,
AlarmText = alarm.AlarmText ?? alarm.AlarmCode,
AlarmTime = alarm.AlarmTime.ToString("yyyy-MM-dd HH:mm:ss"),
EventType = alarm.EventType.ToString(),
IsRecovered = alarm.FinishTime != default,
RecoveryTime = alarm.FinishTime != default
? alarm.FinishTime.ToString("yyyy-MM-dd HH:mm:ss")
: null
};
}
}
}

动态模型-插件事件

用于将插件事件数据转换为自定义模型结构。

基类: DynamicModelBase<PluginEventData>

示例:插件事件模型

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;

public class PluginEventModel : DynamicModelBase<PluginEventData>
{
public override string Name => "PluginEventModel";

public override IEnumerable<object> GetList(IEnumerable<PluginEventData> datas, Logger? logger)
{
foreach (var eventData in datas)
{
yield return new
{
DeviceName = eventData.DeviceName,
ValueType = eventData.ValueType,
Value = eventData.ObjectValue?.ToString(),
EventTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
}
}
}

动态SQL-变量数据

用于将变量数据保存到数据库,支持自定义表结构和存储逻辑。

基类: DynamicSQLBase<VariableBasicData>

示例:变量数据存储(列转行)

将多行变量数据转换为单行存储,变量名作为列名。

原始数据格式:

NameValueCollectTime
Temperature25.52024-01-01 10:00:00
Humidity602024-01-01 10:00:00

数据库表结构:

CollectTimeTemperatureHumidity
2024-01-01 10:00:0025.560
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using TORM;
using System.Collections.Generic;

public class VariableSqlPivot : DynamicSQLBase<VariableBasicData>
{
public override string Name => "VariableSqlPivot";

private static readonly Dictionary<string, string> ColumnMapping = new()
{
["Temperature"] = "Temperature",
["Humidity"] = "Humidity",
["Pressure"] = "Pressure"
};

public override async Task DBInit(OrmClient db, Logger? logger, CancellationToken cancellationToken)
{
var sql = @"
CREATE TABLE IF NOT EXISTS VariableDataPivot (
Id BIGINT PRIMARY KEY AUTO_INCREMENT,
CollectTime DATETIME NOT NULL,
Temperature DOUBLE,
Humidity DOUBLE,
Pressure DOUBLE,
INDEX IX_CollectTime (CollectTime)
)";
await db.Ado.ExecuteNonQueryAsync(sql, null, cancellationToken);
}

public override async Task<int> DBDeleteable(OrmClient db, int days, Logger? logger, CancellationToken cancellationToken)
{
var cutoffDate = DateTime.UtcNow.AddDays(-days);
var param = db.Ado.CreateParameter("cutoffDate", cutoffDate);
return await db.Ado.ExecuteNonQueryAsync(
"DELETE FROM VariableDataPivot WHERE CollectTime < @cutoffDate",
[param], cancellationToken);
}

public override async Task DBSaveable(OrmClient db, IEnumerable<VariableBasicData> datas, Logger? logger, CancellationToken cancellationToken)
{
var grouped = datas.GroupBy(d => d.CollectTime);

foreach (var group in grouped)
{
var collectTime = group.Key;
var row = new Dictionary<string, object?>
{
["CollectTime"] = collectTime
};

foreach (var variable in group)
{
if (ColumnMapping.TryGetValue(variable.Name, out var columnName))
{
row[columnName] = variable.Value != null ? Convert.ToDouble(variable.Value) : null;
}
}

var parameters = new[]
{
db.Ado.CreateParameter("CollectTime", row["CollectTime"]!),
db.Ado.CreateParameter("Temperature", row["Temperature"] ?? DBNull.Value),
db.Ado.CreateParameter("Humidity", row["Humidity"] ?? DBNull.Value),
db.Ado.CreateParameter("Pressure", row["Pressure"] ?? DBNull.Value)
};

await db.Ado.ExecuteNonQueryAsync(
"INSERT INTO VariableDataPivot (CollectTime, Temperature, Humidity, Pressure) VALUES (@CollectTime, @Temperature, @Humidity, @Pressure)",
parameters, cancellationToken);
}
}
}

MQTT RPC

用于处理MQTT RPC请求,实现自定义的RPC逻辑。

基类: MQTTDynamicRPCBase

示例:自定义RPC处理

using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using TUtility;
using TUtility.Json.Extension;
using TouchSocket.Mqtt;
using System.Text.Json;

public class CustomMqttRpc : MQTTDynamicRPCBase
{
public override string Name => "CustomMqttRpc";

public override async Task RPCInvokeAsync(
MqttArrivedMessage message,
Func<TopicArray, CancellationToken, Task> publish,
Func<Dictionary<string, Dictionary<string, JsonElement>>, ValueTask<Dictionary<string, Dictionary<string, IOperResult>>>> getRpcResult,
Logger? logger = null,
CancellationToken cancellationToken = default)
{
TopicArray topicArray = new();
try
{
var request = message.Payload.FromSystemTextJsonString<Dictionary<string, JsonElement>>();
if (request == null) return;

logger?.LogInformation($"收到RPC请求");

var rpcRequest = new Dictionary<string, Dictionary<string, JsonElement>>
{
["Device1"] = new Dictionary<string, JsonElement>
{
["Temperature"] = request["value"]
}
};

var result = await getRpcResult(rpcRequest);

var response = new Dictionary<string, object?>
{
["requestId"] = request.TryGetValue("requestId", out var reqId) ? reqId.ToString() : null,
["success"] = result.All(r => r.Value.All(v => v.Value.IsSuccess)),
["message"] = "执行完成",
["timestamp"] = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")
};

topicArray.Topic = "gateway/rpc/write/Response";
topicArray.Payload = response.ToSystemTextJsonUtf8Bytes();
await publish(topicArray, cancellationToken);
}
catch (Exception ex)
{
logger?.LogError($"RPC处理失败: {ex.Message}");

var errorResponse = new Dictionary<string, object?>
{
["success"] = false,
["error"] = ex.Message,
["timestamp"] = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")
};

topicArray.Topic = "gateway/rpc/write/Response";
topicArray.Payload = errorResponse.ToSystemTextJsonUtf8Bytes();
await publish(topicArray, cancellationToken);
}
}
}

新建脚本

点击"新建脚本"按钮,填写脚本名称、脚本类型、描述(可选)。

Studio新建脚本对话框

图2:新建脚本对话框

新建脚本字段

字段说明校验规则
脚本名称脚本标识名称必填
脚本类型选择脚本类型必填,下拉选择
分类脚本所属分类可选,用于组织和筛选脚本
描述脚本功能说明可选

基类说明

选择脚本类型后,对话框底部会动态显示对应的基类说明,帮助开发者了解脚本编写要求:

脚本类型基类说明
数据转换继承 ExpressionDatatrans,实现 Execute 方法
内存变量继承 MemoryVariable,实现 Execute 方法
动态模型-变量数据继承 DynamicModelVariable,实现 Execute 方法
动态模型-设备数据继承 DynamicModelDevice,实现 Execute 方法
动态模型-告警变量继承 DynamicModelAlarmVariable,实现 Execute 方法
动态模型-插件事件继承 DynamicModelPluginEvent,实现 Execute 方法
动态SQL-变量数据继承 DynamicSqlVariable,实现 Execute 方法
动态SQL-设备数据继承 DynamicSqlDevice,实现 Execute 方法
动态SQL-告警变量继承 DynamicSqlAlarmVariable,实现 Execute 方法
动态SQL-插件事件继承 DynamicSqlPluginEvent,实现 Execute 方法
MQTT RPC继承 MqttRpcScript,实现 Execute 方法
提示

基类说明区域会根据选择的脚本类型自动更新,请参考基类说明编写脚本代码。

编辑脚本

点击"编辑"按钮进入脚本编辑器:

Studio脚本编辑器页面

图3:脚本编辑器页面

编辑器功能

功能说明
代码编辑使用 Monaco 编辑器编写 C# 代码
参数配置配置脚本的输入参数(仅数据转换和内存变量类型支持)
编译编译脚本代码
保存保存脚本文件

代码编辑器

使用 Monaco 编辑器编写 C# 代码,支持以下功能:

功能说明快捷键
语法高亮C# 语法高亮显示-
智能提示代码自动补全,输入 . 或空格触发-
语法检查实时语法检查,错误和警告在编辑器中波浪线标注-
悬浮提示鼠标悬浮显示类型信息和文档-
签名帮助输入方法括号时显示参数签名-
快速修复触发代码修复建议Ctrl+.
撤销/重做撤销和重做操作Ctrl+Z / Ctrl+Y
注释切换注释/取消注释当前行Ctrl+/
保存保存脚本Ctrl+S
格式化格式化代码Ctrl+K, Ctrl+F
复制行向下复制当前行Ctrl+D
删除行删除当前行Ctrl+Shift+K
转到定义跳转到符号定义F12

输入参数配置

数据转换内存变量类型的脚本支持自定义输入参数:

属性说明
参数名参数的标识名称
数据类型参数的数据类型
初始值参数的默认值
描述参数的功能说明

编译日志

编译日志面板位于编辑器右侧,显示编译过程的详细信息和结果。

Studio脚本编辑器-编译日志面板

图4:脚本编辑器-编译日志面板

日志头部

日志面板顶部显示统计信息:

信息说明
错误数红色显示,表示编译错误数量
警告数橙色显示,表示编译警告数量

日志条目类型

类型图标背景色说明
错误红色叉号红色背景编译错误,必须修复才能编译成功
警告橙色感叹号橙色背景编译警告,建议修复但不阻止编译
成功绿色勾号绿色背景编译成功信息
信息灰色 i无背景编译过程信息(如编译进度、脚本类型等)

编译流程日志

编译时按顺序显示以下日志信息:

  1. 开始编译 - 开始编译脚本: {脚本名称}
  2. 脚本类型 - 脚本类型: {类型名称}
  3. 编译结果 - 编译成功/失败
  4. 输出路径 - 编译成功时显示 DLL 路径
  5. 诊断信息 - 编译器返回的错误和警告详情(含行号和列号)

点击跳转

点击跳转到代码行

点击编译日志中的错误警告条目,编辑器会自动跳转到对应的代码行,并高亮选中出错的位置,方便快速定位和修复问题。

实时语法检查

除了编译时的日志,编辑器还会在代码修改时自动进行实时语法检查(约500ms延迟),检查结果会:

  • 在编辑器中以波浪线标注错误和警告位置
  • 同步显示在编译日志面板中
  • 支持点击跳转到对应代码行

操作按钮

操作说明
编辑打开脚本编辑器
编译编译单个脚本
删除删除脚本
批量编译编译选中的多个脚本
批量删除删除选中的多个脚本

相关链接