title: "Getting Started — First Solution with Tags, Simulator, and Dashboard" tags: [getting-started, beginner, first-solution, simulator, tutorial] description: "Create a complete starter solution: tags, Value Simulator device, historian logging, basic alarms, and a dashboard display" version: "1.0" author: "Tatsoft"


What This Skill Does

Build a complete starter solution from scratch: create tags organized by asset, connect a Value Simulator for testing, configure historian logging and basic alarms, and create a dashboard to visualize everything.

This skill follows the Four Pillars build order:

  1. UNS (Tags) → 2. Industrial (Devices, Alarms, Historian) → 3. Business (Scripts) → 4. Client (Displays)

When to Use This Skill

Use when:

Do NOT use when:

Prerequisites

MCP Tools and Tables

CategoryItems
Toolsopen_solution, get_table_schema, write_objects, get_objects, list_protocols, list_elements, designer_action, get_solution_info
TablesUnsTags, DevicesChannels, DevicesNodes, DevicesPoints, HistorianHistorianTags, HistorianHistorianTables, AlarmsItems, DisplaysList

Implementation Steps

Step 1: Create the Solution

open_solution('MyFirstSolution', template='Blank')

Templates available:

After open_solution completes, the full engineering context is delivered. Proceed to the next step — do not pre-fetch schemas.

Step 2: Create Tags (Pillar 1 — UNS)

Fetch the tag schema first:

get_table_schema('UnsTags')

Create tags organized by asset path using / folders:

{
  "table_type": "UnsTags",
  "data": [
    { "Name": "Plant/Tank1/Level",       "Type": "Double", "Description": "Tank 1 level %" },
    { "Name": "Plant/Tank1/Temperature", "Type": "Double", "Description": "Tank 1 temp °C" },
    { "Name": "Plant/Tank1/Pressure",    "Type": "Double", "Description": "Tank 1 pressure bar" },
    { "Name": "Plant/Tank1/ValveOpen",   "Type": "Boolean", "Description": "Inlet valve state" },
    { "Name": "Plant/Tank2/Level",       "Type": "Double", "Description": "Tank 2 level %" },
    { "Name": "Plant/Tank2/Temperature", "Type": "Double", "Description": "Tank 2 temp °C" },
    { "Name": "Plant/Tank2/Pressure",    "Type": "Double", "Description": "Tank 2 pressure bar" },
    { "Name": "Plant/Tank2/ValveOpen",   "Type": "Boolean", "Description": "Inlet valve state" }
  ]
}

Naming convention: Use consistent asset paths so the same display template can be reused for Tank1, Tank2, etc. via asset navigation.

Step 3: Connect Value Simulator (Pillar 2a — Devices)

The Value Simulator generates changing data for testing — no real device needed. It's a built-in protocol.

3a: Get Protocol Details

list_protocols('ValueSimulator')

This returns the protocol-specific field formats for Channel, Node, and Point configuration. Never guess these formats — always fetch first.

3b: Fetch Device Schemas

get_table_schema('DevicesChannels,DevicesNodes,DevicesPoints')

3c: Create the 3-Table Pipeline

Devices follow a strict pipeline: Channel → Node → Point. All three are required, and dependencies must exist before referencing objects.

Use multi-table write_objects to create all three in one call (dependency order is handled automatically):

