Skip to main content

Script Development Reference

The script management feature supports creating, editing, and compiling C# expression scripts to extend the gateway's business logic, which can be used in scenarios such as variable read/write and data conversion.

Usage Note

Create, edit, compile, and save scripts from the Web page. A script can be referenced by variables, data forwarding, MQTT RPC, or rules only after it compiles successfully.

Feature Entry

In the gateway workspace, click "Script" in the left menu to enter the management page.

Script Management Page

Script List

ColumnDescription
NameThe identifier name of the script
CategoryThe category the script belongs to
TypeThe script type
Class NameThe script class name
Update TimeThe last modification time

List Features

FeatureDescription
PaginationSupports paginated display (20/50/100 items per page)
Multi-selectSupports checkbox multi-select for batch operations
Category FilterSupports filtering the script list by category
Type FilterSupports filtering by script type
SearchSupports searching scripts by name

Script Types

TypeDescription
Data ConversionData conversion processing after variable reading
Memory VariableData conversion processing for memory variables
Dynamic Model - Variable DataDynamic model variable data processing
Dynamic Model - Device DataDynamic model device data processing
Dynamic Model - Alarm VariableDynamic model alarm variable processing
Dynamic Model - Plugin EventDynamic model plugin event processing
Dynamic SQL - Variable DataDynamic SQL variable data processing
Dynamic SQL - Device DataDynamic SQL device data processing
Dynamic SQL - Alarm VariableDynamic SQL alarm variable processing
Dynamic SQL - Plugin EventDynamic SQL plugin event processing
MQTT RPCMQTT RPC script
Complete CodeComplete C# code script for advanced logic, not constrained by a specific base class

Script Examples

Data Conversion

Used for data conversion processing after variable reading, such as unit conversion, numerical calculation, etc.

Base Class: ExpressionDatatrans

Example: Temperature Unit Conversion (Kelvin to Celsius)

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);
}
}

Example: Value Range Mapping

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);
}
}

Memory Variable

Used for data conversion processing of memory variables, can reference values of other variables for calculation.

Base Class: MemoryVariableExpressionDatatrans

Example: Multi-variable Summation

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;
}
}

Example: Average Value Calculation

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;
}
}

Dynamic Model - Variable Data

Used to convert variable data into a custom model structure, commonly used for data reporting format conversion.

Base Class: DynamicModelBase<VariableBasicData>

Example: Simplified Variable Model

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
};
}
}
}

Dynamic Model - Device Data

Used to convert device data into a custom model structure.

Base Class: DynamicModelBase<DeviceBasicData>

Example: Device Status Summary

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
};
}
}
}

Dynamic Model - Alarm Variable

Used to convert alarm data into a custom model structure.

Base Class: DynamicModelBase<AlarmVariable>

Example: Alarm Summary Model

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
};
}
}
}

Dynamic Model - Plugin Event

Used to convert plugin event data into a custom model structure.

Base Class: DynamicModelBase<PluginEventData>

Example: Plugin Event Model

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")
};
}
}
}

Dynamic SQL - Variable Data

Used to save variable data to the database, supports custom table structure and storage logic.

Base Class: DynamicSQLBase<VariableBasicData>

Example: Variable Data Storage (Column-to-Row)

Converts multiple rows of variable data into a single row storage, with variable names as column names.

Original Data Format:

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

Database Table Structure:

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

Used to handle MQTT RPC requests, implementing custom RPC logic.

Base Class: MQTTDynamicRPCBase

Example: Custom RPC Handling

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);
}
}
}

New Script

Click the "New Script" button, fill in the script name, script type, and description (optional).

New Script Dialog

New Script Fields

FieldDescriptionValidation Rules
Script NameThe identifier name of the scriptRequired
Script TypeSelect the script typeRequired, dropdown selection
CategoryThe category the script belongs toOptional, used for organizing and filtering scripts
DescriptionDescription of the script functionOptional

Base Class Description

After selecting the script type, the bottom of the dialog dynamically displays the corresponding base class description to help developers understand the script writing requirements:

Script TypeBase Class Description
Data ConversionInherits ExpressionDatatrans, implements the Execute method
Memory VariableInherits MemoryVariable, implements the Execute method
Dynamic Model - Variable DataInherits DynamicModelVariable, implements the Execute method
Dynamic Model - Device DataInherits DynamicModelDevice, implements the Execute method
Dynamic Model - Alarm VariableInherits DynamicModelAlarmVariable, implements the Execute method
Dynamic Model - Plugin EventInherits DynamicModelPluginEvent, implements the Execute method
Dynamic SQL - Variable DataInherits DynamicSqlVariable, implements the Execute method
Dynamic SQL - Device DataInherits DynamicSqlDevice, implements the Execute method
Dynamic SQL - Alarm VariableInherits DynamicSqlAlarmVariable, implements the Execute method
Dynamic SQL - Plugin EventInherits DynamicSqlPluginEvent, implements the Execute method
MQTT RPCInherits MqttRpcScript, implements the Execute method
Full Source CodeInherits FullSourceBase, implements the Execute method
Tip

