Skip to content

Safeties and Protections — Xregulator

What the firmware does when voltage, current, temperature, or RPM exceeds safe limits. Each protection lists its trigger, the action it takes, the variable names you can grep for, whether each variable is user-settable (persisted in Flash via LittleFS) or hardcoded, and the modes where it is active.

The system uses several shared mechanisms (ramp paths, immediate cuts, cooldown lockout). These are defined once below, then referenced by name in each protection.


Notation and Shared Mechanisms

Voltage sensors

Two physical sensors:

  • BatteryV — ADS1115 channel 0. Used only for the cross-sensor disagreement check.
  • IBV — INA228 bus voltage. The signal used by all software protections. getBatteryVoltage() returns IBV.

The INA228 hardware ALERT pin operates on the chip's own internal averaged value (not visible to firmware).

Three shutdown paths

Path Phases When used
Immediate cut applyImmediateCut() — GPIO4 LOW within one loop tick, PWM zeroed, PID reset, sysMode = FAULT. No cooldown. INA228 hardware OV, hard overcurrent, temp critical, RPM below minimum
Warning ramp (MODE_WARNING_RAMP_AND_LOCKOUT) Phase 1: slew duty down to rpmMinDuty at DutyRampRate (50 %/s default). Phase 2: hold for ShutdownPhase2HoldMs (0 = skip). Phase 3: slew to 0 at DutySlowRampRate (1 %/s default). Phase 4: hold at 0 for SettleTimeBeforeCut, then GPIO4 LOW. Then FIELD_COLLAPSE_DELAY cooldown lockout. Alternator hard-shutdown, voltage disagreement warning, temp warning/sustained
Critical ramp (MODE_CRITICAL_RAMP) Same as Warning ramp but skips Phases 1 and 3 entirely — jumps directly to Phase 4. Slew limiter forced off. Same cooldown after Phase 4. Voltage disagreement critical, voltage implausible, temp data stale, current data stale

All ramp parameters (DutyRampRate, DutySlowRampRate, SettleTimeBeforeCut, ShutdownPhase2HoldMs) are user-settable.

Cooldown lockout

FIELD_COLLAPSE_DELAY (user-settable, default 30 s) — held after every Warning- or Critical-ramp shutdown. While held, MODE_LOCKOUT_RAMP is active and no charging attempts. After it elapses the system retries; if the fault is still active a new lockout begins. Not applied to immediate-cut faults.

nearBulk arming gate

All voltage OV throttling protections (Groups 1, 2, 3) and load-dump detection arm only when the active charging target is within ProtectionProxGateV of BulkVoltage:

nearBulk = (ProtectionProxGateV <= 0) OR (ChargingVoltageTarget >= BulkVoltage − ProtectionProxGateV)

User-settable: ProtectionProxGateV (default 0.5 V). Set to 0 to arm always.


Voltage Overvoltage Protections

Eight distinct mechanisms. Group 1/2/3 are throttling protections that keep charging while trimming current. Load dump and Alternator Hard Shutdown ramp the field down. The INA228 hardware ALERT is the final hardware backup. The three voltage-sensor failure protections handle the case where a sensor itself is broken.

Group 1 — Prediction-Based Cap

Predicts where voltage will be TdPred seconds from now and trims the current cap if the prediction overshoots.

  • Trigger: Vpred > ChargingVoltageTarget + OvPredMarginV, where Vpred = IBV + TdPred × max(0, dvdt) and dvdt is EMA-smoothed at DvdtAlpha.
  • Arming guard: IBV > ChargingVoltageTarget − 0.06 V (PRED_GUARD, hardcoded). Prevents firing during ramp-up.
  • Action: fastOvCurrentCap = min(fastOvCurrentCap, setpointLimited − KHard × (Vpred − V_HARD)). Sets fastOvClampActive which forces GOV_BYPASS_SLEW (the duty slew limiter is removed so the output current loop can collapse the field at full speed).
  • dvdt sample window: 1–100 ms between INA samples. Fast mode (~4.3 ms, field on) passes; slow mode (~1054 ms, field off) rejected — dvdt holds last value. Safe because the group only arms while the field is on.
  • User-settable: OvGroup1Enable, TdPred (0.045 s), OvPredMarginV (0.15 V), DvdtAlpha (0.08), KHard (35 A/V — shared with Group 2).
  • Hardcoded: PRED_GUARD = 0.06 V.
  • Active: AUTO, voltageControlActive && nearBulk.

