PRACTICAL GUIDES / UNS FRAMEWORK
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.
- Walkthrough: Describing a Small CNC Shop
- The UNS Framework Standard Definitions
- Templates & Reuse with YAML Anchors
- Generated MQTT Topics
- From YAML to a Running System
- Where factory descriptions live
- Recognize conflicting definitions
- Review changes to the shared model
- Use a readable definition format
- Scaling to Multiple Sites
- Where to go next
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:
- • 1 site — a factory in Detroit
- • 1 area — the machining department
- • 1 line — CNC Line 1
- • 1 cell — Cell A (a group of machines that work together)
- • 2 machines — a Haas VF-2 mill (cnc-01) and a DMG Mori lathe (cnc-02)
- • 1 sensor per machine — each publishing status, spindle speed, program, and tool data
- • 1 environment sensor — temperature monitoring in the cell
- • 1 MQTT broker — Eclipse Mosquitto running locally
- • 1 database — PostgreSQL for historical storage
- • 2 functions — state tracking and historical logging
- • 1 producer — a Node-RED flow reading from the PLCs
- • 1 consumer — the ERP system pulling production data
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
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.
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
| Definition | File | What It Describes | ISA-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.
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
| Scenario | Without Templates | With 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.
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
| Component | Reads From | What 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. |
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.
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.
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.
| Property | Monolithic Vendor Tools | UNS 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 |
Next Steps
| Resource | Description |
|---|---|
| UNS Framework Standard | The complete standard documentation — every definition type, every parameter, every schema |
| Core Definitions | Detailed reference for all 13 definition types |
| Full Example | A complete example deployment with all YAML files and generated MQTT topics |
| Digital Twins & GitOps | How YAML definitions and GitOps enable composable digital twins |
| Flow vs GitOps | Compare flows and functions for the implementation team |
| Getting Started | Deploy 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.