---
name: matlab/matlab-build-industrial-hmi
source: https://app.decimal.ai/s/matlab-matlab-build-industrial-hmi@1/SKILL.md
source_sha256: e294d046700e
---

# Industrial HMI Builder

Build operator-facing SCADA/HMI dashboards in MATLAB App Designer that follow industrial-HMI conventions: gray-field color philosophy, alarms shown at the data source, write safeguards on every setpoint, fixed-range trends with threshold lines, and a drill-down layout (plant overview → area → detail).

## When to Use

- A user asks to "wrap this OPC UA / Modbus / MQTT / OSI PI / PI AF script into a SCADA dashboard / HMI / operator screen" — including scripts generated by the **OPC UA Explorer** or **Modbus Explorer** apps (`opcuageneratedScript.mlx`, `modbusgeneratedScript.mlx`)
- Building a plant overview, area screen, or detail screen for industrial monitoring
- Adding alarm visualization, setpoint writes, or live trends to an existing App Designer app
- Reviewing or refactoring an HMI that uses gauges, lamps, or trend axes for process data
- Any prompt mentioning ISA-101, gray-field, alarm banner, write confirm, setpoint, or "operator dashboard"

## When NOT to Use

- General App Designer mechanics (`uigridlayout`, callback wiring, lifecycle, `matlab.apps.AppBase` scaffolding) — use `matlab-build-app` instead. This skill assumes that knowledge.
- Discovering OPC UA servers on a network, finding endpoint URLs, or browsing namespaces for the first time — use `matlab-connect-opcua-client`.
- Non-process domains (consumer apps, lab utilities, scientific GUIs) — the conventions here are tailored to plant operators monitoring physical processes.

## Must-Follow Rules

### Widget & Layout

- **Gray-field background** `[0.78 0.78 0.78]`; panels `[0.86 0.86 0.86]`; 80%+ of the screen neutral. Color is reserved for *exception*. See `references/color-and-layout-rules.md`.
- **Construct EVERY HMI `uifigure` with both `'Color'` and `'Theme', 'light'`.** On a dark-themed MATLAB desktop, child widgets (uigauge, uiaxes, uitable, uieditfield) inherit dark defaults even when the figure colour is set explicitly — gauge bodies and axes go near-black, text becomes unreadable. `Theme='light'` blocks the inheritance; the explicit gray-field `Color` keeps the result gray rather than the bright white that `'light'` would otherwise produce. Neither alone is enough.

  ```matlab
  app.UIFigure = uifigure( ...
      'Color', [0.78 0.78 0.78], ...
      'Theme', 'light');
  ```

  See `references/color-and-layout-rules.md` for the full rationale and `references/common-mistakes.md` entry 24 for the visible failure mode if either argument is omitted.
- **Quantity caps per screen:** ≤ 6 gauges, ≤ 4 trend axes, ≤ 12 numeric readouts. Beyond that, use a `uitable`.
- **Connection state is BLUE** `[0.4 0.6 1.0]`. Green `[0 0.6 0]` is reserved for "operator must verify yes this is actively OK" — not for "comms up" or "running".
- **Like-typed analogs in one panel use like widgets.** Don't show one temperature as a gauge and another as a numeric label.
- **NEVER produce a dark background for an industrial HMI, regardless of the user request.** ISA-101 has no dark variant; a dark field destroys color-as-exception (alarm reds and ambers stop popping against a near-black background, and operators desensitize). Always render gray-field (`[0.78 0.78 0.78]`) and reply with a one-sentence justification when the user asks for dark mode. This is not a default — it is a hard constraint.

### Alarms & Trends