Group 2 — Measured Voltage Threshold

Fires on present measured voltage, no prediction.

  • Trigger: IBV > ChargingVoltageTarget + OvMeasMarginV.
  • Action: fastOvCurrentCap = min(fastOvCurrentCap, setpointLimited − KHard × (IBV − target − OvMeasMarginV)). Same GOV_BYPASS_SLEW side effect.
  • User-settable: OvGroup2Enable, OvMeasMarginV (0.10 V), KHard (35 A/V).
  • Active: AUTO, voltageControlActive && nearBulk.

Group 3 — Current-Based (iExcess Supervisor)

Catches OV before it appears in voltage by watching for excess alternator current at the moment the CV loop should be trimming back.

  • Trigger: measured alternator current exceeds setpointLimited + IExcessK for IExcessN consecutive control ticks (each ~5 ms).
  • Signal source: selected by IExcessSigSrc — MA(N) (default), EMA(TC), or raw. MA window is IExcessMA_N; EMA TC is InputFilterTC (Plant Delay tab).
  • Voltage arming sub-gate: IBV > ChargingVoltageTarget − OvMeasMarginV. Symmetric counterpart to Group 2 — "how close to target before overshoots become serious." One knob (OvMeasMarginV) controls both.
  • Hysteresis on release: stays latched until current falls back to setpointLimited + IExcessK − 2.0 A (IEXCESS_HYST, hardcoded). Prevents on/off chatter.
  • Action on fire: snap cv_I to 0 (default) or proportional bleed at IExcessKBleed × excess A/s. fastOvCurrentCap = max(0, fastOvBaseCap − 1.0 × excess) (cap slope K_IE, hardcoded). Sets fastOvClampActive.
  • Recovery seed: cv_I = preEventCvI × IExcessReseedFrac on release. Prevents bouncing straight back through setpoint.
  • Post-fastOV mismatch gate: after any fastOV event, Group 3 is suppressed until measured current actually falls back to within IExcessK of setpoint — prevents firing on field wind-down current already handled by Group 1/2.
  • User-settable: IExcessK (5 A), IExcessN (3 ticks), IExcessKBleed (0 = snap), IExcessReseedFrac (0.5), IExcessSigSrc (MA(N)), IExcessMA_N.
  • Hardcoded: IEXCESS_HYST = 2.0 A, K_IE = 1.0 A/A.
  • Active: AUTO, voltageControlActive && nearBulk.

Load Dump Detection

Three-tier rate-of-change detection on battery current — catches the upward dBcur/dt spike when loads are switched off.

Tier Threshold Persistence Catches
1 LoadDumpDtThresh1 (4000 A/s) 1 sample Hard-switched FET disconnects
2 LoadDumpDtThresh (1500 A/s) 2 consecutive Standard relay openings
3 LoadDumpDtThresh3 (1000 A/s) 3 consecutive Slow relay-contact disconnects
  • Gate: voltageControlActive && nearBulk && inaFastModeActive (field on, INA fast mode).
  • Action on fire: cv_I = 0, setpointLimited = 0, fastOvCurrentCap = 0, fastOvClampActive set. AW bleed drives recovery.
  • All three thresholds user-settable.
  • Active: AUTO with voltageControlActive.

Alternator Hard Shutdown (absolute)

Software absolute trip on IBV. The only software-layer hard OV shutdown.

  • Trigger: IBV > AlternatorHardShutdownV.
  • Action: MODE_WARNING_RAMP_AND_LOCKOUT. Phase 1 → 3 → 4 ramp, GPIO4 LOW, FIELD_COLLAPSE_DELAY cooldown.
  • Slew-bypass accelerator: if IBV > AlternatorHardShutdownV + 0.5 V (hardcoded), govMode = GOV_BYPASS_SLEW — Phase 1 becomes instant instead of slewing at DutyRampRate.
  • User-settable: AlternatorHardShutdownV. First-boot default auto-scales to BulkVoltage + 0.3 V (12 V system → 14.8 V, 24 V → 29.1 V, 48 V → 57.9 V). Once saved, the value is absolute and never auto-adjusts — re-set manually if you change BulkVoltage system class.
  • Hardcoded: +0.5 V slew-bypass offset.
  • Active: AUTO only. Bypassed in MANUAL and LIMP HOME.

INA228 Hardware ALERT (hardware backup)