[
  {
    "table_type": "DevicesChannels",
    "data": [{ "ObjectName": "SimChannel", "ProtocolName": "ValueSimulator" }]
  },
  {
    "table_type": "DevicesNodes",
    "data": [{ "ObjectName": "SimNode", "ChannelName": "SimChannel" }]
  },
  {
    "table_type": "DevicesPoints",
    "data": [
      { "ObjectName": "SimPoint_Level1",  "NodeName": "SimNode", "TagName": "Plant/Tank1/Level" },
      { "ObjectName": "SimPoint_Temp1",   "NodeName": "SimNode", "TagName": "Plant/Tank1/Temperature" },
      { "ObjectName": "SimPoint_Press1",  "NodeName": "SimNode", "TagName": "Plant/Tank1/Pressure" },
      { "ObjectName": "SimPoint_Valve1",  "NodeName": "SimNode", "TagName": "Plant/Tank1/ValveOpen" },
      { "ObjectName": "SimPoint_Level2",  "NodeName": "SimNode", "TagName": "Plant/Tank2/Level" },
      { "ObjectName": "SimPoint_Temp2",   "NodeName": "SimNode", "TagName": "Plant/Tank2/Temperature" },
      { "ObjectName": "SimPoint_Press2",  "NodeName": "SimNode", "TagName": "Plant/Tank2/Pressure" },
      { "ObjectName": "SimPoint_Valve2",  "NodeName": "SimNode", "TagName": "Plant/Tank2/ValveOpen" }
    ]
  }
]

Important: TagName in DevicesPoints must use the full tag path including asset folders: Plant/Tank1/Level — not bare Level.

Adapt the protocol-specific fields (Address, ProtocolOptions, etc.) based on what list_protocols('ValueSimulator') returned.

Step 4: Configure Historian (Pillar 2b)

4a: Check if Default Table Exists

Most solutions include a default StorageLocation and Table1:

get_objects('HistorianHistorianTables')

If Table1 exists, skip to 4b. If not, create the pipeline:

get_table_schema('HistorianStorageLocations,HistorianHistorianTables')

4b: Log Tags to Historian

get_table_schema('HistorianHistorianTags')
{
  "table_type": "HistorianHistorianTags",
  "data": [
    { "TagName": "Plant/Tank1/Level",       "TableName": "Table1" },
    { "TagName": "Plant/Tank1/Temperature", "TableName": "Table1" },
    { "TagName": "Plant/Tank1/Pressure",    "TableName": "Table1" },
    { "TagName": "Plant/Tank2/Level",       "TableName": "Table1" },
    { "TagName": "Plant/Tank2/Temperature", "TableName": "Table1" },
    { "TagName": "Plant/Tank2/Pressure",    "TableName": "Table1" }
  ]
}

Tuning tips (optional for a starter solution):

Step 5: Add Basic Alarms (Pillar 2c)

Alarms use predefined groups: Critical, Warning, AuditTrail.

get_table_schema('AlarmsItems')
{
  "table_type": "AlarmsItems",
  "data": [
    { "TagName": "Plant/Tank1/Level",       "GroupName": "Critical", "Condition": "HiHi", "SetPoint": 95, "Description": "Tank 1 level critical high" },
    { "TagName": "Plant/Tank1/Level",       "GroupName": "Warning",  "Condition": "Hi",   "SetPoint": 85, "Description": "Tank 1 level high warning" },
    { "TagName": "Plant/Tank1/Level",       "GroupName": "Warning",  "Condition": "Lo",   "SetPoint": 15, "Description": "Tank 1 level low warning" },
    { "TagName": "Plant/Tank1/Level",       "GroupName": "Critical", "Condition": "LoLo", "SetPoint": 5,  "Description": "Tank 1 level critical low" },
    { "TagName": "Plant/Tank1/Temperature", "GroupName": "Critical", "Condition": "HiHi", "SetPoint": 90, "Description": "Tank 1 temp critical high" },
    { "TagName": "Plant/Tank1/Temperature", "GroupName": "Warning",  "Condition": "Hi",   "SetPoint": 80, "Description": "Tank 1 temp high warning" }
  ]
}

TagName must use the full pathPlant/Tank1/Level, not bare Level.

Repeat a similar pattern for Tank2 tags as appropriate.

Step 6: Create Dashboard (Pillar 4 — Displays)

Now build the operator interface. For a starter solution, write directly into the predefined MainPage display.

6a: Fetch Element Schemas

list_elements('Dashboard,TrendChart,CircularGauge,TextBlock')
get_table_schema('DisplaysList')