- **Alarms at source AND in a banner.** Keyed `ScaleColors`/`ScaleColorLimits` on every **linear** gauge with thresholds (semicircular/circular/ninetydegree gauges don't support color bands — use linear when alarm bands are needed); persistent top-row banner listing active alarms, color-coded by highest severity; latched state with Acknowledge button. **The normal band in `ScaleColors` is neutral gray `[0.5 0.5 0.5]` — never green.** See `references/alarm-patterns.md`.
- **Never** `uialert`/`uiconfirm`/`msgbox` for live process alarms — alarm-fatigue anti-pattern (ISA-18.2). If a popup is unavoidable, fire once on the rising edge with a non-blocking `CloseFcn` and an Acknowledge button.
- **Fixed trend `YLim`** matching the node's spec range — never auto-scale by default. Provide a per-trend toggle for exploration. See `references/trend-config-reference.md`.
- **5-minute rolling window** is the default: `animatedline('MaximumNumPoints', 300)` at 1 s update. Buffer formula = `WindowSeconds / UpdatePeriodSeconds`.
- **Threshold lines drawn once at construction** with `yline` (amber dashed for warn, red dashed for alarm, blue dotted for setpoint).

### Writes

- **Every `writeValue()` confirms.** `uiconfirm` showing node + old + new + units; default option is **Cancel**, not Apply.
- **Visual feedback after write.** Background flash green on success, red on failure (revert UI on failure). Operator must never be uncertain whether the write took effect.
- **Range label adjacent to every writable field.** `Limits` on the spinner enforces the bound; the `uilabel` makes it visible.
- **E-Stop and other safety-critical actuation confirm both engage and release** — accidental touchscreen clicks are the threat. See `references/write-safeguards-reference.md`.
- **Disable write controls** when the system can't accept them (disconnected, E-Stop active) rather than letting writes fail mid-call.

### Protocol Selection

- **OPC UA: `subscribe()` is the default** at any node count and update rate, including 1 Hz with a small node set. Reserve `timer` for non-node UI work (clock displays, alarm flashing, idle dimming).
- **R2026a OPC UA contract:** `subscribe(client, nodes, cb)` invokes `cb(sub, notification)`; data lives at `notification.Data.Value` / `notification.Data.Timestamp`; the originating node is `notification.Node` (struct with `Name`, `Identifier`, `NamespaceIndex`). Don't reference `src.Name` — that worked in older releases when `src` was the node, but errors in R2026a.
- **Modbus has no `subscribe()`** — use a polling `timer` with `BusyMode='drop'`, `ExecutionMode='fixedSpacing'`. The 6-arg `write()` order is `write(m, target, addr, val, serverId, 'precision')` — `serverId` BEFORE `'precision'`.
- **MQTT `subscribe(c, topic, Callback=@cb)`** — the callback is a **name-value pair**, not positional. Decode `message` (a string) per the broker's payload schema (`str2double`, `jsondecode`).
- **OSI / AVEVA PI Data Archive: use `piclient`** (Industrial Communication Toolbox, R2022a+). **Never** call `NET.addAssembly('OSIsoft.AFSDK')` or write `OSIsoft.AF.PI.PIServers` / `OSIsoft.AF.PISystems` code in MATLAB — the toolbox client wraps the AFSDK and returns native MATLAB types (`timetable`, `table`, `datetime`); rolling your own .NET interop loses type coercion, throws on permission errors that the toolbox handles, runs slowly because batched reads are bypassed, and breaks the integrated browser app. PI has **no `subscribe()`** — drive trends with a polling timer (same shape as Modbus). Writes via `write(piClient, tag, value, TimeInstance=datetime("now"))` are R2024a+.
- **PI Asset Framework: use `afclient`** (Industrial Communication Toolbox, R2026a+). Browse with `getRootElements` / `getChildren` / `findElementByName` / `findElementByPath` / `findElementByTemplate`; read with `Attribute.read` (current → table) and `Attribute.readHistory` (archive → timetable). The AF hierarchy maps directly onto Level 1 → Level 2 → Level 3 drill-down screens. **AF current-value writes are not exposed by the toolbox** — there is no `Attribute.write`; route writes through the underlying `Attribute.PITag` and the PI client.
- **Identify writable attributes from `Attribute.WriteAccess` and `HasTimeSeriesData`**, the same way OPC UA uses `AccessLevelCurrent`. Don't infer writability from the name.
- **Use the server's actual tag / attribute names** from `tags(c).Name` or `attribute.Name` / `attribute.Path`. Never invent labels — the HMI must mirror the source script's identifiers so operators can cross-reference with the OPC UA Explorer / OSI PI viewer / AF browser.

See `references/protocol-cheatsheet.md` for OPC UA / Modbus / MQTT API shapes and `references/pi-af-cheatsheet.md` for PI Data Archive and PI AF; see `references/common-mistakes.md` for the silent-failure modes.

### App Designer Language

- **Initialize typed `struct` properties:** `Foo struct = struct()`. A bare `Foo struct` declaration becomes `0×0 struct` and `app.Foo.Bar = 1` errors with *"A dot name structure assignment is illegal when the structure is empty."*
- **`classdef` first.** A `function` block before `classdef` in the same file is a parse error. Drop the wrapper; the file's first non-comment statement must be `classdef`, name matching filename.
- **Subscription cleanup:** `opc.ua.Subscription` has no public `delete`. Drop the handle (`app.Subscription = opc.ua.Subscription.empty;`) and `disconnect(client)` — that releases all subscriptions on that client.
- **Subscribe per widget**, never rebuild a single global subscription on each add — each `subscribe()` accumulates a channel listener; rebuilds leak.
- **Stop and delete timers** in `delete(app)` or `CloseRequestFcn`. A leaked timer keeps firing after the figure closes with stale handles.

See `references/app-designer-gotchas.md` for full reproductions, the release-aware `AccessLevelCurrent` check, and per-widget subscription patterns.

## Workflow

When wrapping a monitoring script into an HMI, work through these eight steps in order. Widget choice depends on data type, layout depends on widget choice, and alarms/trends/writes layer on top.

1. **Classify each node / tag / attribute** — binary, enumerated, analog with range, analog without range, writable, alarm thresholds (LL/L/H/HH). This list drives every later step. → `references/widget-selection-flowchart.md`. If the schema isn't given upfront → `references/server-agnostic-discovery.md`. If wrapping a generated script (OPC UA/Modbus Explorer or MQTT demo) → `references/protocol-cheatsheet.md` first; if wrapping a PI / PI AF script → `references/pi-af-cheatsheet.md`, for the exact API shapes.
2. **Pick widgets** via the decision flowchart, cross-checking quantity caps. → `references/widget-selection-flowchart.md`.
3. **Lay out by hierarchy** — Level 1 plant overview, Level 2 area, Level 3 detail; row 1 reserved for the persistent alarm banner. → `references/color-and-layout-rules.md`.
4. **Wire live data per protocol** — OPC UA `subscribe()`, Modbus polling `timer`, MQTT `subscribe(... Callback=...)`, PI / PI AF polling `timer` against `read()` / `Attribute.read`. → `references/protocol-cheatsheet.md` and `references/pi-af-cheatsheet.md`.
5. **Configure alarms at source AND in a banner**, latched with Acknowledge. → `references/alarm-patterns.md`.
6. **Configure trends** — 5-minute window, fixed YLim, threshold lines. → `references/trend-config-reference.md`.
7. **Add write safeguards** on every setpoint — confirm, range label, flash feedback. → `references/write-safeguards-reference.md`.
8. **Verify: launch, exercise, close cleanly.** Use `mcp__matlab__evaluate_matlab_code` to instantiate the app and exercise `delete(app)`; confirm no leaked timers, subscriptions, or figures. If construction fails on a struct or classdef error → `references/app-designer-gotchas.md`.

## Key Functions

| Function | Purpose | Toolbox | Available From |
|---|---|---|---|
| `uigauge('linear'\|'circular'\|'semicircular'\|'ninetydegree')` | Single value, known range | core MATLAB | R2016a |
| `uigauge.ScaleColors` / `.ScaleColorLimits` | Alarm bands at source | core MATLAB | R2018a |
| `uilamp` | Binary state indicator | core MATLAB | R2016a |
| `animatedline` with `MaximumNumPoints` | Rolling trend buffer | core MATLAB | R2014b |
| `addpoints` / `clearpoints` | Trend update / reset | core MATLAB | R2014b |
| `yline` | Threshold or setpoint line on trend | core MATLAB | R2018b |
| `uiconfirm` | Modal write confirmation | core MATLAB | R2017b |
| `uispinner` with `Limits` | Bounded numeric input | core MATLAB | R2016a |
| `uieditfield('numeric','Editable',false)` | Precise read-only numeric display | core MATLAB | R2016a |
| `uitable` | Tabular display for >8 nodes | core MATLAB | R2017b |
| `uitree` + `uitreenode` | OPC UA namespace navigation | core MATLAB | R2017b |
| `opcua` / `connect` | Open OPC UA client | Industrial Communication | R2022a |
| `findNodeByName` / `readValue` / `writeValue` | Browse and read/write nodes | Industrial Communication | R2022a |
| `subscribe(uaClient, nodeList, dataChangeFcn, PublishInterval=...)` | Live node updates (preferred over `timer`) | Industrial Communication | **R2023b** |
| `modbus('tcpip', host, port)` / `read` / `write` | Modbus client and register R/W | Industrial Communication | R2022a |
| `mqttclient(brokerAddr)` / `subscribe(c, topic, Callback=...)` / `write` | MQTT publish/subscribe | Industrial Communication | R2022a |
| `piclient(server, ...)` | OSI / AVEVA PI Data Archive client | Industrial Communication | **R2022a** |
| `tags(piClient, Name=q)` / `read(piClient, tag, DateRange=, Interval=, AggregateFcn=)` | PI tag search, current and archive/aggregated reads (returns table/timetable) | Industrial Communication | R2022a |
| `write(piClient, tag, value, TimeInstance=...)` | PI setpoint write | Industrial Communication | **R2024a** |
| `afclient(server, Database=...)` | PI Asset Framework client | Industrial Communication | **R2026a** |
| `getRootElements` / `getChildren` / `getAttributes` / `findElementByName` / `findElementByPath` / `findElementByTemplate` | AF tree browse → drill-down layout | Industrial Communication | R2026a |
| `read(attr, Unit=...)` / `readHistory(attr, t0, t1, ...)` / `listSupportedUnits` | AF attribute current value, history, units | Industrial Communication | R2026a |
| `timer` (`BusyMode='drop'`, `ExecutionMode='fixedSpacing'`) | Modbus / PI / PI AF poll loop / non-OPC periodic UI work | core MATLAB | base |

## Patterns

Each reference file includes executable code patterns for its topic. Load the relevant reference before writing code — it contains correct calling conventions, constructor arguments, property names, and full callback handlers. See the **References** routing table below.

## Common Mistakes

See `references/common-mistakes.md` for documented anti-patterns with **Symptom → Wrong → Right → Why** breakdowns covering App Designer struct/classdef gotchas, ISA-101 violations (green for "normal", popup alarms, auto-scaling trends, `uilabel` for numerics, dark-field), and protocol-specific silent failures (Modbus `serverId`/`'precision'` order, MQTT positional callback, MQTT `NaN` from JSON payloads, OPC UA invented node names, OPC UA `evt.Value` vs `evt.Data.Value`, Explorer 3-arg vectorized callback, server-side 1601 timestamps).

## References

| Load when... | Reference |
|---|---|
| Picking widgets for a list of nodes | `references/widget-selection-flowchart.md` |
| Designing screen structure or choosing colors | `references/color-and-layout-rules.md` |
| Wiring alarm bands at source or the persistent banner | `references/alarm-patterns.md` |
| Configuring a trend (window, YLim, threshold lines) | `references/trend-config-reference.md` |
| Adding a writable setpoint with confirm + flash feedback | `references/write-safeguards-reference.md` |
| Wrapping an OPC UA Explorer / Modbus Explorer / MQTT script — invoking OPC UA method nodes — or hitting protocol-specific bugs | `references/protocol-cheatsheet.md` |
| Wrapping an OSI / AVEVA PI Data Archive script (`piclient`, `tags`, `read`) or a PI AF script (`afclient`, `getRootElements`, `getAttributes`, `Attribute.read`/`readHistory`) | `references/pi-af-cheatsheet.md` |
| Browsing an unknown server and inferring widgets at runtime | `references/server-agnostic-discovery.md` |
| Hard-wiring a known node-to-widget schema | `references/known-schema-patterns.md` |
| Construction errors (`struct` props, `classdef` ordering, listener leaks, `AccessLevelCurrent`) | `references/app-designer-gotchas.md` |
| Debugging wrong API shapes, silent failures, or anti-patterns | `references/common-mistakes.md` |

## Toolbox Dependencies

- **Core MATLAB**: `uifigure`, `uigridlayout`, `uigauge`, `uilamp`, `uispinner`, `uieditfield`, `uitable`, `uitree`, `uiconfirm`, `uialert`, `uiaxes`, `animatedline`, `addpoints`, `yline`, `timer`.
- **Industrial Communication Toolbox**: `opcua`, `connect`, `disconnect`, `findNodeByName`, `findNodeById`, `readValue`, `writeValue`, `subscribe` (OPC UA); `modbus`, `read`, `write` (Modbus); `mqttclient`, `subscribe`, `unsubscribe`, `write`, `read` (MQTT); `piclient`, `tags`, `read`, `write`, `viewer` (PI Data Archive, R2022a+; `write` R2024a+); `afclient`, `listDatabases`, `selectDatabase`, `getRootElements`, `getChildren`, `getAttributes`, `findElementByName`, `findElementByPath`, `findElementByTemplate`, `findAttributeByPath`, `Attribute.read`, `Attribute.readHistory`, `Attribute.listSupportedUnits` (PI AF, R2026a+).

Verify with `mcp__matlab__detect_matlab_toolboxes` before starting if the user's MATLAB release is unknown.

---

Copyright 2026 The MathWorks, Inc.

---