Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

title

...

:

...

"MQTT

...

Discovery Services — Connect, Browse, and Build Asset Navigation" tags: [mqtt,

...

discovery-services, tagprovider,

...

sparkplugb, asset-navigation,

...

dynamic-tags

...

, uns, iiot, datagrid, trendchart] description:

...

"Connect

...

FrameworX to

...

an

...

MQTT

...

broker

...

via

...

Discovery Services,

...

explore

...

its

...

namespace with the built-in MQTT browser, link data into the AssetTree, and build a dynamic asset-navigation UI with DataGrid, TrendChart, and AlarmViewer — all driven by Asset() bindings." version: "1.0" author: "Tatsoft"

...

MQTT Discovery Services — Connect, Browse, and Build Asset Navigation

This skill walks through a complete MQTT Discovery Services integration: connecting to a broker, exploring its topic tree, linking data into the UNS AssetTree, and

...

building a dynamic asset-navigation UI

...

Warning
titleDRAFT v0.1

This skill has open items that require resolution before publishing. See the Open Items section at the bottom of this page.

...

width50%

What This Skill Does

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.

Code Block
languagetext
titleArchitecture
MQTT Broker ??? TagProvider ??? UNS Namespace (dynamic)
                                    ?
                              AssetTree Folder
                                    ?
                    ?????????????????????????????????
                  Header        AssetTree         MainPage
                 (Canvas)    (Navigation)     (DataGrid + Trend)
                                    ?
                          Client.Context.AssetPath
                          drives all content dynamically

where selecting a tree node changes all displayed data.

Prerequisite skill: Read skill-discovery-services first if you don't understand the difference between Device Module, DataLink, and Dynamic Tags.

When to Use This Skill

Use when:

  • The user wants to connect to an MQTT broker (external or built-in) using Discovery Services
  • The data structure is dynamic or unknown upfront — the broker defines what exists
  • The user mentions MQTT TagProvider, SparkplugB, IIoT, broker connection, or topic discovery
  • The user wants an asset-navigation UI where selecting a tree node changes displayed data
  • The application is monitoring/visualization focused

Do NOT use when:

  • The user wants explicit point-by-point control over specific MQTT topics with alarms on each — use Device Module with MQTT protocol (Channel → Node → Points)
  • The data source is not MQTT (for OPC UA Discovery Services, similar patterns apply but connection details differ — adapt from this skill)
  • The user just needs simulated data — use ValueSimulator with standard Tags

Prerequisites

  • Solution must be open (open_solution or create_solution completed)
  • An MQTT broker must be accessible (local or remote)
  • For testing: FrameworX has a built-in MQTT broker and a SparkplugB simulator

Implementation Steps

Step 0: Prepare the MQTT Broker (Testing or Production)

Option A: Built-in broker for testing/demo

Navigate to Data Explorer → MQTT Tools and start the built-in services:

designer_action('navigate', 'DataExplorer.MQTTTools')

From MQTT Tools, start the built-in broker and simulator using the tab actions:

designer_action('builtin_broker', 'start')
designer_action('builtin_simulator', 'start')

This creates a local MQTT broker on localhost:1883 and a SparkplugB simulator publishing sample data (solar panels, cities, sensors).

Option B: External production broker

Ask the user for:

  • Broker address and port
  • Authentication (username/password if needed)
  • TLS requirements
  • Whether the broker uses standard MQTT topics or SparkplugB

Step 1: Create the Discovery Services Connection

First, get the protocol schema to understand the PrimaryStation format:

get_table_schema('UnsTagProviders')
list_protocols('mqtt')

PrimaryStation format for MQTT — 14 semicolon-separated fields:

{Host};{Port};{ClientID};{Username};{Password};{CertFile};{KeyFile};{TLS};{CleanSession};{WillTopic};{QoS};{KeepAlive};{RetainPublish};{UseWebSocket};

Common configurations:

ScenarioPrimaryStation
Local built-in brokerlocalhost;1883;FrameworX-001;;;;;None;False;;AtLeastOnce;10;False;False;
Remote brokermqtt.factory.local;1883;FrameworX-001;;;;;None;False;;AtLeastOnce;10;False;False;
With authenticationbroker.cloud.io;8883;MyClient;admin;password123;;;TLS 1.2;False;;AtLeastOnce;30;False;False;
WebSocket connectionbroker.example.com;8083;MyClient;;;;;None;False;;AtLeastOnce;10;False;True;