6b: Write the Dashboard

{
  "table_type": "DisplaysList",
  "data": [{
    "ObjectName": "MainPage",
    "PanelType": "Dashboard",
    "Columns": 4,
    "Rows": 3,
    "Content": [
      {
        "Type": "TextBlock",
        "Text": "Plant Overview",
        "Column": 0, "Row": 0, "ColumnSpan": 4,
        "FontSize": 24, "HorizontalAlignment": "Center"
      },
      {
        "Type": "CircularGauge",
        "Value": "@Tag.Plant/Tank1/Level.Value",
        "Minimum": 0, "Maximum": 100,
        "Column": 0, "Row": 1,
        "Header": "Tank 1 Level"
      },
      {
        "Type": "CircularGauge",
        "Value": "@Tag.Plant/Tank1/Temperature.Value",
        "Minimum": 0, "Maximum": 100,
        "Column": 1, "Row": 1,
        "Header": "Tank 1 Temp"
      },
      {
        "Type": "CircularGauge",
        "Value": "@Tag.Plant/Tank2/Level.Value",
        "Minimum": 0, "Maximum": 100,
        "Column": 2, "Row": 1,
        "Header": "Tank 2 Level"
      },
      {
        "Type": "CircularGauge",
        "Value": "@Tag.Plant/Tank2/Temperature.Value",
        "Minimum": 0, "Maximum": 100,
        "Column": 3, "Row": 1,
        "Header": "Tank 2 Temp"
      },
      {
        "Type": "TrendChart",
        "Column": 0, "Row": 2, "ColumnSpan": 4,
        "Pens": [
          { "TagName": "@Tag.Plant/Tank1/Level", "Color": "#FF2196F3", "Label": "T1 Level" },
          { "TagName": "@Tag.Plant/Tank2/Level", "Color": "#FFFF9800", "Label": "T2 Level" }
        ]
      }
    ]
  }]
}

CRITICAL: PanelType is REQUIRED. Omitting it silently defaults to Canvas, which breaks Dashboard grid positioning.

Note: Verify TrendChart Pens schema against list_elements('TrendChart') output — the exact property names may vary.

Step 7: Start Runtime

designer_action('start_runtime')

The Value Simulator starts feeding changing data. Historian begins logging. Alarms evaluate against the setpoints. The dashboard shows live values.

Verification

After starting runtime:

  1. get_solution_info() — confirm object counts match expectations
  2. Navigate to MainPage: designer_action('navigate', 'Displays', 'Draw', 'MainPage') — dashboard should show live gauge values
  3. Navigate to AlarmsMonitor: designer_action('navigate', 'Alarms', 'AlarmsMonitor') — alarms should evaluate against limits when simulator values cross setpoints
  4. Navigate to HistorianMonitor: designer_action('navigate', 'Historian', 'HistorianMonitor') — confirm tag values are being logged

Adapting for Real Protocols

To adapt this starter for a real device instead of Value Simulator:

  1. In Step 3, replace list_protocols('ValueSimulator') with the target protocol (e.g., list_protocols('ModbusTCP'), list_protocols('OPCUA'))
  2. Configure Channel/Node with real connection parameters (IP, port, etc.)
  3. Map Points to real device addresses
  4. Everything else (Tags, Historian, Alarms, Dashboard) remains the same

Common Pitfalls

MistakeWhy It HappensHow to Avoid
Guessing protocol field formatsSkipping list_protocolsAlways call list_protocols before writing device tables
Bare tag names in DevicesPointsForgetting asset pathAlways use full path: Plant/Tank1/Level
Creating unnecessary HistorianTablesNot checking defaultsCheck get_objects('HistorianHistorianTables') first
Omitting PanelType on dashboardSeems optionalAlways set PanelType explicitly
Pre-fetching all schemas at onceOver-eager optimizationFetch each module's schema only when ready to write it
Screenshotting to verify writesUnnecessary self-validationTrust write_objects success. User sees live updates