The base class description area is automatically updated based on the selected script type. Please refer to the base class description when writing script code.

Edit Script

Click the "Edit" button to enter the script editor:

Script Editor Page

Editor Features

FeatureDescription
Code EditingUse Monaco editor to write C# code
Parameter ConfigurationConfigure input parameters for the script (only supported by data conversion and memory variable types)
CompileCheck the script code and generate a usable script result
SaveSave the script configuration and code

Code Editor

Use Monaco editor to write C# code, supporting the following features:

FeatureDescriptionShortcut
Syntax HighlightingC# syntax highlighting-
IntelliSenseCode auto-completion, triggered by typing . or space-
Syntax CheckingReal-time syntax checking, errors and warnings marked with wavy underlines in the editor-
Hover TipsMouse hover displays type information and documentation-
Signature HelpShows parameter signatures when typing method parentheses-
Quick FixTrigger code fix suggestionsCtrl+.
Undo/RedoUndo and redo operationsCtrl+Z / Ctrl+Y
Toggle CommentComment/uncomment the current lineCtrl+/
SaveSave the script to the databaseCtrl+S
FormatFormat codeCtrl+K, Ctrl+F
Copy LineCopy the current line downCtrl+D
Delete LineDelete the current lineCtrl+Shift+K
Go to DefinitionJump to symbol definitionF12

Input Parameter Configuration

Only Data Conversion and Memory Variable type scripts support custom input parameters:

PropertyDescription
Parameter NameThe identifier name of the parameter
Data TypeThe data type of the parameter
Initial ValueThe default value of the parameter
DescriptionDescription of the parameter function

Compilation Log

The compilation log panel is located on the right side of the editor, displaying detailed information and results of the compilation process.

Script Editor - Compilation Log Panel

Compilation Result

After a script compiles successfully, the runtime loads it and the script becomes available to the related variable, forwarding, MQTT RPC, or rule configuration.

Log Header

The top of the log panel displays statistics:

InformationDescription
Error CountDisplayed in red, indicates the number of compilation errors
Warning CountDisplayed in orange, indicates the number of compilation warnings

Log Entry Types

TypeIconBackground ColorDescription
ErrorRed crossRed backgroundCompilation error, must be fixed for successful compilation
WarningOrange exclamation markOrange backgroundCompilation warning, recommended to fix but does not block compilation
SuccessGreen checkmarkGreen backgroundCompilation success message
InfoGray iNo backgroundCompilation process information (e.g., compilation progress, script type, etc.)

Compilation Process Log

The following log information is displayed in order during compilation:

  1. Start Compilation - Start compiling script: {script name}
  2. Script Type - Script type: {type name}
  3. Compilation Result - Compilation success/failure
  4. Load Result - Whether the script has been loaded by the runtime
  5. Diagnostic Information - Error and warning details, including line and column numbers when available

Click to Jump

Click to Jump to Code Line

Click on error or warning entries in the compilation log, and the editor will automatically jump to the corresponding code line and highlight the error position, making it easy to quickly locate and fix issues.

Real-time Syntax Checking

In addition to compilation logs, the editor also automatically performs real-time syntax checking when code is modified (approximately 500ms delay). The check results will:

  • Mark error and warning positions with wavy underlines in the editor
  • Be displayed synchronously in the compilation log panel
  • Support clicking to jump to the corresponding code line

Compile and Validate Scripts

Manual Compilation

Use the compile button before putting a script into production. Handle errors first, then review warnings.

Check ItemDescription
Error CountMust be 0 before the script can be used normally
Warning CountDoes not always block compilation, but should be reviewed before release
Load ResultConfirms whether the compiled script has been loaded by the runtime
Reference CheckConfirm that variables, data forwarding, MQTT RPC, or rules reference the expected script name

After Deployment

After deployment or upgrade, open the script list and compile important scripts again if the page shows errors, missing load results, or changed dependencies.

Deployment Package Limitations

Some deployment packages may not support dynamic compilation on the target device. In this case, complete script verification before publishing the project.

Action Buttons

ActionDescription
EditOpen the script editor
CompileCompile a single script
DeleteDelete the script
Batch CompileCompile multiple selected scripts
Batch DeleteDelete multiple selected scripts