🚨 Alerts
An alert is a state machine, ok and firing, nested under the
datapoint it watches. Nesting is what
makes an orphaned alert unrepresentable.
datapoints:
battery_soc:
topic: /battery
type: sensor_msgs/msg/BatteryState
field: percentage
alerts:
low_battery:
name: Battery low
severity: warning
enabled: true
condition: { fire_at: 15, resolve_at: 18 }
| field | type | bounds | absent means |
|---|---|---|---|
condition.fire_at |
number, text or boolean | a number must be finite | required |
condition.resolve_at |
number | finite, must differ from fire_at, allowed only when fire_at is a number |
the condition is an equality |
name |
text | 1 to 120 characters | the bare key is shown instead |
severity |
warning or error |
— | warning |
enabled |
boolean | — | true |
The key is the identity, and name is not. A label can be reworded freely.
Renaming an alert is a delete plus a create: runtime state is lost, and an
alert that is still true fires again on the next sample.
The condition has no discriminator
Whether the condition is an upper threshold, a lower one or an equality follows from the two values in it.
| written | means |
|---|---|
{ fire_at: 80, resolve_at: 75 } |
fires at 80, ok at 75 — an upper threshold |
{ fire_at: 15, resolve_at: 18 } |
fires below 15, ok at 18 — a lower threshold |
{ fire_at: true } |
fires while the value is true |
{ fire_at: 3 } |
fires while the value is exactly 3 |
Without resolve_at it is an equality. It fires while the value equals
fire_at and is ok as soon as it differs, which is the only form a boolean or
a string condition can take.
With resolve_at it is a threshold, and the gap between the two is the
hysteresis. That gap is mandatory: without it a value sitting on the line
oscillates and emits an event on every crossing. A resolve_at equal to
fire_at is refused, and so is a resolve_at on a non-numeric fire_at —
a boolean has two values and text has no ordering to take a direction from.
An equality works on any field, including a Bool or a mode string. Only a
threshold needs a numeric field, because only a threshold compares
magnitudes; one on a text field is refused as requires_numeric_field.
severity changes nothing but the colour
Nothing is escalated, retried or delivered differently. The same event is written, at the same moment, to the same places, and the severity travels with it and colours the alert wherever it is shown.
A resolved alert is the exception worth knowing: its event is always info,
whatever the alert’s own severity. Resolving is good news regardless of how
bad the firing was.
enabled defaults the other way from retention
An alert that is written down watches unless it is explicitly switched off.
That is how one is silenced without losing the key that identifies it, and it
is the opposite of retention.enabled, which is off until you ask for it.
Where an alert goes
Every transition writes an org event, which reaches the robot overview, the
organisation overview and the activity log. The event carries the alert’s
name, the value that crossed and the threshold or expected value it crossed.
No mail is sent. Webhooks will come later and will be how an alert reaches another system.
Runtime state across a publish
Alerts are evaluated in the cloud. They become active on publish and never have an effect on the robot. What happens to a running state depends on what the publish changed.
| case | runtime state |
|---|---|
| the condition is unchanged | kept, with the time it entered that state |
| the condition changed | reset to ok, the last value discarded |
| a new alert | starts ok |
| the key is gone | the alert is deleted, and its state with it |
Only the condition resets the state. name, severity and enabled
deliberately do not: an alert re-enabled mid-firing should still read
firing.
Alerts are one of the three groups a datapoint without a field may not
carry. The other two, and the rest of the datapoint, are on
Datapoints; what a publish does and
does not send to the robot is on the
overview.