Operator screen
Settings
Station and plant settings with staged changes, supervisor signature and a change record.
index.htm as it is written with the smart-industrial package; industrial-demos.css is the screens' shared layout
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Settings - Smart.Industrial</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.default.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-elements.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-dark.css" />
<link rel="stylesheet" type="text/css" href="industrial-demos.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body class="viewport" theme="industrial-dark">
<div class="hmi-screen st-screen">
<header class="hmi-header">
<div class="hmi-identity">
<span class="hmi-plant">SETTINGS</span>
<span class="hmi-area">Station OS-2A · Area 2 · logged on: J. Moreau (Operator)</span>
</div>
<div class="hmi-header-actions">
<span class="st-pending" id="pendingLabel">No unsaved changes</span>
<button class="hmi-button" id="discard" type="button" disabled>Discard</button>
<smart-momentary-button id="apply" label="Apply changes" mode="confirm" kind="normal" interlocked interlock-reason="nothing to apply"></smart-momentary-button>
<button class="hmi-theme-toggle" id="themeToggle" type="button">Light</button>
</div>
</header>
<div class="st-body">
<nav class="hmi-panel st-nav" aria-label="Settings sections">
<button class="st-nav-item st-nav-active" type="button" data-section="station">Station</button>
<button class="st-nav-item" type="button" data-section="alarms">Alarms</button>
<button class="st-nav-item" type="button" data-section="connection">Data connection</button>
<button class="st-nav-item" type="button" data-section="security">Security</button>
<button class="st-nav-item" type="button" data-section="record">Change record</button>
</nav>
<main class="st-sections">
<section class="hmi-panel st-section" data-section="station">
<div class="hmi-panel-header"><span>Station</span><span class="hmi-panel-note">Applied at once; the operator's own</span></div>
<div class="hmi-panel-content st-fields">
<smart-selector-switch id="theme" label="Theme"></smart-selector-switch>
<smart-selector-switch id="language" label="Language"></smart-selector-switch>
<smart-selector-switch id="units" label="Unit system"></smart-selector-switch>
<smart-selector-switch id="density" label="Target size"></smart-selector-switch>
<div class="st-field">
<span class="st-field-label">Screen lock after</span>
<div class="st-field-value"><b id="lockValue">15</b> min <button class="hmi-button st-edit" type="button" data-setting="screenLock">Change</button></div>
</div>
<div class="st-field">
<span class="st-field-label">Trend default window</span>
<div class="st-field-value"><b id="windowValue">30</b> min <button class="hmi-button st-edit" type="button" data-setting="trendWindow">Change</button></div>
</div>
</div>
</section>
<section class="hmi-panel st-section" data-section="alarms" hidden>
<div class="hmi-panel-header"><span>Alarms</span><span class="hmi-panel-note">ISA-18.2 · changes need a supervisor's signature</span></div>
<div class="hmi-panel-content st-fields">
<div class="st-field">
<span class="st-field-label">Shelve duration</span>
<div class="st-field-value"><b id="shelveValue">30</b> min <button class="hmi-button st-edit" type="button" data-setting="shelve">Change</button></div>
</div>
<div class="st-field">
<span class="st-field-label">Flood threshold</span>
<div class="st-field-value"><b id="floodValue">10</b> alarms / 10 min <button class="hmi-button st-edit" type="button" data-setting="flood">Change</button></div>
</div>
<div class="st-field">
<span class="st-field-label">Priority 1 audible</span>
<smart-selector-switch id="audible" label=""></smart-selector-switch>
</div>
<div class="st-field">
<span class="st-field-label">Priority colours</span>
<div class="st-swatches">
<span class="st-swatch st-swatch-critical">Priority 1 · Critical</span>
<span class="st-swatch st-swatch-warning">Priority 2 · Warning</span>
<span class="st-swatch st-swatch-advisory">Priority 3 · Advisory</span>
</div>
<p class="hmi-panel-note">Fixed by the ISA-101 theme. A site that needs another palette changes the theme tokens, not the screen.</p>
</div>
</div>
</section>
<section class="hmi-panel st-section" data-section="connection" hidden>
<div class="hmi-panel-header"><span>Data connection</span><span class="hmi-panel-note">Smart.Industrial.Connect adapters</span></div>
<div class="hmi-panel-content st-fields">
<smart-selector-switch id="adapter" label="Adapter"></smart-selector-switch>
<label class="st-field">
<span class="st-field-label">Endpoint</span>
<input class="st-input" id="endpoint" type="text" value="wss://gw-02.northfield.local/tags" spellcheck="false" />
</label>
<div class="st-field">
<span class="st-field-label">Stale after</span>
<div class="st-field-value"><b id="staleValue">3.0</b> s <button class="hmi-button st-edit" type="button" data-setting="stale">Change</button></div>
</div>
<div class="st-field">
<span class="st-field-label">Poll interval</span>
<div class="st-field-value"><b id="pollValue">250</b> ms <button class="hmi-button st-edit" type="button" data-setting="poll">Change</button></div>
</div>
<smart-permissive id="connectionChecks" label="Before the connection changes" mode="permissive"></smart-permissive>
</div>
</section>
<section class="hmi-panel st-section" data-section="security" hidden>
<div class="hmi-panel-header"><span>Security</span><span class="hmi-panel-note">21 CFR Part 11 · changes need a supervisor's signature</span></div>
<div class="hmi-panel-content st-fields">
<div class="st-field">
<span class="st-field-label">Failed logons before lock</span>
<div class="st-field-value"><b id="attemptsValue">3</b> <button class="hmi-button st-edit" type="button" data-setting="attempts">Change</button></div>
</div>
<div class="st-field">
<span class="st-field-label">Continuous session</span>
<div class="st-field-value"><b id="sessionValue">15</b> min <button class="hmi-button st-edit" type="button" data-setting="session">Change</button></div>
</div>
<div class="st-field">
<span class="st-field-label">Signature required for</span>
<smart-selector-switch id="signFor" label=""></smart-selector-switch>
</div>
</div>
</section>
<section class="hmi-panel st-section st-record" data-section="record" hidden>
<div class="hmi-panel-header"><span>Change record</span><span class="hmi-panel-note" id="recordNote">Every applied change, who, why, and what it was before</span></div>
<smart-audit-trail id="record" label="Settings change record" show-review></smart-audit-trail>
</section>
</main>
</div>
<div class="st-overlay" id="overlay" hidden>
<smart-numeric-keypad id="keypad" density="touch"></smart-numeric-keypad>
</div>
<smart-esignature id="sign" label="Confirm the changes" lock-meaning meaning="approved" require-reason auto-close="1500"></smart-esignature>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.locale.de.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.locale.fr.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.locale.es.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.locale.zh.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.units.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.audit.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.selectorswitch.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.momentarybutton.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.numerickeypad.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.permissive.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.esignature.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.audittrail.js"></script>
<script type="module" src="index.js"></script>
<script>
/* Shown once every element has rendered, so nothing moves on the way in (styles/demos.css). */
addEventListener('load', function () {
var all = Array.prototype.slice.call(document.querySelectorAll('*')),
ready = function () { document.body.classList.add('ready'); };
Promise.all(all.map(function (e) { return e.localName.indexOf('smart-') === 0 ? customElements.whenDefined(e.localName) : null; })).then(function () {
return Promise.all(all.filter(function (e) { return typeof e.whenRendered === 'function' && !e.isRendered; }).map(function (e) { return e.whenRendered(); }));
}).then(function () { requestAnimationFrame(function () { requestAnimationFrame(ready); }); }, ready);
});
</script>
<script>
/* The site's light/dark switch is the reader's choice: a screen opened from the light site
in a new tab opens light too. Framed, the site's page sets the theme itself. */
addEventListener('load', function () {
var light = false,
toggle = document.querySelector('.hmi-theme-toggle');
try { light = window.top === window && localStorage.getItem('smart-industrial-dark') === 'false'; } catch (e) { }
/* after the screen's own onload, which is where its button starts listening */
if (light && toggle) { setTimeout(function () { if (document.body.getAttribute('theme') === 'industrial-dark') { toggle.click(); } }, 0); }
});
</script>
</body>
</html>
index.js
/// <reference path="../../../source/typescript/smart.elements.d.ts" />
/* Settings.
*
* Two kinds of setting, and the screen keeps them apart. A station preference - theme,
* language, units, target size - is the operator's own and applies at once. A plant
* setting - alarm shelving, the data connection, the security policy - is staged, shown
* as pending, applied through a confirm button, and where the policy says so signed by a
* supervisor before it takes effect. Every applied change lands on the record with its
* previous value, its reason and, when there was one, its signature.
*
* Numbers are entered on the keypad, which refuses a value outside the setting's range
* rather than clamping it. */
window.onload = function () {
const Industrial = window.Smart.Industrial,
Audit = Industrial.Audit,
el = function (id) {
return document.getElementById(id);
},
keypad = el('keypad'),
overlay = el('overlay'),
sign = el('sign'),
record = el('record'),
apply = el('apply');
const OPERATOR = { id: 'jmoreau', name: 'J. Moreau' };
/* ── The settings, and what each needs ─────────────────────────────── */
const SETTINGS = {
screenLock: { label: 'Screen lock', value: 15, min: 1, max: 120, precision: 0, unit: 'min', node: 'lockValue', signed: false },
trendWindow: { label: 'Trend default window', value: 30, min: 5, max: 480, precision: 0, unit: 'min', node: 'windowValue', signed: false },
shelve: { label: 'Shelve duration', value: 30, min: 5, max: 480, precision: 0, unit: 'min', node: 'shelveValue', signed: true },
flood: { label: 'Flood threshold', value: 10, min: 5, max: 60, precision: 0, unit: 'alarms / 10 min', node: 'floodValue', signed: true },
stale: { label: 'Stale after', value: 3, min: 0.5, max: 60, precision: 1, unit: 's', node: 'staleValue', signed: false },
poll: { label: 'Poll interval', value: 250, min: 50, max: 10000, precision: 0, unit: 'ms', node: 'pollValue', signed: false },
attempts: { label: 'Failed logons before lock', value: 3, min: 1, max: 10, precision: 0, unit: '', node: 'attemptsValue', signed: true },
session: { label: 'Continuous session', value: 15, min: 1, max: 60, precision: 0, unit: 'min', node: 'sessionValue', signed: true }
};
const pending = {};
/* ── The record ─────────────────────────────────────────────────────── */
const trail = new Audit.Trail({ system: 'OS-2A settings' });
trail.subscribe(function (entry) {
record.addEntry(entry);
el('recordNote').textContent = trail.length + ' changes recorded';
});
function log(field, oldValue, newValue, reason, signature, user) {
trail.append({
user: user ? user.id : OPERATOR.id, userName: user ? user.name : OPERATOR.name,
action: 'update', record: 'OS-2A', field: field, oldValue: oldValue, newValue: newValue, reason: reason, signature: signature
});
}
/* ── Station preferences: applied at once ───────────────────────────── */
const theme = el('theme'),
language = el('language'),
units = el('units'),
density = el('density');
theme.positions = [{ id: 'industrial-dark', label: 'Dark' }, { id: 'industrial', label: 'Light' }];
theme.value = 'industrial-dark';
language.positions = [{ id: 'en', label: 'English' }, { id: 'de', label: 'Deutsch' }, { id: 'fr', label: 'Français' }, { id: 'es', label: 'Español' }, { id: 'zh', label: '中文' }];
language.value = 'en';
units.positions = [{ id: '', label: 'As stored' }, { id: 'si', label: 'SI' }, { id: 'us', label: 'US customary' }];
units.value = '';
density.positions = [{ id: 'normal', label: 'Desk' }, { id: 'touch', label: 'Touch' }];
density.value = 'normal';
function immediate(element, field, applyFn) {
element.addEventListener('positionRequest', function (event) {
const from = event.detail.from,
to = event.detail.position;
element.value = to;
applyFn(to);
log(field, from, to, 'station preference');
});
}
immediate(theme, 'theme', function (value) {
document.body.setAttribute('theme', value);
el('themeToggle').textContent = value === 'industrial-dark' ? 'Light' : 'Dark';
});
immediate(language, 'language', function (value) {
Industrial.applyLocale(document.body, value);
});
immediate(units, 'units', function (value) {
Industrial.Units.system = value;
});
immediate(density, 'density', function (value) {
document.querySelectorAll('smart-selector-switch, smart-momentary-button, smart-numeric-keypad, smart-esignature').forEach(function (element) {
element.density = value;
});
});
/* ── Staged settings: change, then apply ────────────────────────────── */
let editing = null;
document.querySelectorAll('.st-edit').forEach(function (button) {
button.addEventListener('click', function () {
const key = button.getAttribute('data-setting'),
setting = SETTINGS[key];
editing = key;
keypad.label = setting.label;
keypad.unit = setting.unit;
keypad.min = setting.min;
keypad.max = setting.max;
keypad.precisionDigits = setting.precision;
overlay.hidden = false;
keypad.open(pending[key] !== undefined ? pending[key] : setting.value);
});
});
keypad.addEventListener('change', function (event) {
const setting = SETTINGS[editing];
overlay.hidden = true;
if (event.detail.value === setting.value) {
delete pending[editing];
}
else {
pending[editing] = event.detail.value;
}
render();
});
keypad.addEventListener('cancel', function () {
overlay.hidden = true;
});
overlay.addEventListener('click', function (event) {
if (event.target === overlay) {
keypad.cancel();
}
});
const audible = el('audible'),
signFor = el('signFor'),
adapter = el('adapter');
audible.positions = [{ id: 'on', label: 'On' }, { id: 'off', label: 'Off' }];
audible.value = 'on';
signFor.positions = [{ id: 'critical', label: 'Critical changes' }, { id: 'all', label: 'All setpoint changes' }];
signFor.value = 'critical';
adapter.positions = [{ id: 'websocket', label: 'WebSocket' }, { id: 'mqtt', label: 'MQTT' }, { id: 'opcua', label: 'OPC UA gateway' }, { id: 'simulator', label: 'Simulator' }];
adapter.value = 'websocket';
const STAGED_SWITCHES = { audible: { element: audible, label: 'Priority 1 audible', signed: true }, signFor: { element: signFor, label: 'Signature required for', signed: true }, adapter: { element: adapter, label: 'Adapter', signed: false } };
Object.keys(STAGED_SWITCHES).forEach(function (key) {
const entry = STAGED_SWITCHES[key];
entry.value = entry.element.value;
entry.element.addEventListener('positionRequest', function (event) {
/* Shown as requested until applied - the plant has not answered yet. */
if (event.detail.position === entry.value) {
delete pending[key];
}
else {
pending[key] = event.detail.position;
}
render();
});
});
el('endpoint').addEventListener('input', function () {
if (el('endpoint').value.trim() === 'wss://gw-02.northfield.local/tags') {
delete pending.endpoint;
}
else {
pending.endpoint = el('endpoint').value.trim();
}
render();
});
el('connectionChecks').conditions = [
{ id: 'k1', label: 'No batch running on this station’s units', satisfied: true },
{ id: 'k2', label: 'Endpoint reachable', satisfied: true },
{ id: 'k3', label: 'Certificate valid', satisfied: true }
];
function needsSignature() {
return Object.keys(pending).some(function (key) {
return (SETTINGS[key] && SETTINGS[key].signed) || (STAGED_SWITCHES[key] && STAGED_SWITCHES[key].signed);
});
}
function render() {
const count = Object.keys(pending).length;
Object.keys(SETTINGS).forEach(function (key) {
const node = el(SETTINGS[key].node),
staged = pending[key] !== undefined;
node.textContent = staged ? pending[key] : SETTINGS[key].value;
node.classList.toggle('st-staged', staged);
});
Object.keys(STAGED_SWITCHES).forEach(function (key) {
const entry = STAGED_SWITCHES[key];
/* Nothing pending: the switch shows the value in force again. */
if (pending[key] === undefined && entry.element.pending()) {
entry.element.value = entry.value;
}
});
el('pendingLabel').textContent = count ? count + ' unsaved change' + (count === 1 ? '' : 's') + (needsSignature() ? ' · supervisor signature needed' : '') : 'No unsaved changes';
el('discard').disabled = !count;
apply.interlocked = !count;
apply.interlockReason = count ? '' : 'nothing to apply';
}
el('discard').addEventListener('click', function () {
Object.keys(pending).forEach(function (key) {
delete pending[key];
});
el('endpoint').value = 'wss://gw-02.northfield.local/tags';
render();
});
apply.addEventListener('activate', function () {
const changes = Object.keys(pending).map(function (key) {
const setting = SETTINGS[key] || STAGED_SWITCHES[key] || { label: 'Endpoint', value: 'wss://gw-02.northfield.local/tags' };
return { key: key, label: setting.label, from: setting.value, to: pending[key] };
});
if (needsSignature()) {
sign.sign({ id: 'OS-2A settings', title: 'Settings change', fields: changes.map(function (change) {
return { label: change.label, value: change.from + ' → ' + change.to };
}) }).then(function (signature) {
commit(changes, signature.reason, signature, { id: signature.userId, name: signature.userName });
}, function () { });
}
else {
commit(changes, 'station change', null, OPERATOR);
}
});
function commit(changes, reason, signature, user) {
changes.forEach(function (change) {
if (SETTINGS[change.key]) {
SETTINGS[change.key].value = change.to;
}
else if (STAGED_SWITCHES[change.key]) {
STAGED_SWITCHES[change.key].value = change.to;
STAGED_SWITCHES[change.key].element.value = change.to;
}
log(change.label, change.from, change.to, reason, signature, user);
delete pending[change.key];
});
render();
}
/* The supervisor's directory. */
sign.addEventListener('signRequest', function (event) {
setTimeout(function () {
if (event.detail.userId === 'asmith' && event.detail.password === 'qa2026') {
sign.accept({ userName: 'A. Smith' });
}
else {
sign.reject(event.detail.userId === 'jmoreau' ? 'an operator cannot approve a policy change' : '');
}
}, 600);
});
record.addEventListener('reviewRequest', function (event) {
trail.append({ user: OPERATOR.id, userName: OPERATOR.name, action: 'review', record: 'OS-2A', field: 'settings record', newValue: event.detail.count + ' entries' });
});
/* ── Navigation ─────────────────────────────────────────────────────── */
document.querySelectorAll('.st-nav-item').forEach(function (item) {
item.addEventListener('click', function () {
const section = item.getAttribute('data-section');
document.querySelectorAll('.st-nav-item').forEach(function (other) {
other.classList.toggle('st-nav-active', other === item);
});
document.querySelectorAll('.st-section').forEach(function (panel) {
panel.hidden = panel.getAttribute('data-section') !== section;
});
});
});
el('themeToggle').addEventListener('click', function () {
theme.request(document.body.getAttribute('theme') === 'industrial-dark' ? 'industrial' : 'industrial-dark');
});
/* Seed the record with the last few changes. */
trail.append({ user: 'asmith', userName: 'A. Smith', action: 'update', record: 'OS-2A', field: 'Shelve duration', oldValue: 60, newValue: 30, reason: 'ISA-18.2 review', at: Audit.stamp(new Date(Date.now() - 86400000 * 3)), signature: { userId: 'asmith', userName: 'A. Smith', meaning: 'approved', at: Audit.stamp(new Date(Date.now() - 86400000 * 3)) } });
trail.append({ user: 'jmoreau', userName: 'J. Moreau', action: 'update', record: 'OS-2A', field: 'language', oldValue: 'fr', newValue: 'en', reason: 'station preference', at: Audit.stamp(new Date(Date.now() - 3600000 * 5)) });
render();
};
styles.css
/* Settings. A section rail and one section at a time; a staged value is shown as staged
until it is applied, because a setting the plant has not taken is not a setting yet. */
html,
body {
height: 100%;
margin: 0;
}
body.viewport {
background: var(--smart-industrial-canvas, var(--smart-body-background-color));
color: var(--smart-text-color);
font-family: var(--smart-font-family);
font-size: 13px;
}
.st-screen {
gap: 12px;
}
.st-pending {
font-size: 12px;
opacity: 0.85;
}
.st-body {
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
gap: 12px;
align-items: start;
}
@media (max-width: 900px) {
.st-body {
grid-template-columns: 1fr;
}
}
/* ── Rail ────────────────────────────────────────────────────────────── */
.st-nav {
display: flex;
flex-direction: column;
padding: 6px;
gap: 2px;
}
.st-nav-item {
text-align: left;
padding: 10px 12px;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--smart-text-color);
background: transparent;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
}
.st-nav-item:focus-visible {
outline: 2px solid var(--smart-industrial-focus);
}
.st-nav-active {
background: var(--smart-industrial-panel-header-background);
border-color: var(--smart-industrial-panel-border);
box-shadow: inset 3px 0 0 var(--smart-primary);
}
/* ── Sections ────────────────────────────────────────────────────────── */
.st-section[hidden] {
display: none;
}
.st-fields {
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.st-field {
display: flex;
flex-direction: column;
gap: 6px;
align-self: stretch;
}
.st-field-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0.85;
}
.st-field-value {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
}
.st-field-value b {
font-variant-numeric: tabular-nums;
font-size: 18px;
}
.st-staged {
color: var(--smart-industrial-alarm-warning-text);
}
.st-staged::after {
content: ' (pending)';
font-size: 11px;
font-weight: 400;
}
.st-edit {
min-height: 28px;
}
.st-input {
max-width: 420px;
min-height: 34px;
padding: 0 10px;
font: inherit;
font-family: Consolas, Monaco, monospace;
color: var(--smart-text-color);
background: var(--smart-industrial-panel-background, var(--smart-surface));
border: 1px solid var(--smart-industrial-panel-border);
border-radius: 4px;
}
.st-swatches {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.st-swatch {
padding: 4px 10px;
font-size: 12px;
font-weight: 600;
border-radius: 3px;
border: 1px solid currentColor;
}
.st-swatch-critical { color: var(--smart-industrial-alarm-critical-text); }
.st-swatch-warning { color: var(--smart-industrial-alarm-warning-text); }
.st-swatch-advisory { color: var(--smart-industrial-alarm-advisory-text); }
.st-record {
--smart-audit-max-height: 420px;
}
/* ── Keypad overlay ──────────────────────────────────────────────────── */
.st-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.45);
z-index: 30;
}
.st-overlay[hidden] {
display: none;
}
smart-esignature {
position: fixed;
right: 24px;
top: 80px;
z-index: 31;
}
Components on this screen
Audit TrailE-SignatureMomentary ButtonNumeric KeypadPermissiveSelector Switch