--- title: "MQTT TagProvider Integration" tags: [mqtt, tagprovider, uns, iiot, asset-navigation, dynamic-tags] description: "Connect to an MQTT broker via TagProvider, explore its namespace, link selected nodes into the UNS AssetTree, and build an asset-navigation UI with DataGrid, TrendChart, and AlarmViewer — all driven by dynamic Asset() bindings." version: "0.1-draft" author: "Tatsoft" ---
Connect FrameworX to an external MQTT broker using TagProvider (zero-local-tags), auto-discover topic trees, link them into the UNS AssetTree, and build a dynamic asset-navigation UI with DataGrid, TrendChart, and AlarmViewer — all driven by Asset() bindings and Client.Context properties.
DRAFT v0.1
This skill has open items that require resolution before publishing. See the Open Items section at the bottom of this page.
This skill connects FrameworX to an external MQTT broker using a TagProvider — the dynamic, zero-local-tags approach. Instead of creating individual UnsTags and mapping them with Device Points, the TagProvider auto-discovers the broker's topic tree and extends the UNS namespace directly. The skill then builds a complete asset-navigation UI: a Layout with an AssetTree on the left, a header with commands, and a content area that reacts to the user's tree selection using Asset() bindings and Client.Context properties.
MQTT Broker ??? TagProvider ??? UNS Namespace (dynamic)
?
AssetTree Folder
?
?????????????????????????????????
Header AssetTree MainPage
(Canvas) (Navigation) (DataGrid + Trend)
?
Client.Context.AssetPath
drives all content dynamically
On this page:
Use this skill when:
Do NOT use this skill when:
Decision guide:
Scenario | Approach |
|---|---|
Dynamic data, auto-discovery wanted | This skill (TagProvider) |
Specific tags, strict validation (FDA) | Device Module |
Large topic tree, monitoring/IoT | This skill (TagProvider) |
Small fixed set of MQTT topics | Device Module may be simpler |
open_solution or create_solution completed)Category | Items |
|---|---|
Tools |
|
Tables |
|
Designer Actions |
|
Key Namespaces |
|
Before creating any configuration, you need to see what data the broker has. This step is interactive — it involves the user.
If using the built-in broker for testing:
designer_action('start_mqtt_broker')
designer_action('start_mqtt_simulator')
This starts a local MQTT broker and a SparkplugB simulator that publishes sample data (solar panels, cities, sensors).
Navigate to MQTT Tools:
designer_action('navigate', 'DataExplorer.MQTTTools')
Connect to the broker:
designer_action('connect_mqtt_explorer', '<PrimaryStation string>')
The PrimaryStation format for MQTT is a semicolon-delimited string with 14 fields:
{Host};{Port};{ClientID};{Username};{Password};{CertFile};{KeyFile};{TLS};{CleanSession};{WillTopic};{QoS};{KeepAlive};{RetainPublish};{UseWebSocket};
Common examples:
Scenario | PrimaryStation |
|---|---|
Local built-in broker |
|
Remote broker |
|
With authentication |
|
Ask the user to explore and select:
The MQTT browser is now connected. Please navigate the tree to explore the available data. When you've identified the nodes you want to integrate into the Unified Namespace, select them and let me know — or I can read your current selection.
Read the user's selection:
get_designer_state()
This returns the current MQTT Tools state including which nodes the user has selected (or browsed to). Use this information to determine the initial tree nodes for the TagProvider link.
AI-driven alternative: If the user says "just connect everything" or "pick reasonable defaults," the AI can read the top-level structure from get_designer_state() and propose a reasonable default selection, then create the configuration. Verify with the user afterward — it's easier to modify an existing object than to wait for the user to manually select before creating anything.
Fetch the schema and protocol details:
get_table_schema('UnsTagProviders')
list_protocols('mqtt')
Write the TagProvider object:
Error rendering macro 'code': Invalid value specified for parameter 'com.atlassian.confluence.ext.code.render.InvalidValueException'{
"table_type": "UnsTagProviders",
"data": [
{
"Name": "MQTT",
"Protocol": "MQTT",
"PrimaryStation": "localhost;1883;FrameworX-001;;;;;None;False;;AtLeastOnce;10;False;False;",
"Separators": "BranchSeparator=/;AttributeSeparator=/;",
"Description": "Production MQTT Broker"
}
]
}
Key decisions:
Tag.MQTT.*. Choose a meaningful name (e.g., ProductionMQTT, FactoryBroker).BranchSeparator=/;AttributeSeparator=/; maps MQTT topic hierarchy (plant/line1/temp) to UNS path hierarchy. This is almost always the correct default for MQTT.After creation, the TagProvider immediately starts discovering topics when runtime starts. Topics appear as dynamic tags under Tag.<ProviderName>.<topic.path>.
The AssetTree folder is what makes the TagProvider data visible and navigable in the UI. A Linked Folder connects an AssetTree node to a TagProvider and specifies which initial nodes from the provider's namespace to display.
Schema confirmation needed
UnsAssetTree currently reports readOnly: true in the schema. The exact field names for TagProviderLink and InitialPath require confirmation from Tatsoft. See Open Items at the bottom of this page.
{
"table_type": "UnsAssetTree",
"data": [
{
"Name": "Production",
"TagProviderLink": "MQTT",
"InitialPath": "spBv1.0/GroupID",
"Description": "MQTT production data from SparkplugB broker"
}
]
}
Key decisions:
From this point down, the AssetTree is dynamic — the platform auto-expands the tree based on whatever the MQTT broker publishes. No further tag creation needed.
The standard pattern for asset-navigation applications is a Layout with three regions:
get_table_schema('DisplaysLayouts')
list_elements('Layout')
{
"table_type": "DisplaysLayouts",
"data": [
{
"Name": "Startup",
"Header": "Header",
"Menu": "AssetTreeMenu",
"Content": "MainPage",
"Description": "Asset navigation layout — tree on left, dynamic content on right"
}
]
}
The Layout Startup (ID 0) is the predefined startup layout — modifying it changes what loads on application launch.
Regions:
A simple Canvas display with the application title:
get_table_schema('DisplaysList')
list_elements('TextBlock,Button')
{
"table_type": "DisplaysList",
"data": [
{
"Name": "Header",
"PanelType": "Canvas",
"Width": 1200,
"Height": 60,
"Description": "Application header bar",
"Objects": [
{
"Type": "TextBlock",
"Name": "Title",
"Left": 20,
"Top": 15,
"Text": "MQTT Production Monitor",
"FontSize": 18,
"FontWeight": "Bold"
}
]
}
]
}
A Canvas display containing the AssetTree control that drives navigation:
list_elements('AssetsTree')
{
"table_type": "DisplaysList",
"data": [
{
"Name": "AssetTreeMenu",
"PanelType": "Canvas",
"Width": 300,
"Height": 700,
"Description": "Asset tree navigation panel",
"Objects": [
{
"Type": "AssetsTree",
"Name": "NavTree",
"Left": 0,
"Top": 0,
"Width": 300,
"Height": 700
}
]
}
]
}
When the user clicks a node in the AssetsTree, the platform automatically updates:
Property | Example Value |
|---|---|
|
|
|
|
| leaf node name |
| full tag path of the selected tag |
These properties drive the content display reactively.
The MainPage uses Asset() bindings to react to the user's tree selection. All content is driven by Client.Context.AssetPath.
list_elements('DataGrid,TrendChart,TextBlock,AlarmViewer')
{
"table_type": "DisplaysList",
"data": [
{
"Name": "MainPage",
"PanelType": "Dashboard",
"Columns": 2,
"Rows": 3,
"Description": "Dynamic content — reacts to AssetTree selection",
"Objects": [
{
"Type": "TextBlock",
"Name": "AssetTitle",
"Row": 0,
"Column": 0,
"ColSpan": 2,
"Text": "",
"LinkedValue": "@Client.Context.AssetName",
"FontSize": 20,
"FontWeight": "Bold"
},
{
"Type": "DataGrid",
"Name": "AssetData",
"Row": 1,
"Column": 0,
"ColSpan": 2,
"LinkedValue": "@Tag",
"LinkedContext": "@Client.Context.AssetPath",
"Width": 800,
"Height": 300
},
{
"Type": "TrendChart",
"Name": "AssetTrend",
"Row": 2,
"Column": 0,
"Width": 500,
"Height": 250,
"Duration": "00:05:00"
},
{
"Type": "AlarmViewer",
"Name": "AssetAlarms",
"Row": 2,
"Column": 1,
"Width": 400,
"Height": 250
}
]
}
]
}
Key pattern — the DataGrid LinkedValue:
When you set the DataGrid's LinkedValue to @Tag and provide a LinkedContext pointing to @Client.Context.AssetPath, the DataGrid automatically shows all children (sub-tags and attributes) of whatever the user selected in the AssetTree. This is the heart of the dynamic pattern — one display serves the entire asset hierarchy.
The Asset() syntax for code and expressions:
// In scripts or CodeBehind: double value = Asset(@Client.Context.AssetPath + "/Temperature"); // In display element LinkedValue fields: // Static: @Tag.MQTT.plant.line1.temperature // Dynamic: Asset(Client.Context.AssetPath + "/Temperature")
designer_action('start_runtime')
After starting:
Client.Context propertiesTake a single screenshot to verify:
get_screenshot('runtime')
void Opening()
{
// Optionally set a default branch to expand
@Client.Context.TreeInitialBranch = "MQTT/spBv1.0/GroupID";
}
public static double GetTemperature(string assetPath)
{
return TK.ToDouble(Asset(assetPath + "/Temperature"));
}
// WPF (.NET Framework 4.8) — automatic type resolution
var value = Asset("/MQTT/plant/line1/temperature");
// HTML5/Portable (NetStandard 2.0) — explicit conversion required
int intValue = TK.ToInt(Asset("/MQTT/plant/line1/count"));
double dblValue = TK.ToDouble(Asset("/MQTT/plant/line1/temperature"));
string strValue = TK.ToString(Asset("/MQTT/plant/line1/status"));
bool boolValue = TK.ToDigital(Asset("/MQTT/plant/line1/running"));
get_objects('UnsTagProviders') — verify the TagProvider was created with correct PrimaryStationdesigner_action('navigate', 'Uns.AssetTree') → get_designer_state() — confirm the linked folder appears with the TagProvider referencedesigner_action('start_runtime') → get_runtime_state() — confirm the TagProvider connection is activeget_screenshot('runtime') — verify the AssetTree populates and the Layout renders correctlyMistake | Why It Happens | How to Avoid |
|---|---|---|
Creating local UnsTags for MQTT data | Habit from Device Module workflow | TagProviders don't need local tags — that's the whole point. Only create UnsTags if you need alarms or historian on specific points. |
Wrong PrimaryStation format | Forgetting semicolon delimiters or field order | Always call |
DataGrid shows nothing | LinkedValue not correctly bound to context | Use |
Topics with special chars not accessible | MQTT topics like | Use quoted syntax: |
AssetTree empty after runtime start | TagProvider not connected or linked folder not configured | Verify TagProvider connection in ServicesMonitor, then check that UnsAssetTree folder has correct TagProviderLink |
Trying to set alarms on dynamic tags | Dynamic tags don't exist as local UnsTags | Create a local tag and use a Script expression to copy the Asset() value into it. Or switch those specific points to Device Module. |
Layout regions not rendering | Wrong display names in Layout configuration | Display names in Header, Menu, Content fields must match exactly (case-insensitive) |
Variation A: Mixed Approach (TagProvider + Device Module)
Variation B: SparkplugB-Specific
MQTTspB protocol instead of MQTT in the TagProviderSeparators to match SparkplugB topic structureVariation C: Multiple Brokers
Client.Context handles any sourceVariation D: Add Historian to Dynamic Tags
Asset() syntax to automatically query historical data from historian TagProvidersskill-device-mqtt — Explicit MQTT integration using Device Module (Channel → Node → Points)skill-asset-navigation — Generic asset-navigation UI patterns (not MQTT-specific)skill-sparkplugb-collector — SparkplugB Collector setup with Device ModuleDRAFT
# | Item | Status |
|---|---|---|
1 |
| TODO |
2 |
| TODO |
3 |
| TODO |
4 | DataGrid | VERIFY |
5 | TrendChart Asset() binding: How does a TrendChart dynamically bind its pens to the currently selected asset's children? | VERIFY |
6 | Layout field names: Verify exact field names for | VERIFY |
7 | Template integration: If solution templates with built-in Header+AssetTree+Content exist, reference them as a shortcut. | VERIFY |