title: "New Solution — First Solution Creation 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"
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:
Use when:
Do NOT use when:
| Category | Items |
|---|---|
| Tools | open_solution, get_table_schema, write_objects, get_objects, list_protocols, list_elements, designer_action, get_solution_info |
| Tables | UnsTags, DevicesChannels, DevicesNodes, DevicesPoints, HistorianHistorianTags, HistorianHistorianTables, AlarmsItems, DisplaysList |
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.
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.
The Value Simulator generates changing data for testing — no real device needed. It's a built-in protocol.
list_protocols('ValueSimulator')
This returns the protocol-specific field formats for Channel, Node, and Point configuration. Never guess these formats — always fetch first.
get_table_schema('DevicesChannels,DevicesNodes,DevicesPoints')
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.
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')
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):
DeadBand: minimum value change before logging (0 = log every change)Deviation: overrides interval-based logging when value changes significantlyAlarms 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 path — Plant/Tank1/Level, not bare Level.
Repeat a similar pattern for Tank2 tags as appropriate.
Now build the operator interface. For a starter solution, write directly into the predefined MainPage display.
list_elements('Dashboard,TrendChart,CircularGauge,TextBlock')
get_table_schema('DisplaysList')
{
"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.
designer_action('start_runtime')
The Value Simulator starts feeding changing data. Historian begins logging. Alarms evaluate against the setpoints. The dashboard shows live values.
After starting runtime:
get_solution_info() — confirm object counts match expectationsdesigner_action('navigate', 'Displays', 'Draw', 'MainPage') — dashboard should show live gauge valuesdesigner_action('navigate', 'Alarms', 'AlarmsMonitor') — alarms should evaluate against limits when simulator values cross setpointsdesigner_action('navigate', 'Historian', 'HistorianMonitor') — confirm tag values are being loggedTo adapt this starter for a real device instead of Value Simulator:
list_protocols('ValueSimulator') with the target protocol (e.g., list_protocols('ModbusTCP'), list_protocols('OPCUA'))| Mistake | Why It Happens | How to Avoid |
|---|---|---|
| Guessing protocol field formats | Skipping list_protocols | Always call list_protocols before writing device tables |
| Bare tag names in DevicesPoints | Forgetting asset path | Always use full path: Plant/Tank1/Level |
| Creating unnecessary HistorianTables | Not checking defaults | Check get_objects('HistorianHistorianTables') first |
| Omitting PanelType on dashboard | Seems optional | Always set PanelType explicitly |
| Pre-fetching all schemas at once | Over-eager optimization | Fetch each module's schema only when ready to write it |
| Screenshotting to verify writes | Unnecessary self-validation | Trust write_objects success. User sees live updates |