A derived measure is computed by the MCS from other measures of the same component: a
temperature difference, a flag, an average over a window, the time since a condition holds. The
compute stage evaluates it at each update of its inputs and publishes it like any measure, so a
derived measure is used exactly like a measure in expect, check, wait until, requires,
verify and alarms.
components:
tcu:
instances: tcu_id
derived:
ppu_temperature_delta:
type: f32
unit: degC
expr: ppu_output_temperature - ppu_input_temperature
active:
type: bool
expr: mode is not STANDBY
ping_answered:
type: bool
expr: updated_at(responding) > sent_at(ping)
cathode_ready:
type: bool
expr: stable(cathode_temperature > 800 degC, 30 s)
anode_voltage_rate:
type: f32
unit: V/s
expr: rate(anode_voltage, 10 s)Keys#
| Key | Required | Meaning |
|---|---|---|
type | yes | Result type: numeric, bool, bytes, a named enum, or enum with values. |
unit | numeric results | Unit of the result; checked against the unit of expr. |
expr | yes | Pure or temporal expression over the measures and derived measures of the component. |
limits | no | Soft and hard limits, as for a measure: they raise the alarm named after the derived measure. |
description | no | Free text for operators. |
The expression is type-checked against the declared type (catalogue::derived-type-mismatch):
an integer result fits an integer or float type, a decimal result needs a float type, and units
must be compatible (a result in mV fits unit: V).
Scope#
An expression refers by name, without index, to the measures and derived measures of its
component. On a multi-instance component, a derived measure exists per instance and reads the
measures of that instance: tcu[TCU2].active reads tcu[TCU2].mode. Calculations across
components or across targets are not supported.
A derived measure may depend on other derived measures; the dependency graph must be acyclic
(catalogue::derived-cycle), and the compute stage evaluates them in dependency order.
Pure expressions#
| Element | Syntax |
|---|---|
| Numbers | 42, 0.5, 1e-3, with an optional unit: 800 degC, 5 V, 30 s |
| Booleans | true, false |
| Enum values | STANDBY, compared with is |
| Arithmetic | +, -, *, /, unary - |
| Comparisons | <, <=, >, >= |
| Equality | a is b, a is not b, and a is b +/- tolerance for numbers |
| Logic | and, or, not |
| Grouping | ( … ) |
Precedence, from lowest to highest: or, and, not, comparisons and is, + and -, *
and /, unary -.
Units follow dimensional analysis: +, - and comparisons need compatible units (V and
mV), * and / combine them (V / s). A number without unit cannot be added to or compared
with a value that has one (expr::missing-unit): write 5 V, not 5.
Functions (pure):
| Function | Result |
|---|---|
abs(x), floor(x), ceil(x), round(x) | Same type and unit as x |
sqrt(x), exp(x), ln(x), log10(x), asin(x), acos(x), atan(x) | Number without unit; x without unit |
sin(x), cos(x), tan(x) | Number without unit; x an angle (rad, deg) or a plain number |
atan2(a, b) | Angle in rad; a and b in compatible units |
hypot(a, b) | Unit of a; a and b in compatible units |
clamp(x, low, high) | Unit of x; the three in compatible units |
bus_power:
type: f32
unit: W
expr: bus_voltage * bus_current
overcurrent:
type: bool
expr: bus_current > 2.5 A or bus_voltage is 0 V +/- 0.1 VTemporal functions#
Temporal functions form a closed list, built into the language. They take literal windows,
bounded by derived.max_window of the global configuration
(1 h by default; expr::window-too-long above it).
| Function | Result | Meaning |
|---|---|---|
age(x) | Duration | Time since the last real-time sample of x |
updated_at(x) | Instant | Time of the last real-time sample of x |
sent_at(tc) | Instant | Last time the telecommand tc of the same instance reached SENT |
since(cond) | Duration | How long cond has been true without interruption (0 when false) |
stable(cond, d) | bool | Whether cond has been true for at least d |
avg(x, d) | Unit of x | Mean of the samples in the window |
min(x, d), max(x, d) | Unit of x | Extremes of the samples in the window |
delta(x, d) | Unit of x | Last sample minus first sample of the window |
rate(x, d) | Unit of x per second | delta divided by the time between the first and last samples |
changes(x, d) | u64 | Number of value changes in the window |
- The first argument of
age,updated_atand the window functions is a measure or derived measure name, not an expression (expr::wrong-arguments): declare a derived measure for an expression and pass its name. avg,min,max,deltaandrateneed a numeric measure;changesaccepts any.- Instants compare with instants (
updated_at(responding) > sent_at(ping)); instant minus instant is a duration; instant plus or minus a duration is an instant. Durations are numbers in a time unit:age(responding) > 30 s, or a derived measure oftype: f32andunit: s. - Temporal functions are allowed in derived measures and alarm conditions, but not in
requiresandverifyof a telecommand, nor in procedures: declare a derived measure and use it there.
responding_late:
type: bool
expr: age(responding) > 45 s
heater_cycles:
type: u64
expr: changes(heater_on, 10 min)
battery_trend:
type: f32
unit: V
expr: avg(battery_voltage, 5 min)How they are evaluated#
- Sample time. Windows and durations use the time of each sample: on-board time when the driver provides it, else ground reception time.
- Real time only. Temporal functions only see real-time samples: deferred samples (telemetry stored on board and dumped later) never enter their windows nor trigger their evaluation.
- Deadlines. Besides evaluating at each input sample, the compute stage schedules the next
moment the result can change: the end of the duration of
stable, the oldest sample leaving a window, and every second forageandsince.cathode_readybecomes true 30 s after the threshold is crossed, without a new sample. - Unknown value. When a window holds no sample (a loss of signal, for instance), the result
is unknown: it is published once with
value: null, andcheckandrequiresrefuse it like a stale value.rateis unknown with fewer than two samples at different times. - Publication. A temporal derived measure is published only when its value changes, dated at the evaluation time.
- Restart. The compute stage keeps no state of its own: when it meets a target (at start, or
on a new configuration revision) it replays
PARAMSover the longest window of the catalogue, and the telecommand events of that window forsent_at, without republishing anything. Asent_atolder than the window is lost at a restart: derived measures reading it are unknown until the telecommand is sent again. - Determinism. The same samples give the same results: the history of a temporal derived measure contains only its published values.
When a derived measure is computed#
- It is computed again at each update of one of its inputs.
- Its
timeis that of its most recent input; its freshness (ground_time) is that of its oldest input: a derived measure is never fresher than its oldest source. - It is not published while one of its inputs has no value.
- It is deferred when one of its inputs is deferred. A deferred frame computes its pure derived measures from its own values, completed by the last known ones; it never replaces newer values in the compute stage.
The web console shows derived measures with the other measures of a target, marked as derived
(GET /v1/targets/{target}/catalogue, field derived).