<p class="auto-cursor-target"><br /></p><ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="d4dd463a-f830-4b78-89b4-7703dd9aac07"><ac:plain-text-body><![CDATA[---
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"
—
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. The result is a fully dynamic application where the UI adapts to whatever data the MQTT broker publishes — no tag-by-tag engineering required.
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 |
Category |
Items |
— |
— |
Tools |
`designer_action`, `get_designer_state`, `get_table_schema`, `write_objects`, `list_protocols`, `list_elements`, `get_screenshot` |
Tables |
`UnsTagProviders`, `UnsAssetTree`, `DisplaysList`, `DisplaysLayouts` |
Designer Actions |
`navigate` (to DataExplorer.MQTTTools), `connect_mqtt_explorer` (new), `start_mqtt_broker`, `start_mqtt_simulator` |
Key Namespaces |
`Client.Context.AssetPath`, `Client.Context.AssetName`, `Client.Context.AssetNodeName`, `Client.Context.SelectedTag` |
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:*
```text
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:*
```text
designer_action('navigate', 'DataExplorer.MQTTTools')
```
*Connect to the broker:*
```text
designer_action('connect_mqtt_explorer', '<PrimaryStation string>')
```
The PrimaryStation format for MQTT is a semicolon-delimited string:
```
;
;
;
;
;
;
;
;
;
;
;
;
;
;
```
Common examples:
*Ask the user to explore and select:*
At this point, tell the user:
> "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:*
```text
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:
```text
get_table_schema('UnsTagProviders')
list_protocols('mqtt')
```
Write the TagProvider object:
```json
{
"table_type": "UnsTagProviders",
"data": [
]
}
```
*Key decisions:*
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.
<!-- TODO: UnsAssetTree schema needs to be updated to include TagProviderLink parameters.
Current schema shows readOnly=true. The write mechanism requires:
```json
{
"table_type": "UnsAssetTree",
"data": [
]
}
```
*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:
```text
get_table_schema('DisplaysLayouts')
list_elements('Layout')
```
Write the Layout:
```json
{
"table_type": "DisplaysLayouts",
"data": [
]
}
```
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 and optional navigation controls:
```text
get_table_schema('DisplaysList')
list_elements('TextBlock,Button')
```
```json
{
"table_type": "DisplaysList",
"data": [
{
"Name": "Header",
"PanelType": "Canvas",
"Width": 1200,
"Height": 60,
"Description": "Application header bar",
"Objects": [
]
}
]
}
```
A Canvas display containing the AssetTree control that drives navigation:
```text
list_elements('AssetsTree')
```
```json
{
"table_type": "DisplaysList",
"data": [
{
"Name": "AssetTreeMenu",
"PanelType": "Canvas",
"Width": 300,
"Height": 700,
"Description": "Asset tree navigation panel",
"Objects": [
]
}
]
}
```
When the user clicks a node in the AssetsTree, the platform automatically updates:
These properties drive the content display reactively.
The MainPage uses `Asset()` bindings to react to the user's tree selection. This is the key pattern — all content is driven by `Client.Context.AssetPath`.
```text
list_elements('DataGrid,TrendChart,TextBlock,AlarmViewer')
```
*Dashboard layout approach (recommended for MainPage):*
```json
{
"table_type": "DisplaysList",
"data": [
{
"Name": "MainPage",
"PanelType": "Dashboard",
"Columns": 2,
"Rows": 3,
"Description": "Dynamic content — reacts to AssetTree selection",
"Objects": [
,
,
,
]
}
]
}
```
*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:*
```csharp
// 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")
```
```text
designer_action('start_runtime')
```
After starting:
1. The TagProvider connects to the MQTT broker and discovers topics
2. The AssetTree populates with the linked folder structure
3. Clicking any node in the tree updates `Client.Context` properties
4. The DataGrid, TrendChart, and other elements on MainPage react to the selection
Take a single screenshot to verify the layout renders correctly:
```text
get_screenshot('runtime')
```
```csharp
// Opening event — set initial context
void Opening()
{
// Optionally set a default branch to expand
@Client.Context.TreeInitialBranch = "MQTT/spBv1.0/GroupID";
}
```
```csharp
// Read a dynamic value based on context
public static double GetTemperature(string assetPath)
{
return TK.ToDouble(Asset(assetPath + "/Temperature"));
}
```
```csharp
// 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"));
```
1. `get_objects('UnsTagProviders')` — verify the TagProvider was created with correct PrimaryStation
2. `designer_action('navigate', 'Uns.AssetTree')` ? `get_designer_state()` — confirm the linked folder appears with the TagProvider reference
3. `designer_action('start_runtime')` ? `get_runtime_state()` — confirm the TagProvider connection is active
4. `get_screenshot('runtime')` — verify the AssetTree populates and the Layout renders correctly
5. Ask the user to click different nodes in the AssetTree and confirm the DataGrid updates dynamically
Mistake |
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 `list_protocols('mqtt')` first. The format is 14 semicolon-separated fields. |
DataGrid shows nothing |
LinkedValue not correctly bound to context |
Use `@Tag` with `LinkedContext` set to `@Client.Context.AssetPath`. The DataGrid needs both to show dynamic children. |
Topics with special chars not accessible |
MQTT topics like `spBv1.0/Group` contain dots |
Use quoted syntax: `Tag.ProviderName.("spBv1.0/Group/Node")` |
AssetTree empty after runtime start |
TagProvider not connected or linked folder not configured |
Verify the TagProvider connection in ServicesMonitor, then check that the UnsAssetTree folder has the correct TagProviderLink |
Trying to set alarms on dynamic tags |
Dynamic tags don't exist as local UnsTags |
If you need alarms on specific values, create a local tag and use a Script expression to copy the Asset() value into it. Or consider switching those specific points to Device Module approach. |
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*
*Variation C: Multiple Brokers*
*Variation D: Add Historian to Dynamic Tags*
> *?? This is a DRAFT (v0.1). The following items need resolution before publishing:*
>
> 1. *`connect_mqtt_explorer` designer_action*: New action needed — navigates to DataExplorer.MQTTTools and initiates connection with a PrimaryStation string. Not yet implemented.
>
> 2. *`get_designer_state()` for MQTT Tools*: Must return the user's selected nodes in the MQTT browser tree. This is how the AI reads which nodes the user wants to integrate.
>
> 3. *`UnsAssetTree` write support*: Currently `get_table_schema('UnsAssetTree')` returns `readOnly: true`. Need writable schema with fields:
> - `Name` (String) — folder name
> - `TagProviderLink` (String) — name of the TagProvider to link
> - `InitialPath` (String) — initial node path(s) from the provider tree
> - `Description` (String) — folder description
> - Exact field names TBD — may be `LinkedTo`, `Path`, or encoded in `Children` field.
>
> 4. *DataGrid `LinkedContext` property*: Verify exact property name and behavior — does the DataGrid have `LinkedContext` as a separate field, or is the asset-driven behavior configured differently?
>
> 5. *TrendChart Asset() binding*: How does a TrendChart dynamically bind its pens to the currently selected asset's children? Does it use `LinkedContext` similar to DataGrid?
>
> 6. *Layout field names*: Verify exact field names for `DisplaysLayouts` — is it `Header`, `Menu`, `Content` or different property names? Need `get_table_schema('DisplaysLayouts')` confirmation.
>
> 7. *Template integration*: The skill currently builds the Layout from scratch. If solution templates with built-in Header+AssetTree+Content are available, reference them as a shortcut.]]></ac:plain-text-body></ac:structured-macro>
<p class="auto-cursor-target"><br /></p>