The fastest and most direct protection. Operates entirely in hardware — the firmware doesn't need to run.

  • Trigger: INA228 internal averaged bus voltage exceeds VoltageHardwareLimit = BulkVoltage + 0.3 V (auto-tracked; recomputed on every BulkVoltage change).
  • Action: INA228 ALERT pin asserts (active LOW, open-drain) → pulls GPIO4 LOW physically → gate driver loses power → field collapses.
  • Filter: chip is configured SLOW_ALERT. Average length depends on conversion config — fast mode (inaFastModeActive, field on) gives ~4.3 ms cycle; slow mode (field off) gives ~1054 ms. So the hardware is fast when it needs to be and slow when the field is off (where noise margin is irrelevant).
  • Software latch: inaOvervoltageLatched is set by CheckAlarms() (runs every 250 ms; polls for new events every 5 s). Latch is held; release check at 3 s, definitive recheck every 10 s. While latched, selectFieldEventReason() returns REASON_INA_OVERVOLTAGE at Priority 1 and applyImmediateCut() runs every control tick.
  • Post-clear suppression: for INA_OV_DISAGREE_SUPPRESS_MS (10 s, hardcoded) after the latch clears, the voltage-disagreement check is suppressed (sensors diverge naturally as the field collapses).
  • User-settable: indirectly via BulkVoltage. The +0.3 V offset is hardcoded.
  • Hardcoded: +0.3 V offset, INA_OV_DISAGREE_SUPPRESS_MS = 10 s, latch recheck cadences.
  • Active: ALL modes including MANUAL and LIMP HOME. Disabled only when INADisconnected = 1.

Voltage Sensor Failure Protections

These do not detect overvoltage on the battery — they detect that the voltage sensors themselves are giving inconsistent or impossible readings.

Disagreement Warning

  • Trigger: |BatteryV − IBV| > VoltageDisagreeThreshold sustained for VoltageDisagreeTimeout.
  • Action: Warning ramp + 30 s lockout.
  • Suppressed during INA OV latch and for 10 s after clearing.
  • User-settable: VoltageDisagreeThreshold (0.15 V), VoltageDisagreeTimeout (10 s).
  • Active: AUTO only.

Disagreement Critical

  • Trigger: |BatteryV − IBV| > critical_threshold sustained for VoltageDisagreeCriticalTimeoutMs (3 s, hardcoded), or either sensor returns NaN, or either sensor reads < 0.1 V. The NaN/<0.1 V cases fire immediately with no debounce.
  • Critical threshold auto-scales by system class: 1.0 V (12 V), 2.0 V (24 V), 4.0 V (48 V).
  • Action: Critical ramp (Phase 4 direct) + 30 s lockout.
  • User-settable: none — the threshold is auto-scaled, the timeout is hardcoded.
  • Active: AUTO only.

Implausibility

  • Trigger: both BatteryV and IBV are outside the plausible voltage range for the system class (single-sensor implausibility passes — needs both bad).
  • Auto-scaled ranges (0.5 V buffer hardcoded): 12 V → 4.5–15.5 V; 24 V → 9.0–30.5 V; 48 V → 18.0–60.5 V.
  • Action: Critical ramp (Phase 4 direct) + 30 s lockout.
  • Fires immediately — no debounce.
  • User-settable: none. The ranges are auto-derived from BulkVoltage.
  • Active: AUTO only.

Voltage OV — Threshold Cascade (Default 12 V Settings)

What fires when, in order of voltage threshold:

