matlab/matlab-build-industrial-hmi
> Build industrial-grade SCADA/HMI dashboards in MATLAB App Designer following source, write safeguards, fixed-range trends, drill-down layout. Use when wrapping OPC UA / Modbus / MQTT / OSI PI / PI AF monitoring scripts into a live App Designer app, building plant overviews, designing operator dashboards, or any time a user asks for a "SCADA dashboard", "HMI", "plant dashboard", "operator screen", or "industrial monitoring app" in MATLAB. uigauge, uilamp, alarm banner, gray-field, ISA-101, OPC UA dashboard, setpoint, write safeguards, alarm visualization, OSIsoft PI, AVEVA PI, PI Server, PI Data Archive, PI AF, PI Asset Framework, piclient, afclient.
npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-build-industrial-hmi
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).
opcuageneratedScript.mlx, modbusgeneratedScript.mlx)uigridlayout, callback wiring, lifecycle, matlab.apps.AppBase scaffolding) — use matlab-build-app instead. This skill assumes that knowledge.matlab-connect-opcua-client.[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.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. 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.
uitable.[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".[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.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.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.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.animatedline('MaximumNumPoints', 300) at 1 s update. Buffer formula = WindowSeconds / UpdatePeriodSeconds.yline (amber dashed for warn, red dashed for alarm, blue dotted for setpoint).writeValue() confirms. uiconfirm showing node + old + new + units; default option is Cancel, not Apply.Limits on the spinner enforces the bound; the uilabel makes it visible.references/write-safeguards-reference.md.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).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.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'.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).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+.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.Attribute.WriteAccess and HasTimeSeriesData, the same way OPC UA uses AccessLevelCurrent. Don't infer writability from the name.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.
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.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() accumulates a channel listener; rebuilds leak.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.
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.
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.references/widget-selection-flowchart.md.references/color-and-layout-rules.md.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.references/alarm-patterns.md.references/trend-config-reference.md.references/write-safeguards-reference.md.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.| 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 |
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.
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).
| 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 |
uifigure, uigridlayout, uigauge, uilamp, uispinner, uieditfield, uitable, uitree, uiconfirm, uialert, uiaxes, animatedline, addpoints, yline, timer.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.
Take matlab/matlab-build-industrial-hmi from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.