VFD-0005 — VFD not in remote automatic control
| Status | verified — engine e2ff2f8, cxf:fnv1a128:a947f42d0ecc74fcad7c622f071d65fe, 2026-08-20 |
| Severity | 2 |
| Method | rule |
| Phase | 2 |
| Category | EXCESS_CONSUMPTION |
| Confidence | HIGH |
| Estimation | QUALITATIVE_ONLY |
| G36 | — |
| Clusters | — |
| Suppresses | VFD-0002, VFD-0003, VFD-0004 |
| Suppressed by | — |
| Related | VFD-0001, PMP-0006 |
| Playbooks | vfd-pump-faults |
| Source | PNNL Variable Speed Drives O&M Best Practice — BAS-connected drive operation and the consequence that active bypass supplies full voltage to the motor; Library-authored operating-mode watchdog distinguishing final enable, verified remote-auto authority, and confirmed active bypass; Brick 1.4.4 VFD_Enable_Command and Manual_Auto_Status; ASHRAE 223 artifact 1.0.0-ppr.2.1 binary command/operating-mode property patterns (see points/vfd.points.json) |
| Operating states | final drive enable commanded while remote automatic control is expected |
Preconditions (host-enforced): Remote BAS automatic operation must be expected for this exact interval. Exclude approved maintenance or emergency bypass, fire/smoke operation, commissioning, functional tests, manual tuning, and authorized local operation. vfd_enable_cmd must be the final drive-level request downstream of normal schedule and interlocks; a system enable several sequence layers upstream is insufficient. vfd_auto_status must prove the commissioned source/authority is remote BAS auto, not merely local PID auto, keypad auto, enabled, ready, or remotely on. vfd_bypass_status must prove the active motor power path, not bypass availability, readiness, or command. Unknown multi-state values and stale telemetry are NO_EVAL, not false. Suppression and evaluation must be scoped to the same drive instance.
Points: vfd_enable_cmd, vfd_auto_status, vfd_bypass_status
Outputs:
yFault— True after an enabled drive remains not-remote-auto or actively bypassed for mode_settle_timeyNotAuto— Immediate enable-gated diagnostic; true while the final enable is on and verified remote-auto status is falseyBypass— Immediate enable-gated diagnostic; true while the final enable is on and active bypass is proven
Parameters:
| Name | Default | Unit | CXF path | Description |
|---|---|---|---|---|
mode_settle_time | 300.0 | s | persist.delayTime | ADOPTED_TUNABLE: continuous enabled nonautomatic/bypass state required before alarm (5 min). No PNNL or manufacturer-universal default is claimed. |
Description
The BAS expects to regulate a running motor, but the drive is not accepting that authority or the motor has been transferred around the drive. Either state can defeat pressure/temperature reset, force an unintended fixed or full-line speed, and make otherwise useful command, saturation, and hunting diagnostics meaningless. The finding is operational rather than electrical: it says where control authority is, not why the operator or drive put it there.
Detection Logic
not_auto = NOT vfd_auto_status
yNotAuto = vfd_enable_cmd AND not_auto
yBypass = vfd_enable_cmd AND vfd_bypass_status
yFault = TrueDelay(yNotAuto OR yBypass, mode_settle_time)
The CXF uses an equivalent factored candidate, vfd_enable_cmd AND (not_auto OR vfd_bypass_status), so the final enable has one meaning throughout.
Block graph (rule.cxf.jsonld):
The direction outputs are immediate and enable-gated; they normally precede the fault by five minutes. A direct handoff from local mode to bypass does not reset the timer because the candidate OR never becomes false. Clearing enable or the last active mode branch drops every applicable output immediately.
Possible Diagnoses
- HOA/keypad selector left in hand/local after service.
- Drive command source configured for keypad, terminals, fieldbus, or internal PID instead of the commissioned BAS source.
- Active bypass contactor or integrated bypass mode left engaged.
- BAS mode mapping inverted or bound to an availability/command point rather than authoritative status.
- Normal emergency, fire/smoke, commissioning, or maintenance operation that the host failed to exclude.
Energy Impact
EXCESS_CONSUMPTION, HIGH confidence in the state when direct commissioned telemetry is present, QUALITATIVE_ONLY for magnitude. Bypass commonly applies full line voltage and local mode may defeat resets, but neither Boolean reports motor kW or load. Downgrade confidence or report NO_EVAL when the point binding cannot prove control authority and active power path.
Emissions Impact
Scope 2, qualitative. Any additional runtime or speed becomes purchased motor electricity. Use metered drive/motor energy during the verified state to size emissions; do not infer full-load kW from bypass alone.
Deviations
- This is a library-authored watchdog. PNNL supports BAS drive monitoring and documents bypass consequences but publishes neither this Boolean equation nor a universal five-minute delay.
- Automatic is not synonymous with remote BAS authority. Brick’s exact
Manual_Auto_Statusclass does not distinguish local automatic control from remote BAS control, sovfd_auto_statusremains provisional and deployment must verify the value mapping. - Brick has no exact bypass-status class in 1.4.4.
Bypass_Commandis not used because a request is not active-path proof. The point is provisional and may be backed by authoritative drive mode or bypass-contactor telemetry. - Diagnostics are enable-gated. Both flags are false while disabled; that means not applicable, not proof of healthy auto mode or an open bypass path.
- One shared delay follows the OR. A local-to-bypass handoff without a clean automatic tick preserves accumulated time. That is intentional because remote authority never returned.
- Approved operation is host-gated. Maintenance/emergency bypass has the same raw signature and the vectors pin that it alarms unless the host reports NO_EVAL.
- Suppression is broader than the roadmap’s VFD-0004 example. VFD-0002 and
VFD-0003 also require an active automatic loop, so this rule suppresses all
three same-drive inferences. Raw mode gates should apply immediately rather
than waiting for this rule’s delayed
yFault. persist.delayOnInit = true, so a drive already in local/bypass at engine start waits the full configured settle time.- No replay validation is claimed: the harness exposes no real auto-authority or bypass-path telemetry, and neither may be synthesized from speed.
Notes
Check this rule before tuning or chasing capacity. If yNotAuto is true, confirm
the selector position and configured command source; if yBypass is true,
confirm the contactor/power path and why it transferred. Restore authority only
through the site’s approved sequence and safety procedure. Once the mode finding
is resolved, VFD-0001 through VFD-0004 become interpretable again.
Test Vectors
17 scenarios, clock step 60 s over 1800 s.
| Scenario | Description |
|---|---|
boolean_matrix_enable_false_auto_false_bypass_false | Truth-table case: enable=false, auto=false, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_false_auto_false_bypass_true | Truth-table case: enable=false, auto=false, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_false_auto_true_bypass_false | Truth-table case: enable=false, auto=true, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_false_auto_true_bypass_true | Truth-table case: enable=false, auto=true, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_true_auto_false_bypass_false | Truth-table case: enable=true, auto=false, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_true_auto_false_bypass_true | Truth-table case: enable=true, auto=false, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_true_auto_true_bypass_false | Truth-table case: enable=true, auto=true, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
boolean_matrix_enable_true_auto_true_bypass_true | Truth-table case: enable=true, auto=true, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer. |
bypass_exactly_settle_time | Bypass is active from t=600 through just before t=900. The staged release is applied before the 300-second boundary tick, so no fault asserts. |
local_mode_transient_shorter_than_settle | The enabled drive leaves remote auto for four minutes, then returns before the 300-second settle timer matures. |
transition_into_and_out_of_bypass | Bypass enters at t=600 s, persists long enough to alarm, and clears immediately when bypass proof drops at t=1200 s. |
direct_local_to_bypass_switch_keeps_candidate_true | An enabled drive changes directly from not-auto to bypass at t=600 s. Direction flags swap, but the OR candidate never releases and the already-matured fault remains active. |
enable_release_clears_fault_and_diagnostics | A startup-local drive alarms after settling; removing the final enable at t=900 s drops yFault and both enable-gated direction outputs immediately. |
auto_status_chatter_resets_timer | The enabled drive alternates auto/not-auto every minute. yNotAuto follows the raw mode, but no episode survives the five-minute settle timer. |
enable_drop_resets_timer | A not-auto drive is enabled for four minutes, disabled long enough to clear all flags, then re-enabled. Only the fresh second interval may mature the full timer. |
simultaneous_branches_recover_one_at_a_time | Both direction flags start active. Restoring auto at t=600 clears only yNotAuto; bypass keeps the candidate and fault active until bypass itself clears at t=1200. |
approved_bypass_raw_behavior_host_gated | Approved maintenance bypass has the same raw values as an accidental bypass and therefore alarms after settling. The host must report NO_EVAL during the approved interval. |
vectors.json
{
"schema": "cxf-library/vectors/v1",
"clock": {
"step_s": 60,
"horizon_s": 1800
},
"scenarios": [
{
"name": "boolean_matrix_enable_false_auto_false_bypass_false",
"description": "Truth-table case: enable=false, auto=false, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": false,
"vfd_auto_status": false,
"vfd_bypass_status": false
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "boolean_matrix_enable_false_auto_false_bypass_true",
"description": "Truth-table case: enable=false, auto=false, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": false,
"vfd_auto_status": false,
"vfd_bypass_status": true
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "boolean_matrix_enable_false_auto_true_bypass_false",
"description": "Truth-table case: enable=false, auto=true, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": false,
"vfd_auto_status": true,
"vfd_bypass_status": false
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "boolean_matrix_enable_false_auto_true_bypass_true",
"description": "Truth-table case: enable=false, auto=true, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": false,
"vfd_auto_status": true,
"vfd_bypass_status": true
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "boolean_matrix_enable_true_auto_false_bypass_false",
"description": "Truth-table case: enable=true, auto=false, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": false,
"vfd_bypass_status": false
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": true
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 1800,
"equals": true
}
]
},
{
"name": "boolean_matrix_enable_true_auto_false_bypass_true",
"description": "Truth-table case: enable=true, auto=false, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": false,
"vfd_bypass_status": true
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": true
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 1800,
"equals": true
}
]
},
{
"name": "boolean_matrix_enable_true_auto_true_bypass_false",
"description": "Truth-table case: enable=true, auto=true, bypass=false. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": true,
"vfd_bypass_status": false
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "boolean_matrix_enable_true_auto_true_bypass_true",
"description": "Truth-table case: enable=true, auto=true, bypass=true. Direction outputs are gated by the final enable command, and only an enabled nonautomatic/bypassed drive can mature the timer.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": true,
"vfd_bypass_status": true
},
"expect": [
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 1800,
"equals": true
}
]
},
{
"name": "bypass_exactly_settle_time",
"description": "Bypass is active from t=600 through just before t=900. The staged release is applied before the 300-second boundary tick, so no fault asserts.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": true,
"vfd_bypass_status": [
{
"t": 0,
"value": false
},
{
"t": 600,
"value": true
},
{
"t": 900,
"value": false
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 840,
"equals": false
},
{
"output": "yFault",
"from_s": 960,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 660,
"to_s": 840,
"equals": true
},
{
"output": "yBypass",
"from_s": 960,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "local_mode_transient_shorter_than_settle",
"description": "The enabled drive leaves remote auto for four minutes, then returns before the 300-second settle timer matures.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": [
{
"t": 0,
"value": true
},
{
"t": 600,
"value": false
},
{
"t": 840,
"value": true
}
],
"vfd_bypass_status": false
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
},
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 540,
"equals": false
},
{
"output": "yNotAuto",
"from_s": 660,
"to_s": 780,
"equals": true
},
{
"output": "yNotAuto",
"from_s": 900,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "transition_into_and_out_of_bypass",
"description": "Bypass enters at t=600 s, persists long enough to alarm, and clears immediately when bypass proof drops at t=1200 s.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": true,
"vfd_bypass_status": [
{
"t": 0,
"value": false
},
{
"t": 600,
"value": true
},
{
"t": 1200,
"value": false
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 840,
"equals": false
},
{
"output": "yFault",
"from_s": 960,
"to_s": 1140,
"equals": true
},
{
"output": "yFault",
"from_s": 1260,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 540,
"equals": false
},
{
"output": "yBypass",
"from_s": 660,
"to_s": 1140,
"equals": true
},
{
"output": "yBypass",
"from_s": 1260,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "direct_local_to_bypass_switch_keeps_candidate_true",
"description": "An enabled drive changes directly from not-auto to bypass at t=600 s. Direction flags swap, but the OR candidate never releases and the already-matured fault remains active.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": [
{
"t": 0,
"value": false
},
{
"t": 600,
"value": true
}
],
"vfd_bypass_status": [
{
"t": 0,
"value": false
},
{
"t": 600,
"value": true
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 1800,
"equals": true
},
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 540,
"equals": true
},
{
"output": "yNotAuto",
"from_s": 660,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 540,
"equals": false
},
{
"output": "yBypass",
"from_s": 660,
"to_s": 1800,
"equals": true
}
]
},
{
"name": "enable_release_clears_fault_and_diagnostics",
"description": "A startup-local drive alarms after settling; removing the final enable at t=900 s drops yFault and both enable-gated direction outputs immediately.",
"inputs": {
"vfd_enable_cmd": [
{
"t": 0,
"value": true
},
{
"t": 900,
"value": false
}
],
"vfd_auto_status": false,
"vfd_bypass_status": true
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 840,
"equals": true
},
{
"output": "yFault",
"from_s": 960,
"to_s": 1800,
"equals": false
},
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 840,
"equals": true
},
{
"output": "yNotAuto",
"from_s": 960,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 840,
"equals": true
},
{
"output": "yBypass",
"from_s": 960,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "auto_status_chatter_resets_timer",
"description": "The enabled drive alternates auto/not-auto every minute. yNotAuto follows the raw mode, but no episode survives the five-minute settle timer.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": [
{
"t": 0,
"value": false
},
{
"t": 60,
"value": true
},
{
"t": 120,
"value": false
},
{
"t": 180,
"value": true
},
{
"t": 240,
"value": false
},
{
"t": 300,
"value": true
},
{
"t": 360,
"value": false
},
{
"t": 420,
"value": true
},
{
"t": 480,
"value": false
},
{
"t": 540,
"value": true
},
{
"t": 600,
"value": false
},
{
"t": 660,
"value": true
},
{
"t": 720,
"value": false
},
{
"t": 780,
"value": true
},
{
"t": 840,
"value": false
},
{
"t": 900,
"value": true
},
{
"t": 960,
"value": false
},
{
"t": 1020,
"value": true
},
{
"t": 1080,
"value": false
},
{
"t": 1140,
"value": true
},
{
"t": 1200,
"value": false
},
{
"t": 1260,
"value": true
},
{
"t": 1320,
"value": false
},
{
"t": 1380,
"value": true
},
{
"t": 1440,
"value": false
},
{
"t": 1500,
"value": true
},
{
"t": 1560,
"value": false
},
{
"t": 1620,
"value": true
},
{
"t": 1680,
"value": false
},
{
"t": 1740,
"value": true
}
],
"vfd_bypass_status": false
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "enable_drop_resets_timer",
"description": "A not-auto drive is enabled for four minutes, disabled long enough to clear all flags, then re-enabled. Only the fresh second interval may mature the full timer.",
"inputs": {
"vfd_enable_cmd": [
{
"t": 0,
"value": true
},
{
"t": 240,
"value": false
},
{
"t": 600,
"value": true
}
],
"vfd_auto_status": false,
"vfd_bypass_status": false
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 840,
"equals": false
},
{
"output": "yFault",
"from_s": 960,
"to_s": 1800,
"equals": true
},
{
"output": "yNotAuto",
"from_s": 300,
"to_s": 540,
"equals": false
}
]
},
{
"name": "simultaneous_branches_recover_one_at_a_time",
"description": "Both direction flags start active. Restoring auto at t=600 clears only yNotAuto; bypass keeps the candidate and fault active until bypass itself clears at t=1200.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": [
{
"t": 0,
"value": false
},
{
"t": 600,
"value": true
}
],
"vfd_bypass_status": [
{
"t": 0,
"value": true
},
{
"t": 1200,
"value": false
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 360,
"to_s": 1140,
"equals": true
},
{
"output": "yFault",
"from_s": 1260,
"to_s": 1800,
"equals": false
},
{
"output": "yNotAuto",
"from_s": 0,
"to_s": 540,
"equals": true
},
{
"output": "yNotAuto",
"from_s": 660,
"to_s": 1800,
"equals": false
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1140,
"equals": true
},
{
"output": "yBypass",
"from_s": 1260,
"to_s": 1800,
"equals": false
}
]
},
{
"name": "approved_bypass_raw_behavior_host_gated",
"description": "Approved maintenance bypass has the same raw values as an accidental bypass and therefore alarms after settling. The host must report NO_EVAL during the approved interval.",
"inputs": {
"vfd_enable_cmd": true,
"vfd_auto_status": true,
"vfd_bypass_status": true
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 1800,
"equals": true
},
{
"output": "yBypass",
"from_s": 0,
"to_s": 1800,
"equals": true
}
]
}
]
}