udmi

UDMI / Docs / Guides / Define Presentation Attributes

Presentation Layer Configuration Guide

This guide explains how to use the embedded JSON schema attributes to control what information is displayed in the UI and how it’s organized.

The entire system is controlled by adding a special object named $presentation to properties within our schema files. A script reads these special objects to generate the final UI configuration.

Try out an interactive demo at đź§Ş Presentation Layer Demo

The Core Example: A Simple Device

Let’s start with a basic schema for a device. By default, nothing is visible.

device_model.json

{
  "title": "Device Model",
  "type": "object",
  "properties": {
    "device_id": {
      "type": "string",
      "description": "A unique identifier for the device."
    },
    "last_seen": {
      "type": "string",
      "description": "The last time the device was online."
    }
  }
}

Right now, both device_id and last_seen are hidden.

Step 1: Making a Property Visible (display)

To make a property appear, we add $presentation and set "display": "show". The system is opt-in, so you must explicitly show every property you want to see.

{
  //...
  "properties": {
    "device_id": {
      "type": "string",
      "description": "A unique identifier for the device.",
      "$presentation": {
        "display": "show"  // display this property
      }
    },
    "last_seen": {
      "type": "string",
      "description": "The last time the device was online."
    }
  }
  //...
}

Result:

Step 2: Grouping Properties (section)

You can group related properties into sections. The section attribute is hereditary - meaning child properties automatically belong to the same section as their parent.

Let’s add a location object and put it in a “Location Details” section.

{
  //...
  "properties": {
    "device_id": {
      // ... as before
    },
    "location": {
      "type": "object",
      "$presentation": {
        "display": "show",
        "section": "Location Details"
      },
      "properties": {
        "floor": {
          "type": "number",
          "$presentation": { "display": "show" }
        },
        "room": {
          "type": "string",
          "$presentation": { "display": "show" }
        }
      }
    }
  }
  //...
}

Result:

Step 3: Hiding a Group (display: hide)

Setting "display": "hide" on a parent property will hide it and all its children, no matter what their own settings are. This is useful for hiding entire blocks of diagnostic data.

Let’s add a diagnostics object that we don’t want to show.

{
  //...
  "properties": {
    "device_id": { /* ... */ },
    "location": { /* ... */ },
    "diagnostics": {
      "type": "object",
      "$presentation": {
        "display": "hide"
      },
      "properties": {
        "last_error_code": {
          "type": "string",
          // This 'show' will be IGNORED because the parent is hidden.
          "$presentation": { "display": "show" }
        }
      }
    }
  }
  //...
}

Result:

Advanced: Presenting Dynamic Maps (patternProperties)

Sometimes, we have objects where the keys are not fixed, like a map of software versions. We need to tell the system which specific keys from the map we want to display. This is done with $presentation.paths.

Use Case 1: Simple Maps

This is for maps where the value is a simple type (like a string). Let’s add a software_versions map to our device.

{
  //...
  "properties": {
    // ... other properties
    "software_versions": {
      "type": "object",
      "description": "A map of software component versions.",
      "patternProperties": {
        "^[a-z_]+$": { "type": "string" }
      },
      "$presentation": {
        "paths": {
          "firmware": { "style": "bold" },
          "operating_system": { "style": "bold" }
        }
      }
    }
  }
  //...
}

How it works:

Result:

Use Case 2: Complex Maps with Configuration Injection

This is the most advanced case. It’s used when a map’s value is another complex object, and you want to configure that child object differently depending on the parent key.

Let’s add network_protocols to our device. We’ll have different settings for bacnet and modbus. This requires two files.

How it works:

Result:


Adding a New Root Schema

A “root schema” is a top-level file that the script starts parsing from. You must explicitly tell the script which files are roots.

This list is managed in docs/guides/presentation_roots.md.

To add a new root schema (e.g., my_new_model.json):

Before:

{
  "roots": [
    "device_model.json"
  ]
}

After:

{
  "roots": [
    "device_model.json",
    "my_new_model.json"
  ]
}

The script will now parse both files when it runs.