Write the connection:

{
  "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"
  }]
}

For SparkplugB brokers, use MQTTspB protocol instead:

{
  "Name": "SparkplugBroker",
  "Protocol": "MQTTspB",
  "PrimaryStation": "localhost;1883;FrameworX-SpB;;;;;None;False;;AtLeastOnce;10;False;False;",
  "Separators": "BranchSeparator=/;AttributeSeparator=/;",
  "Description": "SparkplugB broker with richer metadata"
}

Naming decision: The Name becomes the root of the dynamic namespace — all discovered topics appear under Tag.<Name>.*. Choose a meaningful name: ProductionMQTT, FactoryBroker, EdgeGateway, etc.

Separators: BranchSeparator=/;AttributeSeparator=/; maps MQTT topic hierarchy to UNS path hierarchy. This is almost always correct for MQTT.

Step 2: Link to AssetTree

Create an AssetTree folder that links to the Discovery Services connection. This makes the data navigable in the AssetsTree UI control.

get_table_schema('UnsAssetTree')
{
  "table_type": "UnsAssetTree",
  "data": [{
    "Name": "Production/MQTTData",
    "TagProviderLink": "MQTT",
    "InitialBranch": "spBv1.0/GroupID",
    "Description": "MQTT production data"
  }]
}

Key fields:

  • Name — the folder path in the AssetTree. Users see this in the navigation tree.
  • TagProviderLink — must match the Discovery Services connection Name from Step 1 exactly.
  • InitialBranch — the starting node in the broker's topic tree. Everything below this path auto-expands into the folder. If empty, the entire broker namespace is linked.

From this folder down, the tree is dynamic — no further configuration needed.

Step 3: Build the Layout

The standard asset-navigation pattern uses a Layout with three regions: Header (top), Menu (left side with AssetTree), and Content (main area).

get_table_schema('DisplaysLayouts')
list_elements('Layout')

Read the existing startup layout first to understand current structure:

get_objects('DisplaysLayouts', detail='full', names=['Startup'])

Write or modify the layout:

{
  "table_type": "DisplaysLayouts",
  "data": [{
    "Name": "Startup",
    "Description": "Asset navigation — tree on left, dynamic content on right",
    "Members": [
      {"Region": "Header", "Docking": "Top", "Page": "Display.Header"},
      {"Region": "Menu", "Docking": "Left", "Page": "Display.AssetNav"},
      {"Region": "Content", "Page": "Display.MainPage"}
    ]
  }]
}

Note: Startup (layout ID 0) is the predefined startup layout — it loads on application launch. Read it first before modifying to preserve any existing configuration.

Step 4: Create the Header Display

A simple Canvas display with the application title and optional navigation buttons:

get_table_schema('DisplaysList')
list_elements('TextBlock,IndustrialIcon')
{
  "table_type": "DisplaysList",
  "data": [{
    "Type": "Display",
    "Name": "Header",
    "PanelType": "Canvas",
    "DisplayMode": "Page",
    "Width": 1200,
    "Height": 60,
    "Description": "Application header bar",
    "Elements": [
      {
        "Type": "TextBlock",
        "Name": "Title",
        "Left": 20,
        "Top": 15,
        "Text": "MQTT Production Monitor",
        "FontSize": 18,
        "FontWeight": "Bold"
      }
    ]
  }]
}

Use themed colors from list_elements('ThemeColors') instead of hardcoded hex values.

Step 5: Create the AssetTree Navigation Display

A Canvas display containing the AssetsTree control. When users click nodes, the platform updates Client.Context properties automatically.

list_elements('AssetsTree')
{
  "table_type": "DisplaysList",
  "data": [{
    "Type": "Display",
    "Name": "AssetNav",
    "PanelType": "Canvas",
    "DisplayMode": "Page",
    "Width": 300,
    "Height": 700,
    "Description": "Asset tree navigation panel",
    "Elements": [
      {
        "Type": "AssetsTree",
        "Name": "NavTree",
        "Left": 0,
        "Top": 0,
        "Width": 300,
        "Height": 700
      }
    ]
  }]
}

What happens when the user clicks a node:

PropertyDescriptionExample value
Client.Context.AssetPathFull path of selected nodeMQTT/spBv1.0/GroupID/Barcelona/Panel1
Client.Context.AssetNameDisplay name of selected nodePanel1
Client.Context.AssetNodeNameLeaf node namePanel1
Client.Context.SelectedTagFull tag path if a tag is selectedtag path string

These properties drive the content display reactively — all elements bound to them update automatically.

Step 6: Create the MainPage (Dynamic Content)

The MainPage uses Asset() bindings and Client.Context to react to the user's tree selection. One display serves the entire asset hierarchy.

list_elements('DataGrid,TrendChart,TextBlock,AlarmViewer')

Dashboard layout approach:

Column
width50%

On this page:

Table of Contents
maxLevel2
minLevel2
indent10px
stylenone

When to Use This Skill

Use this skill when:

  • The user wants to connect to an external MQTT broker (not create local simulated data)
  • The data structure is dynamic or unknown upfront — the broker defines what exists
  • The user mentions TagProvider, MQTT, IIoT, UNS integration, or auto-discovery
  • The user wants an asset-navigation UI where selecting a tree node changes the displayed data
  • The application is monitoring/visualization focused (not tight SCADA control)

Do NOT use this skill when:

  • The user wants explicit point-by-point control over specific MQTT topics — use Device Module approach instead (Channel → Node → Points)
  • The user needs local tags with alarms and historian on individual points with strict validation — use Device Module
  • The data source is not MQTT (for OPC UA TagProviders, similar patterns apply but connection details differ)
  • The user just needs a simple dashboard with simulated data — use ValueSimulator with standard Tags

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

Prerequisites

  • Solution must be open (open_solution or create_solution completed)
  • An MQTT broker must be accessible from the machine (local or remote)
  • For testing without an external broker: FrameworX has a built-in MQTT broker and a SparkplugB simulator that can be started from Data Explorer → MQTT Tools

MCP Tools and Tables Involved

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

Implementation Steps

Step 0: Discover the MQTT Broker Data (Interactive)

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:

Code Block
languagetext
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:

Code Block
languagetext
designer_action('navigate', 'DataExplorer.MQTTTools')

Connect to the broker:

Code Block
languagetext
designer_action('connect_mqtt_explorer', '<PrimaryStation string>')

The PrimaryStation format for MQTT is a semicolon-delimited string with 14 fields:

Code Block
languagetext
titlePrimaryStation format
{Host};{Port};{ClientID};{Username};{Password};{CertFile};{KeyFile};{TLS};{CleanSession};{WillTopic};{QoS};{KeepAlive};{RetainPublish};{UseWebSocket};

Common examples:

Scenario

PrimaryStation

Local built-in broker

localhost;1883;FrameworX-Explorer;;;;;None;False;;AtLeastOnce;10;False;False;

Remote broker

mqtt.factory.local;1883;FrameworX-001;;;;;None;False;;AtLeastOnce;10;False;False;

With authentication

broker.cloud.io;8883;MyClient;admin;password123;;;TLS 1.2;False;;AtLeastOnce;30;False;False;

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:

Code Block
languagetext
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.

Tip

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.

Step 1: Create the TagProvider Connection

Fetch the schema and protocol details:

Code Block
languagetext
get_table_schema('UnsTagProviders')
list_protocols('mqtt')

Write the TagProvider object:

