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.
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 List
| Column | Description |
|---|---|
| Name | The identifier name of the script |
| Category | The category the script belongs to |
| Type | The script type |
| Class Name | The script class name |
| Update Time | The last modification time |
List Features
| Feature | Description |
|---|---|
| Pagination | Supports paginated display (20/50/100 items per page) |
| Multi-select | Supports checkbox multi-select for batch operations |
| Category Filter | Supports filtering the script list by category |
| Type Filter | Supports filtering by script type |
| Search | Supports searching scripts by name |
Script Types
| Type | Description |
|---|---|
| Data Conversion | Data conversion processing after variable reading |
| Memory Variable | Data conversion processing for memory variables |
| Dynamic Model - Variable Data | Dynamic model variable data processing |
| Dynamic Model - Device Data | Dynamic model device data processing |
| Dynamic Model - Alarm Variable | Dynamic model alarm variable processing |
| Dynamic Model - Plugin Event | Dynamic model plugin event processing |
| Dynamic SQL - Variable Data | Dynamic SQL variable data processing |
| Dynamic SQL - Device Data | Dynamic SQL device data processing |
| Dynamic SQL - Alarm Variable | Dynamic SQL alarm variable processing |
| Dynamic SQL - Plugin Event | Dynamic SQL plugin event processing |
| MQTT RPC | MQTT RPC script |
| Complete Code | Complete 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:
| Name | Value | CollectTime |
|---|---|---|
| Temperature | 25.5 | 2024-01-01 10:00:00 |
| Humidity | 60 | 2024-01-01 10:00:00 |
Database Table Structure:
| CollectTime | Temperature | Humidity |
|---|---|---|
| 2024-01-01 10:00:00 | 25.5 | 60 |
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 Fields
| Field | Description | Validation Rules |
|---|---|---|
| Script Name | The identifier name of the script | Required |
| Script Type | Select the script type | Required, dropdown selection |
| Category | The category the script belongs to | Optional, used for organizing and filtering scripts |
| Description | Description of the script function | Optional |
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 Type | Base Class Description |
|---|---|
| Data Conversion | Inherits ExpressionDatatrans, implements the Execute method |
| Memory Variable | Inherits MemoryVariable, implements the Execute method |
| Dynamic Model - Variable Data | Inherits DynamicModelVariable, implements the Execute method |
| Dynamic Model - Device Data | Inherits DynamicModelDevice, implements the Execute method |
| Dynamic Model - Alarm Variable | Inherits DynamicModelAlarmVariable, implements the Execute method |
| Dynamic Model - Plugin Event | Inherits DynamicModelPluginEvent, implements the Execute method |
| Dynamic SQL - Variable Data | Inherits DynamicSqlVariable, implements the Execute method |
| Dynamic SQL - Device Data | Inherits DynamicSqlDevice, implements the Execute method |
| Dynamic SQL - Alarm Variable | Inherits DynamicSqlAlarmVariable, implements the Execute method |
| Dynamic SQL - Plugin Event | Inherits DynamicSqlPluginEvent, implements the Execute method |
| MQTT RPC | Inherits MqttRpcScript, implements the Execute method |
| Full Source Code | Inherits FullSourceBase, implements the Execute method |
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:

Editor Features
| Feature | Description |
|---|---|
| Code Editing | Use Monaco editor to write C# code |
| Parameter Configuration | Configure input parameters for the script (only supported by data conversion and memory variable types) |
| Compile | Check the script code and generate a usable script result |
| Save | Save the script configuration and code |
Code Editor
Use Monaco editor to write C# code, supporting the following features:
| Feature | Description | Shortcut |
|---|---|---|
| Syntax Highlighting | C# syntax highlighting | - |
| IntelliSense | Code auto-completion, triggered by typing . or space | - |
| Syntax Checking | Real-time syntax checking, errors and warnings marked with wavy underlines in the editor | - |
| Hover Tips | Mouse hover displays type information and documentation | - |
| Signature Help | Shows parameter signatures when typing method parentheses | - |
| Quick Fix | Trigger code fix suggestions | Ctrl+. |
| Undo/Redo | Undo and redo operations | Ctrl+Z / Ctrl+Y |
| Toggle Comment | Comment/uncomment the current line | Ctrl+/ |
| Save | Save the script to the database | Ctrl+S |
| Format | Format code | Ctrl+K, Ctrl+F |
| Copy Line | Copy the current line down | Ctrl+D |
| Delete Line | Delete the current line | Ctrl+Shift+K |
| Go to Definition | Jump to symbol definition | F12 |
Input Parameter Configuration
Only Data Conversion and Memory Variable type scripts support custom input parameters:
| Property | Description |
|---|---|
| Parameter Name | The identifier name of the parameter |
| Data Type | The data type of the parameter |
| Initial Value | The default value of the parameter |
| Description | Description 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.

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:
| Information | Description |
|---|---|
| Error Count | Displayed in red, indicates the number of compilation errors |
| Warning Count | Displayed in orange, indicates the number of compilation warnings |
Log Entry Types
| Type | Icon | Background Color | Description |
|---|---|---|---|
| Error | Red cross | Red background | Compilation error, must be fixed for successful compilation |
| Warning | Orange exclamation mark | Orange background | Compilation warning, recommended to fix but does not block compilation |
| Success | Green checkmark | Green background | Compilation success message |
| Info | Gray i | No background | Compilation process information (e.g., compilation progress, script type, etc.) |
Compilation Process Log
The following log information is displayed in order during compilation:
- Start Compilation -
Start compiling script: {script name} - Script Type -
Script type: {type name} - Compilation Result - Compilation success/failure
- Load Result - Whether the script has been loaded by the runtime
- Diagnostic Information - Error and warning details, including line and column numbers when available
Click to Jump
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 Item | Description |
|---|---|
| Error Count | Must be 0 before the script can be used normally |
| Warning Count | Does not always block compilation, but should be reviewed before release |
| Load Result | Confirms whether the compiled script has been loaded by the runtime |
| Reference Check | Confirm 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
| Action | Description |
|---|---|
| Edit | Open the script editor |
| Compile | Compile a single script |
| Delete | Delete the script |
| Batch Compile | Compile multiple selected scripts |
| Batch Delete | Delete multiple selected scripts |
Related Links
- Data Management - Channel, device, variable configuration
- Custom Node - Custom node management and development
- Rule Engine - Rule process orchestration