Matlab Build Industrial Hmi
MathWorksOfficialCreated by MathWorks
- Skill
- Workflow
- Automation and integrations
- BSD-2-Clause
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for creating operator-facing SCADA and HMI dashboards within MATLAB App Designer. It enforces industrial design standards, including gray-field color philosophy, alarm visualization at the data source, write safeguards for setpoints, and fixed-range trend configurations. The skill guides users through the process of wrapping monitoring scripts—such as those for OPC UA, Modbus, MQTT, or OSI PI—into functional operator screens, ensuring that the resulting applications adhere to established industrial monitoring patterns.
What it does
- Generates industrial HMI layouts using gray-field color themes.
- Configures alarm thresholds and persistent alarm banners for process monitoring.
- Implements write safeguards with confirmation dialogs and visual feedback for setpoint adjustments.
- Integrates live data streams from protocols like OPC UA, Modbus, MQTT, and OSI PI.
- Produces a formal HMI specification document for review before application construction.
- Delegates final app serialization to the matlab-build-app skill when available.
When to use it
- When you need to wrap monitoring scripts into a live operator dashboard.
- When building plant overviews or area-specific industrial monitoring screens.
- When an application requires ISA-101-aligned alarm visualization and trend configuration.
- When adding setpoint controls with safety-critical write confirmations to an existing app.
Advantages
- Ensures consistency across industrial dashboards by following ISA-101 design patterns.
- Reduces operator fatigue through standardized alarm and color conventions.
- Provides a clear, reviewable specification process before building the application.
- Supports integration with common industrial communication protocols.
Usage examples
- “Create a SCADA dashboard for my OPC UA plant monitoring script.”
- The agent will guide you through classifying your nodes, selecting appropriate widgets, and drafting an HMI specification for review before building the dashboard.
- “How do I add an alarm banner to my operator screen?”
- The agent will provide the pattern for implementing a persistent top-row alarm banner and configuring source-level alarm thresholds.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/matlab/matlab-build-industrial-hmi/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip matlab-build-industrial-hmi.zip -d .agents/skills
Documentation by MathWorks
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.AppBasescaffolding) and serialization to a real App Designer file — this skill delegates those tomatlab-build-app. Seereferences/app-designer-handoff.md. Two things stay here and must not be routed away: gray-field theming (never hand color/dark-mode tomatlab-apply-theme) and HMI trends (never hand them tomatlab-build-chart).Discovering OPC UA servers on a network, finding endpoint URLs, connecting or configuring security and certificate trust, or browsing the address space to find nodes for the first time — use
matlab-use-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. Seereferences/color-and-layout-rules.md.Construct EVERY HMI
uifigurewith both'Color', [0.78 0.78 0.78]and'Theme', 'light'. Neither alone is enough: on a dark-themed desktop child widgets (uigauge, uiaxes, uitable, uieditfield) inherit dark defaults even when the figure colour is set, and'light'alone yields bright white instead of gray-field. Code + rationale:references/color-and-layout-rules.md; failure mode:references/common-mistakes.mdentry 24.Quantity caps per screen: ≤ 6 gauges, ≤ 4 trend axes, ≤ 12 numeric readouts — beyond that, use a
uitable— and ≤ 5 saturated-color elements that the design chooses. One gauge'sScaleColorscounts as one element however many bands it has; the connection lamp's blue is comms state, not alarm state, and sits outside the budget. Fixed MATLAB chrome does not count (aLinearGaugeneedle draws saturated red at every value and no public property recolors it). Full rule:assets/hmi-spec-template.md§4.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".Command buttons/controls are neutral gray. Never a green Start / red Stop — buttons are always-visible chrome and carry no status, so coloring them spends the alarm colors and steals contrast from real alarms. Differentiate by label; show run/stopped state on a separate lamp. (Red E-Stop is the one convention-based exception.) See
references/color-and-layout-rules.md.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/ScaleColorLimitson every gauge with thresholds (Limitsmust cover every band — out-of-range bands are silently clipped); persistent top-row banner listing active alarms, color-coded by highest severity; latched state with an Acknowledge button — latching and its ACK control ship together or neither does. The normal band inScaleColorsis neutral gray[0.5 0.5 0.5]— never green. Seereferences/alarm-patterns.md.Never
uialert/uiconfirm/msgboxfor live process alarms — alarm-fatigue anti-pattern (ISA-18.2). If a popup is unavoidable, fire once on the rising edge with a non-blockingCloseFcnand an Acknowledge button.Fixed trend
YLimmatching the node's spec range — never auto-scale by default. Provide a per-trend toggle for exploration. Seereferences/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.uiconfirmshowing node + old + new + units; default option is Cancel, not Apply.Visual feedback after write is a desaturated flash that reverts —
[0.7 1 0.7]on success,[1 0.7 0.7]on failure, back to the normal face after ~0.4 s (and revert the value too on failure). Do not flash saturated green[0 0.6 0]or alarm red[0.85 0 0]: those are reserved for operator-verified-OK and active alarms, and a control that stays recolored reads as status rather than as an acknowledgement. Operator must never be uncertain whether the write took effect.Range label adjacent to every writable field.
Limitson the spinner enforces the bound; theuilabelmakes 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. Reservetimerfor non-node UI work (clock displays, alarm flashing, idle dimming).R2026a OPC UA contract:
subscribe(client, nodes, cb, PublishInterval=<s>)— always setPublishIntervalto the required rate (seconds; default 1) — invokescb(sub, notification); data lives atnotification.Data.Value/notification.Data.Timestamp; the originating node isnotification.Node(struct withName,Identifier,NamespaceIndex). Don't referencesrc.Name— that worked in older releases whensrcwas the node, but errors in R2026a.Two callback shapes — match the source. When you write the
subscribecall, use the 2-argcb(sub, notification)above. When you paste an OPC UA Explorer–generateddataChangeCallback, it is 3-arg vectorized —cb(subObj, notification, ~)— andnotificationis a struct array (one entry per node changed this cycle): iteratenotification(i).Node.Name/notification(i).Data.Value; never treat it as scalar. Writing a 2-arg@(src, evt)for an Explorer script throws "Too many input arguments"; treatingnotificationas scalar reads only the first node. Seereferences/protocol-cheatsheet.md§OPC UA andreferences/common-mistakes.md#17.Modbus has no
subscribe()— use a pollingtimerwithBusyMode='drop',ExecutionMode='fixedSpacing'. The 6-argwrite()order iswrite(m, target, addr, val, serverId, 'precision')—serverIdBEFORE'precision'. Putting'precision'in slot 5 (or droppingserverIdon a non-default unit) throws "Expected input number 5, serverId, to be one of these types: double, single, …" — the single most common Modbus HMI bug.MQTT
subscribe(c, topic, Callback=@cb)— the callback is a name-value pair, not positional. Decodemessage(a string) per the broker's payload schema (str2double,jsondecode).OSI / AVEVA PI Data Archive: use
piclient(Industrial Communication Toolbox, R2022a+). Never callNET.addAssembly('OSIsoft.AFSDK')or writeOSIsoft.AF.PI.PIServers/OSIsoft.AF.PISystemscode 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 nosubscribe()— drive trends with a polling timer (same shape as Modbus). Writes viawrite(piClient, tag, value, TimeInstance=datetime("now"))are R2024a+.PI Asset Framework: use
afclient(Industrial Communication Toolbox, R2026a+). Browse withgetRootElements/getChildren/findElementByName/findElementByPath/findElementByTemplate; read withAttribute.read(current → table) andAttribute.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 noAttribute.write; route writes through the underlyingAttribute.PITagand the PI client.Identify writable attributes from
Attribute.WriteAccessandHasTimeSeriesData, the same way OPC UA usesAccessLevelCurrent. Don't infer writability from the name.Use the server's actual tag / attribute names from
tags(c).Nameorattribute.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
structproperties:Foo struct = struct(). A bareFoo structdeclaration becomes0×0 structandapp.Foo.Bar = 1errors with "A dot name structure assignment is illegal when the structure is empty."classdeffirst. Afunctionblock beforeclassdefin the same file is a parse error. Drop the wrapper; the file's first non-comment statement must beclassdef, name matching filename.No
function … endin inline evaluation. Function definitions are legal only inside code files, never in a command-window / inline context. When you sanity-check helper logic (severity bands, alarm math) throughevaluate_matlab_code, the transcript is a command-window context — a pastedfunction … endblock errors with"Function definitions are not supported in this context."Verify by exercising the helper from its file, or inline the arithmetic directly.Subscription cleanup:
opc.ua.Subscriptionhas no publicdelete. Drop the handle (app.Subscription = opc.ua.Subscription.empty;) anddisconnect(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.Teardown body lives in
delete(app);CloseRequestFcnonly routes to it. Stop and delete timers, drop subscription handles and disconnect clients insidedelete(app), then wireapp.UIFigure.CloseRequestFcn = @(~,~) delete(app);. Putting the body onCloseRequestFcninstead leaks everything whenever the app is closed programmatically — which is how every test, script and eval closes it. A leaked timer keeps firing after the figure closes with stale handles. Seereferences/common-mistakes.md#32.
See references/app-designer-gotchas.md for full reproductions, the release-aware AccessLevelCurrent check, and per-widget subscription patterns.
Spec, Review, and Output
Write the spec, get it approved, then build. Fill
assets/hmi-spec-template.mdinto<AppName>-hmi-spec.mdbeside the app, present a ≤10-line summary, and STOP for review. Build only after the user approves. Corrections go into the spec, never into a built app. That file is the hand-off contract.Build by invoking the
matlab-build-appskill under the exact name it appears as in your available-skills list — plugin-scoped (<plugin>:matlab-build-app) or bare; only the listed form resolves, so never type a guessed slash command. Hand it the approved spec, which pre-answers its discovery (UIFigure — an HMI is never UIHTML — plus.mlapp, archetype, users, timers). How it builds and what format it emits is its concern.Availability is that same list lookup, never a filesystem search. Not listed → emit the programmatic
.mapp yourself from the same approved spec; say it runs identically but won't open in App Designer, and that enabling thematlab-app-buildingplugin and rebuilding from the same spec produces a.mlapp. Never scan the disk for its skill folder — it ships in a different plugin (matlab-app-building), so a recursive search above this skill runs for tens of minutes and still misses.Editing an existing HMI is a delta, not a rebuild — patch the spec, confirm only changed rows, hand off the change.
references/app-designer-handoff.mdhas the division of labor, the hand-off protocol, and the component/behavior recipes the spec is filled from.
Workflow
When wrapping a monitoring script into an HMI, work through these steps in order. Widget choice depends on data type, layout depends on widget choice, alarms/trends/writes layer on top — and the finished design is written to a spec the user reviews before anything is built.
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.mdfirst; if wrapping a PI / PI AF script →references/pi-af-cheatsheet.md, for the exact API shapes.Pick widgets via the decision flowchart, cross-checking quantity caps. →
references/widget-selection-flowchart.md.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.Wire live data per protocol — OPC UA
subscribe()(self-written callback = 2-argcb(sub, notification); an Explorer-generateddataChangeCallback= 3-arg vectorized over a struct array), Modbus pollingtimer, MQTTsubscribe(... Callback=...), PI / PI AF pollingtimeragainstread()/Attribute.read. →references/protocol-cheatsheet.mdandreferences/pi-af-cheatsheet.md.Paint the screen before you connect. A client constructed against an unreachable host blocks ~21 s then throws, and no
Timeoutshortens it. Build the gray-field layout,drawnow, show "Connecting to<host>…" in the banner, and only then construct the client inside atry. Measurements:references/protocol-cheatsheet.md§Polling pattern.
Configure alarms at source AND in a banner, latched only alongside an Acknowledge control. →
references/alarm-patterns.md.Configure trends — 5-minute window, fixed YLim, threshold lines. →
references/trend-config-reference.md.Add write safeguards on every setpoint — confirm, range label, flash feedback. →
references/write-safeguards-reference.md.Write the HMI spec — fill
assets/hmi-spec-template.mdinto<AppName>-hmi-spec.mdbeside the app: signals, components with ISA-101 values, behaviors and wiring roles, build parameters. Self-audit section 5 first. →assets/hmi-spec-template.md.STOP — spec review gate. Present a ≤10-line summary plus the file path, then wait; revise the spec and re-present until the user approves, then stamp
Review status: APPROVED <date>. Proceed unprompted only on pre-approval, or when the session cannot take a reply (batch/eval run) — then leave itUNREVIEWEDand say so before building — summary first, then build. "Seems busy" is not a bypass.Build from the approved spec per "Spec, Review, and Output" above. →
references/app-designer-handoff.md.Verify by reading the built app back, then close it cleanly. Copy
assets/designReportReadback.mbeside the app verbatim, keeping its filename — it is a function; do not edit it, do not rename it, and do not re-author your own readback. Call it viamcp__matlab__evaluate_matlab_code:designReportReadback("<AppClass>", "<AppClass>-hmi-spec.md"). It reads the live figure and source, writesdesign-report.json, deletes the app and reports leaks. Confirm the file's first key is"reportFormat":"hmi-report/1"(self-check:references/common-mistakes.md#33), then compare it with the spec row by row and fix the app where they disagree. Never hand-write or reshape the numbers. If construction fails on a struct or classdef error →references/app-designer-gotchas.md.
Key Functions and Release Gates
references/key-functions.md lists every widget, client, and read/write call with its toolbox and
release, plus the four gates that change the design rather than the call — the floor is R2023b
(manifest.yaml), set by OPC UA subscribe(). Check it before writing a call on an unknown
install; mcp__matlab__detect_matlab_toolboxes confirms what's present.
Common Mistakes
references/common-mistakes.md is a grep-first lookup table of 33 documented anti-patterns — App Designer gotchas, ISA-101 violations, and protocol-specific silent failures — each a Symptom → Wrong → Right → Why block. Don't load it whole: Grep it for the user's error string, or find the anti-pattern in its index table, and read only the matching ### N. entry.
References
Each file carries executable patterns for its topic — calling conventions, constructor arguments, property names, full callback handlers. Load the relevant one before writing code.
| Load when... | Reference |
|---|---|
| Writing the HMI spec for review and hand-off | assets/hmi-spec-template.md — fill it; it's a form, not a source of domain rules |
| Verifying a built HMI against its spec (workflow step 11) | assets/designReportReadback.m (copy verbatim; see step 11); key contract in assets/design-report-schema.json |
| 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 |
Handing the approved spec to matlab-build-app, picking components/behaviors to fill it, or editing an existing app | references/app-designer-handoff.md |
| Debugging a specific error, silent failure, or anti-pattern | Grep references/common-mistakes.md for the error text or anti-pattern (it has an index table) — read only the matching entry, not the whole file |
| Checking whether a function exists in the user's release, or which toolbox ships it | references/key-functions.md — manifest.yaml carries the product requirement |
Copyright 2026 The MathWorks, Inc.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSELicence file added by AutoGineer1.4 KiB
- SKILL.md21.6 KiB
- assets/design-report-schema.json9.9 KiB
- assets/designReportReadback.m12.5 KiB
- assets/hmi-spec-template.md18.4 KiB
- manifest.yaml1.7 KiB
- references/alarm-patterns.md10.9 KiB
- references/app-designer-gotchas.md9.0 KiB
- references/app-designer-handoff.md17.7 KiB
- references/color-and-layout-rules.md16.3 KiB
- references/common-mistakes.md49.6 KiB
- references/key-functions.md4.1 KiB
- references/known-schema-patterns.md4.1 KiB
- references/pi-af-cheatsheet.md16.7 KiB
- references/protocol-cheatsheet.md19.1 KiB
- references/server-agnostic-discovery.md8.4 KiB
- references/trend-config-reference.md10.9 KiB
- references/widget-selection-flowchart.md6.6 KiB
- references/write-safeguards-reference.md9.2 KiB
references/app-designer-handoff.md
App Designer Build & Serialization — Hand-off to matlab-build-app
This skill owns what an industrial HMI is (gray-field, alarms-at-source, write
safeguards, fixed-range trends, protocol wiring). It does not build or serialize the
App Designer app itself — that is the matlab-build-app skill's job. Design the HMI here,
write the design into an HMI spec file the user reviews, then hand that approved file
over to be built and persisted.
The hand-off is a file, not a conversation: fill assets/hmi-spec-template.md into
<AppName>-hmi-spec.md. That file is the entire contract between the two skills — it can be
reviewed, corrected, version-controlled, and graded on its own, and either side can be tested
without the other.
matlab-build-app owns how the app is built and saved — creating the components,
applying their properties and callbacks, choosing the file format, and the whole
save/open/edit lifecycle. This skill does not need to know any of that and must not
restate it. Hand it the approved spec file; it consults its own guides for the mechanics
and produces the app.
This file specifies only the HMI-specific design: which components to use, which domain property values make them ISA-101-compliant, where they go, and the domain callback logic they carry. The spec template is the form those decisions are written into; the recipes below are the catalog they are chosen from.
Availability and fallback
matlab-build-app either appears in your available skills or it does not — that check is
free. Invoke it when it's listed; fall back when it isn't. Never search the filesystem
for it.
Invoke it under the exact name it appears as in your available-skills list — it may be
plugin-scoped (<plugin-name>:matlab-build-app) or bare. Only the listed form resolves, so
read the name off the list rather than typing a guessed slash command or hardcoding a prefix.
That same list entry is the availability check.
Listed → invoke it and hand it the approved spec file. It owns locating and loading its own build tooling, choosing the file format, and the whole save/open/edit lifecycle. This skill never names, paths, or loads any of that.
Not listed → fall back immediately to the programmatic
.mpath this skill's other references describe. Build it yourself from the same approved spec. No HMI content changes, just a different output format — the app runs identically, it just won't open in App Designer. Tell the user thematlab-build-appskill wasn't available, so the app is a.mclassdef rather than a.mlapp, and that enabling thematlab-app-buildingplugin and rebuilding from the same spec produces a.mlapp.
Do not probe the disk for its skill folder. A recursive search across the directories
above this skill can run for tens of minutes on a large or cloud-synced tree and still miss,
because in a normal install matlab-build-app ships in a different plugin
(matlab-app-building) rather than as a sibling of this skill. Absent from your skill list is
a sufficient and final answer.
Division of labor
| Concern | Owner | Notes |
|---|---|---|
| Writing the HMI spec, and getting it reviewed | HMI | the artifact, the review gate, and the corrections are this skill's |
| Building & serializing the app (components, properties, callbacks, file format, save/open/edit) | matlab-build-app | hand it the approved spec file; it owns all the mechanics |
| UIFigure structural mechanics (grid, panels, components, callback wiring) | matlab-build-app | it decides how the spec becomes an app |
| Editing an existing App Designer app | matlab-build-app | hand it the change; it edits in place rather than rebuilding |
| Layout archetype skeleton | matlab-build-app | plant overview ≈ Dashboard; area/detail ≈ Dashboard/Explorer |
| MVVM for large multi-screen HMIs | matlab-build-app (optional) | it decides when MVVM is warranted |
| Architecture (UIFigure vs UIHTML) | short-circuit | HMI is always UIFigure — don't ask |
| Serialization format choice | short-circuit | default .mlapp — don't ask unless the user wants source-controllable app files |
| Theming / colors / dark mode | HMI — override | gray-field is mandatory; never route to matlab-apply-theme |
| Trends | HMI — override | animatedline + fixed YLim + yline; never route to matlab-build-chart |
| Protocol wiring, alarms-at-source, write safeguards, widget selection, quantity caps | HMI | pure domain |
| Component choice + domain property VALUES + placement + callback bodies | HMI | the spec form — what to build, never how to emit it |
The two overrides are deliberate: matlab-build-app would normally delegate color to
matlab-apply-theme (which supports dark mode) and plots to matlab-build-chart
(general-purpose axes). Both conflict with HMI rules — keep them here.
Hand-off protocol
Design first, here. Classify nodes → pick widgets → lay out by hierarchy → plan alarms, trends, writes, protocol wiring, using this skill's references. Then express the result in the spec form, not in prose.
matlab-build-appnever decides HMI content.Write the spec. Fill
assets/hmi-spec-template.mdinto<AppName>-hmi-spec.mdnext to where the app will live. Sections 8 and 7 are filled from the component and behavior recipes below; section 5 is self-audited before you present anything. Keep it under 10 KB where the app allows — sections 1-6 are a fixed size, so trim section 8 first.STOP for review. Present a summary of at most ten lines plus the file path, then wait. Apply the user's corrections to the spec — never to a built app — and re-present. Stamp
Review status: APPROVED <date>once they agree. Proceed without waiting only when the user has pre-approved the build or the session cannot take a reply (a batch or eval run); in that case leave the statusUNREVIEWEDand say so in your summary. "The user seems busy" and "the prompt said build it" are not bypasses.Branch on the skill list. Listed → invoke
matlab-build-appunder its listed name and hand it the approved spec file. Not listed → build the programmatic.mapp yourself from that same spec. Either way, ask that it be shown in App Designer at the end so the user sees it rendered — offer it as "open it in App Designer so you can take a look."
Editing an existing HMI is a delta, not a rebuild. Threshold: at most 2 new signals, at most 3 new or changed components, and no new screen → delta path; anything larger gets a full spec and a full review.
Spec exists → patch only the affected rows in sections 2/7/8, add a section 9 change-log line, and present just the diff ("3 rows added, 1 changed — say go") for a one-line confirm. Untouched rows are not re-reviewed.
No spec exists (the HMI predates this, or came from elsewhere) → do not reverse-engineer a full spec. Write a delta spec covering only the touched items, headed
Existing app: <file>; components not listed are unchanged.Then gate on that.
Either way the section 5 self-audit is scoped to the touched rows. The what to add is HMI
domain; the how is matlab-build-app's job.
Component recipes — fill section 8 of the spec from these
This is the catalog, keyed by role; assets/hmi-spec-template.md section 8 is the instance,
one row per actual component. Look a role up here, then write the chosen component type and
its property values into the spec. Nothing here is copied into the spec wholesale.
Each recipe gives a role, the App Designer component type to use, the domain property values that make it ISA-101-compliant, and its placement. These are HMI content decisions, not build instructions — the values in the "Domain properties" column are the part that must not be delegated.
Gray-field figure
The figure is the single most important compliance surface. Set both Color and
Theme; containers are not transparent, so every GridLayout/Panel also carries
BackgroundColor or it paints a default gray block.
| Role | Component type | Domain properties (values) | Placement |
|---|---|---|---|
| Root figure | UIFigure | Color = [0.78 0.78 0.78]; Theme = light; Name = the station/screen title | root |
| Structural grid | GridLayout | BackgroundColor = [0.78 0.78 0.78]; rows/columns per archetype (banner row + content rows) | on the figure |
| Container panel / sub-grid | Panel / GridLayout | BackgroundColor = [0.78 0.78 0.78] (or [0.86 0.86 0.86] for a visually distinct secondary zone) | nested |
Theme='light'is mandatory. Gray-fieldColoralone does not satisfy the dark-theme defense: on a dark-themed MATLAB desktop, gauge bodies, axes, tables, and edit fields inherit dark defaults even with the figureColorset, andBackgroundColoron containers does not propagate into widget bodies.Theme='light'is the single-point fix. Set both on every HMI figure. Seecolor-and-layout-rules.md§Dark-theme defense andcommon-mistakes.mdentry 24.
ISA-101 palette — the only colors to use
Whatever component the spec calls for, its colors come only from the ISA-101 palette.
When specifying a color value, never free-hand "a red" or "a green" — use the exact triplets
in color-and-layout-rules.md §Gray-field palette, which is the single source for them
(critical, warning, comms blue, neutral gray, verified-OK green, page/panel gray). Read them
there rather than re-copying, so the two files can't drift. The rule that matters for the
spec: color means exception — blue (not green) for connection, gray for normal, green
reserved for operator-verified-OK only. (The gray-field page/panel values appear literally
in the figure rows above because they are the figure's defining build property, not a
per-component color choice.)
Process values, alarms, controls, status
| Role | Component type | Domain properties (values) | Placement |
|---|---|---|---|
| Alarm gauge (at source) | LinearGauge | Limits = node range; ScaleColors = {normal, warn, crit} — normal band is the neutral-gray palette color, never green; warn is the warning color, crit is the critical color; ScaleColorLimits = one row per band matching the node's warn/alarm thresholds | center / primary visualization grid |
| Analog readout, no alarm | LinearGauge | Limits = node range; single neutral-gray band over the full range | center grid |
| Binary state (valve, pump, run) | Lamp | Color = neutral gray idle; comms blue when on/connected; never green for "running". Always pair with an adjacent Label | with its label |
| Writable setpoint | Spinner | Limits = node range; Value = current; confirm-before-write in the callback (default option Cancel) | secondary zone (right/bottom panel), never the center grid |
| Command button | Button | Text differentiates (e.g. START/STOP); neutral face — default or panel gray, no green Start, no red Stop. Run state lives on a separate lamp, not the button | control zone |
| Status lamp (comms/running) | Lamp | Color = neutral gray idle → comms blue when up (never green). Always pair with a Label (e.g. "Disconnected") | status grid |
| Alarm banner | Label | Text = SYSTEM NORMAL (present even with no alarms); FontWeight = bold; HorizontalAlignment = center; BackgroundColor = neutral panel gray → critical color on active alarm; spans full width of row 1 | top row, spanning all columns |
| Alarm-list table | Table | ColumnName = {Timestamp, Node, Severity, Value, State}; Data driven from code | detail zone |
| High-count homogeneous readouts (> gauge cap) | Table | one row per node; conditional BackgroundColor styling for out-of-range | detail zone |
| Multi-area navigation | TabGroup + Tab | one Tab per plant area, plus an Overview tab; each area tab carries its own gauges and its own trend axes | on the main grid |
| Trend | UIAxes | created as a component, then drawn into from code per trend-config-reference.md, with all four of: animatedline; fixed YLim matching the sensor spec (never left on autoscale); an axis unit label (ylabel with units, xlabel time); no interactive axes toolbar — Toolbar.Visible='off' and Interactions=[], set in startup code, never as a component property: Toolbar is a handle to an AxesToolbar object, so an App Designer .mlapp save rejects it with MATLAB:class:RequireClass (Value must be 'matlab.graphics.Graphics'). Put it on the trend-rendering behavior row of spec section 7, not in a section 8 row; and — whenever the trended node has alarm thresholds — threshold ylines at the same warn/alarm values as the source gauge's ScaleColorLimits ('HandleVisibility','off'). A trend of an alarmed node with no threshold lines or no unit label is incomplete. Never route to matlab-build-chart | spans the trend row |
Bands must lie inside
Limits.LinearGaugeis the default alarm gauge, but every gauge style rendersScaleColors. Whatever the type,Limitsmust cover everyScaleColorLimitsrow — a band outside it is silently clipped. Full rationale:common-mistakes.mdentry 23.
Command buttons carry no status color — neutral face, no green Start / red Stop; run state lives on a lamp. The lone exception is a red E-Stop with a confirm dialog. Full rule + rationale:
color-and-layout-rules.md§Command buttons and controls are neutral; confirm dialog inwrite-safeguards-reference.md. Disable write controls when disconnected (Enable=off) in the disconnect handler.
Behavior recipes — fill section 7 of the spec from these
These are the MATLAB the app should contain — domain behavior this skill owns. You
supply each body and its role; how it gets installed is matlab-build-app's job. The role
tells it how to wire the body: an event handler on a component event (e.g. a setpoint's
value-changed callback), a method invoked by handle (e.g. a subscribe data-change
callback passed as @app.onData), or app state held as private properties initialized
in startupFcn. The code for each already lives in a reference file — read it there and hand
matlab-build-app the body with its role; do not restate the code here.
Spell the data-wiring contract out on the spec's live-data row, because it is the part a builder cannot infer and a reviewer needs to check: the update mechanism (subscription, or a polling timer and its period), the callback's argument shape, how one notification is dispatched to the right signal, and the call used to append a point to a trend.
Confirm-before-write — an event handler on a setpoint
Spinner'sValueChangedFcn; default option Cancel, echo old/new value and units, then a desaturated flash that reverts —[0.7 1 0.7]on success,[1 0.7 0.7]on failure, back to the normal face after ~0.4 s. Not verified-OK green[0 0.6 0]and not alarm red[0.85 0 0]: those are reserved, and a control left recolored reads as status rather than acknowledgement. Body + rationale:write-safeguards-reference.md.Live data handler — a
subscribedata-change callback invoked by handle, so a method (@app.onData), wired instartupFcn; the client/subscription/timer it touches are private properties. Body + OPC UA notification contract:app-designer-gotchas.md.Teardown — the body belongs in
delete(app)(stop and delete timers, drop the subscription handle, disconnect the client); the figure'sCloseRequestFcnis a one-line event handler that routes to it,@(~,~) delete(app). Specify it that way round on the spec row: putting the body onCloseRequestFcnleaks everything on programmatic close, which is how every test and eval closes the app. Body + lifecycle rules:app-designer-gotchas.md.
Trends (animatedline, fixed YLim, threshold ylines) are drawn into the UIAxes
component from startupFcn/handlers per references/trend-config-reference.md — not
configured as gauge properties.
Compliance self-audit
The compliance checklist is section 5 of the spec (assets/hmi-spec-template.md) — tick
every box there before presenting the spec for review. A build check confirms loadability, not
HMI correctness: every ISA-101 violation on that list still saves cleanly. If a box won't
tick, fix the spec, not the built app.
After the app is built, read it back rather than re-reading the spec: copy
assets/designReportReadback.m beside the app verbatim (keep the filename — do not edit,
rename or re-author it), and call the function with the app class and its spec file:
designReportReadback("<AppClass>", "<AppClass>-hmi-spec.md") (inline via evaluate_matlab_code
is fine — a function's local functions resolve when it is called). It constructs the app, pulls the ISA-101 values off the live figure, extracts the
live-data callback and the protocol write lines from the source, emits design-report.json, and
exercises delete(app) — so the teardown check above happens inside the same run, and leaked
figures and timers are counted rather than assumed.
The readback closes the gap this section opens: the spec's section 5 is what the design claims,
and the checklist is ticked before anything exists. The report is what the app is. Where they
disagree the report wins and the app gets fixed — the two ways to lose that check are hand-writing
the numbers and renaming or nesting the keys (common-mistakes.md #33). Key names, sentinel
values, and the four emission rules: assets/design-report-schema.json.
Copyright 2026 The MathWorks, Inc.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- MathWorks
- Publisher
- MathWorks (Official)
- Source
- matlab/matlab-agentic-toolkit · skills-catalog/test-and-measurement/matlab-build-industrial-hmi · commit 2a7f215
- Licence
- BSD 2-Clause License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by MathWorks.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-build-industrial-hmi.zip