Code Block
languagejson
titlewrite_objects — UnsTagProviders
{
  "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:

  • Name: This becomes the root of the dynamic namespace — Tag.MQTT.*. Choose a meaningful name (e.g., ProductionMQTT, FactoryBroker).
  • Separators: BranchSeparator=/;AttributeSeparator=/; maps MQTT topic hierarchy (plant/line1/temp) to UNS path hierarchy. This is almost always the correct default for MQTT.
  • PrimaryStation: Must match the connection details confirmed in Step 0.

After creation, the TagProvider immediately starts discovering topics when runtime starts. Topics appear as dynamic tags under Tag.<ProviderName>.<topic.path>.

Step 2: Create the Linked Folder in AssetTree

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.

Warning
titleSchema 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.

Code Block
languagejson
titlewrite_objects — UnsAssetTree (proposed)
{
  "table_type": "UnsAssetTree",
  "data": [
    {
      "Name": "Production",
      "TagProviderLink": "MQTT",
      "InitialPath": "spBv1.0/GroupID",
      "Description": "MQTT production data from SparkplugB broker"
    }
  ]
}

Key decisions:

  • Name: The folder name in the AssetTree — what operators see in the navigation tree.
  • TagProviderLink: Must match the TagProvider Name from Step 1 exactly.
  • InitialPath: The starting node(s) selected by the user in Step 0. Everything below this path in the broker automatically expands into the folder.

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.

Step 3: Build the Layout (Header + AssetTree + Content)

The standard pattern for asset-navigation applications is a Layout with three regions:

Code Block
languagetext
get_table_schema('DisplaysLayouts')
list_elements('Layout')

...

{
  "table_type": "

...

DisplaysList",
  "data": [

...

{

...

    

...

"

...

Type": "

...

Display",

...

    "

...

Name": "

...

MainPage",
    

...

"

...

PanelType": "

...

Dashboard",

...

    

...

"

...

Columns": 

...

2,
    "Rows": 3,
    "Description": "

...

Dynamic 

...

content 

...

 

...

reacts 

...

to 

...

AssetTree 

...

selection",
 

...

 

...

 

...

 

...

"Elements": [
      

...

{
 

...

The Layout Startup (ID 0) is the predefined startup layout — modifying it changes what loads on application launch.

Regions:

  • Header: Top bar — title, navigation buttons, status indicators
  • Menu (left side): The AssetTree control — users click here to navigate assets
  • Content (main area): The display that reacts to the selected asset

Step 4: Create the Header Display

A simple Canvas display with the application title:

Code Block
languagetext
get_table_schema('DisplaysList')
list_elements('TextBlock,Button')

...

languagejson
titlewrite_objects — Header display

...

       "Type": "TextBlock",
        "Name": "AssetTitle",
        "Row": 0,
        "Column": 0,
        "ColumnSpan": 2,
        "Text": "",
        "LinkedValue": "@Client.Context.AssetName",
        "FontSize": 20,
        "FontWeight": "Bold"
      },
      {
        "

...

Type": "

...

DataGrid",
        "

...

Name": "

...

AssetData",
        "

...

Row": 

...

1,
        "

...

Column": 

...

0,
        "

...

ColumnSpan": 

...

2,
        "

...

Height": 

...

300
      },
      {
        

...

"Type": "

...

TrendChart",

...

        "Name": "

...

AssetTrend",
        

...

"

...

Row": 

...

2,

...

        "

...

Column": 

...

0,
        

...

"

...

Height": 

...

250,

...

        "

...

Duration": 

...

"00:05:00"
      },
    

...

  {
        

...

"Type": "AlarmViewer",
      

...

  "Name": "AssetAlarms",
        "Row": 2,
        "Column": 1,
        "Height": 250
      }
    ]
  }]

...

}

The key pattern — DataGrid bound to context:

Step 5: Create the AssetTree Menu Display

A Canvas display containing the AssetTree control that drives navigation:

...

languagetext

The DataGrid displays all children (sub-tags and attributes) of whatever the user selected in the AssetTree. Check

list_elements('

...

DataGrid') for the exact property names to bind the DataGrid to Client.Context.AssetPath. The DataGrid automatically shows all tags below the selected path.

Asset() in CodeBehind (display events):

Public Sub DisplayOpening()
    ' Optionally set a default branch to expand on load
    @Client.Context.TreeInitialBranch = "MQTT/spBv1.0/GroupID"
End Sub

Step 7: Start Runtime and Verify

designer_action('start_runtime')

After starting:

  1. The Discovery Services connection contacts the MQTT broker and discovers topics
  2. The AssetTree populates with the linked folder structure
  3. Clicking any node updates Client.Context properties
  4. DataGrid, TrendChart, and other elements react to the selection

Take a screenshot to verify:

get_screenshot(target='runtime')

MQTT vs SparkplugB — Which Protocol?

AspectMQTT (standard)MQTTspB (SparkplugB)
Protocol name in FrameworXMQTTMQTTspB
Topic structureFlat or custom hierarchy`spBv1.0/GroupID/NBIRTH
MetadataTopic names onlyRich: data types, birth/death certificates, metrics
Discovery qualityBasic (topic tree)Full (typed variables with descriptions)
When to useGeneric MQTT brokers, custom topic schemesIndustrial IoT with SparkplugB-compliant edge nodes

If the broker publishes SparkplugB messages, always prefer MQTTspB — you get typed variables and richer metadata.

Variations

Variation A: Built-in Broker as Data Hub

Use FrameworX's built-in MQTT broker as a central data hub. External devices publish to it, and the Discovery Services connection reads from it — all on localhost.

{
  "Name": "LocalHub",
  "Protocol": "MQTT",
  "PrimaryStation": "localhost;1883;FrameworX-Hub;;;;;None;False;;AtLeastOnce;10;False;False;",
  "Separators": "BranchSeparator=/;AttributeSeparator=/;",
  "Description": "Built-in broker as central data hub"
}

Start the built-in broker from Runtime Startup or via designer_action('builtin_broker', 'start').

Variation B: Multiple Brokers

Create multiple Discovery Services connections, each with a different Name:

{
  "table_type": "UnsTagProviders",
  "data": [
    {"Name": "FactoryA", "Protocol": "MQTT", "PrimaryStation": "mqtt-a.factory.com;1883;..."},
    {"Name": "FactoryB", "Protocol": "MQTT", "PrimaryStation": "mqtt-b.factory.com;1883;..."}
  ]
}

Create separate AssetTree folders for each. The same Layout and MainPage pattern works — Client.Context handles any source.

Variation C: Hybrid — Dynamic Browsing + DataLink for Critical Points

Use Discovery Services for browsing the full MQTT namespace, but create local tags with DataLink for the few points that need alarms and historian:

{
  "table_type": "UnsTags",
  "data": [
    {
      "Name": "Critical/ReactorTemp",
      "DataType": "Float",
      "DataLink": "/MQTT/plant/reactor/temperature",
      "Min": 0,
      "Max": 500,
      "Description": "Reactor temperature — alarmed and historized"
    }
  ]
}

Then configure alarms and historian on Critical/ReactorTemp as a normal tag. The DataLink binds it to the MQTT path automatically.

Variation D: Add Historian Queries to TrendCharts

If connecting to a historian-capable Discovery Services connection (Canary, InfluxDB, etc.), TrendCharts using Asset() syntax automatically query historical data for the selected time range. Configure the Discovery Services connection with IsHistorian: true.

Common Pitfalls

MistakeWhy it happensHow to avoid
Creating local UnsTags for MQTT dataHabit from Device Module workflowDynamic tags don't need local tags. Only use DataLink for points needing alarms/historian.
Wrong PrimaryStation formatForgetting semicolons or field orderAlways call list_protocols('mqtt') first. The format is 14 semicolon-separated fields.
Topics with special chars not accessibleMQTT topics like spBv1.0/Group contain dotsUse quoted Asset() syntax: Asset("/ProviderName/spBv1.0/Group/Node")
AssetTree empty after runtime startConnection not established or linked folder misconfiguredCheck Services Monitor for connection status. Verify TagProviderLink matches the connection Name exactly.
Using MQTT protocol for SparkplugB brokerSparkplugB has specific message formatUse MQTTspB protocol for SparkplugB brokers — you get typed variables and metadata.
DataGrid shows nothingNot bound to Client.Context properlyCheck list_elements('DataGrid') for the correct binding properties.
Trying to set alarms on dynamic tagsNo local tag exists for dynamic dataCreate a local tag with DataLink for that specific path, then configure alarms on it.

Quick Reference — MCP Tool Calls

TaskTool call
Get MQTT protocol formatlist_protocols('mqtt')
Get SparkplugB formatlist_protocols('mqtt sparkplug')
Create Discovery Services connectionwrite_objects('UnsTagProviders', data=[...])
Create AssetTree linked folderwrite_objects('UnsAssetTree', data=[...])
Browse AssetsTree element schemalist_elements('AssetsTree')
Browse DataGrid schemalist_elements('DataGrid')
Navigate to MQTT Toolsdesigner_action('navigate', 'DataExplorer.MQTTTools')
Start built-in brokerdesigner_action('builtin_broker', 'start')
Start built-in simulatordesigner_action('builtin_simulator', 'start')
Check runtime stateget_state(target='runtime')
Navigate to Services Monitordesigner_action('navigate', 'ServicesMonitor')

Related Skills

  • skill-discovery-services — Conceptual foundation: three connection patterns, DataLink, Asset() syntax, when to use what
  • skill-display-construction — General display building patterns (Canvas/Dashboard, themed colors, element placement)

Documentation References

Code Block
languagejson
titlewrite_objects — AssetTree menu
{
  "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

Client.Context.AssetPath

MQTT/spBv1.0/GroupID/Barcelona/Panel1

Client.Context.AssetName

Panel1

Client.Context.AssetNodeName

leaf node name

Client.Context.SelectedTag

full tag path of the selected tag

These properties drive the content display reactively.

Step 6: Create the MainPage (Dynamic Content)

The MainPage uses Asset() bindings to react to the user's tree selection. All content is driven by Client.Context.AssetPath.

Code Block
languagetext
list_elements('DataGrid,TrendChart,TextBlock,AlarmViewer')
Code Block
languagejson
titlewrite_objects — MainPage dashboard
{
  "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:

Code Block
languagecsharp
titleAsset() usage patterns
// 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")

Step 7: Start Runtime and Verify

Code Block
languagetext
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:

Code Block
languagetext
get_screenshot('runtime')

Code Examples

Asset() in CodeBehind (Display Events)

Code Block
languagecsharp
titleOpening event — set initial context
void Opening()
{
    // Optionally set a default branch to expand
    @Client.Context.TreeInitialBranch = "MQTT/spBv1.0/GroupID";
}

Asset() in Scripts

Code Block
languagecsharp
titleRead dynamic value based on context
public static double GetTemperature(string assetPath)
{
    return TK.ToDouble(Asset(assetPath + "/Temperature"));
}

Data Type Handling for HTML5/Portable Clients

Code Block
languagecsharp
titleType conversion — WPF vs HTML5
// 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"));

Verification

  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

Common Pitfalls

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.

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 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)

Variations

Variation A: Mixed Approach (TagProvider + Device Module)

  • Use TagProvider for browsing and monitoring the full MQTT namespace
  • Use Device Module for specific critical tags that need alarms, historian, and strict validation
  • Both can connect to the same MQTT broker in the same solution
  • Useful when 90% of data is for monitoring, but 10% needs SCADA-grade control

Variation B: SparkplugB-Specific

  • Use MQTTspB protocol instead of MQTT in the TagProvider
  • SparkplugB provides richer metadata (data types, birth/death certificates)
  • Change Separators to match SparkplugB topic structure
  • Useful when the MQTT broker uses Sparkplug B specification

Variation C: Multiple Brokers

  • Create multiple TagProviders, each with a different Name and PrimaryStation
  • Create separate AssetTree linked folders for each provider
  • The same Layout and MainPage pattern works — Client.Context handles any source
  • Useful when aggregating data from multiple factories or edge gateways

Variation D: Add Historian to Dynamic Tags

  • For specific TagProvider paths that need historical trending, create a Historian TagProvider connection
  • TrendCharts can use Asset() syntax to automatically query historical data from historian TagProviders
  • Useful when you need long-term trending on external data without creating local tags

Related Skills

  • skill-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 Module

Documentation References

Open Items (Draft Notes)

Status
colourRed
titleDRAFT

#

Item

Status

1

connect_mqtt_explorer designer_action: New action needed — navigates to DataExplorer.MQTTTools and initiates connection with a PrimaryStation string. Not yet implemented.

Status
colourRed
titleTODO

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.

Status
colourRed
titleTODO

3

UnsAssetTree write support: Currently readOnly: true. Need writable schema with fields: Name, TagProviderLink, InitialPath, Description. Exact field names TBD.

Status
colourRed
titleTODO

4

DataGrid LinkedContext property: Verify exact property name and behavior — does DataGrid have LinkedContext as a separate field, or is asset-driven behavior configured differently?

Status
colourYellow
titleVERIFY

5

TrendChart Asset() binding: How does a TrendChart dynamically bind its pens to the currently selected asset's children?

Status
colourYellow
titleVERIFY

6

Layout field names: Verify exact field names for DisplaysLayouts — is it Header, Menu, Content or different property names?

Status
colourYellow
titleVERIFY

7

Template integration: If solution templates with built-in Header+AssetTree+Content exist, reference them as a shortcut.

Status
colourYellow
titleVERIFY
In this section...

...