PRACTICAL GUIDES / UNS FRAMEWORK

YAML UNS Framework Standard GitOps ISA-95 ~20 min read

Describe a small factory in YAML

Create shared definitions for a CNC shop, from the site and machines to sensors and MQTT topics. Then reuse the structure across equipment.

You will produce a set of definition files. These describe the namespace; deploying brokers and connecting equipment are separate steps. Start with the worked example, then use the field references as needed.

Walkthrough: Describing a Small CNC Shop

Let's build a complete set of UNS Framework YAML definitions from scratch. We'll describe a small CNC machining shop with 2 machines in 1 area — enough to see every definition type in action, small enough to understand completely.

The Physical Reality

Our shop has:

Step 1: uns.yml — Define the Namespace

Start with the namespace itself. This file describes the top-level configuration — what version of the standard you're using, the topic structure, and the information model.

# uns.yml — the namespace definition
# This is the root of your factory-as-code description.

version: '1.0'

uns:
  name: 'Detroit CNC Shop'
  region: 'US'
  timezone: 'America/Detroit'
  topic_structure: 'version/site/area/line/cell/equipment/sensor/information_model/'
  topic_standard: 'ISA95'
  information_model: 'User Defined'

This tells anyone — human or machine — that this namespace follows ISA-95, uses a versioned topic structure, and is located in the US Eastern timezone. The topic_structure field is the blueprint for how every MQTT topic in the namespace will be constructed.

Step 2: site.yml — Describe the Physical Hierarchy

This is the most important file — it describes the physical structure of your factory, from site down to individual sensors. The hierarchy is nested, just like the physical reality.

# site.yml — the physical hierarchy
# Site → Area → Line → Cell → Equipment → Sensors

version: '1.0'

# ── Templates (defined once, reused everywhere) ──────────────

template_equipment: &template_equipment
  dataops:
    normalization:
      units: 'mm'
    transformation:
      timezone: 'utc'

template_sensor_cnc: &template_sensor_cnc
  information_model:
    status:
      data_type: string       # ACTIVE, IDLE, ALARM, SETUP, OFFLINE
    spindle_speed:
      data_type: float        # RPM
    feed_rate:
      data_type: float        # mm/min
    program:
      data_type: string       # Current program name
    tool:
      data_type: string       # Current tool ID
    spindle_load:
      data_type: float        # Percentage

template_sensor_env: &template_sensor_env
  information_model:
    temperature:
      data_type: float        # Celsius
    humidity:
      data_type: float        # Percentage

# ── Physical Hierarchy ────────────────────────────────────────

site:
  - name: Detroit
    id: detroit
    location: 'Detroit, MI, USA'
    area:
      - name: Machining
        id: machining
        line:
          - name: CNC Line 1
            id: line1
            cell:
              - name: Cell A
                id: cell-a
                equipment:
                  - id: 'cnc-01'
                    name: 'Haas VF-2 Mill'
                    <<: *template_equipment
                    sensors:
                      - id: 's1'
                        <<: *template_sensor_cnc

                  - id: 'cnc-02'
                    name: 'DMG Mori NLX 2500'
                    <<: *template_equipment
                    sensors:
                      - id: 's2'
                        <<: *template_sensor_cnc

                  - id: 'env-01'
                    name: 'Environment Sensor'
                    sensors:
                      - id: 's3'
                        <<: *template_sensor_env
