Collection Plugin Development
This document is for developers extending ThingsGatewayRuntime collection protocols. A collection plugin reads PLCs, instruments, sensors, supervisory systems, or virtual data sources into runtime variables and, when permitted, writes external commands or RPC requests back to field devices.
To extend data forwarding, protocol servers, database writers, or cloud integrations, see Business Plugin Development.
Source Code Entry
| Entry | Function |
|---|---|
ThingsGatewayRuntime.Application/Driver/IDriver.cs | All devices drive the runtime interface. |
ThingsGatewayRuntime.Application/Driver/DriverBase.cs | Device plugin lifecycle, logs, task scheduling, channel mounting, release logic. |
ThingsGatewayRuntime.Application/Driver/Collect/CollectBase.cs | Collects the core base class of the plugin, responsible for variable packaging, scheduled reads, script variables, method variables, and write/RPC workflows. |
ThingsGatewayRuntime.Application/Driver/Collect/CollectFoundationBase.cs | A master protocol template based on Foundation IDevice. |
ThingsGatewayRuntime.Application/Driver/Collect/CollectReceivedFoundationBase.cs | Based on Foundation IReceivedDevice passive receiver class protocol template. |
ThingsGatewayRuntime.Application/Task/Collect/DeviceManage/DeviceThreadManage.cs | Device plugin creation, property injection, channel initialization, Scheduling entry for starting task cycles. |
ThingsGatewayRuntime.Application/Service/Plugin/PluginService.cs | scans CollectBase Derived classes and generate a list of plugins. |
ThingsGatewayRuntime.Plugin/Plugin/* | Implemented as an open-source collection plugin. |
ThingsGatewayRuntime.NOAOTPlugin/Plugin/* | Non-AOT collection plugin implementations, such as OPC UA and OPC DA. |
ThingsGatewayRuntimePRO/src/ThingsGatewayRuntime.NOAOTPROPlugin/Plugin/* | Professional Edition Collection Plugin implementation. |
Process Overview
Runtime processing of the collection plugin in the following order:
PluginServicescans all non-abstractCollectBasederived classes to form a plugin list.- When the device starts,
DeviceThreadManage.CreateDrivercreate instances using the full plugin name. DriverBase.InitDevicemountsDeviceRuntime, logs, anddevice.Driver.PluginServiceUtil.SetDriverPropertiesWrites the device plugin attribute dictionary back to the strong-type property object.DriverBase.InitChannelAsyncDriverBase.InitChannelAsync and callAfterVariablesChangedAsyncpackage variables.DriverBase.StartAsyncCall the plugin'sProtectedStartAsync, then create and startTaskSchedulerLoop.CollectBaseRead at variable intervals, retry on failure, go online after success, offline after failure.- External write/RPC enters
InvokeWriteAsyncorInvokeMethodAsync, and the plugin drops to the protocol write. - When stopping the device, call
StopAsync, eventually enteringSafetyDisposeAsyncrelease channels, underlying protocol objects, logs, and locks.
Plugin Discovery Rules
Acquisition plugins must meet these conditions to appear in the collection device configuration.
| Rules | Explanation |
|---|---|
Inheritance CollectBase | PluginService only recognizes non-abstract derivative classes of CollectBase as collection plugins. |
| Exposing the parameterless constructor | Create instances at runtime via Activator.CreateInstance; No parameterless construct will fail to start. |
| Do not connect to the field device in the constructor | The constructor does not yet have device properties, channels, logs, and cancel tokens. Connections should be placed in InitChannelAsync or ProtectedStartAsync. |
| The full name of the plugin is the configuration key | The device entity stores type FullName. Renaming the namespace or class name affects the old configuration. |
[DisplayNonePlugin] hides the plugin | MemoryDriver uses this tag as an internal memory device template and is not displayed as a regular field plugin. |
[OnlyWindowsSupport] will restrict platform | plugins with this property will not be displayed in non-Windows environments. |
NOAOT assembly does not support AOT | PluginService Supports SupportsAot, OPC/COM according to the assembly name label and similar plugins are usually located in the NOAOT assembly. |
Base Class Selection
| Base Class | Suitable Scenario | Must Be Prioritized | Existing References |
|---|---|---|---|
CollectFoundationBase | Protocol can be abstracted into byte read/write bytes by address, such as Modbus, S7, DLT645, Omron, Melsec. | FoundationDevice, CollectProperties, DriverPropertyType, InitChannelAsync, ProtectedLoadSourceReadAsync. Usually, you don't need to rewrite ReadSourceAsync. | ModbusMaster, SiemensS7Master, ControlLogixMaster. |
CollectReceivedFoundationBase | Data is actively reported by the peer or maintained by the underlying device; normal scheduled reads do not apply. | FoundationReceivedDevice, property types, reporting event mapping, and overriding AfterVariablesChangedAsync if necessary. | HJ212Master, EDPF_NTMaster, KELID2008Master, LKSISMaster. |
CollectBase | Custom protocols, subscription protocols, messaging protocols, virtual devices, scanners, CAN, OPC, IEC61850, ZeroMQ. | ProtectedLoadSourceReadAsync,ReadSourceAsync,WriteValuesAsync,IsConnected, and rewrite if necessaryProtectedStartAsync, AfterVariablesChangedAsync. | OpcUaMaster, CanMaster, IEC61850Master, MemoryDriver, MqttCollectBase. |
MqttCollectBase | MQTT client/server collection, variable values are updated by topic messages. | Derived classes handle connections, subscriptions, message reception, and online checks; The parent class is responsible for updating values based on variable relationships. | MqttCollectClient, MqttCollectServer. |
D on't inherit the base class from the higher layer just to "save code." If you can use CollectFoundationBase avoid handwriting read/write locks, retrys, and byte parsing; You must use CollectBase when you need to customize connections and subscriptions.
Attribute Model
Plugin attributes are divided into two layers: base class attributes handle common behavior, and specific attribute classes handle protocol parameters.
| Type | Function |
|---|---|
DriverPropertyBase | All plugin attribute root types. Only properties marked [DynamicProperty] are exposed to frontends, import/export, and runtime property injection. |
CollectPropertyBase | Collects attribute root types, including internal fields such as concurrency, offline recovery interval, retry, read/write duty cycle, and write priority. |
CollectPropertyRetryBase | Exposure Failed Retries,Read/Write Duty Cycle, Write Priority. |
CollectFoundationPropertyBase | Increase read/write timeout,frame time,,String inverted bytes, data parsing order. |
CollectFoundationPackPropertyBase | Increases maximum packing length by. |
CollectFoundationDtuPropertyBase | adds DTU ID. |
CollectFoundationDtuPackPropertyBase | Includes both the packing length and DTU ID. |
CollectPropertyNone | an internal plugin property without page attributes, such as MemoryDriver. |
When implementing attributes, make sure that the following two members point to the same attribute type example:
private readonly MyDriverProperty _driverProperties = new();
public override CollectPropertyBase CollectProperties => _driverProperties;
public override Type DriverPropertyType { get; } = typeof(MyDriverProperty);
DriverPropertyType is used for dynamic frontend forms, Excel import/export, and property deserialization; CollectProperties is the object actually read at runtime. If they do not match, the fields shown on the page will be misaligned with the fields used by the plugin.
DynamicProperty Agreement
| Project | Description |
|---|---|
Description | The page displays the name, which is also the default Chinese name when the localized resource is not configured. |
Remark | Page prompt instructions, address format, organization, and notes that can be written. |
GroupName | Frontend group name, recommended for many attributes. |
ExpressionType | Indicates script input type, commonly used for dynamic models and expression editors. |
CertificatePurpose | Identifies the purpose of selecting certificates; the frontend provides dropdown options from certificate management. |
| Enumeration Properties | It is recommended to add JsonStringEnumConverter<T> or ensure the enumeration items have entered the source generation cache to avoid inconsistent import and export and JSON display. |
| Default Values | New frontend configuration will read the default value of the property instance, which must be a conservative value that can be used directly in the test environment. |
Properties without [DynamicProperty] will not be saved by the page nor injected from the device property dictionary. Do not label runtime caches, connection objects, locks, and queues as dynamic properties.
Lifecycle Hooks
| Phase | Runtime Actions | Plugin Development Notes |
|---|---|---|
| Constructor | Activator.CreateInstance Create an instance. | Only initialize lightweight fields, do not read configurations, do not connect devices, and do not start threads. |
InitDevice | Settings CurrentDevice, Logs, device.Driver, then call ProtectedInitDevice. | To read the device's operating state, it can be accessed after ProtectedInitDevice; The attribute value has not yet been injected. |
| Property injection | PluginServiceUtil.SetDriverProperties Writes the [DynamicProperty][DynamicProperty]. | Specific protocol parameters should be read at subsequent stages rather than cached in constructors. |
InitChannelAsync | Set ChannelObject, and if necessary, Channel.SetupAsync, and finally AfterVariablesChangedAsync. | Foundation plugins typically reconstruct the underlying IDevice here, assign properties to underlying objects, and call InitChannel. |
AfterVariablesChangedAsync | base class rebuilding VariableSourceReads, script variables, method variables, and scheduled tasks. | Variable addition, deletion, and attribute modification will all trigger. When rewriting, make sure to call base and clean up old subscriptions or mappings. |
ProtectedStartAsync | called before starting communication, controlled by StartTimeout. | Connecting to remote ends, starting services, and subscribing messages can be placed here. Cancellation tokens must be respected. |
ProtectedGetTasks | Builds scheduled tasks. | CollectBase provides device status, online testing, and variable reading tasks. Only special protocols are rewritten. |
| Periodically read | call ReadSourceAsync; if failed, press RetryCount to retry. | Success requires setting variable values or returning parsable bytes; Failure should return the failure result; do not accept exceptions and then fake success. |
| Write/RPC | InvokeWriteAsync, InvokeMethodAsync. | Custom writes use write locks, and write-back validation is performed if necessary. |
StopAsync/SafetyDisposeAsync | Stop task loops, release logs, underlying devices, event subscriptions, and locks. | Event subscriptions must be canceled, socket/client/channel must be released to avoid repeated receptions after reboot. |
Variable Reading Model
CollectBase.AfterVariablesChangedAsync divides enabled variables into three categories.
| Type | Detection Method | Operation Mode |
|---|---|---|
| The | address of a normal source read variable is not DeviceStatus,Script, ScriptRead, and there is no OtherMethod. | Enter ProtectedLoadSourceReadAsync, generate VariableSourceRead, and then the scheduled task calls ReadSourceAsync. |
| Script/Special Variable | addresses are DeviceStatus,Script, ScriptRead. | Enter VariableScriptReads, updated by script tasks or special address logic. |
| Method Variable | Configured OtherMethod. | Find the method based on [DynamicMethod], call the method on read, and pass parameters to the method at write. |
ProtectedLoadSourceReadAsync is not to read data, but to organize variables into "packet reading." The Foundation plugin is usually written like this:
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
List<VariableSourceRead> reads = new();
foreach (var group in deviceVariables.GroupBy(a => a.CollectGroup))
{
reads.AddRange(_plc.LoadSourceRead<VariableSourceRead, VariableRuntime>(
group,
_driverProperties.MaxPack,
CurrentDevice.IntervalTime));
}
return Task.FromResult(reads);
}
This approach has two benefits: the same collection group can be packaged independently, MaxPack limits the length of a single packet, and variables that maintain their own IntervalTime or the device's default interval remain active.
Read/Write Locks and Write-First
CollectBase Use AsyncReadWriteLock Manage read/write conflicts.
| Mechanism | Description |
|---|---|
DutyCycle | When writing is heavy, Read intermittently according to duty cycle to avoid long periods of writing without reading. |
WritePriority | Cancels waiting reads during write, allowing control commands to be sent first. |
ReadSourceAsync | Scheduled read to enter read lock. Custom reads should stop blocking threads for long periods. |
WriteValuesAsync | The write implementation should enter write lock, and the Foundation base class has already handled it; When directly inheriting CollectBase, you need to handle it yourself. |
Check | is written successfully, the variable that enabled RpcWriteCheck will be checked back. Custom write implementations can reuse this method. |
Do not perform irrelevant, time-consuming tasks in the write lock, such as waiting for external HTTP, writing large files, or starting threads. Locks only protect the critical zone for protocol read/write.
Connection and Online Status
| Methods | Instructions |
|---|---|
IsConnected() | Used for frontend status, online testing, and device status checks. It must reflect the true connection status and cannot always return true unless permanently online like MemoryDriver. |
ProtectedStartAsync | The location where the service is first connected or started. If timeout is over, the startup failure will be recorded. |
TestOnline | CollectReceivedFoundationBase By default, it attempts to reconnect and offlines the variable; Custom protocols can be overridden for lightweight detection. |
SetDeviceStatus | CollectBase Update device status based on connection status and variable availability. Special plugins can be rewritable, such as MemoryDriver permanently online. |
"Equipment online" on site does not mean "every variable is online." If a read fails, CollectBase will set the read variable to offline and record the final error.
Special Addresses and Dynamic Methods
| Capability | Syntax | Scenarios |
|---|---|---|
| Device Status Variable | Variable Address Writing DeviceStatus. | Treat the device's online/offline status as a regular variable for display or forwarding. |
| Script variable | address writes Script or ScriptRead. | Do not communicate on site; calculate values through expressions or scripts. |
| Dynamic Methods | Method Marker [DynamicMethod("Notes", "Remarks"). | Read and write dates, call protocol-specific commands, execute object methods. |
| RPC writes | external interface write variables. | Enter InvokeWriteAsync or InvokeMethodAsync, then use the plugin to write to the field device. |
The return value of the dynamic method must be convertible to OperResult or IOperResult<T>. After configuring OtherMethod on a variable, the base class will find the corresponding method by method name.
Foundation plugin template
CollectFoundationBase has implemented most of the fixed processes.
| You need to write | descriptions |
|---|---|
the underlying _plc fields | using protocol device classes in Foundation. |
FoundationDevice | returns the current _plc. |
InitChannelAsync | reconstruct _plc, write protocol parameters from the property object, and call _plc.InitChannel(channelObject, LogMessage), and finally call base.InitChannelAsync. |
ProtectedLoadSourceReadAsync | Generates VariableSourceRead using the address resolution/packaging capabilities of underlying devices. |
Optional WriteValuesAsync | protocol allows batch write, special data types, or rewriting when writing strings. |
Optional [DynamicMethod] | Exposing protocol-specific methods to variable or RPC. |
ModbusMaster is the smallest Foundation template; SiemensS7Master demonstrates batch writes and dynamic methods; ControlLogixMaster demonstrates custom writes when the Foundation write path is insufficient.
Directly inheriting CollectBase templates
When directly inheriting from CollectBase, plugins must handle package reading, read, write, and connection status themselves.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public sealed class MyCollectDriver : CollectBase
{
private readonly MyCollectProperty _properties = new();
private MyClient? _client;
public override CollectPropertyBase CollectProperties => _properties;
public override Type DriverPropertyType { get; } = typeof(MyCollectProperty);
public override bool IsConnected() => _client?.Connected == true;
public override ChannelTypeEnum[] SupportedChannelTypes() => [ChannelTypeEnum.Other];
public override DataTypeEnum[] SupportedDataTypes() =>
[DataTypeEnum.Boolean, DataTypeEnum.Int16, DataTypeEnum.Int32, DataTypeEnum.Float, DataTypeEnum.String];
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
{
_client = new MyClient(_properties.Endpoint);
await _client.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
var reads = deviceVariables
.GroupBy(a => string.IsNullOrWhiteSpace(a.IntervalTime) ? CurrentDevice.IntervalTime : a.IntervalTime)
.Select(group =>
{
var read = new VariableSourceRead { IntervalTime = new(group.Key) };
read.AddVariableRange(group);
return read;
})
.ToList();
return Task.FromResult(reads);
}
protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(
VariableSourceRead sourceRead,
CancellationToken cancellationToken)
{
foreach (var variable in sourceRead.Variables)
{
var value = await _client!.ReadAsync(variable.RegisterAddress!, cancellationToken).ConfigureAwait(false);
variable.SetValue(value, DateTime.UtcNow);
}
return new OperResult<ReadOnlyMemory<byte>>();
}
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(
Dictionary<VariableRuntime, JsonElement> writeInfoLists,
CancellationToken cancellationToken)
{
using var writeLock = await ReadWriteLock.WriterLockAsync().ConfigureAwait(false);
var results = new Dictionary<string, OperResult>();
foreach (var (variable, value) in writeInfoLists)
{
results[variable.Name] = await _client!.WriteAsync(
variable.RegisterAddress!,
value.GetObjectFromJsonElement(),
cancellationToken).ConfigureAwait(false);
}
return results;
}
protected override async Task SafetyDisposeAsync(bool disposing)
{
if (_client != null)
await _client.DisposeAsync().ConfigureAwait(false);
await base.SafetyDisposeAsync(disposing).ConfigureAwait(false);
}
}
public sealed class MyCollectProperty : CollectPropertyRetryBase
{
[DynamicProperty("Connection address", Remark = "For example, 127.0.0.1:12345")]
public string Endpoint { get; set; } = "127.0.0.1:12345";
}
Current Source Code Collection Plugin Reference Matrix
The current source tree contains 109 collection implementations: 107 are exposed in the plugin catalog, while MemoryDriver and SyncBridgeMirrorDriver are runtime-only implementations hidden by DisplayNonePlugin. The table below is a representative matrix covering the main base classes, communication shapes, and lifecycle patterns rather than a complete plugin list. Before developing a new plugin, first choose the implementation closest to the required protocol shape.
| Plugin | Base Class | Attribute Class | Key Reference |
|---|---|---|---|
MemoryDriver | CollectBase | CollectPropertyNone | Memory variables, expression triggers, global variable change events, no real channels. |
SyncBridgeMirrorDriver | CollectBase | CollectPropertyNone | Hidden mirror container, inbound push updates, peer-session online state, and reverse-RPC boundaries. |
ModbusMaster | CollectFoundationBase | ModbusMasterProperty | Foundation standard templates, DTU, site numbers, and maximum packing length. |
SiemensS7Master | CollectFoundationBase | SiemensS7MasterProperty | S7 address packaging, batch write, and date/time dynamic methods. |
Dlt645_2007Master | CollectFoundationBase | Dlt645_2007MasterProperty | Meter address, password, operator code, precursor symbol. |
Dlt645_1997Master | CollectFoundationBase | Dlt645_1997MasterProperty | DL/T 645-1997 twelve-digit meter addresses, preamble bytes, DTU routing, and protocol data parsing. |
OpcUaMaster | CollectBase | OpcUaMasterProperty | Subscribe/ Polling mixing, certificates, security policies, node type loading, subscription refresh. |
OpcDaMaster | CollectBase | OpcDaMasterProperty | Windows/COM OPC DA, subscription groups, reconnection check, server-side time. |
MqttCollectClient | MqttCollectBase | MqttCollectClientProperty | MQTT client connection, topic subscription, message mapping variables. |
MqttCollectServer | MqttCollectBase | MqttCollectServerProperty | MQTT server listening, client validation, message mapping variables. |
GatewayMqttCollectClient / GatewayMqttCollectServer | GatewayMqttCollectBase | GatewayMqttCollectClientProperty / GatewayMqttCollectServerProperty | Fixed ThingsGateway protocol, one shared session, multiple RemoteKey sources, snapshots, variable synchronization, and reverse RPC. |
CanMaster | CollectBase | CanMasterProperty | CAN/CAN FD endpoints, frame assembly, hardware filtering, and full-frame slice reading. |
CustomPacketMaster | CollectFoundationBase | CustomPacketMasterProperty | Strongly typed complete-packet configuration, framing, response matching, checksums, read/write templates, and controlled raw debugging. |
ControlLogixMaster | CollectFoundationBase | ControlLogixMasterProperty | AllenBradley CIP, Foundation package read, custom write. |
PCCCMaster | CollectFoundationBase | PCCCMasterProperty | AllenBradley PCCC, Foundation read-write template. |
DCONMaster | CollectFoundationBase | DCONMasterProperty | DCON protocol, DTU, and packaging templates. |
EDPF_NTMaster | CollectReceivedFoundationBase | EDPF_NTUdpProperty | UDP passive reception, reconstructing the map when variables are refreshed, without active read/write implementation. |
HJ212Master | CollectReceivedFoundationBase | HJ212MasterProperty | Eco-friendly HJ212 reporting, address description, and reporting mapping. |
IEC61850Master | CollectBase | IEC61850MasterProperty | MMS read/write, RCB reporting, GOOSE, SOE, TLS, complex subscription lifecycle. |
InovanceMaster | CollectFoundationBase | InovanceMasterProperty | Inovance protocol, inheriting Foundation DTU packaged properties. |
KELID2008Master | CollectReceivedFoundationBase | KELID2008MasterProperty | Reporting Protocol, Address Description Extension, Active Read and Write Not Implemented. |
LKSISMaster | CollectReceivedFoundationBase | LKSISProperty | UDP reporting protocol, maintains receive mapping after variable changes. |
Mc1E_BinaryMaster | CollectFoundationBase | Mc1E_BinaryMasterProperty | Mitsubishi 1E binary, Foundation package template. |
Mc3E_BinaryMaster | CollectFoundationBase | Mc3E_BinaryMasterProperty | Mitsubishi 3E binary, Foundation package template. |
ModbusC1Master | CollectFoundationBase | ModbusC1MasterProperty | Modbus C1, DTU packaging template. |
ModbusC20Master | CollectFoundationBase | ModbusC20MasterProperty | Modbus C20, DTU packaging template. |
OmronFinsMaster | CollectFoundationBase | OmronFinsMasterProperty | Omron FINS, Foundation Packaging Template. |
OpcAeMaster | CollectBase | OpcAeMasterProperty | OPC AE event collection, event subscription conversion volume. |
SECSMaster | CollectReceivedFoundationBase | SECSMasterProperty | SECS/GEM features both receiving devices and active read/write capabilities. |
TIANXINMaster | CollectFoundationBase | TIANXINMasterProperty | Tianxin Instrument Class Protocol, DTU Packaging Template. |
TS550Master | CollectFoundationBase | TS550MasterProperty | TS550 protocol, Foundation universal read/write properties. |
USBScaner | CollectBase | USBScanerProperty | Local scanner Hook, writes variables during scanning, does not perform active read/write. |
VigorMaster | CollectFoundationBase | VigorMasterProperty | Vigor protocol, DTU packaging template. |
ZeroMQCollectClient | CollectBase | ZeroMQCollectClientProperty | ZeroMQ subscription/connection, topic prefix, high watermark, reconnection, and cleanup. |
Common Development Errors
| Errors | Consequences | Correct Practices |
|---|---|---|
| Constructor to read attributes or connect devices | Attributes haven't been injected yet, and logs and cancel tokens don't exist. | The constructor only initializes fields, and the connection is placed in InitChannelAsync or ProtectedStartAsync. |
CollectProperties and DriverPropertyType are inconsistent | misaligned page fields, import/export, and runtime read. | Both point to the same strong attribute class. |
After rewriting InitChannelAsync, do not call base | variables will not be packaged, and scheduled tasks will have no data source. | After completing the initialization of the underlying object, call base.InitChannelAsync. |
| Not cleaning up old subscriptions after variable changes | Repeated receipt, rewriting variables, memory leaks. | Remove the old mapping first in AfterVariablesChangedAsync or release flow. |
| Read fails but still returns a success | The variable appears online but has an incorrect value. | Returns the failed OperResult, offlined, and records the error. |
Ignore CancellationToken | Freezes when stopping or restarting the device. | All network, serial port, wait, and retry pass cancellation tokens. |
| Write without a write lock | Read and write concurrent conflicts, with on-site protocol packets crossing. | Customize WriteValuesAsync Use ReadWriteLock.WriterLockAsync. |
| Do not cancel event subscriptions upon release | The same data is processed multiple times after restarting. | SafetyDisposeAsync. |
Validation Checklist
Verify at least these scenarios after development is complete.
| Scene | Verification Point |
|---|---|
| Plugin Discovery | Plugin Management shows plugins and selects them by dropping down the collection device , plugin type is collection. |
| Properties form | All [DynamicProperty] fields display, default values, enumerations, certificate dropdowns, import/export, all are correct. |
| Channel Type | SupportedChannelTypes() Consistent with the on-site connection method; If there is no normal channel, return Other. |
| Data Types | SupportedDataTypes() Do not expose types that the protocol cannot resolve. |
| Single Point Read | Use device debugging to read 3 to 5 points first, covering boolean, integer, floating-point, string, or protocol-specific types. |
| Batch reading | point tables after importing and confirming that the package quantity, interval, and timeout meet expectations. |
| Write/RPC | Read points cannot be written; writable points can be written; enabling readback verification returns an error if failed. |
| Disconnect and reconnect | After disconnecting, shutting down the server, or disconnecting the serial port, the variable goes offline and can automatically reconnect after restoration. |
| Pause/Resume | Tasks stop after pausing the device; resume collection after recovery. |
| Release reboot | After multiple saves or service restarts without repeated subscriptions, port occupancy, or thread leaks. |