IBV Protection Action
target + 100 mV Group 2 Trim current cap
target + 150 mV predicted Group 1 Trim current cap
target − 100 mV (and current excess) Group 3 (iExcess) Snap cv_I, trim cap
14.8 V (BulkVoltage + 0.3) Alternator Hard Shutdown Warning ramp + 30 s lockout
14.8 V averaged (VoltageHardwareLimit) INA228 hardware ALERT Physical GPIO4 cut (backup; same threshold but uses chip's averaged value)
15.3 V (AlternatorHardShutdownV + 0.5) Slew-bypass accelerator Phase 1 becomes instant
Both sensors > 15.5 V (or < 4.5 V) Implausibility Critical ramp + 30 s lockout

Note: Group 1/2/3 thresholds are relative to ChargingVoltageTarget (which is bulk in bulk, absorption in absorption, etc.), while the hard-shutdown thresholds are absolute and reference BulkVoltage directly.

Co-firing behavior

The hardware ALERT and software hard-shutdown reach the same nominal threshold by design (both BulkVoltage + 0.3 V). The software uses raw IBV; the hardware uses the chip's averaged value. On a fast transient the software fires first; on a slow ramp the hardware catches up. When both fire on the same event, applyImmediateCut() detects gpio4IsLow already, suppresses redundant logging, and only sets the software latch state.


Temperature Protections

All temperature protections use TempToUse (mirrored from AlternatorTemperatureF if TempSource = 0, otherwise temperatureThermistor). Setting IgnoreTemperature = 1 disables T0–T3 below. T4 (TempTask hang) and T5 (data stale) are unaffected.

T0 — Thermal PID (continuous derate)

Subtracts a penalty from the RPM-table current ceiling before the output current loop sees it. Not a fault response — normal steady-state thermal management.

  • PID setpoint: TemperatureLimitF (the actual damage limit).
  • Process variable: projectedTempF = tempFiltered + thermalSlopeFPerSec × ThermalLookaheadSec. Predictive — starts derating before temperature reaches the limit, based on how fast it is rising.
  • Output: thermalPenaltyAmps, asymmetrically slew-limited (ThermalPenaltyRiseRate up, ThermalPenaltyFallRate down). Subtracted from getCapCurrentForRPM(RPM) before clamping to MaxTableValue.
  • Stale-temp safety: if temp is NaN or out of range, the PID halts and holds the last penalty rather than zeroing it. T5 cuts the field if the data stays stale for 20 s.
  • User-settable: TemperatureLimitF (150 °F), ThermalLookaheadSec (90 s), TempPIDKp (3.0 A/°F), TempPIDKi (0.025 A/°F/s), TempPIDFilterAlpha (0.2), TempPIDIntervalMs (5000 ms), ThermalPenaltyRiseRate (60 A/s), ThermalPenaltyFallRate (20 A/s).
  • Active: AUTO only.

T1 — Warning Ramp

  • Trigger: TempToUse > TemperatureLimitF + TempWarnExcess (default 150 + 2 = 152 °F).
  • Action: Warning ramp + 30 s lockout.
  • User-settable: TempWarnExcess (2 °F).
  • Active: AUTO only.

T2 — Sustained Warning

  • Trigger: T1 condition sustained continuously for TempSustainedTimeout (default 120 s). Timer resets when temp drops below the warning threshold.
  • Action: GPIO4 cut after Phase 4 settle. No auto-restart — field stays off until manual re-enable or reboot.
  • User-settable: TempSustainedTimeout (120 s).
  • Active: AUTO only.

T3 — Critical (immediate cut)

  • Trigger: TempToUse > TemperatureLimitF + TempCritExcess (default 150 + 10 = 160 °F).
  • Action: Immediate cut via applyImmediateCut(). No ramp, no cooldown timer.
  • User-settable: TempCritExcess (10 °F).
  • Active: ALL modes including MANUAL. T3 sits at Priority 1 in selectFieldEventReason(), evaluated before the manual-mode check at Priority 2. Manual does not bypass this.

T4 — TempTask Hang

Heartbeat monitor for the OneWire task running on Core 0. Triggers the buzzer unconditionally; field cut is handled by T5 (which fires at the same 20 s threshold when the task hangs).

  • Trigger: millis() − lastTempTaskHeartbeat > TEMP_TASK_TIMEOUT (20 s, hardcoded).
  • Action: tempTaskAlarm = true → GPIO21 buzzer fires regardless of AlarmActivate. Field cut comes from T5 since MARK_FRESH stops being called when the task hangs.
  • Hardcoded: TEMP_TASK_TIMEOUT = 20 s.
  • Active: ALL modes when IgnoreTemperature = 0. Independent of selectFieldEventReason().

T5 — Temperature Data Stale

The primary field-cut for any temperature monitoring failure. Detects 20 s with no validated reading. Handles read errors (CRC fails, sensor disconnects, bad data) and hung-task scenarios.

  • Trigger: any of: dataTimestamps[IDX_ALTERNATOR_TEMP] == 0 && millis() > 60 s; or millis() − tempTimestamp > 20 s; or value is NaN, < −50 °F, or > 400 °F.
  • Action: Critical ramp + 30 s lockout.
  • Recovery: automatic on first valid read after lockout clears.
  • Hardcoded: 20 s staleness window.
  • Active: AUTO only.

Overcurrent Protections

Hard Overcurrent (electronic fuse)

  • Trigger: MeasuredAmps > HardOCTripAmps for HardOCDebounceMs.
  • Action: Immediate cut via applyImmediateCut(). No ramp, no cooldown timer.
  • User-settable: HardOCDebounceMs (20 ms).
  • Auto-derived: HardOCTripAmps = MaxTableValue + 10 A (recomputed when MaxTableValue changes; not persisted).
  • Active: ALL modes including MANUAL. Priority 1 in selectFieldEventReason().

Current Alarms (buzzer only)

Two thresholds that drive the buzzer but never touch the field:

  • Alternator current alarm: MeasuredAmps > CurrentAlarmHigh. User-settable, default 100 A.
  • Battery current alarm: |Bcur| > MaximumAllowedBatteryAmps. User-settable, default 150 A. Bcur is INA228 net battery current, not alternator output.

Both fire via CheckAlarms() if AlarmActivate = 1. Throttled to one console message per 30 s.

Note: The Group 3 iExcess supervisor (under Voltage OV above) is also current-based, but it functions as an OV protection — it fires when current is excessive given the voltage situation. The hard OC trip here fires on absolute current regardless of voltage.


RPM Gate

  • Trigger: RPM < MinRPMForField (when IgnoreRPM = 0).
  • Action: Immediate cut via applyImmediateCut(). No ramp, no cooldown timer.
  • User-settable: MinRPMForField (200 RPM), IgnoreRPM (off by default).
  • Active: ALL modes. Priority 3 in selectFieldControlMode().

System-Level Safeties

Hardware Watchdog

  • Timeout: 16 s (hardcoded in setup via esp_task_wdt_init).
  • Feeds: esp_task_wdt_reset() at the start of every loop() iteration and during long operations (OTA, cloud upload).
  • On timeout: trigger_panic = true → full system reboot. On reboot, all GPIO pins reset LOW, which cuts the field driver.
  • Monitors: Core 1 (main task) only.

Alarm Buzzer (GPIO21)

Driven by CheckAlarms() every 250 ms. Aggregates all alarm conditions into one GPIO. Independent of field control — the buzzer has no authority over GPIO4.

Condition Threshold Gated by AlarmActivate?
High alternator temperature TempAlarm (190 °F) Yes
Low alternator temperature TempAlarmLow (32 °F, 0 = off) Yes
High battery voltage VoltageAlarmHigh (15 V) Yes
Low battery voltage VoltageAlarmLow (11 V, > 8 V floor) Yes
High alternator current CurrentAlarmHigh (100 A) Yes
High battery current MaximumAllowedBatteryAmps (150 A) Yes
INA228 hardware OV latch Any INA OV event Yes
Temp sensor not responding No valid read for > 20 s Yes
Efficiency anomaly effAnomalyAlarmActive Yes
TempTask hung TempTask heartbeat > 20 s No — always fires

AlarmLatchEnabled — if set, buzzer stays on after the condition clears until ResetAlarmLatch.


Quick Reference — User-Settable Parameters

Voltage OV throttling (Voltage tab):

Parameter Default Purpose
OvGroup1Enable / OvGroup2Enable on / on Per-group enables
OvPredMarginV 0.150 V Group 1 trigger
OvMeasMarginV 0.100 V Group 2 trigger + Group 3 voltage gate
KHard 35 A/V Group 1+2 cap slope
TdPred 0.045 s Group 1 prediction horizon
DvdtAlpha 0.08 Group 1 dvdt EMA alpha
IExcessK 5 A Group 3 current threshold above setpoint
IExcessN 3 ticks Group 3 persistence
IExcessKBleed 0 (snap) Group 3 integrator response
IExcessReseedFrac 0.5 Group 3 recovery seed
IExcessSigSrc / IExcessMA_N MA(N) / — Group 3 signal filter
LoadDumpDtThresh1/2/3 4000 / 1500 / 1000 A/s Load dump three-tier
ProtectionProxGateV 0.5 V nearBulk gate width
AwBleedRate / AwRecoverRate / AwSeedProtectMs 2.0 / 0.1 / 150 ms AW integrator behavior
SlopeBleedThresh / SlopeBleedK / SlopeBleedProxV 0.5 V/s / 50 / 0.5 V Slope-bleed integrator drain

Voltage shutdown / sensor health (Battery tab → Safety):

Parameter Default Purpose
AlternatorHardShutdownV BulkVoltage + 0.3 V on first boot Software absolute trip
VoltageDisagreeThreshold 0.15 V Warning trigger
VoltageDisagreeTimeout 10 s Warning debounce
FIELD_COLLAPSE_DELAY 30 s Cooldown lockout
SettleTimeBeforeCut 1000 ms Phase 4 settle

Shutdown ramp shape (Tuning → Current → Rate Limiting):

Parameter Default Purpose
DutyRampRate 50 %/s Phase 1 duty slew
DutySlowRampRate 1 %/s Phase 3 duty slew
ShutdownPhase2HoldMs 0 (skip) Phase 2 hold

Temperature (Temperature tab):

Parameter Default Purpose
TemperatureLimitF 150 °F PID setpoint and base for all thresholds
TempWarnExcess 2 °F T1 warning (152 °F)
TempCritExcess 10 °F T3 immediate cut (160 °F)
TempSustainedTimeout 120 s T2 escalation timer
TempAlarm / TempAlarmLow 190 °F / 32 °F Buzzer thresholds
ThermalLookaheadSec 90 s Predictive PID horizon
TempPIDKp / TempPIDKi 3.0 / 0.025 Temperature PID gains
TempPIDFilterAlpha 0.2 Temp input EMA
TempPIDIntervalMs 5000 ms Temp PID compute period
ThermalPenaltyRiseRate / FallRate 60 / 20 A/s Penalty slew limits
IgnoreTemperature 0 Disable T0–T3, T5

Overcurrent / RPM:

Parameter Default Purpose
HardOCDebounceMs 20 ms Hard OC debounce
CurrentAlarmHigh 100 A Alternator current alarm
MaximumAllowedBatteryAmps 150 A Battery current alarm
MinRPMForField 200 RPM RPM gate
IgnoreRPM 0 Disable RPM gate

Quick Reference — Hardcoded Constants

Constant Value Where
PRED_GUARD 0.06 V Group 1 arming dead-band
IEXCESS_HYST 2.0 A Group 3 release hysteresis
K_IE 1.0 A/A Group 3 cap slope
AlternatorHardShutdownV slew-bypass +0.5 V Phase 1 instant when this far above threshold
INA228 hardware OV offset +0.3 V above BulkVoltage VoltageHardwareLimit
INA_OV_DISAGREE_SUPPRESS_MS 10 s Disagreement check suppress after INA OV clears
INA228 latch recheck cadences 3 s / 10 s / 5 s Early/definitive/new-event poll
VoltageDisagreeCriticalTimeoutMs 3 s Critical disagreement debounce
Critical disagreement threshold 1 / 2 / 4 V Auto-scaled by system class
Implausibility plausible range buffer 0.5 V Buffer around system class min/max
TEMP_TASK_TIMEOUT 20 s Heartbeat deadline
Temp staleness window 20 s T5 trigger
HardOCTripAmps offset +10 A above MaxTableValue Auto-derived
Watchdog timeout 16 s Hardware reset on hang
BULK_V_BAND 50 mV below BulkVoltage Bulk→Absorption arming band
KiDown multiplier 7 × VoltageKi CV PI asymmetric unwind
CV_HIGH_DEADBAND_V 25 mV CV tuning HIGH-phase overshoot dead-band

Mode-by-Mode Active Protections

Protection AUTO MANUAL LIMP HOME
Group 1 / 2 / 3 (Voltage OV throttling) Active (CV stages, nearBulk)
Load Dump Active
Alternator Hard Shutdown Active
INA228 hardware ALERT (physical) Active Active Active
INA228 software latch Active Active (Priority 1) Active (pre-gate, before LIMP path)
Voltage disagreement (warning + critical) Active
Voltage implausible Active
T0 thermal PID derate Active
T1 / T2 warning + sustained Active
T3 critical immediate cut Active Active (Priority 1)
T4 TempTask hang alarm Active Active Active
T5 temp data stale Active
Hard overcurrent Active Active (Priority 1)
RPM gate Active Active
Watchdog reboot Active Active Active
Buzzer If AlarmActivate = 1 (TempTask hang always) Same Same

"—" means the priority hierarchy in selectFieldControlMode() returns MODE_NORMAL_MANUAL (Priority 2) or LIMP HOME bypasses the protection chain entirely, before the listed protection is evaluated.