# Alarms

> Trigger warnings and notifications when sensor values exceed limits

Alarms notify operators when values exceed acceptable limits. A pressure that crosses 600 psi should trigger a warning. A temperature that stays above 300°C for more than 5 seconds should sound an alarm. Arc makes these patterns straightforward.

## Basic threshold

The simplest alarm compares a value to a threshold.

```arc
tank_level < 10.0 -> status.set{ ... }
tank_pressure > 9000.0 -> status.set{ ... }
```

## Time-delayed

A brief spike might be acceptable, but a sustained high value indicates a real problem. Debounce the condition with [`stable.for`](https://docs.synnaxlabs.com/reference/control/arc/reference/standard-library/stable) so the alarm fires only after it holds:

```arc
tank_pressure > 600 -> stable.for{duration=1s} => alert_hq
```

## Deadband

Simple threshold alarms chatter when values oscillate near the limit. If pressure hovers around 600 psi, the alarm toggles on and off rapidly. A deadband prevents this by requiring the value to cross back below a lower threshold before the alarm clears:

```arc
func deadband_alarm{
    high f64, // alarm activates above this
    low f64,  // alarm clears below this
} (value f64) bool {
    alarm_active $= false

    if not alarm_active {
        // Alarm is off, check to activate
        if value > high {
            alarm_active = true
        }
    } else {
        // Alarm is on, check to clear
        if value < low {
            alarm_active = false
        }
    }

    return alarm_active
}

tank_pressure -> deadband_alarm{high=600.0, low=580.0} -> pressure_high
```

The alarm activates when pressure exceeds 600 psi. It only clears when pressure drops below 580 psi. This 20 psi deadband eliminates chatter.

> Choose your deadband based on expected noise and process dynamics. Too narrow and you still get chatter. Too wide and the alarm takes too long to clear.

## High and low with deadband

Many sensors need both high and low alarms. Create separate functions for each:

```arc
func low_deadband_alarm{
    low f64,   // alarm activates below this
    clear f64, // alarm clears above this
} (value f64) bool {
    state $= false

    if not state {
        if value < low {
            state = true
        }
    } else {
        if value > clear {
            state = false
        }
    }

    return state
}
// Use separate flows for high and low alarms
tank_pressure -> deadband_alarm{high=600.0, low=580.0} -> pressure_high
tank_pressure -> low_deadband_alarm{low=100.0, clear=120.0} -> pressure_low
```

## Multi-condition

Some alarms require multiple conditions. An abort alarm might trigger when both inlet and outlet pressures are elevated (potential runaway), or when any single pressure exceeds a critical limit:

```arc
func combined_alarm{
    inlet chan f64,
    outlet chan f64,
    inlet_crit f64,
    outlet_crit f64,
    combined_inlet f64,
    combined_outlet f64,
} () bool {
    p1 := inlet
    p2 := outlet

    // Critical: either exceeds maximum
    critical := p1 > inlet_crit or p2 > outlet_crit
    // Combined: both elevated (potential runaway)
    combined := p1 > combined_inlet and p2 > combined_outlet

    return critical or combined
}

time.interval{period=50ms} -> combined_alarm{
    inlet = inlet_pressure,
    outlet = outlet_pressure,
    inlet_crit = 800.0,
    outlet_crit = 600.0,
    combined_inlet = 500.0,
    combined_outlet = 400.0
} -> abort_trigger
```

## Latching

Some alarms should stay active until manually acknowledged, even if the condition clears:

```arc
func latching_alarm{
    limit f64,
    ack chan bool, // acknowledgment channel (write true to clear)
} (value f64) bool {
    latched $= false

    // Check for acknowledgment
    ack_signal := ack
    if ack_signal {
        latched = false
    }

    // Activate on threshold
    if value > limit {
        latched = true
    }

    return latched
}

tank_pressure -> latching_alarm{limit=600.0, ack=alarm_ack} -> latched_alarm
```

Wire `alarm_ack` to a button in your Console schematic. The alarm stays active until the operator acknowledges it by clicking the button.

## Priority levels

The simplest form maps a reading to a priority level and logs it:

```arc
func classify{warn f64, critical f64} (value f64) str {
    if value >= critical {
        return "Critical"
    }
    if value >= warn {
        return "Warning"
    }
    return ""
}

// "" is falsy, so => never fires for nominal readings
tank_pressure -> classify{warn=600.0, critical=700.0} => log_channel
```

When each level warrants a different response, demux the value with a [custom routing table](https://docs.synnaxlabs.com/reference/control/arc/reference/flow#custom-routing). A warning sets the status, and a critical also triggers the abort stage:

```arc
import status

func prioritize{
    warn f64,
    crit f64,
} (value f64) (nominal bool, warning bool, critical bool) {
    if value < warn {
        nominal = true
    } else if value < crit {
        warning = true
    } else {
        critical = true
    }
}

stage monitor {
    tank_pressure -> prioritize{warn=600.0, crit=700.0} -> {
        // nominal: no-op
        warning:  "Warning" -> status.set{ ... },
        critical: sequence {
            "Critical" -> status.set{ ... },
            true => abort
        }
    }
}
```

## Alarms in sequences

Alarms are often used to trigger sequence transitions:

```arc
sequence main {
    stage pressurize {
        // Safety abort conditions (listed first for priority)
        tank_pressure > 700 => abort
        outlet_pressure > 500 => abort
        abort_btn => abort

        // Normal completion
        true -> valve_cmd
        tank_pressure > 500 => next
    }

    stage hold {
        // Maintain pressure, watch for problems
        tank_pressure -> deadband_alarm{high=600.0, low=580.0} -> pressure_high
        pressure_high => abort
        time.wait{duration=30s} => next
    }

    stage complete {
        false -> valve_cmd
    }
}

sequence abort {
    stage safed {
        false -> valve_cmd
        false -> vent_valve
    }
}

start_cmd => main
```

> Always list safety-critical abort conditions first in a stage. When multiple conditional transitions (`=>`) are truthy in the same cycle, the first one wins.
