title: "Discovery Services — Tag Discovery, DataLink, and Asset() Patterns" tags: [discovery-services, tagprovider, tag-discovery, datalink, linked-tags, dynamic-tags, asset-syntax, uns, iiot] description: "Understand the three connection patterns for external data in the UNS: Local Tags + Device Points, Linked Tags (DataLink), and Dynamic Tags (Asset). Learn when to use each, how Discovery Services extend the namespace, and the DataLink shortcut for hybrid workflows." version: "1.0" author: "Tatsoft"
FrameworX has two independent systems for getting external data into the Unified Namespace: the Device Module and Discovery Services. They share nothing architecturally. Some protocols (MQTT, OPC UA) are available in both, but the workflow and mental model are completely different. This skill teaches the three connection patterns and when to use each.
Use when the user mentions or needs:
Do NOT use when:
DEVICE MODULE DISCOVERY SERVICES
(Pillar 2: Devices) (Technology under Pillar 1: UNS)
????????????????? ????????????????????????????????
• Define local tags FIRST • NO local tags needed
• Create Channel → Node → Points • Give address, system discovers
• Explicit polling, scan rates • On-demand, auto-managed
• ~100 protocols • ~17 protocols (discovery-capable)
• Full control, deterministic • Dynamic, flexible
• Governance: LOCAL • Governance: EXTERNAL
Critical rule for AI: Never create UnsTags for data that will be accessed through Discovery Services dynamic tags. The whole point is zero local tags. Only create UnsTags when:
These are not stages of development — they are three equally valid architectural patterns chosen based on data characteristics.
Define tags locally in UNS, then map them to physical device addresses through the Device Module.
When to use:
Configuration workflow:
Access syntax: @Tag.FolderPath/TagName or Tag.FolderPath/TagName.Member
Modules involved: UNS (tags) + Devices (channel, node, points, access types)
Create local tags in UNS but set the DataLink property to a Discovery Services path. The runtime automatically manages communication — no Device Module configuration needed.
When to use:
Configuration workflow:
DataLink property to the external pathDataLink field example:
Tag: Plant1/TankLevel
Type: Float
DataLink: /MQTT/plant/tank01/level
Access syntax: @Tag.Plant1/TankLevel — same as any local tag. The DataLink binding is transparent.
Key behavior:
Modules involved: UNS only (tags with DataLink). No Device Module tables.
Direct binding to external paths without creating any local tags. Discovery Services dynamically discovers and extends the UNS namespace. Tags are created in the real-time database (RTDB) on-demand when accessed.
When to use:
Configuration workflow:
Access syntax:
// In scripts or expressions:
value = Asset("/ProviderName/path/to/variable")
// In display element bindings (LinkedValue field):
Asset(Client.Context.AssetPath + "/Temperature")
// Static path in display bindings:
@Tag.ProviderName.path.to.variable
Key behavior:
Modules involved: UNS (Discovery Services connection only). No tags, no devices.
| If your data... | Use | Why |
|---|---|---|
| Requires deterministic polling with explicit scan rates | Pattern 1 (Local Tags + Devices) | Only way to control timing |
| Uses legacy/proprietary protocols without discovery | Pattern 1 | Only option for non-discovery protocols |
| Needs local governance + alarms + historian, but flexible sourcing | Pattern 2 (DataLink) | Best of both worlds |
| Has stable structure but multiple sites or changing sources | Pattern 2 | Template once, link many |
| Is temporary or diagnostic | Pattern 3 (Dynamic/Asset) | No configuration overhead |
| Has unknown or changing structure | Pattern 3 | Dynamic discovery at runtime |
| Has thousands of tags, mostly for monitoring | Pattern 3 | Zero engineering per tag |
| Needs alarms on some points from a dynamic source | Mixed: Pattern 3 for browsing + Pattern 2 for alarm points | Use DataLink for the critical subset |
Most production systems use multiple patterns simultaneously. A typical split:
Only protocols with built-in discovery capability can be used. Currently 17 connectors:
| Connector | Type | Notes |
|---|---|---|
| AB Rockwell ControlLogix | PLC | Discovers controller tags |
| Beckhoff TwinCAT | PLC | Discovers TwinCAT symbols |
| Canary Historian | Historian | Also usable as Storage Location |
| Codesys PLC Handler | PLC | Discovers Codesys variables |
| DataHub Communication Service | Application | Connects to Cogent DataHub |
| FlowTimebaseDB Database | Historian | Time-series database |
| GE Proficy Historian | Historian | Also usable as Storage Location |
| InfluxDB Database | Historian | Also usable as Storage Location |
| LineMonitor | Application | Production monitoring |
| MQTT Client | Messaging | Standard MQTT broker |
| MQTT Sparkplug B Collector | Messaging | SparkplugB-specific metadata |
| OPC UA Client | Industrial | Standard OPC UA discovery |
| OSIsoft Aveva PI System | Historian | Also usable as Storage Location |
| Server to Server | Application | Connect to another FrameworX solution |
| SQL Database | Database | Generic SQL data source |
| SQL Database Normalized | Database | Normalized schema variant |
Important: The Device Module has ~100 protocols. Discovery Services has ~17. They are independent lists. Do not confuse them.
Some connectors (Canary, InfluxDB, GE Proficy, OSIsoft PI) serve two purposes:
When IsHistorian = true, TrendCharts using Asset() syntax automatically query the historian for time-range data.
get_table_schema('UnsTagProviders')
list_protocols('<protocol_name>') // Get PrimaryStation format
Write example:
{
"table_type": "UnsTagProviders",
"data": [{
"Name": "ProductionMQTT",
"Protocol": "MQTT",
"PrimaryStation": "localhost;1883;FrameworX-001;;;;;None;False;;AtLeastOnce;10;False;False;",
"Separators": "BranchSeparator=/;AttributeSeparator=/;",
"Description": "Production floor MQTT broker"
}]
}
Key fields:
Tag.<Name>.*list_protocols() first to get the format.BranchSeparator=/;AttributeSeparator=/;The AssetTree makes Discovery Services data navigable in the UI. A linked folder connects an AssetTree node to a Discovery Services connection.
get_table_schema('UnsAssetTree')
{
"table_type": "UnsAssetTree",
"data": [{
"Name": "Production/MQTTData",
"TagProviderLink": "ProductionMQTT",
"InitialBranch": "spBv1.0/GroupID",
"Description": "MQTT production data linked to AssetTree"
}]
}
From this folder down, the tree is dynamic — it auto-expands based on what the Discovery Services connection discovers at runtime.
When the user clicks a node in an AssetsTree control:
Client.Context.AssetPath updates to the selected pathClient.Context.AssetName updates to the selected node name// Read a value
double temp = TK.ToDouble(Asset("/ProviderName/path/to/temperature"));
// Write a value
Asset("/ProviderName/path/to/setpoint") = 75.0;
// Dynamic path from context (in display CodeBehind)
double val = TK.ToDouble(Asset(@Client.Context.AssetPath + "/Temperature"));
WPF (.NET Framework 4.8) — automatic type resolution works.
HTML5/Portable (NetStandard 2.0) — explicit conversion required:
int intValue = TK.ToInt(Asset("/path"));
double dblValue = TK.ToDouble(Asset("/path"));
string strValue = TK.ToString(Asset("/path"));
bool boolValue = TK.ToDigital(Asset("/path"));
| Syntax | When to use | Example |
|---|---|---|
@Tag.Folder/Name | Local tags, Linked Tags (DataLink) | @Tag.Plant1/TankLevel |
Asset("path") | Dynamic tags from Discovery Services | Asset("/MQTT/plant/tank01/level") |
Asset(expression) | Dynamic UIs driven by user selection | Asset(Client.Context.AssetPath + "/Temp") |
Note: Asset() also works with local tags. It's sometimes used even with local tags because the string argument can be an expression, enabling dynamic display patterns.
| Mistake | Why it happens | How to avoid |
|---|---|---|
| Creating UnsTags for dynamic Discovery Services data | Habit from Device Module workflow | Dynamic tags don't need local tags — that's the whole point. Only create UnsTags if you need alarms/historian or want DataLink. |
| Confusing Device Module protocols with Discovery Services protocols | Both can use MQTT/OPC UA | Device Module has ~100 protocols. Discovery Services has ~17. Check list_protocols() for Discovery Services availability. |
| Setting alarms on dynamic tags | Dynamic tags have no local tag to attach alarms to | Create a local tag with DataLink to the specific path, then configure alarms on that tag. |
| Using DataLink without a Discovery Services connection | DataLink needs the underlying TagProvider technology | A UnsTagProviders entry for the matching protocol must exist. DataLink binds to paths in that connection. |
| Thinking Discovery Services replaces Devices | They are independent systems | Both can coexist in the same solution. Use Devices for control, Discovery Services for monitoring/IoT. |
| Wrong PrimaryStation format | Each protocol has its own format | Always call list_protocols('<protocol>') before writing UnsTagProviders. |
A typical production solution combining all three patterns:
UNS Tags:
Safety/EStop → Device Module (Modbus, deterministic polling)
Safety/FireAlarm → Device Module (Modbus)
Production/LineSpeed → DataLink: /MQTT/plant/line1/speed (local tag + auto-comm)
Production/OEE → DataLink: /MQTT/plant/line1/oee
Quality/BatchResult → DataLink: /OPC/mes/quality/result
Discovery Services Connections:
MQTT (ProductionMQTT) → mqtt.factory.local:1883
OPC (MESServer) → opc.tcp://mes:4840
AssetTree:
Plant/
Safety/ → manual tags, Device Module
Production/ → linked to ProductionMQTT via AssetTree folder
Enterprise/ → linked to MESServer, browsed via Asset() syntax
In this setup, safety tags have full deterministic control, production tags have local governance with auto-managed communication, and enterprise data flows dynamically with zero tag engineering.