The <<: *template_name syntax is a YAML merge key. It pulls in all the fields from the referenced anchor (&template_name). This means you define your sensor information model once and reuse it across every machine — the DRY (Don't Repeat Yourself) principle applied to factory definitions. We'll cover templates in detail in the next section.

Step 3: mqtt.yml — Describe the Broker

# mqtt.yml — MQTT broker configuration

version: '1.0'

mqtt:
  broker:
    host: 'mqtt://mqtt.detroit-factory.local'
    port: 1883
    username: 'uns-service'
    password: '${MQTT_PASSWORD}'  # Use env var for secrets

Step 4: database.yml — Describe the Historian

# database.yml — historical storage configuration

version: '1.0'

database:
  timeseries:
    host: 'postgres.detroit-factory.local'
    port: 5432
    username: 'uns'
    password: '${DATABASE_PASSWORD}'

Step 5: function.yml — Describe the Processing Functions

# function.yml — data processing functions

version: '1.0'

function:
  - name: uns-state
    endpoint: http://uns-state:8080
    # Tracks machine state transitions (ACTIVE → IDLE → ALARM)

  - name: uns-historian
    endpoint: http://uns-historian:8080
    # Logs every value change to PostgreSQL

  - name: uns-kpi
    endpoint: http://uns-kpi:8080
    # Calculates OEE, availability, performance

Step 6: producer.yml & consumer.yml — Describe Data Flow

# producer.yml — entities that send data into the namespace

version: '1.0'

producer:
  - name: Node-RED-PLC-Gateway
    endpoint: http://nodered.detroit-factory.local:1880
    # Reads Modbus from both CNCs, publishes to MQTT
# consumer.yml — entities that receive data from the namespace

version: '1.0'

consumer:
  - name: ERP-Production-Sync
    endpoint: opcua://erp.detroit-factory.local:4840
    # Pulls production counts and cycle times into ERP

The Complete File Structure

# Your factory, described as code

detroit-uns/
├── uns.yml          # Namespace: version, region, topic structure
├── site.yml         # Physical: site → area → line → cell → equipment → sensors
├── mqtt.yml         # Infrastructure: MQTT broker connection
├── database.yml     # Infrastructure: PostgreSQL connection
├── function.yml     # Processing: functions that act on the data
├── producer.yml     # Data flow: what sends data in
├── consumer.yml     # Data flow: what reads data out
└── README.md        # Documentation for the team

# Total: 7 YAML files. Your entire factory in a git repository.
# Any engineer can read it. Any tool can parse it.
# Every change is tracked. Every version is recoverable.
That's it. Seven YAML files describe your entire manufacturing namespace — the physical hierarchy, the data infrastructure, the processing functions, and the data flow. This is your single source of truth. Everything else — MQTT topics, dashboard configurations, function subscriptions — derives from these files.

The UNS Framework Standard Definitions

The UNS Framework defines a set of standard YAML definition types that together describe your entire manufacturing namespace. Each definition type maps to a specific concept in the ISA-95 hierarchy or the supporting infrastructure. Think of them as building blocks — you compose them to describe your specific factory.

The Definition Types

DefinitionFileWhat It DescribesISA-95 Level
UNS uns.yml The namespace itself — name, region, timezone, topic structure, topic standard, information model Enterprise
Site site.yml A physical or logical location — factory, plant, building Level 4 — Site
Area site.yml A subdivision within a site — building, floor, department, bay Level 3 — Area
Line site.yml A production line within an area Level 3 — Line
Cell site.yml A workstation or unit within a line Level 2 — Cell
Equipment site.yml A machine, device, or hardware that performs tasks Level 1 — Equipment
Sensor site.yml A data source attached to equipment — temperature, pressure, status, position Level 0 — Sensor
Function function.yml A self-contained block of code that processes data in the namespace Processing
Producer producer.yml An entity that sends data into the namespace — PLC gateway, Node-RED flow, OPC UA client Data source
Consumer consumer.yml An entity that receives data from the namespace — ERP, MES, dashboard, analytics Data sink
MQTT mqtt.yml The MQTT broker — host, port, credentials Infrastructure
Database database.yml The database for historical storage — host, port, credentials Infrastructure
Artifact artifact.yml Static data and reusable templates — equipment specs, sensor catalogues Reference data

How They Map to ISA-95

The physical hierarchy definitions (Site → Area → Line → Cell → Equipment → Sensor) map directly to the ISA-95 equipment hierarchy standard. This isn't accidental — ISA-95 is the most widely adopted standard for describing manufacturing operations, and the UNS Framework's YAML structure mirrors it exactly.

YAML DEFINITIONS → ISA-95 HIERARCHY → MQTT TOPICS YAML Definition ISA-95 Level MQTT Topic Segment uns.yml → version Enterprise v1.0/ site: name / id Level 4 — Site v1.0/detroit/ area: name / id Level 3 — Area v1.0/detroit/machining/ line → cell Level 3/2 — Line / Cell .../line1/cell-a/ equipment: id / name Level 1 — Equipment .../cell-a/cnc-01/ sensor: id + info model Level 0 — Sensor .../cnc-01/s1/status Each YAML definition maps to an ISA-95 level and generates an MQTT topic segment
The UNS Framework is not a software product — it's a descriptive standard. It doesn't replace your SCADA, MES, or MQTT broker. It describes what you have in a portable, version-controlled format that any system can consume. Think of it as a Dockerfile for your factory.

Templates & Reuse with YAML Anchors

In a real factory, you don't have one machine — you have dozens or hundreds, many of the same type. Without templates, you'd copy-paste the same sensor definition for every machine. YAML anchors and aliases solve this elegantly.

How Anchors Work

# Step 1: Define the template with an anchor (&)
template_sensor_cnc: &template_sensor_cnc
  information_model:
    status:
      data_type: string
    spindle_speed:
      data_type: float
    feed_rate:
      data_type: float
    program:
      data_type: string
    tool:
      data_type: string
    spindle_load:
      data_type: float

# Step 2: Reuse with an alias (*) and merge key (<<:)
equipment:
  - id: 'cnc-01'
    sensors:
      - id: 's1'
        <<: *template_sensor_cnc   # ← All 6 fields inherited

  - id: 'cnc-02'
    sensors:
      - id: 's2'
        <<: *template_sensor_cnc   # ← Same 6 fields, no copy-paste

  - id: 'cnc-03'
    sensors:
      - id: 's3'
        <<: *template_sensor_cnc   # ← And again. Define once, use everywhere.

Why Templates Matter at Scale

ScenarioWithout TemplatesWith Templates
10 identical CNC machines Copy-paste the sensor definition 10 times. 60+ lines of duplicated YAML. Define the template once (6 lines). Reference it 10 times (1 line each). 16 lines total.
Change the information model Find and update all 10 copies. Miss one? That machine's data is now inconsistent. Update the template once. All 10 machines inherit the change automatically.
Add a new sensor field Add it to all 10 copies manually. Hope you don't introduce a typo in copy #7. Add it to the template. Done. Every machine that references the template gets the new field.
New site with same machine types Copy the entire site.yml. The templates are embedded in the copy — changes don't propagate. The new site references the same templates. Consistency is guaranteed by the YAML structure.

Templates can also be composed. An equipment template defines dataops (normalisation, transformation), while a sensor template defines the information model. Combine them for a complete machine definition:

# Equipment template — how to process the data
template_equipment: &template_equipment
  dataops:
    normalization:
      units: 'mm'
      scale: 1.0
    transformation:
      timezone: 'utc'
      aggregation:
        method: 'average'
        window: 60

# Sensor template — what data the sensor produces
template_sensor_cnc: &template_sensor_cnc
  information_model:
    status: { data_type: string }
    spindle_speed: { data_type: float }

# Compose both templates on a single machine
equipment:
  - id: 'cnc-01'
    name: 'Haas VF-2 Mill'
    <<: *template_equipment       # ← Inherits dataops
    sensors:
      - id: 's1'
        <<: *template_sensor_cnc   # ← Inherits information model

Generated MQTT Topics

The YAML definitions don't just document your factory — they define the MQTT topic tree. Each level of the hierarchy becomes a segment in the topic path, following the topic_structure defined in uns.yml.

From our walkthrough example, the following MQTT topics are generated:

CNC-01 (Haas VF-2 Mill)

v1.0/detroit/machining/line1/cell-a/cnc-01/s1/status
v1.0/detroit/machining/line1/cell-a/cnc-01/s1/spindle_speed
v1.0/detroit/machining/line1/cell-a/cnc-01/s1/feed_rate
v1.0/detroit/machining/line1/cell-a/cnc-01/s1/program
v1.0/detroit/machining/line1/cell-a/cnc-01/s1/tool
v1.0/detroit/machining/line1/cell-a/cnc-01/s1/spindle_load

CNC-02 (DMG Mori NLX 2500)

v1.0/detroit/machining/line1/cell-a/cnc-02/s2/status
v1.0/detroit/machining/line1/cell-a/cnc-02/s2/spindle_speed
v1.0/detroit/machining/line1/cell-a/cnc-02/s2/feed_rate
v1.0/detroit/machining/line1/cell-a/cnc-02/s2/program
v1.0/detroit/machining/line1/cell-a/cnc-02/s2/tool
v1.0/detroit/machining/line1/cell-a/cnc-02/s2/spindle_load

Environment Sensor

v1.0/detroit/machining/line1/cell-a/env-01/s3/temperature
v1.0/detroit/machining/line1/cell-a/env-01/s3/humidity

Functions

v1.0/fn/uns-state
v1.0/fn/uns-historian
v1.0/fn/uns-kpi

Every topic is deterministic — given the YAML definitions, you can compute the exact topic tree. No manual configuration, no guessing, no "ask Dave." The YAML is the specification, and the topics are the implementation.

YAML DEFINITIONS → MQTT TOPIC TREE v1.0 detroit machining line1 cell-a cnc-01 / s1 status · spindle · feed · program · tool · load cnc-02 / s2 status · spindle · feed · program · tool · load env-01 / s3 temperature · humidity 14 MQTT topics generated deterministically from 7 YAML files

From YAML to a Running System

The YAML definitions are not just documentation — they're the contract between every component in your system. Here's how the definitions connect to a running fn-uns deployment.

How Each Component Uses the Definitions

ComponentReads FromWhat It Does With It
uns-sim (simulator) config.yaml (derived from site.yml) Generates realistic machine data for every equipment and sensor defined. The simulator's config mirrors the site hierarchy — areas, machines, sensors, programs.
uns-framework MQTT wildcard v1.0/# Subscribes to the entire namespace. The topic structure defined in uns.yml determines what the framework sees. New machines appear automatically — no configuration change needed.
uns-state / uns-historian Valkey cache + PostgreSQL The cache keys and database columns follow the topic structure. The YAML defines the namespace; the functions process whatever appears in it.
uns-input config.yaml The operator input screen is configured with YAML — reason codes, categories, theme colours, API endpoints. Same pattern: describe what you want, the system implements it.
Dashboards PostgreSQL queries Dashboard queries reference the topic structure. The YAML definitions ensure consistent naming — the dashboard knows that cnc-01 in the database matches cnc-01 in the YAML.
New developers The YAML files directly A new team member opens site.yml and immediately understands the factory structure — what machines exist, where they are, what data they produce. No tribal knowledge required.
These files describe the factory and namespace. Use the standard reference for their fields; use the implementation guides for the software that reads and publishes data.
🔧

Manufacturing Engineer

When you add a new machine to the shop floor, you add it to site.yml. That single change propagates everywhere — the simulator generates data for it, the framework processes it, the dashboard shows it. One file, one change, one source of truth.

💻

Developer

You're building a new function and need to know the topic structure? Open uns.yml. Need to know what sensors exist on cnc-01? Open site.yml. Need the database connection? Open database.yml. Everything is documented, parseable, and version-controlled. No more "ask Dave."

🖥️

IT Engineer

Deploying to a new site? git clone the repository, update the site-specific values in the YAML files (broker address, database credentials, site name), and docker compose up. The entire namespace is defined in the files — no manual broker configuration, no manual topic setup.

Background: shared definitions and version control

Where factory descriptions live

A factory may be described in equipment lists, tag configurations, maintenance records, and application settings. Those descriptions serve different purposes. A shared definition records the identifiers and relationships that several systems need to agree on.

Recognize conflicting definitions

The diagrams illustrate how separate records can disagree. They describe a possible integration problem, not a limitation of every manufacturing platform. Check which system owns each identifier and how updates reach the others.

THE MONOLITH PROBLEM — EVERY SYSTEM HAS ITS OWN MODEL Your Factory The actual physical reality SCADA proprietary binary MES vendor database ERP SAP / Oracle CMMS Maximo / SAP PM Spreadsheet "the master list" Every system disagrees. No single source of truth. No version control.

Review changes to the shared model

Keeping definitions in Git lets the team see proposed changes and their history. Decide who reviews a renamed machine, new signal, or changed payload, and how dependent applications will be updated.

Use a readable definition format

YAML gives these definitions a text format that people can review and software can parse. The framework reference describes the fields. Validation, generation, and deployment depend on the tools you connect to those files.

Scaling to Multiple Sites

The real power of YAML definitions becomes clear when you scale beyond a single site. Adding a second factory is not a new project — it's a new set of YAML files in the same repository.

Adding a Second Site

# site.yml — now with two sites

version: '1.0'

# Templates are shared across all sites
template_sensor_cnc: &template_sensor_cnc
  information_model:
    status: { data_type: string }
    spindle_speed: { data_type: float }
    feed_rate: { data_type: float }
    program: { data_type: string }
    tool: { data_type: string }
    spindle_load: { data_type: float }

site:
  # ── Site 1: Detroit ──────────────────────────────────────
  - name: Detroit
    id: detroit
    location: 'Detroit, MI, USA'
    area:
      - name: Machining
        id: machining
        line:
          - name: CNC Line 1
            id: line1
            cell:
              - name: Cell A
                id: cell-a
                equipment:
                  - id: 'cnc-01'
                    name: 'Haas VF-2 Mill'
                    sensors:
                      - id: 's1'
                        <<: *template_sensor_cnc
                  - id: 'cnc-02'
                    name: 'DMG Mori NLX 2500'
                    sensors:
                      - id: 's2'
                        <<: *template_sensor_cnc

  # ── Site 2: Munich ───────────────────────────────────────
  - name: Munich
    id: munich
    location: 'Munich, Bavaria, Germany'
    area:
      - name: Precision Machining
        id: precision
        line:
          - name: 5-Axis Line
            id: 5axis
            cell:
              - name: Cell 1
                id: cell-1
                equipment:
                  - id: 'cnc-10'
                    name: 'DMG Mori DMU 50'
                    sensors:
                      - id: 's10'
                        <<: *template_sensor_cnc
                  - id: 'cnc-11'
                    name: 'Hermle C 400'
                    sensors:
                      - id: 's11'
                        <<: *template_sensor_cnc

The git diff for this change tells the whole story:

# git diff site.yml

+ # ── Site 2: Munich ───────────────────────────────────────
+ - name: Munich
+   id: munich
+   location: 'Munich, Bavaria, Germany'
+   area:
+     - name: Precision Machining
+       ...

# git log --oneline
b7e4a2f Add Munich site — 5-axis line with DMU 50 and Hermle C 400
a3f2c1d Initial Detroit site — 2 CNCs in Cell A

Multi-Site Topic Namespacing

Because the site ID is part of the topic path, topics from different sites are naturally namespaced:

# Detroit topics
v1.0/detroit/machining/line1/cell-a/cnc-01/s1/status
v1.0/detroit/machining/line1/cell-a/cnc-02/s2/status

# Munich topics
v1.0/munich/precision/5axis/cell-1/cnc-10/s10/status
v1.0/munich/precision/5axis/cell-1/cnc-11/s11/status

# Subscribe to everything at one site
v1.0/detroit/#

# Subscribe to everything across all sites
v1.0/#

Without YAML Definitions

Adding a second site means: fly someone to Munich, manually configure the MQTT broker, manually set up the SCADA tags, manually create the database tables, manually build the dashboards, hope the naming conventions match Detroit, document everything in a spreadsheet that will be outdated by next month.

With YAML + GitOps

Adding a second site means: add a new site entry to site.yml, commit, push. The templates guarantee the same information model. The topic structure guarantees consistent naming. The git history documents when Munich was added and by whom. Deploy with git clone + docker compose up.

MULTI-SITE YAML DEFINITIONS — ONE REPO, MANY FACTORIES Git Repository uns.yml · site.yml · mqtt.yml · function.yml git clone git clone Detroit 2 CNCs · 1 env sensor · 3 functions same YAML, same standard ✓ Munich 2 5-axis CNCs · 3 functions same YAML, same standard ✓ Site 3 — Future add to site.yml · git push same templates, same standard Shared templates guarantee consistency. Git guarantees traceability. YAML guarantees readability.

Where to go next

Use the shared definitions to make factory structure and signal meanings explicit. Review them with the teams who supply and consume the data, and keep them current as equipment and applications change.

PropertyMonolithic Vendor ToolsUNS Framework YAML
Source of truth Scattered across 6+ systems, each with a partial view One set of YAML files in a git repository
Version control None — changes are invisible and untrackable Full git history — every change has an author, date, and reason
Portability Locked in vendor-specific binary formats Plain text YAML — readable by any tool, any language, any platform
Reproducibility Manual configuration at each site, each time git clone → identical factory definition, instantly
Collaboration "Ask Dave" / check the spreadsheet on the shared drive Pull requests, code review, comments in the YAML itself
Automation Manual updates across multiple systems for every change Tools parse the YAML and act on it — topics, configs, dashboards derived automatically
Auditability No trail — "who changed the tag name?" is unanswerable git log + git blame — cryptographically signed, immutable history
Multi-site scaling Months of manual configuration per site Add a site entry to site.yml, push, deploy. Templates guarantee consistency.
Cost Vendor licenses per site, per device, per user Free — open standard, open source, plain text files
The UNS Framework standard is a Dockerfile for your factory. It doesn't replace your SCADA, MES, or MQTT broker — it describes what you have in a portable, version-controlled, human-readable format that becomes the single source of truth for your entire manufacturing namespace. Start with one site, one area, a few machines. The YAML grows with you.

Next Steps

ResourceDescription
UNS Framework StandardThe complete standard documentation — every definition type, every parameter, every schema
Core DefinitionsDetailed reference for all 13 definition types
Full ExampleA complete example deployment with all YAML files and generated MQTT topics
Digital Twins & GitOpsHow YAML definitions and GitOps enable composable digital twins
Flow vs GitOpsCompare flows and functions for the implementation team
Getting StartedDeploy the fn-uns reference pipeline — see the YAML definitions in action

Guide Version: 1.0 · Applies To: UNS Framework Standard v1.0, YAML definitions, GitOps, ISA-95 topic hierarchy

Last updated March 2026.